emacs-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [PATCH] Added basic file system watching support.


From: Rüdiger Sonderfeld
Subject: Re: [PATCH] Added basic file system watching support.
Date: Fri, 28 Sep 2012 15:06:24 +0200
User-agent: KMail/4.8.5 (Linux/3.2.0-31-generic; KDE/4.8.5; x86_64; ; )

Hello,
this is an updated version of my "file system watching" patch
(full thread: http://thread.gmane.org/gmane.emacs.devel/140158 )

I don't think the patch will be ready for 24.3. It is still not complete and
needs some heavy testing.

I addressed some of the issues mentioned by Stefan Monnier here
http://article.gmane.org/gmane.emacs.devel/141741

> It seems that each event is either of form (SYMBOL . FILENAME) where
> FILENAME is the watched object, or of the form (SYMBOL from NAME . COOKIE)
> where NAME is the file added/removed from the watched object (a
> directory, presumably), but this object's FILENAME is not present.
> Any particular reason for this inconsistency?

FILENAME is not always the watched object.  If a directory is watched then
it is the file name of the file inside the directory.  This should really not
be used as identification for the watched object!  That's why CALLBACK
also gets the ID.

> IIUC, these cookies are part of the patch to which Paul objected.
> And IIUC their content has no importance, they're only checked for
> equality, right?
>
> So they don't have to be represented as numbers, but could be
> represented by some other special object, as long as (eq cookie1
> cookie2) returns the proper boolean value when comparing those
> special objects.
>
> I guess that several `ev' objects can have the same `ev->cookie'
> value, right?

Yes exactly.  The value is unimportant as long as it is unique to an event
and can be compared with eq.  What would you propose as an alternative
object here?

> Wouldn't it be a bug for events to be nil here (that would mean you
> receive inotify events for files you do not know you're watching, or
> something like that)?

The problem is that even after removal of a watch descriptor there can
still be issues in the queue.  Therefore I think it is best to ignore any
events for files we do not watch.  There could also be event types we do not
know about when inotify gets expanded.  That's why I think unknown
event types should be ignored instead of reporting an error.

> I don't think you need ev->wd in your file-watch objects.  You could use
> Fcons (Qfile_watch, cell) instead, IIUC.  It would be good, because it
> would let you use a SAVE_VALUE object for ev->wd (since it wouldn't
> escape to Lisp any more), which would solve the problem of losing a few
> bits of data in make_number.

I don't need to make the watchdescriptor (wd) available to elisp.  But I need
a way to get the descriptor from the watch handle.  How could this be
implemented with SAVE_VALUE?

> Shouldn't this signal an error instead?

This could be the result of calling file-unwatch too often.  Currently
the behaviour is to return nil in that case.  But this could also be
seen as an error of course.

Regards,
Rüdiger

-- >8 --

The current patch only works with inotify (Linux).  It adds two elisp
functions file-watch and file-unwatch.

Example

(let ((fh (file-watch "foo" t #'(lambda (id event) (message "%s %s" id
event)))))
  (delete-file "foo/bar")
  (file-unwatch fh))

(Expects a directory foo containing a file bar)

This watches for any kind of changes in a directory foo and then
deletes a file foo/bar and finally removes the watch.

Signed-off-by: Rüdiger Sonderfeld <address@hidden>
---
 configure.ac                      |  14 ++
 lisp/subr.el                      |  23 ++
 src/Makefile.in                   |   2 +-
 src/emacs.c                       |   4 +
 src/filewatch.c                   | 457 ++++++++++++++++++++++++++++++++++++++
 src/keyboard.c                    |  28 +++
 src/lisp.h                        |   5 +
 src/termhooks.h                   |   5 +
 test/automated/filewatch-tests.el |  50 +++++
 9 files changed, 587 insertions(+), 1 deletion(-)
 create mode 100644 src/filewatch.c
 create mode 100644 test/automated/filewatch-tests.el

