aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMattias Andrée <m@maandree.se>2025-12-28 00:12:27 +0100
committerMattias Andrée <m@maandree.se>2025-12-28 00:12:27 +0100
commit9a7f9c3fcd27f4b945dd5159e7fdc8795bb3c452 (patch)
tree886f0bdfca144fc6f2057841eef3313fdf745d06
parentFirst commit (diff)
downloadgluhncheck-9a7f9c3fcd27f4b945dd5159e7fdc8795bb3c452.tar.gz
gluhncheck-9a7f9c3fcd27f4b945dd5159e7fdc8795bb3c452.tar.bz2
gluhncheck-9a7f9c3fcd27f4b945dd5159e7fdc8795bb3c452.tar.xz
Fork into gluhncheckHEAD1.0master
Signed-off-by: Mattias Andrée <m@maandree.se>
Diffstat (limited to '')
-rw-r--r--.gitignore2
-rw-r--r--Makefile22
-rw-r--r--README33
-rw-r--r--config.mk5
-rw-r--r--gluhncheck.133
-rw-r--r--gluhncheck.c307
-rw-r--r--luhncheck.185
-rw-r--r--luhncheck.c81
8 files changed, 363 insertions, 205 deletions
diff --git a/.gitignore b/.gitignore
index 968c03a..ba8620b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,4 +12,4 @@
*.gcov
*.gcno
*.gcda
-/luhncheck
+/gluhncheck
diff --git a/Makefile b/Makefile
index 2cf17d4..e779d2e 100644
--- a/Makefile
+++ b/Makefile
@@ -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
diff --git a/README b/README
index 08f88d5..666c8b3 100644
--- a/README
+++ b/README
@@ -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)
diff --git a/config.mk b/config.mk
index f4adf12..cc39964 100644
--- a/config.mk
+++ b/config.mk
@@ -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(&gtk_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(&copybutton_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;
-}