Commit | Line | Data |
---|---|---|
6f011d81 EZ |
1 | /* Filesystem notifications support for GNU Emacs on the Microsoft Windows API. |
2 | Copyright (C) 2012 Free Software Foundation, Inc. | |
3 | ||
4 | This file is part of GNU Emacs. | |
5 | ||
6 | GNU Emacs is free software: you can redistribute it and/or modify | |
7 | it under the terms of the GNU General Public License as published by | |
8 | the Free Software Foundation, either version 3 of the License, or | |
9 | (at your option) any later version. | |
10 | ||
11 | GNU Emacs is distributed in the hope that it will be useful, | |
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | GNU General Public License for more details. | |
15 | ||
16 | You should have received a copy of the GNU General Public License | |
17 | along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. */ | |
18 | ||
19 | #include <stddef.h> | |
20 | #include <errno.h> | |
21 | ||
22 | /* must include CRT headers *before* config.h */ | |
23 | #include <config.h> | |
24 | ||
25 | #include <windows.h> | |
26 | ||
27 | #include "lisp.h" | |
28 | #include "w32term.h" /* for enter_crit/leave_crit and WM_EMACS_FILENOTIFY */ | |
29 | #include "w32heap.h" /* for OS version data */ | |
30 | #include "w32.h" /* for w32_strerror */ | |
31 | #include "coding.h" | |
32 | #include "keyboard.h" | |
33 | #include "frame.h" /* needed by termhooks.h */ | |
34 | #include "termhooks.h" /* for FILE_NOTIFY_EVENT */ | |
35 | ||
36 | struct notification { | |
37 | BYTE *buf; /* buffer for ReadDirectoryChangesW */ | |
38 | OVERLAPPED *io_info; /* the OVERLAPPED structure for async I/O */ | |
39 | BOOL subtree; /* whether to watch subdirectories */ | |
40 | DWORD filter; /* bit mask for events to watch */ | |
41 | char *watchee; /* the file we are interested in */ | |
42 | HANDLE dir; /* handle to the watched directory */ | |
43 | HANDLE thr; /* handle to the thread that watches */ | |
44 | int terminate; /* if non-zero, request for the thread to terminate */ | |
45 | }; | |
46 | ||
47 | /* FIXME: this needs to be changed to support more that one request at | |
48 | a time. */ | |
49 | static struct notification dirwatch; | |
50 | ||
51 | /* Used for communicating notifications to the main thread. */ | |
52 | int notification_buffer_in_use; | |
53 | BYTE file_notifications[16384]; | |
54 | DWORD notifications_size; | |
55 | HANDLE notifications_desc; | |
56 | ||
57 | static Lisp_Object Qfile_name, Qdirectory_name, Qattributes, Qsize; | |
58 | static Lisp_Object Qlast_write_time, Qlast_access_time, Qcreation_time; | |
59 | static Lisp_Object Qsecurity_desc, Qsubtree, watch_list; | |
60 | ||
61 | #if 0 | |
62 | /* FIXME: Debugging code, should be removed eventually. */ | |
63 | const wchar_t * | |
64 | format_file_action (DWORD action) | |
65 | { | |
66 | static const wchar_t *action_str[] = | |
67 | { L"???", L"Added", L"Removed", L"Modified", L"Renamed from", L"Renamed to" }; | |
68 | ||
69 | if (action >= sizeof(action_str)/sizeof(action_str[0])) | |
70 | action = 0; | |
71 | return action_str[action]; | |
72 | } | |
73 | ||
74 | void | |
75 | parse_notifications (BYTE *info, DWORD info_size) | |
76 | { | |
77 | BYTE *p = info; | |
78 | FILE_NOTIFY_INFORMATION *fni = (PFILE_NOTIFY_INFORMATION)p; | |
79 | const DWORD min_size | |
80 | = offsetof (FILE_NOTIFY_INFORMATION, FileName) + sizeof(wchar_t); | |
81 | ||
82 | if (!info_size) | |
83 | { | |
84 | printf ("No info in notifications!\n"); | |
85 | return; | |
86 | } | |
87 | ||
88 | while (info_size >= min_size) | |
89 | { | |
90 | wchar_t *fn = alloca (fni->FileNameLength + sizeof(wchar_t)); | |
91 | const wchar_t *action_str; | |
92 | ||
93 | memcpy (fn, fni->FileName, fni->FileNameLength); | |
94 | fn[fni->FileNameLength/sizeof(wchar_t)] = 0; | |
95 | action_str = format_file_action (fni->Action); | |
96 | wprintf (L"%s: %s\n", action_str, fn); | |
97 | if (!fni->NextEntryOffset) | |
98 | break; | |
99 | p += fni->NextEntryOffset; | |
100 | fni = (PFILE_NOTIFY_INFORMATION)p; | |
101 | info_size -= fni->NextEntryOffset; | |
102 | } | |
103 | } | |
104 | #endif /* debugging code */ | |
105 | ||
106 | /* Signal to the main thread that we have file notifications for it to | |
107 | process. */ | |
108 | static void | |
109 | send_notifications (BYTE *info, DWORD info_size, HANDLE hdir, int *terminate) | |
110 | { | |
111 | int done = 0; | |
112 | FRAME_PTR f = SELECTED_FRAME (); | |
113 | ||
114 | /* Too bad, but PostMessage will not work in non-GUI sessions. | |
115 | FIXME. */ | |
116 | if (!FRAME_W32_P (f)) | |
117 | return; | |
118 | ||
119 | /* A single buffer is used to communicate all notifications to the | |
120 | main thread. Since both the main thread and several watcher | |
121 | threads could be active at the same time, we use a critical area | |
122 | and an "in-use" flag to synchronize them. A watcher thread can | |
123 | only put its notifications in the buffer if it acquires the | |
124 | critical area and finds the "in-use" flag reset. The main thread | |
125 | resets the flag after it is done processing notifications. | |
126 | ||
127 | FIXME: is there a better way of dealing with this? */ | |
128 | while (!done && !*terminate) | |
129 | { | |
130 | enter_crit (); | |
131 | if (!notification_buffer_in_use) | |
132 | { | |
133 | if (info_size) | |
134 | memcpy (file_notifications, info, info_size); | |
135 | notifications_size = info_size; | |
136 | notifications_desc = hdir; | |
137 | /* If PostMessage fails, the message queue is full. If that | |
138 | happens, the last thing they will worry about is file | |
139 | notifications. So we effectively discard the | |
140 | notification in that case. */ | |
141 | if (PostMessage (FRAME_W32_WINDOW (f), WM_EMACS_FILENOTIFY, 0, 0)) | |
142 | notification_buffer_in_use = 1; | |
143 | done = 1; | |
144 | DebPrint (("Announced notifications of %lu bytes\n", info_size)); | |
145 | } | |
146 | leave_crit (); | |
147 | if (!done) | |
148 | Sleep (5); | |
149 | } | |
150 | } | |
151 | ||
152 | /* An APC routine to cancel outstanding directory watch. Invoked by | |
153 | the main thread via QueueUserAPC. This is needed because only the | |
154 | thread that issued the ReadDirectoryChangesW call can call CancelIo | |
155 | to cancel that. (CancelIoEx is only available since Vista, so we | |
156 | cannot use it on XP.) */ | |
157 | VOID CALLBACK | |
158 | watch_end (ULONG_PTR arg) | |
159 | { | |
160 | HANDLE hdir = (HANDLE)arg; | |
161 | ||
162 | if (hdir && hdir != INVALID_HANDLE_VALUE) | |
163 | { | |
164 | CancelIo (hdir); | |
165 | CloseHandle (hdir); | |
166 | } | |
167 | } | |
168 | ||
169 | /* A completion routine (a.k.a. APC function) for handling events read | |
170 | by ReadDirectoryChangesW. Called by the OS when the thread which | |
171 | issued the asynchronous ReadDirectoryChangesW call is in the | |
172 | "alertable state", i.e. waiting inside SleepEx call. */ | |
173 | VOID CALLBACK | |
174 | watch_completion (DWORD status, DWORD bytes_ret, OVERLAPPED *io_info) | |
175 | { | |
176 | struct notification *dirwatch; | |
177 | ||
178 | /* Who knows what happened? Perhaps the OVERLAPPED structure was | |
179 | freed by someone already? In any case, we cannot do anything | |
180 | with this request, so just punt and skip it. FIXME: should we | |
181 | raise the 'terminate' flag in this case? */ | |
182 | if (!io_info) | |
183 | return; | |
184 | ||
185 | /* We have a pointer to our dirwatch structure conveniently stashed | |
186 | away in the hEvent member of the OVERLAPPED struct. According to | |
187 | MSDN documentation of ReadDirectoryChangesW: "The hEvent member | |
188 | of the OVERLAPPED structure is not used by the system, so you can | |
189 | use it yourself." */ | |
190 | dirwatch = (struct notification *)io_info->hEvent; | |
191 | if (status == ERROR_OPERATION_ABORTED) | |
192 | { | |
193 | /* We've been called because the main thread told us to issue | |
194 | CancelIo on the directory we watch, and watch_end did so. | |
195 | The directory handle is already closed. We should clean up | |
196 | and exit, signalling to the thread worker routine not to | |
197 | issue another call to ReadDirectoryChangesW. */ | |
198 | xfree (dirwatch->buf); | |
199 | dirwatch->buf = NULL; | |
200 | xfree (dirwatch->io_info); | |
201 | dirwatch->io_info = NULL; | |
202 | xfree (dirwatch->watchee); | |
203 | dirwatch->watchee = NULL; | |
204 | dirwatch->terminate = 1; | |
205 | } | |
206 | else | |
207 | { | |
208 | #if 0 /* debugging code */ | |
209 | parse_notifications (dirwatch->buf, bytes_ret); | |
210 | #endif | |
211 | /* Tell the main thread we have notifications for it. */ | |
212 | send_notifications (dirwatch->buf, bytes_ret, dirwatch->dir, | |
213 | &dirwatch->terminate); | |
214 | } | |
215 | } | |
216 | ||
217 | /* Worker routine for the watch thread. */ | |
218 | static DWORD WINAPI | |
219 | watch_worker (LPVOID arg) | |
220 | { | |
221 | struct notification *dirwatch = (struct notification *)arg; | |
222 | ||
223 | do { | |
224 | BOOL status; | |
225 | DWORD sleep_result; | |
226 | DWORD bytes_ret = 0; | |
227 | ||
228 | if (dirwatch->dir) | |
229 | { | |
230 | status = ReadDirectoryChangesW (dirwatch->dir, dirwatch->buf, 16384, | |
231 | dirwatch->subtree, dirwatch->filter, | |
232 | &bytes_ret, | |
233 | dirwatch->io_info, watch_completion); | |
234 | if (!status) | |
235 | { | |
236 | DebPrint (("watch_worker(1): %lu\n", GetLastError ())); | |
237 | xfree (dirwatch->buf); | |
238 | dirwatch->buf = NULL; | |
239 | xfree (dirwatch->io_info); | |
240 | dirwatch->io_info = NULL; | |
241 | CloseHandle (dirwatch->dir); | |
242 | dirwatch->dir = NULL; | |
243 | xfree (dirwatch->watchee); | |
244 | dirwatch->watchee = NULL; | |
245 | return 1; | |
246 | } | |
247 | } | |
248 | /* Sleep indefinitely until awoken by the I/O completion, which | |
249 | could be either a change notification or a cancellation of the | |
250 | watch. */ | |
251 | sleep_result = SleepEx (INFINITE, TRUE); | |
252 | if (dirwatch->terminate) | |
253 | DebPrint (("watch_worker: exiting by request\n")); | |
254 | } while (!dirwatch->terminate); | |
255 | ||
256 | DebPrint (("watch_worker(2): %lu\n", GetLastError ())); | |
257 | return 0; | |
258 | } | |
259 | ||
260 | /* Launch a thread to watch changes to FILE in a directory open on | |
261 | handle HDIR. */ | |
262 | static int | |
263 | start_watching (const char *file, HANDLE hdir, BOOL subdirs, DWORD flags) | |
264 | { | |
265 | dirwatch.buf = xmalloc (16384); | |
266 | dirwatch.io_info = xzalloc (sizeof(OVERLAPPED)); | |
267 | /* Stash a pointer to dirwatch structure for use by the completion | |
268 | routine. According to MSDN documentation of ReadDirectoryChangesW: | |
269 | "The hEvent member of the OVERLAPPED structure is not used by the | |
270 | system, so you can use it yourself." */ | |
271 | dirwatch.io_info->hEvent = &dirwatch; | |
272 | dirwatch.subtree = subdirs; | |
273 | dirwatch.filter = flags; | |
274 | dirwatch.watchee = xstrdup (file); | |
275 | dirwatch.terminate = 0; | |
276 | dirwatch.dir = hdir; | |
277 | ||
278 | /* See w32proc.c where it calls CreateThread for the story behind | |
279 | the 2nd and 5th argument in the call to CreateThread. */ | |
280 | dirwatch.thr = CreateThread (NULL, 64 * 1024, watch_worker, | |
281 | (void *)&dirwatch, 0x00010000, NULL); | |
282 | ||
283 | if (!dirwatch.thr) | |
284 | { | |
285 | dirwatch.terminate = 1; | |
286 | xfree (dirwatch.buf); | |
287 | dirwatch.buf = NULL; | |
288 | xfree (dirwatch.io_info); | |
289 | dirwatch.io_info = NULL; | |
290 | xfree (dirwatch.watchee); | |
291 | dirwatch.watchee = NULL; | |
292 | return -1; | |
293 | } | |
294 | return 0; | |
295 | } | |
296 | ||
297 | /* Called from the main thread to start watching FILE in PARENT_DIR, | |
298 | subject to FLAGS. If SUBDIRS is TRUE, watch the subdirectories of | |
299 | PARENT_DIR as well. Value is the handle on which the directory is | |
300 | open. */ | |
301 | static HANDLE * | |
302 | add_watch (const char *parent_dir, const char *file, BOOL subdirs, DWORD flags) | |
303 | { | |
304 | HANDLE hdir; | |
305 | ||
306 | if (!file || !*file) | |
307 | return NULL; | |
308 | ||
309 | hdir = CreateFile (parent_dir, | |
310 | FILE_LIST_DIRECTORY, | |
311 | /* FILE_SHARE_DELETE doesn't preclude other | |
312 | processes from deleting files inside | |
313 | parent_dir. */ | |
314 | FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, | |
315 | NULL, OPEN_EXISTING, | |
316 | FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, | |
317 | NULL); | |
318 | if (hdir == INVALID_HANDLE_VALUE) | |
319 | return NULL; | |
320 | ||
321 | if (start_watching (file, hdir, subdirs, flags) == 0) | |
322 | return hdir; | |
323 | ||
324 | CloseHandle (hdir); | |
325 | return NULL; | |
326 | } | |
327 | ||
328 | /* Stop watching a directory specified by its handle HDIR. */ | |
329 | static int | |
330 | remove_watch (HANDLE hdir) | |
331 | { | |
332 | if (hdir == dirwatch.dir) | |
333 | { | |
334 | int i; | |
335 | BOOL status; | |
336 | DWORD exit_code, err; | |
337 | ||
338 | /* Only the thread that issued the outstanding I/O call can call | |
339 | CancelIo on it. (CancelIoEx is available only since Vista.) | |
340 | So we need to queue an APC for the worker thread telling it | |
341 | to terminate. */ | |
342 | if (!QueueUserAPC (watch_end, dirwatch.thr, (ULONG_PTR)dirwatch.dir)) | |
343 | DebPrint (("QueueUserAPC failed (%lu)!\n", GetLastError ())); | |
344 | /* We also set the terminate flag, for when the thread is | |
345 | waiting on the critical section that never gets acquired. | |
346 | FIXME: is there a cleaner method? Using SleepEx there is a | |
347 | no-no, as that will lead to recursive APC invocations and | |
348 | stack overflow. */ | |
349 | dirwatch.terminate = 1; | |
350 | /* Wait for the thread to exit. FIXME: is there a better method | |
351 | that is not overly complex? */ | |
352 | for (i = 0; i < 50; i++) | |
353 | { | |
354 | if (!((status = GetExitCodeThread (dirwatch.thr, &exit_code)) | |
355 | && exit_code == STILL_ACTIVE)) | |
356 | break; | |
357 | Sleep (10); | |
358 | } | |
359 | if ((status == FALSE && (err = GetLastError ()) == ERROR_INVALID_HANDLE) | |
360 | || exit_code == STILL_ACTIVE) | |
361 | { | |
362 | if (!(status == FALSE && err == ERROR_INVALID_HANDLE)) | |
363 | TerminateThread (dirwatch.thr, 0); | |
364 | } | |
365 | ||
366 | /* Clean up. */ | |
367 | if (dirwatch.thr) | |
368 | { | |
369 | CloseHandle (dirwatch.thr); | |
370 | dirwatch.thr = NULL; | |
371 | } | |
372 | return 0; | |
373 | } | |
374 | else if (!dirwatch.dir) | |
375 | { | |
376 | DebPrint (("Directory handle already closed!\n")); | |
377 | return 0; | |
378 | } | |
379 | else | |
380 | { | |
381 | DebPrint (("Unknown directory handle!\n")); | |
382 | return -1; | |
383 | } | |
384 | } | |
385 | ||
386 | static DWORD | |
387 | filter_list_to_flags (Lisp_Object filter_list) | |
388 | { | |
389 | DWORD flags = 0; | |
390 | ||
391 | if (NILP (filter_list)) | |
392 | return flags; | |
393 | ||
394 | if (!NILP (Fmember (Qfile_name, filter_list))) | |
395 | flags |= FILE_NOTIFY_CHANGE_FILE_NAME; | |
396 | if (!NILP (Fmember (Qdirectory_name, filter_list))) | |
397 | flags |= FILE_NOTIFY_CHANGE_DIR_NAME; | |
398 | if (!NILP (Fmember (Qattributes, filter_list))) | |
399 | flags |= FILE_NOTIFY_CHANGE_ATTRIBUTES; | |
400 | if (!NILP (Fmember (Qsize, filter_list))) | |
401 | flags |= FILE_NOTIFY_CHANGE_SIZE; | |
402 | if (!NILP (Fmember (Qlast_write_time, filter_list))) | |
403 | flags |= FILE_NOTIFY_CHANGE_LAST_WRITE; | |
404 | if (!NILP (Fmember (Qlast_access_time, filter_list))) | |
405 | flags |= FILE_NOTIFY_CHANGE_LAST_ACCESS; | |
406 | if (!NILP (Fmember (Qcreation_time, filter_list))) | |
407 | flags |= FILE_NOTIFY_CHANGE_CREATION; | |
408 | if (!NILP (Fmember (Qsecurity_desc, filter_list))) | |
409 | flags |= FILE_NOTIFY_CHANGE_SECURITY; | |
410 | ||
411 | return flags; | |
412 | } | |
413 | ||
414 | DEFUN ("w32notify-add-watch", Fw32notify_add_watch, | |
415 | Sw32notify_add_watch, 3, 3, 0, | |
416 | doc: /* Add a watch for filesystem events pertaining to FILE. | |
417 | ||
418 | This arranges for filesystem events pertaining to FILE to be reported | |
419 | to Emacs. Use `w32notify-rm-watch' to cancel the watch. | |
420 | ||
421 | Value is a descriptor for the added watch, or nil if the file | |
422 | cannot be watched. | |
423 | ||
424 | FILTER is a list of conditions for reporting an event. It can include | |
425 | the following symbols: | |
426 | ||
427 | 'file-name' -- report file creation, deletion, or renaming | |
428 | 'directory-name' -- report directory creation, deletion, or renaming | |
429 | 'attributes' -- report changes in attributes | |
430 | 'size' -- report changes in file-size | |
431 | 'last-write-time' -- report changes in last-write time | |
432 | 'last-access-time' -- report changes in last-access time | |
433 | 'creation-time' -- report changes in creation time | |
434 | 'security-desc' -- report changes in security descriptor | |
435 | ||
436 | If FILE is a directory, and FILTER includes 'subtree', then all the | |
437 | subdirectories will also be watched and changes in them reported. | |
438 | ||
439 | When any event happens that satisfies the conditions specified by | |
440 | FILTER, Emacs will call the CALLBACK function passing it a single | |
441 | argument EVENT, which is of the form | |
442 | ||
443 | (DESCRIPTOR ACTION FILE) | |
444 | ||
445 | DESCRIPTOR is the same object as the one returned by this function. | |
446 | ACTION is the description of the event. It could be any one of the | |
447 | following: | |
448 | ||
449 | 'added' -- FILE was added | |
450 | 'removed' -- FILE was deleted | |
451 | 'modified' -- FILE's contents or its attributes were modified | |
452 | 'renamed-from' -- a file was renamed whose old name was FILE | |
453 | 'renamed-to' -- a file was renamed and its new name is FILE | |
454 | ||
455 | FILE is the name of the file whose event is being reported. */) | |
456 | (Lisp_Object file, Lisp_Object filter, Lisp_Object callback) | |
457 | { | |
458 | Lisp_Object encoded_file, watch_object, watch_descriptor; | |
459 | char parent_dir[MAX_PATH], *basename; | |
460 | size_t fn_len; | |
461 | HANDLE hdir; | |
462 | DWORD flags; | |
463 | BOOL subdirs = FALSE; | |
464 | Lisp_Object lisp_errstr; | |
465 | char *errstr; | |
466 | ||
467 | CHECK_LIST (filter); | |
468 | ||
469 | /* The underlying features are available only since XP. */ | |
470 | if (os_subtype == OS_9X | |
471 | || (w32_major_version == 5 && w32_major_version < 1)) | |
472 | { | |
473 | errno = ENOSYS; | |
474 | report_file_error ("Watching filesystem events is not supported", | |
475 | Qnil); | |
476 | } | |
477 | ||
478 | /* We needa full absolute file name of FILE, and we need to remove | |
479 | any trailing slashes from it, so that GetFullPathName below gets | |
480 | the basename part correctly. */ | |
481 | file = Fdirectory_file_name (Fexpand_file_name (file, Qnil)); | |
482 | encoded_file = ENCODE_FILE (file); | |
483 | ||
484 | fn_len = GetFullPathName (SDATA (encoded_file), MAX_PATH, parent_dir, | |
485 | &basename); | |
486 | if (!fn_len) | |
487 | { | |
488 | errstr = w32_strerror (0); | |
489 | errno = EINVAL; | |
490 | if (!NILP (Vlocale_coding_system)) | |
491 | lisp_errstr | |
492 | = code_convert_string_norecord (build_unibyte_string (errstr), | |
493 | Vlocale_coding_system, 0); | |
494 | else | |
495 | lisp_errstr = build_string (errstr); | |
496 | report_file_error ("GetFullPathName failed", | |
497 | Fcons (lisp_errstr, Fcons (file, Qnil))); | |
498 | } | |
499 | /* We need the parent directory without the slash that follows it. | |
500 | If BASENAME is NULL, the argument was the root directory on its | |
501 | drive. */ | |
502 | if (basename) | |
503 | basename[-1] = '\0'; | |
504 | else | |
505 | subdirs = TRUE; | |
506 | ||
507 | if (!NILP (Fmember (Qsubtree, filter))) | |
508 | subdirs = TRUE; | |
509 | ||
510 | flags = filter_list_to_flags (filter); | |
511 | ||
512 | hdir = add_watch (parent_dir, basename, subdirs, flags); | |
513 | if (!hdir) | |
514 | { | |
515 | DWORD err = GetLastError (); | |
516 | ||
517 | errno = EINVAL; | |
518 | if (err) | |
519 | { | |
520 | errstr = w32_strerror (err); | |
521 | if (!NILP (Vlocale_coding_system)) | |
522 | lisp_errstr | |
523 | = code_convert_string_norecord (build_unibyte_string (errstr), | |
524 | Vlocale_coding_system, 0); | |
525 | else | |
526 | lisp_errstr = build_string (errstr); | |
527 | report_file_error ("Cannot watch file", | |
528 | Fcons (lisp_errstr, Fcons (file, Qnil))); | |
529 | } | |
530 | else | |
531 | report_file_error ("Cannot watch file", Fcons (file, Qnil)); | |
532 | } | |
533 | /* Store watch object in watch list. */ | |
534 | watch_descriptor = make_number (hdir); | |
535 | watch_object = Fcons (watch_descriptor, callback); | |
536 | watch_list = Fcons (watch_object, watch_list); | |
537 | ||
538 | return watch_descriptor; | |
539 | } | |
540 | ||
541 | DEFUN ("w32notify-rm-watch", Fw32notify_rm_watch, | |
542 | Sw32notify_rm_watch, 1, 1, 0, | |
543 | doc: /* Remove an existing watch specified by its WATCH-DESCRIPTOR. | |
544 | ||
545 | WATCH-DESCRIPTOR should be an object returned by `w32notify-add-watch'. */) | |
546 | (Lisp_Object watch_descriptor) | |
547 | { | |
548 | Lisp_Object watch_object; | |
549 | HANDLE hdir = (HANDLE)XINT (watch_descriptor); | |
550 | ||
551 | if (remove_watch (hdir) == -1) | |
552 | report_file_error ("Could not remove watch", Fcons (watch_descriptor, | |
553 | Qnil)); | |
554 | ||
555 | /* Remove watch descriptor from watch list. */ | |
556 | watch_object = Fassoc (watch_descriptor, watch_list); | |
557 | if (!NILP (watch_object)) | |
558 | watch_list = Fdelete (watch_object, watch_list); | |
559 | ||
560 | return Qnil; | |
561 | } | |
562 | ||
563 | Lisp_Object | |
564 | get_watch_object (Lisp_Object desc) | |
565 | { | |
566 | return Fassoc (desc, watch_list); | |
567 | } | |
568 | ||
569 | void | |
570 | syms_of_w32notify (void) | |
571 | { | |
572 | DEFSYM (Qfile_name, "file-name"); | |
573 | DEFSYM (Qdirectory_name, "directory-name"); | |
574 | DEFSYM (Qattributes, "attributes"); | |
575 | DEFSYM (Qsize, "size"); | |
576 | DEFSYM (Qlast_write_time, "last-write-time"); | |
577 | DEFSYM (Qlast_access_time, "last-access-time"); | |
578 | DEFSYM (Qcreation_time, "creation-time"); | |
579 | DEFSYM (Qsecurity_desc, "security-desc"); | |
580 | DEFSYM (Qsubtree, "subtree"); | |
581 | ||
582 | defsubr (&Sw32notify_add_watch); | |
583 | defsubr (&Sw32notify_rm_watch); | |
584 | ||
585 | staticpro (&watch_list); | |
586 | ||
587 | Fprovide (intern_c_string ("w32notify"), Qnil); | |
588 | } |