8

I use the calendar and scheduling application calcurse. This is a fine command-line program.

For example, with calcurse -a you can display the events for the day.

How i can send this info to notify-send, to show a notification on the desktop?

$ calcurse -a | notify-send

will not work ...

Jacob Vlijm
  • 85,475
Dino
  • 148

4 Answers4

3

About Calcurse

As often, reverting to a (very) basic, but nevertheless complete functionality feels weird, but in a way refreshing. Not sure if I would use it though.

Although the bottom of Calcurse's window suggests an option to pipe, and Calcurse's website mentions "user-configurable notification system" I couldn't find a way to use it for automatic scheduled notifications of all events.

enter image description here

At the same time: indeed such a reminder functionality would be a useful addition. The script below can be used as a plug- in for calcurse to show reminders. I hope you find it useful.

It is extremely easy to use; simply run the script in the background. All it does is check the events for today (updated twice per minute), and run notifications at an arbitrary time before expiration.

What is needed to create reminders

The output of the command:

calcurse -a 

looks like:

12/29/15:
 * Free today!
 - 12:00 -> ..:..
    See if this script does its job :)

It immediately occurs that the scheduled items start with a "-", while items for all day start with "*". What we need to do is create two groups (lists) of items:

  • the scheduled ones
  • the unscheduled ones

Of the first category, we need to parse out the time, convert it into a format we can calculate with, so we can show reminder at n- minutes before expiration.

Subsequently, we need to "keep an eye on the clock", to compare the set time (of the event) to the current time. If the time span between the two enters the set warning time, simply show the notification.
Finally, to prevent repeated notification, we need to add the item to a "done" list.

Additionally, the script cleans up the "done" list; if an item does not occur in today's items anymore (either if you removed it or the day switched during your session), the items are removed automatically.

The result

On startup (log in)

  • the script shows all today's items, in succession:

    enter image description here enter image description here

On an arbitrary time before expiration (in this case 15 minutes)

  • the scripts reminds you in a notification:

    enter image description here

The script

#!/usr/bin/env python3
import subprocess
import time
import sys

warn = int(sys.argv[1])

def get(command):
    return subprocess.check_output(command).decode("utf-8")

def convert(t):
    # convert set time into a calculate- able time
    return [int(n) for n in t.split(":")]

def calc_diff(t_curr, t_event):
    # calculate time span
    diff_hr = (t_event[0] - t_curr[0])*60
    diff_m = t_event[1] - t_curr[1]
    return diff_hr + diff_m

def cleanup(done, newlist):
    # clean up "done" -lists
    for item in done:
        if not item in newlist:
            done.remove(item)
    return done

def show_time(event, s = ""):
    # show scheduled event
    hrs = str(event[0][0]); mnts = str(event[0][1])
    mnts = "0"+mnts if len(mnts) != 2 else mnts
    subprocess.call(["notify-send", s, "At "+hrs+":"+mnts+" - "+event[1]])

startups = []; times = []

while True:
    currtime = convert(time.strftime("%H:%M"))
    events = [l.strip() for l in get(["calcurse", "-a"]).splitlines()][1:]
    # arrange event data:
    groups = []; sub = []
    for l in events:
        if l.startswith("* "):
            groups.append([l.replace("* ", "")])
        elif l.startswith("- "):
            sub.append(convert(l.split("->")[0].replace("-", "").strip()))
        else:
            groups.append(sub+[l])
            sub = []
    # run notifications
    for item in groups:
        # on startup:
        if not item in startups:
            # all- day events
            if len(item) == 1:
                subprocess.call(["notify-send", "Today - "+item[0]])
            # time- specific events
            elif len(item) == 2:
                show_time(item, "Appointment")
            startups.append(item)
        # as a reminder:
        if all([len(item) == 2, not item in times]):
            span = calc_diff(currtime, item[0])
            if span <= warn:
                show_time(item, "[Reminder]")
                times.append(item)     
    # clean up events
    startups = cleanup(startups, groups); times = cleanup(times, groups)
    time.sleep(30)

How to use

  • Copy the script into an empty file, save it as run_ccursereminders.py
  • Test- run it with the time you'd like to be warned (before expiration time, in minutes) as an argument. An example, to run reminders 30 minutes before your appointment:

    python3 /path/to/run_ccursereminders.py 30
    
  • If it works as you wish, add it to Startup Applications: choose Dash > Startup Applications > Add. Add the command

    /bin/bash -c "sleep 30 && python3 /path/to/run_ccursereminders.py 30"
    

    where the last 30 is the number of minutes to warn you before an appointment expires

