/* * not-the-dice-control-panel.c - an example program to demonstrate some * ideas that might be useful for writing a _real_ DICE control panel * * compile with: gcc $(pkg-config --cflags --libs gtk+-2.0) \ * -o not-the-dice-control-panel not-the-dice-control-panel.c * * ... blah blah ... public domain ... blah blah ... no warranty ... blah blah ... */ #include #include #include #include #include #include #include #include #include #include #include #include #if 0 #include #else /* just in case this header is not installed */ /* -------------------------------------------------------------------------- */ #include #define SNDRV_FIREWIRE_EVENT_LOCK_STATUS 0x000010cc #define SNDRV_FIREWIRE_EVENT_DICE_NOTIFICATION 0xd1ce004e struct snd_firewire_event_common { unsigned int type; /* SNDRV_FIREWIRE_EVENT_xxx */ }; struct snd_firewire_event_lock_status { unsigned int type; unsigned int status; /* 0/1 = unlocked/locked */ }; struct snd_firewire_event_dice_notification { unsigned int type; unsigned int notification; /* DICE-specific bits */ }; union snd_firewire_event { struct snd_firewire_event_common common; struct snd_firewire_event_lock_status lock_status; struct snd_firewire_event_dice_notification dice_notification; }; #define SNDRV_FIREWIRE_IOCTL_GET_INFO _IOR('H', 0xf8, struct snd_firewire_get_info) #define SNDRV_FIREWIRE_IOCTL_LOCK _IO('H', 0xf9) #define SNDRV_FIREWIRE_IOCTL_UNLOCK _IO('H', 0xfa) #define SNDRV_FIREWIRE_TYPE_DICE 1 struct snd_firewire_get_info { unsigned int type; /* SNDRV_FIREWIRE_TYPE_xxx */ unsigned int card; /* same as fw_cdev_get_info.card */ unsigned char guid[8]; char device_name[16]; /* device node in /dev */ }; /* -------------------------------------------------------------------------- */ #endif /* -------------------------------------------------------------------------- */ /* GLib main loop event source for reading from a device file */ struct DeviceReadSource { GSource source; GPollFD poll; }; typedef void (*DeviceReadSourceFunc)(int fd, gboolean active, /* FALSE = unplugged */ gpointer user_data); static gboolean device_read_source_prepare(GSource *source, int *timeout_) { *timeout_ = -1; return FALSE; } static gboolean device_read_source_check(GSource *source_) { struct DeviceReadSource *source = (void *)source_; return (source->poll.revents & (G_IO_IN | G_IO_HUP | G_IO_ERR)) != 0; } static gboolean device_read_source_dispatch(GSource *source_, GSourceFunc callback_, gpointer user_data) { struct DeviceReadSource *source = (void *)source_; DeviceReadSourceFunc callback = (DeviceReadSourceFunc)callback_; gboolean active; g_assert(callback != NULL); active = !(source->poll.revents & (G_IO_HUP | G_IO_ERR)); callback(source->poll.fd, active, user_data); return active; } static GSourceFuncs device_read_source_funcs = { .prepare = device_read_source_prepare, .check = device_read_source_check, .dispatch = device_read_source_dispatch, }; static GSource *device_read_source_new(int fd) { GSource *gsource; struct DeviceReadSource *source; gsource = g_source_new(&device_read_source_funcs, sizeof(*source)); source = (void *)gsource; source->poll.fd = fd; source->poll.events = G_IO_IN | G_IO_HUP | G_IO_ERR; g_source_add_poll(gsource, &source->poll); return gsource; } static guint device_read_source_add(int fd, DeviceReadSourceFunc function, gpointer data) { GSource *source; source = device_read_source_new(fd); g_source_set_callback(source, (GSourceFunc)function, data, NULL); return g_source_attach(source, NULL); } /* -------------------------------------------------------------------------- */ #define _(s) s static const unsigned int dice_rates[7] = { 32000, 44100, 48000, 88200, 96000, 176400, 192000 }; static int fd_hwdep; static int fd_firewire; static struct snd_firewire_get_info hwdep_info; static guint64 dice_global; static guint32 supported_rates; static guint32 current_rate; static gboolean clock_locked; static char *nick_name; static enum { CHANGE_NICK_NAME = 0x1, CHANGE_SAMPLE_RATE = 0x2, } pending_changes; static unsigned int pending_responses; static unsigned int fw_generation; static GtkWindow *main_window; static GtkWidget *clock_label; static GtkWidget *statusbar; static guint statusbar_context; #define fatal_error(fmt, args...) \ do_fatal_error(gtk_message_dialog_new( \ main_window, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, \ fmt, ##args)) static void do_fatal_error(GtkWidget *dialog) { gtk_window_set_title(GTK_WINDOW(dialog), NULL); gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); exit(EXIT_FAILURE); } static void show_status(const char *format, ...) { va_list ap; char *msg = NULL; if (format) { va_start(ap, format); msg = g_strdup_vprintf(format, ap); va_end(ap); } gtk_statusbar_pop(GTK_STATUSBAR(statusbar), statusbar_context); if (format) gtk_statusbar_push(GTK_STATUSBAR(statusbar), statusbar_context, msg); g_free(msg); } static void open_hwdep_device(int argc, char *argv[]) { if (argc != 2) fatal_error(_("This program must be called with the hwdep device file,\n" "e.g., \"not-the-dice-control-panel /dev/snd/hwdepC5D0\".")); fd_hwdep = open(argv[1], O_RDWR); if (fd_hwdep == -1) fatal_error(_("Cannot open %s: %s"), argv[1], strerror(errno)); if (ioctl(fd_hwdep, SNDRV_FIREWIRE_IOCTL_GET_INFO, &hwdep_info) == -1) fatal_error(_("Cannot get information from %s: %s\n" "(Wrong device file?)"), argv[1], strerror(errno)); if (hwdep_info.type != SNDRV_FIREWIRE_TYPE_DICE) fatal_error(_("%s: not a DICE device"), argv[1]); } static void on_bus_reset(struct fw_cdev_event_bus_reset *bus_reset) { fw_generation = bus_reset->generation; } static void open_firewire_device(void) { char *file_name; struct fw_cdev_get_info info; struct fw_cdev_event_bus_reset bus_reset; file_name = g_strdup_printf("/dev/%s", hwdep_info.device_name); fd_firewire = open(file_name, O_RDWR); if (fd_firewire == -1) fatal_error(_("Cannot open %s: %s"), file_name, strerror(errno)); info.version = 4; info.rom = 0; info.bus_reset = (__u64)(uintptr_t)&bus_reset; info.bus_reset_closure = (__u64)(uintptr_t)on_bus_reset; if (ioctl(fd_firewire, FW_CDEV_IOC_GET_INFO, &info) == -1) fatal_error(_("Cannot get information from %s: %s\n" "(Wrong device file?)"), file_name, strerror(errno)); if (info.card != hwdep_info.card) /* FIXME: also check GUID */ fatal_error(_("hwdep and fw devices do not match")); on_bus_reset(&bus_reset); g_free(file_name); } static void fw_read(guint64 offset, guint length, void *data) { struct fw_cdev_send_request request = { .closure = 0, .data = 0, .generation = fw_generation, }; struct { struct fw_cdev_event_response response; char data[64]; } buffer; if (length == 4) request.tcode = TCODE_READ_QUADLET_REQUEST; else request.tcode = TCODE_READ_BLOCK_REQUEST; request.length = length; request.offset = offset; g_assert(length <= sizeof(buffer.data)); if (ioctl(fd_firewire, FW_CDEV_IOC_SEND_REQUEST, &request) == -1) fatal_error("send request failed"); do { ssize_t bytes = read(fd_firewire, &buffer, sizeof(buffer)); if (bytes < sizeof(struct fw_cdev_event_common)) fatal_error("short read"); if (buffer.response.type == FW_CDEV_EVENT_BUS_RESET) fatal_error("bus reset"); } while (buffer.response.type != FW_CDEV_EVENT_RESPONSE); if (buffer.response.rcode != RCODE_COMPLETE) fatal_error("read request failed"); memcpy(data, buffer.response.data, length); } static void init_dice_registers(void) { guint32 global_offset; char nick[65]; guint32 clock_sel; guint32 clock_caps; guint32 status; unsigned int i; fw_read(0xffffe0000000uLL, 4, &global_offset); dice_global = 0xffffe0000000uLL + 4 * be32toh(global_offset); fw_read(dice_global + 0xc, 64, nick); for (i = 0; i < 64; i += 4) *(guint32 *)&nick[i] = __bswap_32(*(guint32 *)&nick[i]); nick[64] = '\0'; nick_name = g_strdup(nick); fw_read(dice_global + 0x4c, 4, &clock_sel); current_rate = (be32toh(clock_sel) >> 8) & 0xff; fw_read(dice_global + 0x64, 4, &clock_caps); supported_rates = be32toh(clock_caps) & 0x7f; fw_read(dice_global + 0x54, 4, &status); clock_locked = !!(status & htobe32(0x1)); } static void change_completed(void) { pending_responses--; if (pending_responses == 0) ioctl(fd_hwdep, SNDRV_FIREWIRE_IOCTL_UNLOCK); } static void write_nick_name_response(struct fw_cdev_event_response *response) { change_completed(); if (response->rcode != RCODE_COMPLETE) /* FIXME: retry */ show_status("nick write failed: %u", response->rcode); } static void write_nick_name(void) { char data[64]; struct fw_cdev_send_request request = { .tcode = TCODE_WRITE_BLOCK_REQUEST, .length = 64, .offset = dice_global + 0xc, .closure = (guint64)write_nick_name_response, .data = (guint64)data, .generation = fw_generation, }; unsigned int i; strncpy(data, nick_name, 64); for (i = 0; i < 64; i += 4) *(guint32 *)&data[i] = __bswap_32(*(guint32 *)&data[i]); if (ioctl(fd_firewire, FW_CDEV_IOC_SEND_REQUEST, &request) == -1) { show_status("nick write request failed"); return; } pending_responses++; } static void write_sample_rate_response(struct fw_cdev_event_response *response) { change_completed(); if (response->rcode != RCODE_COMPLETE) /* FIXME: retry */ show_status("rate write failed: %u", response->rcode); } static void write_sample_rate(void) { guint32 data; struct fw_cdev_send_request request = { .tcode = TCODE_WRITE_QUADLET_REQUEST, .length = 4, .offset = dice_global + 0x4c, .closure = (guint64)write_sample_rate_response, .data = (guint64)&data, .generation = fw_generation, }; data = htobe32((current_rate << 8) | 0x8/*ARX1*/); if (ioctl(fd_firewire, FW_CDEV_IOC_SEND_REQUEST, &request) == -1) { show_status("rate write request failed"); return; } pending_responses++; } static void apply_pending_changes(void) { if (ioctl(fd_hwdep, SNDRV_FIREWIRE_IOCTL_LOCK) == -1) { show_status(_("waiting for device being idle ...")); return; } show_status(NULL); if (pending_changes & CHANGE_NICK_NAME) { pending_changes &= ~CHANGE_NICK_NAME; write_nick_name(); } if (pending_changes & CHANGE_SAMPLE_RATE) { pending_changes &= ~CHANGE_SAMPLE_RATE; write_sample_rate(); } } static void on_nick_activate(GtkWidget *entry, gpointer user_data) { const char *new_nick; new_nick = gtk_entry_get_text(GTK_ENTRY(entry)); if (strcmp(nick_name, new_nick) != 0) { g_free(nick_name); nick_name = g_strdup(new_nick); pending_changes |= CHANGE_NICK_NAME; apply_pending_changes(); } } static gboolean on_nick_focus_out(GtkWidget *entry, GdkEventFocus *event, gpointer user_data) { on_nick_activate(entry, user_data); return FALSE; } static void on_rate_toggled(GtkToggleButton *button, gpointer user_data) { guint index = (guint)(guintptr)user_data; if (gtk_toggle_button_get_active(button) && index != current_rate) { current_rate = index; pending_changes |= CHANGE_SAMPLE_RATE; apply_pending_changes(); } } static void create_window(void) { GtkWidget *main_vbox; GtkWidget *label; GtkWidget *frame; GtkWidget *alignment; GtkWidget *entry; GtkWidget *rates_vbox; GtkSizeGroup *sizegroup; GtkWidget *radio; GtkWidget *radio0; char *str; unsigned int i; main_window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL)); g_signal_connect(main_window, "destroy", G_CALLBACK(gtk_main_quit), NULL); main_vbox = gtk_vbox_new(FALSE, 0); gtk_container_add(GTK_CONTAINER(main_window), main_vbox); label = gtk_label_new(NULL); gtk_label_set_markup_with_mnemonic(GTK_LABEL(label), _("_Nick name:")); frame = gtk_frame_new(NULL); gtk_frame_set_label_widget(GTK_FRAME(frame), label); gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_NONE); gtk_box_pack_start(GTK_BOX(main_vbox), frame, TRUE, TRUE, 0); alignment = gtk_alignment_new(0, 0, 1, 1); gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), 0, 0, 12, 0); gtk_container_add(GTK_CONTAINER(frame), alignment); entry = gtk_entry_new(); gtk_entry_set_max_length(GTK_ENTRY(entry), 64); gtk_entry_set_width_chars(GTK_ENTRY(entry), 25); gtk_entry_set_text(GTK_ENTRY(entry), nick_name); gtk_container_add(GTK_CONTAINER(alignment), entry); gtk_label_set_mnemonic_widget(GTK_LABEL(label), entry); g_signal_connect(entry, "activate", G_CALLBACK(on_nick_activate), NULL); g_signal_connect_after(entry, "focus-out-event", G_CALLBACK(on_nick_focus_out), NULL); label = gtk_label_new(NULL); gtk_label_set_markup(GTK_LABEL(label), _("Sample rate:")); frame = gtk_frame_new(NULL); gtk_frame_set_label_widget(GTK_FRAME(frame), label); gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_NONE); gtk_box_pack_start(GTK_BOX(main_vbox), frame, TRUE, TRUE, 0); alignment = gtk_alignment_new(0, 0, 1, 1); gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), 0, 0, 12, 0); gtk_container_add(GTK_CONTAINER(frame), alignment); rates_vbox = gtk_vbox_new(FALSE, 0); gtk_container_add(GTK_CONTAINER(alignment), rates_vbox); sizegroup = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); radio0 = NULL; for (i = 0; i <= 6; ++i) { str = g_strdup_printf("%u Hz", dice_rates[i]); label = gtk_label_new(str); g_free(str); gtk_size_group_add_widget(sizegroup, label); if (!radio0) radio = gtk_radio_button_new(NULL); else radio = gtk_radio_button_new_from_widget(GTK_RADIO_BUTTON(radio0)); gtk_container_add(GTK_CONTAINER(radio), label); gtk_button_set_alignment(GTK_BUTTON(radio), 1, 0); if (!(supported_rates & (1 << i))) gtk_widget_set_sensitive(radio, FALSE); if (i == current_rate) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio), TRUE); g_signal_connect(radio, "toggled", G_CALLBACK(on_rate_toggled), (gpointer)(guintptr)i); gtk_box_pack_start(GTK_BOX(rates_vbox), radio, TRUE, TRUE, 0); if (!radio0) radio0 = radio; } g_object_unref(sizegroup); label = gtk_label_new(NULL); gtk_label_set_markup(GTK_LABEL(label), _("Clock source:")); frame = gtk_frame_new(NULL); gtk_frame_set_label_widget(GTK_FRAME(frame), label); gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_NONE); gtk_box_pack_start(GTK_BOX(main_vbox), frame, TRUE, TRUE, 0); alignment = gtk_alignment_new(0, 0, 1, 1); gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), 0, 0, 12, 0); gtk_container_add(GTK_CONTAINER(frame), alignment); str = g_strdup_printf("%s (%s)", "PC", clock_locked ? _("locked") : _("unlocked")); clock_label = gtk_label_new(str); g_free(str); gtk_misc_set_alignment(GTK_MISC(clock_label), 0, 0); gtk_container_add(GTK_CONTAINER(alignment), clock_label); statusbar = gtk_statusbar_new(); gtk_statusbar_set_has_resize_grip(GTK_STATUSBAR(statusbar), FALSE); statusbar_context = gtk_statusbar_get_context_id(GTK_STATUSBAR(statusbar), ":-)"); gtk_box_pack_end(GTK_BOX(main_vbox), statusbar, FALSE, TRUE, 0); gtk_widget_show_all(GTK_WIDGET(main_window)); } static void on_device_lock_status(gboolean locked) { if (!locked && pending_changes) apply_pending_changes(); } static void read_status_response(struct fw_cdev_event_response *response) { gboolean new_locked; char *str; if (response->rcode != RCODE_COMPLETE) { /* FIXME: retry */ show_status("read status failed: %u", response->rcode); return; } new_locked = !!(response->data[0] & htobe32(0x1)); if (new_locked != clock_locked) { clock_locked = new_locked; str = g_strdup_printf("%s (%s)", "PC", clock_locked ? _("locked") : _("unlocked")); gtk_label_set_text(GTK_LABEL(clock_label), str); g_free(str); } } static void on_dice_lock_changed(void) { struct fw_cdev_send_request request = { .tcode = TCODE_READ_QUADLET_REQUEST, .length = 4, .offset = dice_global + 0x54, .closure = (guint64)read_status_response, .data = 0, .generation = fw_generation, }; if (ioctl(fd_firewire, FW_CDEV_IOC_SEND_REQUEST, &request) == -1) { show_status("read status request failed"); return; } } static void on_dice_clock_accepted(void) { /* * TODO: read the clock from the device; it might have been changed not * by us but by somebody else */ } static void on_dice_interface_changed(void) { } static void on_dice_notification(unsigned int bits) { if (bits & 0x10) on_dice_lock_changed(); if (bits & 0x20) on_dice_clock_accepted(); if (bits & 0x40) on_dice_interface_changed(); } static void on_hwdep_event(int fd, gboolean active, gpointer user_data) { union snd_firewire_event event; ssize_t bytes; if (!active) { gtk_widget_destroy(GTK_WIDGET(main_window)); return; } bytes = read(fd, &event, sizeof(event)); if (bytes < sizeof(struct snd_firewire_event_common)) return; switch (event.common.type) { case SNDRV_FIREWIRE_EVENT_LOCK_STATUS: on_device_lock_status(event.lock_status.status); break; case SNDRV_FIREWIRE_EVENT_DICE_NOTIFICATION: on_dice_notification(event.dice_notification.notification); break; default: break; } } typedef void (*firewire_event)(struct fw_cdev_event_common *event); static void on_firewire_event(int fd, gboolean active, gpointer user_data) { struct { union fw_cdev_event event; char response_buffer[4]; } buffer; ssize_t bytes; if (!active) { gtk_widget_destroy(GTK_WIDGET(main_window)); return; } bytes = read(fd, &buffer, sizeof(buffer)); if (bytes < sizeof(struct fw_cdev_event_common)) return; if (buffer.event.common.closure != 0) ((firewire_event)buffer.event.common.closure)(&buffer.event.common); } int main(int argc, char *argv[]) { guint src1, src2; gtk_init(&argc, &argv); g_set_application_name(_("Not the DICE Control Panel")); open_hwdep_device(argc, argv); open_firewire_device(); init_dice_registers(); create_window(); src1 = device_read_source_add(fd_hwdep, on_hwdep_event, NULL); src2 = device_read_source_add(fd_firewire, on_firewire_event, NULL); gtk_main(); g_source_remove(src1); g_source_remove(src2); close(fd_firewire); close(fd_hwdep); return 0; }