/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef LABWC_VIEW_H
#define LABWC_VIEW_H

#include <stdbool.h>
#include <stdint.h>
#include <wayland-util.h>
#include <wlr/util/box.h>
#include <xkbcommon/xkbcommon.h>
#include "common/edge.h"
#include "config.h"
#include "config/types.h"

/*
 * Default minimal window size. Clients can explicitly set smaller values via
 * e.g. xdg_toplevel::set_min_size.
 */
#define LAB_MIN_VIEW_WIDTH 100
#define LAB_MIN_VIEW_HEIGHT 60

/*
 * Fallback view geometry used in some cases where a better position
 * and/or size can't be determined. Try to avoid using these except as
 * a last resort.
 */
#define VIEW_FALLBACK_X 100
#define VIEW_FALLBACK_Y 100
#define VIEW_FALLBACK_WIDTH  640
#define VIEW_FALLBACK_HEIGHT 480

/*
 * In labwc, a view is a container for surfaces which can be moved around by
 * the user. In practice this means XDG toplevel and XWayland windows.
 */
enum view_type {
	LAB_XDG_SHELL_VIEW,
#if HAVE_XWAYLAND
	LAB_XWAYLAND_VIEW,
#endif
};

enum ssd_preference {
	LAB_SSD_PREF_UNSPEC = 0,
	LAB_SSD_PREF_CLIENT,
	LAB_SSD_PREF_SERVER,
};

/**
 * Directions in which a view can be maximized. "None" is used
 * internally to mean "not maximized" but is not valid in rc.xml.
 * Therefore when parsing rc.xml, "None" means "Invalid".
 */
enum view_axis {
	VIEW_AXIS_NONE = 0,
	VIEW_AXIS_HORIZONTAL = (1 << 0),
	VIEW_AXIS_VERTICAL = (1 << 1),
	VIEW_AXIS_BOTH = (VIEW_AXIS_HORIZONTAL | VIEW_AXIS_VERTICAL),
	/*
	 * If view_axis is treated as a bitfield, INVALID should never
	 * set the HORIZONTAL or VERTICAL bits.
	 */
	VIEW_AXIS_INVALID = (1 << 2),
};

enum view_wants_focus {
	/* View does not want focus */
	VIEW_WANTS_FOCUS_NEVER = 0,
	/* View wants focus */
	VIEW_WANTS_FOCUS_ALWAYS,
	/*
	 * The following values apply only to XWayland views using the
	 * Globally Active input model per the ICCCM. These views are
	 * offered focus and will voluntarily accept or decline it.
	 *
	 * In some cases, labwc needs to decide in advance whether to
	 * focus the view. For this purpose, these views are classified
	 * (by a heuristic) as likely or unlikely to want focus. However,
	 * it is still ultimately up to the client whether the view gets
	 * focus or not.
	 */
	VIEW_WANTS_FOCUS_LIKELY,
	VIEW_WANTS_FOCUS_UNLIKELY,
};

enum view_layer {
	VIEW_LAYER_NORMAL = 0,
	VIEW_LAYER_ALWAYS_ON_TOP,
	VIEW_LAYER_ALWAYS_ON_BOTTOM,
};

struct view;
struct wlr_surface;
struct foreign_toplevel;

/* Common to struct view and struct xwayland_unmanaged */
struct mappable {
	bool connected;
	struct wl_listener map;
	struct wl_listener unmap;
};

/* Basic size hints (subset of XSizeHints from X11) */
struct view_size_hints {
	int min_width;
	int min_height;
	int width_inc;
	int height_inc;
	int base_width;
	int base_height;
};

struct view_impl {
	void (*configure)(struct view *view, struct wlr_box geo);
	void (*close)(struct view *view);
	void (*set_activated)(struct view *view, bool activated);
	void (*set_fullscreen)(struct view *view, bool fullscreen);
	void (*notify_tiled)(struct view *view);
	void (*maximize)(struct view *view, enum view_axis maximized);
	void (*minimize)(struct view *view, bool minimize);
	struct view *(*get_parent)(struct view *self);
	struct view *(*get_root)(struct view *self);
	void (*append_children)(struct view *self, struct wl_array *children);
	bool (*is_modal_dialog)(struct view *self);
	struct view_size_hints (*get_size_hints)(struct view *self);
	/* if not implemented, VIEW_WANTS_FOCUS_ALWAYS is assumed */
	enum view_wants_focus (*wants_focus)(struct view *self);
	void (*offer_focus)(struct view *self);
	/* returns true if view reserves space at screen edge */
	bool (*has_strut_partial)(struct view *self);
	/* returns true if view declared itself a window type */
	bool (*contains_window_type)(struct view *view,
		enum lab_window_type window_type);
	/* returns the client pid that this view belongs to */
	pid_t (*get_pid)(struct view *view);
};

