#include #include #include #include #include #include #include #include "shared.h" #include "generated.h" #include "rcm-client-main.h" #include "rcm-client-receive.h" #define WINDOW_WIDTH 300 #define WINDOW_HEIGHT 300 struct keymap_entry { char *protocol; guint64 scancode; char *keycode; }; struct remote { char *id; char *name; char *description; guint16 width; guint16 height; GList *buttons; GtkWidget *widget; GtkWidget *grid; GtkWidget *frame; GtkWidget *edit_button; GList *keymap; }; enum rcbutton_type { RCBUTTON_TYPE_NORMAL, RCBUTTON_TYPE_BLANK }; struct rcbutton { char *name; enum rcbutton_type type; GtkWidget *button; guint x; guint y; struct remote *remote; }; struct state { RCDevice *object; GList *remotes; GList *header_buttons; bool editing; GtkWidget *stack; GtkWidget *title; GtkWidget *popover; }; static struct state state; /* static void quick_message(GtkWidget *widget, gchar *message) { GtkWindow *parent = GTK_WINDOW(gtk_widget_get_toplevel(widget)); 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); } */ /* 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); 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); gdk_cairo_set_source_rgba(ct, color); gdk_rgba_free(color); if (!strcmp(keycode, "KEY_RESERVED")) { x = (float)img_size / 2.0f; y = (float)img_size / 2.0f; cairo_set_line_width(ct, 2); cairo_translate(ct, x, y); cairo_arc(ct, 0, 0, x - 2 - REMOTE_BUTTON_PADDING * 2.0f, 0, 2 * M_PI); cairo_stroke(ct); cairo_set_line_width(ct, 3); cairo_move_to(ct, -x + 3 + REMOTE_BUTTON_PADDING * 4.0f, 0); cairo_line_to(ct, x - 3 - REMOTE_BUTTON_PADDING * 4.0f, 0); cairo_stroke(ct); cairo_move_to(ct, 0, -y + 3 + REMOTE_BUTTON_PADDING * 4.0f); cairo_line_to(ct, 0, y - 3 - REMOTE_BUTTON_PADDING * 4.0f); cairo_stroke(ct); } else { if (!strncmp(label, "KEY_NUMERIC_", strlen("KEY_NUMERIC_"))) label += strlen("KEY_NUMERIC_"); else if (!strncmp(label, "KEY_", strlen("KEY_"))) label += strlen("KEY_"); layout = pango_cairo_create_layout(ct); pango_layout_set_font_description(layout, font_description); pango_font_description_free(font_description); 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 GtkWidget * new_button_widget(struct rcbutton *rcb, const gchar *protocol, guint64 scancode, const gchar *keycode); static GtkWidget * new_blank_widget(struct rcbutton *rcb); static void edit_button_ok(GtkButton *button, gpointer user_data) { struct rcbutton *rcb = user_data; GtkComboBoxText *keycode_cbox; gchar *keycode; keycode_cbox = GTK_COMBO_BOX_TEXT(get_object("edit_button_keycode_value")); keycode = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(keycode_cbox)); if (!keycode) goto out; printf("New keycode is %s\n", keycode); if (rcb->name) g_free(rcb->name); if (rcb->button) gtk_widget_destroy(rcb->button); rcb->name = keycode; rcb->type = RCBUTTON_TYPE_NORMAL; rcb->button = new_button_widget(rcb, "dummy", 0xf001f001, keycode); gtk_widget_set_sensitive(rcb->button, true); gtk_widget_show_all(rcb->button); out: gtk_widget_hide(state.popover); } static void edit_button_delete(GtkButton *button, gpointer user_data) { struct rcbutton *rcb = user_data; if (rcb->type == RCBUTTON_TYPE_BLANK) goto out; g_free(rcb->name); rcb->name = NULL; rcb->type = RCBUTTON_TYPE_BLANK; gtk_widget_destroy(rcb->button); rcb->button = new_blank_widget(rcb); gtk_revealer_set_reveal_child(GTK_REVEALER(rcb->button), true); gtk_widget_show_all(rcb->button); out: gtk_widget_hide(state.popover); } static void edit_button_dialog(GtkButton *button, struct rcbutton *rcb) { GtkComboBoxText *keycode_cbox; GtkEntry *comment_entry; GList *ke; unsigned i; if (!state.popover) { GtkWidget *popover; popover = GTK_WIDGET(get_object("edit_button")); gtk_popover_set_position(GTK_POPOVER(popover), GTK_POS_BOTTOM); state.popover = popover; } keycode_cbox = GTK_COMBO_BOX_TEXT(get_object("edit_button_keycode_value")); gtk_combo_box_text_remove_all(keycode_cbox); for (ke = rcb->remote->keymap, i = 0; ke; ke = ke->next, i++) { struct keymap_entry *entry = ke->data; gtk_combo_box_text_append(keycode_cbox, NULL, entry->keycode); if (rcb->name && !strcmp(rcb->name, entry->keycode)) gtk_combo_box_set_active(GTK_COMBO_BOX(keycode_cbox), i); } comment_entry = GTK_ENTRY(get_object("edit_button_comment_value")); gtk_entry_set_text(comment_entry, ""); g_signal_replace_id("edit_button_ok", "clicked", G_CALLBACK(edit_button_ok), rcb); g_signal_replace_id("edit_button_delete", "clicked", G_CALLBACK(edit_button_delete), rcb); gtk_popover_set_relative_to(GTK_POPOVER(state.popover), GTK_WIDGET(button)); gtk_widget_show_all(state.popover); } static void edit_button(GtkButton *button, gpointer user_data) { struct rcbutton *rcb = user_data; gboolean active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)); if (!state.editing || !active) return; switch (rcb->type) { case RCBUTTON_TYPE_NORMAL: case RCBUTTON_TYPE_BLANK: edit_button_dialog(button, rcb); break; default: break; } gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), false); } static GtkWidget * new_blank_widget(struct rcbutton *rcb) { GtkWidget *revealer; GtkWidget *button; revealer = gtk_revealer_new(); gtk_revealer_set_reveal_child(GTK_REVEALER(revealer), false); gtk_revealer_set_transition_type(GTK_REVEALER(revealer), GTK_REVEALER_TRANSITION_TYPE_CROSSFADE); gtk_grid_attach(GTK_GRID(rcb->remote->grid), revealer, rcb->x, rcb->y, 1, 1); button = gtk_toggle_button_new(); g_signal_connect(button, "clicked", G_CALLBACK(edit_button), rcb); gtk_widget_set_sensitive(button, true); gtk_widget_set_tooltip_text(button, "Add key"); gtk_container_add(GTK_CONTAINER(revealer), button); create_button_img(button, "KEY_RESERVED"); return revealer; } static struct rcbutton * new_blank_add(struct remote *remote, guint x, guint y) { struct rcbutton *rcb; rcb = g_malloc0(sizeof(*rcb)); rcb->type = RCBUTTON_TYPE_BLANK; rcb->remote = remote; rcb->x = x; rcb->y = y; rcb->button = new_blank_widget(rcb); return rcb; } static GtkWidget * new_button_widget(struct rcbutton *rcb, const gchar *protocol, guint64 scancode, const gchar *keycode) { GtkWidget *button; gchar *tooltip; button = gtk_toggle_button_new(); g_signal_connect(button, "clicked", G_CALLBACK(edit_button), rcb); gtk_widget_set_sensitive(button, false); tooltip = g_strdup_printf("Protocol: %s\nScancode: 0x%08" PRIx64 "\nKeycode : %s", protocol, scancode, keycode); gtk_widget_set_tooltip_text(button, tooltip); g_free(tooltip); gtk_grid_attach(GTK_GRID(rcb->remote->grid), button, rcb->x, rcb->y, 1, 1); create_button_img(button, keycode); return button; } static struct rcbutton * new_button_add(struct remote *remote, const gchar *protocol, guint64 scancode, const char *keycode, guint x, guint y) { struct rcbutton *rcb; rcb = g_malloc0(sizeof(*rcb)); rcb->type = RCBUTTON_TYPE_NORMAL; rcb->remote = remote; rcb->name = strdup(keycode); rcb->x = x; rcb->y = y; rcb->button = new_button_widget(rcb, protocol, scancode, keycode); return rcb; } static void local_create_header_button(const gchar *tooltip, const gchar *icon_name, gboolean end, GCallback callback, gpointer user_data) { GtkWidget *button; button = create_header_button(NULL, tooltip, icon_name, end, callback, user_data); 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_layout(struct remote *remote, guint16 new_width, guint16 new_height) { guint16 old_width = remote->width; guint16 old_height = remote->height; guint16 x, y; GList *new_buttons = NULL; printf("Resizing...\n"); if (new_width < old_width || new_height < old_height) { GList *b = remote->buttons; for (y = 0; y < old_height; y++) { for (x = 0; x < old_width; x++) { GList *delete = b; struct rcbutton *rcb = delete->data; b = b->next; if (x < new_width && y < new_height) continue; printf("\tDeleting old button at %ux%u\n", x, y); remote->buttons = g_list_remove_link(remote->buttons, delete); gtk_widget_destroy(rcb->button); g_free(rcb->name); g_free(rcb); g_list_free(delete); } } } for (y = 0; y < new_height; y++) { for (x = 0; x < new_width; x++) { if (x < old_width && y < old_height) { GList *b; b = remote->buttons; remote->buttons = g_list_remove_link(remote->buttons, b); new_buttons = g_list_concat(new_buttons, b); printf("\tCopied button at %ux%u\n", x, y); } else { struct rcbutton *rcb; rcb = new_blank_add(remote, x, y); new_buttons = g_list_append(new_buttons, rcb); gtk_revealer_set_reveal_child(GTK_REVEALER(rcb->button), true); printf("\tNew button at %ux%u\n", x, y); } } } g_assert(remote->buttons == NULL); remote->buttons = new_buttons; remote->width = new_width; remote->height = new_height; g_object_ref(remote->edit_button); gtk_container_remove(GTK_CONTAINER(remote->grid), remote->edit_button); gtk_grid_attach(GTK_GRID(remote->grid), remote->edit_button, 0, -1, remote->width, 1); g_object_unref(remote->edit_button); gtk_widget_show_all(remote->grid); } static void update_remote(struct remote *remote) { GVariantBuilder kbuilder; GVariantBuilder lbuilder; GList *l; g_variant_builder_init(&kbuilder, G_VARIANT_TYPE("aa{sv}")); for (l = remote->keymap; l; l = l->next) { struct keymap_entry *ke = l->data; g_variant_builder_open(&kbuilder, G_VARIANT_TYPE ("a{sv}")); g_variant_builder_add(&kbuilder, "{sv}", "protocol", g_variant_new_string(ke->protocol)); g_variant_builder_add(&kbuilder, "{sv}", "scancode", g_variant_new_uint64(ke->scancode)); g_variant_builder_add(&kbuilder, "{sv}", "keycode", g_variant_new_string(ke->keycode)); g_variant_builder_close(&kbuilder); } g_variant_builder_init(&lbuilder, G_VARIANT_TYPE("aa{sv}")); for (l = remote->buttons; l; l = l->next) { struct rcbutton *rcb = l->data; g_variant_builder_open(&lbuilder, G_VARIANT_TYPE ("a{sv}")); switch (rcb->type) { case RCBUTTON_TYPE_NORMAL: g_variant_builder_add(&lbuilder, "{sv}", "type", g_variant_new_string("button")); g_variant_builder_add(&lbuilder, "{sv}", "keycode", g_variant_new_string(rcb->name)); break; case RCBUTTON_TYPE_BLANK: g_variant_builder_add(&lbuilder, "{sv}", "type", g_variant_new_string("blank")); break; default: g_assert_not_reached(); } g_variant_builder_close(&lbuilder); } rcdevice_call_set_keymap_sync(state.object, remote->id, remote->name, remote->description, remote->width, remote->height, g_variant_builder_end(&kbuilder), g_variant_builder_end(&lbuilder), NULL, NULL); } static void properties_dialog(GtkButton *button, gpointer user_data) { struct remote *remote = user_data; GtkWidget *dialog; GtkWidget *id; GtkWidget *name_input; GtkWidget *desc_input; GtkWidget *width_input; GtkWidget *height_input; GtkAdjustment *width_adj; GtkAdjustment *height_adj; const gchar *name; gchar *name_cpy; const gchar *desc; gchar *desc_cpy; gint width; gint height; gint r; dialog = GTK_WIDGET(gtk_builder_get_object(global->builder, "keymap_properties")); id = GTK_WIDGET(gtk_builder_get_object(global->builder, "keymap_properties_id_value")); gtk_label_set_text(GTK_LABEL(id), remote->id); name_input = GTK_WIDGET(gtk_builder_get_object(global->builder, "keymap_properties_name_value")); gtk_entry_set_text(GTK_ENTRY(name_input), remote->name); desc_input = GTK_WIDGET(gtk_builder_get_object(global->builder, "keymap_properties_description_value")); gtk_entry_set_text(GTK_ENTRY(desc_input), remote->description); width_input = GTK_WIDGET(gtk_builder_get_object(global->builder, "keymap_properties_width_value")); width_adj = GTK_ADJUSTMENT(gtk_builder_get_object(global->builder, "keymap_properties_width_adjustment")); gtk_adjustment_set_value(width_adj, remote->width); height_input = GTK_WIDGET(gtk_builder_get_object(global->builder, "keymap_properties_height_value")); height_adj = GTK_ADJUSTMENT(gtk_builder_get_object(global->builder, "keymap_properties_height_adjustment")); gtk_adjustment_set_value(height_adj, remote->height); gtk_widget_show_all(dialog); r = gtk_dialog_run(GTK_DIALOG(dialog)); if (r != 1) goto out; name = gtk_entry_get_text(GTK_ENTRY(name_input)); desc = gtk_entry_get_text(GTK_ENTRY(desc_input)); 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)); if (!name || strlen(name) < 1) { printf("Invalid name: %s\n", name ? name : "NULL"); goto out; } if (!desc || strlen(desc) < 1) { printf("Invalid description: %s\n", desc ? desc : "NULL"); goto out; } if (width < 1 || width > REMOTE_LAYOUT_MAX_WIDTH) { printf("Invalid width: %i\n", width); goto out; } if (height < 1 || height > REMOTE_LAYOUT_MAX_HEIGHT) { printf("Invalid height: %i\n", height); goto out; } name_cpy = strdup(name); desc_cpy = strdup(desc); if (!name_cpy || !desc_cpy) { free(name_cpy); free(desc_cpy); printf("strdup failed\n"); goto out; } free(remote->name); free(remote->description); remote->name = name_cpy; remote->description = desc_cpy; resize_layout(remote, width, height); update_remote(remote); gtk_frame_set_label(GTK_FRAME(remote->frame), remote->name); gtk_widget_set_name(gtk_frame_get_label_widget(GTK_FRAME(remote->frame)), "RemoteControlClientRemoteLabel"); out: gtk_widget_hide(dialog); } static void show_hardware_list(GtkButton *button, gpointer user_data) { remove_header_buttons(); gtk_header_bar_set_custom_title(GTK_HEADER_BAR(gtk_builder_get_object(global->builder, "main_headerbar")), NULL); gtk_stack_set_visible_child_name(global->stack, "hardware_page"); if (state.title) gtk_widget_destroy(state.title); if (state.stack) gtk_widget_destroy(state.stack); g_object_unref(state.title); state.title = NULL; state.stack = NULL; } static void set_edit_keymap(bool editing) { GList *l; remove_header_buttons(); local_create_header_button("Return to hardware list", "go-previous-symbolic", false, G_CALLBACK(show_hardware_list), NULL); if (editing) local_create_header_button("Save changes", "document-save-symbolic", true, G_CALLBACK(toggle_edit_keymap), NULL); else local_create_header_button("Edit keymaps", "document-properties-symbolic", true, G_CALLBACK(toggle_edit_keymap), NULL); for (l = state.remotes; l; l = l->next) { struct remote *remote = l->data; GList *b; if (editing) { remote->edit_button = gtk_button_new_with_label("Properties"); g_signal_connect(remote->edit_button, "clicked", G_CALLBACK(properties_dialog), 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; } for (b = remote->buttons; b; b = b->next) { struct rcbutton *rcb = b->data; switch (rcb->type) { case RCBUTTON_TYPE_BLANK: gtk_revealer_set_reveal_child(GTK_REVEALER(rcb->button), editing); break; case RCBUTTON_TYPE_NORMAL: gtk_widget_set_sensitive(rcb->button, editing); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rcb->button), false); break; default: break; } } } } static void toggle_edit_keymap(GtkButton *button, gpointer user_data) { state.editing = !state.editing; set_edit_keymap(state.editing); } void rcng_client_receive_key_pressed(GDBusObject *obj, const gchar *protocol, guint64 scancode, const gchar *keycode) { GList *l; unsigned affected = 0; /* FIXME: Use object path, this is just test code */ if (state.editing) 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 (rcb->type != RCBUTTON_TYPE_NORMAL) continue; if (strcmp(keycode, rcb->name)) continue; gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rcb->button), true); affected++; } } g_print("Key %s:0x%08" PRIx64 ":%s pressed (%u affected)\n", protocol, scancode, keycode, affected); } void rcng_client_receive_key_released(GDBusObject *obj, const gchar *keycode) { GList *l; unsigned affected = 0; /* FIXME: Use object path, this is just test code */ if (state.editing) 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 (rcb->type != RCBUTTON_TYPE_NORMAL) continue; if (strcmp(keycode, rcb->name)) continue; gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rcb->button), false); affected++; } } g_print("Key %s released (%u affected)\n", keycode, affected); } static struct remote * get_keymap(RCDevice *object, const gchar *keymap_id) { struct remote *remote; GVariant *keymap_entries = NULL; GVariant *layout_entries = NULL; remote = g_malloc0(sizeof(*remote)); remote->id = strdup(keymap_id); rcdevice_call_get_keymap_sync(object, keymap_id, &remote->name, &remote->description, &remote->width, &remote->height, &keymap_entries, &layout_entries, NULL, NULL); g_assert(g_variant_is_of_type(keymap_entries, G_VARIANT_TYPE("aa{sv}"))); g_assert(g_variant_is_of_type(layout_entries, G_VARIANT_TYPE("aa{sv}"))); GtkWidget *fixed; fixed = gtk_fixed_new(); gtk_widget_set_name(fixed, "RemoteControlClientFixed"); remote->widget = fixed; GtkWidget *frame; frame = gtk_frame_new(remote->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); remote->frame = frame; 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; GVariantIter iter; gsize n_items; GVariant *item; n_items = g_variant_iter_init(&iter, keymap_entries); g_print("Keymap items: %zu\n", n_items); while (g_variant_iter_loop (&iter, "@a{sv}", &item)) { gchar *protocol; guint64 scancode; gchar *keycode; struct keymap_entry *entry; g_variant_lookup(item, "protocol", "s", &protocol); g_variant_lookup(item, "scancode", "t", &scancode); g_variant_lookup(item, "keycode", "s", &keycode); entry = g_malloc0(sizeof(*entry)); entry->protocol = protocol; entry->scancode = scancode; entry->keycode = keycode; remote->keymap = g_list_append(remote->keymap, entry); } n_items = g_variant_iter_init(&iter, layout_entries); g_print("Layout items: %zu\n", n_items); guint row = 0; guint col = 0; while (g_variant_iter_loop (&iter, "@a{sv}", &item)) { gchar *type; struct keymap_entry *keymap_entry; g_variant_lookup(item, "type", "s", &type); if (!strcmp(type, "button")) { GList *k; gchar *keycode; g_variant_lookup(item, "keycode", "s", &keycode); for (k = remote->keymap; k; k = k->next) { struct keymap_entry *tmp = k->data; if (!strcmp(keycode, tmp->keycode)) break; } if (k) keymap_entry = k->data; else keymap_entry = NULL; } else if (!strcmp(type, "blank")) { printf("Got a blank\n"); keymap_entry = NULL; } else { printf("Error: unknown type: %s (treating as blank)\n", type); keymap_entry = NULL; } if (keymap_entry) { struct rcbutton *rcb; rcb = new_button_add(remote, keymap_entry->protocol, keymap_entry->scancode, keymap_entry->keycode, col, row); remote->buttons = g_list_append(remote->buttons, rcb); printf("Got a button: %s:0x%08" PRIx64 ":%s\n", keymap_entry->protocol, keymap_entry->scancode, keymap_entry->keycode); } else { struct rcbutton *rcb; rcb = new_blank_add(remote, col, row); remote->buttons = g_list_append(remote->buttons, rcb); } col++; if (col >= remote->width) { col = 0; row++; } } return remote; } void rcng_client_receive_init_ui(GDBusObject *new_hw) { unsigned i; GtkWidget *scrollda; GtkWidget *label; GtkWidget *stack_switch; gchar *titlestr; GDBusInterface *interface; state.remotes = NULL; state.header_buttons = NULL; state.editing = false; interface = g_dbus_object_get_interface(new_hw, "org.gnome.RemoteControlManager.Device"); if (!interface) return; /* FIXME: unref object */ state.object = RCDEVICE(interface); state.stack = gtk_stack_new(); state.title = gtk_grid_new(); gtk_grid_set_row_spacing(GTK_GRID(state.title), 6); gtk_widget_set_margin_top(state.title, 6); gtk_widget_set_margin_bottom(state.title, 6); label = gtk_label_new(NULL); titlestr = g_markup_printf_escaped("Keymaps for %s", rcdevice_get_sys_name(state.object)); gtk_label_set_markup(GTK_LABEL(label), titlestr); g_free(titlestr); stack_switch = gtk_stack_switcher_new(); gtk_stack_switcher_set_stack(GTK_STACK_SWITCHER(stack_switch), GTK_STACK(state.stack)); gtk_grid_attach(GTK_GRID(state.title), label, 1, 1, 1, 1); gtk_grid_attach(GTK_GRID(state.title), stack_switch, 1, 2, 1, 1); g_object_ref(state.title); gtk_widget_show_all(state.title); scrollda = gtk_scrolled_window_new(NULL, NULL); gtk_container_set_border_width(GTK_CONTAINER(scrollda), 12); gtk_stack_add_titled(GTK_STACK(state.stack), scrollda, "Receive", "Receive"); label = gtk_label_new("Transmit"); gtk_stack_add_titled(GTK_STACK(state.stack), label, "Transmit", "Transmit"); 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); gchar **keymap_ids = NULL; rcdevice_call_list_keymaps_sync(state.object, &keymap_ids, NULL, NULL); for (i = 0; keymap_ids[i]; i++) { struct remote *remote; g_print("Fetching keymap: %s\n", keymap_ids[i]); remote = get_keymap(state.object, keymap_ids[i]); state.remotes = g_list_prepend(state.remotes, remote); gtk_grid_attach(GTK_GRID(rgrid), remote->widget, i, 1, 1, 1); } g_free(keymap_ids); gtk_widget_show_all(state.stack); gtk_stack_add_named(global->stack, state.stack, "remotes"); gtk_stack_set_visible_child(global->stack, state.stack); set_edit_keymap(state.editing); gtk_header_bar_set_custom_title(GTK_HEADER_BAR(gtk_builder_get_object(global->builder, "main_headerbar")), state.title); }