aboutsummaryrefslogtreecommitdiff
path: root/README.org
blob: 3303efea962d688cee6b46e6f742acf984436fea (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
* CHIME Heralds Imminent Modeline Events

[[#about][About]] | [[#features][Features]] | [[#installation][Installation]] | [[#quick-start][Quick Start]] | [[#configuration][Configuration]] | [[#usage][Usage]] | [[#troubleshooting][Troubleshooting]] | [[#testing][Testing]] | [[#credits][Credits]]

[[https://www.gnu.org/software/emacs/][file:assets/made-for-emacs-badge.svg]]

Customizable org notifications for Emacs with visual alerts, audible chimes, and modeline display.

** About
:PROPERTIES:
:CUSTOM_ID: about
:END:

CHIME (backronym: *CHIME Heralds Imminent Modeline Events*) notifies you about upcoming org-agenda events. You get desktop notifications, an audible chime, and your next event shows up in the modeline.

This is an updated and maintained fork of the abandoned [[https://github.com/akhramov/org-wild-notifier.el][org-wild-notifier]] project, renamed to CHIME with bug-fixes and new features.

Note: while I've found this package to be quite reliable, it's still undergoing feature development and will be changing frequently. 

** Features
:PROPERTIES:
:CUSTOM_ID: features
:END:

- *Visual notifications* with customizable alert times
- *Audible chime sound* when notifications are displayed (bundled WAV or custom file)
- *Customizable notification icon* for desktop notifications
- *Interactive modeline display* of next upcoming event, highly configurable:
  - Enable/disable modeline modifications
  - Hover tooltip showing all upcoming events grouped by day
  - Click to jump directly to event's org entry
  - Customize notification text format (show/hide time, countdown, or title)
  - Choose 12-hour or 24-hour time display
  - Customize time-until format (verbose or compact)
  - Configurable lookahead window and tooltip event limit
- Multiple notification times per event (e.g., 5 minutes before and at event time)
- Works with SCHEDULED and DEADLINE and just plain ol' regular timestamps
- Supports repeating timestamps (=+1w=, =.+1d=, =++1w=)
- Async background checking (runs every minute)
- *All-day event support* (birthdays, holidays, multi-day events)
  - Configurable daily notification times for all-day events
  - Advance notice (e.g., "Blake's birthday is tomorrow")
  - Show/hide all-day events in tooltip
- *org-contacts integration* with birthday conversion and capture template
- Configurable notification filtering by keywords, tags, and custom predicates
- Configurable startup delay for async org-agenda-files initialization
- [[https://github.com/cjennings/chime/tree/main/tests][Well-tested]], including with org-gcal

** Installation
:PROPERTIES:
:CUSTOM_ID: installation
:END:

This package is NOT YET available on MELPA.

*** package-vc-install (Emacs 29+)

#+BEGIN_SRC elisp
(unless (package-installed-p 'chime)
  (package-vc-install "https://github.com/cjennings/chime"))
#+END_SRC

*** use-package with :vc (Emacs 29+)

#+BEGIN_SRC elisp
(use-package chime
  :vc (:url "https://github.com/cjennings/chime" :rev :newest)
  :after alert
  :commands (chime-mode chime-check)
  :bind ("C-c A" . chime-check)
  :config
  ;; Alert intervals: (minutes . severity) pairs
  ;; Notify 5 minutes before (medium urgency) and at event time (high urgency)
  (setq chime-alert-intervals '((5 . medium) (0 . high)))

  ;; Chime sound
  (setq chime-play-sound t)

  ;; Modeline display (see "Modeline Display" section for more options)
  (setq chime-enable-modeline t)
  (setq chime-modeline-lookahead-minutes 120)
  (setq chime-modeline-format " ⏰ %s")

  ;; Notification settings
  (setq chime-notification-title "Reminder")

  ;; Don't filter by TODO keywords - notify for all events
  (setq chime-keyword-whitelist nil)
  (setq chime-keyword-blacklist nil)

  ;; Only notify for non-done items
  (setq chime-predicate-blacklist
        '(chime-done-keywords-predicate))

  ;; Enable chime-mode automatically
  (chime-mode 1))
#+END_SRC

*** Manual Installation (Recommended for Development)

#+BEGIN_SRC elisp
;; Add to load-path
(add-to-list 'load-path "~/path/to/chime")

;; Load and configure
(require 'chime)
#+END_SRC

** Quick Start
:PROPERTIES:
:CUSTOM_ID: quick-start
:END:

Minimal configuration — clone the repo and point your load-path at it:

#+BEGIN_SRC elisp
  (add-to-list 'load-path "/path/to/chime/")
  (require 'chime)

  ;; Alert intervals: notify 5 minutes before (medium) and at event time (high)
  (setq chime-alert-intervals '((5 . medium) (0 . high)))

  ;; Enable notifications
  (chime-mode 1)
#+END_SRC

** Configuration
:PROPERTIES:
:CUSTOM_ID: configuration
:END:

*** Polling Interval

Control how often chime checks for upcoming events:

#+BEGIN_SRC elisp
;; Default: check every 60 seconds (1 minute) - recommended for most users
(setq chime-check-interval 60)

;; More responsive: check every 30 seconds
(setq chime-check-interval 30)

;; Reduce polling overhead: check every 5 minutes
(setq chime-check-interval 300)
#+END_SRC

Lower values make notifications more responsive but increase system load. Higher values reduce polling overhead but may delay notifications slightly.

*Choosing a polling interval:*

- *120-300 seconds (2-5 minutes)*: Okay for reducing system load, but most people require more timely notifications. 
- *60 seconds (default)*: Ideal for most users. Matches org's minute-based timestamps and provides timely notifications with minimal overhead.
- *30 seconds*: Fine if you want quicker notification delivery. Reasonable resource usage.
- *15-10 seconds*: Maximum responsiveness, but you're polling 4-6 times more frequently for marginal precision gain on minute-based events.
- *Below 10 seconds*: Not recommended or supported. Org events are scheduled to the minute. Faster polling provides near-zero benefit while significantly increasing CPU, disk I/O, and battery usage.

*Note:* Changes take effect after restarting chime-mode (=M-x chime-mode= twice, or restart Emacs).

*** Alert Intervals

Set when to receive notifications and their urgency levels using (minutes . severity) pairs:

#+BEGIN_SRC elisp
;; Default: 10 minutes before (medium) and at event time (high)
(setq chime-alert-intervals '((10 . medium) (0 . high)))

;; Single notification 10 minutes before
(setq chime-alert-intervals '((10 . medium)))

;; Multiple notifications with escalating urgency
(setq chime-alert-intervals '((10 . low)     ;; 10 min before: low urgency
                               (5 . medium)   ;; 5 min before: medium urgency
                               (0 . high)))   ;; At event time: high urgency
#+END_SRC

Severity levels (=high=, =medium=, =low=) affect how your notification daemon displays them (e.g., notification persistence, sound).

*** Chime Sound

Control the audible chime that plays when notifications appear:

#+BEGIN_SRC elisp
;; Enable/disable chime sound (default: t)
(setq chime-play-sound t)

;; Use custom sound file (defaults to bundled chime.wav)
(setq chime-sound-file "/path/to/your/chime.wav")

;; Disable sound completely (no sound file, no beep)
(setq chime-sound-file nil)
#+END_SRC

A bundled chime.wav is included (GPL-licensed). Swap in your own WAV if you prefer.

*** Notification Icon

Set a custom icon for desktop notifications:

#+BEGIN_SRC elisp
;; Use a custom notification icon
(setq chime-notification-icon "/path/to/icon.png")

;; No custom icon (default: nil, uses system default)
(setq chime-notification-icon nil)
#+END_SRC

*** Startup Delay

Control how long chime waits before the first event check after enabling =chime-mode=. This allows org-agenda-files and related infrastructure to finish loading:

#+BEGIN_SRC elisp
;; Default: wait 10 seconds before first check
(setq chime-startup-delay 10)

;; Faster startup (if you know org is ready)
(setq chime-startup-delay 5)

;; Increase if you see "found 0 events" messages on startup
(setq chime-startup-delay 20)
#+END_SRC

*** Modeline Display

Display your next upcoming event in your modeline:

#+BEGIN_SRC elisp
;; Enable/disable modeline display (default: t)
(setq chime-enable-modeline t)

;; Show events up to 2 hours ahead (default: 120)
(setq chime-modeline-lookahead-minutes 120)

;; Customize the modeline prefix format (default: " ⏰ %s")
(setq chime-modeline-format " [Next: %s]")
#+END_SRC

The modeline will display the soonest event within the lookahead window, formatted as:
- Default: =⏰ Meeting with Team at 02:30 PM (in 15 minutes)=
- Updates automatically every minute

**** Minor mode lighter

By default, the minor mode lighter is empty — the =⏰= event display already tells you chime is running. If you want a separate indicator in the minor modes list:

#+BEGIN_SRC elisp
;; Show "Chime" in the minor modes list
(setq chime-modeline-lighter " Chime")

;; Or an emoji
(setq chime-modeline-lighter " 🔔")
#+END_SRC

**** No-Events Text

Control what appears in the modeline when no events are within the lookahead window:

#+BEGIN_SRC elisp
;; Default: alarm icon
(setq chime-modeline-no-events-text " ⏰")

;; Muted bell
(setq chime-modeline-no-events-text " 🔕")

;; Show nothing (clean modeline)
(setq chime-modeline-no-events-text nil)

;; Custom text
(setq chime-modeline-no-events-text " No events")
#+END_SRC

This only applies when events exist beyond the lookahead window. If there are no events at all, the modeline is always empty.

**** Interactive Modeline Features

The modeline text is interactive - you can click it and hover for more information:

***** Tooltip

Hover your mouse over the modeline event to see a tooltip showing all upcoming events within the lookahead window, grouped by day:

#+BEGIN_EXAMPLE
Upcoming Events as of Mon Oct 28 2024 @ 02:00 PM

Today, Oct 28:
─────────────
Team Meeting at 02:10 PM (in 10 minutes)
Code Review at 02:30 PM (in 30 minutes)
Coffee break at 02:45 PM (in 45 minutes)

Tomorrow, Oct 29:
─────────────
Sprint Planning at 09:00 AM (tomorrow)
Quarterly Review at 02:00 PM (tomorrow)
#+END_EXAMPLE

The tooltip displays up to 5 events by default. Configure the maximum with:

#+BEGIN_SRC elisp
;; Show up to 10 events in tooltip
(setq chime-modeline-tooltip-max-events 10)

;; Show all events in lookahead window (beware -- no limit!)
(setq chime-modeline-tooltip-max-events nil)
#+END_SRC

Customize the tooltip header format:

#+BEGIN_SRC elisp
;; Default: "Upcoming Events as of Tue Nov 04 2025 @ 08:25 PM"
(setq chime-tooltip-header-format "Upcoming Events as of %a %b %d %Y @ %I:%M %p")

;; 24-hour time
(setq chime-tooltip-header-format "Upcoming Events as of %a %b %d %Y @ %H:%M")

;; Minimal header
(setq chime-tooltip-header-format "Events — %a %b %d")
#+END_SRC

Uses =format-time-string= codes (=%a= weekday, =%b= month, =%d= day, =%Y= year, =%I= 12-hour, =%H= 24-hour, =%M= minutes, =%p= AM/PM).

***** Tooltip Lookahead Window

The tooltip can show events beyond the modeline lookahead window. By default, it shows events up to 1 week (168 hours) in the future, while the modeline only shows events within the next hour:

#+BEGIN_SRC elisp
;; Modeline shows events within next 2 hours (default: 120)
(setq chime-modeline-lookahead-minutes 120)

;; Tooltip shows events within next 168 hours / 1 week (default)
(setq chime-tooltip-lookahead-hours 168)

;; Example: Show only today's events in tooltip (24 hours)
(setq chime-tooltip-lookahead-hours 24)

;; Example: Show events for the next month
(setq chime-tooltip-lookahead-hours 720)  ; 30 days × 24 hours

;; Example: Show all events for the next year (may slow checks for large org collections)
(setq chime-tooltip-lookahead-hours 8760)
#+END_SRC

Note: larger values increase the agenda span fetched by the async subprocess, which can slow event checks if you have many org files.

This way the modeline stays focused on what's imminent, while the tooltip gives you a wider view of what's coming up.

***** Click Actions

The modeline supports two click actions:

- *Left-click*: Opens your calendar in a web browser (if configured)
- *Right-click*: Jumps directly to the event's org entry in its file

To enable left-click calendar access, set your calendar URL:

#+BEGIN_SRC elisp
;; Open Google Calendar on left-click
(setq chime-calendar-url "https://calendar.google.com")

;; Or Outlook Calendar
(setq chime-calendar-url "https://outlook.office.com/calendar")

;; Or any custom calendar web interface
(setq chime-calendar-url "https://your-calendar-url")
#+END_SRC

When no calendar URL is set (default), left-click does nothing. Right-click always jumps to the next event in your org file.

**** Customizing Modeline Content

Control what information appears in the modeline with fine-grained formatting:

***** Notification Text Format

Customize which components are shown:

#+BEGIN_SRC elisp
;; Default: title, time, and countdown
(setq chime-notification-text-format "%t at %T (%u)")
;; → "Meeting with Team at 02:30 PM (in 15 minutes)"

;; Title and time only (no countdown)
(setq chime-notification-text-format "%t at %T")
;; → "Meeting with Team at 02:30 PM"

;; Title and countdown only (no time)
(setq chime-notification-text-format "%t (%u)")
;; → "Meeting with Team (in 15 minutes)"

;; Title only (minimal)
(setq chime-notification-text-format "%t")
;; → "Meeting with Team"

;; Custom separator
(setq chime-notification-text-format "%t - %T")
;; → "Meeting with Team - 02:30 PM"

;; Time first
(setq chime-notification-text-format "%T: %t")
;; → "02:30 PM: Meeting with Team"
#+END_SRC

Available placeholders:
- =%t= - Event title
- =%T= - Event time (formatted per =chime-display-time-format-string=)
- =%u= - Time until event (formatted per =chime-time-left-format-*=)

***** Event Time Format

Choose between 12-hour and 24-hour time display:

#+BEGIN_SRC elisp
;; 12-hour with AM/PM (default)
(setq chime-display-time-format-string "%I:%M %p")
;; → "02:30 PM"

;; 24-hour format
(setq chime-display-time-format-string "%H:%M")
;; → "14:30"

;; 12-hour without space before AM/PM
(setq chime-display-time-format-string "%I:%M%p")
;; → "02:30PM"

;; 12-hour with lowercase am/pm
(setq chime-display-time-format-string "%I:%M %P")
;; → "02:30 pm"
#+END_SRC

Available format codes:
- =%I= - Hour (01-12, 12-hour format)
- =%H= - Hour (00-23, 24-hour format)
- =%M= - Minutes (00-59)
- =%p= - AM/PM (uppercase)
- =%P= - am/pm (lowercase)

***** Time-Until Format

Customize how the countdown is displayed:

#+BEGIN_SRC elisp
;; Default: verbose format
(setq chime-time-left-format-short "in %M")      ; Under 1 hour
(setq chime-time-left-format-long "in %H %M")    ; 1 hour or more
;; → "in 10 minutes" or "in 1 hour 30 minutes"

;; Compact format
(setq chime-time-left-format-short "in %mm")
(setq chime-time-left-format-long "in %hh %mm")
;; → "in 10m" or "in 1h 30m"

;; Very compact (no prefix)
(setq chime-time-left-format-short "%mm")
(setq chime-time-left-format-long "%hh%mm")
;; → "10m" or "1h30m"

;; Custom "at event time" message
(setq chime-time-left-format-at-event "NOW!")
;; → "NOW!" instead of "right now"
#+END_SRC

Available format codes (from =format-seconds=):
- =%h= / =%H= - Hours (number only / with unit name)
- =%m= / =%M= - Minutes (number only / with unit name)

***** Title Truncation

Limit the length of long event titles to conserve modeline space:

#+BEGIN_SRC elisp
;; No truncation - show full title (default)
(setq chime-max-title-length nil)
;; → " ⏰ Very Long Meeting Title That Goes On And On ( in 10m)"

;; Truncate to 25 characters
(setq chime-max-title-length 25)
;; → " ⏰ Very Long Meeting Titl... ( in 10m)"

;; Truncate to 15 characters
(setq chime-max-title-length 15)
;; → " ⏰ Very Long Me... ( in 10m)"
#+END_SRC

*Important:* This setting affects *only the event title* (%t), not the icon, time, or countdown. The icon comes from =chime-modeline-format= and is added separately.

The truncation includes the "..." in the character count, so a 15-character limit means up to 12 characters of title plus "...".

Minimum recommended value: 10 characters.

***** Complete Compact Example

For maximum modeline space savings:

#+BEGIN_SRC elisp
(setq chime-enable-modeline t)
(setq chime-modeline-lookahead-minutes 60)
(setq chime-modeline-format " ⏰%s")                    ; Minimal prefix
(setq chime-notification-text-format "%t (%u)")        ; No time shown
(setq chime-time-left-format-short "%mm")              ; Compact short
(setq chime-time-left-format-long "%hh%mm")            ; Compact long
(setq chime-max-title-length 20)                       ; Truncate long titles
;; Result: "⏰Meeting (10m)" or "⏰Very Long Meeti... (1h30m)"
#+END_SRC

***** Disabling Modeline Display

#+BEGIN_SRC elisp
;; Completely disable modeline modifications
(setq chime-enable-modeline nil)

;; Alternative: set lookahead to 0 (legacy method)
(setq chime-modeline-lookahead-minutes 0)
#+END_SRC

*** Notification Settings

#+BEGIN_SRC elisp
;; Notification title
(setq chime-notification-title "Reminder")

;; Note: Severity is now configured per-interval in chime-alert-intervals
;; See "Alert Intervals" section above
#+END_SRC

*** Filtering

#+BEGIN_SRC elisp
;; Only notify for specific TODO keywords
(setq chime-keyword-whitelist '("TODO" "NEXT"))

;; Never notify for these keywords
(setq chime-keyword-blacklist '("DONE" "CANCELLED"))

;; Only notify for specific tags
(setq chime-tags-whitelist '("@important"))

;; Never notify for these tags
(setq chime-tags-blacklist '("someday"))
#+END_SRC

**** Whitelist and Blacklist Precedence

If the same keyword or tag appears in both a whitelist and blacklist, the *blacklist wins* and the item is filtered out.

Examples:
- Item with =TODO= keyword when =TODO= is in both ~chime-keyword-whitelist~ and ~chime-keyword-blacklist~ → *filtered out* (blacklist wins)
- Item with =:urgent:= tag when =urgent= is in both ~chime-tags-whitelist~ and ~chime-tags-blacklist~ → *filtered out* (blacklist wins)
- Item with whitelisted keyword but blacklisted tag → *filtered out* (blacklist wins)

Most users configure either whitelists or blacklists, not both. If you use both, ensure they don't overlap to avoid confusion.

**** Custom Predicate Filtering

For filtering logic that goes beyond keywords and tags, you can write custom predicate functions. Each predicate receives an org marker (POM) and should return non-nil to match:

#+BEGIN_SRC elisp
;; Only notify for events in specific files
(defun my-work-file-predicate (marker)
  "Only match events in work.org."
  (string-match-p "work\\.org" (buffer-file-name (marker-buffer marker))))

(setq chime-predicate-whitelist '(my-work-file-predicate))

;; Exclude events with specific properties
(defun my-no-notify-predicate (marker)
  "Exclude events with NO_NOTIFY property set."
  (org-entry-get marker "NO_NOTIFY"))

(setq chime-predicate-blacklist
      '(chime-done-keywords-predicate my-no-notify-predicate))
#+END_SRC

The built-in =chime-done-keywords-predicate= is in the blacklist by default, filtering out DONE items.

*** Advanced Settings

**** Failure Warnings

If the async event check fails repeatedly (e.g., due to a misconfigured org file), chime will show a warning after a configurable number of consecutive failures:

#+BEGIN_SRC elisp
;; Warn after 5 consecutive failures (default)
(setq chime-max-consecutive-failures 5)

;; Disable failure warnings
(setq chime-max-consecutive-failures 0)
#+END_SRC

**** Validation Retries

On startup, chime validates that =org-agenda-files= is populated. If your config loads org-agenda-files asynchronously (e.g., via idle timers), chime will retry validation a few times before giving up:

#+BEGIN_SRC elisp
;; Retry validation 3 times before showing error (default)
(setq chime-validation-max-retries 3)

;; Show error immediately if org-agenda-files is empty
(setq chime-validation-max-retries 0)
#+END_SRC

**** Extra Alert Arguments

Pass additional arguments to every =alert= call (see alert.el documentation for available options):

#+BEGIN_SRC elisp
;; Example: always use a specific alert style
(setq chime-extra-alert-plist '(:style libnotify))
#+END_SRC

**** Async Environment Variables

If you have custom variables that need to be available in chime's async subprocess (e.g., for custom predicate functions), add their name patterns:

#+BEGIN_SRC elisp
;; Inject custom variables into the async process
(setq chime-additional-environment-regexes '("my-custom-var"))
#+END_SRC

*** All-Day Events

Chime distinguishes between *timed events* (with specific times like =10:00=) and *all-day events* (without times, such as birthdays or holidays).

**** What are All-Day Events?

All-day events are org timestamps without a time component:

#+BEGIN_SRC org
,* Blake's Birthday
<2025-12-19 Fri>

,* Holiday: Christmas
<2025-12-25 Thu>

,* Multi-day Conference
<2025-11-10 Mon>--<2025-11-13 Thu>
#+END_SRC

Compare with timed events:

#+BEGIN_SRC org
,* Team Meeting
<2025-10-28 Tue 14:30-15:30>

,* Doctor Appointment
SCHEDULED: <2025-10-30 Thu 10:00>
#+END_SRC

**** Current Behavior

*Modeline:*
- All-day events are *never* shown in the modeline
- Only timed events with specific times appear
- Rationale: Modeline shows urgent, time-sensitive items

*Notifications:*
- All-day events can trigger notifications at configured times
- By default, =chime-day-wide-alert-times= is ='("08:00")= (morning notification)
- When set, chime will notify you of all-day events happening *today* at those times

**** Configuring All-Day Event Notifications

To receive notifications for all-day events (like birthdays):

#+BEGIN_SRC elisp
;; Notify at 8:00 AM for all-day events happening today
(setq chime-day-wide-alert-times '("08:00"))

;; Multiple notification times
(setq chime-day-wide-alert-times '("08:00" "17:00"))  ; Morning and evening

;; Disable all-day event notifications
(setq chime-day-wide-alert-times nil)
#+END_SRC

*Example workflow:*
1. You have =* Blake's Birthday <2025-12-19 Fri>= in your org file
2. On December 19th at 8:00 AM, chime notifies: "Blake's Birthday is due or scheduled today"
3. This gives you a reminder to send birthday wishes or buy a gift

**** Showing Overdue TODOs

Control whether overdue TODO items and past events appear alongside all-day event notifications:

#+BEGIN_SRC elisp
;; Show overdue items with all-day event notifications (default: t)
(setq chime-show-any-overdue-with-day-wide-alerts t)

;; Only show today's events, not overdue items from past days
(setq chime-show-any-overdue-with-day-wide-alerts nil)
#+END_SRC

*When enabled (default =t=):*
- Shows today's DEADLINE/SCHEDULED tasks that have passed (e.g., 9am deadline when it's now 2pm)
- Shows today's all-day events even if you launch Emacs after the alert time (e.g., launch at 10am when alert was 8am)
- Shows all-day events from past days (e.g., yesterday's birthday, last week's holiday)

*When disabled (=nil=):*
- Shows today's DEADLINE/SCHEDULED tasks that have passed ✓
- Shows today's all-day events even if you launch Emacs late ✓
- Hides all-day events from past days (prevents old birthday/holiday spam) ✓

Most users want the default (=t=) to catch overdue items. Disable it if you only want to see today's events and don't want past birthdays/holidays cluttering notifications.

**** Advance Notice for All-Day Events

Get notified about all-day events before they happen — useful for birthdays (buying gifts) or conferences (packing, travel):

#+BEGIN_SRC elisp
;; Only notify on the day of the event (default)
(setq chime-day-wide-advance-notice nil)

;; Notify 1 day before as well
(setq chime-day-wide-advance-notice 1)
;; → "Blake's birthday is tomorrow" at 08:00 the day before
;; → "Blake's birthday is today" at 08:00 on the day

;; Notify 2 days before
(setq chime-day-wide-advance-notice 2)
#+END_SRC

Note: This only affects notifications, not tooltip or modeline display.

**** Showing All-Day Events in Tooltip

Control whether all-day events (birthdays, holidays, etc.) appear in the modeline tooltip:

#+BEGIN_SRC elisp
;; Show all-day events in tooltip (default: t)
(setq chime-tooltip-show-all-day-events t)

;; Hide all-day events from tooltip
(setq chime-tooltip-show-all-day-events nil)
#+END_SRC

All-day events are never shown in the modeline itself (only timed events appear there). This setting controls only the tooltip display. Notifications are unaffected.

***** How alert times and overdue settings interact

The relationship between =chime-day-wide-alert-times= and =chime-show-any-overdue-with-day-wide-alerts= can be confusing:

- =chime-day-wide-alert-times= controls *when* notifications fire (e.g., 8:00 AM)
- =chime-show-any-overdue-with-day-wide-alerts= controls *what happens if you miss that time*

*Example scenario:*
#+BEGIN_EXAMPLE
You have:
  (setq chime-day-wide-alert-times '("08:00"))
  (setq chime-show-any-overdue-with-day-wide-alerts t)

Today's birthday: * Blake's Birthday <2025-10-28 Tue>

Timeline:
- 8:00 AM: Chime fires notification "Blake's Birthday is due or scheduled today" ✓
- You close Emacs at 9:00 AM
- You relaunch Emacs at 2:00 PM (afternoon)
- Because overdue alerts are ENABLED (t), chime shows the notification again ✓
  → This catches you up on today's events you might have missed
#+END_EXAMPLE

*If you disable overdue alerts:*
#+BEGIN_EXAMPLE
  (setq chime-show-any-overdue-with-day-wide-alerts nil)

Same scenario, but now:
- 8:00 AM: Chime fires notification ✓
- You close Emacs at 9:00 AM
- You relaunch Emacs at 2:00 PM
- Because overdue alerts are DISABLED (nil), chime STILL shows today's birthday ✓
  → Today's events are always shown regardless of this setting
  → This setting only hides events from PAST DAYS (yesterday, last week, etc.)
#+END_EXAMPLE

*Key insight:* You'll always see today's all-day events when you launch Emacs, even if you missed the configured alert time. The =chime-show-any-overdue-with-day-wide-alerts= setting only controls whether you see events from *previous days*.

**** Common Use Cases

*Birthdays:*
#+BEGIN_SRC org
,* Blake Michael's Birthday
<2025-02-20 Thu>
#+END_SRC

With =chime-day-wide-alert-times= set to ='("08:00")=, you'll get a morning reminder on the birthday.

*Holidays:*
#+BEGIN_SRC org
,* Holiday: Thanksgiving
<2025-11-27 Thu>
#+END_SRC

*Multi-day Events:*
#+BEGIN_SRC org
,* Conference: EmacsCon 2025
<2025-11-10 Mon>--<2025-11-13 Thu>
#+END_SRC

You'll receive notifications on each day of the conference at your configured alert times.

**** Integration with org-contacts
:PROPERTIES:
:CUSTOM_ID: integration-with-org-contacts
:END:

If you use [[https://repo.or.cz/org-contacts.git][org-contacts]] for managing contacts and birthdays, chime provides built-in integration to ensure birthdays appear in your agenda and trigger notifications.

*The Problem:*

Org-contacts stores birthdays as properties (=:BIRTHDAY: 1985-03-15=) and uses diary sexps (=%%(org-contacts-anniversaries)=) to display them in your agenda. However, chime's async subprocess doesn't have org-contacts loaded, causing "Bad sexp" errors and preventing birthdays from appearing.

*The Solution:*

Chime provides a two-part solution:

1. *One-time conversion* for existing contacts
2. *Automatic capture template* for new contacts

Both approaches add plain org timestamps alongside the =:BIRTHDAY:= property, preserving vCard export compatibility while enabling chime notifications.

*Step 1: Convert Existing Contacts*

Use the included conversion script to add birthday timestamps to your existing contacts file:

#+BEGIN_SRC elisp
;; Load conversion script
(require 'convert-org-contacts-birthdays
         (expand-file-name "convert-org-contacts-birthdays.el"
                          (file-name-directory (locate-library "chime"))))

;; Convert your contacts file IN-PLACE (creates timestamped backup)
M-x chime-convert-contacts-in-place RET ~/org/contacts.org RET
#+END_SRC

This modifies your contacts.org file, transforming:

#+BEGIN_SRC org
,* Alice Anderson
:PROPERTIES:
:EMAIL: alice@example.com
:BIRTHDAY: 1985-03-15
:END:
#+END_SRC

Into:

#+BEGIN_SRC org
,* Alice Anderson
:PROPERTIES:
:EMAIL: alice@example.com
:BIRTHDAY: 1985-03-15
:END:
<1985-03-15 Sat +1y>
#+END_SRC

*Safety:* The script creates a timestamped backup (=contacts.org.backup-YYYY-MM-DD-HHMMSS=) before making any changes.

After conversion, comment out the diary sexp in your schedule file:

#+BEGIN_SRC org
# %%(org-contacts-anniversaries)
#+END_SRC

*Step 2: Enable Capture Template for New Contacts*

To automatically add birthday timestamps when capturing new contacts:

#+BEGIN_SRC elisp
;; Enable org-contacts integration
(setq chime-org-contacts-file "~/org/contacts.org")

;; Optional: customize capture key (default: "C")
(setq chime-org-contacts-capture-key "C")

;; Optional: customize heading (default: "Contacts")
(setq chime-org-contacts-heading "Contacts")
#+END_SRC

This adds an org-capture template that:
- Prompts for contact details (name, email, phone, birthday, etc.)
- Automatically inserts a yearly repeating timestamp if birthday is provided
- Preserves the =:BIRTHDAY:= property for vCard export

*Using the Capture Template:*

1. Press =C-c c= (or your org-capture binding)
2. Press =C= (or your configured capture key)
3. Fill in contact information
4. Birthday timestamps are added automatically on save

*Template Fields:*
- Name, Email, Phone, Address
- Birthday (YYYY-MM-DD or MM-DD format)
- Nickname, Company, Title, Website
- Note (instead of free-form text below properties)

*Disabling the Integration:*

Set =chime-org-contacts-file= to =nil= to disable the capture template:

#+BEGIN_SRC elisp
(setq chime-org-contacts-file nil)  ; Disabled by default
#+END_SRC

*Result:*

After setup, birthdays will:
- ✓ Appear in org-agenda
- ✓ Trigger chime notifications at configured times
- ✓ Work with chime's async subprocess (no "Bad sexp" errors)
- ✓ Still export to vCard via org-contacts
- ✓ Automatically include timestamps for new contacts

** Usage
:PROPERTIES:
:CUSTOM_ID: usage
:END:

*** Basic Event with Timestamp

#+BEGIN_SRC org
,* Meeting with Team
<2025-10-25 Sat 14:00>
#+END_SRC

Will notify at 14:00 (if =chime-alert-intervals= includes =(0 . severity)=).

*** Events with SCHEDULED or DEADLINE

#+BEGIN_SRC org
,* TODO Call Doctor
SCHEDULED: <2025-10-25 Sat 10:00>
#+END_SRC

*** Repeating Events

Repeating timestamps are fully supported:

#+BEGIN_SRC org
,* TODO Weekly Team Meeting
SCHEDULED: <2025-10-25 Sat 14:00 +1w>

,* TODO Daily Standup
SCHEDULED: <2025-10-25 Sat 09:00 +1d>

,* TODO Review Email
SCHEDULED: <2025-10-25 Sat 08:00 .+1d>
#+END_SRC

Supported repeaters:
- =+1w= - Repeat weekly from original date
- =.+1d= - Repeat daily from completion
- =++1w= - Repeat weekly from scheduled date

** Known Limitations
:PROPERTIES:
:CUSTOM_ID: known-limitations
:END:

*** S-expression Diary Entries Are Not Supported

Note: org-contacts users will quickly discover the above unsupported format is how org-contacts integrate birthdays into your calendar. If you use org-contacts, you will not be automatically notified about your contacts birthdays. 

Specifically, this format is *not supported*:

#+BEGIN_SRC org
,* TODO Daily Standup
SCHEDULED: <%%(memq (calendar-day-of-week date) '(1 2 3 4 5))>
#+END_SRC

For those using this format outside of org-contacts, your workaround is to use standard repeating timestamps instead:

#+BEGIN_SRC org
,* TODO Daily Standup
SCHEDULED: <2025-10-24 Fri 09:00 +1d>
#+END_SRC

For Monday-Friday events, you can either:
1. Accept weekend notifications (mark as DONE on weekends)
2. Create 5 separate entries, one for each weekday with =+1w= repeater

** Full Example Configuration
:PROPERTIES:
:CUSTOM_ID: full-example-configuration
:END:

#+BEGIN_SRC elisp
  (use-package chime
    :vc (:url "https://github.com/cjennings/chime" :rev :newest)
    :after alert
    :commands (chime-mode chime-check)
    :config
    ;; Polling interval: check every 60 seconds (default)
    (setq chime-check-interval 60)

    ;; Alert intervals: 5 minutes before (medium) and at event time (high)
    (setq chime-alert-intervals '((5 . medium) (0 . high)))

    ;; Chime sound
    (setq chime-play-sound t)
    ;; Uses bundled chime.wav by default

    ;; Modeline display - compact format
    (setq chime-enable-modeline t)
    (setq chime-modeline-lookahead-minutes 180)    ; Show events 3 hrs ahead
    (setq chime-modeline-format " ⏰%s")           ; Minimal prefix
    (setq chime-notification-text-format "%t (%u)") ; Title + countdown only
    (setq chime-display-time-format-string "%H:%M") ; 24-hour time
    (setq chime-time-left-format-short "in %mm")   ; Compact: "in 5m"
    (setq chime-time-left-format-long "%hh%mm")    ; Compact: "1h30m"
    (setq chime-time-left-format-at-event "NOW!")  ; Custom at-event message

    ;; Notification settings
    (setq chime-notification-title "Reminder")

    ;; Don't filter by TODO keywords - notify for all events
    (setq chime-keyword-whitelist nil)
    (setq chime-keyword-blacklist nil)

    ;; Only notify for non-done items
    (setq chime-predicate-blacklist
          '(chime-done-keywords-predicate))

    ;; Enable chime-mode automatically
    (chime-mode 1))
#+END_SRC

** Manual Check
:PROPERTIES:
:CUSTOM_ID: manual-check
:END:

You can manually trigger a notification check:

#+BEGIN_SRC elisp
M-x chime-check
#+END_SRC

To update the modeline display *without* sending notifications (useful after syncing your calendar with org-gcal or similar tools):

#+BEGIN_SRC elisp
M-x chime-refresh-modeline
#+END_SRC

** Troubleshooting
:PROPERTIES:
:CUSTOM_ID: troubleshooting
:END:

*** Enabling Debug Mode

If something isn't working right, enable debug mode for detailed diagnostics in the =*Messages*= buffer:

#+BEGIN_SRC elisp
;; Enable BEFORE loading chime
(setq chime-debug t)
(require 'chime)
#+END_SRC

This loads =chime-debug.el= and gives you these commands:

- =M-x chime--debug-dump-events= -- show all stored upcoming events
- =M-x chime--debug-dump-tooltip= -- show tooltip content
- =M-x chime--debug-config= -- dump complete configuration
- =M-x chime--debug-show-async-stats= -- show async process success/failure stats
- =M-x chime-debug-force-check= -- force an immediate check with diagnostics

It also monitors event loading timing and async process performance in the background, logging to =*Messages*=.

*** No notifications appearing

1. Verify chime-mode is enabled: =M-: chime-mode=
2. Check that alert is configured correctly:
   #+BEGIN_SRC elisp
   (setq alert-default-style 'libnotify)  ; or 'notifications on some systems
   #+END_SRC
3. Manually test: =M-x chime-check=
4. Check =*Messages*= buffer for error messages
5. Enable [[#enabling-debug-mode][debug mode]] for detailed diagnostics

*** No sound playing

1. Verify sound is enabled: =M-: chime-play-sound= should return =t=
2. Check sound file exists: =M-: (file-exists-p chime-sound-file)=
3. Test sound directly: =M-: (play-sound-file chime-sound-file)=
4. Ensure your system has audio support configured

*** Events not being detected

1. Ensure files are in =org-agenda-files=
2. Verify timestamps have time components: =<2025-10-25 Sat 14:00>= not =<2025-10-25 Sat>=
3. Check filtering settings (keyword/tag whitelist/blacklist)
4. Timestamps support both 24-hour (=14:00=) and 12-hour (=2:00pm=, =2:00 PM=) formats

*** org-contacts diary sexp errors

If you see errors like "Bad sexp at line 2: (let ((entry) (date '(10 29 2025))) (org-contacts-anniversaries))" in your =*emacs:err*= buffer, this is because chime's async subprocess doesn't have org-contacts loaded.

*Symptoms:*
- No events appear in modeline despite having scheduled items
- =*emacs:err*= buffer shows "Bad sexp" errors for org-contacts
- Errors appear repeatedly (every minute during chime checks)

*Solution 1: Load org-contacts in your config (Recommended)*

Add this to your config BEFORE chime loads:

#+BEGIN_SRC elisp
(require 'org-contacts nil t)  ; Load if available, don't error if missing
#+END_SRC

Chime will automatically load org-contacts in its async subprocess if it's installed.

*Solution 2: Comment out the sexp line*

In your org file (usually =schedule.org=), comment out or remove:

#+BEGIN_SRC org
# %%(org-contacts-anniversaries)
#+END_SRC

*Solution 3: Convert to plain timestamps (recommended)*

Use the included conversion script to add birthday timestamps directly to your contacts file. See the [[#integration-with-org-contacts][org-contacts integration]] section for full details, but the quick version:

#+BEGIN_SRC elisp
;; Load the conversion script
(require 'convert-org-contacts-birthdays
         (expand-file-name "convert-org-contacts-birthdays.el"
                          (file-name-directory (locate-library "chime"))))

;; Convert your contacts file in-place (creates timestamped backup first)
M-x chime-convert-contacts-in-place RET ~/org/contacts.org RET
#+END_SRC

Then comment out the diary sexp:

#+BEGIN_SRC org
# %%(org-contacts-anniversaries)
#+END_SRC

Restart chime or run =M-x chime-check= to verify birthdays appear without errors.

*Why this happens:*

Org-mode diary sexps like =%%(org-contacts-anniversaries)= are dynamic expressions evaluated during agenda building. Chime runs agenda building in an async subprocess for performance, but that subprocess needs the generating package (org-contacts) loaded. Chime includes org-contacts as a soft dependency, but it must be installed for the sexp to work.

*** Multiple Emacs instances producing duplicate notifications

If you receive duplicate notifications for every event, you likely have multiple Emacs processes running with chime-mode enabled.

*Symptoms:*
- Receiving 2 (or more) identical notifications for each event
- Notifications appear at the same time but as separate alerts

*Common Scenario:*

You're running chime with an emacsclient connected to an emacs daemon (=emacs --daemon=), then launch a separate Emacs process from the command line. Each process runs its own instance of chime-mode, resulting in duplicate notifications.

*Example:*
#+BEGIN_SRC bash
# Start emacs daemon with chime-mode enabled
emacs --daemon

# Connect with emacsclient (uses daemon - chime runs here)
emacsclient -c

# Later, accidentally launch standalone Emacs process
emacs &  # This creates a SECOND chime instance!
#+END_SRC

*Solution:*

1. Check for multiple Emacs processes:
   #+BEGIN_SRC bash
   ps aux | grep emacs
   #+END_SRC

2. Decide on your preferred architecture:
   - *Option A*: Use emacs daemon + emacsclient exclusively (recommended for consistency)
   - *Option B*: Use standalone Emacs processes only (simpler, but separate configs)

3. Kill extra processes:
   #+BEGIN_SRC bash
   # To stop the daemon
   emacsclient -e "(kill-emacs)"

   # Or kill specific process by PID
   kill <PID>
   #+END_SRC

4. Verify only one Emacs process is running after cleanup

*Prevention:*

- If using emacs daemon, always connect with =emacsclient -c= instead of launching =emacs=
- Add shell aliases to prevent accidents:
  #+BEGIN_SRC bash
  alias emacs="emacsclient -c -a ''"  # Auto-start daemon if not running
  #+END_SRC

** Requirements
:PROPERTIES:
:CUSTOM_ID: requirements
:END:

- Emacs 27.1+
- Org-mode 9.0+
- =alert= package
- =dash= package
- =async= package

** Testing
:PROPERTIES:
:CUSTOM_ID: testing
:END:

600+ ERT tests. See [[file:TESTING.org][TESTING.org]] for details on running them and the test architecture.

Quick start:
#+BEGIN_SRC bash
cd tests
make test       # Run all tests
make test-file FILE=modeline  # Run specific test file
#+END_SRC

** License
:PROPERTIES:
:CUSTOM_ID: license
:END:

GPL-3.0

** Credits
:PROPERTIES:
:CUSTOM_ID: credits
:END:

All credit and thanks to Artem Khramov for his work on [[https://github.com/akhramov/org-wild-notifier.el][org-wild-notifier]], which served me well for some time. Sadly, the author deprecated org-wild-notifier on Aug 2, 2025 in favor of [[https://github.com/spegoraro/org-alert][org-alert]]. I began fixing bugs and adding features, and it eventually became CHIME.

I plan to keep maintaining this out of appreciation for Artem's work and for the Emacs community.

** Migration from org-wild-notifier
:PROPERTIES:
:CUSTOM_ID: migration-from-org-wild-notifier
:END:

If you're migrating from org-wild-notifier, you'll need to update your configuration:

1. Change package name:
   - =(require 'org-wild-notifier)= → =(require 'chime)=

2. Update all configured variable names:
   - =org-wild-notifier-*= → =chime-*=

3. Update configured function names:
   - =org-wild-notifier-mode= → =chime-mode=
   - =org-wild-notifier-check= → =chime-check=

4. Note: The =:WILD_NOTIFIER_NOTIFY_BEFORE:= / =:CHIME_NOTIFY_BEFORE:= property has been removed. Use the global =chime-alert-intervals= variable instead (e.g., =(setq chime-alert-intervals '((30 . low) (15 . medium) (5 . medium) (0 . high)))=).

** Development
:PROPERTIES:
:CUSTOM_ID: development
:END:

See [[file:TESTING.org][TESTING.org]].