mirror of
https://github.com/artizirk/wdisplays.git
synced 2025-03-26 17:18:52 +02:00
394 lines
16 KiB
C
394 lines
16 KiB
C
/* SPDX-FileCopyrightText: 2020 Jason Francis <jason@cycles.network>
|
||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||
|
||
#include "headform.h"
|
||
#include "wdisplays.h"
|
||
|
||
typedef struct _WdHeadFormPrivate {
|
||
GtkWidget *enabled;
|
||
GtkWidget *description;
|
||
GtkWidget *physical_size;
|
||
GtkWidget *scale;
|
||
GtkWidget *pos_x;
|
||
GtkWidget *pos_y;
|
||
GtkWidget *width;
|
||
GtkWidget *height;
|
||
GtkWidget *refresh;
|
||
GtkWidget *mode_button;
|
||
GtkWidget *rotate_button;
|
||
GtkWidget *flipped;
|
||
|
||
GAction *mode_action;
|
||
GAction *rotate_action;
|
||
} WdHeadFormPrivate;
|
||
|
||
enum {
|
||
CHANGED,
|
||
LAST_SIGNAL
|
||
};
|
||
|
||
static guint signals[LAST_SIGNAL];
|
||
|
||
G_DEFINE_TYPE_WITH_CODE(WdHeadForm, wd_head_form, GTK_TYPE_GRID,
|
||
G_ADD_PRIVATE(WdHeadForm))
|
||
|
||
static const char *HEAD_PREFIX = "head";
|
||
static const char *MODE_PREFIX = "mode";
|
||
static const char *ROTATE_PREFIX = "rotate";
|
||
|
||
static void head_form_update_sensitivity(WdHeadForm *form) {
|
||
WdHeadFormPrivate *priv = wd_head_form_get_instance_private(form);
|
||
|
||
bool enabled_toggled = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->enabled));
|
||
|
||
g_autoptr(GList) children = gtk_container_get_children(GTK_CONTAINER(form));
|
||
for (GList *child = children; child != NULL; child = child->next) {
|
||
GtkWidget *widget = GTK_WIDGET(child->data);
|
||
if (widget != priv->enabled) {
|
||
gtk_widget_set_sensitive(widget, enabled_toggled);
|
||
}
|
||
}
|
||
}
|
||
|
||
static GVariant *create_mode_variant(int32_t w, int32_t h, int32_t r) {
|
||
GVariant * const children[] = {
|
||
g_variant_new_int32(w),
|
||
g_variant_new_int32(h),
|
||
g_variant_new_int32(r),
|
||
};
|
||
return g_variant_new_tuple(children, G_N_ELEMENTS(children));
|
||
}
|
||
|
||
struct vid_mode {
|
||
int32_t width;
|
||
int32_t height;
|
||
int32_t refresh;
|
||
};
|
||
|
||
static void unpack_mode_variant(GVariant *value, struct vid_mode *mode) {
|
||
g_autoptr(GVariant) width = g_variant_get_child_value(value, 0);
|
||
mode->width = g_variant_get_int32(width);
|
||
g_autoptr(GVariant) height = g_variant_get_child_value(value, 1);
|
||
mode->height = g_variant_get_int32(height);
|
||
g_autoptr(GVariant) refresh = g_variant_get_child_value(value, 2);
|
||
mode->refresh = g_variant_get_int32(refresh);
|
||
}
|
||
|
||
static void enabled_toggled(GtkToggleButton *toggle, gpointer data) {
|
||
WdHeadForm *form = WD_HEAD_FORM(data);
|
||
head_form_update_sensitivity(form);
|
||
g_signal_emit(form, signals[CHANGED], 0, WD_FIELD_ENABLED);
|
||
}
|
||
|
||
static void mode_spin_changed(GtkSpinButton *spin_button, gpointer data) {
|
||
WdHeadForm *form = WD_HEAD_FORM(data);
|
||
WdHeadFormPrivate *priv = wd_head_form_get_instance_private(form);
|
||
struct vid_mode mode;
|
||
GVariant *value = g_action_get_state(priv->mode_action);
|
||
unpack_mode_variant(value, &mode);
|
||
if (strcmp(gtk_widget_get_name(GTK_WIDGET(spin_button)), "width") == 0) {
|
||
mode.width = gtk_spin_button_get_value(spin_button);
|
||
} else if (strcmp(gtk_widget_get_name(GTK_WIDGET(spin_button)), "height") == 0) {
|
||
mode.height = gtk_spin_button_get_value(spin_button);
|
||
} else if (strcmp(gtk_widget_get_name(GTK_WIDGET(spin_button)), "refresh") == 0) {
|
||
mode.refresh = gtk_spin_button_get_value(spin_button) * 1000.;
|
||
}
|
||
g_action_activate(priv->mode_action, create_mode_variant(mode.width, mode.height, mode.refresh));
|
||
g_signal_emit(form, signals[CHANGED], 0, WD_FIELD_MODE);
|
||
}
|
||
|
||
static void position_spin_changed(GtkSpinButton *spin_button, gpointer data) {
|
||
WdHeadForm *form = WD_HEAD_FORM(data);
|
||
g_signal_emit(form, signals[CHANGED], 0, WD_FIELD_POSITION);
|
||
}
|
||
|
||
static void flipped_toggled(GtkToggleButton *toggle, gpointer data) {
|
||
WdHeadForm *form = WD_HEAD_FORM(data);
|
||
g_signal_emit(form, signals[CHANGED], 0, WD_FIELD_TRANSFORM);
|
||
}
|
||
|
||
static void wd_head_form_class_init(WdHeadFormClass *class) {
|
||
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(class);
|
||
|
||
signals[CHANGED] = g_signal_new("changed",
|
||
G_OBJECT_CLASS_TYPE(class),
|
||
G_SIGNAL_RUN_LAST,
|
||
G_STRUCT_OFFSET(WdHeadFormClass, changed),
|
||
NULL, NULL, NULL,
|
||
G_TYPE_NONE, 1, G_TYPE_INT);
|
||
|
||
gtk_widget_class_set_template_from_resource(widget_class,
|
||
WDISPLAYS_RESOURCE_PREFIX "/head.ui");
|
||
|
||
gtk_widget_class_bind_template_callback(widget_class, enabled_toggled);
|
||
gtk_widget_class_bind_template_callback(widget_class, mode_spin_changed);
|
||
gtk_widget_class_bind_template_callback(widget_class, position_spin_changed);
|
||
gtk_widget_class_bind_template_callback(widget_class, flipped_toggled);
|
||
|
||
gtk_widget_class_bind_template_child_private(widget_class, WdHeadForm, enabled);
|
||
gtk_widget_class_bind_template_child_private(widget_class, WdHeadForm, description);
|
||
gtk_widget_class_bind_template_child_private(widget_class, WdHeadForm, physical_size);
|
||
gtk_widget_class_bind_template_child_private(widget_class, WdHeadForm, scale);
|
||
gtk_widget_class_bind_template_child_private(widget_class, WdHeadForm, pos_x);
|
||
gtk_widget_class_bind_template_child_private(widget_class, WdHeadForm, pos_y);
|
||
gtk_widget_class_bind_template_child_private(widget_class, WdHeadForm, width);
|
||
gtk_widget_class_bind_template_child_private(widget_class, WdHeadForm, height);
|
||
gtk_widget_class_bind_template_child_private(widget_class, WdHeadForm, refresh);
|
||
gtk_widget_class_bind_template_child_private(widget_class, WdHeadForm, mode_button);
|
||
gtk_widget_class_bind_template_child_private(widget_class, WdHeadForm, rotate_button);
|
||
gtk_widget_class_bind_template_child_private(widget_class, WdHeadForm, flipped);
|
||
gtk_widget_class_set_css_name(widget_class, "wd-head-form");
|
||
}
|
||
|
||
static int32_t get_rotate_value(enum wl_output_transform transform) {
|
||
if (transform == WL_OUTPUT_TRANSFORM_90 || transform == WL_OUTPUT_TRANSFORM_FLIPPED_90) {
|
||
return 90;
|
||
} else if (transform == WL_OUTPUT_TRANSFORM_180 || transform == WL_OUTPUT_TRANSFORM_FLIPPED_180) {
|
||
return 180;
|
||
} else if (transform == WL_OUTPUT_TRANSFORM_270 || transform == WL_OUTPUT_TRANSFORM_FLIPPED_270) {
|
||
return 270;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static void rotate_selected(GSimpleAction *action, GVariant *param, gpointer data) {
|
||
WdHeadForm *form = data;
|
||
WdHeadFormPrivate *priv = wd_head_form_get_instance_private(form);
|
||
GMenuModel *menu = gtk_menu_button_get_menu_model(GTK_MENU_BUTTON(priv->rotate_button));
|
||
int items = g_menu_model_get_n_items(menu);
|
||
for (int i = 0; i < items; i++) {
|
||
g_autoptr(GVariant) target = g_menu_model_get_item_attribute_value(menu, i, G_MENU_ATTRIBUTE_TARGET, NULL);
|
||
g_autoptr(GVariant) label = g_menu_model_get_item_attribute_value(menu, i, G_MENU_ATTRIBUTE_LABEL, NULL);
|
||
if (g_variant_get_int32(target) == g_variant_get_int32(param)) {
|
||
gtk_button_set_label(GTK_BUTTON(priv->rotate_button), g_variant_get_string(label, NULL));
|
||
break;
|
||
}
|
||
}
|
||
g_simple_action_set_state(action, param);
|
||
g_signal_emit(form, signals[CHANGED], 0, WD_FIELD_TRANSFORM);
|
||
}
|
||
|
||
static void mode_selected(GSimpleAction *action, GVariant *param, gpointer data) {
|
||
WdHeadForm *form = data;
|
||
WdHeadFormPrivate *priv = wd_head_form_get_instance_private(form);
|
||
struct vid_mode mode;
|
||
unpack_mode_variant(param, &mode);
|
||
|
||
g_simple_action_set_state(action, param);
|
||
gtk_spin_button_set_value(GTK_SPIN_BUTTON(priv->width), mode.width);
|
||
gtk_spin_button_set_value(GTK_SPIN_BUTTON(priv->height), mode.height);
|
||
gtk_spin_button_set_value(GTK_SPIN_BUTTON(priv->refresh), mode.refresh / 1000.);
|
||
g_signal_emit(form, signals[CHANGED], 0, WD_FIELD_MODE);
|
||
}
|
||
|
||
static void wd_head_form_init(WdHeadForm *form) {
|
||
gtk_widget_init_template(GTK_WIDGET(form));
|
||
WdHeadFormPrivate *priv = wd_head_form_get_instance_private(form);
|
||
|
||
GSimpleActionGroup *head_actions = g_simple_action_group_new();
|
||
gtk_widget_insert_action_group(priv->mode_button, HEAD_PREFIX, G_ACTION_GROUP(head_actions));
|
||
gtk_widget_insert_action_group(priv->rotate_button, HEAD_PREFIX, G_ACTION_GROUP(head_actions));
|
||
|
||
GMenu *rotate_menu = g_menu_new();
|
||
g_menu_append(rotate_menu, "Don't Rotate", "head.rotate(0)");
|
||
g_menu_append(rotate_menu, "Rotate 90°", "head.rotate(90)");
|
||
g_menu_append(rotate_menu, "Rotate 180°", "head.rotate(180)");
|
||
g_menu_append(rotate_menu, "Rotate 270°", "head.rotate(270)");
|
||
gtk_menu_button_set_menu_model(GTK_MENU_BUTTON(priv->rotate_button), G_MENU_MODEL(rotate_menu));
|
||
|
||
static const GVariantType * const mode_types[] = {
|
||
G_VARIANT_TYPE_INT32,
|
||
G_VARIANT_TYPE_INT32,
|
||
G_VARIANT_TYPE_INT32
|
||
};
|
||
GSimpleAction *action = g_simple_action_new_stateful("mode",
|
||
g_variant_type_new_tuple(mode_types, G_N_ELEMENTS(mode_types)),
|
||
create_mode_variant(0, 0, 0));
|
||
g_action_map_add_action(G_ACTION_MAP(head_actions), G_ACTION(action));
|
||
g_signal_connect(action, "change-state", G_CALLBACK(mode_selected), form);
|
||
g_object_unref(action);
|
||
priv->mode_action = G_ACTION(action);
|
||
|
||
action = g_simple_action_new_stateful(ROTATE_PREFIX, G_VARIANT_TYPE_INT32,
|
||
g_variant_new_int32(0));
|
||
g_action_map_add_action(G_ACTION_MAP(head_actions), G_ACTION(action));
|
||
g_signal_connect(action, "change-state", G_CALLBACK(rotate_selected), form);
|
||
g_object_unref(action);
|
||
priv->rotate_action = G_ACTION(action);
|
||
|
||
g_object_unref(head_actions);
|
||
}
|
||
|
||
void wd_head_form_update(WdHeadForm *form, const struct wd_head *head,
|
||
enum wd_head_fields fields) {
|
||
g_return_if_fail(form);
|
||
g_return_if_fail(head);
|
||
|
||
WdHeadFormPrivate *priv = wd_head_form_get_instance_private(form);
|
||
if (!fields)
|
||
return;
|
||
|
||
if (fields & WD_FIELD_DESCRIPTION)
|
||
gtk_label_set_text(GTK_LABEL(priv->description), head->description);
|
||
if (fields & WD_FIELD_PHYSICAL_SIZE) {
|
||
g_autofree gchar *physical_str = g_strdup_printf("%dmm × %dmm", head->phys_width, head->phys_height);
|
||
gtk_label_set_text(GTK_LABEL(priv->physical_size), physical_str);
|
||
}
|
||
if (fields & WD_FIELD_ENABLED)
|
||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->enabled), head->enabled);
|
||
if (fields & WD_FIELD_SCALE)
|
||
gtk_spin_button_set_value(GTK_SPIN_BUTTON(priv->scale), head->scale);
|
||
if (fields & WD_FIELD_POSITION) {
|
||
gtk_spin_button_set_value(GTK_SPIN_BUTTON(priv->pos_x), head->x);
|
||
gtk_spin_button_set_value(GTK_SPIN_BUTTON(priv->pos_y), head->y);
|
||
}
|
||
|
||
if (fields & WD_FIELD_MODE) {
|
||
GMenu *mode_menu = g_menu_new();
|
||
struct wd_mode *mode;
|
||
g_autofree gchar *action = g_strdup_printf("%s.%s", HEAD_PREFIX, MODE_PREFIX);
|
||
wl_list_for_each(mode, &head->modes, link) {
|
||
g_autofree gchar *name = g_strdup_printf("%d×%d@%0.3fHz", mode->width, mode->height, mode->refresh / 1000.);
|
||
GMenuItem *item = g_menu_item_new(name, action);
|
||
g_menu_item_set_attribute_value(item, G_MENU_ATTRIBUTE_TARGET,
|
||
create_mode_variant(mode->width, mode->height, mode->refresh));
|
||
g_menu_append_item(mode_menu, item);
|
||
}
|
||
gtk_menu_button_set_menu_model(GTK_MENU_BUTTON(priv->mode_button), G_MENU_MODEL(mode_menu));
|
||
// Mode entries
|
||
int w = head->custom_mode.width;
|
||
int h = head->custom_mode.height;
|
||
int r = head->custom_mode.refresh;
|
||
if (head->enabled && head->mode != NULL) {
|
||
w = head->mode->width;
|
||
h = head->mode->height;
|
||
r = head->mode->refresh;
|
||
} else if (!head->enabled && w == 0 && h == 0) {
|
||
struct wd_mode *mode;
|
||
wl_list_for_each(mode, &head->modes, link) {
|
||
if (mode->preferred) {
|
||
w = mode->width;
|
||
h = mode->height;
|
||
r = mode->refresh;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
g_action_change_state(priv->mode_action, create_mode_variant(w, h, r));
|
||
}
|
||
|
||
if (fields & WD_FIELD_TRANSFORM) {
|
||
int active_rotate = get_rotate_value(head->transform);
|
||
g_action_change_state(priv->rotate_action, g_variant_new_int32(active_rotate));
|
||
|
||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->flipped),
|
||
head->transform == WL_OUTPUT_TRANSFORM_FLIPPED
|
||
|| head->transform == WL_OUTPUT_TRANSFORM_FLIPPED_90
|
||
|| head->transform == WL_OUTPUT_TRANSFORM_FLIPPED_180
|
||
|| head->transform == WL_OUTPUT_TRANSFORM_FLIPPED_270);
|
||
}
|
||
|
||
// Sync state
|
||
if (fields & WD_FIELD_ENABLED) {
|
||
head_form_update_sensitivity(form);
|
||
}
|
||
g_signal_emit(form, signals[CHANGED], 0);
|
||
}
|
||
|
||
GtkWidget *wd_head_form_new(void) {
|
||
return gtk_widget_new(WD_TYPE_HEAD_FORM, NULL);
|
||
}
|
||
|
||
gboolean wd_head_form_get_enabled(WdHeadForm *form) {
|
||
WdHeadFormPrivate *priv = wd_head_form_get_instance_private(form);
|
||
return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->enabled));
|
||
}
|
||
|
||
gboolean wd_head_form_has_changes(WdHeadForm *form, const struct wd_head *head) {
|
||
g_return_val_if_fail(form, FALSE);
|
||
|
||
WdHeadFormPrivate *priv = wd_head_form_get_instance_private(form);
|
||
if (head->enabled != gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->enabled))) {
|
||
return TRUE;
|
||
}
|
||
double old_scale = round(head->scale * 100.) / 100.;
|
||
double new_scale = round(gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->scale)) * 100.) / 100.;
|
||
if (old_scale != new_scale) {
|
||
return TRUE;
|
||
}
|
||
if (head->x != gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->pos_x))) {
|
||
return TRUE;
|
||
}
|
||
if (head->y != gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->pos_y))) {
|
||
return TRUE;
|
||
}
|
||
int w = head->mode != NULL ? head->mode->width : head->custom_mode.width;
|
||
if (w != gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->width))) {
|
||
return TRUE;
|
||
}
|
||
int h = head->mode != NULL ? head->mode->height : head->custom_mode.height;
|
||
if (h != gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->height))) {
|
||
return TRUE;
|
||
}
|
||
int r = head->mode != NULL ? head->mode->refresh : head->custom_mode.refresh;
|
||
if (r / 1000. != gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->refresh))) {
|
||
return TRUE;
|
||
}
|
||
if (g_variant_get_int32(g_action_get_state(priv->rotate_action)) != get_rotate_value(head->transform)) {
|
||
return TRUE;
|
||
}
|
||
bool flipped = head->transform == WL_OUTPUT_TRANSFORM_FLIPPED
|
||
|| head->transform == WL_OUTPUT_TRANSFORM_FLIPPED_90
|
||
|| head->transform == WL_OUTPUT_TRANSFORM_FLIPPED_180
|
||
|| head->transform == WL_OUTPUT_TRANSFORM_FLIPPED_270;
|
||
if (flipped != gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->flipped))) {
|
||
return TRUE;
|
||
}
|
||
return FALSE;
|
||
}
|
||
|
||
void wd_head_form_fill_config(WdHeadForm *form, struct wd_head_config *output) {
|
||
g_return_if_fail(form);
|
||
g_return_if_fail(output);
|
||
|
||
WdHeadFormPrivate *priv = wd_head_form_get_instance_private(form);
|
||
output->enabled = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->enabled));
|
||
output->scale = gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->scale));
|
||
output->x = gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->pos_x));
|
||
output->y = gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->pos_y));
|
||
output->width = gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->width));
|
||
output->height = gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->height));
|
||
output->refresh = gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->refresh)) * 1000.;
|
||
int32_t rotate = g_variant_get_int32(g_action_get_state(priv->rotate_action));
|
||
gboolean flipped = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->flipped));
|
||
switch (rotate) {
|
||
case 0: output->transform = flipped ? WL_OUTPUT_TRANSFORM_FLIPPED : WL_OUTPUT_TRANSFORM_NORMAL; break;
|
||
case 90: output->transform = flipped ? WL_OUTPUT_TRANSFORM_FLIPPED_90 : WL_OUTPUT_TRANSFORM_90; break;
|
||
case 180: output->transform = flipped ? WL_OUTPUT_TRANSFORM_FLIPPED_180 : WL_OUTPUT_TRANSFORM_180; break;
|
||
case 270: output->transform = flipped ? WL_OUTPUT_TRANSFORM_FLIPPED_270 : WL_OUTPUT_TRANSFORM_270; break;
|
||
}
|
||
}
|
||
|
||
void wd_head_form_get_dimensions(WdHeadForm *form, WdHeadDimensions *dimensions) {
|
||
g_return_if_fail(form);
|
||
g_return_if_fail(dimensions);
|
||
|
||
WdHeadFormPrivate *priv = wd_head_form_get_instance_private(form);
|
||
|
||
dimensions->x = gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->pos_x));
|
||
dimensions->y = gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->pos_y));
|
||
dimensions->w = gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->width));
|
||
dimensions->h = gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->height));
|
||
dimensions->scale = gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->scale));
|
||
dimensions->rotation_id = g_variant_get_int32(g_action_get_state(priv->rotate_action)) / 90;
|
||
dimensions->flipped = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->flipped));
|
||
}
|
||
|
||
void wd_head_form_set_position(WdHeadForm *form, double x, double y) {
|
||
g_return_if_fail(form);
|
||
WdHeadFormPrivate *priv = wd_head_form_get_instance_private(form);
|
||
gtk_spin_button_set_value(GTK_SPIN_BUTTON(priv->pos_x), x);
|
||
gtk_spin_button_set_value(GTK_SPIN_BUTTON(priv->pos_y), y);
|
||
}
|