Jacob Vlijm
  • 85,475
3

I was able to make it working with command substitution:

notify-send "calcurse notification header" "$(calcurse -n)"
2

I no this is old, but if anyone is still interested, the following settings work for me in my ~/.calcurse/conf file:

notification.command=calcurse --next | xargs -0 notify-send "Appointment"
daemon.enable=yes
notification.warning=300
David Foerster
  • 36,890
  • 56
  • 97
  • 151
0

I wrote a short bash script to be used in combination with cron to get notifications. It also has support to notify at variable intervals as well as notify for whole-day events.

This is the current version of the script:

#!/bin/bash
#Will send a notification if the next appointment in the next 24 hours occurs within $1 minutes 
#Set $1 to be the time (minutes) you want the notification to pop up in advance before the appointment actually happens.
#This script should be executed by cron every minute or something like that thus it cannot run exactly $1 minutes before event. 
#So we use $2 to be the time +/- of $1 that it is okay to send the notification.
#Probably set $2 this to the same period between when cron runs this script

notify_within=$1 script_frequency=$2

#if there are no appointments in the next two days, the stop right away if [ "$(calcurse -a -d 2)" = "" ] ; then exit 0 fi

if ! [ "$(calcurse -n)" = "" ] ; then #There is an event today with an associated time hours_remaining=$(calcurse -n | tail -1 | sed 's/ //g' | sed 's/[//g' | sed 's/].//g' | sed 's/:..//g') minutes_remaining=$(calcurse -n | tail -1 | sed 's/ //g' | sed 's/[//g' | sed 's/].//g' | sed 's/..://g')

time_remaining=$( echo &quot;$hours_remaining * 60 + $minutes_remaining&quot; | bc ) #in minutes. just adding the hours to minutes

#Test whether the next event is within the given time frame
#We need bc (calculator) to do the calculation. Will return &quot;1&quot; if the conditional is true
#0.95 factor because you can get notifications to pop 3 times if cron is running every 1 minute and you use script_frequency=1
notify_boolean=$( echo &quot;$time_remaining &lt;= ($script_frequency*0.95 + $notify_within) &amp;&amp; $time_remaining &gt;= ($notify_within - $script_frequency*0.95)&quot; | bc )
if [ &quot;$notify_boolean&quot; = 1 ] ; then
    eventname=&quot;$(calcurse -n | sed -n 2p | sed 's/^   \[..:..\] //g')&quot;
    event_in=&quot;$(calcurse -n | sed -n 2p | sed 's/^   \[//g' | sed 's/\].*//g')&quot;
    notify-send &quot;Appointment: ${eventname} in ${event_in}&quot;
    fbcli -t -1 -E message-new-instant 2&gt;&amp;1 &gt;/dev/null || true #send a request to vibrate and make sound with feedbackd if possible
fi

fi

tomorrow="$(date --date 'next day' +"%Y-%m-%d")" daily_events="$(calcurse -a -d 2 | tr "\n" "@" | sed "0,/^.*${tomorrow}/ s///" | sed 's/^:@ * //g' | sed 's/@ * /, /g' | sed 's/@$//g' )" #Gives list of daily events tomorrow, Hopefully your event doesn't have "@" in it if ! [ "$daily_events" = "" ] ; then current_hour="$(date +"%H")" current_minute="$(date +"%H")" minutes_today=$(( $current_hour * 60 + $current_minute )) #Minutes that have elapsed today minutes_left_today=$(( 2460 - $minutes_today )) #Minutes elapsed at midnight notify_boolean_2="$(echo "$minutes_left_today <= ($script_frequency0.95 + $notify_within)" | bc )" #It is $notify_within minutes of midnight! if [ "$notify_boolean_2" = 1 ] ; then notify-send "Daily Events: $daily_events" fbcli -t -1 -E message-new-instant 2>&1 >/dev/null || true #send a request to vibrate and make sound with feedbackd if possible fi fi

And this is the cron line to call it:

*/2 * * * * export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u)/bus; /path/to/calendar_appointment_notify.sh 30 2

This script depends on

  • libnotify
  • calcurse
  • bc
  • date

Of course, the current version can be found on GitHUB.

zx485
  • 2,865