]> git.saurik.com Git - wxWidgets.git/blame - src/unix/fswatcher_kqueue.cpp
Fix horizontal mouse wheel scrolling in wxGTK.
[wxWidgets.git] / src / unix / fswatcher_kqueue.cpp
CommitLineData
6b8ef0b3
VZ
1/////////////////////////////////////////////////////////////////////////////
2// Name: src/unix/fswatcher_kqueue.cpp
3// Purpose: kqueue-based wxFileSystemWatcher implementation
4// Author: Bartosz Bekier
5// Created: 2009-05-26
6b8ef0b3
VZ
6// Copyright: (c) 2009 Bartosz Bekier <bartosz.bekier@gmail.com>
7// Licence: wxWindows licence
8/////////////////////////////////////////////////////////////////////////////
9
10// For compilers that support precompilation, includes "wx.h".
11#include "wx/wxprec.h"
12
13#ifdef __BORLANDC__
14 #pragma hdrstop
15#endif
16
17#if wxUSE_FSWATCHER
18
19#include "wx/fswatcher.h"
20
21#ifdef wxHAS_KQUEUE
22
23#include <sys/types.h>
24#include <sys/event.h>
5cd99866 25
6b8ef0b3 26#include "wx/dynarray.h"
5cd99866
VZ
27#include "wx/evtloop.h"
28#include "wx/evtloopsrc.h"
29
6b8ef0b3
VZ
30#include "wx/private/fswatcher.h"
31
5cd99866
VZ
32// ============================================================================
33// wxFSWSourceHandler helper class
34// ============================================================================
35
36class wxFSWatcherImplKqueue;
37
38/**
39 * Handler for handling i/o from inotify descriptor
40 */
41class wxFSWSourceHandler : public wxEventLoopSourceHandler
42{
43public:
44 wxFSWSourceHandler(wxFSWatcherImplKqueue* service) :
45 m_service(service)
46 { }
47
48 virtual void OnReadWaiting();
49 virtual void OnWriteWaiting();
50 virtual void OnExceptionWaiting();
51
52protected:
53 wxFSWatcherImplKqueue* m_service;
54};
55
6b8ef0b3
VZ
56// ============================================================================
57// wxFSWatcherImpl implementation & helper wxFSWSourceHandler implementation
58// ============================================================================
59
60/**
61 * Helper class encapsulating inotify mechanism
62 */
63class wxFSWatcherImplKqueue : public wxFSWatcherImpl
64{
65public:
66 wxFSWatcherImplKqueue(wxFileSystemWatcherBase* watcher) :
67 wxFSWatcherImpl(watcher),
6b8ef0b3
VZ
68 m_source(NULL),
69 m_kfd(-1)
70 {
71 m_handler = new wxFSWSourceHandler(this);
72 }
73
5cd99866 74 virtual ~wxFSWatcherImplKqueue()
6b8ef0b3
VZ
75 {
76 // we close kqueue only if initialized before
77 if (IsOk())
78 {
79 Close();
80 }
81
82 delete m_handler;
83 }
84
85 bool Init()
86 {
87 wxCHECK_MSG( !IsOk(), false,
88 "Kqueue appears to be already initialized" );
6b8ef0b3 89
5cd99866
VZ
90 wxEventLoopBase *loop = wxEventLoopBase::GetActive();
91 wxCHECK_MSG( loop, false, "File system watcher needs an active loop" );
6b8ef0b3
VZ
92
93 // create kqueue
94 m_kfd = kqueue();
95 if (m_kfd == -1)
96 {
97 wxLogSysError(_("Unable to create kqueue instance"));
98 return false;
99 }
100
101 // create source
5cd99866 102 m_source = loop->AddSourceForFD(m_kfd, m_handler, wxEVENT_SOURCE_INPUT);
6b8ef0b3 103
5cd99866 104 return m_source != NULL;
6b8ef0b3
VZ
105 }
106
5cd99866 107 void Close()
6b8ef0b3 108 {
5cd99866 109 wxCHECK_RET( IsOk(),
6b8ef0b3 110 "Kqueue not initialized or invalid kqueue descriptor" );
6b8ef0b3 111
5cd99866 112 if ( close(m_kfd) != 0 )
6b8ef0b3 113 {
5cd99866 114 wxLogSysError(_("Error closing kqueue instance"));
6b8ef0b3 115 }
6b8ef0b3 116
5276b0a5 117 wxDELETE(m_source);
6b8ef0b3
VZ
118 }
119
120 virtual bool DoAdd(wxSharedPtr<wxFSWatchEntryKq> watch)
121 {
122 wxCHECK_MSG( IsOk(), false,
123 "Kqueue not initialized or invalid kqueue descriptor" );
124
125 struct kevent event;
126 int action = EV_ADD | EV_ENABLE | EV_CLEAR | EV_ERROR;
127 int flags = Watcher2NativeFlags(watch->GetFlags());
128 EV_SET( &event, watch->GetFileDescriptor(), EVFILT_VNODE, action,
129 flags, 0, watch.get() );
130
131 // TODO more error conditions according to man
132 // TODO best deal with the error here
133 int ret = kevent(m_kfd, &event, 1, NULL, 0, NULL);
134 if (ret == -1)
135 {
136 wxLogSysError(_("Unable to add kqueue watch"));
137 return false;
138 }
139
140 return true;
141 }
142
143 virtual bool DoRemove(wxSharedPtr<wxFSWatchEntryKq> watch)
144 {
145 wxCHECK_MSG( IsOk(), false,
146 "Kqueue not initialized or invalid kqueue descriptor" );
147
148 // TODO more error conditions according to man
149 // XXX closing file descriptor removes the watch. The logic resides in
150 // the watch which is not nice, but effective and simple
70a5b905 151 if ( !watch->Close() )
6b8ef0b3
VZ
152 {
153 wxLogSysError(_("Unable to remove kqueue watch"));
154 return false;
155 }
156
157 return true;
158 }
159
160 virtual bool RemoveAll()
161 {
162 wxFSWatchEntries::iterator it = m_watches.begin();
163 for ( ; it != m_watches.end(); ++it )
164 {
165 (void) DoRemove(it->second);
166 }
167 m_watches.clear();
168 return true;
169 }
170
171 // return true if there was no error, false on error
172 bool ReadEvents()
173 {
174 wxCHECK_MSG( IsOk(), false,
175 "Kqueue not initialized or invalid kqueue descriptor" );
176
177 // read events
178 do
179 {
180 struct kevent event;
181 struct timespec timeout = {0, 0};
182 int ret = kevent(m_kfd, NULL, 0, &event, 1, &timeout);
183 if (ret == -1)
184 {
185 wxLogSysError(_("Unable to get events from kqueue"));
186 return false;
187 }
188 else if (ret == 0)
189 {
190 return true;
191 }
192
193 // we have event, so process it
194 ProcessNativeEvent(event);
195 }
196 while (true);
197
198 // when ret>0 we still have events, when ret<=0 we return
199 wxFAIL_MSG( "Never reached" );
200 return true;
201 }
202
5cd99866 203 bool IsOk() const
6b8ef0b3 204 {
5cd99866 205 return m_source != NULL;
6b8ef0b3
VZ
206 }
207
6b8ef0b3 208protected:
6b8ef0b3
VZ
209 // returns all new dirs/files present in the immediate level of the dir
210 // pointed by watch.GetPath(). "new" means created between the last time
211 // the state of watch was computed and now
5cd99866
VZ
212 void FindChanges(wxFSWatchEntryKq& watch,
213 wxArrayString& changedFiles,
214 wxArrayInt& changedFlags)
6b8ef0b3
VZ
215 {
216 wxFSWatchEntryKq::wxDirState old = watch.GetLastState();
217 watch.RefreshState();
218 wxFSWatchEntryKq::wxDirState curr = watch.GetLastState();
219
220 // iterate over old/curr file lists and compute changes
221 wxArrayString::iterator oit = old.files.begin();
222 wxArrayString::iterator cit = curr.files.begin();
223 for ( ; oit != old.files.end() && cit != curr.files.end(); )
224 {
225 if ( *cit == *oit )
226 {
227 ++cit;
228 ++oit;
229 }
230 else if ( *cit <= *oit )
231 {
232 changedFiles.push_back(*cit);
233 changedFlags.push_back(wxFSW_EVENT_CREATE);
234 ++cit;
235 }
236 else // ( *cit > *oit )
237 {
238 changedFiles.push_back(*oit);
239 changedFlags.push_back(wxFSW_EVENT_DELETE);
240 ++oit;
241 }
242 }
243
244 // border conditions
245 if ( oit == old.files.end() )
246 {
247 for ( ; cit != curr.files.end(); ++cit )
248 {
249 changedFiles.push_back( *cit );
250 changedFlags.push_back(wxFSW_EVENT_CREATE);
251 }
252 }
253 else if ( cit == curr.files.end() )
254 {
255 for ( ; oit != old.files.end(); ++oit )
256 {
257 changedFiles.push_back( *oit );
258 changedFlags.push_back(wxFSW_EVENT_DELETE);
259 }
260 }
261
262 wxASSERT( changedFiles.size() == changedFlags.size() );
263
264#if 0
265 wxLogTrace(wxTRACE_FSWATCHER, "Changed files:");
266 wxArrayString::iterator it = changedFiles.begin();
267 wxArrayInt::iterator it2 = changedFlags.begin();
268 for ( ; it != changedFiles.end(); ++it, ++it2)
269 {
270 wxString action = (*it2 == wxFSW_EVENT_CREATE) ?
271 "created" : "deleted";
272 wxLogTrace(wxTRACE_FSWATCHER, wxString::Format("File: '%s' %s",
273 *it, action));
274 }
275#endif
276 }
277
278 void ProcessNativeEvent(const struct kevent& e)
279 {
280 wxASSERT_MSG(e.udata, "Null user data associated with kevent!");
281
282 wxLogTrace(wxTRACE_FSWATCHER, "Event: ident=%d, filter=%d, flags=%u, "
283 "fflags=%u, data=%d, user_data=%p",
284 e.ident, e.filter, e.flags, e.fflags, e.data, e.udata);
285
286 // for ease of use
287 wxFSWatchEntryKq& w = *(static_cast<wxFSWatchEntry*>(e.udata));
288 int nflags = e.fflags;
289
290 // clear ignored flags
291 nflags &= ~NOTE_REVOKE;
292
293 // TODO ignore events we didn't ask for + refactor this cascade ifs
294 // check for events
295 while ( nflags )
296 {
297 // when monitoring dir, this means create/delete
f3b1d0fc
VZ
298 const wxString basepath = w.GetPath();
299 if ( nflags & NOTE_WRITE && wxDirExists(basepath) )
6b8ef0b3
VZ
300 {
301 // NOTE_LINK is set when the dir was created, but we
302 // don't care - we look for new names in directory
303 // regardless of type. Also, clear all this, because
304 // it cannot mean more by itself
305 nflags &= ~(NOTE_WRITE | NOTE_ATTRIB | NOTE_LINK);
306
307 wxArrayString changedFiles;
308 wxArrayInt changedFlags;
309 FindChanges(w, changedFiles, changedFlags);
f3b1d0fc 310
6b8ef0b3
VZ
311 wxArrayString::iterator it = changedFiles.begin();
312 wxArrayInt::iterator changeType = changedFlags.begin();
313 for ( ; it != changedFiles.end(); ++it, ++changeType )
314 {
d66ddd70
VZ
315 const wxString fullpath = w.GetPath() +
316 wxFileName::GetPathSeparator() +
317 *it;
318 const wxFileName path(wxDirExists(fullpath)
319 ? wxFileName::DirName(fullpath)
320 : wxFileName::FileName(fullpath));
6b8ef0b3
VZ
321
322 wxFileSystemWatcherEvent event(*changeType, path, path);
323 SendEvent(event);
324 }
325 }
326 else if ( nflags & NOTE_RENAME )
327 {
328 // CHECK it'd be only possible to detect name if we had
329 // parent files listing which we could confront with now and
330 // still we couldn't be sure we have the right name...
331 nflags &= ~NOTE_RENAME;
332 wxFileSystemWatcherEvent event(wxFSW_EVENT_RENAME,
f3b1d0fc 333 basepath, wxFileName());
6b8ef0b3
VZ
334 SendEvent(event);
335 }
336 else if ( nflags & NOTE_WRITE || nflags & NOTE_EXTEND )
337 {
338 nflags &= ~(NOTE_WRITE | NOTE_EXTEND);
339 wxFileSystemWatcherEvent event(wxFSW_EVENT_MODIFY,
f3b1d0fc 340 basepath, basepath);
6b8ef0b3
VZ
341 SendEvent(event);
342 }
343 else if ( nflags & NOTE_DELETE )
344 {
345 nflags &= ~(NOTE_DELETE);
346 wxFileSystemWatcherEvent event(wxFSW_EVENT_DELETE,
f3b1d0fc 347 basepath, basepath);
6b8ef0b3
VZ
348 SendEvent(event);
349 }
350 else if ( nflags & NOTE_ATTRIB )
351 {
352 nflags &= ~(NOTE_ATTRIB);
353 wxFileSystemWatcherEvent event(wxFSW_EVENT_ACCESS,
f3b1d0fc 354 basepath, basepath);
6b8ef0b3
VZ
355 SendEvent(event);
356 }
357
358 // clear any flags that won't mean anything by themselves
359 nflags &= ~(NOTE_LINK);
360 }
361 }
362
363 void SendEvent(wxFileSystemWatcherEvent& evt)
364 {
365 m_watcher->GetOwner()->ProcessEvent(evt);
366 }
367
368 static int Watcher2NativeFlags(int WXUNUSED(flags))
369 {
370 // TODO: it would be better to only subscribe to what we need
371 return NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND |
372 NOTE_ATTRIB | NOTE_LINK | NOTE_RENAME |
373 NOTE_REVOKE;
374 }
375
376 wxFSWSourceHandler* m_handler; // handler for kqueue event source
5cd99866
VZ
377 wxEventLoopSource* m_source; // our event loop source
378
379 // descriptor created by kqueue()
6b8ef0b3
VZ
380 int m_kfd;
381};
382
383
384// once we get signaled to read, actuall event reading occurs
385void wxFSWSourceHandler::OnReadWaiting()
386{
387 wxLogTrace(wxTRACE_FSWATCHER, "--- OnReadWaiting ---");
388 m_service->ReadEvents();
389}
390
391void wxFSWSourceHandler::OnWriteWaiting()
392{
393 wxFAIL_MSG("We never write to kqueue descriptor.");
394}
395
396void wxFSWSourceHandler::OnExceptionWaiting()
397{
398 wxFAIL_MSG("We never receive exceptions on kqueue descriptor.");
399}
400
401
402// ============================================================================
403// wxKqueueFileSystemWatcher implementation
404// ============================================================================
405
406wxKqueueFileSystemWatcher::wxKqueueFileSystemWatcher()
407 : wxFileSystemWatcherBase()
408{
409 Init();
410}
411
412wxKqueueFileSystemWatcher::wxKqueueFileSystemWatcher(const wxFileName& path,
413 int events)
414 : wxFileSystemWatcherBase()
415{
416 if (!Init())
417 {
5276b0a5 418 wxDELETE(m_service);
6b8ef0b3
VZ
419 return;
420 }
421
422 Add(path, events);
423}
424
425wxKqueueFileSystemWatcher::~wxKqueueFileSystemWatcher()
426{
427}
428
429bool wxKqueueFileSystemWatcher::Init()
430{
431 m_service = new wxFSWatcherImplKqueue(this);
432 return m_service->Init();
433}
434
435#endif // wxHAS_KQUEUE
436
437#endif // wxUSE_FSWATCHER