struct view {
	enum view_type type;
	const struct view_impl *impl;
	struct wl_list link;

	/* This is cleared when the view is not in the cycle list */
	struct wl_list cycle_link;

	/*
	 * The primary output that the view is displayed on. Specifically:
	 *
	 *  - For floating views, this is the output nearest to the
	 *    center of the view. It is computed automatically when the
	 *    view is moved or the output layout changes.
	 *
	 *  - For fullscreen/maximized/tiled views, this is the output
	 *    used to compute the view's geometry. The view remains on
	 *    the same output unless it is disabled or disconnected.
	 *
	 * Many view functions (e.g. view_center(), view_fullscreen(),
	 * view_maximize(), etc.) allow specifying a particular output
	 * by calling view_set_output() beforehand.
	 */
	struct output *output;

	/*
	 * The outputs that the view is displayed on.
	 * This is used to notify the foreign toplevel
	 * implementation and to update the SSD invisible
	 * resize area.
	 * It is a bitset of output->id_bit.
	 */
	uint64_t outputs;

	struct workspace *workspace;
	struct wlr_surface *surface;
	struct wlr_scene_tree *scene_tree;
	struct wlr_scene_tree *content_tree; /* may be NULL for unmapped view */

	/* These are never NULL and an empty string is set instead. */
	char *title;
	char *app_id; /* WM_CLASS for xwayland windows */

	struct {
		struct wlr_scene *scene;
		struct wlr_ext_image_capture_source_v1 *source;
		struct wl_listener on_capture_source_destroy;
	} capture;

	bool mapped;
	bool been_mapped;
	uint64_t creation_id;
	enum lab_ssd_mode ssd_mode;
	enum ssd_preference ssd_preference;
	bool shaded;
	bool minimized;
	bool was_minimized_by_show_desktop_action;
	enum view_axis maximized;
	bool fullscreen;
	bool tearing_hint;
	enum lab_tristate force_tearing;
	enum view_layer layer;
	bool visible_on_all_workspaces;
	enum lab_edge tiled;
	enum lab_edge edges_visible;
	bool inhibits_keybinds; /* also inhibits mousebinds */
	xkb_layout_index_t keyboard_layout;

	/* Pointer to an output owned struct region, may be NULL */
	struct region *tiled_region;
	/* Set to region->name when tiled_region is free'd by a destroying output */
	char *tiled_region_evacuate;

	/*
	 * Geometry of the wlr_surface contained within the view, as
	 * currently displayed. Should be kept in sync with the
	 * scene-graph at all times.
	 */
	struct wlr_box current;
	/*
	 * Expected geometry after any pending move/resize requests
	 * have been processed. Should match current geometry when no
	 * move/resize requests are pending.
	 */
	struct wlr_box pending;
	/*
	 * Saved geometry which will be restored when the view returns
	 * to normal/floating state after being maximized/fullscreen/
	 * tiled. Values are undefined/out-of-date when the view is not
	 * maximized/fullscreen/tiled.
	 */
	struct wlr_box natural_geometry;
	/*
	 * last_placement represents the last view position set by the user.
	 * output_name and relative_geo are used to keep or restore the view
	 * position relative to the output and layout_geo is used to keep the
	 * global position when the output is lost.
	 */
	struct {
		char *output_name;
		/* view geometry in output-relative coordinates */
		struct wlr_box relative_geo;
		/* view geometry in layout coordinates */
		struct wlr_box layout_geo;
	} last_placement;
	/* Set temporarily when moving view due to layout change */
	bool adjusting_for_layout_change;

	/* used by xdg-shell views */
	uint32_t pending_configure_serial;
	struct wl_event_source *pending_configure_timeout;

	struct ssd *ssd;
	struct resize_indicator {
		int width, height;
		struct wlr_scene_tree *tree;
		struct wlr_scene_rect *border;
		struct wlr_scene_rect *background;
		struct scaled_font_buffer *text;
	} resize_indicator;
	struct resize_outlines {
		struct wlr_box view_geo;
		struct lab_scene_rect *rect;
	} resize_outlines;

	struct mappable mappable;

