Compare commits

..

41 Commits

Author SHA1 Message Date
Dianne Skoll
72b0bf96fe Update release notes. 2024-02-29 20:28:09 -05:00
Dianne Skoll
3388849fa5 Fix test bug. 2024-02-29 20:27:19 -05:00
Dianne Skoll
dc9650d5fa Fix test bug. SIGH. 2024-02-29 20:25:13 -05:00
Dianne Skoll
8eb40ae748 Note the bug fix. 2024-02-29 15:53:31 -05:00
Dianne Skoll
89184f1d0f Update release notes. 2024-02-29 15:52:43 -05:00
Dianne Skoll
e899c790b9 Add Catalan translation file, courtesy of Eloi Torrents 2024-02-29 15:29:24 -05:00
Dianne Skoll
bd6d695020 Add some more test cases. 2024-02-29 15:27:55 -05:00
Dianne Skoll
20d4626a71 Bump version to 04.03.00 2024-02-29 13:48:15 -05:00
Dianne Skoll
8ff94c5031 Install the .desktop and icon files; add to menu. 2024-02-29 13:19:00 -05:00
Dianne Skoll
ee185a0eeb Desktop file should be executable. 2024-02-29 13:08:49 -05:00
Dianne Skoll
06f8932efd Add .desktop file and icon for TkRemind, courtesy of Eloi Torrents 2024-02-29 13:04:19 -05:00
Dianne Skoll
1dc627148c Fix tests so they don't depend on current date; add more tests for Feb 29 edge cases. 2024-02-29 12:44:59 -05:00
Dianne Skoll
3cdde5351f Issue "NOTE newdate" in legacy mode in response to an inotify event. 2024-02-29 11:28:05 -05:00
Dianne Skoll
6e93b8a73d Update TkRemind man page to properly reflect inotify support. 2024-02-29 11:26:07 -05:00
Dianne Skoll
267e8533cf Fix stupid bug. 2024-02-29 11:14:05 -05:00
Dianne Skoll
d3bfb0a28f Let Remind handle the inotify stuff. 2024-02-29 11:07:32 -05:00
Dianne Skoll
5a31bc7058 Integrate inotify support directly into Remind for server mode. 2024-02-29 11:03:28 -05:00
Dianne Skoll
746bde71bd Check for inotify_init1 2024-02-29 10:41:49 -05:00
Dianne Skoll
b274ac635c Clarify comment. 2024-02-29 09:46:56 -05:00
Dianne Skoll
9e0a74e583 Don't spit anything out to client for RUN-type reminders in server mode. 2024-02-29 09:31:37 -05:00
Dianne Skoll
0f782f7697 Set CLOEXEC flag on files we open.
When running programs in server mode, connect stdin and stdout to /dev/null
2024-02-29 09:22:15 -05:00
Dianne Skoll
8efde3e9af Fix typo 2024-02-28 10:59:51 -05:00
Dianne Skoll
3bf3137dc4 Check for existence of tags key. 2024-02-28 10:58:46 -05:00
Dianne Skoll
63ec32d28d Add missing $Sunday to man page. 2024-02-27 18:40:35 -05:00
Dianne Skoll
d2f4177cdb Update man pages. 2024-02-27 11:11:21 -05:00
Dianne Skoll
1d958fb7a8 Use JSON server mode from TkRemind. 2024-02-27 10:55:26 -05:00
Dianne Skoll
fcd580d42e Add a test for -zj 2024-02-27 10:28:08 -05:00
Dianne Skoll
34dab68805 Finish implementing "-zj" mode - Daemon mode with JSON responses. 2024-02-27 10:18:18 -05:00
Dianne Skoll
216dd03922 Start adding support for JSON-formatted daemon responses. 2024-02-27 09:54:35 -05:00
Dianne Skoll
5eef9ac621 Add test for zero-arg forms of easterdate() and orthodoxeaster() 2024-02-26 17:21:12 -05:00
Dianne Skoll
6b798d5f7c Allow arg to easterdate() and orthodoxeaster() to be omitted, defaulting it to today(). 2024-02-26 17:19:22 -05:00
Dianne Skoll
22ccce0934 Lay groundwork for having TriggerReminder put the results in a DynamicBuffer rather than sending to stdout
Eventually should allow us to make a JSON-based daemon mode.
2024-02-25 09:17:54 -05:00
Dianne Skoll
fe2af14952 Remove obsolete file 2024-02-24 09:46:56 -05:00
Dianne Skoll
8e99ed27e7 Take is_queued into account when deciding to issue banner. 2024-02-24 09:33:59 -05:00
Dianne Skoll
bb12362cc8 Make "Go To Date..." dialog non-modal. 2024-02-14 11:16:24 -05:00
Dianne Skoll
1bfc630a64 Uneascape JSON properly. 2024-02-07 10:27:38 -05:00
Dianne Skoll
987983f8ae Add empty line between queue items. 2024-02-05 13:49:11 -05:00
Dianne Skoll
657a6118aa Set -selectbackground 2024-02-05 13:46:28 -05:00
Dianne Skoll
43e7e6ec7f Alternate queue item background colors. 2024-02-05 10:10:52 -05:00
Dianne Skoll
b8b3c19fbf *sigh* A JSON key was changed. :( 2024-02-05 09:56:47 -05:00
Dianne Skoll
69298c96a5 Make the version of rem2html track the version of Remind. 2024-02-04 21:23:57 -05:00
29 changed files with 1540 additions and 1222 deletions

1
.gitignore vendored
View File

@@ -16,6 +16,7 @@ man/remind.1
man/tkremind.1 man/tkremind.1
pm_to_blib pm_to_blib
rem2html/Makefile rem2html/Makefile
rem2html/rem2html
rem2html/rem2html.1 rem2html/rem2html.1
rem2pdf/Makefile.PL rem2pdf/Makefile.PL
rem2pdf/Makefile.old rem2pdf/Makefile.old

View File

@@ -44,7 +44,7 @@ test:
@$(MAKE) -C src -s test @$(MAKE) -C src -s test
distclean: clean distclean: clean
rm -f config.cache config.log config.status src/Makefile src/config.h tests/test.out www/Makefile rem2pdf/Makefile.top rem2pdf/Makefile.old rem2pdf/Makefile rem2pdf/Makefile.PL rem2pdf/bin/rem2pdf rm -f config.cache config.log config.status src/Makefile src/config.h tests/test.out www/Makefile rem2pdf/Makefile.top rem2pdf/Makefile.old rem2pdf/Makefile rem2pdf/Makefile.PL rem2pdf/bin/rem2pdf rem2html/rem2html
src/Makefile: src/Makefile.in src/Makefile: src/Makefile.in
./configure ./configure

17
configure vendored
View File

@@ -4034,6 +4034,12 @@ then :
printf "%s\n" "#define HAVE_LANGINFO_H 1" >>confdefs.h printf "%s\n" "#define HAVE_LANGINFO_H 1" >>confdefs.h
fi fi
ac_fn_c_check_header_compile "$LINENO" "sys/inotify.h" "ac_cv_header_sys_inotify_h" "$ac_includes_default"
if test "x$ac_cv_header_sys_inotify_h" = xyes
then :
printf "%s\n" "#define HAVE_SYS_INOTIFY_H 1" >>confdefs.h
fi
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether struct tm is in sys/time.h or time.h" >&5 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether struct tm is in sys/time.h or time.h" >&5
@@ -4211,14 +4217,20 @@ then :
printf "%s\n" "#define HAVE_INITGROUPS 1" >>confdefs.h printf "%s\n" "#define HAVE_INITGROUPS 1" >>confdefs.h
fi fi
ac_fn_c_check_func "$LINENO" "inotify_init1" "ac_cv_func_inotify_init1"
if test "x$ac_cv_func_inotify_init1" = xyes
then :
printf "%s\n" "#define HAVE_INOTIFY_INIT1 1" >>confdefs.h
fi
VERSION=04.02.09 VERSION=04.03.01
ac_config_files="$ac_config_files src/Makefile www/Makefile src/version.h rem2html/Makefile rem2pdf/Makefile.PL rem2pdf/Makefile.top rem2pdf/bin/rem2pdf man/rem.1 man/rem2ps.1 man/remind.1 man/tkremind.1" ac_config_files="$ac_config_files src/Makefile www/Makefile src/version.h rem2html/Makefile rem2html/rem2html rem2pdf/Makefile.PL rem2pdf/Makefile.top rem2pdf/bin/rem2pdf man/rem.1 man/rem2ps.1 man/remind.1 man/tkremind.1"
cat >confcache <<\_ACEOF cat >confcache <<\_ACEOF
# This file is a shell script that caches the results of configure # This file is a shell script that caches the results of configure
@@ -4910,6 +4922,7 @@ do
"www/Makefile") CONFIG_FILES="$CONFIG_FILES www/Makefile" ;; "www/Makefile") CONFIG_FILES="$CONFIG_FILES www/Makefile" ;;
"src/version.h") CONFIG_FILES="$CONFIG_FILES src/version.h" ;; "src/version.h") CONFIG_FILES="$CONFIG_FILES src/version.h" ;;
"rem2html/Makefile") CONFIG_FILES="$CONFIG_FILES rem2html/Makefile" ;; "rem2html/Makefile") CONFIG_FILES="$CONFIG_FILES rem2html/Makefile" ;;
"rem2html/rem2html") CONFIG_FILES="$CONFIG_FILES rem2html/rem2html" ;;
"rem2pdf/Makefile.PL") CONFIG_FILES="$CONFIG_FILES rem2pdf/Makefile.PL" ;; "rem2pdf/Makefile.PL") CONFIG_FILES="$CONFIG_FILES rem2pdf/Makefile.PL" ;;
"rem2pdf/Makefile.top") CONFIG_FILES="$CONFIG_FILES rem2pdf/Makefile.top" ;; "rem2pdf/Makefile.top") CONFIG_FILES="$CONFIG_FILES rem2pdf/Makefile.top" ;;
"rem2pdf/bin/rem2pdf") CONFIG_FILES="$CONFIG_FILES rem2pdf/bin/rem2pdf" ;; "rem2pdf/bin/rem2pdf") CONFIG_FILES="$CONFIG_FILES rem2pdf/bin/rem2pdf" ;;

View File

@@ -38,7 +38,7 @@ AC_CHECK_SIZEOF(unsigned long)
AC_CHECK_SIZEOF(time_t) AC_CHECK_SIZEOF(time_t)
dnl Checks for header files. dnl Checks for header files.
AC_CHECK_HEADERS(sys/types.h glob.h wctype.h locale.h langinfo.h) AC_CHECK_HEADERS(sys/types.h glob.h wctype.h locale.h langinfo.h sys/inotify.h)
dnl Checks for typedefs, structures, and compiler characteristics. dnl Checks for typedefs, structures, and compiler characteristics.
AC_STRUCT_TM AC_STRUCT_TM
@@ -86,13 +86,13 @@ if test "$?" != 0 ; then
echo "*** COULD NOT DETERMINE RELEASE DATE: docs/WHATSNEW is incorrect!" echo "*** COULD NOT DETERMINE RELEASE DATE: docs/WHATSNEW is incorrect!"
exit 1 exit 1
fi fi
AC_CHECK_FUNCS(setenv unsetenv glob mbstowcs setlocale initgroups) AC_CHECK_FUNCS(setenv unsetenv glob mbstowcs setlocale initgroups inotify_init1)
VERSION=04.02.09 VERSION=04.03.01
AC_SUBST(VERSION) AC_SUBST(VERSION)
AC_SUBST(PERL) AC_SUBST(PERL)
AC_SUBST(PERLARTIFACTS) AC_SUBST(PERLARTIFACTS)
AC_SUBST(RELEASE_DATE) AC_SUBST(RELEASE_DATE)
AC_CONFIG_FILES([src/Makefile www/Makefile src/version.h rem2html/Makefile rem2pdf/Makefile.PL rem2pdf/Makefile.top rem2pdf/bin/rem2pdf man/rem.1 man/rem2ps.1 man/remind.1 man/tkremind.1]) AC_CONFIG_FILES([src/Makefile www/Makefile src/version.h rem2html/Makefile rem2html/rem2html rem2pdf/Makefile.PL rem2pdf/Makefile.top rem2pdf/bin/rem2pdf man/rem.1 man/rem2ps.1 man/remind.1 man/tkremind.1])
AC_OUTPUT AC_OUTPUT
chmod a+x rem2pdf/bin/rem2pdf chmod a+x rem2pdf/bin/rem2pdf

View File

@@ -1,5 +1,56 @@
CHANGES TO REMIND CHANGES TO REMIND
* VERSION 4.3 Patch 1 - 2024-02-29
- BUG FIX: tests: "make test" could fail because of a bad test. This
has been fixed. There are no actual code changes to any of the programs
in Remind compared to 04.03.00.
* VERSION 4.3 Patch 0 - 2024-02-29
- IMPROVEMENT: remind: If Remind is compiled on a system that supports
inotify, then in server mode (-z0 or -zj) it monitors the reminders file
and restarts itself if it detects a change, and also notifies the client.
Moving inotify support directly into Remind means that tkremind no longer
has to invoke a separate inotifywait process.
- IMPROVEMENT: remind: Set the CLOEXEC flag on files we open so we
don't leak file descriptors to programs that we run. While I don't
think there's a security issue here (any program you run can do
anything as your userid anyway) it's best to be clean and tidy.
- IMPROVEMENT: remind: Add localization for the Catalan language, courtesy
of Eloi Torrents.
- IMPROVEMENT: tkremind: Add a .desktop file and icon so TkRemind can be
integrated into the desktop menu system, courtesy of Eloi Torrents.
- CHANGE: Add a new server mode with the "-zj" flag. This is just
like "-z0" except it uses JSON messages to communicate with the
client rather than an ad-hoc protocol. The "-z0" mode is still
supported, but is deprecated.
- CHANGE: In server mode (-z0 or -zj) any RUN-type reminders, or message
commands of the "-kcommand" type are run with standard input and standard
output connected to /dev/null. NOTE INCOMPATIBILITY: If you previously
relied on RUN-type reminders to pop up reminders in TkRemind, they no
longer do. If you want this, you'll have to get the command that you
run to pop up its own window with "xmessage" or something similar.
- IMPROVEMENT: tkremind: Make the "Go to date..." dialog non-modal.
- CHANGE: remind: Allow the argument to easterdate() and
orthodoxeaster() to be omitted, in which case it defaults to
today().
- BUG FIX: Miscellaneous man page fixes.
- BUG FIX: Fix a leap-year edge-case. The reminder: REM 29 MSG whatever
was not triggered on Feb 29 of leap years.
- BUG FIX: rem2html: Make the version of rem2html track the version of
Remind. Noted by Ian! D. Allen.
* VERSION 4.2 Patch 9 - 2024-02-04 * VERSION 4.2 Patch 9 - 2024-02-04
- CHANGE: remind: Do not attempt to guess terminal background color on - CHANGE: remind: Do not attempt to guess terminal background color on
@@ -18,7 +69,7 @@ CHANGES TO REMIND
- MINOR NEW FEATURE: remind: The expression STRING * INT or INT * STRING - MINOR NEW FEATURE: remind: The expression STRING * INT or INT * STRING
is now accepted and yields a string that is INT concatenations of the is now accepted and yields a string that is INT concatenations of the
origina STRING. In this case, INT must be non-negative and the total original STRING. In this case, INT must be non-negative and the total
string length can't exceed $MaxStringLen. string length can't exceed $MaxStringLen.
- DOCUMENTATION: Add "Astronomical Algorithms" by Jean Meeus to bibliography. - DOCUMENTATION: Add "Astronomical Algorithms" by Jean Meeus to bibliography.