diff --git a/configure.ac b/configure.ac
index 5a3aea7..8b8054b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -184,6 +184,7 @@ OPTION_DEFAULT_ON([gconf],[don't compile with GConf 
support])
 OPTION_DEFAULT_ON([gsettings],[don't compile with GSettings support])
 OPTION_DEFAULT_ON([selinux],[don't compile with SELinux support])
 OPTION_DEFAULT_ON([gnutls],[don't use -lgnutls for SSL/TLS support])
+OPTION_DEFAULT_ON([inotify],[don't compile with inotify (file-watch) support])
 
 ## For the times when you want to build Emacs but don't have
 ## a suitable makeinfo, and can live without the manuals.
@@ -2101,6 +2102,19 @@ fi
 AC_SUBST(LIBGNUTLS_LIBS)
 AC_SUBST(LIBGNUTLS_CFLAGS)
 
+dnl inotify is only available on GNU/Linux.
+HAVE_INOTIFY=no
+if test "${with_inotify}" = "yes"; then
+   AC_CHECK_HEADERS(sys/inotify.h)
+   if test "$ac_cv_header_sys_inotify_h" = yes ; then
+     AC_CHECK_FUNCS(inotify_init1 inotify_add_watch inotify_rm_watch, 
HAVE_INOTIFY=yes)
+   fi
+fi
+if test "${HAVE_INOTIFY}" = "yes"; then
+   AC_DEFINE(HAVE_INOTIFY, [1], [Define to 1 to use inotify])
+   AC_DEFINE(HAVE_FILEWATCH, [1], [Define to 1 to support filewatch])
+fi
+
 dnl Do not put whitespace before the #include statements below.
 dnl Older compilers (eg sunos4 cc) choke on it.
 HAVE_XAW3D=no
diff --git a/lisp/subr.el b/lisp/subr.el
index 8dfe78d..f47d4fb 100644
--- a/lisp/subr.el
+++ b/lisp/subr.el
@@ -4159,6 +4159,29 @@ convenience wrapper around `make-progress-reporter' and 
friends.
        nil ,@(cdr (cdr spec)))))
 
 
+;;;; Support for watching filesystem events.
+
+(defun filewatch-event-p (event)
+  "Check if EVENT is a filewatch event."
+  (and (listp event)
+       (eq (car event) 'filewatch-event)))
+
+;;;###autoload
+(defun filewatch-handle-event (event)
+  "Handle file system monitoring event.
+If EVENT is a filewatch event then the callback is called.  If EVENT is
+not a filewatch event then a `filewatch-error' is signaled."
+  (interactive "e")
+
+  (unless (filewatch-event-p event)
+    (signal 'filewatch-error (cons "Not a valid filewatch event" event)))
+
+  (dolist (ev (cdr event))
+    (unless (and (listp ev) (>= (length ev) 3))
+      (signal 'filewatch-error (cons "Not a valid filewatch event" event)))
+    (funcall (car (cdr ev)) (car ev) (car (cdr (cdr ev))))))
+
+
 ;;;; Comparing version strings.
 
 (defconst version-separator "."
diff --git a/src/Makefile.in b/src/Makefile.in
index e43f83e..1f17830 100644
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -338,7 +338,7 @@ base_obj = dispnew.o frame.o scroll.o xdisp.o menu.o 
$(XMENU_OBJ) window.o \
        syntax.o $(UNEXEC_OBJ) bytecode.o \
        process.o gnutls.o callproc.o \
        region-cache.o sound.o atimer.o \
-       doprnt.o intervals.o textprop.o composite.o xml.o \
+       doprnt.o intervals.o textprop.o composite.o xml.o filewatch.o \
        profiler.o \
        $(MSDOS_OBJ) $(MSDOS_X_OBJ) $(NS_OBJ) $(CYGWIN_OBJ) $(FONT_OBJ) \
        $(WINDOW_SYSTEM_OBJ)
diff --git a/src/emacs.c b/src/emacs.c
index 05affee..7c306ee 100644
--- a/src/emacs.c
+++ b/src/emacs.c
@@ -1411,6 +1411,10 @@ Using an Emacs configured with --with-x-toolkit=lucid 
does not have this problem
       syms_of_gnutls ();
 #endif
 
+#ifdef HAVE_FILEWATCH
+      syms_of_filewatch ();
+#endif /* HAVE_FILEWATCH */
+
 #ifdef HAVE_DBUS
       syms_of_dbusbind ();
 #endif /* HAVE_DBUS */
diff --git a/src/filewatch.c b/src/filewatch.c
new file mode 100644
index 0000000..c29e8b1
--- /dev/null
+++ b/src/filewatch.c
@@ -0,0 +1,457 @@
+/* Watching file system changes.
+
+Copyright (C) 2011
+  Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+#ifdef HAVE_FILEWATCH
+#include "lisp.h"
+#include "coding.h"
+#include "process.h"
+#include "keyboard.h"
+#include "character.h"
+#include "frame.h" /* Required for termhooks.h.  */
+#include "termhooks.h"
+
+static Lisp_Object Qmodify, Qmove, Qattrib, Qdelete, Qfrom, Qto, Qall_modify;
+static Lisp_Object Qfile_watch;
+static Lisp_Object Qaccess, Qclose_write, Qclose_nowrite, Qopen, Qall;
+
+#ifdef HAVE_INOTIFY
+#include <sys/inotify.h>
+#include <sys/ioctl.h>
+
+enum { uninitialized = -100 };
+/* File handle for inotify.  */
+static int inotifyfd = uninitialized;
+
+/* Assoc list of files being watched.
+ Format:
+ (id . (filename ((subid callback flags) (subid callbacks flags) ...))) */
+static Lisp_Object watch_list;
+
+static Lisp_Object
+get_watch_data_by_watchdesc (int watchdesc)
+{
+  return Fassq (make_number (watchdesc), watch_list);
+}
+
+#define CADDR(x) XCAR (XCDR (XCDR (x)))
+#define CADR(x) XCAR (XCDR (x))
+
+static Lisp_Object
+append2 (Lisp_Object s1, Lisp_Object s2)
+{
+  Lisp_Object args[2];
+  args[0] = s1;
+  args[1] = s2;
+  return Fappend (2, args);
+}
+
+/* Returns a list with all the elements from EVENTS the `watch_list' CELL
+   is interested in.  */
+static Lisp_Object
+match_aspects (Lisp_Object cell, Lisp_Object events)
+{
+  Lisp_Object e;
+  Lisp_Object ret = Qnil;
+  Lisp_Object aspects = CADDR (cell);
+  if (EQ (aspects, Qt) || EQ (aspects, Qall))
+    return events;
+  else if (EQ (aspects, Qall_modify))
+    aspects = list4 (Qmodify, Qmove, Qattrib, Qdelete);
+  else if (! CONSP (aspects))
+    aspects = list1 (aspects);
+
+  while (CONSP (events))
+    {
+      e = XCAR (events);
+      if (! NILP (Fmemq (XCAR (e), aspects)))
+        ret = Fcons (e, ret);
+      events = XCDR (events);
+    }
+  return ret;
+}
+
+static Lisp_Object
+inotifyevent_to_events (Lisp_Object name, struct inotify_event *ev)
+{
+  Lisp_Object events = Qnil;
+  if (ev->mask & (IN_MODIFY|IN_CREATE) )
+    events = Fcons (Fcons (Qmodify, name), events);
+  if (ev->mask & IN_MOVE_SELF)
+    events = Fcons (Fcons (Qmove, name), events);
+  if (ev->mask & IN_MOVED_FROM)
+    events = Fcons (Fcons (Qmove,
+                           Fcons (Qfrom,
+                                  Fcons (name,
+                                         make_number (ev->cookie)))),
+                    events);
+  if (ev->mask & IN_MOVED_TO)
+    events = Fcons (Fcons (Qmove,
+                           Fcons (Qto,
+                                  Fcons (name,
+                                         make_number (ev->cookie)))),
+                    events);
+  if (ev->mask & IN_ATTRIB)
+    events = Fcons (Fcons (Qattrib, name), events);
+  if (ev->mask & (IN_DELETE|IN_DELETE_SELF) )
+    events = Fcons (Fcons (Qdelete, name), events);
+  if (ev->mask & IN_ACCESS)
+    events = Fcons (Fcons (Qaccess, name), events);
+  if (ev->mask & IN_CLOSE_WRITE)
+    events = Fcons (Fcons (Qclose_write, name), events);
+  if (ev->mask & IN_CLOSE_NOWRITE)
+    events = Fcons (Fcons (Qclose_nowrite, name), events);
+  if (ev->mask & IN_OPEN)
+    events = Fcons (Fcons (Qopen, name), events);
+  return events;
+}
+
+/* This callback is called when the FD is available for read.  The inotify
+   events are read from FD and converted into input_events.  */
+static void
+inotify_callback (int fd, void *_)
+{
+  struct input_event event;
+  int to_read;
+  char *buffer;
+  ssize_t n;
+  size_t i;
+
+  to_read = 0;
+  if (ioctl (fd, FIONREAD,  &to_read) == -1)
+    report_file_error ("Error while trying to retrieve file system events",
+                       Qnil);
+  buffer = xmalloc (to_read);
+  n = read (fd, buffer, to_read);
+  if (n < 0)
+    {
+      xfree (buffer);
+      report_file_error ("Error while trying to read file system events",
+                         Qnil);
+    }
+
+  EVENT_INIT (event);
+  event.kind = FILEWATCH_EVENT;
+  event.arg = Qnil;
+
+  i = 0;
+  while (i < (size_t)n)
+    {
+      struct inotify_event *ev = (struct inotify_event*)&buffer[i];
+
+      Lisp_Object watch_data = get_watch_data_by_watchdesc (ev->wd);
+      if (!NILP (watch_data))
+        {
+          Lisp_Object name, events;
+
+          /* If a directory is watched name contains the name
+             of the file that was changed.  */
+          if (ev->len > 0)
+            {
+              size_t const len = strlen (ev->name);
+              name = make_unibyte_string (ev->name, min (len, ev->len));
+              name = DECODE_FILE (name);
+            }
+          else
+            {
+              name = CADR (watch_data);
+            }
+
+          events = inotifyevent_to_events (name, ev);
+
+          if (!NILP (events))
+            {
+              Lisp_Object x = CADDR (watch_data);
+              while (CONSP (x))
+                {
+                  Lisp_Object cell = XCAR (x);
+                  Lisp_Object aspects = match_aspects (cell, events);
+                  if (!NILP (aspects))
+                    {
+                      Lisp_Object id = list3 (Qfile_watch, make_number 
(ev->wd),
+                                              XCAR (cell));
+                      Lisp_Object event_info = list1 (list3 (id, CADR (cell),
+                                                             aspects));
+
+                      if (NILP (event.arg))
+                        event.arg = event_info;
+                      else
+                        event.arg = append2 (event.arg, event_info);
+                    }
+                  x = XCDR (x);
+                }
+            }
+
+          if (ev->mask & IN_IGNORED)
+            {
+              /* Event was removed automatically: Drop it from data list.  */
+              add_to_log ("File-watch: \"%s\" will be ignored", name, Qnil);
+              watch_list = Fdelete (watch_data, watch_list);
+            }
+          if (ev->mask & IN_Q_OVERFLOW)
+            add_to_log ("File watch: Inotify Queue Overflow!", Qnil, Qnil);
+        }
+
+      i += sizeof (*ev) + ev->len;
+    }
+
+  if (!NILP (event.arg))
+    kbd_buffer_store_event (&event);
+
+  xfree (buffer);
+}
+
+static uint32_t
+symbol_to_inotifymask (Lisp_Object symb, int in_list)
+{
+  if (EQ (symb, Qmodify))
+    return IN_MODIFY | IN_CREATE;
+  else if (EQ (symb, Qmove))
+    return IN_MOVE_SELF | IN_MOVE;
+  else if (EQ (symb, Qattrib))
+    return IN_ATTRIB;
+  else if (EQ (symb, Qdelete))
+    return IN_DELETE_SELF | IN_DELETE;
+  else if (!in_list && EQ (symb, Qall_modify))
+    return IN_MODIFY | IN_CREATE | IN_MOVE_SELF | IN_MOVE | IN_ATTRIB
+      | IN_DELETE_SELF | IN_DELETE;
+  else if (EQ (symb, Qaccess))
+    return IN_ACCESS;
+  else if (EQ (symb, Qclose_write))
+    return IN_CLOSE_WRITE;
+  else if (EQ (symb, Qclose_nowrite))
+    return IN_CLOSE_NOWRITE;
+  else if (EQ (symb, Qopen))
+    return IN_OPEN;
+  else if (!in_list && (EQ (symb, Qt) || EQ (symb, Qall)))
+    return IN_MODIFY | IN_CREATE | IN_MOVE_SELF | IN_MOVE | IN_ATTRIB
+      | IN_DELETE_SELF | IN_DELETE | IN_ACCESS | IN_CLOSE_WRITE
+      | IN_CLOSE_NOWRITE | IN_OPEN;
+  else
+    signal_error ("Unknown aspect", symb);
+}
+
+static uint32_t
+aspect_to_inotifymask (Lisp_Object aspect)
+{
+  if (CONSP (aspect))
+    {
+      Lisp_Object x = aspect;
+      uint32_t mask = 0;
+      while (CONSP (x))
+        {
+          mask |= symbol_to_inotifymask (XCAR (x), 1);
+          x = XCDR (x);
+        }
+      return mask;
+    }
+  else
+    return symbol_to_inotifymask (aspect, 0);
+}
+
+DEFUN ("file-watch", Ffile_watch, Sfile_watch, 3, 3, 0,
+       doc: /* Arrange to call FUNC if ASPECT of FILENAME changes.
+
+ASPECT might be one of the following symbols or a list of those symbols (except
+for all-modify and t):
+
+modify -- notify when a file is modified or created.
+
+move -- notify when a file/directory is moved.
+
+attrib -- notify when attributes change.
+
+delete -- notify when a file/directory is deleted.
+
+all-modify -- notify for all of the above (all modifying changes).
+
+access -- notify when file was accessed/read.
+
+close-write -- notify when a file opened for writing was closed.
+
+close-nowrite -- notify when a file not opened for writing was closed.
+
+open -- notify when a file was opened.
+
+t -- notify for all of the above.
+
+Watching a directory is not recursive.  CALLBACK gets passed two
+arguments ID and EVENT.  The ID argument is the id returned by
+file-watch.  The EVENT argument is a list of events taking the form
+(OP . FILENAME) or (OP DIRECTION NAME COOKIE).  OP is the operation
+that caused the event and FILENAME is the name of the file the
+operation applied to.  If the even belongs to a move operation inside
+a directory then the (OP DIRECTION NAME COOKIE) form is used and
+DIRECTION is either 'from or 'to indicating the direction of the move
+operation.  NAME is the corresponding file name.  COOKIE is an
+unspecified object that can be compared using `eq' to identify the
+matching from and to move operation.
+
+Example:
+(file-watch "foo" t #'(lambda (id event) (message "%s %s" id event)))
+
+Use `file-unwatch' to stop watching.
+
+usage: (file-watch FILENAME ASPECT CALLBACK) */)
+  (Lisp_Object file_name, Lisp_Object aspect, Lisp_Object callback)
+{
+  static int intern_counter = 0; /* assign local ids */
+  uint32_t mask;
+  int watchdesc;
+  Lisp_Object decoded_file_name;
+  Lisp_Object data;
+  Lisp_Object info;
+
+  CHECK_STRING (file_name);
+
+  if (inotifyfd == uninitialized)
+    {
+      inotifyfd = inotify_init1 (IN_NONBLOCK|IN_CLOEXEC);
+      if (inotifyfd == -1)
+        {
+          inotifyfd = uninitialized;
+          report_file_error ("File watching feature (inotify) is not 
available",
+                             Qnil);
+        }
+      watch_list = Qnil;
+      add_read_fd (inotifyfd, &inotify_callback, NULL);
+    }
+
+  mask = aspect_to_inotifymask (aspect) | IN_MASK_ADD;
+  decoded_file_name = ENCODE_FILE (file_name);
+  watchdesc = inotify_add_watch (inotifyfd, SSDATA (decoded_file_name), mask);
+  if (watchdesc == -1)
+    report_file_error ("Could not watch file", Fcons (file_name, Qnil));
+
+  info = list3 (make_number (intern_counter), callback, aspect);
+  ++intern_counter;
+
+  data = get_watch_data_by_watchdesc (watchdesc);
+  if (CONSP (data))
+    XSETCDR (XCDR (data), Fcons (append2 (CADDR (data), Fcons (info, Qnil)),
+                                 Qnil));
+  else
+    watch_list = Fcons (list3 (make_number (watchdesc),
+                               file_name, Fcons (info, Qnil)),
+                        watch_list);
+
+  return list3 (Qfile_watch, make_number (watchdesc), XCAR (info));
+}
+
+static int
+file_watch_objectp (Lisp_Object obj)
+{
+  return CONSP (obj) && XINT (Flength (obj)) == 3
+    && EQ (XCAR (obj), Qfile_watch);
+}
+
+DEFUN ("file-unwatch", Ffile_unwatch, Sfile_unwatch, 1, 1, 0,
+       doc: /* Stop watching a file or directory.  */)
+  (Lisp_Object watch_object)
+{
+  Lisp_Object watch_data;
+  Lisp_Object info;
+
+  if (!file_watch_objectp (watch_object))
+    wrong_type_argument (Qfile_watch, watch_object);
+
+  if (inotifyfd == uninitialized)
+    return Qnil;
+
+  watch_data = Fassoc ( CADR (watch_object), watch_list );
+  if (NILP (watch_data))
+    return Qnil;
+
+  info = Fassoc (CADDR (watch_object), CADDR (watch_data));
+  if (NILP (info))
+    return Qnil;
+
+  XSETCDR (XCDR (watch_data), Fcons (Fdelete (info, CADDR (watch_data)), 
Qnil));
+
+  if (NILP (CADDR (watch_data)))
+    {
+      int const magicno = XINT (CADR (watch_object));
+      watch_list = Fdelete (watch_data, watch_list);
+
+      if (inotify_rm_watch (inotifyfd, magicno) == -1)
+        report_file_error ("Could not unwatch file",
+                           Fcons (XCAR (watch_data), Qnil));
+
+      /* Cleanup if watch_list is empty.  */
+      if (NILP (watch_list))
+        {
+          close (inotifyfd);
+          delete_read_fd (inotifyfd);
+          inotifyfd = uninitialized;
+        }
+    }
+  else
+    {
+      Lisp_Object decoded_file_name = ENCODE_FILE (CADR (watch_data));
+      Lisp_Object x = CADDR (watch_data);
+      uint32_t mask = 0;
+      while (CONSP (x))
+        {
+          Lisp_Object cell = XCAR (x);
+          mask |= aspect_to_inotifymask (CADDR (cell));
+          x = XCDR (x);
+        }
+      /* Reset watch mask */
+      if (inotify_add_watch (inotifyfd, SSDATA (decoded_file_name), mask) == 
-1)
+        report_file_error ("Could not unwatch file",
+                           Fcons (XCAR (watch_data), Qnil));
+    }
+
+  return Qt;
+}
+
+#else /* HAVE_INOTIFY */
+#error "Filewatch defined but no watch mechanism (inotify) available"
+#endif /* HAVE_INOTIFY */
+
+void
+syms_of_filewatch (void)
+{
+  DEFSYM (Qmodify, "modify");
+  DEFSYM (Qmove, "move");
+  DEFSYM (Qattrib, "attrib");
+  DEFSYM (Qdelete, "delete");
+  DEFSYM (Qfrom, "from");
+  DEFSYM (Qto, "to");
+  DEFSYM (Qall_modify, "all-modify");
+
+  DEFSYM (Qaccess, "access");
+  DEFSYM (Qclose_write, "close-write");
+  DEFSYM (Qclose_nowrite, "close-nowrite");
+  DEFSYM (Qopen, "open");
+  DEFSYM (Qall, "all");
+
+  DEFSYM (Qfile_watch, "file-watch");
+
+  defsubr (&Sfile_watch);
+  defsubr (&Sfile_unwatch);
+
+  staticpro (&watch_list);
+
+  Fprovide (intern_c_string ("filewatch"), Qnil);
+}
+
+#endif /* HAVE_FILEWATCH */
diff --git a/src/keyboard.c b/src/keyboard.c
index f3d7df5..f694b13 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -321,6 +321,9 @@ static Lisp_Object Qsave_session;
 #ifdef HAVE_DBUS
 static Lisp_Object Qdbus_event;
 #endif
+#ifdef HAVE_FILEWATCH
+static Lisp_Object Qfilewatch_event;
+#endif /* HAVE_FILEWATCH */
 static Lisp_Object Qconfig_changed_event;
 
 /* Lisp_Object Qmouse_movement; - also an event header */
@@ -4021,6 +4024,13 @@ kbd_buffer_get_event (KBOARD **kbp,
          kbd_fetch_ptr = event + 1;
        }
 #endif
+#ifdef HAVE_FILEWATCH
+      else if (event->kind == FILEWATCH_EVENT)
+        {
+          obj = make_lispy_event (event);
+          kbd_fetch_ptr = event + 1;
+        }
+#endif
       else if (event->kind == CONFIG_CHANGED_EVENT)
        {
          obj = make_lispy_event (event);
@@ -5938,6 +5948,13 @@ make_lispy_event (struct input_event *event)
       }
 #endif /* HAVE_DBUS */
 
+#ifdef HAVE_FILEWATCH
+    case FILEWATCH_EVENT:
+      {
+        return Fcons (Qfilewatch_event, event->arg);
+      }
+#endif /* HAVE_FILEWATCH */
+
     case CONFIG_CHANGED_EVENT:
        return Fcons (Qconfig_changed_event,
                       Fcons (event->arg,
@@ -11408,6 +11425,10 @@ syms_of_keyboard (void)
   DEFSYM (Qdbus_event, "dbus-event");
 #endif
 
+#ifdef HAVE_FILEWATCH
+  DEFSYM (Qfilewatch_event, "filewatch-event");
+#endif /* HAVE_FILEWATCH */
+
   DEFSYM (QCenable, ":enable");
   DEFSYM (QCvisible, ":visible");
   DEFSYM (QChelp, ":help");
@@ -12168,6 +12189,13 @@ keys_of_keyboard (void)
                            "dbus-handle-event");
 #endif
 
+#ifdef HAVE_FILEWATCH
+  /* Define a special event which is raised for filewatch callback
+     functions.  */
+  initial_define_lispy_key (Vspecial_event_map, "filewatch-event",
+                            "filewatch-handle-event");
+#endif /* HAVE_FILEWATCH */
+
   initial_define_lispy_key (Vspecial_event_map, "config-changed-event",
                            "ignore");
 #if defined (WINDOWSNT)
diff --git a/src/lisp.h b/src/lisp.h
index 21ac55c..2f3e8b7 100644
--- a/src/lisp.h
+++ b/src/lisp.h
@@ -3497,6 +3497,11 @@ extern void syms_of_fontset (void);
 extern Lisp_Object Qfont_param;
 #endif
 
+/* Defined in filewatch.c */
+#ifdef HAVE_FILEWATCH
+extern void syms_of_filewatch (void);
+#endif
+
 /* Defined in xfaces.c.  */
 extern Lisp_Object Qdefault, Qtool_bar, Qfringe;
 extern Lisp_Object Qheader_line, Qscroll_bar, Qcursor;
diff --git a/src/termhooks.h b/src/termhooks.h
index f35bd92..ccb5887 100644
--- a/src/termhooks.h
+++ b/src/termhooks.h
@@ -211,6 +211,11 @@ enum event_kind
   , NS_NONKEY_EVENT
 #endif
 
+#ifdef HAVE_FILEWATCH
+  /* File or directory was changed.  */
+  , FILEWATCH_EVENT
+#endif
+
 };
 
 /* If a struct input_event has a kind which is SELECTION_REQUEST_EVENT
diff --git a/test/automated/filewatch-tests.el 
b/test/automated/filewatch-tests.el
new file mode 100644
index 0000000..494d704
--- /dev/null
+++ b/test/automated/filewatch-tests.el
@@ -0,0 +1,50 @@
+;;; filewatch-tests.el --- Test suite for filewatch.
+
+;; Copyright (C) 2011 Free Software Foundation, Inc.
+
+;; Author: Rüdiger Sonderfeld <address@hidden>
+;; Keywords:       internal
+;; Human-Keywords: internal
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Code:
+
+(require 'ert)
+
+(when (featurep 'filewatch)
+
+  (ert-deftest filewatch-file-unwatch-type-check ()
+    "Test whether `file-unwatch' does proper type checking."
+    (should-error (file-unwatch "path") :type 'wrong-type-argument)
+    (should-error (file-unwatch '(file 1 2)) :type 'wrong-type-argument))
+
+  (ert-deftest filewatch-file-watch-aspects-check ()
+    "Test whether `file-watch' properly checks the aspects."
+    (let ((temp-file (make-temp-file "filewatch-aspects")))
+      (should (stringp temp-file))
+      (should-error (file-watch temp-file 'wrong nil)
+                    :type 'unknown-aspect)
+      (should-error (file-watch temp-file '(modify t) nil)
+                    :type 'unknown-aspect)
+      (should-error (file-watch temp-file '(modify Qall-modify) nil)
+                    :type 'unknown-aspect)
+      (should-error (file-watch temp-file '(access wrong modify) nil)
+                    :type 'unknown-aspect)))
+)
+
+(provide 'filewatch-tests)
+;;; filewatch-tests.el ends here.
-- 
1.7.11.3




reply via email to

[Prev in Thread] Current Thread [Next in Thread]