#include #include #include #include #include #include #include #include "generated.h" #include "rcm-client-main.h" #include "rcm-client-receive.h" #include "linux-input-keycodes.h" #define WINDOW_WIDTH 300 #define WINDOW_HEIGHT 300 struct rcbutton { char *name; GtkWidget *button; }; struct remote { char *name; guint16 width; guint16 height; GList *buttons; GtkWidget *widget; GtkWidget *grid; GtkWidget *edit_button; }; struct state { GList *remotes; GList *header_buttons; bool editing; bool active; }; static struct state state; #if 0 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); } #endif /* Hacky McHack - Estimate the size needed for a button with ABCDEF */ static int calculate_img_size(GtkWidget *button) { static bool first = true; static int size; cairo_surface_t *surface; cairo_t *ct; PangoLayout *layout; PangoFontDescription *font_description; if (!first) return size; surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1); ct = cairo_create(surface); layout = pango_cairo_create_layout(ct); gtk_style_context_get(gtk_widget_get_style_context(button), gtk_widget_get_state_flags(button), GTK_STYLE_PROPERTY_FONT, &font_description, NULL); pango_layout_set_font_description(layout, font_description); pango_font_description_free(font_description); pango_layout_set_text(layout, "ABCDEF", -1); pango_layout_get_pixel_size(layout, &size, NULL); g_object_unref(layout); cairo_destroy(ct); cairo_surface_destroy(surface); first = false; return size; } #define REMOTE_BUTTON_PADDING 2.0f static void create_button_img(GtkWidget *button, const char *keycode) { struct linux_input_keycode *lik; cairo_surface_t *surface; cairo_t *ct; PangoLayout *layout; PangoFontDescription *font_description; GtkWidget *img; const char *label = keycode; double x, y; double scale_x, scale_y; double scale = 1.0; GdkRGBA *color; gint pw, ph; int img_size; for (lik = &linux_input_keycodes[0]; lik->name; lik++) if (keycode && !strcmp(lik->name, keycode)) break; if (!lik->name) { printf("Unknown keycode! %s\n", keycode); label = ""; } if (lik->cairo_surface) goto out; img_size = calculate_img_size(button); surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, img_size, img_size); ct = cairo_create(surface); layout = pango_cairo_create_layout(ct); gtk_style_context_get(gtk_widget_get_style_context(button), gtk_widget_get_state_flags(button), GTK_STYLE_PROPERTY_FONT, &font_description, GTK_STYLE_PROPERTY_COLOR, &color, NULL); pango_layout_set_font_description(layout, font_description); pango_font_description_free(font_description); gdk_cairo_set_source_rgba(ct, color); gdk_rgba_free(color); pango_layout_set_text(layout, label, -1); pango_layout_get_pixel_size(layout, &pw, &ph); scale_x = ((float)img_size - REMOTE_BUTTON_PADDING * 2.0f) / (float)pw; scale_y = ((float)img_size - REMOTE_BUTTON_PADDING * 2.0f) / (float)ph; scale = scale_x < scale ? scale_x : scale; scale = scale_y < scale ? scale_y : scale; x = (float)img_size / 2.0f - ((float)pw / 2.0f) * scale; y = (float)img_size / 2.0f - ((float)ph / 2.0f) * scale; cairo_move_to(ct, x, y); cairo_scale(ct, scale, scale); pango_cairo_show_layout(ct, layout); g_object_unref(layout); cairo_destroy(ct); lik->cairo_surface = surface; out: img = gtk_image_new_from_surface(lik->cairo_surface); gtk_button_set_image(GTK_BUTTON(button), img); } static struct rcbutton * new_button_add(GtkGrid *grid, const gchar *protocol, guint64 scancode, const char *keycode, GdkRGBA *color, guint x, guint y) { struct rcbutton *rcb; gchar *tooltip; rcb = g_malloc0(sizeof(*rcb)); rcb->name = strdup(keycode); rcb->button = gtk_toggle_button_new(); gtk_widget_set_sensitive(rcb->button, false); tooltip = g_strdup_printf("Protocol: %s\nScancode: 0x%08" PRIx64 "\nKeycode : %s", protocol, scancode, keycode); gtk_widget_set_tooltip_text(rcb->button, tooltip); g_free(tooltip); gtk_grid_attach(grid, rcb->button, x, y, 1, 1); create_button_img(rcb->button, keycode); return rcb; } static GtkWidget *scrollda = NULL; void rcng_client_receive_destroy_ui() { if (scrollda) gtk_widget_destroy(scrollda); scrollda = NULL; } static gint own_page_num = -1; /* 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); state.header_buttons = g_list_prepend(state.header_buttons, button); } static void remove_header_buttons(void) { GList *l; for (l = state.header_buttons; l; l = l->next) gtk_widget_destroy(l->data); g_list_free(state.header_buttons); state.header_buttons = NULL; } static void toggle_edit_keymap(GtkButton *button, gpointer user_data); static void resize_keymap(GtkButton *button, gpointer user_data) { struct remote *remote = user_data; GtkWindow *parent = GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(button))); GtkWidget *dialog; GtkWidget *content_area; GtkWidget *grid; GtkWidget *width_label; GtkAdjustment *width_adj; GtkWidget *width_input; GtkWidget *height_label; GtkAdjustment *height_adj; GtkWidget *height_input; gint r; gint width; gint height; dialog = gtk_dialog_new_with_buttons("Resize Keymap", parent, GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, "OK", GTK_RESPONSE_ACCEPT, "Cancel", GTK_RESPONSE_REJECT, NULL); grid = gtk_grid_new(); gtk_grid_set_column_spacing(GTK_GRID(grid), 12); gtk_grid_set_row_spacing(GTK_GRID(grid), 12); gtk_container_set_border_width(GTK_CONTAINER(grid), 12); width_label = gtk_label_new("Width:"); gtk_widget_set_halign(width_label, GTK_ALIGN_END); width_adj = gtk_adjustment_new(remote->width, 1.0, 100.0, 1.0, 5.0, 0.0); width_input = gtk_spin_button_new(width_adj, 1.0, 0); gtk_grid_attach(GTK_GRID(grid), width_label, 1, 1, 1, 1); gtk_grid_attach(GTK_GRID(grid), width_input, 2, 1, 1, 1); height_label = gtk_label_new("Height:"); gtk_widget_set_halign(height_label, GTK_ALIGN_END); height_adj = gtk_adjustment_new(remote->height, 1.0, 100.0, 1.0, 5.0, 0.0); height_input = gtk_spin_button_new(height_adj, 1.0, 0); gtk_grid_attach(GTK_GRID(grid), height_label, 1, 2, 1, 1); gtk_grid_attach(GTK_GRID(grid), height_input, 2, 2, 1, 1); content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); gtk_container_set_border_width(GTK_CONTAINER(content_area), 12); gtk_container_add(GTK_CONTAINER(content_area), grid); gtk_widget_show_all(dialog); r = gtk_dialog_run(GTK_DIALOG(dialog)); if (r != GTK_RESPONSE_ACCEPT) goto out; width = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(width_input)); height = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(height_input)); printf("Asked to resize keymap '%s' to to %ix%i\n", remote->name, width, height); out: gtk_widget_destroy(dialog); } static void set_edit_keymap(bool editing) { GList *l; remove_header_buttons(); if (editing) create_header_button("Apply", "gtk-apply", G_CALLBACK(toggle_edit_keymap), NULL); else create_header_button("Edit", "list-add", G_CALLBACK(toggle_edit_keymap), NULL); for (l = state.remotes; l; l = l->next) { struct remote *remote = l->data; if (editing) { remote->edit_button = gtk_button_new_with_label("Resize"); g_signal_connect(remote->edit_button, "clicked", G_CALLBACK(resize_keymap), remote); gtk_grid_attach(GTK_GRID(remote->grid), remote->edit_button, 0, -1, remote->width, 1); gtk_widget_show_all(remote->edit_button); } else { if (remote->edit_button) gtk_widget_destroy(remote->edit_button); remote->edit_button = NULL; } } } static void toggle_edit_keymap(GtkButton *button, gpointer user_data) { state.editing = !state.editing; set_edit_keymap(state.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(); state.editing = false; state.active = false; return; } state.active = true; state.editing = false; set_edit_keymap(state.editing); g_print("Page change: %i -> %u\n", own_page_num, new_page_num); } void rcng_client_receive_keypress(GDBusObject *obj, const gchar *keycode, bool pressed) { GList *l; unsigned affected = 0; /* FIXME: Use object path, this is just test code */ if (!state.active) return; for (l = state.remotes; l; l = l->next) { struct remote *remote = l->data; GList *button; for (button = remote->buttons; button; button = button->next) { struct rcbutton *rcb = button->data; if (strcmp(keycode, rcb->name)) continue; gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rcb->button), pressed); affected++; } } g_print("Key %s %s (%u affected)\n", keycode, pressed ? "pressed" : "released", affected); } static struct remote * get_keymap(RCDevice *object, const gchar *keymap_name) { struct remote *remote; GVariant *km_entries = NULL; remote = g_malloc0(sizeof(*remote)); remote->name = strdup(keymap_name); rcdevice_call_get_keymap_sync(object, keymap_name, &remote->width, &remote->height, &km_entries, NULL, NULL); g_print("type of keymaps is %s\n", g_variant_get_type_string(km_entries)); GVariantIter iter; gsize n_items; GVariant *item; n_items = g_variant_iter_init(&iter, km_entries); g_print("Key items: %zu\n", n_items); guint row = 0; guint col = 0; GtkWidget *fixed; fixed = gtk_fixed_new(); gtk_widget_set_name(fixed, "RemoteControlClientFixed"); remote->widget = fixed; GtkWidget *frame; frame = gtk_frame_new(keymap_name); gtk_widget_set_name(gtk_frame_get_label_widget(GTK_FRAME(frame)), "RemoteControlClientRemoteLabel"); gtk_widget_set_name(frame, "RemoteControlClientRemoteFrame"); gtk_fixed_put(GTK_FIXED(fixed), frame, 0, 0); GtkWidget *grid; grid = gtk_grid_new(); gtk_container_add(GTK_CONTAINER(frame), grid); gtk_grid_set_column_spacing(GTK_GRID(grid), 12); gtk_grid_set_row_spacing(GTK_GRID(grid), 12); gtk_grid_set_column_homogeneous(GTK_GRID(grid), true); gtk_grid_set_row_homogeneous(GTK_GRID(grid), true); gtk_container_set_border_width(GTK_CONTAINER(grid), 12); remote->grid = grid; while (g_variant_iter_loop (&iter, "@a{sv}", &item)) { gchar *type; gchar *protocol; guint64 scancode; gchar *keycode; g_variant_lookup(item, "type", "s", &type); g_variant_lookup(item, "protocol", "s", &protocol); g_variant_lookup(item, "scancode", "t", &scancode); g_variant_lookup(item, "keycode", "s", &keycode); if (!strcmp(type, "button")) { struct rcbutton *rcb; rcb = new_button_add(GTK_GRID(grid), protocol, scancode, keycode, NULL, col, row); remote->buttons = g_list_append(remote->buttons, rcb); printf("Got a button: %s:0x%08" PRIx64 ":%s\n", protocol, scancode, keycode); } else if (!strcmp(type, "blank")) printf("Got a blank\n"); else printf("Error: unknown type: %s (treating as blank)\n", type); col++; if (col >= remote->width) { col = 0; row++; } } return remote; } void rcng_client_receive_init_ui(GDBusObject *new_hw) { static bool first = true; unsigned i; if (first) { g_signal_connect(global->notebook, "switch-page", G_CALLBACK(on_notebook_page_change), NULL); state.remotes = NULL; state.header_buttons = NULL; state.editing = false; state.active = false; first = false; } rcng_client_receive_destroy_ui(); /* FIXME: if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(a(uuuuaa{sv}))"))) */ scrollda = gtk_scrolled_window_new(NULL, NULL); gtk_container_set_border_width(GTK_CONTAINER(scrollda), 12); own_page_num = gtk_notebook_append_page(global->notebook, scrollda, gtk_label_new("Receive")); GtkWidget *rgrid; rgrid = gtk_grid_new(); gtk_widget_set_halign(rgrid, GTK_ALIGN_CENTER); gtk_grid_set_column_spacing(GTK_GRID(rgrid), 12); gtk_grid_set_row_spacing(GTK_GRID(rgrid), 12); gtk_container_add(GTK_CONTAINER(scrollda), rgrid); GDBusInterface *interface; interface = g_dbus_object_get_interface(new_hw, "org.gnome.RemoteControlManager.Device"); if (!interface) return; gchar **keymaps = NULL; RCDevice *object = RCDEVICE(interface); rcdevice_call_list_keymaps_sync(object, &keymaps, NULL, NULL); for (i = 0; keymaps[i]; i++) { struct remote *remote; g_print("Fetching keymap: %s\n", keymaps[i]); remote = get_keymap(object, keymaps[i]); state.remotes = g_list_prepend(state.remotes, remote); gtk_grid_attach(GTK_GRID(rgrid), remote->widget, i, 1, 1, 1); } g_free(keymaps); gtk_widget_show_all(scrollda); }