53
include/lang/ca.rem Normal file
View File

@@ -0,0 +1,53 @@
# Support for the Catalan language.
# This file is part of REMIND.
# REMIND is Copyright (C) 1992-2024 by Dianne Skoll
# This file was created by Eloi Torrents <eloitor@disroot.org>
SET $Monday "dilluns"
SET $Tuesday "dimarts"
SET $Wednesday "dimecres"
SET $Thursday "dijous"
SET $Friday "divendres"
SET $Saturday "dissabte"
SET $Sunday "diumenge"
SET $January "gener"
SET $February "febrer"
SET $March "març"
SET $April "abril"
SET $May "maig"
SET $June "juny"
SET $July "juliol"
SET $August "agost"
SET $September "setembre"
SET $October "octubre"
SET $November "novembre"
SET $December "desembre"
SET $Today "avui"
SET $Tomorrow "demà"
FSET subst_bx(a,d,t) iif(d==today()+2, "demà passat", "d'aquí " + (d-today()) + " dies")
# 1 d'abril vs 1 de maig.
FSET subst_sx(a,d,t) iif(isany(substr(mon(d), 1, 1), "a", "o") , "d'", "de")
FSET subst_ordinal(d) ""
BANNER Agenda pel %w, %d %s %m de %y%o:
SET $Am "am"
SET $Pm "pm"
SET $Ago "fa"
SET $Fromnow "des d'avui"
SET $On "el dia"
SET $Now "ara"
SET $At "a les"
SET $Minute "minut"
SET $Mplu "s"
SET $Hour "hora"
FSET subst_hours(h) iif(h==1, "1 hora", h + " hores")
SET $Is "és"
SET $Was "va ser"
SET $And "i"

View File

@@ -368,9 +368,12 @@ defaults to 1, and can range from 1 to 60. Note that the use of the
\fB\-z\fR option also enables the \fB\-f\fR option. \fB\-z\fR option also enables the \fB\-f\fR option.
.PP .PP
.RS .RS
If you supply the option \fB\-z0\fR, \fBRemind\fR runs in a If you supply the option \fB\-zj\fR, \fBRemind\fR runs in a
special mode called \fBserver mode\fR. This is documented special mode called \fBserver mode\fR. This is documented
in the tkremind man page; see tkremind(1). in the tkremind man page; see tkremind(1). The older server mode
option \fB\-z0\fR still works, but is deprecated; it uses an ad-hoc
method to communicate with the client rather than using JSON to communicate
with the client.
.RE .RE
.TP .TP
\fB\-u\fR\fIname\fR \fB\-u\fR\fIname\fR
@@ -3080,11 +3083,12 @@ will produce undefined results.
Returns the time of "civil twilight" on the specified \fIdate\fR. If Returns the time of "civil twilight" on the specified \fIdate\fR. If
\fIdate\fR is omitted, defaults to \fBtoday()\fR. \fIdate\fR is omitted, defaults to \fBtoday()\fR.
.TP .TP
.B easterdate(dqi_arg) .B easterdate([dqi_arg])
If \fIarg\fR is an \fBINT\fR, then returns the date of Easter Sunday If \fIarg\fR is an \fBINT\fR, then returns the date of Easter Sunday
for the specified year. If \fIarg\fR is a \fBDATE\fR or for the specified year. If \fIarg\fR is a \fBDATE\fR or
\fBDATETIME\fR, then returns the date of the next Easter Sunday on or \fBDATETIME\fR, then returns the date of the next Easter Sunday on or
after \fIarg\fR. (The time component of a datetime is ignored.) after \fIarg\fR. (The time component of a datetime is ignored.) If \fIarg\fR
is omitted, then it defaults to \fBtoday()\fR.
.RS .RS
.P .P
Note that \fBeasterdate\fR computes the Western Easter. For the Orthodox Note that \fBeasterdate\fR computes the Western Easter. For the Orthodox
@@ -3439,11 +3443,12 @@ the actual time, or a time supplied on the command line.
Returns a string that is the ordinal number \fInum\fR. For example, Returns a string that is the ordinal number \fInum\fR. For example,
\fBord(2)\fR returns "2nd", and \fBord(213)\fR returns "213th". \fBord(2)\fR returns "2nd", and \fBord(213)\fR returns "213th".
.TP .TP
.B orthodoxeaster(dqi_arg) .B orthodoxeaster([dqi_arg])
If \fIarg\fR is an \fBINT\fR, then returns the date of Orthodox Easter Sunday If \fIarg\fR is an \fBINT\fR, then returns the date of Orthodox Easter Sunday
for the specified year. If \fIarg\fR is a \fBDATE\fR or for the specified year. If \fIarg\fR is a \fBDATE\fR or
\fBDATETIME\fR, then returns the date of the next Orthodox Easter Sunday on or \fBDATETIME\fR, then returns the date of the next Orthodox Easter Sunday on or
after \fIarg\fR. (The time component of a datetime is ignored.) after \fIarg\fR. (The time component of a datetime is ignored.) If \fIarg\fR
is omitted, then it defaults to \fBtoday()\fR.
.RS .RS
.P .P
Note that \fBorthodoxeaster\fR computes the Orthodox Easter. For the Western Note that \fBorthodoxeaster\fR computes the Orthodox Easter. For the Western
@@ -5255,7 +5260,7 @@ A number of system variables let you translate various phrases
to other languages. These system variables are: to other languages. These system variables are:
.PP .PP
.TP .TP
.B $Monday, $Tuesday, $Wednesday, $Thursday, $Friday, $Saturday .B $Monday, $Tuesday, $Wednesday, $Thursday, $Friday, $Saturday, $Sunday
Set each of these system variables to a string representing the corresponding Set each of these system variables to a string representing the corresponding
day's name in your language. Strings must be valid UTF-8 strings. day's name in your language. Strings must be valid UTF-8 strings.
.TP .TP

View File

