diff options
| author | Mattias Andrée <m@maandree.se> | 2025-12-28 00:12:27 +0100 |
|---|---|---|
| committer | Mattias Andrée <m@maandree.se> | 2025-12-28 00:12:27 +0100 |
| commit | 9a7f9c3fcd27f4b945dd5159e7fdc8795bb3c452 (patch) | |
| tree | 886f0bdfca144fc6f2057841eef3313fdf745d06 /gluhncheck.c | |
| parent | First commit (diff) | |
| download | gluhncheck-1.0.tar.gz gluhncheck-1.0.tar.bz2 gluhncheck-1.0.tar.xz | |
Signed-off-by: Mattias Andrée <m@maandree.se>
Diffstat (limited to '')
| -rw-r--r-- | gluhncheck.c | 307 |
1 files changed, 307 insertions, 0 deletions
diff --git a/gluhncheck.c b/gluhncheck.c new file mode 100644 index 0000000..56741c1 --- /dev/null +++ b/gluhncheck.c @@ -0,0 +1,307 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/timerfd.h> +#include <stdatomic.h> +#include <libsimple.h> +#include <libsimple-arg.h> +#if defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wdeprecated-declarations" +# pragma GCC diagnostic ignored "-Wstrict-prototypes" +#endif +#include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> +#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(); +} |