	struct wl_listener destroy;
	struct wl_listener commit;
	struct wl_listener request_move;
	struct wl_listener request_resize;
	struct wl_listener request_minimize;
	struct wl_listener request_maximize;
	struct wl_listener request_fullscreen;
	struct wl_listener set_title;

	struct foreign_toplevel *foreign_toplevel;

	/* used by scaled_icon_buffer */
	struct {
		char *name;
		struct wl_array buffers; /* struct lab_data_buffer * */
	} icon;

	struct {
		struct wl_signal new_app_id;
		struct wl_signal new_title;
		struct wl_signal new_outputs;
		struct wl_signal maximized;
		struct wl_signal minimized;
		struct wl_signal fullscreened;
		struct wl_signal activated;     /* bool *activated */
		struct wl_signal always_on_top;
		/*
		 * This is emitted when app_id, or icon set via xdg_toplevel_icon
		 * is updated. This is listened by scaled_icon_buffer.
		 */
		struct wl_signal set_icon;
		struct wl_signal destroy;
	} events;
};

struct view_query {
	struct wl_list link;
	char *identifier;
	char *title;
	enum lab_window_type window_type;
	char *sandbox_engine;
	char *sandbox_app_id;
	enum lab_tristate shaded;
	enum view_axis maximized;
	enum lab_tristate iconified;
	enum lab_tristate focused;
	enum lab_tristate omnipresent;
	enum lab_edge tiled;
	char *tiled_region;
	char *desktop;
	enum lab_ssd_mode decoration;
	char *monitor;
};

struct xdg_toplevel_view {
	struct view base;
	struct wlr_xdg_surface *xdg_surface;

	/* Optional black background fill behind fullscreen view */
	struct wlr_scene_rect *fullscreen_bg;

	/* Events unique to xdg-toplevel views */
	struct wl_listener set_app_id;
	struct wl_listener request_show_window_menu;
	struct wl_listener set_parent;
	struct wl_listener new_popup;
};

/**
 * view_from_wlr_surface() - returns the view associated with a
 * wlr_surface, or NULL if the surface has no associated view.
 */
struct view *view_from_wlr_surface(struct wlr_surface *surface);

/**
 * view_query_create() - Create a new heap allocated view query with
 * all members initialized to their default values (window_type = -1,
 * NULL for strings)
 */
struct view_query *view_query_create(void);

/**
 * view_query_free() - Free a given view query
 * @query: Query to be freed.
 */
void view_query_free(struct view_query *view);

/**
 * view_matches_query() - Check if view matches the given criteria
 * @view: View to checked.
 * @query: Criteria to match against.
 *
 * Returns true if %view matches all of the criteria given in %query, false
 * otherwise.
 */
bool view_matches_query(struct view *view, struct view_query *query);

/**
 * for_each_view() - iterate over all views which match criteria (front to back)
 * @view: Iterator.
 * @head: Head of list to iterate over.
 * @criteria: Criteria to match against.
 * Example:
 *	struct view *view;
 *	for_each_view(view, &server.views, LAB_VIEW_CRITERIA_NONE) {
 *		printf("%s\n", view_get_string_prop(view, "app_id"));
 *	}
 */
#define for_each_view(view, head, criteria)           \
	for (view = view_next(head, NULL, criteria);  \
	     view;                                    \
	     view = view_next(head, view, criteria))

/**
 * for_each_view_reverse() - iterate over all views which match criteria (back to front)
 * @view: Iterator.
 * @head: Head of list to iterate over.
 * @criteria: Criteria to match against.
 * Example:
 *	struct view *view;
 *	for_each_view_reverse(view, &server.views, LAB_VIEW_CRITERIA_NONE) {
 *		printf("%s\n", view_get_string_prop(view, "app_id"));
 *	}
 */
#define for_each_view_reverse(view, head, criteria)   \
	for (view = view_prev(head, NULL, criteria);  \
	     view;                                    \
	     view = view_prev(head, view, criteria))

/**
 * view_next() - Get next view which matches criteria.
 * @head: Head of list to iterate over.
 * @view: Current view from which to find the next one. If NULL is provided as
 *	  the view argument, the start of the list will be used.
 * @criteria: Criteria to match against.
 *
 * Returns NULL if there are no views matching the criteria.
 */
struct view *view_next(struct wl_list *head, struct view *view,
	enum lab_view_criteria criteria);

