#include #include #include #include #include #include #include #include "generated.h" #include "rcm-client-main.h" #include "rcm-client-receive.h" #define WINDOW_WIDTH 300 #define WINDOW_HEIGHT 300 struct remote { GList *buttons; GList *header_buttons; guint width; guint height; struct rcbutton *hover; bool editing; }; struct rcbutton { char *name; bool clicked; guint x; guint y; guint row; guint col; guint rows; guint cols; guint width; guint height; guint padding; guint corner_radius; struct rcbutton *next; GdkRGBA *color; void (*on_hover)(struct rcbutton *rcb, GtkWidget *widget, bool hover); }; static struct remote *remote = NULL; static void curve_rectangle(cairo_t * cr, gdouble x0, gdouble y0, gdouble width, gdouble height, gdouble radius) { gdouble x1, y1; if (!width || !height) return; x1 = x0 + width; y1 = y0 + height; radius = MIN (radius, MIN (width / 2, height / 2)); cairo_move_to (cr, x0, y0 + radius); cairo_arc (cr, x0 + radius, y0 + radius, radius, M_PI, 3 * M_PI / 2); cairo_line_to (cr, x1 - radius, y0); cairo_arc (cr, x1 - radius, y0 + radius, radius, 3 * M_PI / 2, 2 * M_PI); cairo_line_to (cr, x1, y1 - radius); cairo_arc (cr, x1 - radius, y1 - radius, radius, 0, M_PI / 2); cairo_line_to (cr, x0 + radius, y1); cairo_arc (cr, x0 + radius, y1 - radius, radius, M_PI / 2, M_PI); cairo_close_path (cr); } GdkRGBA *c; GdkRGBA *cd; GdkRGBA *cre; GdkRGBA *cb; GdkRGBA cgreen; GdkRGBA cred; static void draw_curve_rectangle(cairo_t * cr, struct rcbutton *rcb) { double tx, ty; cairo_text_extents_t extents; const char *label = rcb->name ? rcb->name : "?"; curve_rectangle(cr, rcb->x, rcb->y, rcb->width, rcb->height, rcb->corner_radius); if (rcb->color) gdk_cairo_set_source_rgba(cr, rcb->color); else gdk_cairo_set_source_rgba(cr, rcb->clicked ? cre : cd); cairo_fill_preserve(cr); gdk_cairo_set_source_rgba(cr, cb); cairo_set_line_width(cr, 1.0); cairo_stroke(cr); /* if (filled) cairo_fill(cr); else { cairo_set_line_width(cr, 1.0); cairo_stroke(cr); } */ cairo_save(cr); cairo_rectangle(cr, rcb->x + rcb->padding, rcb->y + rcb->padding, rcb->width - rcb->padding * 2, rcb->height - rcb->padding * 2); cairo_clip(cr); cairo_set_font_size(cr, 13); cairo_text_extents (cr, label, &extents); tx = rcb->x + (rcb->width / 2) - (extents.width / 2 + extents.x_bearing); ty = rcb->y + (rcb->height / 2) - (extents.height / 2 + extents.y_bearing); cairo_set_source_rgb(cr, 0.1, 0.1, 0.1); cairo_move_to(cr, tx, ty); cairo_show_text(cr, label); cairo_restore(cr); cairo_new_sub_path(cr); } #define SPACING 5 #define PADDING 5 #define BW 56 #define BH 56 static gboolean draw_cb(GtkWidget *widget, cairo_t *cr, gpointer data) { struct rcbutton *rcb; guint w = 0, h = 0; /* Set color for background */ gdk_cairo_set_source_rgba(cr, c); /* fill in the background color*/ cairo_paint(cr); GList *l; for (l = remote->buttons; l; l = l->next) { rcb = l->data; draw_curve_rectangle(cr, rcb); if (rcb->x + rcb->width > w) w = rcb->x + rcb->width; if (rcb->y + rcb->height > h) h = rcb->y + rcb->height; } w += (SPACING + BW) / 2; h += (SPACING + BH) / 2; /*->name ? rcb->name : "?", false, rcb->clicked ? cre : cd, rcb->x, rcb->y, rcb->width, rcb->height, rcb->padding, rcb->corner_radius); */ gtk_widget_set_size_request(widget, w, h); gtk_widget_set_halign(widget, GTK_ALIGN_CENTER); gtk_widget_set_valign(widget, GTK_ALIGN_CENTER); gtk_widget_set_margin_top(widget, BH / 2); gtk_widget_set_margin_bottom(widget, BH / 2); gtk_widget_set_margin_start(widget, BW / 2); gtk_widget_set_margin_end(widget, BW / 2); return FALSE; } #if 0 static struct rcbutton *new_button(const char *name, guint row, guint col) { struct rcbutton *rcb; rcb = g_malloc(sizeof(*rcb)); rcb->name = strdup(name); rcb->clicked = false; rcb->x = (SPACING + BW) * (col) + (SPACING + BW) / 2; rcb->y = (SPACING + BH) * (row) + (SPACING + BH) / 2; rcb->width = BW; rcb->height = BH; rcb->padding = PADDING; rcb->corner_radius = 3; rcb->color = NULL; return rcb; } #endif static void button_calculate_position(struct rcbutton *rcb) { rcb->x = (SPACING + BW) * (rcb->col) + (SPACING + BW) / 2; rcb->y = (SPACING + BH) * (rcb->row) + (SPACING + BH) / 2; rcb->width = BW + (rcb->cols - 1) * (BW + SPACING); rcb->height = BH + (rcb->rows - 1) * (BH + SPACING); } static void move_buttons(gint row, gint col) { GList *l; for (l = remote->buttons; l; l = l->next) { struct rcbutton *rcb = l->data; rcb->row += row; rcb->col += col; button_calculate_position(rcb); } } static struct rcbutton * new_button_add(const char *name, GdkRGBA *color, guint x, guint y, guint w, guint h) { struct rcbutton *rcb; rcb = g_malloc0(sizeof(*rcb)); rcb->name = strdup(name); rcb->clicked = false; rcb->col = x; rcb->row = y; rcb->cols = w; rcb->rows = h; button_calculate_position(rcb); rcb->padding = PADDING; rcb->corner_radius = 3; rcb->color = color; remote->buttons = g_list_append(remote->buttons, rcb); remote->width = MAX(remote->width, w); remote->height = MAX(remote->height, h); return rcb; } #if 0 static struct rcbutton *new_button_addr(const char *name, guint row, guint col) { struct rcbutton *rcb; rcb = g_malloc(sizeof(*rcb)); rcb->name = strdup(name); rcb->clicked = false; rcb->x = (SPACING + BW) / 2; rcb->y = (SPACING + BH) / 2; rcb->width = (SPACING + BW) * (col) - SPACING; rcb->height = BH; rcb->padding = PADDING; rcb->corner_radius = 3; rcb->color = &cred; return rcb; } #endif static void button_redraw(struct rcbutton *rcb, GtkWidget *widget) { printf("Asked to redraw: X,Y,W,H - %u,%u,%u,%u\n", rcb->x - 1, rcb->y - 1, rcb->width + 2, rcb->height + 2); gtk_widget_queue_draw_area(widget, rcb->x - 1, rcb->y - 1, rcb->width + 2, rcb->height + 2); } static void quick_message(GtkWindow *parent, gchar *message) { GtkWidget *dialog, *label, *content_area; GtkDialogFlags flags; flags = GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL; dialog = gtk_dialog_new_with_buttons("Message", parent, flags, "OK", GTK_RESPONSE_NONE, NULL); content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); label = gtk_label_new(message); g_signal_connect_swapped(dialog, "response", G_CALLBACK(gtk_widget_destroy), dialog); gtk_container_add(GTK_CONTAINER(content_area), label); gtk_widget_show_all(dialog); } static gboolean button_press_event_cb(GtkWidget *widget, GdkEventButton *event, gpointer data) { struct rcbutton *rcb; GList *l; if (event->button != GDK_BUTTON_PRIMARY) return true; for (l = remote->buttons; l; l = l->next) { rcb = l->data; if (event->x < rcb->x || event->x > (rcb->x + rcb->width)) continue; if (event->y < rcb->y || event->y > (rcb->y + rcb->height)) continue; if (remote->editing) { printf("Button at %ux%u clicked in edit mode\n", rcb->x, rcb->y); quick_message(GTK_WINDOW(gtk_widget_get_toplevel(widget)), "Edit button pressed"); } else { rcb->clicked = true; button_redraw(rcb, widget); printf("Button at %ux%u clicked\n", rcb->x, rcb->y); poke_objects(); } } return true; } static gboolean button_release_event_cb(GtkWidget *widget, GdkEventButton *event, gpointer data) { struct rcbutton *rcb; GList *l; if (event->button != GDK_BUTTON_PRIMARY) return true; for (l = remote->buttons; l; l = l->next) { rcb = l->data; if (!rcb->clicked) continue; rcb->clicked = false; button_redraw(rcb, widget); } return true; } static gboolean motion_notify_event_cb(GtkWidget *widget, GdkEventMotion *event, gpointer data) { struct rcbutton *rcb; static GdkCursor *cursor = NULL; GdkWindow* win = gtk_widget_get_parent_window(widget); GList *l; if (!cursor) cursor = gdk_cursor_new_for_display(gdk_window_get_display(win), GDK_HAND1); for (l = remote->buttons; l; l = l->next) { rcb = l->data; if (event->x < rcb->x || event->x > (rcb->x + rcb->width)) continue; if (event->y < rcb->y || event->y > (rcb->y + rcb->height)) continue; break; } if (!l) rcb = NULL; if (remote->hover == rcb) return true; gdk_window_set_cursor(win, rcb ? cursor : NULL); if (remote->hover && remote->hover->on_hover) { remote->hover->on_hover(remote->hover, widget, false); remote->hover = NULL; } if (rcb && rcb->on_hover) { rcb->on_hover(rcb, widget, true); remote->hover = rcb; } return true; } static GtkWidget *scrollda = NULL; void rcng_client_receive_destroy_ui() { if (scrollda) gtk_widget_destroy(scrollda); scrollda = NULL; } static gint own_page_num = -1; static GtkWidget *da; #if 0 static void edit_keymap_done(GtkButton *button, gpointer user_data) { } #endif static void on_hover_delete(struct rcbutton *rcb, GtkWidget *widget, bool hover) { if (hover) { if (rcb->col == 0 && rcb->cols == 1) { rcb->cols = 2 + remote->width; button_calculate_position(rcb); button_redraw(rcb, widget); } else if (rcb->row == 0 && rcb->rows == 1) { rcb->rows = 2 + remote->height; button_calculate_position(rcb); button_redraw(rcb, widget); } } else { if (rcb->col == 0 && rcb->cols != 1) { button_redraw(rcb, widget); rcb->cols = 1; button_calculate_position(rcb); } else if (rcb->row == 0 && rcb->rows != 1) { button_redraw(rcb, widget); rcb->rows = 1; button_calculate_position(rcb); } } } /* FIXME: These two functions should probably move to the core */ static void create_header_button(const gchar *label, const gchar *icon_name, GCallback callback, gpointer user_data) { GtkWidget *button; button = gtk_button_new_from_icon_name(icon_name, GTK_ICON_SIZE_BUTTON); gtk_button_set_label(GTK_BUTTON(button), label); gtk_button_set_always_show_image(GTK_BUTTON(button), TRUE); g_signal_connect(button, "clicked", callback, user_data); gtk_widget_show_all(button); gtk_header_bar_pack_end(GTK_HEADER_BAR(global->header), button); remote->header_buttons = g_list_prepend(remote->header_buttons, button); } static void remove_header_buttons(void) { GList *l; for (l = remote->header_buttons; l; l = l->next) gtk_widget_destroy(l->data); g_list_free(remote->header_buttons); remote->header_buttons = NULL; } static void edit_keymap(GtkButton *button, gpointer user_data) { guint i; //struct remote *remote = user_data; struct rcbutton *rcb; g_print("Button clicked\n"); g_print("Remote width: %u x %u\n", remote->width, remote->height); if (remote->width < 1 || remote->height < 1) return; if (!remote->editing) { remove_header_buttons(); create_header_button("Done Editing Keymap", "gtk-apply", G_CALLBACK(edit_keymap), &remote); move_buttons(2, 2); new_button_add("+", &cgreen, 2, 1, remote->width, 1); new_button_add("+", &cgreen, 2, 2 + remote->height, remote->width, 1); new_button_add("+", &cgreen, 1, 2, 1, remote->height); new_button_add("+", &cgreen, 2 + remote->width, 2, 1, remote->height); for (i = 0; i < remote->height; i++) { rcb = new_button_add("-", &cred, 0, 2 + i, 1, 1); rcb->on_hover = on_hover_delete; } for (i = 0; i < remote->width; i++) { rcb = new_button_add("-", &cred, 2 + i, 0, 1, 1); rcb->on_hover = on_hover_delete; } gtk_widget_queue_draw(da); } else { GList *l; remove_header_buttons(); create_header_button("Edit Keymap", "list-add", G_CALLBACK(edit_keymap), &remote); l = remote->buttons; while (l) { GList *next = l->next; struct rcbutton *rcb = l->data; if (rcb->color) { g_free(rcb); remote->buttons = g_list_delete_link (remote->buttons, l); } l = next; } move_buttons(-2, -2); gtk_widget_queue_draw(da); } remote->editing = !remote->editing; } static void on_notebook_page_change(GtkNotebook *notebook, GtkWidget *page, guint new_page_num, gpointer user_data) { if (new_page_num != own_page_num) { remove_header_buttons(); remote->editing = false; return; } /* FIXME: call edit_keymap() instead */ create_header_button("Edit Keymap", "list-add", G_CALLBACK(edit_keymap), &remote); g_print("Page change: %i -> %u\n", own_page_num, new_page_num); } void rcng_client_receive_init_ui(GDBusObject *new_hw) { GtkStateFlags state; GtkStyleContext *style_context; static bool first = true; if (first) { g_signal_connect(global->notebook, "switch-page", G_CALLBACK(on_notebook_page_change), NULL); first = false; } rcng_client_receive_destroy_ui(); GDBusInterface *interface; interface = g_dbus_object_get_interface(new_hw, "org.gnome.RemoteControlManager.Device"); if (!interface) return; /* FIXME: if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(a(uuuuaa{sv}))"))) */ gchar **keymaps = NULL; RCDevice *object = RCDEVICE(interface); rcdevice_call_list_keymaps_sync(object, &keymaps, NULL, NULL); g_print("I should look at keymap: %s\n", keymaps[0]); guint16 km_width; guint16 km_height; GVariant *km_entries = NULL; rcdevice_call_get_keymap_sync(object, keymaps[0], &km_width, &km_height, &km_entries, NULL, NULL); g_free(keymaps); remote = g_malloc0(sizeof(*remote)); remote->width = km_width; remote->height = km_height; remote->editing = false; g_print("type of keymaps is %s\n", g_variant_get_type_string(km_entries)); GVariantIter iter; gsize n_items; GVariant *item; style_context = gtk_widget_get_style_context(GTK_WIDGET(global->window)); state = gtk_widget_get_state_flags(GTK_WIDGET(global->window)); gtk_style_context_get(style_context, state, "background-color", &c, NULL); cd = gdk_rgba_copy(c); cd->red *= 0.7; cd->green *= 0.7; cd->blue *= 0.7; cre = gdk_rgba_copy(c); cre->red *= 2.0; cb = gdk_rgba_copy(c); cb->red = 0.0; cb->green = 0.0; cb->blue = 0.0; cb->alpha = 1.0; gdk_rgba_parse(&cgreen, "#a9b68f"); gdk_rgba_parse(&cred, "#cf8989"); cred.alpha = 0.8; n_items = g_variant_iter_init(&iter, km_entries); g_print("Key items: %zu\n", n_items); guint row = 0; guint col = 0; while (g_variant_iter_loop (&iter, "@a{sv}", &item)) { gchar *protocol; guint64 scancode; gchar *keycode; g_variant_lookup(item, "protocol", "s", &protocol); g_variant_lookup(item, "scancode", "t", &scancode); g_variant_lookup(item, "keycode", "s", &keycode); new_button_add(keycode, NULL, col, row, 1, 1); col++; if (col >= km_width) { col = 0; row++; } printf("Got a key: %s:0x%08" PRIx64 ":%s\n", protocol, scancode, keycode); } da = gtk_drawing_area_new(); // gtk_widget_set_size_request (da, WINDOW_WIDTH, WINDOW_HEIGHT); g_signal_connect (da, "draw", G_CALLBACK(draw_cb), NULL); g_signal_connect(da, "button-press-event", G_CALLBACK (button_press_event_cb), NULL); g_signal_connect(da, "button-release-event", G_CALLBACK (button_release_event_cb), NULL); g_signal_connect(da, "motion-notify-event", G_CALLBACK (motion_notify_event_cb), NULL); gtk_widget_set_events (da, gtk_widget_get_events (da) | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK); scrollda = gtk_scrolled_window_new(NULL, NULL); gtk_container_add(GTK_CONTAINER(scrollda), da); gtk_widget_show_all(scrollda); own_page_num = gtk_notebook_append_page(global->notebook, scrollda, gtk_label_new("Receive")); }