@@ -301,11 +301,11 @@ Today
.SH IMMEDIATE UPDATES .SH IMMEDIATE UPDATES
If you are running \fBTkRemind\fR on Linux and have the If you are running \fBTkRemind\fR on Linux and \fBRemind\fR has been
\fBinotifywait\fR program installed (part of the \fBinotify-tools\fR compiled with \fBinotify\fR(7) support, then \fBTkRemind\fR redraws
or similar package), then \fBTkRemind\fR redraws the calendar window the calendar window \fIimmediately\fR if \fB$HOME/.reminders\fR
\fIimmediately\fR if \fB$HOME/.reminders\fR changes (or, if it is a changes (or, if it is a directory, any files in that directory
directory, any files in that directory change.) change.)
.PP .PP
This lets \fBTkRemind\fR react immediately to hand-edited reminders or This lets \fBTkRemind\fR react immediately to hand-edited reminders or
to reminder files that are imported from another calendar system (for example, to reminder files that are imported from another calendar system (for example,
@@ -366,75 +366,91 @@ your hand-edited files in a separate \fB*.rem\fR file than \fBTkRemind\fR's
\fBRemind\fR has a special mode for interacting with programs like \fBRemind\fR has a special mode for interacting with programs like
\fBTkRemind\fR. This mode is called \fIserver mode\fR and is \fBTkRemind\fR. This mode is called \fIserver mode\fR and is
selected by supplying the \fB\-z0\fR option to \fBRemind\fR. selected by supplying the \fB\-zj\fR option to \fBRemind\fR.
In server mode, \fBRemind\fR operates similar to daemon mode, except In server mode, \fBRemind\fR operates similar to daemon mode, except
it reads commands (one per line) it reads commands (one per line) from standard input and writes status
from standard input and writes status lines to standard output. lines to standard output. Each status line is a JSON object.
The commands accepted in server mode are: The commands accepted in server mode are:
.TP .TP
EXIT EXIT
Terminate the \fBRemind\fR process. EOF on standard input does the Terminate the \fBRemind\fR process. EOF on standard input does the
same thing. same thing. \fBRemind\fR exits immediately without printing
a JSON status line.
.TP .TP
STATUS STATUS
Return the number of queued reminders. Return the number of queued reminders. The JSON object looks
something like this:
.nf
{"response":"queued","nqueued":n,"command":"STATUS"}
.fi
where \fIn\fR is the number of reminders queued.
.TP .TP
QUEUE QUEUE or JSONQUEUE
Returns the contents of the queue, printed between "NOTE queue" and Returns the contents of the queue. The JSON object looks something
"NOTE endqueue" lines. like this:
.nf
.TP {"response":"queue","queue":[ ... ],"command":"QUEUE"}
JSONQUEUE
Returns the contents of the queue in JSON format, printed between .fi
"NOTE JSONQUEUE" and "NOTE ENDJSONQUEUE" lines. The value of the \fBqueue\fR key is an array of JSON objects, each
representing a queued reminder.
.TP .TP
REREAD REREAD
Re-read the reminder file Re-read the reminder file. Returns the following status line:
.nf
{"response":"reread","command":"REREAD"}
.fi
.PP .PP
The status lines written are as follows: Additional status lines written are as follows:
.TP .TP
NOTE reminder \fItime\fR \fItag\fR .nf
Signifies the beginning of a timed reminder whose trigger time is
\fItime\fR with tag \fItag\fR. If the reminder has no tag, an {"response":"reminder","ttime":tt,"now":now,"tags":tags,"body":body}
asterisk is supplied for \fItag\fR. All lines following this line
are the body of the reminder, until the line \fBNOTE endreminder\fR .fi
is transmitted. In this line, \fItt\fR is the trigger time of the reminder (expressed
as a string), \fInow\fR is the current time, \fItags\fR (if present)
is the tag or tags associated with the reminder, and \fIbody\fR is
the body of the reminder. This response causes \fBTkRemind\fR to
pop up a reminder notification.
.TP .TP
NOTE newdate .nf
{"response":"newdate"}
.fi
This line is emitted whenever \fBRemind\fR has detected a rollover of This line is emitted whenever \fBRemind\fR has detected a rollover of
the system date. The front-end program should redraw its calendar the system date. The front-end program should redraw its calendar
or take whatever other action is needed. or take whatever other action is needed.
.TP .TP
NOTE reread .nf
This line is emitted whenever the number of reminders in \fBRemind\fR's
queue changes because of a date rollover or a \fBREREAD\fR command.
The front-end should issue a \fBSTATUS\fR command in response to this
message.
.TP {"response":"reread","command":"inotify"}
NOTE queued \fIn\fR
This line is emitted in response to a \fBSTATUS\fR command. The number
\fIn\fR is the number of reminders in the queue.
.TP .fi
NOTE queue
Indicates that queue contents are about to follow. The end of the
queue is indicated by a NOTE endqueue line.
.TP If \fBRemind\fR was compiled with support for \fBinotify\fR(7), then
NOTE JSONQUEUE if it detects a change to the top-level reminder file or directory,
Indicates that queue contents in JSON format are about to follow. The it issues the above response. The front-end should redraw its
end of the queue is indicated by a NOTE ENDJSONQUEUE line. calendar since this response indicates that a change has been made
to the reminder file or directory.
.PP .PP
Please note that \fBRemind\fR can write a status message \fIat any time\fR Please note that \fBRemind\fR can write a status message \fIat any time\fR

View File

@@ -10,7 +10,7 @@ use Encode;
my %Options; my %Options;
my $rem2html_version = '2.1'; my $rem2html_version = '@VERSION@';
my($days, $shades, $moons, $classes, $Month, $Year, $Numdays, $Firstwkday, $Mondayfirst, $weeks, my($days, $shades, $moons, $classes, $Month, $Year, $Numdays, $Firstwkday, $Mondayfirst, $weeks,
@Daynames, $Nextmon, $Nextlen, $Prevmon, $Prevlen); @Daynames, $Nextmon, $Nextlen, $Prevmon, $Prevlen);

15
resources/tkremind.desktop Executable file
View File

@@ -0,0 +1,15 @@
[Desktop Entry]
Type=Application
Exec=tkremind
StartupNotify=true
Icon=tkremind
Terminal=false
Name=tkremind
Comment=TkRemind Calendar Program
Categories=Office;Calendar;
Keywords=Calendar;remind;
Keywords[ca]=Calendari;remind;
Keywords[de]=Kalender;remind;
Keywords[en_GB]=Calendar;remind;
Keywords[es]=Calendario;remind;

BIN
resources/tkremind.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -110,6 +110,19 @@ if {$tcl_platform(platform) == "windows"} {
exit 1 exit 1
} }
#---------------------------------------------------------------------------
# GetRemindVersion
# Arguments:
# none
# Returns:
# The version of Remind
#---------------------------------------------------------------------------
proc GetRemindVersion {} {
global Remind
set ver [exec sh -c "(echo \"banner %\"; echo \"msg \[version()\]%\") | $Remind -"]
return $ver
}
#--------------------------------------------------------------------------- #---------------------------------------------------------------------------
# GLOBAL VARIABLES # GLOBAL VARIABLES
#--------------------------------------------------------------------------- #---------------------------------------------------------------------------
@@ -203,9 +216,6 @@ set ConfigFile ""
set EditorPid -1 set EditorPid -1
# Inotify file
set InotifyFP ""
# Errors from last remind run # Errors from last remind run
set RemindErrors "" set RemindErrors ""
@@ -294,6 +304,14 @@ set WarningHeaders [list "# Lines starting with REM TAG TKTAGnnn ... were create
# Highest tag seen so far. Array of tags is stored in ReminderTags() # Highest tag seen so far. Array of tags is stored in ReminderTags()
set HighestTagSoFar 0 set HighestTagSoFar 0
# Check Remind version
set ver [GetRemindVersion]
if {"$ver" < "04.03.00"} {
tk_dialog .error Error "This version of TkRemind requires Remind version 04.03.00 or newer; you have version $ver" error 0 OK
exit 1
}
proc get_weekday { yyyymmdd } { proc get_weekday { yyyymmdd } {
global EnglishDayNames global EnglishDayNames
return [lindex $EnglishDayNames [clock format [clock scan $yyyymmdd] -format %w -locale C]] return [lindex $EnglishDayNames [clock format [clock scan $yyyymmdd] -format %w -locale C]]
@@ -1528,9 +1546,7 @@ proc GotoDialog {} {
bind .g <KeyPress-Escape> ".g.b.cancel flash; .g.b.cancel invoke" bind .g <KeyPress-Escape> ".g.b.cancel flash; .g.b.cancel invoke"
CenterWindow .g . CenterWindow .g .
set oldFocus [focus] set oldFocus [focus]
grab .g
focus .g.y.e focus .g.y.e
tkwait window .g
catch {focus $oldFocus} catch {focus $oldFocus}
} }
@@ -1551,7 +1567,7 @@ proc DoGoto {} {
set month [lsearch -exact $MonthNames [.g.mon cget -text]] set month [lsearch -exact $MonthNames [.g.mon cget -text]]
set CurMonth $month set CurMonth $month
set CurYear $year set CurYear $year
destroy .g catch { destroy .g }
FillCalWindow FillCalWindow
} }
@@ -1560,19 +1576,14 @@ proc DoGoto {} {
#--------------------------------------------------------------------------- #---------------------------------------------------------------------------
proc Quit {} { proc Quit {} {
global Option global Option
global InotifyFP
if { !$Option(ConfirmQuit) } { if { !$Option(ConfirmQuit) } {
destroy . destroy .
StopBackgroundRemindDaemon StopBackgroundRemindDaemon
catch { exec kill [pid $InotifyFP] }
catch { close $InotifyFP }
exit 0 exit 0
} }
if { [tk_dialog .question "Confirm..." {Really quit?} question 0 No Yes] } { if { [tk_dialog .question "Confirm..." {Really quit?} question 0 No Yes] } {
destroy . destroy .
StopBackgroundRemindDaemon StopBackgroundRemindDaemon
catch { exec kill [pid $InotifyFP] }
catch { close $InotifyFP }
exit 0 exit 0
} }
} }
@@ -2603,7 +2614,6 @@ proc BrowseForFileRead {w {dir ""}} {
cd $cwd cd $cwd
$w.entry delete 0 end $w.entry delete 0 end
} }
#--------------------------------------------------------------------------- #---------------------------------------------------------------------------
# StartBackgroundRemindDaemon # StartBackgroundRemindDaemon
# Arguments: # Arguments:
@@ -2616,9 +2626,9 @@ proc BrowseForFileRead {w {dir ""}} {
proc StartBackgroundRemindDaemon {} { proc StartBackgroundRemindDaemon {} {
global Remind DaemonFile ReminderFile Option TwentyFourHourMode global Remind DaemonFile ReminderFile Option TwentyFourHourMode
if {$TwentyFourHourMode} { if {$TwentyFourHourMode} {
set problem [catch { set DaemonFile [open "|$Remind -b1 -z0 -itkremind=1 $Option(ExtraRemindArgs) $ReminderFile" "r+"] } err] set problem [catch { set DaemonFile [open "|$Remind -b1 -zj -itkremind=1 $Option(ExtraRemindArgs) $ReminderFile" "r+"] } err]
} else { } else {
set problem [catch { set DaemonFile [open "|$Remind -z0 -itkremind=1 $Option(ExtraRemindArgs) $ReminderFile" "r+"] } err] set problem [catch { set DaemonFile [open "|$Remind -zj -itkremind=1 $Option(ExtraRemindArgs) $ReminderFile" "r+"] } err]
} }
if {$problem} { if {$problem} {
tk_dialog .error Error "Can't start Remind daemon in background: $err" error 0 OK tk_dialog .error Error "Can't start Remind daemon in background: $err" error 0 OK
@@ -2674,19 +2684,19 @@ proc RestartBackgroundRemindDaemon {} {
#--------------------------------------------------------------------------- #---------------------------------------------------------------------------
# ShowQueue # ShowQueue
# Arguments: # Arguments:
# file -- file channel that is readable # queue - the queue
# Returns: # Returns:
# nothing # nothing
# Description: # Description:
# Dumps the debugging queue listing # Dumps the debugging queue listing
#--------------------------------------------------------------------------- #---------------------------------------------------------------------------
proc ShowQueue { file } { proc ShowQueue { queue } {
set w .queuedbg set w .queuedbg
catch { destroy $w } catch { destroy $w }
toplevel $w toplevel $w
wm title $w "Queue (Debugging Output)" wm title $w "Queue (Debugging Output)"
wm iconname $w "Queue Dbg" wm iconname $w "Queue Dbg"
text $w.t -width 80 -height 30 -wrap word -yscrollcommand "$w.sb set" text $w.t -fg black -bg white -width 80 -height 30 -wrap word -yscrollcommand "$w.sb set"
scrollbar $w.sb -orient vertical -command "$w.text yview" scrollbar $w.sb -orient vertical -command "$w.text yview"
button $w.ok -text "OK" -command "destroy $w" button $w.ok -text "OK" -command "destroy $w"
grid $w.t -row 0 -column 0 -sticky nsew grid $w.t -row 0 -column 0 -sticky nsew
@@ -2697,31 +2707,34 @@ proc ShowQueue { file } {
grid rowconfigure $w 0 -weight 1 grid rowconfigure $w 0 -weight 1
grid rowconfigure $w 1 -weight 0 grid rowconfigure $w 1 -weight 0
CenterWindow $w . CenterWindow $w .
while (1) { set obj [lsort -command sort_q $queue]
# We should only get one line set did 0
gets $file line $w.t tag configure grey -background "#DDDDDD" -selectbackground "#999999"
if {$line == "NOTE ENDJSONQUEUE"} { set toggle 0
break foreach q $obj {
} if { $did > 0 } {
if {[catch {set obj [::json::json2dict $line]}]} { $w.t insert end "\n"
continue;
}
set obj [lsort -command sort_q $obj]
set did 0
foreach q $obj {
$w.t insert end "$q\n"
set did 1
}
if { $did == 0 } {
$w.t insert end "(Queue is empty)\n"
} }
foreach r $q {
if { $toggle != 0 } {
$w.t insert end "$r " grey
} else {
$w.t insert end "$r "
}
}
$w.t insert end "\n"
set toggle [expr 1 - $toggle]
set did 1
}
if { $did == 0 } {
$w.t insert end "(Queue is empty)\n"
} }
$w.t configure -state disabled $w.t configure -state disabled
} }
proc sort_q { a b } { proc sort_q { a b } {
set a_ttime [dict get $a nextttime] set a_ttime [dict get $a nexttime]
set b_ttime [dict get $b nextttime] set b_ttime [dict get $b nexttime]
if {$a_ttime < $b_ttime} { if {$a_ttime < $b_ttime} {
return -1 return -1
} }
@@ -2748,43 +2761,63 @@ proc DaemonReadable { file } {
catch { close $file } catch { close $file }
return return
} }
switch -glob -- $line { if {[catch {set obj [::json::json2dict $line]}]} {
"NOTE reminder*" { return;
scan $line "NOTE reminder %s %s %s" time now tag }
IssueBackgroundReminder $file $time $now $tag if (![dict exists $obj response]) {
} return;
"NOTE JSONQUEUE" { }
ShowQueue $file set response [dict get $obj response]
} switch -- $response {
"NOTE newdate" { "queued" {
# Date has rolled over -- clear "ignore" list set n [dict get $obj nqueued]
catch { unset Ignore}
Initialize
FillCalWindow
ShowTodaysReminders
}
"NOTE reread" {
puts $file "STATUS"
flush $file
}
"NOTE queued*" {
scan $line "NOTE queued %d" n
if {$n == 1} { if {$n == 1} {
.b.nqueued configure -text "1 reminder queued" .b.nqueued configure -text "1 reminder queued"
} else { } else {
.b.nqueued configure -text "$n reminders queued" .b.nqueued configure -text "$n reminders queued"
} }
} }
default { "reminder" {
puts stderr "Unknown message from daemon: $line\n" set time [dict get $obj ttime]
} set now [dict get $obj now]
set tag "*"
if {[dict exists $obj tags]} {
set tag [dict get $obj tags]
}
set body [dict get $obj body]
IssueBackgroundReminder $body $time $now $tag
}
"queue" {
set queue [dict get $obj queue]
ShowQueue $queue
}
"newdate" {
# Date has rolled over -- clear "ignore" list
catch { unset Ignore}
Initialize
FillCalWindow
ShowTodaysReminders
}
"reread" {
if {[dict exists $obj command]} {
set cmd [dict get $obj command]
if {"$cmd" == "inotify"} {
FillCalWindow
}
}
puts $file "STATUS"
flush $file
}
default {
puts stderr "Unknown message from daemon: $line\n"
}
} }
} }
#--------------------------------------------------------------------------- #---------------------------------------------------------------------------
# IssueBackgroundReminder # IssueBackgroundReminder
# Arguments: # Arguments:
# file -- file channel that is readable # body -- body of reminder
# time -- time of reminder # time -- time of reminder
# now -- current time according to Remind daemon # now -- current time according to Remind daemon
# tag -- tag for reminder, or "*" if no tag # tag -- tag for reminder, or "*" if no tag
@@ -2793,26 +2826,14 @@ proc DaemonReadable { file } {
# Description: # Description:
# Reads a background reminder from daemon and pops up window. # Reads a background reminder from daemon and pops up window.
#--------------------------------------------------------------------------- #---------------------------------------------------------------------------
proc IssueBackgroundReminder { file time now tag } { proc IssueBackgroundReminder { body time now tag } {
global BgCounter Option Ignore global BgCounter Option Ignore
if {$Option(Deiconify)} { if {$Option(Deiconify)} {
wm deiconify . wm deiconify .
} }
set msg ""
set line ""
while (1) {
gets $file line
if {$line == "NOTE endreminder"} {
break
}
if {$msg != ""} {
append msg "\n";
}
append msg $line
}
# Do nothing if it's blank -- was probably a RUN-type reminder. # Do nothing if it's blank -- was probably a RUN-type reminder.
if {$msg == ""} { if {$body == ""} {
return return
} }
@@ -2827,17 +2848,17 @@ proc IssueBackgroundReminder { file time now tag } {
wm iconname $w "Reminder" wm iconname $w "Reminder"
wm title $w "Timed reminder ($time)" wm title $w "Timed reminder ($time)"
label $w.l -text "Reminder for $time issued at $now" label $w.l -text "Reminder for $time issued at $now"
message $w.msg -width 6i -text $msg message $w.msg -width 6i -text $body
frame $w.b frame $w.b
# Automatically shut down window after a minute if option says so # Automatically shut down window after a minute if option says so
set after_token [after 60000 [list ClosePopup $w "" $Option(MailAddr) $Option(AutoClose) "" $tag $msg $time]] set after_token [after 60000 [list ClosePopup $w "" $Option(MailAddr) $Option(AutoClose) "" $tag $body $time]]
wm protocol $w WM_DELETE_WINDOW [list ClosePopup $w $after_token "" 1 "" $tag $msg $time] wm protocol $w WM_DELETE_WINDOW [list ClosePopup $w $after_token "" 1 "" $tag $body $time]
button $w.ok -text "OK" -command [list ClosePopup $w $after_token "" 1 "" $tag $msg $time] button $w.ok -text "OK" -command [list ClosePopup $w $after_token "" 1 "" $tag $body $time]
if {$tag != "*"} { if {$tag != "*"} {
button $w.nomore -text "Don't remind me again today" -command [list ClosePopup $w $after_token "" 1 "ignore" $tag $msg $time] button $w.nomore -text "Don't remind me again today" -command [list ClosePopup $w $after_token "" 1 "ignore" $tag $body $time]
button $w.kill -text "Delete this reminder completely" -command [list ClosePopup $w $after_token "" 1 "kill" $tag $msg $time] button $w.kill -text "Delete this reminder completely" -command [list ClosePopup $w $after_token "" 1 "kill" $tag $body $time]
} }
pack $w.l -side top pack $w.l -side top
pack $w.msg -side top -expand 1 -fill both pack $w.msg -side top -expand 1 -fill both
@@ -2855,7 +2876,7 @@ proc IssueBackgroundReminder { file time now tag } {
} }
if {$Option(RunCmd) != ""} { if {$Option(RunCmd) != ""} {
if {$Option(FeedReminder)} { if {$Option(FeedReminder)} {
FeedReminderToCommand $Option(RunCmd) "$time: $msg" FeedReminderToCommand $Option(RunCmd) "$time: $body"
} else { } else {
exec "/bin/sh" "-c" $Option(RunCmd) "&" exec "/bin/sh" "-c" $Option(RunCmd) "&"
} }
@@ -2905,7 +2926,7 @@ proc main {} {
global AppendFile HighestTagSoFar DayNames global AppendFile HighestTagSoFar DayNames
catch { catch {
puts "\nTkRemind Copyright (C) 1996-2021 Dianne Skoll" puts "\nTkRemind Copyright (C) 1996-2024 Dianne Skoll"
} }
catch { SetFonts } catch { SetFonts }
Initialize Initialize
@@ -2924,7 +2945,6 @@ proc main {} {
CreateCalWindow $DayNames CreateCalWindow $DayNames
FillCalWindow FillCalWindow
StartBackgroundRemindDaemon StartBackgroundRemindDaemon
SetupInotify
DisplayTimeContinuously DisplayTimeContinuously
} }
@@ -3956,30 +3976,6 @@ proc SetFonts {} {
set SetFontsWorked 1 set SetFontsWorked 1
} }
# Set up inotify to watch for changes to reminder file/directory
proc SetupInotify {} {
global InotifyFP
global ReminderFile
set failed [catch {set InotifyFP [open "|inotifywait -r -q -m -e close_write -e move -e create -e delete $ReminderFile < /dev/null 2>/dev/null" "r"] } ]
if {$failed} {
# inotifywait probably not available... meh.
return
}
fileevent $InotifyFP readable [list InotifyReadable $InotifyFP]
}
# Called when inotifywait reports an event. Schedule a calendar update
# and daemon reload.
proc InotifyReadable { fp } {
catch { set num [gets $fp line] }
if {$num < 0} {
catch { exec kill [pid $fp] }
catch { close $fp }
return
}
ScheduleUpdateForChanges
}
### Balloon help ### Balloon help
set Balloon(HelpTime) 400 set Balloon(HelpTime) 400
set Balloon(StayTime) 3500 set Balloon(StayTime) 3500

View File

@@ -63,6 +63,15 @@ install: all
done done
-mkdir -p $(DESTDIR)$(datarootdir)/remind || true -mkdir -p $(DESTDIR)$(datarootdir)/remind || true
cp -R ../include/* $(DESTDIR)$(datarootdir)/remind cp -R ../include/* $(DESTDIR)$(datarootdir)/remind
-mkdir -p $(DESTDIR)$(prefix)/icons
-mkdir -p $(DESTDIR)$(prefix)/applications
$(INSTALL_DATA) $(srcdir)/../resources/tkremind.png $(DESTDIR)$(prefix)/icons
$(INSTALL_PROGRAM) $(srcdir)/../resources/tkremind.desktop $(DESTDIR)$(prefix)/applications
-if test "$(DESTDIR)" = ""; then \
update-desktop-database < /dev/null > /dev/null 2>&1 ; \
xdg-icon-resource install --novendor --size 64 $(DESTDIR)$(prefix)/icons/tkremind.png < /dev/null > /dev/null 2>&1; \
xdg-desktop-menu install --novendor $(DESTDIR)$(prefix)/applications/tkremind.desktop < /dev/null > /dev/null 2>&1 ; \
fi
install-stripped: install install-stripped: install
strip $(DESTDIR)$(bindir)/remind || true strip $(DESTDIR)$(bindir)/remind || true

View File

@@ -475,7 +475,7 @@ void PrintJSONKeyPairTime(char const *name, int t)
} }
#ifdef REM_USE_WCHAR #ifdef REM_USE_WCHAR
void PutWideChar(wchar_t const wc) void PutWideChar(wchar_t const wc, DynamicBuffer *output)
{ {
char buf[MB_CUR_MAX+1]; char buf[MB_CUR_MAX+1];
int len; int len;
@@ -483,7 +483,11 @@ void PutWideChar(wchar_t const wc)
len = wctomb(buf, wc); len = wctomb(buf, wc);
if (len > 0) { if (len > 0) {
buf[len] = 0; buf[len] = 0;
fputs(buf, stdout); if (output) {
DBufPuts(output, buf);
} else {
fputs(buf, stdout);
}
} }
} }
#endif #endif
@@ -1227,7 +1231,7 @@ static void PrintLeft(char const *s, int width, char pad)
ws = buf; ws = buf;
for (i=0; i<width;) { for (i=0; i<width;) {
if (*ws) { if (*ws) {
PutWideChar(*ws++); PutWideChar(*ws++, NULL);
i+= wcwidth(*ws); i+= wcwidth(*ws);
} else { } else {
break; break;
@@ -1235,7 +1239,7 @@ static void PrintLeft(char const *s, int width, char pad)
} }
/* Mop up any potential combining characters */ /* Mop up any potential combining characters */
while (*ws && wcwidth(*ws) == 0) { while (*ws && wcwidth(*ws) == 0) {
PutWideChar(*ws++); PutWideChar(*ws++, NULL);
} }
/* Possibly send lrm control sequence */ /* Possibly send lrm control sequence */
@@ -1308,7 +1312,7 @@ static void PrintCentered(char const *s, int width, char *pad)
for (i=0; i<d; i++) fputs(pad, stdout); for (i=0; i<d; i++) fputs(pad, stdout);
for (i=0; i<width; i++) { for (i=0; i<width; i++) {
if (*ws) { if (*ws) {
PutWideChar(*ws++); PutWideChar(*ws++, NULL);
if (wcwidth(*ws) == 0) { if (wcwidth(*ws) == 0) {
/* Don't count this character... it's zero-width */ /* Don't count this character... it's zero-width */
i--; i--;
@@ -1319,7 +1323,7 @@ static void PrintCentered(char const *s, int width, char *pad)
} }
/* Mop up any potential combining characters */ /* Mop up any potential combining characters */
while (*ws && wcwidth(*ws) == 0) { while (*ws && wcwidth(*ws) == 0) {
PutWideChar(*ws++); PutWideChar(*ws++, NULL);
} }
/* Possibly send lrm control sequence */ /* Possibly send lrm control sequence */
send_lrm(); send_lrm();
@@ -1448,7 +1452,7 @@ static int WriteOneColLine(int col)
} }
numwritten += wcwidth(*ws); numwritten += wcwidth(*ws);
} }
PutWideChar(*ws); PutWideChar(*ws, NULL);
} }
} }
e->wc_pos = ws; e->wc_pos = ws;
@@ -1463,7 +1467,7 @@ static int WriteOneColLine(int col)
if (wcwidth(*ws) > 0) { if (wcwidth(*ws) > 0) {
numwritten += wcwidth(*ws); numwritten += wcwidth(*ws);
} }
PutWideChar(*ws); PutWideChar(*ws, NULL);
} }
} }
} }
@@ -2306,15 +2310,9 @@ void WriteJSONTrigger(Trigger const *t, int include_tags, int today)
if (t->noqueue) { if (t->noqueue) {
PrintJSONKeyPairInt("noqueue", 1); PrintJSONKeyPairInt("noqueue", 1);
} }
if (*t->sched) { PrintJSONKeyPairString("sched", t->sched);
PrintJSONKeyPairString("sched", t->sched); PrintJSONKeyPairString("warn", t->warn);
} PrintJSONKeyPairString("omitfunc", t->omitfunc);
if (*t->warn) {
PrintJSONKeyPairString("warn", t->warn);
}
if (*t->omitfunc) {
PrintJSONKeyPairString("omitfunc", t->omitfunc);
}
if (t->addomit) { if (t->addomit) {
PrintJSONKeyPairInt("addomit", 1); PrintJSONKeyPairInt("addomit", 1);
} }

View File

@@ -10,6 +10,9 @@
/* Define if you have the <sys/types.h> header file. */ /* Define if you have the <sys/types.h> header file. */
#undef HAVE_SYS_TYPES_H #undef HAVE_SYS_TYPES_H
/* Define if you have the <sys/inotify.h> header file. */
#undef HAVE_SYS_INOTIFY_H
/* Define if you have the <glob.h> header file */ /* Define if you have the <glob.h> header file */
#undef HAVE_GLOB_H #undef HAVE_GLOB_H
@@ -17,6 +20,8 @@
#undef HAVE_LOCALE_H #undef HAVE_LOCALE_H
#undef HAVE_INOTIFY_INIT1
#undef HAVE_LANGINFO_H #undef HAVE_LANGINFO_H
#undef HAVE_GLOB #undef HAVE_GLOB

View File

@@ -192,7 +192,7 @@ int DoRem(ParsePtr p)
r = OK; r = OK;
if (ShouldTriggerReminder(&trig, &tim, dse, &err)) { if (ShouldTriggerReminder(&trig, &tim, dse, &err)) {
if ( (r=TriggerReminder(p, &trig, &tim, dse, 0)) ) { if ( (r=TriggerReminder(p, &trig, &tim, dse, 0, NULL)) ) {
FreeTrig(&trig); FreeTrig(&trig);
return r; return r;
} }
@@ -910,7 +910,7 @@ static int ParseScanFrom(ParsePtr s, Trigger *t, int type)
/* Trigger the reminder if it's a RUN or MSG type. */ /* Trigger the reminder if it's a RUN or MSG type. */
/* */ /* */
/***************************************************************/ /***************************************************************/
int TriggerReminder(ParsePtr p, Trigger *t, TimeTrig *tim, int dse, int is_queued) int TriggerReminder(ParsePtr p, Trigger *t, TimeTrig *tim, int dse, int is_queued, DynamicBuffer *output)
{ {
int r, y, m, d; int r, y, m, d;
char PrioExpr[VAR_NAME_LEN+25]; char PrioExpr[VAR_NAME_LEN+25];
@@ -977,7 +977,7 @@ int TriggerReminder(ParsePtr p, Trigger *t, TimeTrig *tim, int dse, int is_queue
} }
/* If it's a MSG-type reminder, and no -k option was used, issue the banner. */ /* If it's a MSG-type reminder, and no -k option was used, issue the banner. */
if ((t->typ == MSG_TYPE || t->typ == MSF_TYPE) if ((t->typ == MSG_TYPE || t->typ == MSF_TYPE)
&& !DidMsgReminder && !NextMode && !msg_command) { && !DidMsgReminder && !NextMode && !msg_command && !is_queued) {
DidMsgReminder = 1; DidMsgReminder = 1;
if (!DoSubstFromString(DBufValue(&Banner), &buf, if (!DoSubstFromString(DBufValue(&Banner), &buf,
DSEToday, NO_TIME) && DSEToday, NO_TIME) &&
@@ -1044,11 +1044,18 @@ int TriggerReminder(ParsePtr p, Trigger *t, TimeTrig *tim, int dse, int is_queue
return E_NO_MEM; return E_NO_MEM;
} }
printf("%s%s%s\n", DBufValue(&calRow), DBufValue(&pre_buf), DBufValue(&buf)); r = OK;
if (output) {
if (DBufPuts(output, DBufValue(&calRow)) != OK) r = E_NO_MEM;
if (DBufPuts(output, DBufValue(&pre_buf)) != OK) r = E_NO_MEM;
if (DBufPuts(output, DBufValue(&buf)) != OK) r = E_NO_MEM;
} else {
printf("%s%s%s\n", DBufValue(&calRow), DBufValue(&pre_buf), DBufValue(&buf));
}
DBufFree(&buf); DBufFree(&buf);
DBufFree(&pre_buf); DBufFree(&pre_buf);
DBufFree(&calRow); DBufFree(&calRow);
return OK; return r;
} }
/* Correct colors */ /* Correct colors */
@@ -1144,23 +1151,27 @@ int TriggerReminder(ParsePtr p, Trigger *t, TimeTrig *tim, int dse, int is_queue
case MSG_TYPE: case MSG_TYPE:
case PASSTHRU_TYPE: case PASSTHRU_TYPE:
if (msg_command) { if (msg_command) {
DoMsgCommand(msg_command, DBufValue(&buf)); DoMsgCommand(msg_command, DBufValue(&buf), is_queued);
} else { } else {
/* Add a space before "NOTE endreminder" */ if (output) {
if (IsServerMode() && !strncmp(DBufValue(&buf), "NOTE endreminder", 16)) { DBufPuts(output, DBufValue(&buf));
printf(" %s", DBufValue(&buf));
} else { } else {
printf("%s", DBufValue(&buf)); /* Add a space before "NOTE endreminder" */
if (IsServerMode() && !strncmp(DBufValue(&buf), "NOTE endreminder", 16)) {
printf(" %s", DBufValue(&buf));
} else {
printf("%s", DBufValue(&buf));
}
} }
} }
break; break;
case MSF_TYPE: case MSF_TYPE:
FillParagraph(DBufValue(&buf)); FillParagraph(DBufValue(&buf), output);
break; break;
case RUN_TYPE: case RUN_TYPE:
System(DBufValue(&buf)); System(DBufValue(&buf), is_queued);
break; break;
default: /* Unknown/illegal type? */ default: /* Unknown/illegal type? */
@@ -1387,7 +1398,7 @@ static int ParsePriority(ParsePtr s, Trigger *t)
/* Execute the '-k' command, escaping shell chars in message. */ /* Execute the '-k' command, escaping shell chars in message. */
/* */ /* */
/***************************************************************/ /***************************************************************/
int DoMsgCommand(char const *cmd, char const *msg) int DoMsgCommand(char const *cmd, char const *msg, int is_queued)
{ {
int r; int r;
int i, l; int i, l;
@@ -1424,7 +1435,7 @@ int DoMsgCommand(char const *cmd, char const *msg)
} }
r = OK; r = OK;
System(DBufValue(&execBuffer)); System(DBufValue(&execBuffer), is_queued);
finished: finished:
DBufFree(&buf); DBufFree(&buf);

View File

@@ -15,7 +15,7 @@
#include "config.h" #include "config.h"
#include <stdio.h> #include <stdio.h>
#include <fcntl.h>
#include <string.h> #include <string.h>
#include <errno.h> #include <errno.h>
#include <ctype.h> #include <ctype.h>
@@ -100,6 +100,18 @@ static int CheckSafety (void);
static int CheckSafetyAux (struct stat *statbuf); static int CheckSafetyAux (struct stat *statbuf);
static int PopFile (void); static int PopFile (void);
static int IncludeCmd(char const *); static int IncludeCmd(char const *);
void set_cloexec(int fd)
{
int flags;
flags = fcntl(fd, F_GETFD);
if (flags >= 0) {
flags |= FD_CLOEXEC;
fcntl(fd, F_SETFD, flags);
}
}
static void OpenPurgeFile(char const *fname, char const *mode) static void OpenPurgeFile(char const *fname, char const *mode)
{ {
DynamicBuffer fname_buf; DynamicBuffer fname_buf;
@@ -123,6 +135,7 @@ static void OpenPurgeFile(char const *fname, char const *mode)
if (!PurgeFP) { if (!PurgeFP) {
fprintf(ErrFp, "Cannot open `%s' for writing: %s\n", DBufValue(&fname_buf), strerror(errno)); fprintf(ErrFp, "Cannot open `%s' for writing: %s\n", DBufValue(&fname_buf), strerror(errno));
} }
set_cloexec(fileno(PurgeFP));
DBufFree(&fname_buf); DBufFree(&fname_buf);
} }
@@ -327,6 +340,7 @@ int OpenFile(char const *fname)
} }
} else { } else {
fp = fopen(fname, "r"); fp = fopen(fname, "r");
if (fp) set_cloexec(fileno(fp));
if (DebugFlag & DB_TRACE_FILES) { if (DebugFlag & DB_TRACE_FILES) {
fprintf(ErrFp, "Reading `%s': Opening file on disk\n", fname); fprintf(ErrFp, "Reading `%s': Opening file on disk\n", fname);
} }
@@ -346,6 +360,7 @@ int OpenFile(char const *fname)
if (strcmp(fname, "-")) { if (strcmp(fname, "-")) {
fp = fopen(fname, "r"); fp = fopen(fname, "r");
if (!fp || !CheckSafety()) return E_CANT_OPEN; if (!fp || !CheckSafety()) return E_CANT_OPEN;
set_cloexec(fileno(fp));
if (PurgeMode) OpenPurgeFile(fname, "w"); if (PurgeMode) OpenPurgeFile(fname, "w");
} else { } else {
fp = stdin; fp = stdin;
@@ -542,6 +557,7 @@ static int PopFile(void)
if (strcmp(i->filename, "-")) { if (strcmp(i->filename, "-")) {
fp = fopen(i->filename, "r"); fp = fopen(i->filename, "r");
if (!fp || !CheckSafety()) return E_CANT_OPEN; if (!fp || !CheckSafety()) return E_CANT_OPEN;
set_cloexec(fileno(fp));
if (PurgeMode) OpenPurgeFile(i->filename, "a"); if (PurgeMode) OpenPurgeFile(i->filename, "a");
} else { } else {
fp = stdin; fp = stdin;

View File

@@ -251,7 +251,7 @@ BuiltinFunc Func[] = {
{ "defined", 1, 1, 0, FDefined }, { "defined", 1, 1, 0, FDefined },
{ "dosubst", 1, 3, 0, FDosubst }, { "dosubst", 1, 3, 0, FDosubst },
{ "dusk", 0, 1, 0, FDusk }, { "dusk", 0, 1, 0, FDusk },
{ "easterdate", 1, 1, 0, FEasterdate }, { "easterdate", 0, 1, 0, FEasterdate },
{ "evaltrig", 1, 2, 0, FEvalTrig }, { "evaltrig", 1, 2, 0, FEvalTrig },
{ "filedate", 1, 1, 0, FFiledate }, { "filedate", 1, 1, 0, FFiledate },
{ "filedatetime", 1, 1, 0, FFiledatetime }, { "filedatetime", 1, 1, 0, FFiledatetime },
@@ -289,7 +289,7 @@ BuiltinFunc Func[] = {
{ "nonomitted", 2, NO_MAX, 0, FNonomitted }, { "nonomitted", 2, NO_MAX, 0, FNonomitted },
{ "now", 0, 0, 0, FNow }, { "now", 0, 0, 0, FNow },
{ "ord", 1, 1, 1, FOrd }, { "ord", 1, 1, 1, FOrd },
{ "orthodoxeaster",1, 1, 0, FOrthodoxeaster }, { "orthodoxeaster",0, 1, 0, FOrthodoxeaster },
{ "ostype", 0, 0, 1, FOstype }, { "ostype", 0, 0, 1, FOstype },
{ "pad", 3, 4, 1, FPad }, { "pad", 3, 4, 1, FPad },
{ "plural", 1, 3, 1, FPlural }, { "plural", 1, 3, 1, FPlural },
@@ -2364,13 +2364,17 @@ static int FEasterdate(func_info *info)
{ {
int y, m, d; int y, m, d;
int g, c, x, z, e, n; int g, c, x, z, e, n;
if (ARG(0).type == INT_TYPE) { if (Nargs == 0) {
y = ARGV(0); FromDSE(DSEToday, &y, &m, &d);
if (y < BASE) return E_2LOW; } else {
else if (y > BASE+YR_RANGE) return E_2HIGH; if (ARG(0).type == INT_TYPE) {
} else if (HASDATE(ARG(0))) { y = ARGV(0);
FromDSE(DATEPART(ARG(0)), &y, &m, &d); /* We just want the year */ if (y < BASE) return E_2LOW;
} else return E_BAD_TYPE; else if (y > BASE+YR_RANGE) return E_2HIGH;
} else if (HASDATE(ARG(0))) {
FromDSE(DATEPART(ARG(0)), &y, &m, &d); /* We just want the year */
} else return E_BAD_TYPE;
}
do { do {
g = (y % 19) + 1; /* golden number */ g = (y % 19) + 1; /* golden number */
@@ -2409,13 +2413,17 @@ static int FOrthodoxeaster(func_info *info)
{ {
int y, m, d; int y, m, d;
int a, b, c, dd, e, f, dse; int a, b, c, dd, e, f, dse;
if (ARG(0).type == INT_TYPE) { if (Nargs == 0) {
y = ARGV(0); FromDSE(DSEToday, &y, &m, &d);
if (y < BASE) return E_2LOW; } else {
else if (y > BASE+YR_RANGE) return E_2HIGH; if (ARG(0).type == INT_TYPE) {
} else if (HASDATE(ARG(0))) { y = ARGV(0);
FromDSE(DATEPART(ARG(0)), &y, &m, &d); /* We just want the year */ if (y < BASE) return E_2LOW;
} else return E_BAD_TYPE; else if (y > BASE+YR_RANGE) return E_2HIGH;
} else if (HASDATE(ARG(0))) {
FromDSE(DATEPART(ARG(0)), &y, &m, &d); /* We just want the year */
} else return E_BAD_TYPE;
}
do { do {
a = y % 4; a = y % 4;

View File

@@ -92,6 +92,7 @@ EXTERN INIT( int DontQueue, 0);
EXTERN INIT( int NumQueued, 0); EXTERN INIT( int NumQueued, 0);
EXTERN INIT( int DontIssueAts, 0); EXTERN INIT( int DontIssueAts, 0);
EXTERN INIT( int Daemon, 0); EXTERN INIT( int Daemon, 0);
EXTERN INIT( int DaemonJSON, 0);
EXTERN INIT( char DateSep, DATESEP); EXTERN INIT( char DateSep, DATESEP);
EXTERN INIT( char TimeSep, TIMESEP); EXTERN INIT( char TimeSep, TIMESEP);
EXTERN INIT( char DateTimeSep, DATETIMESEP); EXTERN INIT( char DateTimeSep, DATETIMESEP);

View File

@@ -425,7 +425,11 @@ void InitRemind(int argc, char const *argv[])
case 'z': case 'z':
case 'Z': case 'Z':
DontFork = 1; DontFork = 1;
if (*arg == '0') { if (*arg == 'j' || *arg == 'J') {
while (*arg) arg++;
Daemon = -1;
DaemonJSON = 1;
} else if (*arg == '0') {
PARSENUM(Daemon, arg); PARSENUM(Daemon, arg);
if (Daemon == 0) Daemon = -1; if (Daemon == 0) Daemon = -1;
else if (Daemon < 1) Daemon = 1; else if (Daemon < 1) Daemon = 1;

View File

@@ -14,6 +14,8 @@
#define _XOPEN_SOURCE 600 #define _XOPEN_SOURCE 600
#include "config.h" #include "config.h"
#include <fcntl.h>
#include <sys/wait.h>
#include <errno.h> #include <errno.h>
#include <stdlib.h> #include <stdlib.h>
#include <unistd.h> #include <unistd.h>
@@ -45,6 +47,9 @@
static void DoReminders(void); static void DoReminders(void);
/* Macro for simplifying common block so as not to litter code */
#define OUTPUT(c) do { if (output) { DBufPutc(output, c); } else { putchar(c); } } while(0)
/***************************************************************/ /***************************************************************/
/***************************************************************/ /***************************************************************/
/** **/ /** **/
@@ -1251,19 +1256,19 @@ int CalcMinsFromUTC(int dse, int tim, int *mins, int *isdst)
return 0; return 0;
} }
static char const *OutputEscapeSequences(char const *s, int print) static char const *OutputEscapeSequences(char const *s, int print, DynamicBuffer *output)
{ {
while (*s == 0x1B && *(s+1) == '[') { while (*s == 0x1B && *(s+1) == '[') {
if (print) putchar(*s); if (print) OUTPUT(*s);
s++; s++;
if (print) putchar(*s); if (print) OUTPUT(*s);
s++; s++;
while (*s && (*s < 0x40 || *s > 0x7E)) { while (*s && (*s < 0x40 || *s > 0x7E)) {
if (print) putchar(*s); if (print) OUTPUT(*s);
s++; s++;
} }
if (*s) { if (*s) {
if (print) putchar(*s); if (print) OUTPUT(*s);
s++; s++;
} }
} }
@@ -1272,19 +1277,19 @@ static char const *OutputEscapeSequences(char const *s, int print)
#ifdef REM_USE_WCHAR #ifdef REM_USE_WCHAR
#define ISWBLANK(c) (iswspace(c) && (c) != '\n') #define ISWBLANK(c) (iswspace(c) && (c) != '\n')
static wchar_t const *OutputEscapeSequencesWS(wchar_t const *s, int print) static wchar_t const *OutputEscapeSequencesWS(wchar_t const *s, int print, DynamicBuffer *output)
{ {
while (*s == 0x1B && *(s+1) == '[') { while (*s == 0x1B && *(s+1) == '[') {
if (print) PutWideChar(*s); if (print) PutWideChar(*s, output);
s++; s++;
if (print) PutWideChar(*s); if (print) PutWideChar(*s, output);
s++; s++;
while (*s && (*s < 0x40 || *s > 0x7E)) { while (*s && (*s < 0x40 || *s > 0x7E)) {
if (print) PutWideChar(*s); if (print) PutWideChar(*s, output);
s++; s++;
} }
if (*s) { if (*s) {
if (print) PutWideChar(*s); if (print) PutWideChar(*s, output);
s++; s++;
} }
} }
@@ -1293,7 +1298,7 @@ static wchar_t const *OutputEscapeSequencesWS(wchar_t const *s, int print)
static void static void
FillParagraphWCAux(wchar_t const *s) FillParagraphWCAux(wchar_t const *s, DynamicBuffer *output)
{ {
int line = 0; int line = 0;
int i, j; int i, j;
@@ -1308,7 +1313,7 @@ FillParagraphWCAux(wchar_t const *s)
/* If it's a carriage return, output it and start new paragraph */ /* If it's a carriage return, output it and start new paragraph */
if (*s == '\n') { if (*s == '\n') {
putchar('\n'); OUTPUT('\n');
s++; s++;
line = 0; line = 0;
while(ISWBLANK(*s)) s++; while(ISWBLANK(*s)) s++;
@@ -1321,7 +1326,7 @@ FillParagraphWCAux(wchar_t const *s)
number of spaces */ number of spaces */
j = line ? SubsIndent : FirstIndent; j = line ? SubsIndent : FirstIndent;
for (i=0; i<j; i++) { for (i=0; i<j; i++) {
putchar(' '); OUTPUT(' ');
} }
/* Calculate the amount of room left on this line */ /* Calculate the amount of room left on this line */
@@ -1334,7 +1339,7 @@ FillParagraphWCAux(wchar_t const *s)
if (*s == '\n') break; if (*s == '\n') break;
while(1) { while(1) {
t = s; t = s;
s = OutputEscapeSequencesWS(s, 1); s = OutputEscapeSequencesWS(s, 1, output);
if (s == t) break; if (s == t) break;
while(ISWBLANK(*s)) s++; while(ISWBLANK(*s)) s++;
} }
@@ -1342,7 +1347,7 @@ FillParagraphWCAux(wchar_t const *s)
len = 0; len = 0;
while(*s && !iswspace(*s)) { while(*s && !iswspace(*s)) {
if (*s == 0x1B && *(s+1) == '[') { if (*s == 0x1B && *(s+1) == '[') {
s = OutputEscapeSequencesWS(s, 0); s = OutputEscapeSequencesWS(s, 0, output);
continue; continue;
} }
len += wcwidth(*s); len += wcwidth(*s);
@@ -1353,17 +1358,17 @@ FillParagraphWCAux(wchar_t const *s)
} }
if (!pendspace || len+pendspace <= roomleft) { if (!pendspace || len+pendspace <= roomleft) {
for (i=0; i<pendspace; i++) { for (i=0; i<pendspace; i++) {
putchar(' '); OUTPUT(' ');
} }
while(t < s) { while(t < s) {
PutWideChar(*t); PutWideChar(*t, output);
if (strchr(EndSent, *t)) doublespace = 2; if (strchr(EndSent, *t)) doublespace = 2;
else if (!strchr(EndSentIg, *t)) doublespace = 1; else if (!strchr(EndSentIg, *t)) doublespace = 1;
t++; t++;
} }
} else { } else {
s = t; s = t;
putchar('\n'); OUTPUT('\n');
line++; line++;
break; break;
} }
@@ -1374,7 +1379,7 @@ FillParagraphWCAux(wchar_t const *s)
} }
static int static int
FillParagraphWC(char const *s) FillParagraphWC(char const *s, DynamicBuffer *output)
{ {
size_t len; size_t len;
wchar_t *buf; wchar_t *buf;
@@ -1384,7 +1389,7 @@ FillParagraphWC(char const *s)
buf = calloc(len+1, sizeof(wchar_t)); buf = calloc(len+1, sizeof(wchar_t));
if (!buf) return E_NO_MEM; if (!buf) return E_NO_MEM;
(void) mbstowcs(buf, s, len+1); (void) mbstowcs(buf, s, len+1);
FillParagraphWCAux(buf); FillParagraphWCAux(buf, output);
free(buf); free(buf);
return OK; return OK;
} }
@@ -1404,7 +1409,7 @@ FillParagraphWC(char const *s)
/* A macro safe ONLY if used with arg with no side effects! */ /* A macro safe ONLY if used with arg with no side effects! */
#define ISBLANK(c) (isspace(c) && (c) != '\n') #define ISBLANK(c) (isspace(c) && (c) != '\n')
void FillParagraph(char const *s) void FillParagraph(char const *s, DynamicBuffer *output)
{ {
int line = 0; int line = 0;
@@ -1422,7 +1427,7 @@ void FillParagraph(char const *s)
if (!*s) return; if (!*s) return;
#ifdef REM_USE_WCHAR #ifdef REM_USE_WCHAR
if (FillParagraphWC(s) == OK) { if (FillParagraphWC(s, output) == OK) {
return; return;
} }
#endif #endif
@@ -1432,7 +1437,7 @@ void FillParagraph(char const *s)
/* If it's a carriage return, output it and start new paragraph */ /* If it's a carriage return, output it and start new paragraph */
if (*s == '\n') { if (*s == '\n') {
putchar('\n'); OUTPUT('\n');
s++; s++;
line = 0; line = 0;
while(ISBLANK(*s)) s++; while(ISBLANK(*s)) s++;
@@ -1445,7 +1450,7 @@ void FillParagraph(char const *s)
number of spaces */ number of spaces */
j = line ? SubsIndent : FirstIndent; j = line ? SubsIndent : FirstIndent;
for (i=0; i<j; i++) { for (i=0; i<j; i++) {
putchar(' '); OUTPUT(' ');
} }
/* Calculate the amount of room left on this line */ /* Calculate the amount of room left on this line */
@@ -1458,7 +1463,7 @@ void FillParagraph(char const *s)
if (*s == '\n') break; if (*s == '\n') break;
while(1) { while(1) {
t = s; t = s;
s = OutputEscapeSequences(s, 1); s = OutputEscapeSequences(s, 1, output);
if (s == t) break; if (s == t) break;
while(ISBLANK(*s)) s++; while(ISBLANK(*s)) s++;
} }
@@ -1466,7 +1471,7 @@ void FillParagraph(char const *s)
len = 0; len = 0;
while(*s && !isspace(*s)) { while(*s && !isspace(*s)) {
if (*s == 0x1B && *(s+1) == '[') { if (*s == 0x1B && *(s+1) == '[') {
s = OutputEscapeSequences(s, 0); s = OutputEscapeSequences(s, 0, output);
continue; continue;
} }
s++; s++;
@@ -1477,17 +1482,17 @@ void FillParagraph(char const *s)
} }
if (!pendspace || len+pendspace <= roomleft) { if (!pendspace || len+pendspace <= roomleft) {
for (i=0; i<pendspace; i++) { for (i=0; i<pendspace; i++) {
putchar(' '); OUTPUT(' ');
} }
while(t < s) { while(t < s) {
putchar(*t); OUTPUT(*t);
if (strchr(EndSent, *t)) doublespace = 2; if (strchr(EndSent, *t)) doublespace = 2;
else if (!strchr(EndSentIg, *t)) doublespace = 1; else if (!strchr(EndSentIg, *t)) doublespace = 1;
t++; t++;
} }
} else { } else {
s = t; s = t;
putchar('\n'); OUTPUT('\n');
line++; line++;
break; break;
} }
@@ -1651,11 +1656,45 @@ SaveLastTimeTrig(TimeTrig const *t)
memcpy(&LastTimeTrig, t, sizeof(LastTimeTrig)); memcpy(&LastTimeTrig, t, sizeof(LastTimeTrig));
} }
/* Wrapper to ignore warnings about ignoring return value of system() */ /* Wrapper to ignore warnings about ignoring return value of system()
Also redirects stdin and stdout to /dev/null for queued reminders */
void void
System(char const *cmd) System(char const *cmd, int is_queued)
{ {
int r; int r;
pid_t kid;
int fd;
int status;
if (is_queued && IsServerMode()) {
/* Server mode... redirect stdin and stdout to /dev/null */
kid = fork();
if (kid == (pid_t) -1) {
/* Fork failed... nothing we can do */
return;
} else if (kid == 0) {
/* In the child */
(void) close(STDIN_FILENO);
(void) close(STDOUT_FILENO);
fd = open("/dev/null", O_RDONLY);
if (fd >= 0 && fd != STDIN_FILENO) {
dup2(fd, STDIN_FILENO);
close(STDIN_FILENO);
}
fd = open("/dev/null", O_WRONLY);
if (fd >= 0 && fd != STDOUT_FILENO) {
dup2(fd, STDOUT_FILENO);
close(STDOUT_FILENO);
}
} else {
/* In the parent */
while (waitpid(kid, &status, 0) != kid) {
continue;
}
return;
}
}
/* This is the child process or original if we never forked */
r = system(cmd); r = system(cmd);
if (r == 0) { if (r == 0) {
return; return;

View File

@@ -38,7 +38,7 @@ int DoRem (ParsePtr p);
int DoFlush (ParsePtr p); int DoFlush (ParsePtr p);
void DoExit (ParsePtr p); void DoExit (ParsePtr p);
int ParseRem (ParsePtr s, Trigger *trig, TimeTrig *tim, int save_in_globals); int ParseRem (ParsePtr s, Trigger *trig, TimeTrig *tim, int save_in_globals);
int TriggerReminder (ParsePtr p, Trigger *t, TimeTrig *tim, int dse, int is_queued); int TriggerReminder (ParsePtr p, Trigger *t, TimeTrig *tim, int dse, int is_queued, DynamicBuffer *output);
int ShouldTriggerReminder (Trigger *t, TimeTrig *tim, int dse, int *err); int ShouldTriggerReminder (Trigger *t, TimeTrig *tim, int dse, int *err);
int DoSubst (ParsePtr p, DynamicBuffer *dbuf, Trigger *t, TimeTrig *tt, int dse, int mode); int DoSubst (ParsePtr p, DynamicBuffer *dbuf, Trigger *t, TimeTrig *tt, int dse, int mode);
int DoSubstFromString (char const *source, DynamicBuffer *dbuf, int dse, int tim); int DoSubstFromString (char const *source, DynamicBuffer *dbuf, int dse, int tim);
@@ -118,7 +118,7 @@ void DestroyVars (int all);
int PreserveVar (char const *name); int PreserveVar (char const *name);
int DoPreserve (Parser *p); int DoPreserve (Parser *p);
int DoSatRemind (Trigger *trig, TimeTrig *tt, ParsePtr p); int DoSatRemind (Trigger *trig, TimeTrig *tt, ParsePtr p);
int DoMsgCommand (char const *cmd, char const *msg); int DoMsgCommand (char const *cmd, char const *msg, int is_queued);
int ParseNonSpaceChar (ParsePtr p, int *err, int peek); int ParseNonSpaceChar (ParsePtr p, int *err, int peek);
unsigned int HashVal (char const *str); unsigned int HashVal (char const *str);
int DateOK (int y, int m, int d); int DateOK (int y, int m, int d);
@@ -142,7 +142,7 @@ int GetSysVar (char const *name, Value *val);
int SetSysVar (char const *name, Value *val); int SetSysVar (char const *name, Value *val);
void DumpSysVarByName (char const *name); void DumpSysVarByName (char const *name);
int CalcMinsFromUTC (int dse, int tim, int *mins, int *isdst); int CalcMinsFromUTC (int dse, int tim, int *mins, int *isdst);
void FillParagraph (char const *s); void FillParagraph (char const *s, DynamicBuffer *output);
void LocalToUTC (int locdate, int loctime, int *utcdate, int *utctime); void LocalToUTC (int locdate, int loctime, int *utcdate, int *utctime);
void UTCToLocal (int utcdate, int utctime, int *locdate, int *loctime); void UTCToLocal (int utcdate, int utctime, int *locdate, int *loctime);
int MoonPhase (int date, int time); int MoonPhase (int date, int time);
@@ -168,7 +168,7 @@ void PrintJSONKeyPairString(char const *name, char const *val);
void PrintJSONKeyPairDate(char const *name, int dse); void PrintJSONKeyPairDate(char const *name, int dse);
void PrintJSONKeyPairDateTime(char const *name, int dt); void PrintJSONKeyPairDateTime(char const *name, int dt);
void PrintJSONKeyPairTime(char const *name, int t); void PrintJSONKeyPairTime(char const *name, int t);
void System(char const *cmd); void System(char const *cmd, int queued);
int ShellEscape(char const *in, DynamicBuffer *out); int ShellEscape(char const *in, DynamicBuffer *out);
int AddGlobalOmit(int dse); int AddGlobalOmit(int dse);
void set_lat_and_long_from_components(void); void set_lat_and_long_from_components(void);
@@ -179,6 +179,7 @@ int GetTerminalBackground(void);
char const *get_day_name(int wkday); char const *get_day_name(int wkday);
char const *get_month_name(int mon); char const *get_month_name(int mon);
void set_cloexec(int fd);
int push_call(char const *filename, char const *func, int lineno); int push_call(char const *filename, char const *func, int lineno);
void clear_callstack(void); void clear_callstack(void);
int print_callstack(FILE *fp); int print_callstack(FILE *fp);
@@ -190,5 +191,5 @@ void WriteJSONTimeTrigger(TimeTrig const *tt);
#define _XOPEN_SOURCE 600 #define _XOPEN_SOURCE 600
#include <wctype.h> #include <wctype.h>
#include <wchar.h> #include <wchar.h>
void PutWideChar(wchar_t const wc); void PutWideChar(wchar_t const wc, DynamicBuffer *output);
#endif #endif

View File

@@ -24,17 +24,28 @@
#include <sys/types.h> #include <sys/types.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/time.h> #include <sys/time.h>
#include <time.h>
#include <sys/select.h> #include <sys/select.h>
#include <stdlib.h> #include <stdlib.h>
#include <unistd.h> #include <unistd.h>
#include <fcntl.h> #include <fcntl.h>
#include <errno.h>
#include "types.h" #include "types.h"
#include "globals.h" #include "globals.h"
#include "err.h" #include "err.h"
#include "protos.h" #include "protos.h"
#include "expr.h" #include "expr.h"
#undef USE_INOTIFY
#if defined(HAVE_SYS_INOTIFY_H) && defined(HAVE_INOTIFY_INIT1)
#define USE_INOTIFY 1
#include <sys/inotify.h>
int watch_fd = -1;
static void consume_inotify_events(int fd);
static int setup_inotify_watch(void);
#endif
/* A list of filenames associated with queued reminders */ /* A list of filenames associated with queued reminders */
typedef struct queuedfname { typedef struct queuedfname {
struct queuedfname *next; struct queuedfname *next;
@@ -73,6 +84,33 @@ static void reread (void);
static void PrintQueue(void); static void PrintQueue(void);
static char const *QueueFilename(char const *fname); static char const *QueueFilename(char const *fname);
static void chomp(DynamicBuffer *buf)
{
char *s = DBufValue(buf);
int l = DBufLen(buf);
while (l) {
if (s[l-1] == '\n') {
s[l-1] = 0;
DBufLen(buf)--;
l--;
} else {
break;
}
}
}
char const *SimpleTimeNoSpace(int tim)
{
char *s = (char *) SimpleTime(tim);
if (s && *s) {
size_t l = strlen(s);
if (l > 0 && s[l-1] == ' ') {
s[l-1] = 0;
}
}
return s;
}
/***************************************************************/ /***************************************************************/
/* */ /* */
/* QueueFilename */ /* QueueFilename */
@@ -212,7 +250,14 @@ print_num_queued(void)
} }
q = q->next; q = q->next;
} }
printf("NOTE queued %d\n", nqueued); if (DaemonJSON) {
printf("{");
PrintJSONKeyPairString("response", "queued");
PrintJSONKeyPairInt("nqueued", nqueued);
printf("\"command\":\"STATUS\"}\n");
} else {
printf("NOTE queued %d\n", nqueued);
}
fflush(stdout); fflush(stdout);
} }
@@ -233,9 +278,6 @@ void HandleQueuedReminders(void)
struct timeval sleep_tv; struct timeval sleep_tv;
struct sigaction sa; struct sigaction sa;
/* Suppress the BANNER from being issued */
DidMsgReminder = 1;
/* Turn off sorting -- otherwise, TriggerReminder has no effect! */ /* Turn off sorting -- otherwise, TriggerReminder has no effect! */
SortByDate = 0; SortByDate = 0;
@@ -285,6 +327,11 @@ void HandleQueuedReminders(void)
(void) sigaction(SIGCONT, &sa, NULL); (void) sigaction(SIGCONT, &sa, NULL);
} }
#ifdef USE_INOTIFY
if (IsServerMode()) {
watch_fd = setup_inotify_watch();
}
#endif
/* Sit in a loop, issuing reminders when necessary */ /* Sit in a loop, issuing reminders when necessary */
while(1) { while(1) {
q = FindNextReminder(); q = FindNextReminder();
@@ -367,24 +414,44 @@ void HandleQueuedReminders(void)
/* Trigger the reminder */ /* Trigger the reminder */
CreateParser(q->text, &p); CreateParser(q->text, &p);
RunDisabled = q->RunDisabled; RunDisabled = q->RunDisabled;
if (IsServerMode()) { if (IsServerMode() && q->typ != RUN_TYPE) {
printf("NOTE reminder %s", if (DaemonJSON) {
SimpleTime(q->tt.ttime)); printf("{\"response\":\"reminder\",");
printf("%s", SimpleTime(MinutesPastMidnight(1))); PrintJSONKeyPairString("ttime", SimpleTimeNoSpace(q->tt.ttime));
if (!*DBufValue(&q->t.tags)) { PrintJSONKeyPairString("now", SimpleTimeNoSpace(MinutesPastMidnight(1)));
printf("*\n"); PrintJSONKeyPairString("tags", DBufValue(&q->t.tags));
} else { } else {
printf("%s\n", DBufValue(&(q->t.tags))); printf("NOTE reminder %s",
} SimpleTime(q->tt.ttime));
printf("%s", SimpleTime(MinutesPastMidnight(1)));
if (!*DBufValue(&q->t.tags)) {
printf("*\n");
} else {
printf("%s\n", DBufValue(&(q->t.tags)));
}
}
} }
/* Set up global variables so some functions like trigdate() /* Set up global variables so some functions like trigdate()
and trigtime() work correctly */ and trigtime() work correctly */
SaveAllTriggerInfo(&(q->t), &(q->tt), DSEToday, q->tt.ttime, 1); SaveAllTriggerInfo(&(q->t), &(q->tt), DSEToday, q->tt.ttime, 1);
FileName = (char *) q->fname; FileName = (char *) q->fname;
(void) TriggerReminder(&p, &q->t, &q->tt, DSEToday, 1); if (DaemonJSON) {
DynamicBuffer out;
DBufInit(&out);
(void) TriggerReminder(&p, &q->t, &q->tt, DSEToday, 1, &out);
if (q->typ != RUN_TYPE) {
printf("\"body\":\"");
chomp(&out);
PrintJSONString(DBufValue(&out));
printf("\"}\n");
}
DBufFree(&out);
} else {
(void) TriggerReminder(&p, &q->t, &q->tt, DSEToday, 1, NULL);
}
FileName = NULL; FileName = NULL;
if (IsServerMode()) { if (IsServerMode() && !DaemonJSON && q->typ != RUN_TYPE) {
printf("NOTE endreminder\n"); printf("NOTE endreminder\n");
} }
fflush(stdout); fflush(stdout);
@@ -605,6 +672,9 @@ static void
json_queue(QueuedRem const *q) json_queue(QueuedRem const *q)
{ {
int done = 0; int done = 0;
if (DaemonJSON) {
printf("{\"response\":\"queue\",\"queue\":");
}
printf("["); printf("[");
while(q) { while(q) {
if (q->tt.nexttime == NO_TIME) { if (q->tt.nexttime == NO_TIME) {
@@ -647,7 +717,12 @@ json_queue(QueuedRem const *q)
printf("\"}"); printf("\"}");
q = q->next; q = q->next;
} }
printf("]\n"); printf("]");
if (DaemonJSON) {
printf(",\"command\":\"QUEUE\"}\n");
} else {
printf("\n");
}
} }
/***************************************************************/ /***************************************************************/
@@ -662,15 +737,28 @@ static void DaemonWait(struct timeval *sleep_tv)
fd_set readSet; fd_set readSet;
int retval; int retval;
int y, m, d; int y, m, d;
int max = 1;
char cmdLine[256]; char cmdLine[256];
FD_ZERO(&readSet); FD_ZERO(&readSet);
FD_SET(0, &readSet); FD_SET(0, &readSet);
retval = select(1, &readSet, NULL, NULL, sleep_tv);
#ifdef USE_INOTIFY
if (watch_fd >= 0) {
FD_SET(watch_fd, &readSet);
if (watch_fd > max-1)
max = watch_fd+1;
}
#endif
retval = select(max, &readSet, NULL, NULL, sleep_tv);
/* If date has rolled around, restart */ /* If date has rolled around, restart */
if (RealToday != SystemDate(&y, &m, &d)) { if (RealToday != SystemDate(&y, &m, &d)) {
printf("NOTE newdate\nNOTE reread\n"); if (DaemonJSON) {
printf("{\"response\":\"newdate\"}\n{\"response\":\"reread\",\"command\":\"newdate\"}\n");
} else {
printf("NOTE newdate\nNOTE reread\n");
}
fflush(stdout); fflush(stdout);
reread(); reread();
} }
@@ -678,6 +766,24 @@ static void DaemonWait(struct timeval *sleep_tv)
/* If nothing readable or interrupted system call, return */ /* If nothing readable or interrupted system call, return */
if (retval <= 0) return; if (retval <= 0) return;
/* If inotify watch descriptor is readable, handle it */
#ifdef USE_INOTIFY
if (watch_fd >= 0) {
if (FD_ISSET(watch_fd, &readSet)) {
consume_inotify_events(watch_fd);
if (DaemonJSON) {
printf("{\"response\":\"reread\",\"command\":\"inotify\"}\n");
} else {
/* In deprecated server mode, we need to spit out
a NOTE newdate to force the front-end to redraw
the calendar */
printf("NOTE newdate\nNOTE reread\n");
}
fflush(stdout);
reread();
}
}
#endif
/* If stdin not readable, return */ /* If stdin not readable, return */
if (!FD_ISSET(0, &readSet)) return; if (!FD_ISSET(0, &readSet)) return;
@@ -696,43 +802,65 @@ static void DaemonWait(struct timeval *sleep_tv)
} else if (!strcmp(cmdLine, "STATUS\n")) { } else if (!strcmp(cmdLine, "STATUS\n")) {
print_num_queued(); print_num_queued();
} else if (!strcmp(cmdLine, "QUEUE\n")) { } else if (!strcmp(cmdLine, "QUEUE\n")) {
printf("NOTE queue\n"); if (DaemonJSON) {
QueuedRem *q = QueueHead; json_queue(QueueHead);
while (q) { } else {
if (q->tt.nexttime != NO_TIME) { printf("NOTE queue\n");
switch (q->typ) { QueuedRem *q = QueueHead;
case NO_TYPE: printf("NO_TYPE "); break; while (q) {
case MSG_TYPE: printf("MSG_TYPE "); break; if (q->tt.nexttime != NO_TIME) {
case RUN_TYPE: printf("RUN_TYPE "); break; switch (q->typ) {
case CAL_TYPE: printf("CAL_TYPE "); break; case NO_TYPE: printf("NO_TYPE"); break;
case SAT_TYPE: printf("SAT_TYPE "); break; case MSG_TYPE: printf("MSG_TYPE"); break;
case PS_TYPE: printf("PS_TYPE "); break; case RUN_TYPE: printf("RUN_TYPE"); break;
case PSF_TYPE: printf("PSF_TYPE "); break; case CAL_TYPE: printf("CAL_TYPE"); break;
case MSF_TYPE: printf("MSF_TYPE "); break; case SAT_TYPE: printf("SAT_TYPE"); break;
case PASSTHRU_TYPE: printf("PASSTHRU_TYPE "); break; case PS_TYPE: printf("PS_TYPE"); break;
default: printf("? "); break; case PSF_TYPE: printf("PSF_TYPE"); break;
} case MSF_TYPE: printf("MSF_TYPE"); break;
printf("RunDisabled=%d ntrig=%d ttime=%02d:%02d nexttime=%02d:%02d delta=%d rep=%d duration=%d ", q->RunDisabled, q->ntrig, q->tt.ttime/60, q->tt.ttime % 60, q->tt.nexttime / 60, q->tt.nexttime % 60, q->tt.delta, (q->tt.rep != NO_TIME ? q->tt.rep : -1), (q->tt.duration != NO_TIME ? q->tt.duration : -1)); case PASSTHRU_TYPE: printf("PASSTHRU_TYPE"); break;
printf("%s %s %s\n", default: printf("?"); break;
(q->passthru[0] ? q->passthru : "*"), }
(q->sched[0] ? q->sched : "*"), printf(" RunDisabled=%d ntrig=%d ttime=%02d:%02d nexttime=%02d:%02d delta=%d rep=%d duration=%d ", q->RunDisabled, q->ntrig, q->tt.ttime/60, q->tt.ttime % 60, q->tt.nexttime / 60, q->tt.nexttime % 60, q->tt.delta, (q->tt.rep != NO_TIME ? q->tt.rep : -1), (q->tt.duration != NO_TIME ? q->tt.duration : -1));
q->text ? q->text : "NULL"); printf("%s %s %s\n",
(q->passthru[0] ? q->passthru : "*"),
(q->sched[0] ? q->sched : "*"),
q->text ? q->text : "NULL");
}
q = q->next;
} }
q = q->next; printf("NOTE endqueue\n");
} }
printf("NOTE endqueue\n");
fflush(stdout); fflush(stdout);
} else if (!strcmp(cmdLine, "JSONQUEUE\n")) { } else if (!strcmp(cmdLine, "JSONQUEUE\n")) {
printf("NOTE JSONQUEUE\n"); if (!DaemonJSON) {
printf("NOTE JSONQUEUE\n");
}
json_queue(QueueHead); json_queue(QueueHead);
printf("NOTE ENDJSONQUEUE\n"); if (!DaemonJSON) {
printf("NOTE ENDJSONQUEUE\n");
}
fflush(stdout); fflush(stdout);
} else if (!strcmp(cmdLine, "REREAD\n")) { } else if (!strcmp(cmdLine, "REREAD\n")) {
printf("NOTE reread\n"); if (DaemonJSON) {
printf("{\"response\":\"reread\",\"command\":\"REREAD\"}\n");
} else {
printf("NOTE reread\n");
}
fflush(stdout); fflush(stdout);
reread(); reread();
} else { } else {
printf("ERR Invalid daemon command: %s", cmdLine); if (DaemonJSON) {
size_t l = strlen(cmdLine);
if (l && cmdLine[l-1] == '\n') {
cmdLine[l-1] = 0;
}
printf("{\"response\":\"error\",\"error\":\"Unknown command\",\"command\":\"");
PrintJSONString(cmdLine);
printf("\"}\n");
} else {
printf("ERR Invalid daemon command: %s", cmdLine);
}
fflush(stdout); fflush(stdout);
} }
} }
@@ -749,3 +877,47 @@ static void reread(void)
execvp(ArgV[0], (char **) ArgV); execvp(ArgV[0], (char **) ArgV);
} }
#ifdef USE_INOTIFY
static void consume_inotify_events(int fd)
{
char buf[sizeof(struct inotify_event) + NAME_MAX + 1];
int n;
struct timespec sleeptime;
/* HACK: sleep for 0.2 seconds to let multiple events queue up so we
only do a single reread */
sleeptime.tv_sec = 0;
sleeptime.tv_nsec = 200000000;
nanosleep(&sleeptime, NULL);
/* Consume all the inotify events */
while(1) {
n = read(fd, buf, sizeof(buf));
if (n < 0) {
if (errno == EINTR) continue;
return;
}
}
}
static int setup_inotify_watch(void)
{
int fd;
/* Don't inotify_watch stdin */
if (!strcmp(InitialFile, "-")) {
return -1;
}
fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
if (fd < 0) {
return fd;
}
if (inotify_add_watch(fd, InitialFile, IN_CLOSE_WRITE | IN_CREATE | IN_DELETE | IN_MODIFY | IN_MOVED_FROM | IN_MOVED_TO) < 0) {
close(fd);
return -1;
}
return fd;
}
#endif

View File

@@ -136,7 +136,7 @@ void IssueSortedReminders(void)
switch(cur->typ) { switch(cur->typ) {
case MSG_TYPE: case MSG_TYPE:
if (MsgCommand && *MsgCommand) { if (MsgCommand && *MsgCommand) {
DoMsgCommand(MsgCommand, cur->text); DoMsgCommand(MsgCommand, cur->text, 0);
} else { } else {
if (cur->trigdate != olddate) { if (cur->trigdate != olddate) {
IssueSortBanner(cur->trigdate); IssueSortBanner(cur->trigdate);
@@ -151,11 +151,11 @@ void IssueSortedReminders(void)
IssueSortBanner(cur->trigdate); IssueSortBanner(cur->trigdate);
olddate = cur->trigdate; olddate = cur->trigdate;
} }
FillParagraph(cur->text); FillParagraph(cur->text, NULL);
break; break;
case RUN_TYPE: case RUN_TYPE:
System(cur->text); System(cur->text, 0);
break; break;
} }

View File

@@ -72,7 +72,10 @@ static int NextSimpleTrig(int startdate, Trigger *trig, int *err)
m++; m++;
if (m == 12) { m = 0; y++; } if (m == 12) { m = 0; y++; }
} }
while (trig->d > DaysInMonth(m, trig->y)) m++; while (trig->d > DaysInMonth(m, y)) {
m++;
if (m == 12) { m = 0; y++; }
}
j = DSE(y, m, trig->d); j = DSE(y, m, trig->d);
return j; return j;

View File

@@ -453,6 +453,91 @@ rm -rf include_dir/ww
# date, we have to convert it to some constant (in this case, # date, we have to convert it to some constant (in this case,
# VOLATILE) so that tests are not dependent on the system date. # VOLATILE) so that tests are not dependent on the system date.
echo JSONQUEUE | ../src/remind -z0 ../tests/queue1.rem 2>&1 | sed -e 's/"eventstart":"................"/"eventstart":"VOLATILE"/g' >> ../tests/test.out 2>&1 echo JSONQUEUE | ../src/remind -z0 ../tests/queue1.rem 2>&1 | sed -e 's/"eventstart":"................"/"eventstart":"VOLATILE"/g' >> ../tests/test.out 2>&1
echo QUEUE | ../src/remind -zj ../tests/queue1.rem 2>&1 | sed -e 's/"eventstart":"................"/"eventstart":"VOLATILE"/g' >> ../tests/test.out 2>&1
# Test for leap year bug that was fixed
../src/remind -dte - 28 Feb 2024 <<'EOF' >> ../tests/test.out 2>&1
BANNER %
REM 29 MSG One
REM 29 Feb MSG two
REM 29 2024 MSG three
REM 29 Feb 2024 MSG four
REM Thursday 29 MSG One
REM Thursday 29 Feb MSG two
REM Thursday 29 2024 MSG three
REM Thursday 29 Feb 2024 MSG four
REM Wednesday 29 MSG One
REM Wednesday 29 Feb MSG two
REM Wednesday 29 2024 MSG three
REM Wednesday 29 Feb 2024 MSG four
REM Friday 29 MSG One
REM Friday 29 Feb MSG two
REM Friday 29 2024 MSG three
REM Friday 29 Feb 2024 MSG four
EOF
../src/remind -dte - 1 Mar 2024 <<'EOF' >> ../tests/test.out 2>&1
BANNER %
REM 29 MSG One
REM 29 Feb MSG two
REM 29 2024 MSG three
REM 29 Feb 2024 MSG four
REM Thursday 29 MSG One
REM Thursday 29 Feb MSG two
REM Thursday 29 2024 MSG three
REM Thursday 29 Feb 2024 MSG four
REM Wednesday 29 MSG One
REM Wednesday 29 Feb MSG two
REM Wednesday 29 2024 MSG three
REM Wednesday 29 Feb 2024 MSG four
REM Friday 29 MSG One
REM Friday 29 Feb MSG two
REM Friday 29 2024 MSG three
REM Friday 29 Feb 2024 MSG four
EOF
../src/remind -dte - 28 Feb 2025 <<'EOF' >> ../tests/test.out 2>&1
BANNER %
REM 29 MSG One
REM 29 Feb MSG two
REM 29 2025 MSG three
REM 29 Feb 2025 MSG four
REM Thursday 29 MSG One
REM Thursday 29 Feb MSG two
REM Thursday 29 2025 MSG three
REM Thursday 29 Feb 2025 MSG four
REM Wednesday 29 MSG One
REM Wednesday 29 Feb MSG two
REM Wednesday 29 2025 MSG three
REM Wednesday 29 Feb 2025 MSG four
REM Friday 29 MSG One
REM Friday 29 Feb MSG two
REM Friday 29 2025 MSG three
REM Friday 29 Feb 2025 MSG four
EOF
../src/remind -dte - 1 Mar 2025 <<'EOF' >> ../tests/test.out 2>&1
BANNER %
REM 29 MSG One
REM 29 Feb MSG two
REM 29 2025 MSG three
REM 29 Feb 2025 MSG four
REM Thursday 29 MSG One
REM Thursday 29 Feb MSG two
REM Thursday 29 2025 MSG three
REM Thursday 29 Feb 2025 MSG four
REM Wednesday 29 MSG One
REM Wednesday 29 Feb MSG two
REM Wednesday 29 2025 MSG three
REM Wednesday 29 Feb 2025 MSG four
REM Friday 29 MSG One
REM Friday 29 Feb MSG two
REM Friday 29 2025 MSG three
REM Friday 29 Feb 2025 MSG four
EOF
(echo 'BANNER %'; echo 'REM 29 MSG No bug') | ../src/remind -dt - 29 Feb 2024 >> ../tests/test.out 2>&1
# Remove references to SysInclude, which is build-specific # Remove references to SysInclude, which is build-specific
grep -F -v '$SysInclude' < ../tests/test.out > ../tests/test.out.1 && mv -f ../tests/test.out.1 ../tests/test.out grep -F -v '$SysInclude' < ../tests/test.out > ../tests/test.out.1 && mv -f ../tests/test.out.1 ../tests/test.out

File diff suppressed because one or more lines are too long

View File

@@ -381,9 +381,11 @@ msg [a076]%
set a077 dosubst("%*Y %*Z", '1992/5/5') set a077 dosubst("%*Y %*Z", '1992/5/5')
msg [a077]% msg [a077]%
set a078 easterdate(today()) set a078 easterdate(today())
set a078 easterdate()
set a079 easterdate(1992) set a079 easterdate(1992)
set a080 easterdate(1995) set a080 easterdate(1995)
set a078 orthodoxeaster(today()) set a078 orthodoxeaster(today())
set a078 orthodoxeaster()
set a079 orthodoxeaster(1992) set a079 orthodoxeaster(1992)
set a080 orthodoxeaster(1995) set a080 orthodoxeaster(1995)
set a080 orthodoxeaster(2023) set a080 orthodoxeaster(2023)

View File

@@ -1,341 +0,0 @@
<?php
class Remind
{
# For validating commands we send to popen
function is_valid_day($d) {
return (preg_match('/^\d+$/', $d)) &&
$d >= 1 && $d <= 31;
}
function is_valid_month($m) {
return
($m == 'January') ||
($m == 'February') ||
($m == 'March') ||
($m == 'April') ||
($m == 'May') ||
($m == 'June') ||
($m == 'July') ||
($m == 'August') ||
($m == 'September') ||
($m == 'October') ||
($m == 'November') ||
($m == 'December');
}
function is_valid_year($y) {
return preg_match('/^\d\d\d\d$/', $y) &&
$y >= 1900;
}
function get_el(&$array, $i)
{
if (!array_key_exists($i, $array)) return null;
return $array[$i];
}
function get_elem($array, $indexes)
{
foreach ($indexes as $i) {
if (!is_array($array)) return null;
if (!array_key_exists($i, $array)) return null;
$array = $array[$i];
}
return $array;
}
function munge_entry($day, &$results, &$specials, &$options, $str, &$e) {
return htmlspecialchars($str);
}
function format_entry($day, &$results, &$specials, &$options, &$e) {
$special = $this->get_el($e, 'special');
$body = $this->get_el($e, 'body');
if ($body === null) $body = '';
if ($special === null || $special == '*') {
return $this->munge_entry($day, $results, $specials, $options, $body, $e);
}
if ($special == 'COLOR' || $special == 'COLOUR') {
if (preg_match('/^(\d+)\s+(\d+)\s+(\d+)\s+(.*)/', $body, $matches)) {
return sprintf('<span style="color: #%02x%02x%02x">%s</span>',
$matches[1] % 255,
$matches[2] % 255,
$matches[3] % 255,
$this->munge_entry($day, $results, $specials, $options, $matches[4], $e));
}
return 'Bad COLOR spec: ' . htmlspecialchars($body);
}
# HTML is passed through un-munged.
if ($special == 'HTML') return $body;
# Ignore unknown specials
return '';
}
function format_entries($day, &$results, &$specials, &$options, &$entries) {
$html = '';
foreach ($entries as $e) {
$html .= '<div class="rem-entry">' . $this->format_entry($day, $results, $specials, $options, $e) . '</div>';
}
return $html;
}
function do_one_day($day, &$results, &$specials, &$options) {
$class = $this->get_elem($specials, array('HTMLCLASS', $day, 0, 'body'));
$shade = $this->get_elem($specials, array('SHADE', $day, 0, 'body'));
$moon = $this->get_elem($specials, array('MOON', $day, 0, 'body'));
if ($class === null) $class = 'rem-cell';
$bg = '';
if ($shade !== null) {
if (preg_match('/(\d+)\s+(\d+)\s+(\d+)/', $shade, $matches)) {
if ($matches[1] <= 255 && $matches[2] <= 255 && $matches[3] <= 255) {
$bg = sprintf(' style="background: #%02x%02x%02x"',
$matches[1], $matches[2], $matches[3]);
}
}
}
$html = "<td class=\"$class\"$bg>";
$week = $this->get_elem($specials, array('WEEK', $day, 0, 'body'));
if ($week === null) {
$week = '';
} else {
$week = ' ' . $week;
}
$moon_html = '';
if ($moon !== null) {
$phase = -1;
if (preg_match('/(\d+)\s+(\S+)\s+(\S+)\s+(.*)$/', $moon, $matches)) {
$phase = $matches[1];
$moonsize = $matches[2];
$fontsize = $matches[3];
$msg = $matches[4];
} elseif (preg_match('/(\d+)/', $moon, $matches)) {
$phase = $matches[1];
$msg = '';
}
if ($phase >= 0) {
if ($phase == 0) {
$img = 'newmoon.png';
$title = 'New Moon';
$alt = 'new';
} elseif ($phase == 1) {
$img = 'firstquarter.png';
$title = 'First Quarter';
$alt = '1st';
} elseif ($phase == 2) {
$img = 'fullmoon.png';
$alt = 'full';
$title = 'Full Moon';
} else {
$img = 'lastquarter.png';
$alt = 'last';
$title = 'Last Quarter';
}
$base = rtrim($this->get_el($options, 'imgbase'), '/');
if ($base !== null) {
$img = $base . '/' . $img;
}
$moon_html = '<div class="rem-moon">' . "<img width=\"16\" height=\"16\" alt=\"$alt\" title=\"$title\" src=\"$img\">" . htmlspecialchars($msg) . '</div>';
}
}
# Day number
$html .= $moon_html . '<div class="rem-daynumber">' . $day . $week . '</div>';
# And the entries
$entries = $this->get_elem($results, array('entries', $day));
if (is_array($entries) && count($entries) > 0) {
$html .= '<div class="rem-entries">';
$html .= $this->format_entries($day, $results, $specials, $options, $entries);
$html .= '</div>';
}
$html .= "</td>\n";
return $html;
}
function small_calendar($results, $month, $monlen, $first_col, $which, &$options)
{
$monday_first = $results['monday_flag'];
if ($monday_first) {
$first_col--;
if ($first_col < 0) {
$first_col = 6;
}
}
$html = "<td class=\"rem-small-calendar\">\n<table class=\"rem-sc-table\">\n<caption class=\"rem-sc-caption\">";
# TODO: URL for small calendar
$html .= $month;
$html .= "</caption>\n";
}
function generate_html(&$results, &$specials, &$options)
{
$monday_first = $results['monday_flag'];
$first_col = $results['first_day'];
if ($monday_first) {
$first_col--;
if ($first_col < 0) $first_col = 6;
}
$last_col = ($first_col + $results['days_in_mon'] -1) % 7;
$html = '<table class="rem-cal"><caption class="rem-cal-caption">' .
htmlspecialchars($results['month']) . ' ' . htmlspecialchars($results['year']) .
"</caption>\n";
$html .= '<tr class="rem-cal-hdr-row">';
if (!$monday_first) $html .= '<th class="rem-cal-hdr">' . htmlspecialchars($results['day_names'][0]) . '</th>';
for ($i=1; $i<7; $i++) $html .= '<th class="rem-cal-hdr">' . htmlspecialchars($results['day_names'][$i]) . '</th>';
if ($monday_first) $html .= '<th class="rem-cal-hdr">' . htmlspecialchars($results['day_names'][0]) . '</th>';
$html .= "</tr>\n";
# Do the leading empty columns
for ($col=0; $col < $first_col; $col++) {
if ($col == 0) $html .= '<tr class="rem-cal-body-row">';
$html .= '<td class="rem-empty">&nbsp;</td>';
}
for ($day=1; $day <= $results['days_in_mon']; $day++) {
if ($col == 0) $html .= '<tr class="rem-cal-body-row">';
$col++;
$html .= $this->do_one_day($day, $results, $specials, $options);
if ($col == 7) {
$html .= "</tr>\n";
$col = 0;
}
}
if ($col) {
while ($col++ < 7) {
$html .= '<td class="rem-empty">&nbsp;</td>';
}
}
$html .= "</tr>\n";
$html .= "</table>\n";
return $html;
}
function parse_remind_output ($fp)
{
while(1) {
$line = fgets($fp);
if ($line === false) break;
$line = trim($line);
if ($line == '# rem2ps begin') break;
}
if ($line === false) {
return array('success' => 0,
'error' => 'Could not find any Rem2PS data');
}
$line = fgets($fp);
if ($line === false) {
return array('success' => 0,
'error' => 'Unexpected end-of-file');
}
$line = trim($line);
list($month, $year, $days_in_mon, $first_day, $monday_flag) = explode(' ', $line);
$retval = array('month' => $month,
'year' => $year,
'days_in_mon' => $days_in_mon,
'first_day' => $first_day,
'monday_flag' => $monday_flag);
$line = fgets($fp);
if ($line === false) {
return array('success' => 0,
'error' => 'Unexpected end-of-file');
}
$line = trim($line);
$retval['day_names'] = explode(' ', $line);
$line = fgets($fp);
if ($line === false) {
return array('success' => 0,
'error' => 'Unexpected end-of-file');
}
$line = trim($line);
list($m, $n) = explode(' ', $line);
$retval['prev'] = array('month' => $m, 'days' => $n);
$line = fgets($fp);
if ($line === false) {
return array('success' => 0,
'error' => 'Unexpected end-of-file');
}
$line = trim($line);
list($m, $n) = explode(' ', $line);
$retval['next'] = array('month' => $m, 'days' => $n);
$line_info = 0;
$entries = array();
$specials = array();
while (1) {
$line = fgets($fp);
if ($line === false) break;
$line = trim($line);
if ($line == '# rem2ps end') break;
if (strpos($line, '# fileinfo ') === 0) {
list($lno, $fname) = explode(' ', substr($line, 11), 2);
$lineinfo = array('file' => $fname, 'line' => $lno);
continue;
}
list($date, $special, $tags, $duration, $time, $body) = explode(' ', $line, 6);
list($y, $m, $d) = explode('/', $date);
$d = preg_replace('/^0(.)/', '$1', $d);
$m = preg_replace('/^0(.)/', '$1', $m);
$entry = array('day' => $d,
'month' => $m,
'year' => $y,
'special' => $special,
'tags' => $tags,
'duration' => $duration,
'time' => $time,
'body' => $body);
if (is_array($lineinfo)) {
$entry['line'] = $lineinfo['line'];
$entry['file'] = $lineinfo['file'];
$lineinfo = 0;
}
if ($special != '*' && $special != 'COLOR' && $special != 'COLOUR' && $special != 'HTML') {
if (!array_key_exists($special, $specials)) {
$specials[$special] = array();
}
if (!array_key_exists($d, $specials[$special])) {
$specials[$special][$d] = array();
}
$specials[$special][$d][] = $entry;
} else {
if (!array_key_exists($d, $entries)) {
$entries[$d] = array();
}
$entries[$d][] = $entry;
}
}
$retval['entries'] = $entries;
return array('success' => 1, 'results' => $retval, 'specials' => $specials);
}
}
$fp = popen('rem -p -l', 'r');
$r = new Remind;
$ans = $r->parse_remind_output($fp);
pclose($fp);
print_r($ans);
$options = array();
#print $r->generate_html($ans['results'], $ans['specials'], $options);
?>