/**
 * view_prev() - Get previous view which matches criteria.
 * @head: Head of list to iterate over.
 * @view: Current view from which to find the previous one. If NULL is provided
 *        as the view argument, the end of the list will be used.
 * @criteria: Criteria to match against.
 *
 * Returns NULL if there are no views matching the criteria.
 */
struct view *view_prev(struct wl_list *head, struct view *view,
	enum lab_view_criteria criteria);

/**
 * view_array_append() - Append views that match criteria to array
 * @server: server context
 * @views: arrays to append to
 * @criteria: criteria to match against
 *
 * This function is useful in cases where the calling function may change the
 * stacking order or where it needs to iterate over the views multiple times,
 * for example to get the number of views before processing them.
 *
 * Note: This array has a very short shelf-life so it is intended to be used
 *       with a single-use-throw-away approach.
 *
 * Example usage:
 *	struct view **view;
 *	struct wl_array views;
 *	wl_array_init(&views);
 *	view_array_append(&views, LAB_VIEW_CRITERIA_CURRENT_WORKSPACE);
 *	wl_array_for_each(view, &views) {
 *		// Do something with *view
 *	}
 *	wl_array_release(&views);
 */
void view_array_append(struct wl_array *views,
	enum lab_view_criteria criteria);

enum view_wants_focus view_wants_focus(struct view *view);

/* If view is NULL, the size of SSD is not considered */
struct wlr_box view_get_edge_snap_box(struct view *view, struct output *output,
	enum lab_edge edge);
struct wlr_box view_get_region_snap_box(struct view *view, struct region *region);

/**
 * view_is_focusable() - Check whether or not a view can be focused
 * @view: view to be checked
 *
 * The purpose of this test is to filter out views (generally Xwayland) which
 * are not meant to be focused such as those with surfaces
 *	a. that have been created but never mapped;
 *	b. set to NULL after client minimize-request.
 *
 * The only views that are allowed to be focused are those that have a surface
 * and have been mapped at some point since creation.
 */
bool view_is_focusable(struct view *view);

/*
 * For use by desktop_focus_view() only - please do not call directly.
 * See the description of VIEW_WANTS_FOCUS_OFFER for more information.
 */
void view_offer_focus(struct view *view);

struct wlr_box view_get_edge_snap_box(struct view *view, struct output *output,
	enum lab_edge edge);

void mappable_connect(struct mappable *mappable, struct wlr_surface *surface,
	wl_notify_func_t notify_map, wl_notify_func_t notify_unmap);
void mappable_disconnect(struct mappable *mappable);

void view_toggle_keybinds(struct view *view);
bool view_inhibits_actions(struct view *view, struct wl_list *actions);

void view_set_activated(struct view *view, bool activated);
void view_set_output(struct view *view, struct output *output);
void view_close(struct view *view);

/**
 * view_move_resize - resize and move view
 * @view: view to be resized and moved
 * @geo: the new geometry
 * NOTE: Only use this when the view actually changes width and/or height
 * otherwise the serials might cause a delay in moving xdg-shell clients.
 * For move only, use view_move()
 */
void view_move_resize(struct view *view, struct wlr_box geo);
void view_resize_relative(struct view *view,
	int left, int right, int top, int bottom);
void view_move_relative(struct view *view, int x, int y);
void view_move(struct view *view, int x, int y);
void view_moved(struct view *view);
void view_minimize(struct view *view, bool minimized);
bool view_compute_centered_position(struct view *view,
	const struct wlr_box *ref, int w, int h, int *x, int *y);
struct wlr_box view_get_fallback_natural_geometry(struct view *view);
void view_store_natural_geometry(struct view *view);

/**
 * view_apply_natural_geometry - adjust view->natural_geometry if it doesn't
 * intersect with view->output and then apply it
 */
void view_apply_natural_geometry(struct view *view);

/**
 * view_effective_height - effective height of view, with respect to shaded state
 * @view: view for which effective height is desired
 * @use_pending: if false, report current height; otherwise, report pending height
 */
int view_effective_height(struct view *view, bool use_pending);

/**
 * view_center - center view within some region
 * @view: view to be centered
 * @ref: optional reference region (in layout coordinates) to center
 * within; if NULL, view is centered within usable area of its output
 */
void view_center(struct view *view, const struct wlr_box *ref);

/**
 * view_compute_position_by_policy() - compute view placement
 * @view: view to be placed
 * @geom: floating view geometry to update (in/out)
 * @allow_cursor: set to false to ignore center-on-cursor policy
 * @policy: placement policy to apply
 *
 * Computes floating view placement according to configured strategy.
 * Unlike view_place_by_policy(), this function doesn't actually move
 * the view. It returns false if position could not be computed (e.g.
 * if no outputs are connected). In that case, @geom is not modified.
 */
