Skip to content

Instantly share code, notes, and snippets.

@crowding
Created August 9, 2012 07:41
Show Gist options
  • Save crowding/3302050 to your computer and use it in GitHub Desktop.
Save crowding/3302050 to your computer and use it in GitHub Desktop.
Periodic productivity reminder.

ABOUT

This is a setup for my computer to regularly remind myself to log my current activity. I find it helps keep me a bit more productive.

Every so often, it will pop up an emacs buffer pointing to the end of a worklog file, and prompt me to write what I'm presently doing.

It tries to check the system idle time so that it doesn't steal the keyboard focus in the middle of typing and doesn't pester you when you're away from the desk or watching a movie or something.

INSTALLATION

I guarantee this is all particular to my setup (Emacs.app 24 on OS X with Growl and whatever my particular local configuration is) so instead of running the install script just glance through the code and see what you need to change.

Now, on my system, I install by running make install and saying yes when Emacs wants confirmation.

###Testing

First test by M-: (reminder-remind-log-entry) in Emacs.

Then test by running the reminder script. (you'll have to wait several seconds as it waits for a bit of UI idle time before going ahead.)

Then test by launchctl start localhost.reminder

After that, it should run periodically when you're at your computer.

###LICENSE

Released under the MIT license. See [LICENSE.txt] for details.

/*****************************************
* idler.c
*
* Uses Cocoe to figure out the idle time of the system, and prints
* it to stdout.
*
* Code originally from
* <http://www.danandcheryl.com/2010/06/how-to-check-the-system-idle-time-using-cocoa>
*/
#include <IOKit/IOKitLib.h>
#include <CoreFoundation/CoreFoundation.h>
#include <CoreServices/CoreServices.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
/**
Returns the number of seconds the machine has been idle or -1 if an error occurs.
The code is compatible with Tiger/10.4 and later (but not iOS).
*/
int64_t SystemIdleTime(void) {
int64_t idlesecs = -1;
io_iterator_t iter = 0;
if (IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceMatching("IOHIDSystem"), &iter) == KERN_SUCCESS) {
io_registry_entry_t entry = IOIteratorNext(iter);
if (entry) {
CFMutableDictionaryRef dict = NULL;
if (IORegistryEntryCreateCFProperties(entry, &dict, kCFAllocatorDefault, 0) == KERN_SUCCESS) {
CFNumberRef obj = CFDictionaryGetValue(dict, CFSTR("HIDIdleTime"));
if (obj) {
int64_t nanoseconds = 0;
if (CFNumberGetValue(obj, kCFNumberSInt64Type, &nanoseconds)) {
idlesecs = (nanoseconds >> 30); // Divide by 10^9 to convert from nanoseconds to seconds.
}
}
CFRelease(dict);
}
IOObjectRelease(entry);
}
IOObjectRelease(iter);
}
return idlesecs;
}
int main(void) {
printf("%lld\n", (long long)SystemIdleTime());
return 0;
}
#!/bin/bash
set -v verbose
trap exit ERR
#Look over these values...
BINDIR=${HOME}/bin
LAUNCHCTL_DIR=${HOME}/Library/LaunchAgents
SCRIPTLABEL="localhost.reminder"
LOG_FILE="${HOME}/org/what_are_you_doing.md"
EDITOR_PACKAGE_NAME=reminder
EDITOR_CODE_FILE="$(dirname $0)/${EDITOR_PACKAGE_NAME}.el"
#This sets our editor command (it's complicated with emacsclient)
EDITOR_COMMAND="/Applications/Emacs.app/Contents/MacOS/bin/emacsclient --alternate-editor /Applications/Emacs.app/Contents/MacOS/Emacs"
#And here's elisp code to install / replace an elisp package.
read -r -d '' "EDITOR_INSTALL_CODE" <<EOF || true
(progn
(when (package-installed-p '${EDITOR_PACKAGE_NAME})
(package-delete "${EDITOR_PACKAGE_NAME}"
(mapconcat 'number-to-string
(aref (cdr (assq '${EDITOR_PACKAGE_NAME} package-alist)) 0) "."))
(setq package-alist (assq-delete-all '${EDITOR_PACKAGE_NAME} package-alist))
)
(package-install-file "${EDITOR_CODE_FILE}")
(customize-set-variable '${EDITOR_PACKAGE_NAME}-log-file-location "${LOG_FILE}")
(customize-save-variable '${EDITOR_PACKAGE_NAME}-log-file-location "${LOG_FILE}")
)
EOF
# ;package-delete takes a string for version but I can only find a number?
# ;I think package-delete might not be quite right because I also have to do this.
#Irreversible operations start here.
#compile the idle-time-computing program
make idler
#install the elisp code
${EDITOR_COMMAND} --eval "${EDITOR_INSTALL_CODE}"
#and install it
cp idler ${BINDIR}/idler
# Then install the shell wrapper
cat <<- EOF > ${BINDIR}/${EDITOR_PACKAGE_NAME}
#!/bin/bash
while (( (IDLE_TIME=\`$BINDIR/idler\`) < 4 )); do sleep 3; done
(( \$IDLE_TIME < 120 )) || exit
${EDITOR_COMMAND} --eval "(${EDITOR_PACKAGE_NAME}-remind-log-entry)"
EOF
chmod a+x ${BINDIR}/${EDITOR_PACKAGE_NAME}
#and finally install a launch agent (OS X's replacement for cron)
launchctl unload ${LAUNCHCTL_DIR}/${SCRIPTLABEL}.plist || true
cat <<EOF > ${LAUNCHCTL_DIR}/${SCRIPTLABEL}.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Username</key>
<string>${USER}</string>
<key>Label</key>
<string>${SCRIPTLABEL}</string>
<key>ProgramArguments</key>
<array>
<string>${BINDIR}/reminder</string>
</array>
<key>StartInterval</key>
<integer>1380</integer>
</dict>
</plist>
EOF
launchctl load ${LAUNCHCTL_DIR}/${SCRIPTLABEL}.plist
###LICENSE
Released under the MIT license.
Copyright (c) 2012 Peter Meilstrup
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
idler: idler.c
gcc -Wall -framework IOKit -framework Carbon idler.c -o idler
install:
./install.bash
all: idler
;;; reminder.el --- Simple logging reminder
;; Copyright (C) 2012 Peter Meilstrup
;; Author: Peter Meilstrup <peter.meilstrup@gmail.com>
;; Version: 0.0.1
;; Created: 15 Oct 2012
;; Keywords: akrasia
;; This file is not part of GNU Emacs.
(defgroup reminder nil "Periodic note taking reminder."
:group 'extensions :prefix "reminder-")
(defcustom reminder-log-file-location "~/log.txt" "Full path of your note file."
:group 'reminder :type 'string)
(defcustom reminder-growl-title "What are you doing?" "The title to use in Growl notifications."
:group 'reminder :type 'string)
(defcustom reminder-use-growl t "Whether to use Growl notifications for reminders."
:group 'reminder :type 'boolean)
(defcustom reminder-growl-text "Make a note." "The text to use in Growl notifications."
:group 'reminder :type 'string)
(defcustom reminder-activate-emacs-p t "Whether to bring Emacs to the front to ask for notes."
:group 'reminder :type 'boolean)
(defcustom reminder-buffer-action
'((display-buffer-reuse-window display-buffer-pop-up-frame).((reusable-frames . t) (pop-up-frames . t)))
"The action to use when selecting the frame to show the buffer in (see display-buffer)"
:group 'reminder :type 'sexp)
;;;###autoload
(defun reminder-remind-log-entry ()
"Find the activity logging file, put the cursor at the
bottom, and display a writing prompt with Growl."
(pop-to-buffer (find-file-noselect reminder-log-file-location) reminder-buffer-action)
(goto-char (point-max))
(if reminder-use-growl
(reminder-send-growl-notification reminder-growl-title reminder-growl-text))
(if reminder-activate-emacs
(do-applescript "tell application \"Emacs\" to activate"))
)
;;Interface to Growl originally from
;;http://lojic.com/blog/2009/08/06/send-growl-notifications-from-carbon-emacs-on-osx/
;;one-time register Emacs as a Growl source.
(do-applescript
"tell application \"GrowlHelperApp\"
-- Declare a list of notification types
set the allNotificationsList to {\"Emacs Notification\"}
-- Declare list of active notifications. If some of them
-- isn't activated, user can do this later via preferences
set the enabledNotificationsList to {\"Emacs Notification\"}
-- Register our application in Growl.
register as application \"Emacs.app\" ¬
all notifications allNotificationsList ¬
default notifications enabledNotificationsList ¬
icon of application \"Emacs.app\"
end tell"
)
(defun reminder-send-growl-notification (title message &optional sticky)
"Send a Growl notification."
(do-applescript
(format "tell application \"GrowlHelperApp\"
notify with name \"Emacs Notification\" title \"%s\" description \"%s\" application name \"Emacs.app\" sticky %s
end tell"
title
(replace-regexp-in-string "\"" "''" message)
(if sticky "yes" "no"))))
(provide 'reminder)
;;; reminder.el ends here
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment