a489f435e7
[INTERPRETER] * NEW: Add an API that returns the first element of a hash table. [GB.NOTIFY] * BUG: Don't crash anymore when releasing watches.
697 lines
15 KiB
C
697 lines
15 KiB
C
/*
|
|
* c_watch.c - Watch and .Watch.Events classes
|
|
*
|
|
* Copyright (C) 2013, 2014 Tobias Boege <tobias@gambas-buch.de>
|
|
*
|
|
* This program 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 1, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This program 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 this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
* MA 02110-1301, USA.
|
|
*/
|
|
|
|
#define __C_WATCH_C
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <sys/inotify.h>
|
|
#include <limits.h>
|
|
|
|
#include "main.h"
|
|
#include "c_watch.h"
|
|
|
|
//#define DEBUG_ME 1
|
|
|
|
#define GB_ErrorErrno() GB.Error(strerror(errno))
|
|
|
|
enum { EV_READ, EV_STAT, EV_CLOSE, EV_CREATE, EV_DELETE, EV_WRITE, EV_MOVE, EV_MOVED_FROM, EV_MOVED_TO, EV_OPEN, NUM_EV };
|
|
|
|
typedef
|
|
struct cinfo CINFO;
|
|
|
|
struct cinfo {
|
|
struct inotify_event *iev;
|
|
CINFO *prev;
|
|
};
|
|
|
|
typedef
|
|
struct {
|
|
int fd;
|
|
GB_HASHTABLE watches;
|
|
CINFO *top;
|
|
}
|
|
CINOTIFY;
|
|
|
|
typedef
|
|
struct {
|
|
CWATCH *root;
|
|
char *path;
|
|
int wd;
|
|
int events[NUM_EV];
|
|
}
|
|
WATCH_LIST;
|
|
|
|
typedef
|
|
struct {
|
|
int *eventp;
|
|
uint32_t mask;
|
|
}
|
|
EVENT_TABLE;
|
|
|
|
/* XXX: We currently only support one inotify instance */
|
|
static CINOTIFY _ino = {-1, NULL, NULL};
|
|
|
|
DECLARE_EVENT(EVENT_Read);
|
|
DECLARE_EVENT(EVENT_Stat);
|
|
DECLARE_EVENT(EVENT_Close);
|
|
DECLARE_EVENT(EVENT_Create);
|
|
DECLARE_EVENT(EVENT_Delete);
|
|
DECLARE_EVENT(EVENT_Write);
|
|
DECLARE_EVENT(EVENT_Move);
|
|
DECLARE_EVENT(EVENT_MoveFrom);
|
|
DECLARE_EVENT(EVENT_MoveTo);
|
|
DECLARE_EVENT(EVENT_Open);
|
|
|
|
static EVENT_TABLE _event_table[] =
|
|
{
|
|
{&EVENT_Read, IN_ACCESS},
|
|
{&EVENT_Stat, IN_ATTRIB},
|
|
{&EVENT_Close, IN_CLOSE_WRITE | IN_CLOSE_NOWRITE},
|
|
{&EVENT_Create, IN_CREATE},
|
|
{&EVENT_Delete, IN_DELETE | IN_DELETE_SELF},
|
|
{&EVENT_Write, IN_MODIFY},
|
|
{&EVENT_Move, IN_MOVE_SELF},
|
|
{&EVENT_MoveFrom, IN_MOVED_FROM},
|
|
{&EVENT_MoveTo, IN_MOVED_TO},
|
|
{&EVENT_Open, IN_OPEN}
|
|
};
|
|
|
|
static bool destroy_watch(CWATCH *watch);
|
|
static void callback(int fd, int flags, CINOTIFY *ino);
|
|
|
|
static void init_inotify(void)
|
|
{
|
|
if (_ino.fd >= 0)
|
|
return;
|
|
|
|
#if DEBUG_ME
|
|
fprintf(stderr, "init_inotify\n");
|
|
#endif
|
|
|
|
_ino.fd = inotify_init();
|
|
if (_ino.fd == -1) {
|
|
GB_ErrorErrno();
|
|
return;
|
|
}
|
|
GB.HashTable.New(&_ino.watches, GB_COMP_BINARY);
|
|
GB.Watch(_ino.fd, GB_WATCH_READ, callback, (intptr_t) &_ino);
|
|
_ino.top = NULL;
|
|
}
|
|
|
|
static void destroy_watch_list(WATCH_LIST *list)
|
|
{
|
|
while (!destroy_watch(list->root));
|
|
}
|
|
|
|
static void exit_inotify(void)
|
|
{
|
|
WATCH_LIST *list;
|
|
int fd = _ino.fd; // prevent a recursion
|
|
|
|
if (_ino.fd < 0)
|
|
return;
|
|
|
|
_ino.fd = -1;
|
|
|
|
#if DEBUG_ME
|
|
fprintf(stderr, "exit_inotify\n");
|
|
#endif
|
|
|
|
while (!GB.HashTable.First(_ino.watches, (void **)&list))
|
|
destroy_watch_list(list);
|
|
|
|
GB.Watch(fd, GB_WATCH_NONE, NULL, 0);
|
|
close(fd);
|
|
GB.HashTable.Free(&_ino.watches);
|
|
}
|
|
|
|
static WATCH_LIST *find_watch_list(CINOTIFY *ino, int wd)
|
|
{
|
|
WATCH_LIST *list = NULL;
|
|
GB.HashTable.Get(ino->watches, (char *)&wd, sizeof(wd), (void **)&list);
|
|
return list;
|
|
}
|
|
|
|
static WATCH_LIST *find_watch_list_from_path(CINOTIFY *ino, const char *path, int len, bool create)
|
|
{
|
|
WATCH_LIST *list = NULL;
|
|
GB.HashTable.Get(ino->watches, path, len, (void **)&list);
|
|
|
|
if (!list && create)
|
|
{
|
|
#if DEBUG_ME
|
|
fprintf(stderr, "find_watch_list_from_path: create watch list for %.*s\n", len, path);
|
|
#endif
|
|
GB.AllocZero(POINTER(&list), sizeof(WATCH_LIST));
|
|
list->wd = -1;
|
|
list->path = GB.NewString(path, len);
|
|
GB.HashTable.Add(ino->watches, path, len, list);
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
static void update_watch_list(WATCH_LIST *list)
|
|
{
|
|
int i;
|
|
uint32_t mask = 0;
|
|
|
|
for (i = 0; i < NUM_EV; i++)
|
|
{
|
|
if (list->events[i])
|
|
mask |= _event_table[i].mask;
|
|
}
|
|
|
|
if (mask == 0)
|
|
{
|
|
if (list->wd >= 0)
|
|
{
|
|
#if DEBUG_ME
|
|
fprintf(stderr, "update_watch_list: remove watch %d\n", list->wd);
|
|
#endif
|
|
GB.HashTable.Remove(_ino.watches, (char *)&list->wd, sizeof(list->wd));
|
|
inotify_rm_watch(_ino.fd, list->wd);
|
|
list->wd = -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int wd = inotify_add_watch(_ino.fd, list->path, mask);
|
|
|
|
if (wd >= 0 && list->wd != wd)
|
|
{
|
|
#if DEBUG_ME
|
|
fprintf(stderr, "update_watch_list: add watch %d %08X\n", wd, mask);
|
|
#endif
|
|
list->wd = wd;
|
|
GB.HashTable.Add(_ino.watches, (char *)&wd, sizeof(wd), list);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void create_watch(CWATCH *watch, const char *path, int len)
|
|
{
|
|
int i;
|
|
WATCH_LIST *list = find_watch_list_from_path(&_ino, path, len, TRUE);
|
|
|
|
watch->root = list;
|
|
|
|
#if DEBUG_ME
|
|
fprintf(stderr, "create_watch: %p %.*s\n", watch, len, path);
|
|
#endif
|
|
|
|
LIST_insert(&list->root, watch, &watch->list);
|
|
|
|
for (i = 0; i < NUM_EV; i++)
|
|
{
|
|
if (watch->events & (1 << i))
|
|
list->events[i]++;
|
|
}
|
|
|
|
update_watch_list(list);
|
|
}
|
|
|
|
static bool destroy_watch(CWATCH *watch)
|
|
{
|
|
int i;
|
|
WATCH_LIST *list = (WATCH_LIST *)watch->root;
|
|
|
|
if (!list)
|
|
return FALSE;
|
|
|
|
watch->root = NULL;
|
|
|
|
#if DEBUG_ME
|
|
fprintf(stderr, "destroy_watch: %p %s\n", watch, list->path);
|
|
#endif
|
|
|
|
GB.StoreVariant(NULL, &watch->tag);
|
|
LIST_remove(&list->root, watch, &watch->list);
|
|
|
|
for (i = 0; i < NUM_EV; i++)
|
|
{
|
|
if (watch->events & (1 << i))
|
|
list->events[i]--;
|
|
}
|
|
|
|
update_watch_list(list);
|
|
|
|
if (list->root == NULL)
|
|
{
|
|
GB.HashTable.Remove(_ino.watches, list->path, GB.StringLength(list->path));
|
|
|
|
#if DEBUG_ME
|
|
fprintf(stderr, "destroy_watch: remove watch list: %s -> %d\n", list->path, GB.HashTable.Count(_ino.watches));
|
|
#endif
|
|
|
|
GB.FreeString(&list->path);
|
|
GB.Free(POINTER(&list));
|
|
|
|
if (GB.HashTable.Count(_ino.watches) == 0)
|
|
exit_inotify();
|
|
|
|
return TRUE;
|
|
}
|
|
else
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
static void pause_watch(CWATCH *watch)
|
|
{
|
|
int i;
|
|
WATCH_LIST *list = (WATCH_LIST *)watch->root;
|
|
|
|
if (watch->paused)
|
|
return;
|
|
|
|
watch->paused = TRUE;
|
|
watch->save_events = watch->events;
|
|
|
|
for (i = 0; i < NUM_EV; i++)
|
|
{
|
|
if (watch->events & (1 << i))
|
|
list->events[i]--;
|
|
}
|
|
|
|
watch->events = 0;
|
|
|
|
update_watch_list(list);
|
|
}
|
|
|
|
static void resume_watch(CWATCH *watch)
|
|
{
|
|
int i;
|
|
WATCH_LIST *list = (WATCH_LIST *)watch->root;
|
|
|
|
if (!watch->paused)
|
|
return;
|
|
|
|
watch->paused = FALSE;
|
|
watch->events = watch->save_events;
|
|
|
|
for (i = 0; i < NUM_EV; i++)
|
|
{
|
|
if (watch->events & (1 << i))
|
|
list->events[i]++;
|
|
}
|
|
|
|
watch->save_events = 0;
|
|
|
|
update_watch_list(list);
|
|
}
|
|
|
|
static void callback(int fd, int flags, CINOTIFY *ino)
|
|
{
|
|
struct inotify_event *iev;
|
|
int i, j, length;
|
|
int event;
|
|
CINFO info;
|
|
WATCH_LIST *list;
|
|
CWATCH *watch;
|
|
uint32_t mask;
|
|
char buf[sizeof(*iev) + NAME_MAX + 1] __attribute__((aligned(sizeof(int))));
|
|
|
|
for(;;)
|
|
{
|
|
if ((length = read(fd, buf, sizeof(buf))) <= 0)
|
|
{
|
|
if (errno == EINTR)
|
|
continue;
|
|
|
|
GB_ErrorErrno();
|
|
return;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
for (i = 0; i < length; i += sizeof(*iev) + iev->len)
|
|
{
|
|
|
|
iev = (struct inotify_event *) &buf[i];
|
|
|
|
list = find_watch_list(ino, iev->wd);
|
|
|
|
if (!list && !(iev->mask & IN_Q_OVERFLOW)) {
|
|
if (getenv("GB_INOTIFY_DEBUG"))
|
|
fprintf(stderr, "gb.inotify: descriptor %d not known. Name was `%s'\n", iev->wd, iev->name);
|
|
continue;
|
|
}
|
|
|
|
mask = iev->mask & ~(IN_IGNORED | IN_ISDIR | IN_Q_OVERFLOW | IN_UNMOUNT);
|
|
|
|
LIST_for_each(watch, list->root)
|
|
{
|
|
if (watch->paused)
|
|
continue;
|
|
|
|
for (j = 0; j < NUM_EV; j++)
|
|
{
|
|
event = *_event_table[j].eventp;
|
|
if (_event_table[j].mask & mask && GB.CanRaise(watch, event))
|
|
{
|
|
/*
|
|
* In case, the event loop kicks in at GB.Raise()
|
|
* and preempts this callback with itself.
|
|
*
|
|
* The problem is then that the event info structure
|
|
* may get replaced by another, thus we lose data.
|
|
* That's why we save it here. However, it is
|
|
* sufficient to keep the CINFO on the stack!
|
|
*/
|
|
info.iev = iev;
|
|
info.prev = _ino.top;
|
|
_ino.top = &info;
|
|
|
|
GB.Raise(watch, event, 0);
|
|
|
|
_ino.top = info.prev;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Also remove the watch if the kernel did. */
|
|
/*if (iev->mask & IN_IGNORED)
|
|
destroy_watch(watch);*/
|
|
}
|
|
}
|
|
|
|
/**G
|
|
* Tidy up.
|
|
**/
|
|
BEGIN_METHOD_VOID(Watch_exit)
|
|
|
|
/* Free the remaining objects. destroy_watch() will then also take
|
|
* care of calling INOTIFY_exit() to free bookkeeping data. */
|
|
exit_inotify();
|
|
|
|
END_METHOD
|
|
|
|
#if 0
|
|
/**G
|
|
* Return a Watch from its path. If the path is not watched, Null is
|
|
* returned.
|
|
**/
|
|
BEGIN_METHOD(Watch_get, GB_STRING path)
|
|
|
|
CWATCH *watch;
|
|
|
|
watch = find_watch_path(&_ino, GB.ToZeroString(ARG(path)));
|
|
if (!watch) {
|
|
GB.ReturnNull();
|
|
return;
|
|
}
|
|
GB.ReturnObject(watch);
|
|
|
|
END_METHOD
|
|
#endif
|
|
|
|
/**G
|
|
* The name of the file or directory which is subject to the event, relative
|
|
* to the Watch object which triggered the event. If Null, the directory
|
|
* itself is subject.
|
|
**/
|
|
BEGIN_PROPERTY(Watch_Name)
|
|
|
|
struct inotify_event *iev = _ino.top->iev;
|
|
|
|
GB.ReturnNewZeroString(iev->len ? iev->name : "");
|
|
|
|
END_PROPERTY
|
|
|
|
/**G
|
|
* Return whether the event subject is/was a directory.
|
|
**/
|
|
BEGIN_PROPERTY(Watch_IsDir)
|
|
|
|
GB.ReturnBoolean(!!(_ino.top->iev->mask & IN_ISDIR));
|
|
|
|
END_PROPERTY
|
|
|
|
/**G
|
|
* Return whether the filesystem on which the watched path resided, was
|
|
* unmounted. In this case, the Watch object is invalidated just after the
|
|
* event.
|
|
**/
|
|
BEGIN_PROPERTY(Watch_Unmount)
|
|
|
|
GB.ReturnBoolean(!!(_ino.top->iev->mask & IN_UNMOUNT));
|
|
|
|
END_PROPERTY
|
|
|
|
/**G
|
|
* A cookie used to associate events. This is currently only used to connect
|
|
* MoveFrom and MoveTo events of the same file.
|
|
**/
|
|
BEGIN_PROPERTY(Watch_Cookie)
|
|
|
|
GB.ReturnInteger(_ino.top->iev->cookie);
|
|
|
|
END_PROPERTY
|
|
|
|
#define THIS ((CWATCH *) _object)
|
|
|
|
/**G
|
|
* Create a new watch for the given path.
|
|
*
|
|
* If the NoFollowLink parameter is set and true, symlinks are not followed.
|
|
*
|
|
* Events is a bitmask specifying which events are to be reported at all.
|
|
* The default mask is determined by the event handlers defined for this
|
|
* object.
|
|
*
|
|
* TODO: Maybe add support for IN_EXCL_UNLINK, IN_ONESHOT and IN_ONLYDIR.
|
|
**/
|
|
BEGIN_METHOD(Watch_new, GB_STRING path; GB_BOOLEAN nofollow; GB_INTEGER events)
|
|
|
|
int i;
|
|
ushort events = VARGOPT(events, 0);
|
|
|
|
if (LENGTH(path) == 0)
|
|
{
|
|
GB.Error("Null path");
|
|
return;
|
|
}
|
|
|
|
//fprintf(stderr, "Watch_new: %p\n", THIS);
|
|
|
|
/* If this is the first watch, we need an inotify instance first.
|
|
* We don't use the component's init function to set up _ino because
|
|
* the inotify instance needs a GB.Watch() which would keep the
|
|
* program open even if no watches were registered. So this is done
|
|
* ad-hoc if there are watches. */
|
|
init_inotify();
|
|
|
|
/* Get the mask */
|
|
if (!events)
|
|
{
|
|
for (i = 0; i < NUM_EV; i++)
|
|
{
|
|
if (GB.CanRaise(THIS, *_event_table[i].eventp))
|
|
events |= (1 << i); //_event_table[i].mask;
|
|
}
|
|
/* But we need at least to watch one event. IN_DELETE_SELF
|
|
* seems a good one as it doesn't trigger too often :-) */
|
|
//mask |= IN_DELETE_SELF;
|
|
}
|
|
|
|
THIS->events = events;
|
|
THIS->nofollow = VARGOPT(nofollow, FALSE);
|
|
THIS->tag.type = GB_T_NULL;
|
|
|
|
create_watch(THIS, STRING(path), LENGTH(path));
|
|
|
|
END_METHOD
|
|
|
|
/**G
|
|
* Deallocate the watch.
|
|
**/
|
|
BEGIN_METHOD_VOID(Watch_free)
|
|
|
|
//fprintf(stderr, "Watch_free: %p\n", THIS);
|
|
|
|
destroy_watch(THIS);
|
|
|
|
END_METHOD
|
|
|
|
BEGIN_METHOD_VOID(Watch_Resume)
|
|
|
|
resume_watch(THIS);
|
|
|
|
END_METHOD
|
|
|
|
BEGIN_METHOD_VOID(Watch_Pause)
|
|
|
|
pause_watch(THIS);
|
|
|
|
END_METHOD
|
|
|
|
/**G
|
|
* Return the watched path.
|
|
**/
|
|
BEGIN_PROPERTY(Watch_Path)
|
|
|
|
GB.ReturnString(((WATCH_LIST *)THIS->root)->path);
|
|
|
|
END_PROPERTY
|
|
|
|
/**G
|
|
* Return or set if the watch is paused.
|
|
*
|
|
* {seealso
|
|
* Pause(), Resume()
|
|
* }
|
|
**/
|
|
BEGIN_PROPERTY(Watch_IsPaused)
|
|
|
|
if (READ_PROPERTY)
|
|
GB.ReturnBoolean(THIS->paused);
|
|
else if (THIS->paused != VPROP(GB_BOOLEAN))
|
|
{
|
|
if (VPROP(GB_BOOLEAN))
|
|
pause_watch(THIS);
|
|
else
|
|
resume_watch(THIS);
|
|
}
|
|
|
|
END_PROPERTY
|
|
|
|
/**G
|
|
* This Variant is free for use by the Gambas programmer.
|
|
**/
|
|
BEGIN_PROPERTY(Watch_Tag)
|
|
|
|
if (READ_PROPERTY) {
|
|
GB.ReturnVariant(&THIS->tag);
|
|
return;
|
|
}
|
|
GB.StoreVariant(PROP(GB_VARIANT), &THIS->tag);
|
|
|
|
END_PROPERTY
|
|
|
|
GB_DESC CWatch[] =
|
|
{
|
|
GB_DECLARE("Watch", sizeof(CWATCH)),
|
|
|
|
GB_CONSTANT("Read", "i", 1 << EV_READ),
|
|
GB_CONSTANT("Stat", "i", 1 << EV_STAT),
|
|
GB_CONSTANT("Close", "i", 1 << EV_CLOSE),
|
|
GB_CONSTANT("Create", "i", 1 << EV_CREATE),
|
|
GB_CONSTANT("Delete", "i", 1 << EV_DELETE),
|
|
GB_CONSTANT("Write", "i", 1 << EV_WRITE),
|
|
GB_CONSTANT("Move", "i", 1 << EV_MOVE),
|
|
GB_CONSTANT("MoveFrom", "i", 1 << EV_MOVED_FROM),
|
|
GB_CONSTANT("MoveTo", "i", 1 << EV_MOVED_TO),
|
|
GB_CONSTANT("Open", "i", 1 << EV_OPEN),
|
|
GB_CONSTANT("All", "i", -1),
|
|
|
|
GB_EVENT("Read", NULL, NULL, &EVENT_Read),
|
|
GB_EVENT("Stat", NULL, NULL, &EVENT_Stat),
|
|
GB_EVENT("Close", NULL, NULL, &EVENT_Close),
|
|
GB_EVENT("Create", NULL, NULL, &EVENT_Create),
|
|
GB_EVENT("Delete", NULL, NULL, &EVENT_Delete),
|
|
GB_EVENT("Write", NULL, NULL, &EVENT_Write),
|
|
GB_EVENT("Move", NULL, NULL, &EVENT_Move),
|
|
GB_EVENT("MoveFrom", NULL, NULL, &EVENT_MoveFrom),
|
|
GB_EVENT("MoveTo", NULL, NULL, &EVENT_MoveTo),
|
|
GB_EVENT("Open", NULL, NULL, &EVENT_Open),
|
|
|
|
GB_STATIC_METHOD("_exit", NULL, Watch_exit, NULL),
|
|
//GB_STATIC_METHOD("_get", "Watch", Watch_get, "(Path)s"),
|
|
|
|
GB_METHOD("Resume", NULL, Watch_Resume, NULL),
|
|
GB_METHOD("Pause", NULL, Watch_Pause, NULL),
|
|
|
|
GB_STATIC_PROPERTY_READ("Name", "s", Watch_Name),
|
|
GB_STATIC_PROPERTY_READ("IsDir", "b", Watch_IsDir),
|
|
GB_STATIC_PROPERTY_READ("Unmount", "b", Watch_Unmount),
|
|
GB_STATIC_PROPERTY_READ("Cookie", "i", Watch_Cookie),
|
|
|
|
GB_METHOD("_new", NULL, Watch_new, "(Path)s[(NoFollowLink)b(Events)i]"),
|
|
GB_METHOD("_free", NULL, Watch_free, NULL),
|
|
|
|
GB_PROPERTY_SELF("Events", ".Watch.Events"),
|
|
GB_PROPERTY_READ("Path", "s", Watch_Path),
|
|
GB_PROPERTY("IsPaused", "b", Watch_IsPaused),
|
|
GB_PROPERTY("Tag", "v", Watch_Tag),
|
|
|
|
GB_END_DECLARE
|
|
};
|
|
|
|
/**G
|
|
* Return if the given flag is set. You can also combine multiple flags to
|
|
* check if at least one of them is set.
|
|
**/
|
|
BEGIN_METHOD(WatchEvents_get, GB_INTEGER events)
|
|
|
|
GB.ReturnBoolean(!!((THIS->paused ? THIS->save_events : THIS->events) & VARG(events)));
|
|
|
|
END_METHOD
|
|
|
|
/**G
|
|
* Set or clear a flag. You can combine multiple flags to set or clear them
|
|
* en masse.
|
|
**/
|
|
BEGIN_METHOD(WatchEvents_put, GB_BOOLEAN value; GB_INTEGER events)
|
|
|
|
WATCH_LIST *list = (WATCH_LIST *)THIS->root;
|
|
int i;
|
|
int events = VARG(events);
|
|
bool old, new;
|
|
|
|
if (!THIS->paused)
|
|
{
|
|
for (i = 0; i < NUM_EV; i++)
|
|
{
|
|
new = (events & (1 << i)) != 0;
|
|
old = (THIS->events & (1 << i)) != 0;
|
|
if (new != old)
|
|
{
|
|
if (new)
|
|
list->events[i]++;
|
|
else
|
|
list->events[i]--;
|
|
}
|
|
}
|
|
|
|
THIS->events = events;
|
|
update_watch_list(list);
|
|
}
|
|
else
|
|
THIS->save_events = events;
|
|
|
|
END_METHOD
|
|
|
|
GB_DESC CWatchEvents[] =
|
|
{
|
|
GB_DECLARE_VIRTUAL(".Watch.Events"),
|
|
|
|
GB_METHOD("_get", "b", WatchEvents_get, "(Events)i"),
|
|
GB_METHOD("_put", NULL, WatchEvents_put, "(Value)b(Events)i"),
|
|
|
|
GB_END_DECLARE
|
|
};
|