/* See LICENSE file for copyright and license details. */ #include #include #include #include #if defined(__GNUC__) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wdeprecated-declarations" # pragma GCC diagnostic ignored "-Wstrict-prototypes" #endif #include #include #if defined(__GNUC__) # pragma GCC diagnostic pop #endif USAGE(""); static GtkWidget *window; static GtkWidget *status; static GtkAccelGroup *accelgroup; static int timer; static const char *statustext = ""; static atomic_flag lock = ATOMIC_FLAG_INIT; static volatile int statusdefered = 0; #define DELAY {.tv_sec = 0, .tv_nsec = 500000000L} static const struct itimerspec delay = {.it_interval = {0, 0}, .it_value = DELAY}; #if defined(__GNUC__) __attribute__((__pure__)) #endif static int check_luhn(const char *s) { unsigned sum = 0; switch (strlen(s) & 1U) { for (;;) { case 0: if ('0' <= *s && *s <= '4') sum += 2U * (unsigned)(*s - '0'); else if ('5' <= *s && *s <= '9') sum += 2U * (unsigned)(*s - '0') - 9U; else if (!*s) break; s++; /* fall through */ case 1: if ('0' <= *s && *s <= '9') sum += (unsigned)(*s - '0'); else if (!*s) break; s++; sum %= 10U; } } return (sum % 10U) == 0U; } static int set_status_text(void *user_data) { (void) user_data; while (atomic_flag_test_and_set_explicit(&lock, memory_order_acquire)); gtk_label_set_text(GTK_LABEL(status), statustext); statusdefered = 0; atomic_flag_clear(&lock); return 0; /* remove from idle */ } static void * defer_set_status_text(void *user_data) { read(timer, &(uint64_t){0}, sizeof(uint64_t)); g_idle_add(&set_status_text, user_data); return NULL; } static void number_changed(GtkEditable *editable, GtkWidget *button) { const char *text = gtk_entry_get_text(GTK_ENTRY(editable)); int add_idle = 0; int ok = *text && check_luhn(text); const char *display; while (atomic_flag_test_and_set_explicit(&lock, memory_order_acquire)); if (!text) { display = statustext = ""; } else if (ok) { display = statustext = "Valid number"; } else { statustext = "Invalid number"; display = timer >= 0 ? "" : statustext; if (!statusdefered && timer >= 0) { statusdefered = 1; add_idle = 1; } } if (timerfd_settime(timer, 0, &delay, NULL)) display = statustext; gtk_label_set_text(GTK_LABEL(status), display); gtk_widget_set_sensitive(button, ok); atomic_flag_clear(&lock); if (add_idle) g_thread_unref(g_thread_new("deferred validity update", &defer_set_status_text, NULL)); } static void copybutton_clicked(GtkWidget *button, GtkEditable *editable) { const char *text = gtk_entry_get_text(GTK_ENTRY(editable)); (void) button; gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), text, -1); gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_PRIMARY), text, -1); gtk_clipboard_store(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD)); } static void on_insert_text(GtkEditable *editable, const char *text, int length, int *position, void *user_data) { size_t i, j, n = (size_t)length; char *result = n >= 1024U ? malloc(n + 1U) : alloca(n + 1U); (void) user_data; j = 0; for (i = 0; i < n; i++) if ('0' <= text[i] && text[i] <= '9') result[j++] = text[i]; result[j] = '\0'; if (j < n) { g_signal_stop_emission_by_name(editable, "insert-text"); if (j) gtk_editable_insert_text(editable, result, (int)j, position); } if (n >= 1024U) free(result); } static void close_window(GtkWidget *menu_item, void *user_data) { (void) menu_item; (void) user_data; gtk_main_quit(); } static GtkWidget * add_menu(GtkWidget *parent, const char *label) { GtkWidget *menu = gtk_menu_new(); GtkWidget *item = gtk_menu_item_new_with_mnemonic(label); gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), menu); gtk_menu_shell_append(GTK_MENU_SHELL(parent), item); return menu; } static GtkWidget * add_menu_item(GtkWidget *parent, const char *label, const char *icon, unsigned int key, GdkModifierType mods, void (*action)(GtkWidget *menu_item, void *user_data)) { GtkWidget *item, *image; if (icon) { item = gtk_image_menu_item_new_with_mnemonic(label); image = gtk_image_new_from_icon_name(icon, GTK_ICON_SIZE_MENU); gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image); gtk_image_menu_item_set_always_show_image(GTK_IMAGE_MENU_ITEM(item), TRUE); } else { item = gtk_menu_item_new_with_mnemonic(label); } gtk_menu_shell_append(GTK_MENU_SHELL(parent), item); if (key) gtk_widget_add_accelerator(item, "activate", accelgroup, key, mods, GTK_ACCEL_VISIBLE); if (action) g_signal_connect(item, "activate", G_CALLBACK(action), NULL); return item; } static void create_window(void) { GtkWidget *outer_vbox, *vbox, *hbox, *label, *number, *copybutton; GtkWidget *menubar, *menu; window = gtk_window_new(GTK_WINDOW_TOPLEVEL); g_signal_connect(window, "destroy", G_CALLBACK(>k_main_quit), NULL); gtk_window_set_title(GTK_WINDOW(window), "gluhncheck"); accelgroup = gtk_accel_group_new(); gtk_window_add_accel_group(GTK_WINDOW(window), accelgroup); outer_vbox = gtk_vbox_new(FALSE, 0); gtk_container_add(GTK_CONTAINER(window), outer_vbox); menubar = gtk_menu_bar_new(); gtk_box_pack_start(GTK_BOX(outer_vbox), menubar, FALSE, FALSE, 0); menu = add_menu(menubar, "_File"); add_menu_item(menu, "_Close", "window-close", GDK_w, GDK_CONTROL_MASK, &close_window); vbox = gtk_vbox_new(FALSE, 0); gtk_container_set_border_width(GTK_CONTAINER(vbox), 8); gtk_container_add(GTK_CONTAINER(outer_vbox), vbox); label = gtk_label_new_with_mnemonic("Enter _number:"); gtk_misc_set_alignment(GTK_MISC(label), 0.0f, 0.5f); gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 2); number = gtk_entry_new(); gtk_entry_set_activates_default(GTK_ENTRY(number), TRUE); gtk_box_pack_start(GTK_BOX(vbox), number, FALSE, FALSE, 0); label = gtk_label_new(NULL); gtk_widget_set_size_request(label, 8, 8); gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0); gtk_label_set_mnemonic_widget(GTK_LABEL(label), number); status = gtk_label_new(""); gtk_misc_set_alignment(GTK_MISC(status), 0.5f, 0.0f); gtk_box_pack_start(GTK_BOX(vbox), status, TRUE, TRUE, 0); hbox = gtk_hbox_new(FALSE, 0); gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); label = gtk_label_new(NULL); gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0); copybutton = gtk_button_new_from_stock(GTK_STOCK_COPY); gtk_widget_set_tooltip_text(copybutton, "Copy to the clipboard"); gtk_widget_set_sensitive(copybutton, FALSE); gtk_box_pack_start(GTK_BOX(hbox), copybutton, FALSE, FALSE, 0); g_signal_connect(G_OBJECT(copybutton), "clicked", G_CALLBACK(©button_clicked), number); g_signal_connect(G_OBJECT(number), "changed", G_CALLBACK(&number_changed), copybutton); g_signal_connect(G_OBJECT(number), "insert-text", G_CALLBACK(&on_insert_text), NULL); } int main(int argc, char *argv[]) { GtkIconTheme *theme; const char *icon; int xargc = 1; char *xargva[] = {argv[0], NULL}; char **xargv = xargva; ARGBEGIN { default: usage(); } ARGEND; if (argc) usage(); timer = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC); g_set_prgname(argv0); g_set_application_name("gluhncheck"); gtk_init(&xargc, &xargv); theme = gtk_icon_theme_get_default(); if (!gtk_icon_theme_has_icon(theme, icon = "apps/gluhncheck") && !gtk_icon_theme_has_icon(theme, icon = "gluhncheck") && !gtk_icon_theme_has_icon(theme, icon = "actions/dialog-apply") && !gtk_icon_theme_has_icon(theme, icon = "dialog-apply")) icon = "gluhncheck"; gtk_window_set_default_icon_name(icon); create_window(); gtk_window_set_icon_name(GTK_WINDOW(window), icon); gtk_window_set_wmclass(GTK_WINDOW(window), "gluhncheck", "gluhncheck"); gtk_window_set_role(GTK_WINDOW(window), "gluhncheck"); gtk_widget_show_all(window); gtk_window_present(GTK_WINDOW(window)); gtk_main(); }