From 1976ce567613e6569f3bbba7288c870f9d67b7c9 Mon Sep 17 00:00:00 2001 From: Michal Siedlaczek Date: Sat, 4 Jun 2022 15:14:37 -0400 Subject: [PATCH] System notification via event notification system Introduces `devel_new_mail_command` config option. When used instead of `new_mail_command`, Neomutt's event notification system will be used for new mail notifications. This is part of a bigger refactoring of the legacy notification approach. The initial implementation uses only very limited set of expandos. --- Makefile.autosetup | 2 +- core/mailbox.h | 2 + docs/config.c | 11 ++++ main.c | 1 + mutt/lib.h | 2 + mutt/new_mail.c | 99 +++++++++++++++++++++++++++++ mutt/new_mail.h | 40 ++++++++++++ mutt_config.c | 3 + mx.c | 6 ++ test/Makefile.autosetup | 4 ++ test/main.c | 4 ++ test/new_mail/new_mail_format_str.c | 55 ++++++++++++++++ test/new_mail/new_mail_observer.c | 65 +++++++++++++++++++ test/pattern/dummy.c | 8 +-- 14 files changed, 295 insertions(+), 7 deletions(-) create mode 100644 mutt/new_mail.c create mode 100644 mutt/new_mail.h create mode 100644 test/new_mail/new_mail_format_str.c create mode 100644 test/new_mail/new_mail_observer.c diff --git a/Makefile.autosetup b/Makefile.autosetup index 266d54d3dd3..2955ed60379 100644 --- a/Makefile.autosetup +++ b/Makefile.autosetup @@ -563,7 +563,7 @@ LIBMUTTOBJS= mutt/atoi.o mutt/base64.o mutt/buffer.o mutt/charset.o \ mutt/date.o mutt/envlist.o mutt/exit.o mutt/file.o \ mutt/filter.o mutt/hash.o mutt/list.o mutt/logging.o \ mutt/mapping.o mutt/mbyte.o mutt/md5.o mutt/memory.o \ - mutt/notify.o mutt/path.o mutt/pool.o mutt/prex.o \ + mutt/new_mail.o mutt/notify.o mutt/path.o mutt/pool.o mutt/prex.o \ mutt/qsort_r.o mutt/random.o mutt/regex.o mutt/signal.o \ mutt/slist.o mutt/state.o mutt/string.o diff --git a/core/mailbox.h b/core/mailbox.h index 627f12e1842..84897b5533b 100644 --- a/core/mailbox.h +++ b/core/mailbox.h @@ -178,6 +178,8 @@ enum NotifyMailbox NT_MAILBOX_RESORT, ///< Email list needs resorting NT_MAILBOX_UPDATE, ///< Update internal tables NT_MAILBOX_UNTAG, ///< Clear the 'last-tagged' pointer + + NT_MAILBOX_NEW_MAIL, ///< New messages have been added }; /** diff --git a/docs/config.c b/docs/config.c index 616c1c16e6e..608529157bc 100644 --- a/docs/config.c +++ b/docs/config.c @@ -986,6 +986,17 @@ ** or when you save it to another folder. */ +{ "devel_new_mail_command", DT_COMMAND, 0 }, +/* +** .pp +** If \fIset\fP, NeoMutt will call this command after a new message is received. +** The following \fCprintf(3)\fP-like sequences are supported: +** .dl +** .dt %n .dd Mailbox name +** .dt %f .dd Mailbox folder path +** .de +*/ + { "digest_collapse", DT_BOOL, true }, /* ** .pp diff --git a/main.c b/main.c index 38e5a853465..1ccfb58a074 100644 --- a/main.c +++ b/main.c @@ -944,6 +944,7 @@ main notify_observer_add(NeoMutt->notify, NT_CONFIG, main_hist_observer, NULL); notify_observer_add(NeoMutt->notify, NT_CONFIG, main_log_observer, NULL); notify_observer_add(NeoMutt->notify, NT_CONFIG, main_config_observer, NULL); + notify_observer_add(NeoMutt->notify, NT_MAILBOX, new_mail_observer, NULL); if (sendflags & SEND_POSTPONED) { diff --git a/mutt/lib.h b/mutt/lib.h index caa7eda9d56..b093eaeda34 100644 --- a/mutt/lib.h +++ b/mutt/lib.h @@ -46,6 +46,7 @@ * | mutt/mbyte.c | @subpage mutt_mbyte | * | mutt/md5.c | @subpage mutt_md5 | * | mutt/memory.c | @subpage mutt_memory | + * | mutt/new_mail.c | @subpage mutt_new_mail | * | mutt/notify.c | @subpage mutt_notify | * | mutt/path.c | @subpage mutt_path | * | mutt/pool.c | @subpage mutt_pool | @@ -86,6 +87,7 @@ #include "md5.h" #include "memory.h" #include "message.h" +#include "new_mail.h" #include "notify.h" #include "notify_type.h" #include "observer.h" diff --git a/mutt/new_mail.c b/mutt/new_mail.c new file mode 100644 index 00000000000..74dd142d98a --- /dev/null +++ b/mutt/new_mail.c @@ -0,0 +1,99 @@ +/** + * @file + * New mail notification observer implementation. + * + * @authors + * Copyright (C) 2022 Michal Siedlaczek + * + * @copyright + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +/** + * @page new_mail Notify about new incoming mail + * + * Use an external command to send a system notification when new mail arrives. + */ + +#include "config/lib.h" +#include "new_mail.h" +#include "mutt_mailbox.h" +#include "muttlib.h" +#include "protos.h" + +/** + * new_mail_format_str - Format a string for the new mail notification. + * @param[out] buf Buffer in which to save string + * @param[in] buflen Buffer length + * @param[in] col Starting column + * @param[in] cols Number of screen columns + * @param[in] src Printf-like format string + * @param[in] prec Field precision, e.g. "-3.4" + * ############# TODO ############# + * + * | Expando | Description + * | :------ | :------------------------------------------------------- + * | \%n | Folder name + * | \%f | Folder path + */ +const char *new_mail_format_str(char *buf, size_t buflen, size_t col, int cols, + char op, const char *src, const char *prec, + const char *if_str, const char *else_str, + intptr_t data, MuttFormatFlags flags) +{ + struct Mailbox *mailbox = (struct Mailbox *) data; + + switch (op) + { + case 'n': + snprintf(buf, buflen, "%s", NONULL(mailbox->name)); + break; + case 'f': + snprintf(buf, buflen, "%s", NONULL(mailbox_path(mailbox))); + break; + } + return src; +} + +int handle_new_mail_event(const char *cmd, struct NotifyCallback *nc, Execute *execute) +{ + if (nc->event_subtype != NT_MAILBOX_NEW_MAIL) + return 0; + + struct EventMailbox *ev_m = nc->event_data; + if (!ev_m || !ev_m->mailbox) + return 0; + + struct Mailbox *mailbox = ev_m->mailbox; + char expanded_cmd[1024]; + mutt_expando_format(expanded_cmd, 1024, 0, 1024, cmd, new_mail_format_str, + (intptr_t) mailbox, MUTT_FORMAT_NO_FLAGS); + execute(expanded_cmd); + return 0; +} + +int execute_cmd(const char *cmd) +{ + if (mutt_system(cmd) != 0) + mutt_error(_("Error running \"%s\""), cmd); + return 0; +} + +int new_mail_observer(struct NotifyCallback *nc) +{ + const char *c_devel_new_mail_command = cs_subset_string(NeoMutt->sub, "devel_new_mail_command"); + if (!c_devel_new_mail_command) + return 0; + return handle_new_mail_event(c_devel_new_mail_command, nc, execute_cmd); +} diff --git a/mutt/new_mail.h b/mutt/new_mail.h new file mode 100644 index 00000000000..265697c5edd --- /dev/null +++ b/mutt/new_mail.h @@ -0,0 +1,40 @@ +/** + * @file + * New mail notification observer. + * + * @authors + * Copyright (C) 2022 Michal Siedlaczek + * + * @copyright + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#ifndef MUTT_NEW_MAIL_H +#define MUTT_NEW_MAIL_H + +#include +#include +#include "mutt/notify.h" +#include "format_flags.h" + +typedef int(Execute)(const char *cmd); + +const char *new_mail_format_str(char *buf, size_t buflen, size_t col, int cols, + char op, const char *src, const char *prec, + const char *if_str, const char *else_str, + intptr_t data, MuttFormatFlags flags); +int handle_new_mail_event(const char *cmd, struct NotifyCallback *nc, Execute *execute); +int new_mail_observer(struct NotifyCallback *nc); + +#endif diff --git a/mutt_config.c b/mutt_config.c index ac282268e6a..da046f1820e 100644 --- a/mutt_config.c +++ b/mutt_config.c @@ -407,6 +407,9 @@ static struct ConfigDef MainVars[] = { { "new_mail_command", DT_STRING|DT_COMMAND, 0, 0, NULL, "External command to run when new mail arrives" }, + { "devel_new_mail_command", DT_STRING|DT_COMMAND, 0, 0, NULL, + "External command to run when new mail arrives (in development, clashes with new_mail_command)" + }, { "pager", DT_STRING|DT_COMMAND, IP "builtin", 0, NULL, "External command for viewing messages, or 'builtin' to use NeoMutt's" }, diff --git a/mx.c b/mx.c index f0a06a57a68..693265bce86 100644 --- a/mx.c +++ b/mx.c @@ -1808,6 +1808,12 @@ enum MxStatus mx_mbox_check_stats(struct Mailbox *m, uint8_t flags) struct EventMailbox ev_m = { m }; notify_send(m->notify, NT_MAILBOX, NT_MAILBOX_CHANGE, &ev_m); } + if (rc == MX_STATUS_NEW_MAIL && !m->notified) + { + struct EventMailbox ev_m = { m }; + notify_send(m->notify, NT_MAILBOX, NT_MAILBOX_NEW_MAIL, &ev_m); + m->notified = true; + } return rc; } diff --git a/test/Makefile.autosetup b/test/Makefile.autosetup index 8447a9670e2..b13a8469986 100644 --- a/test/Makefile.autosetup +++ b/test/Makefile.autosetup @@ -355,6 +355,9 @@ NEOMUTT_OBJS = test/neo/neomutt_account_add.o \ test/neo/neomutt_mailboxlist_get_all.o \ test/neo/neomutt_new.o +NEW_MAIL_OBJS = test/new_mail/new_mail_observer.o \ + test/new_mail/new_mail_format_str.o + NOTIFY_OBJS = test/notify/notify_free.o \ test/notify/notify_new.o \ test/notify/notify_observer_add.o \ @@ -610,6 +613,7 @@ TEST_OBJS = test/main.o test/common.o \ $(MD5_OBJS) \ $(MEMORY_OBJS) \ $(NEOMUTT_OBJS) \ + $(NEW_MAIL_OBJS) \ $(NOTIFY_OBJS) \ $(NOTMUCH_OBJS) \ $(PARAMETER_OBJS) \ diff --git a/test/main.c b/test/main.c index fefbf99a827..ec51de8eb6e 100644 --- a/test/main.c +++ b/test/main.c @@ -396,6 +396,10 @@ NEOMUTT_TEST_ITEM(test_neomutt_mailboxlist_get_all) \ NEOMUTT_TEST_ITEM(test_neomutt_new) \ \ + /* new_mail */ \ + NEOMUTT_TEST_ITEM(test_new_mail_observer) \ + NEOMUTT_TEST_ITEM(test_new_mail_format_str) \ + \ /* notify */ \ NEOMUTT_TEST_ITEM(test_notify_free) \ NEOMUTT_TEST_ITEM(test_notify_new) \ diff --git a/test/new_mail/new_mail_format_str.c b/test/new_mail/new_mail_format_str.c new file mode 100644 index 00000000000..f83ceacc0ac --- /dev/null +++ b/test/new_mail/new_mail_format_str.c @@ -0,0 +1,55 @@ +/** + * @file + * Test code for new_mail_format_str() + * + * @authors + * Copyright (C) 2022 Michal Siedlaczek + * + * @copyright + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include "mutt/string2.h" +#define TEST_NO_MAIN +#include "config.h" +#include "acutest.h" +#include "mutt/lib.h" +#include "core/lib.h" + +void test_new_mail_format_str(void) +{ + // const char *new_mail_format_str(char *buf, size_t buflen, size_t col, int cols, + // char op, const char *src, const char *prec, + // const char *if_str, const char *else_str, + // intptr_t data, MuttFormatFlags flags); + + char buf[64]; + size_t col = 0; + int cols = 64; + struct Mailbox *mailbox = mailbox_new(); + mailbox->name = mutt_str_dup("MailBox"); + mailbox->pathbuf = mutt_buffer_make(16); + mutt_buffer_strcpy(&mailbox->pathbuf, "/path"); + intptr_t data = (intptr_t) mailbox; + + new_mail_format_str((char *) buf, 64, col, cols, 'n', NULL, NULL, NULL, NULL, data, 0); + TEST_CHECK(mutt_str_equal(buf, "MailBox")); + TEST_MSG("Check failed: %s != MailBox", buf); + + new_mail_format_str((char *) buf, 64, col, cols, 'f', NULL, NULL, NULL, NULL, data, 0); + TEST_CHECK(mutt_str_equal(buf, "/path")); + TEST_MSG("Check failed: %s != /path", buf); + + mailbox_free(&mailbox); +} diff --git a/test/new_mail/new_mail_observer.c b/test/new_mail/new_mail_observer.c new file mode 100644 index 00000000000..1986edd2c49 --- /dev/null +++ b/test/new_mail/new_mail_observer.c @@ -0,0 +1,65 @@ +/** + * @file + * Test code for new_mail_observer() + * + * @authors + * Copyright (C) 2022 Michal Siedlaczek + * + * @copyright + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include "mutt/notify.h" +#include "mutt/observer.h" +#include "mutt/string2.h" +#define TEST_NO_MAIN +#include "config.h" +#include "acutest.h" +#include "mutt/lib.h" +#include "core/lib.h" + +static char *message = NULL; + +int dummy_execute_cmd(const char *cmd) +{ + message = mutt_str_dup(cmd); + return 0; +} + +int dummy_new_mail_observer(struct NotifyCallback *nc) +{ + return handle_new_mail_event("New messages", nc, dummy_execute_cmd); +} + +void test_new_mail_observer(void) +{ + struct Notify *notify = notify_new(); + notify_observer_add(notify, NT_MAILBOX, dummy_new_mail_observer, NULL); + struct Mailbox *mailbox = mailbox_new(); + mailbox->name = mutt_str_dup("Mailbox"); + struct EventMailbox event; + event.mailbox = mailbox; + + notify_send(notify, NT_MAILBOX, NT_MAILBOX_NEW_MAIL, NULL); + TEST_CHECK(message == NULL); + + notify_send(notify, NT_MAILBOX, NT_MAILBOX_NEW_MAIL, &event); + TEST_CHECK(message != NULL); + TEST_CHECK(mutt_str_equal(message, "New messages")); + TEST_MSG("Check failed: \"%s\" != \"New messages\"", message); + + notify_free(¬ify); + mailbox_free(&mailbox); + FREE(&message); +} diff --git a/test/pattern/dummy.c b/test/pattern/dummy.c index 99b4f7f1903..f7f821f9b15 100644 --- a/test/pattern/dummy.c +++ b/test/pattern/dummy.c @@ -26,6 +26,7 @@ #include #include #include "mutt/lib.h" +#include "mutt/string2.h" #include "core/lib.h" #include "history/lib.h" #include "menu/lib.h" @@ -89,16 +90,10 @@ bool g_is_subscribed_list = false; const char *g_myvar = "hello"; short AbortKey; -typedef uint8_t MuttFormatFlags; typedef uint16_t CompletionFlags; typedef uint16_t PagerFlags; typedef uint8_t SelectFileFlags; -typedef const char *(format_t) (char *buf, size_t buflen, size_t col, int cols, - char op, const char *src, const char *prec, - const char *if_str, const char *else_str, - intptr_t data, MuttFormatFlags flags); - struct Address *alias_reverse_lookup(const struct Address *addr) { return NULL; @@ -214,6 +209,7 @@ int mutt_rfc822_write_header(FILE *fp, struct Envelope *env, struct Body *attach void mutt_expando_format(char *buf, size_t buflen, size_t col, int cols, const char *src, format_t *callback, intptr_t data, MuttFormatFlags flags) { + mutt_str_copy(buf, src, buflen); } void menu_pop_current(struct Menu *menu)