diff options
Diffstat (limited to '')
| -rw-r--r-- | .gitignore | 2 | ||||
| -rw-r--r-- | Makefile | 22 | ||||
| -rw-r--r-- | README | 33 | ||||
| -rw-r--r-- | config.mk | 5 | ||||
| -rw-r--r-- | gluhncheck.1 | 33 | ||||
| -rw-r--r-- | gluhncheck.c | 307 | ||||
| -rw-r--r-- | luhncheck.1 | 85 | ||||
| -rw-r--r-- | luhncheck.c | 81 |
8 files changed, 363 insertions, 205 deletions
@@ -12,4 +12,4 @@ *.gcov *.gcno *.gcda -/luhncheck +/gluhncheck @@ -4,32 +4,32 @@ CONFIGFILE = config.mk include $(CONFIGFILE) OBJ =\ - luhncheck.o + gluhncheck.o HDR = -all: luhncheck +all: gluhncheck $(OBJ): $(HDR) .c.o: - $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS) + $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS) $(CFLAGS_GTK2) -luhncheck: $(OBJ) - $(CC) -o $@ $(OBJ) $(LDFLAGS) +gluhncheck: $(OBJ) + $(CC) -o $@ $(OBJ) $(LDFLAGS) $(LDFLAGS_GTK2) -install: luhncheck +install: gluhncheck mkdir -p -- "$(DESTDIR)$(PREFIX)/bin" mkdir -p -- "$(DESTDIR)$(MANPREFIX)/man1/" - cp -- luhncheck "$(DESTDIR)$(PREFIX)/bin/" - cp -- luhncheck.1 "$(DESTDIR)$(MANPREFIX)/man1/" + cp -- gluhncheck "$(DESTDIR)$(PREFIX)/bin/" + cp -- gluhncheck.1 "$(DESTDIR)$(MANPREFIX)/man1/" uninstall: - -rm -f -- "$(DESTDIR)$(PREFIX)/bin/luhncheck" - -rm -f -- "$(DESTDIR)$(MANPREFIX)/man1/luhncheck.1" + -rm -f -- "$(DESTDIR)$(PREFIX)/bin/gluhncheck" + -rm -f -- "$(DESTDIR)$(MANPREFIX)/man1/gluhncheck.1" clean: -rm -f -- *.o *.a *.lo *.su *.so *.so.* *.gch *.gcov *.gcno *.gcda - -rm -f -- luhncheck + -rm -f -- gluhncheck .SUFFIXES: .SUFFIXES: .o .c @@ -1,40 +1,21 @@ NAME - luhncheck - Check number with Luhn's algorithm + gluhncheck - Check number with Luhn's algorithm SYNOPSIS - luhncheck number ... + gluhncheck DESCRIPTION - The luhncheck utility checks that a number, containing a - check digit, is valid according to Luhn's algorithm. + gluhncheck is a graphical utility that checks that a number, + containing a check digit, is valid according to Luhn's algorithm. OPTIONS - The luhncheck utility conforms to the Base Definitions volume + The gluhncheck utility conforms to the Base Definitions volume of POSIX.1-2017, Section 12.2, Utility Syntax Guidelines. No options are supported. OPERANDS - The following operand is supported: - - number - Number to check. - -STDOUT - The luhncheck utility will print one line per number, in the - order they are provided in the command line, to the standard - output. Each line will be use the format - - "%s is %s\n", number, <"OK" if valid, "invalid" otherwise>. - -EXIT STATUS - The following exit values are returned: - - 0 All numbers are valid. - - 1 At least one number is invalid. - - 2 An error occured. + No operands are supported. SEE ALSO - gluhncheck(1) + luhncheck(1) @@ -1,8 +1,11 @@ PREFIX = /usr MANPREFIX = $(PREFIX)/share/man -CC = c99 +CC = cc -std=c11 CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE CFLAGS = LDFLAGS = + +CFLAGS_GTK2 = $$(pkg-config --cflags gtk+-2.0) +LDFLAGS_GTK2 = $$(pkg-config --libs gtk+-2.0) diff --git a/gluhncheck.1 b/gluhncheck.1 new file mode 100644 index 0000000..e388827 --- /dev/null +++ b/gluhncheck.1 @@ -0,0 +1,33 @@ +.TH GLUHNCHECK 1 GLUHNCHECK + +.SH NAME +gluhncheck \- Check number with Luhn's algorithm + +.SH SYNOPSIS +.B gluhncheck + +.SH DESCRIPTION +.B gluhncheck +is a graphical utility that checks that a number, +containing a check digit, is valid according to Luhn's algorithm. + +.SH OPTIONS +The +.B gluhncheck +utility conforms to the Base Definitions volume of POSIX.1-2017, +.IR "Section 12.2" , +.IR "Utility Syntax Guidelines" . +.PP +No options are supported. + +.SH OPERANDS +No operands are supported. + +.SH ENVIRONMENT VARIABLES +The execution of +.B gluhncheck +is affected by environment variables that +affects its tooltik. + +.SH SEE ALSO +.BR luhncheck (1) 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(); +} diff --git a/luhncheck.1 b/luhncheck.1 deleted file mode 100644 index 11da1e5..0000000 --- a/luhncheck.1 +++ /dev/null @@ -1,85 +0,0 @@ -.TH LUHNCHECK 1 LUHNCHECK - -.SH NAME -luhncheck \- Check number with Luhn's algorithm - -.SH SYNOPSIS -.B luhncheck -.IR number " ..." - -.SH DESCRIPTION -The -.B luhncheck -utility checks that a number, containing a check digit, -is valid according to Luhn's algorithm. - -.SH OPTIONS -The -.B luhncheck -utility conforms to the Base Definitions volume of POSIX.1-2017, -.IR "Section 12.2" , -.IR "Utility Syntax Guidelines" . -.PP -No options are supported. - -.SH OPERANDS -The following operand is supported: -.TP -.I number -Number to check. - -.SH STDIN -Not used. - -.SH INPUT FILES -None. - -.SH ENVIRONMENT VARIABLES -No environment variables affect the execution of -.BR luhncheck . - -.SH ASYNCHRONOUS EVENTS -Default. - -.SH STDOUT -The -.B luhncheck -utility will print one line per -.IR number , -in the order they are provided in the command line, -to the standard output. -Each line will be use the format - -.RS -.B \(dq%s is %s\en\(dq, -.IB number , -.RI < \fB\(dqOK\(dq\fP\ if\ valid,\ \fB\(dqinvalid\(dq\fP\ otherwise >. -.RE - -.SH STDERR -The standard error is only used for diagnostic messages. - -.SH OUTPUT FILES -None. - -.SH EXTENDED DESCRIPTION -None. - -.SH EXIT STATUS -The following exit values are returned: -.TP -0 -All -.IR number s -are valid. -.TP -1 -At least one -.I number -is invalid. -.TP -2 -An error occured. - -.SH SEE ALSO -.BR gluhncheck (1) diff --git a/luhncheck.c b/luhncheck.c deleted file mode 100644 index 51f04ab..0000000 --- a/luhncheck.c +++ /dev/null @@ -1,81 +0,0 @@ -/* See LICENSE file for copyright and license details. */ -#include <libsimple-arg.h> -#include <errno.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - -NUSAGE(2, "number ..."); - - -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) - return 0; - else - break; - s++; - - case 1: - if ('0' <= *s && *s <= '9') - sum += (unsigned)(*s - '0'); - else if (*s) - return 0; - else - break; - s++; - - sum %= 10U; - } - } - - return (sum % 10U) == 0U; -} - - -int -main(int argc, char *argv[]) -{ - int use_colour; - int ret = 0; - int r, ok; - - ARGBEGIN { - default: - usage(); - } ARGEND; - - if (!argc) - usage(); - - use_colour = isatty(STDOUT_FILENO); - - for (; *argv; argv++) { - ok = check_luhn(*argv); - ret |= !ok; - r = printf("%s%s %s%s\n", use_colour ? ok ? "\033[1;32m" : "\033[1;31m" : "", - *argv, ok ? "is OK" : "is invalid", use_colour ? "\033[m" : ""); - if (r < 0) { - fprintf(stderr, "%s: printf: %s\n", argv0, strerror(errno)); - exit(2); - } - } - - if (fflush(stdout) || fclose(stdout)) { - fprintf(stderr, "%s: printf: %s\n", argv0, strerror(errno)); - exit(2); - } - - return ret; -} |