bool view_compute_position_by_policy(struct view *view, struct wlr_box *geom,
	bool allow_cursor, enum lab_placement_policy policy);

/**
 * view_place_by_policy - apply placement strategy to view
 * @view: view to be placed
 * @allow_cursor: set to false to ignore center-on-cursor policy
 * @policy: placement policy to apply
 *
 * Places a floating view according to configured placement strategy.
 * Clears any maximized/fullscreen/tiled state and restores natural
 * geometry of the view before positioning.
 */
void view_place_by_policy(struct view *view, bool allow_cursor,
	enum lab_placement_policy policy);
void view_constrain_size_to_that_of_usable_area(struct view *view);

void view_set_maximized(struct view *view, enum view_axis maximized);
void view_set_untiled(struct view *view);
void view_maximize(struct view *view, enum view_axis axis);
void view_set_fullscreen(struct view *view, bool fullscreen);
void view_toggle_maximize(struct view *view, enum view_axis axis);
bool view_wants_decorations(struct view *view);
void view_toggle_decorations(struct view *view);

void view_set_layer(struct view *view, enum view_layer layer);
void view_toggle_always_on_top(struct view *view);
void view_toggle_always_on_bottom(struct view *view);
void view_toggle_visible_on_all_workspaces(struct view *view);

bool view_is_tiled(struct view *view);
bool view_is_tiled_and_notify_tiled(struct view *view);
bool view_is_floating(struct view *view);
void view_move_to_workspace(struct view *view, struct workspace *workspace);
bool view_titlebar_visible(struct view *view);
void view_set_ssd_mode(struct view *view, enum lab_ssd_mode mode);
void view_set_decorations(struct view *view, enum lab_ssd_mode mode, bool force_ssd);
void view_toggle_fullscreen(struct view *view);

/*
 * Saves the window position in view->last_placement. This should be called
 * when a view is first mapped or manually moved by the user.
 */
void view_save_last_placement(struct view *view);
/* Restores and adjusts the view's position from the view->last_placement */
void view_adjust_for_layout_change(struct view *view);

void view_move_to_edge(struct view *view, enum lab_edge direction, bool snap_to_windows);
void view_grow_to_edge(struct view *view, enum lab_edge direction);
void view_shrink_to_edge(struct view *view, enum lab_edge direction);
void view_snap_to_edge(struct view *view, enum lab_edge direction,
	bool across_outputs, bool combine);
void view_snap_to_region(struct view *view, struct region *region);
void view_move_to_output(struct view *view, struct output *output);

void view_move_to_front(struct view *view);
void view_move_to_back(struct view *view);

bool view_is_modal_dialog(struct view *view);

/**
 * view_get_modal_dialog() - returns any modal dialog found among this
 * view's children or siblings (or possibly this view itself). Applies
 * only to xwayland views and always returns NULL for xdg-shell views.
 */
struct view *view_get_modal_dialog(struct view *view);

bool view_on_output(struct view *view, struct output *output);

/**
 * view_has_strut_partial() - returns true for views that reserve space
 * at a screen edge (e.g. panels). These views are treated as if they
 * have the fixedPosition window rule: i.e. they are not restricted to
 * the usable area and cannot be moved/resized interactively.
 */
bool view_has_strut_partial(struct view *view);

void view_set_title(struct view *view, const char *title);
void view_set_app_id(struct view *view, const char *app_id);
void view_reload_ssd(struct view *view);

void view_set_shade(struct view *view, bool shaded);

/* Icon buffers set with this function are dropped later */
void view_set_icon(struct view *view, const char *icon_name,
	struct wl_array *buffers);

struct view_size_hints view_get_size_hints(struct view *view);
void view_adjust_size(struct view *view, int *w, int *h);

void view_evacuate_region(struct view *view);
void view_on_output_destroy(struct view *view);
void view_update_visibility(struct view *view);

void view_init(struct view *view);
void view_destroy(struct view *view);

enum view_axis view_axis_parse(const char *direction);
enum lab_placement_policy view_placement_parse(const char *policy);

/* xdg.c */
struct wlr_xdg_surface *xdg_surface_from_view(struct view *view);

bool view_matches_criteria(struct view *view, enum lab_view_criteria criteria);

#endif /* LABWC_VIEW_H */
