2019-07-06 05:51:52 +03:00
|
|
|
|
/*
|
|
|
|
|
* Copyright (C) 2019 cyclopsian
|
|
|
|
|
|
|
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining
|
|
|
|
|
* a copy of this software and associated documentation files (the
|
|
|
|
|
* "Software"), to deal in the Software without restriction, including
|
|
|
|
|
* without limitation the rights to use, copy, modify, merge, publish,
|
|
|
|
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
|
|
|
|
* permit persons to whom the Software is furnished to do so, subject to
|
|
|
|
|
* the following conditions:
|
|
|
|
|
|
|
|
|
|
* The above copyright notice and this permission notice shall be
|
|
|
|
|
* included in all copies or substantial portions of the Software.
|
|
|
|
|
|
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
|
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
|
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
|
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE X CONSORTIUM BE LIABLE FOR ANY
|
|
|
|
|
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
|
|
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
|
|
|
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include <gtk/gtk.h>
|
|
|
|
|
#include <gdk/gdkwayland.h>
|
|
|
|
|
|
|
|
|
|
#include "wdisplay.h"
|
2019-07-27 03:26:37 +03:00
|
|
|
|
#include "glviewport.h"
|
2019-07-06 05:51:52 +03:00
|
|
|
|
|
|
|
|
|
__attribute__((noreturn)) void wd_fatal_error(int status, const char *message) {
|
|
|
|
|
GtkWindow *parent = gtk_application_get_active_window(GTK_APPLICATION(g_application_get_default()));
|
|
|
|
|
GtkWidget *dialog = gtk_message_dialog_new(parent, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", message);
|
|
|
|
|
gtk_dialog_run(GTK_DIALOG(dialog));
|
|
|
|
|
gtk_widget_destroy(dialog);
|
|
|
|
|
exit(status);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#define DEFAULT_ZOOM 0.1
|
|
|
|
|
#define MIN_ZOOM (1./1000.)
|
|
|
|
|
#define MAX_ZOOM 1000.
|
|
|
|
|
#define CANVAS_MARGIN 100
|
|
|
|
|
|
|
|
|
|
static const char *MODE_PREFIX = "mode";
|
|
|
|
|
static const char *TRANSFORM_PREFIX = "transform";
|
2019-07-06 20:12:28 +03:00
|
|
|
|
static const char *APP_PREFIX = "app";
|
2019-07-06 05:51:52 +03:00
|
|
|
|
|
|
|
|
|
#define NUM_ROTATIONS 4
|
|
|
|
|
static const char *ROTATE_IDS[NUM_ROTATIONS] = {
|
|
|
|
|
"rotate_0", "rotate_90", "rotate_180", "rotate_270"
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static int get_rotate_index(enum wl_output_transform transform) {
|
|
|
|
|
if (transform == WL_OUTPUT_TRANSFORM_90 || transform == WL_OUTPUT_TRANSFORM_FLIPPED_90) {
|
|
|
|
|
return 1;
|
|
|
|
|
} else if (transform == WL_OUTPUT_TRANSFORM_180 || transform == WL_OUTPUT_TRANSFORM_FLIPPED_180) {
|
|
|
|
|
return 2;
|
|
|
|
|
} else if (transform == WL_OUTPUT_TRANSFORM_270 || transform == WL_OUTPUT_TRANSFORM_FLIPPED_270) {
|
|
|
|
|
return 3;
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool has_changes(const struct wd_state *state) {
|
|
|
|
|
g_autoptr(GList) forms = gtk_container_get_children(GTK_CONTAINER(state->stack));
|
|
|
|
|
for (GList *form_iter = forms; form_iter != NULL; form_iter = form_iter->next) {
|
|
|
|
|
GtkBuilder *builder = GTK_BUILDER(g_object_get_data(G_OBJECT(form_iter->data), "builder"));
|
|
|
|
|
const struct wd_head *head = g_object_get_data(G_OBJECT(form_iter->data), "head");
|
|
|
|
|
if (head->enabled != gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gtk_builder_get_object(builder, "enabled")))) {
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
2019-07-06 20:12:28 +03:00
|
|
|
|
double old_scale = round(head->scale * 100.) / 100.;
|
|
|
|
|
double new_scale = round(gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "scale"))) * 100.) / 100.;
|
|
|
|
|
if (old_scale != new_scale) {
|
2019-07-06 05:51:52 +03:00
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
2019-07-06 20:12:28 +03:00
|
|
|
|
if (head->x != gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "pos_x")))) {
|
2019-07-06 05:51:52 +03:00
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
2019-07-06 20:12:28 +03:00
|
|
|
|
if (head->y != gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "pos_y")))) {
|
2019-07-06 05:51:52 +03:00
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
int w = head->mode != NULL ? head->mode->width : head->custom_mode.width;
|
2019-07-06 20:12:28 +03:00
|
|
|
|
if (w != gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "width")))) {
|
2019-07-06 05:51:52 +03:00
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
int h = head->mode != NULL ? head->mode->height : head->custom_mode.height;
|
2019-07-06 20:12:28 +03:00
|
|
|
|
if (h != gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "height")))) {
|
2019-07-06 05:51:52 +03:00
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
int r = head->mode != NULL ? head->mode->refresh : head->custom_mode.refresh;
|
2019-07-06 20:12:28 +03:00
|
|
|
|
if (r / 1000. != gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "refresh")))) {
|
2019-07-06 05:51:52 +03:00
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
for (int i = 0; i < NUM_ROTATIONS; i++) {
|
|
|
|
|
GtkWidget *rotate = GTK_WIDGET(gtk_builder_get_object(builder, ROTATE_IDS[i]));
|
|
|
|
|
gboolean selected;
|
|
|
|
|
g_object_get(rotate, "active", &selected, NULL);
|
|
|
|
|
if (selected) {
|
|
|
|
|
if (i != get_rotate_index(head->transform)) {
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
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(gtk_builder_get_object(builder, "flipped")))) {
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-06 20:12:28 +03:00
|
|
|
|
void fill_output_from_form(struct wd_head_config *output, GtkWidget *form) {
|
|
|
|
|
GtkBuilder *builder = GTK_BUILDER(g_object_get_data(G_OBJECT(form), "builder"));
|
|
|
|
|
output->head = g_object_get_data(G_OBJECT(form), "head");
|
|
|
|
|
output->enabled = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gtk_builder_get_object(builder, "enabled")));
|
|
|
|
|
output->scale = gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "scale")));
|
|
|
|
|
output->x = gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "pos_x")));
|
|
|
|
|
output->y = gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "pos_y")));
|
|
|
|
|
output->width = gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "width")));
|
|
|
|
|
output->height = gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "height")));
|
|
|
|
|
output->refresh = gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "refresh"))) * 1000.;
|
|
|
|
|
gboolean flipped = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gtk_builder_get_object(builder, "flipped")));
|
|
|
|
|
for (int i = 0; i < NUM_ROTATIONS; i++) {
|
|
|
|
|
GtkWidget *rotate = GTK_WIDGET(gtk_builder_get_object(builder, ROTATE_IDS[i]));
|
|
|
|
|
gboolean selected;
|
|
|
|
|
g_object_get(rotate, "active", &selected, NULL);
|
|
|
|
|
if (selected) {
|
|
|
|
|
switch (i) {
|
|
|
|
|
case 0: output->transform = flipped ? WL_OUTPUT_TRANSFORM_FLIPPED : WL_OUTPUT_TRANSFORM_NORMAL; break;
|
|
|
|
|
case 1: output->transform = flipped ? WL_OUTPUT_TRANSFORM_FLIPPED_90 : WL_OUTPUT_TRANSFORM_90; break;
|
|
|
|
|
case 2: output->transform = flipped ? WL_OUTPUT_TRANSFORM_FLIPPED_180 : WL_OUTPUT_TRANSFORM_180; break;
|
|
|
|
|
case 3: output->transform = flipped ? WL_OUTPUT_TRANSFORM_FLIPPED_270 : WL_OUTPUT_TRANSFORM_270; break;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static gboolean send_apply(gpointer data) {
|
|
|
|
|
struct wd_state *state = data;
|
|
|
|
|
struct wl_list *outputs = calloc(1, sizeof(*outputs));
|
|
|
|
|
wl_list_init(outputs);
|
|
|
|
|
g_autoptr(GList) forms = gtk_container_get_children(GTK_CONTAINER(state->stack));
|
|
|
|
|
for (GList *form_iter = forms; form_iter != NULL; form_iter = form_iter->next) {
|
|
|
|
|
struct wd_head_config *output = calloc(1, sizeof(*output));
|
|
|
|
|
wl_list_insert(outputs, &output->link);
|
|
|
|
|
fill_output_from_form(output, GTK_WIDGET(form_iter->data));
|
|
|
|
|
}
|
|
|
|
|
wd_apply_state(state, outputs);
|
|
|
|
|
state->apply_pending = false;
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void apply_state(struct wd_state *state) {
|
|
|
|
|
gtk_stack_set_visible_child_name(GTK_STACK(state->header_stack), "title");
|
|
|
|
|
if (!state->autoapply) {
|
|
|
|
|
gtk_style_context_add_class(gtk_widget_get_style_context(state->spinner), "visible");
|
|
|
|
|
gtk_overlay_set_overlay_pass_through(GTK_OVERLAY(state->overlay), state->spinner, FALSE);
|
|
|
|
|
|
|
|
|
|
gtk_widget_set_sensitive(state->stack_switcher, FALSE);
|
|
|
|
|
gtk_widget_set_sensitive(state->stack, FALSE);
|
|
|
|
|
gtk_widget_set_sensitive(state->zoom_in, FALSE);
|
|
|
|
|
gtk_widget_set_sensitive(state->zoom_reset, FALSE);
|
|
|
|
|
gtk_widget_set_sensitive(state->zoom_out, FALSE);
|
|
|
|
|
gtk_widget_set_sensitive(state->menu_button, FALSE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* queue this once per iteration in order to prevent duplicate updates */
|
|
|
|
|
if (!state->apply_pending) {
|
|
|
|
|
state->apply_pending = true;
|
|
|
|
|
g_idle_add(send_apply, state);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static gboolean apply_done_reset(gpointer data) {
|
|
|
|
|
wd_ui_reset_all(data);
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-27 03:26:37 +03:00
|
|
|
|
static void update_scroll_size(struct wd_state *state) {
|
|
|
|
|
state->render.viewport_width = gtk_widget_get_allocated_width(state->canvas);
|
|
|
|
|
state->render.viewport_height = gtk_widget_get_allocated_height(state->canvas);
|
|
|
|
|
|
|
|
|
|
GtkAdjustment *scroll_x_adj = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(state->scroller));
|
|
|
|
|
GtkAdjustment *scroll_y_adj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(state->scroller));
|
|
|
|
|
int scroll_x_upper = state->render.width;
|
|
|
|
|
int scroll_y_upper = state->render.height;
|
|
|
|
|
gtk_adjustment_set_upper(scroll_x_adj, MAX(0, scroll_x_upper));
|
|
|
|
|
gtk_adjustment_set_upper(scroll_y_adj, MAX(0, scroll_y_upper));
|
|
|
|
|
gtk_adjustment_set_page_size(scroll_x_adj, state->render.viewport_width);
|
|
|
|
|
gtk_adjustment_set_page_size(scroll_y_adj, state->render.viewport_height);
|
|
|
|
|
gtk_adjustment_set_page_increment(scroll_x_adj, state->render.viewport_width);
|
|
|
|
|
gtk_adjustment_set_page_increment(scroll_y_adj, state->render.viewport_height);
|
|
|
|
|
gtk_adjustment_set_step_increment(scroll_x_adj, state->render.viewport_width / 10);
|
|
|
|
|
gtk_adjustment_set_step_increment(scroll_y_adj, state->render.viewport_height / 10);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Recalculates the desired canvas size, accounting for zoom + margins.
|
|
|
|
|
*/
|
|
|
|
|
static void update_canvas_size(struct wd_state *state) {
|
|
|
|
|
int xmin = 0;
|
|
|
|
|
int xmax = 0;
|
|
|
|
|
int ymin = 0;
|
|
|
|
|
int ymax = 0;
|
|
|
|
|
|
|
|
|
|
struct wd_head *head;
|
|
|
|
|
wl_list_for_each(head, &state->heads, link) {
|
|
|
|
|
int w = head->custom_mode.width;
|
|
|
|
|
int h = head->custom_mode.height;
|
|
|
|
|
if (head->enabled && head->mode != NULL) {
|
|
|
|
|
w = head->mode->width;
|
|
|
|
|
h = head->mode->height;
|
|
|
|
|
}
|
|
|
|
|
if (head->scale > 0.) {
|
|
|
|
|
w /= head->scale;
|
|
|
|
|
h /= head->scale;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int x2 = head->x + w;
|
|
|
|
|
int y2 = head->y + h;
|
|
|
|
|
xmin = MIN(xmin, head->x);
|
|
|
|
|
xmax = MAX(xmax, x2);
|
|
|
|
|
ymin = MIN(ymin, head->y);
|
|
|
|
|
ymax = MAX(ymax, y2);
|
|
|
|
|
}
|
|
|
|
|
// update canvas sizings
|
|
|
|
|
state->render.x_origin = floor(xmin * state->zoom) - CANVAS_MARGIN;
|
|
|
|
|
state->render.y_origin = floor(ymin * state->zoom) - CANVAS_MARGIN;
|
|
|
|
|
state->render.width = ceil((xmax - xmin) * state->zoom) + CANVAS_MARGIN * 2;
|
|
|
|
|
state->render.height = ceil((ymax - ymin) * state->zoom) + CANVAS_MARGIN * 2;
|
|
|
|
|
|
|
|
|
|
update_scroll_size(state);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void cache_scroll(struct wd_state *state) {
|
|
|
|
|
GtkAdjustment *scroll_x_adj = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(state->scroller));
|
|
|
|
|
GtkAdjustment *scroll_y_adj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(state->scroller));
|
|
|
|
|
state->render.scroll_x = gtk_adjustment_get_value(scroll_x_adj);
|
|
|
|
|
state->render.scroll_y = gtk_adjustment_get_value(scroll_y_adj);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static gboolean redraw_canvas(GtkWidget *widget, GdkFrameClock *frame_clock, gpointer data);
|
|
|
|
|
|
|
|
|
|
static void update_tick_callback(struct wd_state *state) {
|
|
|
|
|
bool any_animate = false;
|
|
|
|
|
for (int i = 0; i < state->render.head_count; i++) {
|
|
|
|
|
struct wd_render_head_data *head = &state->render.heads[i];
|
|
|
|
|
if (state->render.updated_at < head->transition_begin + HOVER_USECS) {
|
|
|
|
|
any_animate = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!any_animate && !state->capture) {
|
|
|
|
|
if (state->canvas_tick != -1) {
|
|
|
|
|
gtk_widget_remove_tick_callback(state->canvas, state->canvas_tick);
|
|
|
|
|
state->canvas_tick = -1;
|
|
|
|
|
}
|
|
|
|
|
} else if (state->canvas_tick == -1) {
|
|
|
|
|
state->canvas_tick =
|
|
|
|
|
gtk_widget_add_tick_callback(state->canvas, redraw_canvas, state, NULL);
|
|
|
|
|
}
|
|
|
|
|
gtk_gl_area_queue_render(GTK_GL_AREA(state->canvas));
|
|
|
|
|
gtk_gl_area_set_auto_render(GTK_GL_AREA(state->canvas), state->capture);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void update_cursor(struct wd_state *state) {
|
|
|
|
|
bool any_hovered = false;
|
|
|
|
|
struct wd_head *head;
|
|
|
|
|
wl_list_for_each(head, &state->heads, link) {
|
|
|
|
|
struct wd_render_head_data *render = head->render;
|
|
|
|
|
if (render != NULL && render->hovered) {
|
|
|
|
|
any_hovered = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
GdkWindow *window = gtk_widget_get_window(state->canvas);
|
|
|
|
|
if (any_hovered) {
|
|
|
|
|
gdk_window_set_cursor(window, state->grab_cursor);
|
|
|
|
|
} else if (state->clicked != NULL) {
|
|
|
|
|
gdk_window_set_cursor(window, state->grabbing_cursor);
|
|
|
|
|
} else if (state->panning) {
|
|
|
|
|
gdk_window_set_cursor(window, state->move_cursor);
|
|
|
|
|
} else {
|
|
|
|
|
gdk_window_set_cursor(window, NULL);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void update_hovered(struct wd_state *state) {
|
|
|
|
|
GdkDisplay *display = gdk_display_get_default();
|
|
|
|
|
GdkWindow *window = gtk_widget_get_window(state->canvas);
|
|
|
|
|
if (!gtk_widget_get_realized(state->canvas)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
GdkFrameClock *clock = gtk_widget_get_frame_clock(state->canvas);
|
|
|
|
|
uint64_t tick = gdk_frame_clock_get_frame_time(clock);
|
|
|
|
|
g_autoptr(GList) seats = gdk_display_list_seats(display);
|
|
|
|
|
struct wd_head *head;
|
|
|
|
|
wl_list_for_each(head, &state->heads, link) {
|
|
|
|
|
struct wd_render_head_data *render = head->render;
|
|
|
|
|
if (render != NULL) {
|
|
|
|
|
bool init_hovered = render->hovered;
|
|
|
|
|
render->hovered = false;
|
|
|
|
|
if (state->clicked == head) {
|
|
|
|
|
render->hovered = true;
|
|
|
|
|
} else if (state->clicked == NULL) {
|
|
|
|
|
for (GList *iter = seats; iter != NULL; iter = iter->next) {
|
|
|
|
|
double mouse_x;
|
|
|
|
|
double mouse_y;
|
|
|
|
|
|
|
|
|
|
GdkDevice *pointer = gdk_seat_get_pointer(GDK_SEAT(iter->data));
|
|
|
|
|
gdk_window_get_device_position_double(window, pointer, &mouse_x, &mouse_y, NULL);
|
|
|
|
|
if (mouse_x >= render->x1 && mouse_x < render->x2 &&
|
|
|
|
|
mouse_y >= render->y1 && mouse_y < render->y2) {
|
|
|
|
|
render->hovered = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (init_hovered != render->hovered) {
|
|
|
|
|
render->transition_begin = tick;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
update_cursor(state);
|
|
|
|
|
update_tick_callback(state);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline void color_to_float_array(GtkStyleContext *ctx,
|
|
|
|
|
const char *color_name, float out[4]) {
|
|
|
|
|
GdkRGBA color;
|
|
|
|
|
gtk_style_context_lookup_color(ctx, color_name, &color);
|
|
|
|
|
out[0] = color.red;
|
|
|
|
|
out[1] = color.green;
|
|
|
|
|
out[2] = color.blue;
|
|
|
|
|
out[3] = color.alpha;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void queue_canvas_draw(struct wd_state *state) {
|
|
|
|
|
GtkStyleContext *style_ctx = gtk_widget_get_style_context(state->canvas);
|
|
|
|
|
color_to_float_array(style_ctx,
|
|
|
|
|
"theme_fg_color", state->render.fg_color);
|
|
|
|
|
color_to_float_array(style_ctx,
|
|
|
|
|
"theme_bg_color", state->render.bg_color);
|
|
|
|
|
color_to_float_array(style_ctx,
|
|
|
|
|
"borders", state->render.border_color);
|
|
|
|
|
color_to_float_array(style_ctx,
|
|
|
|
|
"theme_selected_bg_color", state->render.selection_color);
|
|
|
|
|
|
|
|
|
|
cache_scroll(state);
|
|
|
|
|
|
|
|
|
|
state->render.head_count = 0;
|
|
|
|
|
g_autoptr(GList) forms = gtk_container_get_children(GTK_CONTAINER(state->stack));
|
|
|
|
|
for (GList *form_iter = forms; form_iter != NULL; form_iter = form_iter->next) {
|
|
|
|
|
GtkBuilder *builder = GTK_BUILDER(g_object_get_data(G_OBJECT(form_iter->data), "builder"));
|
|
|
|
|
gboolean enabled = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gtk_builder_get_object(builder, "enabled")));
|
|
|
|
|
if (enabled) {
|
|
|
|
|
int x = gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "pos_x")));
|
|
|
|
|
int y = gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "pos_y")));
|
|
|
|
|
int w = gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "width")));
|
|
|
|
|
int h = gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "height")));
|
|
|
|
|
double scale = gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "scale")));
|
|
|
|
|
if (scale <= 0.)
|
|
|
|
|
scale = 1.;
|
|
|
|
|
|
|
|
|
|
struct wd_head *head = g_object_get_data(G_OBJECT(form_iter->data), "head");
|
|
|
|
|
struct wd_render_head_data *render = &state->render.heads[state->render.head_count];
|
|
|
|
|
render->x1 = floor(x * state->zoom - state->render.scroll_x - state->render.x_origin);
|
|
|
|
|
render->y1 = floor(y * state->zoom - state->render.scroll_y - state->render.y_origin);
|
|
|
|
|
render->x2 = floor(render->x1 + w * state->zoom / scale);
|
|
|
|
|
render->y2 = floor(render->y1 + h * state->zoom / scale);
|
|
|
|
|
head->render = render;
|
|
|
|
|
|
|
|
|
|
state->render.head_count++;
|
|
|
|
|
if (state->render.head_count >= HEADS_MAX)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
gtk_gl_area_queue_render(GTK_GL_AREA(state->canvas));
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-06 05:51:52 +03:00
|
|
|
|
// BEGIN FORM CALLBACKS
|
|
|
|
|
static void show_apply(struct wd_state *state) {
|
2019-07-06 20:12:28 +03:00
|
|
|
|
const gchar *page = "title";
|
|
|
|
|
if (has_changes(state)) {
|
|
|
|
|
if (state->autoapply) {
|
|
|
|
|
apply_state(state);
|
|
|
|
|
} else {
|
|
|
|
|
page = "apply";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
gtk_stack_set_visible_child_name(GTK_STACK(state->header_stack), page);
|
2019-07-27 03:26:37 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void update_ui(struct wd_state *state) {
|
|
|
|
|
show_apply(state);
|
|
|
|
|
update_canvas_size(state);
|
|
|
|
|
queue_canvas_draw(state);
|
2019-07-06 05:51:52 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void update_sensitivity(GtkWidget *form) {
|
|
|
|
|
GtkBuilder *builder = GTK_BUILDER(g_object_get_data(G_OBJECT(form), "builder"));
|
|
|
|
|
GtkWidget *enabled = GTK_WIDGET(gtk_builder_get_object(builder, "enabled"));
|
|
|
|
|
bool enabled_toggled = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(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 != enabled) {
|
|
|
|
|
gtk_widget_set_sensitive(widget, enabled_toggled);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void select_rotate_option(GtkWidget *form, GtkWidget *model_button) {
|
|
|
|
|
GtkBuilder *builder = GTK_BUILDER(g_object_get_data(G_OBJECT(form), "builder"));
|
|
|
|
|
GtkWidget *rotate_button = GTK_WIDGET(gtk_builder_get_object(builder, "rotate_button"));
|
|
|
|
|
for (int i = 0; i < NUM_ROTATIONS; i++) {
|
|
|
|
|
GtkWidget *rotate = GTK_WIDGET(gtk_builder_get_object(builder, ROTATE_IDS[i]));
|
|
|
|
|
gboolean selected = model_button == rotate;
|
|
|
|
|
g_object_set(rotate, "active", selected, NULL);
|
|
|
|
|
if (selected) {
|
|
|
|
|
g_autofree gchar *rotate_text = NULL;
|
|
|
|
|
g_object_get(rotate, "text", &rotate_text, NULL);
|
|
|
|
|
gtk_button_set_label(GTK_BUTTON(rotate_button), rotate_text);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void rotate_selected(GSimpleAction *action, GVariant *param, gpointer data) {
|
|
|
|
|
select_rotate_option(GTK_WIDGET(data), g_object_get_data(G_OBJECT(action), "widget"));
|
|
|
|
|
const struct wd_head *head = g_object_get_data(G_OBJECT(data), "head");
|
2019-07-27 03:26:37 +03:00
|
|
|
|
update_ui(head->state);
|
2019-07-06 05:51:52 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void select_mode_option(GtkWidget *form, int32_t w, int32_t h, int32_t r) {
|
|
|
|
|
GtkBuilder *builder = GTK_BUILDER(g_object_get_data(G_OBJECT(form), "builder"));
|
|
|
|
|
GtkWidget *mode_box = GTK_WIDGET(gtk_builder_get_object(builder, "mode_box"));
|
|
|
|
|
g_autoptr(GList) children = gtk_container_get_children(GTK_CONTAINER(mode_box));
|
|
|
|
|
for (GList *child = children; child != NULL; child = child->next) {
|
|
|
|
|
const struct wd_mode *mode = g_object_get_data(G_OBJECT(child->data), "mode");
|
|
|
|
|
g_object_set(child->data, "active", w == mode->width && h == mode->height && r == mode->refresh, NULL);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void update_mode_entries(GtkWidget *form, int32_t w, int32_t h, int32_t r) {
|
|
|
|
|
GtkBuilder *builder = GTK_BUILDER(g_object_get_data(G_OBJECT(form), "builder"));
|
|
|
|
|
GtkWidget *width = GTK_WIDGET(gtk_builder_get_object(builder, "width"));
|
|
|
|
|
GtkWidget *height = GTK_WIDGET(gtk_builder_get_object(builder, "height"));
|
|
|
|
|
GtkWidget *refresh = GTK_WIDGET(gtk_builder_get_object(builder, "refresh"));
|
|
|
|
|
|
2019-07-06 20:12:28 +03:00
|
|
|
|
gtk_spin_button_set_value(GTK_SPIN_BUTTON(width), w);
|
|
|
|
|
gtk_spin_button_set_value(GTK_SPIN_BUTTON(height), h);
|
|
|
|
|
gtk_spin_button_set_value(GTK_SPIN_BUTTON(refresh), r / 1000.);
|
2019-07-06 05:51:52 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void mode_selected(GSimpleAction *action, GVariant *param, gpointer data) {
|
|
|
|
|
GtkWidget *form = data;
|
|
|
|
|
const struct wd_head *head = g_object_get_data(G_OBJECT(form), "head");
|
|
|
|
|
const struct wd_mode *mode = g_object_get_data(G_OBJECT(action), "mode");
|
|
|
|
|
|
|
|
|
|
update_mode_entries(form, mode->width, mode->height, mode->refresh);
|
|
|
|
|
select_mode_option(form, mode->width, mode->height, mode->refresh);
|
2019-07-27 03:26:37 +03:00
|
|
|
|
update_ui(head->state);
|
2019-07-06 05:51:52 +03:00
|
|
|
|
}
|
|
|
|
|
// END FORM CALLBACKS
|
|
|
|
|
|
|
|
|
|
static void clear_menu(GtkWidget *box, GActionMap *action_map) {
|
|
|
|
|
g_autoptr(GList) children = gtk_container_get_children(GTK_CONTAINER(box));
|
|
|
|
|
for (GList *child = children; child != NULL; child = child->next) {
|
|
|
|
|
g_action_map_remove_action(action_map, strchr(gtk_actionable_get_action_name(GTK_ACTIONABLE(child->data)), '.') + 1);
|
|
|
|
|
gtk_container_remove(GTK_CONTAINER(box), GTK_WIDGET(child->data));
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-07-27 03:26:37 +03:00
|
|
|
|
|
2019-07-06 05:51:52 +03:00
|
|
|
|
static void update_head_form(GtkWidget *form, unsigned int fields) {
|
|
|
|
|
GtkBuilder *builder = GTK_BUILDER(g_object_get_data(G_OBJECT(form), "builder"));
|
|
|
|
|
GtkWidget *description = GTK_WIDGET(gtk_builder_get_object(builder, "description"));
|
|
|
|
|
GtkWidget *physical_size = GTK_WIDGET(gtk_builder_get_object(builder, "physical_size"));
|
|
|
|
|
GtkWidget *enabled = GTK_WIDGET(gtk_builder_get_object(builder, "enabled"));
|
|
|
|
|
GtkWidget *scale = GTK_WIDGET(gtk_builder_get_object(builder, "scale"));
|
|
|
|
|
GtkWidget *pos_x = GTK_WIDGET(gtk_builder_get_object(builder, "pos_x"));
|
|
|
|
|
GtkWidget *pos_y = GTK_WIDGET(gtk_builder_get_object(builder, "pos_y"));
|
|
|
|
|
GtkWidget *mode_box = GTK_WIDGET(gtk_builder_get_object(builder, "mode_box"));
|
|
|
|
|
GtkWidget *flipped = GTK_WIDGET(gtk_builder_get_object(builder, "flipped"));
|
|
|
|
|
const struct wd_head *head = g_object_get_data(G_OBJECT(form), "head");
|
|
|
|
|
|
|
|
|
|
if (fields & WD_FIELD_NAME) {
|
|
|
|
|
gtk_container_child_set(GTK_CONTAINER(head->state->stack), form, "name", head->name, "title", head->name, NULL);
|
|
|
|
|
}
|
|
|
|
|
if (fields & WD_FIELD_DESCRIPTION) {
|
|
|
|
|
gtk_label_set_text(GTK_LABEL(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(physical_size), physical_str);
|
|
|
|
|
}
|
|
|
|
|
if (fields & WD_FIELD_ENABLED) {
|
|
|
|
|
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(enabled), head->enabled);
|
|
|
|
|
}
|
|
|
|
|
if (fields & WD_FIELD_SCALE) {
|
|
|
|
|
gtk_spin_button_set_value(GTK_SPIN_BUTTON(scale), head->scale);
|
|
|
|
|
}
|
|
|
|
|
if (fields & WD_FIELD_POSITION) {
|
2019-07-06 20:12:28 +03:00
|
|
|
|
gtk_spin_button_set_value(GTK_SPIN_BUTTON(pos_x), head->x);
|
|
|
|
|
gtk_spin_button_set_value(GTK_SPIN_BUTTON(pos_y), head->y);
|
2019-07-06 05:51:52 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (fields & WD_FIELD_MODE) {
|
|
|
|
|
GActionMap *mode_actions = G_ACTION_MAP(g_object_get_data(G_OBJECT(form), "mode-group"));
|
|
|
|
|
clear_menu(mode_box, mode_actions);
|
|
|
|
|
struct wd_mode *mode;
|
|
|
|
|
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.);
|
|
|
|
|
GSimpleAction *action = g_simple_action_new(name, NULL);
|
|
|
|
|
g_action_map_add_action(G_ACTION_MAP(mode_actions), G_ACTION(action));
|
|
|
|
|
g_signal_connect(action, "activate", G_CALLBACK(mode_selected), form);
|
|
|
|
|
g_object_set_data(G_OBJECT(action), "mode", mode);
|
|
|
|
|
g_object_unref(action);
|
|
|
|
|
|
|
|
|
|
GtkWidget *button = gtk_model_button_new();
|
|
|
|
|
g_autoptr(GString) prefixed_name = g_string_new(MODE_PREFIX);
|
|
|
|
|
g_string_append(prefixed_name, ".");
|
|
|
|
|
g_string_append(prefixed_name, name);
|
|
|
|
|
gtk_actionable_set_action_name(GTK_ACTIONABLE(button), prefixed_name->str);
|
|
|
|
|
g_object_set(button, "role", GTK_BUTTON_ROLE_RADIO, "text", name, NULL);
|
|
|
|
|
gtk_box_pack_start(GTK_BOX(mode_box), button, FALSE, FALSE, 0);
|
|
|
|
|
g_object_set_data(G_OBJECT(button), "mode", mode);
|
|
|
|
|
gtk_widget_show_all(button);
|
|
|
|
|
}
|
|
|
|
|
// 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;
|
2019-07-27 03:26:37 +03:00
|
|
|
|
} 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;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-07-06 05:51:52 +03:00
|
|
|
|
}
|
2019-07-27 03:26:37 +03:00
|
|
|
|
|
2019-07-06 05:51:52 +03:00
|
|
|
|
update_mode_entries(form, w, h, r);
|
|
|
|
|
select_mode_option(form, w, h, r);
|
|
|
|
|
gtk_widget_show_all(mode_box);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (fields & WD_FIELD_TRANSFORM) {
|
|
|
|
|
int active_rotate = get_rotate_index(head->transform);
|
|
|
|
|
select_rotate_option(form, GTK_WIDGET(gtk_builder_get_object(builder, ROTATE_IDS[active_rotate])));
|
|
|
|
|
|
|
|
|
|
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(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) {
|
|
|
|
|
update_sensitivity(form);
|
|
|
|
|
}
|
2019-07-27 03:26:37 +03:00
|
|
|
|
update_ui(head->state);
|
2019-07-06 05:51:52 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void wd_ui_reset_heads(struct wd_state *state) {
|
|
|
|
|
if (state->stack == NULL) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g_autoptr(GList) forms = gtk_container_get_children(GTK_CONTAINER(state->stack));
|
|
|
|
|
GList *form_iter = forms;
|
|
|
|
|
struct wd_head *head;
|
|
|
|
|
wl_list_for_each(head, &state->heads, link) {
|
|
|
|
|
GtkBuilder *builder;
|
|
|
|
|
GtkWidget *form;
|
|
|
|
|
if (form_iter == NULL) {
|
|
|
|
|
builder = gtk_builder_new_from_resource("/head.ui");
|
|
|
|
|
form = GTK_WIDGET(gtk_builder_get_object(builder, "form"));
|
|
|
|
|
g_object_set_data(G_OBJECT(form), "builder", builder);
|
|
|
|
|
g_object_set_data(G_OBJECT(form), "head", head);
|
|
|
|
|
gtk_stack_add_titled(GTK_STACK(state->stack), form, head->name, head->name);
|
|
|
|
|
|
|
|
|
|
GtkWidget *mode_button = GTK_WIDGET(gtk_builder_get_object(builder, "mode_button"));
|
|
|
|
|
GtkWidget *rotate_button = GTK_WIDGET(gtk_builder_get_object(builder, "rotate_button"));
|
|
|
|
|
|
|
|
|
|
GSimpleActionGroup *mode_actions = g_simple_action_group_new();
|
|
|
|
|
gtk_widget_insert_action_group(mode_button, MODE_PREFIX, G_ACTION_GROUP(mode_actions));
|
|
|
|
|
g_object_set_data(G_OBJECT(form), "mode-group", mode_actions);
|
|
|
|
|
g_object_unref(mode_actions);
|
|
|
|
|
|
|
|
|
|
GSimpleActionGroup *transform_actions = g_simple_action_group_new();
|
|
|
|
|
gtk_widget_insert_action_group(rotate_button, TRANSFORM_PREFIX, G_ACTION_GROUP(transform_actions));
|
|
|
|
|
g_object_unref(transform_actions);
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < NUM_ROTATIONS; i++) {
|
|
|
|
|
GtkWidget *button = GTK_WIDGET(gtk_builder_get_object(builder, ROTATE_IDS[i]));
|
|
|
|
|
g_object_set(button, "role", GTK_BUTTON_ROLE_RADIO, NULL);
|
|
|
|
|
GSimpleAction *action = g_simple_action_new(ROTATE_IDS[i], NULL);
|
|
|
|
|
g_action_map_add_action(G_ACTION_MAP(transform_actions), G_ACTION(action));
|
|
|
|
|
g_signal_connect(action, "activate", G_CALLBACK(rotate_selected), form);
|
|
|
|
|
g_object_set_data(G_OBJECT(action), "widget", button);
|
|
|
|
|
g_object_unref(action);
|
|
|
|
|
}
|
|
|
|
|
update_head_form(form, WD_FIELDS_ALL);
|
|
|
|
|
|
|
|
|
|
gtk_widget_show_all(form);
|
|
|
|
|
|
|
|
|
|
g_signal_connect_swapped(gtk_builder_get_object(builder, "enabled"), "toggled", G_CALLBACK(update_sensitivity), form);
|
2019-07-27 03:26:37 +03:00
|
|
|
|
g_signal_connect_swapped(gtk_builder_get_object(builder, "enabled"), "toggled", G_CALLBACK(update_ui), state);
|
|
|
|
|
g_signal_connect_swapped(gtk_builder_get_object(builder, "scale"), "value-changed", G_CALLBACK(update_ui), state);
|
|
|
|
|
g_signal_connect_swapped(gtk_builder_get_object(builder, "pos_x"), "value-changed", G_CALLBACK(update_ui), state);
|
|
|
|
|
g_signal_connect_swapped(gtk_builder_get_object(builder, "pos_y"), "value-changed", G_CALLBACK(update_ui), state);
|
|
|
|
|
g_signal_connect_swapped(gtk_builder_get_object(builder, "width"), "value-changed", G_CALLBACK(update_ui), state);
|
|
|
|
|
g_signal_connect_swapped(gtk_builder_get_object(builder, "height"), "value-changed", G_CALLBACK(update_ui), state);
|
|
|
|
|
g_signal_connect_swapped(gtk_builder_get_object(builder, "refresh"), "value-changed", G_CALLBACK(update_ui), state);
|
|
|
|
|
g_signal_connect_swapped(gtk_builder_get_object(builder, "flipped"), "toggled", G_CALLBACK(update_ui), state);
|
2019-07-06 05:51:52 +03:00
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
form = form_iter->data;
|
|
|
|
|
g_object_set_data(G_OBJECT(form), "head", head);
|
|
|
|
|
form_iter = form_iter->next;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// remove everything else
|
|
|
|
|
for (; form_iter != NULL; form_iter = form_iter->next) {
|
|
|
|
|
GtkBuilder *builder = GTK_BUILDER(g_object_get_data(G_OBJECT(form_iter->data), "builder"));
|
|
|
|
|
g_object_unref(builder);
|
|
|
|
|
gtk_container_remove(GTK_CONTAINER(state->stack), GTK_WIDGET(form_iter->data));
|
|
|
|
|
}
|
2019-07-27 03:26:37 +03:00
|
|
|
|
update_canvas_size(state);
|
|
|
|
|
queue_canvas_draw(state);
|
2019-07-06 05:51:52 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void wd_ui_reset_head(const struct wd_head *head, unsigned int fields) {
|
|
|
|
|
if (head->state->stack == NULL) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
g_autoptr(GList) forms = gtk_container_get_children(GTK_CONTAINER(head->state->stack));
|
|
|
|
|
for (GList *form_iter = forms; form_iter != NULL; form_iter = form_iter->next) {
|
|
|
|
|
const struct wd_head *other = g_object_get_data(G_OBJECT(form_iter->data), "head");
|
|
|
|
|
if (head == other) {
|
|
|
|
|
update_head_form(GTK_WIDGET(form_iter->data), fields);
|
2019-07-27 03:26:37 +03:00
|
|
|
|
break;
|
2019-07-06 05:51:52 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
2019-07-27 03:26:37 +03:00
|
|
|
|
update_canvas_size(head->state);
|
|
|
|
|
queue_canvas_draw(head->state);
|
2019-07-06 05:51:52 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void wd_ui_reset_all(struct wd_state *state) {
|
|
|
|
|
wd_ui_reset_heads(state);
|
|
|
|
|
g_autoptr(GList) forms = gtk_container_get_children(GTK_CONTAINER(state->stack));
|
|
|
|
|
for (GList *form_iter = forms; form_iter != NULL; form_iter = form_iter->next) {
|
|
|
|
|
update_head_form(GTK_WIDGET(form_iter->data), WD_FIELDS_ALL);
|
|
|
|
|
}
|
2019-07-27 03:26:37 +03:00
|
|
|
|
update_canvas_size(state);
|
|
|
|
|
queue_canvas_draw(state);
|
2019-07-06 05:51:52 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void wd_ui_apply_done(struct wd_state *state, struct wl_list *outputs) {
|
|
|
|
|
gtk_style_context_remove_class(gtk_widget_get_style_context(state->spinner), "visible");
|
|
|
|
|
gtk_overlay_set_overlay_pass_through(GTK_OVERLAY(state->overlay), state->spinner, TRUE);
|
|
|
|
|
|
|
|
|
|
gtk_widget_set_sensitive(state->stack_switcher, TRUE);
|
|
|
|
|
gtk_widget_set_sensitive(state->stack, TRUE);
|
|
|
|
|
gtk_widget_set_sensitive(state->zoom_in, TRUE);
|
|
|
|
|
gtk_widget_set_sensitive(state->zoom_reset, TRUE);
|
|
|
|
|
gtk_widget_set_sensitive(state->zoom_out, TRUE);
|
2019-07-06 20:12:28 +03:00
|
|
|
|
gtk_widget_set_sensitive(state->menu_button, TRUE);
|
|
|
|
|
if (!state->autoapply) {
|
|
|
|
|
show_apply(state);
|
|
|
|
|
}
|
|
|
|
|
g_idle_add(apply_done_reset, state);
|
2019-07-06 05:51:52 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void wd_ui_show_error(struct wd_state *state, const char *message) {
|
|
|
|
|
gtk_label_set_text(GTK_LABEL(state->info_label), message);
|
|
|
|
|
gtk_widget_show(state->info_bar);
|
|
|
|
|
gtk_info_bar_set_revealed(GTK_INFO_BAR(state->info_bar), TRUE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// BEGIN GLOBAL CALLBACKS
|
2019-07-27 03:26:37 +03:00
|
|
|
|
static void cleanup(GtkWidget *window, gpointer data) {
|
2019-07-06 05:51:52 +03:00
|
|
|
|
struct wd_state *state = data;
|
2019-07-27 03:26:37 +03:00
|
|
|
|
g_object_unref(state->grab_cursor);
|
|
|
|
|
g_object_unref(state->grabbing_cursor);
|
|
|
|
|
g_object_unref(state->move_cursor);
|
|
|
|
|
wd_state_destroy(state);
|
|
|
|
|
}
|
2019-07-06 05:51:52 +03:00
|
|
|
|
|
2019-07-27 03:26:37 +03:00
|
|
|
|
static void monitor_added(GdkDisplay *display, GdkMonitor *monitor, gpointer data) {
|
|
|
|
|
wd_add_output(data, gdk_wayland_monitor_get_wl_output(monitor));
|
|
|
|
|
}
|
2019-07-06 05:51:52 +03:00
|
|
|
|
|
2019-07-27 03:26:37 +03:00
|
|
|
|
static void monitor_removed(GdkDisplay *display, GdkMonitor *monitor, gpointer data) {
|
|
|
|
|
struct wl_display *wl_display = gdk_wayland_display_get_wl_display(display);
|
|
|
|
|
wd_remove_output(data, gdk_wayland_monitor_get_wl_output(monitor), wl_display);
|
|
|
|
|
}
|
2019-07-06 05:51:52 +03:00
|
|
|
|
|
2019-07-27 03:26:37 +03:00
|
|
|
|
static void canvas_realize(GtkWidget *widget, gpointer data) {
|
|
|
|
|
gtk_gl_area_make_current(GTK_GL_AREA(widget));
|
|
|
|
|
if (gtk_gl_area_get_error(GTK_GL_AREA(widget)) != NULL) {
|
|
|
|
|
return;
|
2019-07-06 05:51:52 +03:00
|
|
|
|
}
|
|
|
|
|
|
2019-07-27 03:26:37 +03:00
|
|
|
|
struct wd_state *state = data;
|
|
|
|
|
state->gl_data = wd_gl_setup();
|
2019-07-06 05:51:52 +03:00
|
|
|
|
}
|
|
|
|
|
|
2019-07-27 03:26:37 +03:00
|
|
|
|
static inline bool size_changed(const struct wd_render_head_data *render) {
|
|
|
|
|
return render->x2 - render->x1 != render->tex_width ||
|
|
|
|
|
render->y2 - render->y1 != render->tex_height;
|
2019-07-06 05:51:52 +03:00
|
|
|
|
}
|
|
|
|
|
|
2019-07-27 03:26:37 +03:00
|
|
|
|
static inline void cairo_set_source_color(cairo_t *cr, float color[4]) {
|
|
|
|
|
cairo_set_source_rgba(cr, color[0], color[1], color[2], color[3]);
|
2019-07-06 05:51:52 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void update_zoom(struct wd_state *state) {
|
|
|
|
|
g_autofree gchar *zoom_percent = g_strdup_printf("%.f%%", state->zoom * 100.);
|
|
|
|
|
gtk_button_set_label(GTK_BUTTON(state->zoom_reset), zoom_percent);
|
|
|
|
|
gtk_widget_set_sensitive(state->zoom_in, state->zoom < MAX_ZOOM);
|
|
|
|
|
gtk_widget_set_sensitive(state->zoom_out, state->zoom > MIN_ZOOM);
|
2019-07-27 03:26:37 +03:00
|
|
|
|
|
|
|
|
|
update_canvas_size(state);
|
|
|
|
|
queue_canvas_draw(state);
|
2019-07-06 05:51:52 +03:00
|
|
|
|
}
|
|
|
|
|
|
2019-07-27 03:26:37 +03:00
|
|
|
|
static void zoom_to(struct wd_state *state, double zoom) {
|
|
|
|
|
state->zoom = zoom;
|
2019-07-06 05:51:52 +03:00
|
|
|
|
state->zoom = MAX(state->zoom, MIN_ZOOM);
|
2019-07-27 03:26:37 +03:00
|
|
|
|
state->zoom = MIN(state->zoom, MAX_ZOOM);
|
2019-07-06 05:51:52 +03:00
|
|
|
|
update_zoom(state);
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-27 03:26:37 +03:00
|
|
|
|
static void zoom_out(struct wd_state *state) {
|
|
|
|
|
zoom_to(state, state->zoom * 0.75);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void zoom_reset(struct wd_state *state) {
|
|
|
|
|
zoom_to(state, DEFAULT_ZOOM);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void zoom_in(struct wd_state *state) {
|
|
|
|
|
zoom_to(state, state->zoom / 0.75);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#define TEXT_MARGIN 5
|
|
|
|
|
|
|
|
|
|
static cairo_surface_t *draw_head(PangoContext *pango,
|
|
|
|
|
struct wd_render_data *info, const char *name,
|
|
|
|
|
unsigned width, unsigned height) {
|
|
|
|
|
cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
|
|
|
|
|
width, height);
|
|
|
|
|
cairo_t *cr = cairo_create(surface);
|
|
|
|
|
|
|
|
|
|
cairo_rectangle(cr, 0., 0., width, height);
|
|
|
|
|
cairo_set_source_color(cr, info->border_color);
|
|
|
|
|
cairo_fill(cr);
|
|
|
|
|
|
|
|
|
|
cairo_set_line_width(cr, 1.);
|
|
|
|
|
cairo_rectangle(cr, 0, 0, width, height);
|
|
|
|
|
cairo_set_source_color(cr, info->fg_color);
|
|
|
|
|
cairo_stroke(cr);
|
|
|
|
|
|
|
|
|
|
PangoLayout *layout = pango_layout_new(pango);
|
|
|
|
|
pango_layout_set_text(layout, name, -1);
|
|
|
|
|
int text_width = pango_units_from_double(width - TEXT_MARGIN * 2);
|
|
|
|
|
int text_height = pango_units_from_double(height - TEXT_MARGIN * 2);
|
|
|
|
|
pango_layout_set_width(layout, MAX(text_width, 0));
|
|
|
|
|
pango_layout_set_height(layout, MAX(text_height, 0));
|
|
|
|
|
pango_layout_set_wrap(layout, PANGO_WRAP_WORD_CHAR);
|
|
|
|
|
pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END);
|
|
|
|
|
pango_layout_set_alignment(layout, PANGO_ALIGN_CENTER);
|
|
|
|
|
|
|
|
|
|
cairo_set_source_color(cr, info->fg_color);
|
|
|
|
|
pango_layout_get_size(layout, &text_width, &text_height);
|
|
|
|
|
cairo_move_to(cr, TEXT_MARGIN, (height - PANGO_PIXELS(text_height)) / 2);
|
|
|
|
|
pango_cairo_show_layout(cr, layout);
|
|
|
|
|
g_object_unref(layout);
|
|
|
|
|
|
|
|
|
|
cairo_destroy(cr);
|
|
|
|
|
cairo_surface_flush(surface);
|
|
|
|
|
return surface;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void canvas_render(GtkGLArea *area, GdkGLContext *context, gpointer data) {
|
2019-07-06 05:51:52 +03:00
|
|
|
|
struct wd_state *state = data;
|
2019-07-27 03:26:37 +03:00
|
|
|
|
|
|
|
|
|
PangoContext *pango = gtk_widget_get_pango_context(state->canvas);
|
|
|
|
|
GdkFrameClock *clock = gtk_widget_get_frame_clock(state->canvas);
|
|
|
|
|
uint64_t tick = gdk_frame_clock_get_frame_time(clock);
|
|
|
|
|
|
|
|
|
|
wd_capture_frame(state);
|
|
|
|
|
|
|
|
|
|
struct wd_head *head;
|
|
|
|
|
wl_list_for_each(head, &state->heads, link) {
|
|
|
|
|
struct wd_render_head_data *render = head->render;
|
|
|
|
|
struct wd_output *output = wd_find_output(state, head);
|
|
|
|
|
struct wd_frame *frame = NULL;
|
|
|
|
|
if (output != NULL && !wl_list_empty(&output->frames)) {
|
|
|
|
|
frame = wl_container_of(output->frames.prev, frame, link);
|
|
|
|
|
}
|
|
|
|
|
if (render != NULL) {
|
|
|
|
|
if (state->capture && frame != NULL && frame->pixels != NULL) {
|
|
|
|
|
if (frame->tick > render->updated_at) {
|
|
|
|
|
render->tex_stride = frame->stride;
|
|
|
|
|
render->tex_width = frame->width;
|
|
|
|
|
render->tex_height = frame->height;
|
|
|
|
|
render->pixels = frame->pixels;
|
|
|
|
|
render->preview = true;
|
|
|
|
|
render->updated_at = tick;
|
|
|
|
|
render->y_invert = frame->y_invert;
|
|
|
|
|
}
|
|
|
|
|
} else if (render->preview
|
|
|
|
|
|| render->pixels == NULL || size_changed(render)) {
|
|
|
|
|
render->tex_width = render->x2 - render->x1;
|
|
|
|
|
render->tex_height = render->y2 - render->y1;
|
|
|
|
|
render->preview = false;
|
|
|
|
|
if (head->surface != NULL) {
|
|
|
|
|
cairo_surface_destroy(head->surface);
|
|
|
|
|
}
|
|
|
|
|
head->surface = draw_head(pango, &state->render, head->name,
|
|
|
|
|
render->tex_width, render->tex_height);
|
|
|
|
|
render->pixels = cairo_image_surface_get_data(head->surface);
|
|
|
|
|
render->tex_stride = cairo_image_surface_get_stride(head->surface);
|
|
|
|
|
render->updated_at = tick;
|
|
|
|
|
render->y_invert = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wd_gl_render(state->gl_data, &state->render, tick);
|
|
|
|
|
state->render.updated_at = tick;
|
2019-07-06 05:51:52 +03:00
|
|
|
|
}
|
|
|
|
|
|
2019-07-27 03:26:37 +03:00
|
|
|
|
static void canvas_unrealize(GtkWidget *widget, gpointer data) {
|
|
|
|
|
gtk_gl_area_make_current(GTK_GL_AREA(widget));
|
|
|
|
|
if (gtk_gl_area_get_error(GTK_GL_AREA(widget)) != NULL) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2019-07-06 05:51:52 +03:00
|
|
|
|
struct wd_state *state = data;
|
2019-07-27 03:26:37 +03:00
|
|
|
|
|
|
|
|
|
GdkDisplay *gdk_display = gdk_display_get_default();
|
|
|
|
|
struct wl_display *display = gdk_wayland_display_get_wl_display(gdk_display);
|
|
|
|
|
wd_capture_wait(state, display);
|
|
|
|
|
|
|
|
|
|
wd_gl_cleanup(state->gl_data);
|
|
|
|
|
state->gl_data = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static gboolean canvas_click(GtkWidget *widget, GdkEvent *event,
|
|
|
|
|
gpointer data) {
|
|
|
|
|
struct wd_state *state = data;
|
|
|
|
|
if (event->button.type == GDK_BUTTON_PRESS) {
|
|
|
|
|
if (event->button.button == 1) {
|
|
|
|
|
int i = 0;
|
|
|
|
|
struct wd_head *head;
|
|
|
|
|
wl_list_for_each(head, &state->heads, link) {
|
|
|
|
|
struct wd_render_head_data *render = head->render;
|
|
|
|
|
if (render != NULL) {
|
|
|
|
|
double mouse_x = event->button.x;
|
|
|
|
|
double mouse_y = event->button.y;
|
|
|
|
|
if (mouse_x >= render->x1 && mouse_x < render->x2 &&
|
|
|
|
|
mouse_y >= render->y1 && mouse_y < render->y2) {
|
|
|
|
|
state->clicked = head;
|
|
|
|
|
state->click_offset.x = event->button.x - render->x1;
|
|
|
|
|
state->click_offset.y = event->button.y - render->y1;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
i++;
|
|
|
|
|
}
|
|
|
|
|
if (state->clicked != NULL) {
|
|
|
|
|
g_autoptr(GList) forms = gtk_container_get_children(GTK_CONTAINER(state->stack));
|
|
|
|
|
for (GList *form_iter = forms; form_iter != NULL; form_iter = form_iter->next) {
|
|
|
|
|
const struct wd_head *other = g_object_get_data(G_OBJECT(form_iter->data), "head");
|
|
|
|
|
if (state->clicked == other) {
|
|
|
|
|
gtk_stack_set_visible_child(GTK_STACK(state->stack), form_iter->data);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if (event->button.button == 2) {
|
|
|
|
|
state->panning = true;
|
|
|
|
|
state->pan_last.x = event->button.x;
|
|
|
|
|
state->pan_last.y = event->button.y;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static gboolean canvas_release(GtkWidget *widget, GdkEvent *event,
|
|
|
|
|
gpointer data) {
|
|
|
|
|
struct wd_state *state = data;
|
|
|
|
|
if (event->button.button == 1) {
|
|
|
|
|
state->clicked = NULL;
|
|
|
|
|
}
|
|
|
|
|
if (event->button.button == 2) {
|
|
|
|
|
state->panning = false;
|
|
|
|
|
}
|
|
|
|
|
update_cursor(state);
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#define SNAP_DIST 6.
|
|
|
|
|
|
|
|
|
|
static gboolean canvas_motion(GtkWidget *widget, GdkEvent *event,
|
|
|
|
|
gpointer data) {
|
|
|
|
|
struct wd_state *state = data;
|
|
|
|
|
if (event->motion.state & GDK_BUTTON2_MASK) {
|
|
|
|
|
GtkAdjustment *xadj = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(state->scroller));
|
|
|
|
|
GtkAdjustment *yadj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(state->scroller));
|
|
|
|
|
double delta_x = event->motion.x - state->pan_last.x;
|
|
|
|
|
double delta_y = event->motion.y - state->pan_last.y;
|
|
|
|
|
gtk_adjustment_set_value(xadj, gtk_adjustment_get_value(xadj) + delta_x);
|
|
|
|
|
gtk_adjustment_set_value(yadj, gtk_adjustment_get_value(yadj) + delta_y);
|
|
|
|
|
state->pan_last.x = event->motion.x;
|
|
|
|
|
state->pan_last.y = event->motion.y;
|
|
|
|
|
queue_canvas_draw(state);
|
|
|
|
|
}
|
|
|
|
|
if ((event->motion.state & GDK_BUTTON1_MASK) && state->clicked != NULL) {
|
|
|
|
|
GtkWidget *form = NULL;
|
|
|
|
|
g_autoptr(GList) forms = gtk_container_get_children(GTK_CONTAINER(state->stack));
|
|
|
|
|
for (GList *form_iter = forms; form_iter != NULL; form_iter = form_iter->next) {
|
|
|
|
|
const struct wd_head *other = g_object_get_data(G_OBJECT(form_iter->data), "head");
|
|
|
|
|
if (state->clicked == other) {
|
|
|
|
|
form = form_iter->data;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (form != NULL) {
|
|
|
|
|
GtkBuilder *builder = GTK_BUILDER(g_object_get_data(G_OBJECT(form), "builder"));
|
|
|
|
|
struct wd_point size = {
|
|
|
|
|
.x = gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "width"))),
|
|
|
|
|
.y = gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "height"))),
|
|
|
|
|
};
|
|
|
|
|
struct wd_point tl = {
|
|
|
|
|
.x = (event->motion.x - state->click_offset.x
|
|
|
|
|
+ state->render.x_origin + state->render.scroll_x) / state->zoom,
|
|
|
|
|
.y = (event->motion.y - state->click_offset.y
|
|
|
|
|
+ state->render.y_origin + state->render.scroll_y) / state->zoom
|
|
|
|
|
};
|
|
|
|
|
const struct wd_point br = {
|
|
|
|
|
.x = tl.x + size.x,
|
|
|
|
|
.y = tl.y + size.y
|
|
|
|
|
};
|
|
|
|
|
struct wd_point new_pos = tl;
|
|
|
|
|
float snap = SNAP_DIST / state->zoom;
|
|
|
|
|
|
|
|
|
|
for (GList *form_iter = forms; form_iter != NULL; form_iter = form_iter->next) {
|
|
|
|
|
const struct wd_head *other = g_object_get_data(G_OBJECT(form_iter->data), "head");
|
|
|
|
|
if (other != state->clicked && !(event->motion.state & GDK_SHIFT_MASK)) {
|
|
|
|
|
GtkBuilder *other_builder = GTK_BUILDER(g_object_get_data(G_OBJECT(form_iter->data), "builder"));
|
|
|
|
|
double x1 = gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(other_builder, "pos_x")));
|
|
|
|
|
double y1 = gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(other_builder, "pos_y")));
|
|
|
|
|
double x2 = x1 + gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(other_builder, "width")));
|
|
|
|
|
double y2 = y1 + gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(other_builder, "height")));
|
|
|
|
|
if (fabs(br.x) <= snap)
|
|
|
|
|
new_pos.x = -size.x;
|
|
|
|
|
if (fabs(br.y) <= snap)
|
|
|
|
|
new_pos.y = -size.y;
|
|
|
|
|
if (fabs(br.x - x1) <= snap)
|
|
|
|
|
new_pos.x = x1 - size.x;
|
|
|
|
|
if (fabs(br.x - x2) <= snap)
|
|
|
|
|
new_pos.x = x2 - size.x;
|
|
|
|
|
if (fabs(br.y - y1) <= snap)
|
|
|
|
|
new_pos.y = y1 - size.y;
|
|
|
|
|
if (fabs(br.y - y2) <= snap)
|
|
|
|
|
new_pos.y = y2 - size.y;
|
|
|
|
|
|
|
|
|
|
if (fabs(tl.x) <= snap)
|
|
|
|
|
new_pos.x = 0.;
|
|
|
|
|
if (fabs(tl.y) <= snap)
|
|
|
|
|
new_pos.y = 0.;
|
|
|
|
|
if (fabs(tl.x - x1) <= snap)
|
|
|
|
|
new_pos.x = x1;
|
|
|
|
|
if (fabs(tl.x - x2) <= snap)
|
|
|
|
|
new_pos.x = x2;
|
|
|
|
|
if (fabs(tl.y - y1) <= snap)
|
|
|
|
|
new_pos.y = y1;
|
|
|
|
|
if (fabs(tl.y - y2) <= snap)
|
|
|
|
|
new_pos.y = y2;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
GtkWidget *pos_x = GTK_WIDGET(gtk_builder_get_object(builder, "pos_x"));
|
|
|
|
|
GtkWidget *pos_y = GTK_WIDGET(gtk_builder_get_object(builder, "pos_y"));
|
|
|
|
|
gtk_spin_button_set_value(GTK_SPIN_BUTTON(pos_x), new_pos.x);
|
|
|
|
|
gtk_spin_button_set_value(GTK_SPIN_BUTTON(pos_y), new_pos.y);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
update_hovered(state);
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static gboolean canvas_enter(GtkWidget *widget, GdkEvent *event,
|
|
|
|
|
gpointer data) {
|
|
|
|
|
struct wd_state *state = data;
|
|
|
|
|
if (!(event->crossing.state & GDK_BUTTON1_MASK)) {
|
|
|
|
|
state->clicked = NULL;
|
|
|
|
|
}
|
|
|
|
|
if (!(event->crossing.state & GDK_BUTTON2_MASK)) {
|
|
|
|
|
state->panning = false;
|
|
|
|
|
}
|
|
|
|
|
update_cursor(state);
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static gboolean canvas_leave(GtkWidget *widget, GdkEvent *event,
|
|
|
|
|
gpointer data) {
|
|
|
|
|
struct wd_state *state = data;
|
|
|
|
|
for (int i = 0; i < state->render.head_count; i++) {
|
|
|
|
|
struct wd_render_head_data *head = &state->render.heads[i];
|
|
|
|
|
head->hovered = false;
|
|
|
|
|
}
|
|
|
|
|
update_tick_callback(state);
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static gboolean canvas_scroll(GtkWidget *widget, GdkEvent *event,
|
|
|
|
|
gpointer data) {
|
|
|
|
|
struct wd_state *state = data;
|
|
|
|
|
if (event->scroll.state & GDK_CONTROL_MASK) {
|
|
|
|
|
switch (event->scroll.direction) {
|
|
|
|
|
case GDK_SCROLL_UP:
|
|
|
|
|
zoom_in(state);
|
|
|
|
|
break;
|
|
|
|
|
case GDK_SCROLL_DOWN:
|
|
|
|
|
zoom_out(state);
|
|
|
|
|
break;
|
|
|
|
|
case GDK_SCROLL_SMOOTH:
|
|
|
|
|
if (event->scroll.delta_y)
|
|
|
|
|
zoom_to(state, state->zoom * pow(0.75, event->scroll.delta_y));
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
GtkAdjustment *xadj = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(state->scroller));
|
|
|
|
|
GtkAdjustment *yadj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(state->scroller));
|
|
|
|
|
double xstep = gtk_adjustment_get_step_increment(xadj);
|
|
|
|
|
double ystep = gtk_adjustment_get_step_increment(yadj);
|
|
|
|
|
switch (event->scroll.direction) {
|
|
|
|
|
case GDK_SCROLL_UP:
|
|
|
|
|
gtk_adjustment_set_value(yadj, gtk_adjustment_get_value(yadj) - ystep);
|
|
|
|
|
break;
|
|
|
|
|
case GDK_SCROLL_DOWN:
|
|
|
|
|
gtk_adjustment_set_value(yadj, gtk_adjustment_get_value(yadj) + ystep);
|
|
|
|
|
break;
|
|
|
|
|
case GDK_SCROLL_LEFT:
|
|
|
|
|
gtk_adjustment_set_value(xadj, gtk_adjustment_get_value(xadj) - xstep);
|
|
|
|
|
break;
|
|
|
|
|
case GDK_SCROLL_RIGHT:
|
|
|
|
|
gtk_adjustment_set_value(xadj, gtk_adjustment_get_value(xadj) + xstep);
|
|
|
|
|
break;
|
|
|
|
|
case GDK_SCROLL_SMOOTH:
|
|
|
|
|
if (event->scroll.delta_x)
|
|
|
|
|
gtk_adjustment_set_value(xadj, gtk_adjustment_get_value(xadj) + xstep * event->scroll.delta_x);
|
|
|
|
|
if (event->scroll.delta_y)
|
|
|
|
|
gtk_adjustment_set_value(yadj, gtk_adjustment_get_value(yadj) + ystep * event->scroll.delta_y);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void canvas_resize(GtkWidget *widget, GdkRectangle *allocation,
|
|
|
|
|
gpointer data) {
|
|
|
|
|
struct wd_state *state = data;
|
|
|
|
|
update_scroll_size(state);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void cancel_changes(GtkButton *button, gpointer data) {
|
|
|
|
|
struct wd_state *state = data;
|
|
|
|
|
gtk_stack_set_visible_child_name(GTK_STACK(state->header_stack), "title");
|
|
|
|
|
wd_ui_reset_all(state);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void apply_changes(GtkButton *button, gpointer data) {
|
|
|
|
|
apply_state(data);
|
2019-07-06 05:51:52 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void info_response(GtkInfoBar *info_bar, gint response_id, gpointer data) {
|
|
|
|
|
gtk_info_bar_set_revealed(info_bar, FALSE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void info_bar_animation_done(GObject *object, GParamSpec *pspec, gpointer data) {
|
|
|
|
|
gboolean done = gtk_revealer_get_child_revealed(GTK_REVEALER(object));
|
|
|
|
|
if (!done) {
|
|
|
|
|
struct wd_state *state = data;
|
|
|
|
|
gtk_widget_set_visible(state->info_bar, gtk_revealer_get_reveal_child(GTK_REVEALER(object)));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-06 20:12:28 +03:00
|
|
|
|
static void auto_apply_selected(GSimpleAction *action, GVariant *param, gpointer data) {
|
|
|
|
|
struct wd_state *state = data;
|
|
|
|
|
state->autoapply = !state->autoapply;
|
|
|
|
|
g_simple_action_set_state(action, g_variant_new_boolean(state->autoapply));
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-27 03:26:37 +03:00
|
|
|
|
static gboolean redraw_canvas(GtkWidget *widget, GdkFrameClock *frame_clock, gpointer data) {
|
|
|
|
|
struct wd_state *state = data;
|
|
|
|
|
if (state->capture) {
|
|
|
|
|
wd_capture_frame(state);
|
|
|
|
|
}
|
|
|
|
|
queue_canvas_draw(state);
|
|
|
|
|
return G_SOURCE_CONTINUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void capture_selected(GSimpleAction *action, GVariant *param, gpointer data) {
|
|
|
|
|
struct wd_state *state = data;
|
|
|
|
|
state->capture = !state->capture;
|
|
|
|
|
g_simple_action_set_state(action, g_variant_new_boolean(state->capture));
|
|
|
|
|
update_tick_callback(state);
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-06 05:51:52 +03:00
|
|
|
|
static void activate(GtkApplication* app, gpointer user_data) {
|
|
|
|
|
GdkDisplay *gdk_display = gdk_display_get_default();
|
|
|
|
|
if (!GDK_IS_WAYLAND_DISPLAY(gdk_display)) {
|
|
|
|
|
wd_fatal_error(1, "This program is only usable on Wayland sessions.");
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-27 03:26:37 +03:00
|
|
|
|
struct wd_state *state = wd_state_create();
|
2019-07-06 05:51:52 +03:00
|
|
|
|
state->zoom = DEFAULT_ZOOM;
|
2019-07-27 03:26:37 +03:00
|
|
|
|
state->canvas_tick = -1;
|
2019-07-06 05:51:52 +03:00
|
|
|
|
|
|
|
|
|
GtkCssProvider *css_provider = gtk_css_provider_new();
|
|
|
|
|
gtk_css_provider_load_from_resource(css_provider, "/style.css");
|
|
|
|
|
gtk_style_context_add_provider_for_screen(gdk_screen_get_default(), GTK_STYLE_PROVIDER(css_provider),
|
|
|
|
|
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
|
|
|
|
|
|
2019-07-27 03:26:37 +03:00
|
|
|
|
state->grab_cursor = gdk_cursor_new_from_name(gdk_display, "grab");
|
|
|
|
|
state->grabbing_cursor = gdk_cursor_new_from_name(gdk_display, "grabbing");
|
|
|
|
|
state->move_cursor = gdk_cursor_new_from_name(gdk_display, "move");
|
|
|
|
|
|
2019-07-06 20:12:28 +03:00
|
|
|
|
GtkBuilder *builder = gtk_builder_new_from_resource("/wdisplay.ui");
|
2019-07-06 05:51:52 +03:00
|
|
|
|
GtkWidget *window = GTK_WIDGET(gtk_builder_get_object(builder, "heads_window"));
|
|
|
|
|
state->header_stack = GTK_WIDGET(gtk_builder_get_object(builder, "header_stack"));
|
|
|
|
|
state->stack_switcher = GTK_WIDGET(gtk_builder_get_object(builder, "heads_stack_switcher"));
|
|
|
|
|
state->stack = GTK_WIDGET(gtk_builder_get_object(builder, "heads_stack"));
|
|
|
|
|
state->scroller = GTK_WIDGET(gtk_builder_get_object(builder, "heads_scroll"));
|
|
|
|
|
state->spinner = GTK_WIDGET(gtk_builder_get_object(builder, "spinner"));
|
|
|
|
|
state->zoom_out = GTK_WIDGET(gtk_builder_get_object(builder, "zoom_out"));
|
|
|
|
|
state->zoom_reset = GTK_WIDGET(gtk_builder_get_object(builder, "zoom_reset"));
|
|
|
|
|
state->zoom_in = GTK_WIDGET(gtk_builder_get_object(builder, "zoom_in"));
|
|
|
|
|
state->overlay = GTK_WIDGET(gtk_builder_get_object(builder, "overlay"));
|
|
|
|
|
state->info_bar = GTK_WIDGET(gtk_builder_get_object(builder, "heads_info"));
|
|
|
|
|
state->info_label = GTK_WIDGET(gtk_builder_get_object(builder, "heads_info_label"));
|
2019-07-06 20:12:28 +03:00
|
|
|
|
state->menu_button = GTK_WIDGET(gtk_builder_get_object(builder, "menu_button"));
|
2019-07-27 03:26:37 +03:00
|
|
|
|
|
2019-07-06 05:51:52 +03:00
|
|
|
|
gtk_builder_add_callback_symbol(builder, "apply_changes", G_CALLBACK(apply_changes));
|
|
|
|
|
gtk_builder_add_callback_symbol(builder, "cancel_changes", G_CALLBACK(cancel_changes));
|
|
|
|
|
gtk_builder_add_callback_symbol(builder, "zoom_out", G_CALLBACK(zoom_out));
|
|
|
|
|
gtk_builder_add_callback_symbol(builder, "zoom_reset", G_CALLBACK(zoom_reset));
|
|
|
|
|
gtk_builder_add_callback_symbol(builder, "zoom_in", G_CALLBACK(zoom_in));
|
|
|
|
|
gtk_builder_add_callback_symbol(builder, "info_response", G_CALLBACK(info_response));
|
2019-07-06 20:12:28 +03:00
|
|
|
|
gtk_builder_add_callback_symbol(builder, "destroy", G_CALLBACK(cleanup));
|
2019-07-06 05:51:52 +03:00
|
|
|
|
gtk_builder_connect_signals(builder, state);
|
|
|
|
|
gtk_box_set_homogeneous(GTK_BOX(gtk_builder_get_object(builder, "zoom_box")), FALSE);
|
2019-07-27 03:26:37 +03:00
|
|
|
|
|
|
|
|
|
state->canvas = wd_gl_viewport_new();
|
|
|
|
|
gtk_container_add(GTK_CONTAINER(state->scroller), state->canvas);
|
|
|
|
|
gtk_widget_add_events(state->canvas, GDK_POINTER_MOTION_MASK
|
|
|
|
|
| GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_SCROLL_MASK
|
|
|
|
|
| GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
|
|
|
|
|
g_signal_connect(state->canvas, "realize", G_CALLBACK(canvas_realize), state);
|
|
|
|
|
g_signal_connect(state->canvas, "render", G_CALLBACK(canvas_render), state);
|
|
|
|
|
g_signal_connect(state->canvas, "unrealize", G_CALLBACK(canvas_unrealize), state);
|
|
|
|
|
g_signal_connect(state->canvas, "button-press-event", G_CALLBACK(canvas_click), state);
|
|
|
|
|
g_signal_connect(state->canvas, "button-release-event", G_CALLBACK(canvas_release), state);
|
|
|
|
|
g_signal_connect(state->canvas, "enter-notify-event", G_CALLBACK(canvas_enter), state);
|
|
|
|
|
g_signal_connect(state->canvas, "leave-notify-event", G_CALLBACK(canvas_leave), state);
|
|
|
|
|
g_signal_connect(state->canvas, "motion-notify-event", G_CALLBACK(canvas_motion), state);
|
|
|
|
|
g_signal_connect(state->canvas, "scroll-event", G_CALLBACK(canvas_scroll), state);
|
|
|
|
|
g_signal_connect(state->canvas, "size-allocate", G_CALLBACK(canvas_resize), state);
|
|
|
|
|
gtk_gl_area_set_use_es(GTK_GL_AREA(state->canvas), TRUE);
|
|
|
|
|
gtk_gl_area_set_has_alpha(GTK_GL_AREA(state->canvas), TRUE);
|
|
|
|
|
gtk_gl_area_set_auto_render(GTK_GL_AREA(state->canvas), state->capture);
|
|
|
|
|
|
|
|
|
|
GtkAdjustment *scroll_x_adj = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(state->scroller));
|
|
|
|
|
GtkAdjustment *scroll_y_adj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(state->scroller));
|
|
|
|
|
g_signal_connect_swapped(scroll_x_adj, "value-changed", G_CALLBACK(queue_canvas_draw), state);
|
|
|
|
|
g_signal_connect_swapped(scroll_y_adj, "value-changed", G_CALLBACK(queue_canvas_draw), state);
|
|
|
|
|
|
2019-07-06 05:51:52 +03:00
|
|
|
|
update_zoom(state);
|
|
|
|
|
|
2019-07-06 20:12:28 +03:00
|
|
|
|
GSimpleActionGroup *main_actions = g_simple_action_group_new();
|
|
|
|
|
gtk_widget_insert_action_group(state->menu_button, APP_PREFIX, G_ACTION_GROUP(main_actions));
|
|
|
|
|
g_object_unref(main_actions);
|
|
|
|
|
|
|
|
|
|
GSimpleAction *autoapply_action = g_simple_action_new_stateful("auto-apply", NULL,
|
|
|
|
|
g_variant_new_boolean(state->autoapply));
|
|
|
|
|
g_signal_connect(autoapply_action, "activate", G_CALLBACK(auto_apply_selected), state);
|
|
|
|
|
g_action_map_add_action(G_ACTION_MAP(main_actions), G_ACTION(autoapply_action));
|
|
|
|
|
|
2019-07-27 03:26:37 +03:00
|
|
|
|
GSimpleAction *capture_action = g_simple_action_new_stateful("capture-screens", NULL,
|
|
|
|
|
g_variant_new_boolean(state->capture));
|
|
|
|
|
g_signal_connect(capture_action, "activate", G_CALLBACK(capture_selected), state);
|
|
|
|
|
g_action_map_add_action(G_ACTION_MAP(main_actions), G_ACTION(capture_action));
|
|
|
|
|
|
2019-07-06 05:51:52 +03:00
|
|
|
|
/* first child of GtkInfoBar is always GtkRevealer */
|
|
|
|
|
g_autoptr(GList) info_children = gtk_container_get_children(GTK_CONTAINER(state->info_bar));
|
|
|
|
|
g_signal_connect(info_children->data, "notify::child-revealed", G_CALLBACK(info_bar_animation_done), state);
|
|
|
|
|
|
|
|
|
|
struct wl_display *display = gdk_wayland_display_get_wl_display(gdk_display);
|
|
|
|
|
wd_add_output_management_listener(state, display);
|
|
|
|
|
|
|
|
|
|
if (state->output_manager == NULL) {
|
|
|
|
|
wd_fatal_error(1, "Compositor doesn't support wlr-output-management-unstable-v1");
|
|
|
|
|
}
|
2019-07-27 03:26:37 +03:00
|
|
|
|
if (state->xdg_output_manager == NULL) {
|
|
|
|
|
wd_fatal_error(1, "Compositor doesn't support xdg-output-unstable-v1");
|
|
|
|
|
}
|
|
|
|
|
if (state->copy_manager == NULL) {
|
|
|
|
|
state->capture = false;
|
|
|
|
|
g_simple_action_set_state(capture_action, g_variant_new_boolean(state->capture));
|
|
|
|
|
g_simple_action_set_enabled(capture_action, FALSE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int n_monitors = gdk_display_get_n_monitors(gdk_display);
|
|
|
|
|
for (int i = 0; i < n_monitors; i++) {
|
|
|
|
|
GdkMonitor *monitor = gdk_display_get_monitor(gdk_display, i);
|
|
|
|
|
wd_add_output(state, gdk_wayland_monitor_get_wl_output(monitor));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g_signal_connect(gdk_display, "monitor-added", G_CALLBACK(monitor_added), state);
|
|
|
|
|
g_signal_connect(gdk_display, "monitor-removed", G_CALLBACK(monitor_removed), state);
|
2019-07-06 05:51:52 +03:00
|
|
|
|
|
|
|
|
|
gtk_application_add_window(app, GTK_WINDOW(window));
|
|
|
|
|
gtk_widget_show_all(window);
|
|
|
|
|
g_object_unref(builder);
|
|
|
|
|
}
|
|
|
|
|
// END GLOBAL CALLBACKS
|
|
|
|
|
|
|
|
|
|
int main(int argc, char *argv[]) {
|
|
|
|
|
GtkApplication *app = gtk_application_new("org.swaywm.sway-outputs", G_APPLICATION_FLAGS_NONE);
|
|
|
|
|
g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
|
|
|
|
|
int status = g_application_run(G_APPLICATION(app), argc, argv);
|
|
|
|
|
g_object_unref(app);
|
|
|
|
|
|
|
|
|
|
return status;
|
|
|
|
|
}
|