diff --git a/Makefile.in b/Makefile.in
index 2483738bb3..de571fc4db 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -2253,7 +2253,8 @@ COND_TOOLKIT_OSX_CARBON_BASE_OSX_SRC = \
src/unix/threadpsx.cpp \
src/unix/utilsunx.cpp \
src/unix/wakeuppipe.cpp \
- src/unix/fswatcher_kqueue.cpp
+ src/unix/fswatcher_kqueue.cpp \
+ src/osx/fswatcher_fsevents.cpp
@COND_TOOLKIT_OSX_CARBON@BASE_OSX_SRC = $(COND_TOOLKIT_OSX_CARBON_BASE_OSX_SRC)
COND_TOOLKIT_OSX_COCOA_BASE_OSX_SRC = \
src/osx/core/mimetype.cpp \
@@ -2277,7 +2278,8 @@ COND_TOOLKIT_OSX_COCOA_BASE_OSX_SRC = \
src/unix/threadpsx.cpp \
src/unix/utilsunx.cpp \
src/unix/wakeuppipe.cpp \
- src/unix/fswatcher_kqueue.cpp
+ src/unix/fswatcher_kqueue.cpp \
+ src/osx/fswatcher_fsevents.cpp
@COND_TOOLKIT_OSX_COCOA@BASE_OSX_SRC = $(COND_TOOLKIT_OSX_COCOA_BASE_OSX_SRC)
COND_TOOLKIT_OSX_IPHONE_BASE_OSX_SRC = \
src/osx/core/mimetype.cpp \
@@ -2301,7 +2303,8 @@ COND_TOOLKIT_OSX_IPHONE_BASE_OSX_SRC = \
src/unix/threadpsx.cpp \
src/unix/utilsunx.cpp \
src/unix/wakeuppipe.cpp \
- src/unix/fswatcher_kqueue.cpp
+ src/unix/fswatcher_kqueue.cpp \
+ src/osx/fswatcher_fsevents.cpp
@COND_TOOLKIT_OSX_IPHONE@BASE_OSX_SRC = $(COND_TOOLKIT_OSX_IPHONE_BASE_OSX_SRC)
COND_TOOLKIT_COCOA_BASE_OSX_SRC = \
src/common/fdiodispatcher.cpp \
@@ -2442,7 +2445,8 @@ COND_TOOLKIT_OSX_CARBON_BASE_OSX_HDR = \
wx/unix/stdpaths.h \
wx/unix/stackwalk.h \
wx/unix/tls.h \
- wx/unix/fswatcher_kqueue.h
+ wx/unix/fswatcher_kqueue.h \
+ wx/osx/fswatcher_fsevents.h
@COND_TOOLKIT_OSX_CARBON@BASE_OSX_HDR = $(COND_TOOLKIT_OSX_CARBON_BASE_OSX_HDR)
COND_TOOLKIT_OSX_COCOA_BASE_OSX_HDR = \
wx/osx/core/cfdataref.h \
@@ -2463,7 +2467,8 @@ COND_TOOLKIT_OSX_COCOA_BASE_OSX_HDR = \
wx/unix/stdpaths.h \
wx/unix/stackwalk.h \
wx/unix/tls.h \
- wx/unix/fswatcher_kqueue.h
+ wx/unix/fswatcher_kqueue.h \
+ wx/osx/fswatcher_fsevents.h
@COND_TOOLKIT_OSX_COCOA@BASE_OSX_HDR = $(COND_TOOLKIT_OSX_COCOA_BASE_OSX_HDR)
COND_TOOLKIT_COCOA_BASE_OSX_HDR = \
wx/unix/app.h \
@@ -4513,7 +4518,8 @@ COND_PLATFORM_MACOSX_1___BASE_PLATFORM_SRC_OBJECTS = \
monodll_threadpsx.o \
monodll_utilsunx.o \
monodll_wakeuppipe.o \
- monodll_fswatcher_kqueue.o
+ monodll_fswatcher_kqueue.o \
+ monodll_fswatcher_fsevents.o
@COND_PLATFORM_MACOSX_1@__BASE_PLATFORM_SRC_OBJECTS = $(COND_PLATFORM_MACOSX_1___BASE_PLATFORM_SRC_OBJECTS)
@COND_PLATFORM_MSDOS_1@__BASE_PLATFORM_SRC_OBJECTS = \
@COND_PLATFORM_MSDOS_1@ monodll_msdos_dir.o monodll_msdos_mimetype.o \
@@ -6888,7 +6894,8 @@ COND_PLATFORM_MACOSX_1___BASE_PLATFORM_SRC_OBJECTS_1 = \
monolib_threadpsx.o \
monolib_utilsunx.o \
monolib_wakeuppipe.o \
- monolib_fswatcher_kqueue.o
+ monolib_fswatcher_kqueue.o \
+ monolib_fswatcher_fsevents.o
@COND_PLATFORM_MACOSX_1@__BASE_PLATFORM_SRC_OBJECTS_1 = $(COND_PLATFORM_MACOSX_1___BASE_PLATFORM_SRC_OBJECTS_1)
@COND_PLATFORM_MSDOS_1@__BASE_PLATFORM_SRC_OBJECTS_1 \
@COND_PLATFORM_MSDOS_1@ = monolib_msdos_dir.o monolib_msdos_mimetype.o \
@@ -9316,7 +9323,8 @@ COND_PLATFORM_MACOSX_1___BASE_PLATFORM_SRC_OBJECTS_2 = \
basedll_threadpsx.o \
basedll_utilsunx.o \
basedll_wakeuppipe.o \
- basedll_fswatcher_kqueue.o
+ basedll_fswatcher_kqueue.o \
+ basedll_fswatcher_fsevents.o
@COND_PLATFORM_MACOSX_1@__BASE_PLATFORM_SRC_OBJECTS_2 = $(COND_PLATFORM_MACOSX_1___BASE_PLATFORM_SRC_OBJECTS_2)
@COND_PLATFORM_MSDOS_1@__BASE_PLATFORM_SRC_OBJECTS_2 \
@COND_PLATFORM_MSDOS_1@ = basedll_msdos_dir.o basedll_msdos_mimetype.o \
@@ -9404,7 +9412,8 @@ COND_PLATFORM_MACOSX_1___BASE_PLATFORM_SRC_OBJECTS_3 = \
baselib_threadpsx.o \
baselib_utilsunx.o \
baselib_wakeuppipe.o \
- baselib_fswatcher_kqueue.o
+ baselib_fswatcher_kqueue.o \
+ baselib_fswatcher_fsevents.o
@COND_PLATFORM_MACOSX_1@__BASE_PLATFORM_SRC_OBJECTS_3 = $(COND_PLATFORM_MACOSX_1___BASE_PLATFORM_SRC_OBJECTS_3)
@COND_PLATFORM_MSDOS_1@__BASE_PLATFORM_SRC_OBJECTS_3 \
@COND_PLATFORM_MSDOS_1@ = baselib_msdos_dir.o baselib_msdos_mimetype.o \
@@ -17740,6 +17749,9 @@ monodll_strconv_cf.o: $(srcdir)/src/osx/core/strconv_cf.cpp $(MONODLL_ODEP)
monodll_utilsexc_base.o: $(srcdir)/src/osx/core/utilsexc_base.cpp $(MONODLL_ODEP)
$(CXXC) -c -o $@ $(MONODLL_CXXFLAGS) $(srcdir)/src/osx/core/utilsexc_base.cpp
+monodll_fswatcher_fsevents.o: $(srcdir)/src/osx/fswatcher_fsevents.cpp $(MONODLL_ODEP)
+ $(CXXC) -c -o $@ $(MONODLL_CXXFLAGS) $(srcdir)/src/osx/fswatcher_fsevents.cpp
+
monodll_msdos_dir.o: $(srcdir)/src/msdos/dir.cpp $(MONODLL_ODEP)
$(CXXC) -c -o $@ $(MONODLL_CXXFLAGS) $(srcdir)/src/msdos/dir.cpp
@@ -23662,6 +23674,9 @@ monolib_strconv_cf.o: $(srcdir)/src/osx/core/strconv_cf.cpp $(MONOLIB_ODEP)
monolib_utilsexc_base.o: $(srcdir)/src/osx/core/utilsexc_base.cpp $(MONOLIB_ODEP)
$(CXXC) -c -o $@ $(MONOLIB_CXXFLAGS) $(srcdir)/src/osx/core/utilsexc_base.cpp
+monolib_fswatcher_fsevents.o: $(srcdir)/src/osx/fswatcher_fsevents.cpp $(MONOLIB_ODEP)
+ $(CXXC) -c -o $@ $(MONOLIB_CXXFLAGS) $(srcdir)/src/osx/fswatcher_fsevents.cpp
+
monolib_msdos_dir.o: $(srcdir)/src/msdos/dir.cpp $(MONOLIB_ODEP)
$(CXXC) -c -o $@ $(MONOLIB_CXXFLAGS) $(srcdir)/src/msdos/dir.cpp
@@ -29584,6 +29599,9 @@ basedll_strconv_cf.o: $(srcdir)/src/osx/core/strconv_cf.cpp $(BASEDLL_ODEP)
basedll_utilsexc_base.o: $(srcdir)/src/osx/core/utilsexc_base.cpp $(BASEDLL_ODEP)
$(CXXC) -c -o $@ $(BASEDLL_CXXFLAGS) $(srcdir)/src/osx/core/utilsexc_base.cpp
+basedll_fswatcher_fsevents.o: $(srcdir)/src/osx/fswatcher_fsevents.cpp $(BASEDLL_ODEP)
+ $(CXXC) -c -o $@ $(BASEDLL_CXXFLAGS) $(srcdir)/src/osx/fswatcher_fsevents.cpp
+
basedll_msdos_dir.o: $(srcdir)/src/msdos/dir.cpp $(BASEDLL_ODEP)
$(CXXC) -c -o $@ $(BASEDLL_CXXFLAGS) $(srcdir)/src/msdos/dir.cpp
@@ -30064,6 +30082,9 @@ baselib_strconv_cf.o: $(srcdir)/src/osx/core/strconv_cf.cpp $(BASELIB_ODEP)
baselib_utilsexc_base.o: $(srcdir)/src/osx/core/utilsexc_base.cpp $(BASELIB_ODEP)
$(CXXC) -c -o $@ $(BASELIB_CXXFLAGS) $(srcdir)/src/osx/core/utilsexc_base.cpp
+baselib_fswatcher_fsevents.o: $(srcdir)/src/osx/fswatcher_fsevents.cpp $(BASELIB_ODEP)
+ $(CXXC) -c -o $@ $(BASELIB_CXXFLAGS) $(srcdir)/src/osx/fswatcher_fsevents.cpp
+
baselib_msdos_dir.o: $(srcdir)/src/msdos/dir.cpp $(BASELIB_ODEP)
$(CXXC) -c -o $@ $(BASELIB_CXXFLAGS) $(srcdir)/src/msdos/dir.cpp
diff --git a/build/bakefiles/files.bkl b/build/bakefiles/files.bkl
index ae2b56d178..4526caee00 100644
--- a/build/bakefiles/files.bkl
+++ b/build/bakefiles/files.bkl
@@ -210,10 +210,12 @@ IMPORTANT: please read docs/tech/tn0016.txt before modifying this file!
src/osx/core/mimetype.cpp
$(BASE_COREFOUNDATION_SRC)
$(BASE_UNIX_AND_DARWIN_SRC)
+ src/osx/fswatcher_fsevents.cpp
$(BASE_COREFOUNDATION_HDR)
$(BASE_UNIX_AND_DARWIN_HDR)
+ wx/osx/fswatcher_fsevents.h
diff --git a/build/files b/build/files
index cb3c4505a2..d582b8ef3e 100644
--- a/build/files
+++ b/build/files
@@ -151,10 +151,12 @@ BASE_COREFOUNDATION_HDR =
# Base files used by OS X ports (not Carbon)
BASE_OSX_SHARED_SRC =
src/osx/core/mimetype.cpp
+ src/osx/fswatcher_fsevents.cpp
$(BASE_COREFOUNDATION_SRC)
$(BASE_UNIX_AND_DARWIN_SRC)
BASE_OSX_SHARED_HDR =
+ wx/osx/fswatcher_fsevents.h
$(BASE_COREFOUNDATION_HDR)
$(BASE_UNIX_AND_DARWIN_HDR)
diff --git a/build/osx/wxcarbon.xcodeproj/project.pbxproj b/build/osx/wxcarbon.xcodeproj/project.pbxproj
index dd26a40844..c60de66008 100644
--- a/build/osx/wxcarbon.xcodeproj/project.pbxproj
+++ b/build/osx/wxcarbon.xcodeproj/project.pbxproj
@@ -877,6 +877,8 @@
8292D346BFC33D6E8D3CDDC0 /* xh_sttxt.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B3F1680BBE8331A7B745638C /* xh_sttxt.cpp */; };
82FA4AA043213728AC266700 /* wizard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8F08F70E1EF239999A4D2AC4 /* wizard.cpp */; };
82FA4AA043213728AC266701 /* wizard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8F08F70E1EF239999A4D2AC4 /* wizard.cpp */; };
+ 830A61EA04FD367C9EB6A757 /* fswatcher_fsevents.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5B83407D156C3CC3A66F05A4 /* fswatcher_fsevents.cpp */; };
+ 830A61EA04FD367C9EB6A758 /* fswatcher_fsevents.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5B83407D156C3CC3A66F05A4 /* fswatcher_fsevents.cpp */; };
834F2ADD0520313FBAC4F927 /* LexCsound.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 0A59A5C2305D3D1C8049BE71 /* LexCsound.cxx */; };
834F2ADD0520313FBAC4F928 /* LexCsound.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 0A59A5C2305D3D1C8049BE71 /* LexCsound.cxx */; };
83616D33080E3F0F9FA5FBB4 /* xh_html.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 889FFA9573A835F280A21CB4 /* xh_html.cpp */; };
@@ -2023,6 +2025,7 @@
5AACC1EC2E2A33B3ABF5EDCA /* xh_radbt.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = xh_radbt.cpp; path = ../../src/xrc/xh_radbt.cpp; sourceTree = ""; };
5AFB85719CBC3D60BA2EDC2E /* CharClassify.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = CharClassify.cxx; path = ../../src/stc/scintilla/src/CharClassify.cxx; sourceTree = ""; };
5B32A13D5B3336098B1B9765 /* pngtrans.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = pngtrans.c; path = ../../src/png/pngtrans.c; sourceTree = ""; };
+ 5B83407D156C3CC3A66F05A4 /* fswatcher_fsevents.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = fswatcher_fsevents.cpp; path = ../../src/osx/fswatcher_fsevents.cpp; sourceTree = ""; };
5B9586328A1F3C4BA0390AA5 /* time.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = time.cpp; path = ../../src/common/time.cpp; sourceTree = ""; };
5BD6231188AB329CAA5E1171 /* evtloop_cf.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = evtloop_cf.cpp; path = ../../src/osx/core/evtloop_cf.cpp; sourceTree = ""; };
5BEC6B3CAFB532CBB9F95D74 /* jutils.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = jutils.c; path = ../../src/jpeg/jutils.c; sourceTree = ""; };
@@ -3555,6 +3558,7 @@
DC75C7251C1732B0B07C7BD3 /* utilsunx.cpp */,
B38F3D4DC6D139BA93401F7A /* wakeuppipe.cpp */,
C019CE87CF9931B0B77C0823 /* fswatcher_kqueue.cpp */,
+ 5B83407D156C3CC3A66F05A4 /* fswatcher_fsevents.cpp */,
7A34C5BBBA543DC0A50DE1B6 /* event.cpp */,
C9A305CEC03B3085B159B617 /* fs_mem.cpp */,
E968913A9A593B258BD8EACB /* msgout.cpp */,
@@ -3908,6 +3912,7 @@
B5C7FD8C27F43F3289A77FCA /* utilsunx.cpp in Sources */,
F9C5EAC42CCF3267B4100BAF /* wakeuppipe.cpp in Sources */,
FF7DB2884F6E3C5DB4BDF61E /* fswatcher_kqueue.cpp in Sources */,
+ 830A61EA04FD367C9EB6A758 /* fswatcher_fsevents.cpp in Sources */,
55D893FDD00633FEA82ABD82 /* event.cpp in Sources */,
131B879180AE3FB481F81CC8 /* fs_mem.cpp in Sources */,
05814571E7A83F5DBFB6E4C5 /* msgout.cpp in Sources */,
@@ -4776,6 +4781,7 @@
B5C7FD8C27F43F3289A77FC9 /* utilsunx.cpp in Sources */,
F9C5EAC42CCF3267B4100BAE /* wakeuppipe.cpp in Sources */,
FF7DB2884F6E3C5DB4BDF61D /* fswatcher_kqueue.cpp in Sources */,
+ 830A61EA04FD367C9EB6A757 /* fswatcher_fsevents.cpp in Sources */,
55D893FDD00633FEA82ABD81 /* event.cpp in Sources */,
131B879180AE3FB481F81CC7 /* fs_mem.cpp in Sources */,
05814571E7A83F5DBFB6E4C4 /* msgout.cpp in Sources */,
diff --git a/build/osx/wxcocoa.xcodeproj/project.pbxproj b/build/osx/wxcocoa.xcodeproj/project.pbxproj
index 7ae569fdc8..2a22cfb63d 100644
--- a/build/osx/wxcocoa.xcodeproj/project.pbxproj
+++ b/build/osx/wxcocoa.xcodeproj/project.pbxproj
@@ -798,6 +798,10 @@
3D762A0BBF1B39B88A769632 /* helpwnd.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2DBD5DB511C53218B3EF1625 /* helpwnd.cpp */; };
3D762A0BBF1B39B88A769633 /* helpwnd.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2DBD5DB511C53218B3EF1625 /* helpwnd.cpp */; };
3D762A0BBF1B39B88A769634 /* helpwnd.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2DBD5DB511C53218B3EF1625 /* helpwnd.cpp */; };
+ 3DE2CD678CEB39C2B1E09ACA /* power.mm in Sources */ = {isa = PBXBuildFile; fileRef = 60DFD5962DE3379F801AF78F /* power.mm */; };
+ 3DE2CD678CEB39C2B1E09ACB /* power.mm in Sources */ = {isa = PBXBuildFile; fileRef = 60DFD5962DE3379F801AF78F /* power.mm */; };
+ 3DE2CD678CEB39C2B1E09ACC /* power.mm in Sources */ = {isa = PBXBuildFile; fileRef = 60DFD5962DE3379F801AF78F /* power.mm */; };
+ 3DE2CD678CEB39C2B1E09ACD /* power.mm in Sources */ = {isa = PBXBuildFile; fileRef = 60DFD5962DE3379F801AF78F /* power.mm */; };
3E6AA08E72A030D39D867D4B /* ScintillaWX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8E6F9D4319F639BE89E5A82F /* ScintillaWX.cpp */; };
3E6AA08E72A030D39D867D4C /* ScintillaWX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8E6F9D4319F639BE89E5A82F /* ScintillaWX.cpp */; };
3E6AA08E72A030D39D867D4D /* ScintillaWX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8E6F9D4319F639BE89E5A82F /* ScintillaWX.cpp */; };
@@ -1483,6 +1487,9 @@
82FA4AA043213728AC266700 /* wizard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8F08F70E1EF239999A4D2AC4 /* wizard.cpp */; };
82FA4AA043213728AC266701 /* wizard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8F08F70E1EF239999A4D2AC4 /* wizard.cpp */; };
82FA4AA043213728AC266702 /* wizard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8F08F70E1EF239999A4D2AC4 /* wizard.cpp */; };
+ 830A61EA04FD367C9EB6A757 /* fswatcher_fsevents.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5B83407D156C3CC3A66F05A4 /* fswatcher_fsevents.cpp */; };
+ 830A61EA04FD367C9EB6A758 /* fswatcher_fsevents.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5B83407D156C3CC3A66F05A4 /* fswatcher_fsevents.cpp */; };
+ 830A61EA04FD367C9EB6A759 /* fswatcher_fsevents.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5B83407D156C3CC3A66F05A4 /* fswatcher_fsevents.cpp */; };
834F2ADD0520313FBAC4F927 /* LexCsound.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 0A59A5C2305D3D1C8049BE71 /* LexCsound.cxx */; };
834F2ADD0520313FBAC4F928 /* LexCsound.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 0A59A5C2305D3D1C8049BE71 /* LexCsound.cxx */; };
834F2ADD0520313FBAC4F929 /* LexCsound.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 0A59A5C2305D3D1C8049BE71 /* LexCsound.cxx */; };
@@ -1771,10 +1778,6 @@
A1A7B833061C35B4AABD093C /* preferencesg.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D8F06DEA1AA339ED819B3812 /* preferencesg.cpp */; };
A1A7B833061C35B4AABD093D /* preferencesg.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D8F06DEA1AA339ED819B3812 /* preferencesg.cpp */; };
A1A7B833061C35B4AABD093E /* preferencesg.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D8F06DEA1AA339ED819B3812 /* preferencesg.cpp */; };
- A1A7C58E276F6F2B247F0813 /* power.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0714536835B5227019E29D06 /* power.mm */; };
- A1A7C58E276F6F2B247F0814 /* power.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0714536835B5227019E29D06 /* power.mm */; };
- A1A7C58E276F6F2B247F0815 /* power.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0714536835B5227019E29D06 /* power.mm */; };
- A1A7C58E276F6F2B247F0816 /* power.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0714536835B5227019E29D06 /* power.mm */; };
A1A7D793B034398B8696EF33 /* utils.mm in Sources */ = {isa = PBXBuildFile; fileRef = 789F45D14FF23E248FCFB5FA /* utils.mm */; };
A1A7D793B034398B8696EF34 /* utils.mm in Sources */ = {isa = PBXBuildFile; fileRef = 789F45D14FF23E248FCFB5FA /* utils.mm */; };
A1A7D793B034398B8696EF35 /* utils.mm in Sources */ = {isa = PBXBuildFile; fileRef = 789F45D14FF23E248FCFB5FA /* utils.mm */; };
@@ -4114,6 +4117,7 @@
5AACC1EC2E2A33B3ABF5EDCA /* xh_radbt.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = xh_radbt.cpp; path = ../../src/xrc/xh_radbt.cpp; sourceTree = ""; };
5AFB85719CBC3D60BA2EDC2E /* CharClassify.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = CharClassify.cxx; path = ../../src/stc/scintilla/src/CharClassify.cxx; sourceTree = ""; };
5B32A13D5B3336098B1B9765 /* pngtrans.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = pngtrans.c; path = ../../src/png/pngtrans.c; sourceTree = ""; };
+ 5B83407D156C3CC3A66F05A4 /* fswatcher_fsevents.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = fswatcher_fsevents.cpp; path = ../../src/osx/fswatcher_fsevents.cpp; sourceTree = ""; };
5B9586328A1F3C4BA0390AA5 /* time.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = time.cpp; path = ../../src/common/time.cpp; sourceTree = ""; };
5BD6231188AB329CAA5E1171 /* evtloop_cf.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = evtloop_cf.cpp; path = ../../src/osx/core/evtloop_cf.cpp; sourceTree = ""; };
5BEC6B3CAFB532CBB9F95D74 /* jutils.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = jutils.c; path = ../../src/jpeg/jutils.c; sourceTree = ""; };
@@ -4145,6 +4149,7 @@
600740717F7E320F8CA78384 /* scrolbar_osx.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = scrolbar_osx.cpp; path = ../../src/osx/scrolbar_osx.cpp; sourceTree = ""; };
604D9B79D41F32339AEC0EA0 /* libwx_osx_cocoau_xrc.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libwx_osx_cocoau_xrc.dylib; sourceTree = BUILT_PRODUCTS_DIR; };
607EF0043E723B7B9BE101EA /* wxprintf.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = wxprintf.cpp; path = ../../src/common/wxprintf.cpp; sourceTree = ""; };
+ 60DFD5962DE3379F801AF78F /* power.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = power.mm; path = ../../src/osx/cocoa/power.mm; sourceTree = ""; };
60EE4448A28D38F5ADE17B5A /* xh_filectrl.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = xh_filectrl.cpp; path = ../../src/xrc/xh_filectrl.cpp; sourceTree = ""; };
61548D0FE1353D7C846DD721 /* menuitem.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = menuitem.mm; path = ../../src/osx/cocoa/menuitem.mm; sourceTree = ""; };
61658C3EABB4341AA38C691E /* m_pre.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = m_pre.cpp; path = ../../src/html/m_pre.cpp; sourceTree = ""; };
@@ -4218,7 +4223,6 @@
777385D10CCC350C90F02824 /* textentry_osx.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = textentry_osx.cpp; path = ../../src/osx/textentry_osx.cpp; sourceTree = ""; };
77D6E66F72443765A2FBE263 /* aboutdlgg.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = aboutdlgg.cpp; path = ../../src/generic/aboutdlgg.cpp; sourceTree = ""; };
77F5E7BCD9B2307D8DBCC052 /* font.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = font.cpp; path = ../../src/osx/carbon/font.cpp; sourceTree = ""; };
- 0714536835B5227019E29D06 /* power.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = power.mm; path = ../../src/osx/cocoa/power.mm; sourceTree = ""; };
789F45D14FF23E248FCFB5FA /* utils.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = utils.mm; path = ../../src/osx/cocoa/utils.mm; sourceTree = ""; };
78D7866F95C73A28BB540606 /* LexBash.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = LexBash.cxx; path = ../../src/stc/scintilla/lexers/LexBash.cxx; sourceTree = ""; };
7906BD74118A3B4DAC515BC2 /* odcombo.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = odcombo.cpp; path = ../../src/generic/odcombo.cpp; sourceTree = ""; };
@@ -5964,12 +5968,13 @@
DC75C7251C1732B0B07C7BD3 /* utilsunx.cpp */,
B38F3D4DC6D139BA93401F7A /* wakeuppipe.cpp */,
C019CE87CF9931B0B77C0823 /* fswatcher_kqueue.cpp */,
+ 5B83407D156C3CC3A66F05A4 /* fswatcher_fsevents.cpp */,
7A34C5BBBA543DC0A50DE1B6 /* event.cpp */,
C9A305CEC03B3085B159B617 /* fs_mem.cpp */,
E968913A9A593B258BD8EACB /* msgout.cpp */,
4188821BBA833CCAA678B234 /* utilscmn.cpp */,
- 0714536835B5227019E29D06 /* power.mm */,
789F45D14FF23E248FCFB5FA /* utils.mm */,
+ 60DFD5962DE3379F801AF78F /* power.mm */,
);
name = base;
sourceTree = "";
@@ -7365,12 +7370,13 @@
B5C7FD8C27F43F3289A77FCB /* utilsunx.cpp in Sources */,
F9C5EAC42CCF3267B4100BB0 /* wakeuppipe.cpp in Sources */,
FF7DB2884F6E3C5DB4BDF61F /* fswatcher_kqueue.cpp in Sources */,
+ 830A61EA04FD367C9EB6A759 /* fswatcher_fsevents.cpp in Sources */,
55D893FDD00633FEA82ABD83 /* event.cpp in Sources */,
131B879180AE3FB481F81CC9 /* fs_mem.cpp in Sources */,
05814571E7A83F5DBFB6E4C6 /* msgout.cpp in Sources */,
80665EEAE8613DF8A93A7986 /* utilscmn.cpp in Sources */,
- A1A7C58E276F6F2B247F0815 /* power.mm in Sources */,
A1A7D793B034398B8696EF35 /* utils.mm in Sources */,
+ 3DE2CD678CEB39C2B1E09ACC /* power.mm in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -7402,8 +7408,8 @@
131B879180AE3FB481F81CCA /* fs_mem.cpp in Sources */,
05814571E7A83F5DBFB6E4C7 /* msgout.cpp in Sources */,
80665EEAE8613DF8A93A7987 /* utilscmn.cpp in Sources */,
- A1A7C58E276F6F2B247F0816 /* power.mm in Sources */,
A1A7D793B034398B8696EF36 /* utils.mm in Sources */,
+ 3DE2CD678CEB39C2B1E09ACD /* power.mm in Sources */,
F4C0CEADEDC23610BF6983D8 /* artmac.cpp in Sources */,
296692A7A3783E3A83D005C8 /* brush.cpp in Sources */,
86AED49CEAFC3637B1F10539 /* dialog_osx.cpp in Sources */,
@@ -8068,12 +8074,13 @@
B5C7FD8C27F43F3289A77FCA /* utilsunx.cpp in Sources */,
F9C5EAC42CCF3267B4100BAF /* wakeuppipe.cpp in Sources */,
FF7DB2884F6E3C5DB4BDF61E /* fswatcher_kqueue.cpp in Sources */,
+ 830A61EA04FD367C9EB6A758 /* fswatcher_fsevents.cpp in Sources */,
55D893FDD00633FEA82ABD82 /* event.cpp in Sources */,
131B879180AE3FB481F81CC8 /* fs_mem.cpp in Sources */,
05814571E7A83F5DBFB6E4C5 /* msgout.cpp in Sources */,
80665EEAE8613DF8A93A7985 /* utilscmn.cpp in Sources */,
- A1A7C58E276F6F2B247F0814 /* power.mm in Sources */,
A1A7D793B034398B8696EF34 /* utils.mm in Sources */,
+ 3DE2CD678CEB39C2B1E09ACB /* power.mm in Sources */,
F4C0CEADEDC23610BF6983D7 /* artmac.cpp in Sources */,
296692A7A3783E3A83D005C7 /* brush.cpp in Sources */,
86AED49CEAFC3637B1F10538 /* dialog_osx.cpp in Sources */,
@@ -9258,12 +9265,13 @@
B5C7FD8C27F43F3289A77FC9 /* utilsunx.cpp in Sources */,
F9C5EAC42CCF3267B4100BAE /* wakeuppipe.cpp in Sources */,
FF7DB2884F6E3C5DB4BDF61D /* fswatcher_kqueue.cpp in Sources */,
+ 830A61EA04FD367C9EB6A757 /* fswatcher_fsevents.cpp in Sources */,
55D893FDD00633FEA82ABD81 /* event.cpp in Sources */,
131B879180AE3FB481F81CC7 /* fs_mem.cpp in Sources */,
05814571E7A83F5DBFB6E4C4 /* msgout.cpp in Sources */,
80665EEAE8613DF8A93A7984 /* utilscmn.cpp in Sources */,
- A1A7C58E276F6F2B247F0813 /* power.mm in Sources */,
A1A7D793B034398B8696EF33 /* utils.mm in Sources */,
+ 3DE2CD678CEB39C2B1E09ACA /* power.mm in Sources */,
F4C0CEADEDC23610BF6983D6 /* artmac.cpp in Sources */,
296692A7A3783E3A83D005C6 /* brush.cpp in Sources */,
86AED49CEAFC3637B1F10537 /* dialog_osx.cpp in Sources */,
diff --git a/build/osx/wxiphone.xcodeproj/project.pbxproj b/build/osx/wxiphone.xcodeproj/project.pbxproj
index 6b916c5a74..90662597d5 100644
--- a/build/osx/wxiphone.xcodeproj/project.pbxproj
+++ b/build/osx/wxiphone.xcodeproj/project.pbxproj
@@ -432,6 +432,7 @@
825EAD51920B387DB4F8C426 /* LexAsn1.cxx in Sources */ = {isa = PBXBuildFile; fileRef = A46D50BEBF523B3F88831086 /* LexAsn1.cxx */; };
8292D346BFC33D6E8D3CDDBF /* xh_sttxt.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B3F1680BBE8331A7B745638C /* xh_sttxt.cpp */; };
82FA4AA043213728AC266700 /* wizard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8F08F70E1EF239999A4D2AC4 /* wizard.cpp */; };
+ 830A61EA04FD367C9EB6A757 /* fswatcher_fsevents.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5B83407D156C3CC3A66F05A4 /* fswatcher_fsevents.cpp */; };
834F2ADD0520313FBAC4F927 /* LexCsound.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 0A59A5C2305D3D1C8049BE71 /* LexCsound.cxx */; };
83616D33080E3F0F9FA5FBB4 /* xh_html.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 889FFA9573A835F280A21CB4 /* xh_html.cpp */; };
83C492B87F4A3A97930F227A /* ExternalLexer.cxx in Sources */ = {isa = PBXBuildFile; fileRef = FD6D2664C05131C3A06E98B4 /* ExternalLexer.cxx */; };
@@ -1126,6 +1127,7 @@
5AACC1EC2E2A33B3ABF5EDCA /* xh_radbt.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = xh_radbt.cpp; path = ../../src/xrc/xh_radbt.cpp; sourceTree = ""; };
5AFB85719CBC3D60BA2EDC2E /* CharClassify.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = CharClassify.cxx; path = ../../src/stc/scintilla/src/CharClassify.cxx; sourceTree = ""; };
5B32A13D5B3336098B1B9765 /* pngtrans.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = pngtrans.c; path = ../../src/png/pngtrans.c; sourceTree = ""; };
+ 5B83407D156C3CC3A66F05A4 /* fswatcher_fsevents.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = fswatcher_fsevents.cpp; path = ../../src/osx/fswatcher_fsevents.cpp; sourceTree = ""; };
5B9586328A1F3C4BA0390AA5 /* time.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = time.cpp; path = ../../src/common/time.cpp; sourceTree = ""; };
5BD6231188AB329CAA5E1171 /* evtloop_cf.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = evtloop_cf.cpp; path = ../../src/osx/core/evtloop_cf.cpp; sourceTree = ""; };
5BEC6B3CAFB532CBB9F95D74 /* jutils.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = jutils.c; path = ../../src/jpeg/jutils.c; sourceTree = ""; };
@@ -2619,6 +2621,7 @@
DC75C7251C1732B0B07C7BD3 /* utilsunx.cpp */,
B38F3D4DC6D139BA93401F7A /* wakeuppipe.cpp */,
C019CE87CF9931B0B77C0823 /* fswatcher_kqueue.cpp */,
+ 5B83407D156C3CC3A66F05A4 /* fswatcher_fsevents.cpp */,
7A34C5BBBA543DC0A50DE1B6 /* event.cpp */,
C9A305CEC03B3085B159B617 /* fs_mem.cpp */,
E968913A9A593B258BD8EACB /* msgout.cpp */,
@@ -2930,6 +2933,7 @@
B5C7FD8C27F43F3289A77FC9 /* utilsunx.cpp in Sources */,
F9C5EAC42CCF3267B4100BAE /* wakeuppipe.cpp in Sources */,
FF7DB2884F6E3C5DB4BDF61D /* fswatcher_kqueue.cpp in Sources */,
+ 830A61EA04FD367C9EB6A757 /* fswatcher_fsevents.cpp in Sources */,
55D893FDD00633FEA82ABD81 /* event.cpp in Sources */,
131B879180AE3FB481F81CC7 /* fs_mem.cpp in Sources */,
05814571E7A83F5DBFB6E4C4 /* msgout.cpp in Sources */,
diff --git a/docs/changes.txt b/docs/changes.txt
index 690b17f601..d1182e9302 100644
--- a/docs/changes.txt
+++ b/docs/changes.txt
@@ -161,6 +161,7 @@ wxMSW:
wxOSX/Cocoa:
+- Use more efficient FSEvents (10.7+) in wxFileSystemWatcher (Roberto Perpuly).
- Implement wxWindow::Disable() for non-native controls too (Steve Browne).
- Fix wxEVT_CHAR for non-BMP Unicode characters (ARATA Mizuki).
- Add support for wxEVT_COMBOBOX_DROPDOWN and wxEVT_COMBOBOX_CLOSEUP
diff --git a/include/wx/fswatcher.h b/include/wx/fswatcher.h
index 7f0fcbd72e..a29ff834e6 100644
--- a/include/wx/fswatcher.h
+++ b/include/wx/fswatcher.h
@@ -52,7 +52,7 @@ enum
wxFSW_EVENT_RENAME | wxFSW_EVENT_MODIFY |
wxFSW_EVENT_ACCESS | wxFSW_EVENT_ATTRIB |
wxFSW_EVENT_WARNING | wxFSW_EVENT_ERROR
-#ifdef wxHAS_INOTIFY
+#if defined(wxHAS_INOTIFY) || defined(wxHAVE_FSEVENTS_FILE_NOTIFICATIONS)
,wxFSW_EVENT_UNMOUNT = 0x2000
#endif
};
@@ -395,6 +395,10 @@ protected:
#ifdef wxHAS_INOTIFY
#include "wx/unix/fswatcher_inotify.h"
#define wxFileSystemWatcher wxInotifyFileSystemWatcher
+#elif defined(wxHAS_KQUEUE) && defined(wxHAVE_FSEVENTS_FILE_NOTIFICATIONS)
+ #include "wx/unix/fswatcher_kqueue.h"
+ #include "wx/osx/fswatcher_fsevents.h"
+ #define wxFileSystemWatcher wxFsEventsFileSystemWatcher
#elif defined(wxHAS_KQUEUE)
#include "wx/unix/fswatcher_kqueue.h"
#define wxFileSystemWatcher wxKqueueFileSystemWatcher
diff --git a/include/wx/osx/cocoa/chkconf.h b/include/wx/osx/cocoa/chkconf.h
index 03fd501925..4cb5623b49 100644
--- a/include/wx/osx/cocoa/chkconf.h
+++ b/include/wx/osx/cocoa/chkconf.h
@@ -50,6 +50,16 @@
#define wxOSX_USE_QUICKTIME 0
#define wxOSX_USE_AUDIOTOOLBOX 1
+/*
+ Use the more efficient FSEvents API instead of kqueue
+ events for file system watcher, but only on OS X >= 10.7 since
+ that version introduced a flag that allows watching files as
+ well as sub directories.
+ */
+#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_7
+ #define wxHAVE_FSEVENTS_FILE_NOTIFICATIONS 1
+#endif
+
/*
* turning off capabilities that don't work under cocoa yet
*/
diff --git a/include/wx/osx/fswatcher_fsevents.h b/include/wx/osx/fswatcher_fsevents.h
new file mode 100644
index 0000000000..bbaa95d951
--- /dev/null
+++ b/include/wx/osx/fswatcher_fsevents.h
@@ -0,0 +1,88 @@
+/////////////////////////////////////////////////////////////////////////////
+// Name: wx/osx/fswatcher_fsevents.h
+// Purpose: File System watcher that uses the FSEvents API
+// of OS X to efficiently watch trees
+// Author: Roberto Perpuly
+// Created: 2015-04-24
+// Copyright: (c) 2015 Roberto Perpuly
+// Licence: wxWindows licence
+/////////////////////////////////////////////////////////////////////////////
+
+#ifndef _WX_FSWATCHER_FSEVENTS_H_
+#define _WX_FSWATCHER_FSEVENTS_H_
+
+#include "wx/defs.h"
+
+#if wxUSE_FSWATCHER
+
+#include
+#include "wx/unix/fswatcher_kqueue.h"
+
+WX_DECLARE_STRING_HASH_MAP(FSEventStreamRef, FSEventStreamRefMap);
+
+/*
+ The FSEvents watcher uses the newer FSEvents service
+ that is available in OS X, the service allows for
+ efficient watching of entire directory hierarchies.
+ Note that adding a single file watch (or directory
+ watch) still use kqueue events.
+
+ We take care to only use this on OS X >= 10.7, as that
+ version introduced the ability to get file-level notifications.
+
+ See the following docs that outline the FSEvents API
+
+ https://developer.apple.com/library/mac/documentation/Darwin/Conceptual/FSEvents_ProgGuide/UsingtheFSEventsFramework/UsingtheFSEventsFramework.html
+
+ https://developer.apple.com/library/mac/documentation/Darwin/Reference/FSEvents_Ref/index.html
+*/
+class WXDLLIMPEXP_BASE wxFsEventsFileSystemWatcher :
+ public wxKqueueFileSystemWatcher
+{
+public:
+ wxFsEventsFileSystemWatcher();
+
+ wxFsEventsFileSystemWatcher(const wxFileName& path,
+ int events = wxFSW_EVENT_ALL);
+
+ ~wxFsEventsFileSystemWatcher();
+
+ // reimplement adding a tree so that it does not use
+ // kqueue at all
+ bool AddTree(const wxFileName& path, int events = wxFSW_EVENT_ALL,
+ const wxString& filespec = wxEmptyString) wxOVERRIDE;
+
+ // reimplement removing a tree so that we
+ // cleanup the opened fs streams
+ bool RemoveTree(const wxFileName& path) wxOVERRIDE;
+
+ // reimplement remove all so that we cleanup
+ // watches from kqeueue and from FSEvents
+ bool RemoveAll() wxOVERRIDE;
+
+ // post an file change event to the owner
+ void PostChange(const wxFileName& oldFileName,
+ const wxFileName& newFileName, int event);
+
+ // post a warning event to the owner
+ void PostWarning(wxFSWWarningType warning, const wxString& msg);
+
+ // post an error event to the owner
+ void PostError(const wxString& msg);
+
+ // reimplement count to include the FS stream watches
+ int GetWatchedPathsCount() const;
+
+ // reimplement to include paths from FS stream watches
+ int GetWatchedPaths(wxArrayString* paths) const;
+
+private:
+
+ // map of path => FSEventStreamRef
+ FSEventStreamRefMap m_streams;
+
+};
+
+#endif /* wxUSE_FSWATCHER */
+
+#endif /* _WX_FSWATCHER_FSEVENTS_H_ */
diff --git a/interface/wx/fswatcher.h b/interface/wx/fswatcher.h
index cd4cd29a6f..40078e7e46 100644
--- a/interface/wx/fswatcher.h
+++ b/interface/wx/fswatcher.h
@@ -78,10 +78,11 @@ public:
Additionally a file mask can be specified to include only files
matching that particular mask.
- This method is implemented efficiently on MSW, but should be used with
- care on other platforms for directories with lots of children (e.g. the
- root directory) as it calls Add() for each subdirectory, potentially
- creating a lot of watches and taking a long time to execute.
+ This method is implemented efficiently on MSW and OS X >= 10.7, but
+ should be used with care on other platforms for directories with lots
+ of children (e.g. the root directory) as it calls Add() for each
+ subdirectory, potentially creating a lot of watches and taking a long
+ time to execute.
Note that on platforms that use symbolic links, you will probably want
to have called wxFileName::DontFollowLink on @a path. This is especially
@@ -242,8 +243,9 @@ enum wxFSWFlags
Notice that under MSW this event is sometimes -- although not always --
followed by a ::wxFSW_EVENT_MODIFY for the new file.
- Under OS X this event is currently not detected and instead separate
- ::wxFSW_EVENT_CREATE and ::wxFSW_EVENT_DELETE events are.
+ Under OS X this event is only detected when watching entire trees. When
+ watching directories, separate ::wxFSW_EVENT_CREATE and
+ ::wxFSW_EVENT_DELETE events are detected instead.
*/
wxFSW_EVENT_RENAME = 0x04,
@@ -253,7 +255,7 @@ enum wxFSWFlags
Depending on the program doing the file modification, multiple such
events can be reported for a single logical file update.
- Under OS X this event is currently not detected.
+ Under OS X this event is only detected when watching entire trees.
*/
wxFSW_EVENT_MODIFY = 0x08,
@@ -267,7 +269,8 @@ enum wxFSWFlags
/**
The item's metadata was changed, e.g.\ its permissions or timestamps.
- This event is currently only detected under Linux.
+ This event is currently only detected under Linux and OS X.
+ Under OS X this event is only detected when watching entire trees.
@since 2.9.5
*/
@@ -279,7 +282,8 @@ enum wxFSWFlags
wxFSW_EVENT_UNMOUNT cannot be set; unmount events are produced automatically. This flag
is therefore not included in wxFSW_EVENT_ALL.
- This event is currently only detected under Linux.
+ This event is currently only detected under Linux and OS X.
+ Under OS X this event is only detected when watching entire trees.
@since 2.9.5
*/
diff --git a/src/osx/fswatcher_fsevents.cpp b/src/osx/fswatcher_fsevents.cpp
new file mode 100644
index 0000000000..849beb97ec
--- /dev/null
+++ b/src/osx/fswatcher_fsevents.cpp
@@ -0,0 +1,509 @@
+/////////////////////////////////////////////////////////////////////////////
+// Name: src/osx/fswatcher_fsevents.cpp
+// Purpose: File System watcher that uses the FSEvents API
+// of OS X to efficiently watch trees
+// Author: Roberto Perpuly
+// Created: 2015-04-24
+// Copyright: (c) 2015 Roberto Perpuly
+// Licence: wxWindows licence
+/////////////////////////////////////////////////////////////////////////////
+
+// For compilers that support precompilation, includes "wx.h".
+#include "wx/wxprec.h"
+
+#ifdef __BORLANDC__
+ #pragma hdrstop
+#endif
+
+#if wxUSE_FSWATCHER && defined(wxHAVE_FSEVENTS_FILE_NOTIFICATIONS)
+
+#include "wx/fswatcher.h"
+#include "wx/osx/core/cfstring.h"
+#include
+
+// A small class that we will give the FSEvents
+// framework, which will be forwarded to the function
+// that gets called when files change.
+class wxFSEventWatcherContext
+{
+public:
+
+ // Watcher pointer will not be owned by this class.
+ wxFSEventWatcherContext(wxFsEventsFileSystemWatcher* watcher,
+ int watcherEventFlags,
+ const wxString& filespec)
+ : m_watcher(watcher)
+ , m_watcherEventFlags(watcherEventFlags)
+ , m_filespec(filespec)
+ {
+
+ }
+
+ // Will return true if the given event file and flags
+ // match the filespec and event flags given to the
+ // AddTree method.
+ bool IsDesiredEvent(const wxFileName& eventFileName, int eventFlags)
+ {
+ // warning and errors are always sent to the event handler
+ if ( eventFlags & wxFSW_EVENT_ERROR )
+ {
+ return true;
+ }
+ if ( eventFlags & wxFSW_EVENT_WARNING )
+ {
+ return true;
+ }
+
+ if ( (m_watcherEventFlags & eventFlags) == 0 )
+ {
+ // event handler does not want to see this event
+ return false;
+ }
+
+ return m_filespec.empty() ||
+ wxMatchWild(m_filespec, eventFileName.GetFullName());
+ }
+
+ wxFsEventsFileSystemWatcher* m_watcher;
+
+ // The event flags that the event handler
+ // desires to be notified of.
+ int m_watcherEventFlags;
+
+ // The filespec that the event handler
+ // desires to be notified of.
+ wxString m_filespec;
+
+private:
+
+ wxDECLARE_NO_COPY_CLASS(wxFSEventWatcherContext);
+};
+
+// Translate kFSEventStreamEventFlag* flags
+// to wxFSW_EVENT_* flags.
+// warning and msg are out parameters, filled in when
+// there is an error in the stream.
+static int wxFSEventsToWatcherFlags(FSEventStreamEventFlags flags,
+wxFSWWarningType& warning, wxString& msg)
+{
+ msg = "";
+ warning = wxFSW_WARNING_NONE;
+
+ // see https://developer.apple.com/library/mac/documentation/Darwin/Reference/FSEvents_Ref/index.html
+ // for event flag meanings
+ int ret = 0;
+ int warnings =
+ kFSEventStreamEventFlagMustScanSubDirs
+ | kFSEventStreamEventFlagUserDropped
+ | kFSEventStreamEventFlagKernelDropped
+ | kFSEventStreamEventFlagMount
+ ;
+
+ int errors = kFSEventStreamEventFlagRootChanged;
+
+ // The following flags are not handled:
+ // kFSEventStreamEventFlagHistoryDone (we never ask for old events)
+ // kFSEventStreamEventFlagEventIdsWrapped ( we don't keep track nor
+ // expose event IDs)
+
+ int created = kFSEventStreamEventFlagItemCreated;
+ int deleted = kFSEventStreamEventFlagItemRemoved;
+ int renamed = kFSEventStreamEventFlagItemRenamed;
+ int modified = kFSEventStreamEventFlagItemModified;
+ int attrib = kFSEventStreamEventFlagItemChangeOwner
+ | kFSEventStreamEventFlagItemFinderInfoMod
+ | kFSEventStreamEventFlagItemInodeMetaMod
+ | kFSEventStreamEventFlagItemXattrMod;
+
+ if ( created & flags )
+ {
+ ret |= wxFSW_EVENT_CREATE;
+ }
+ if ( deleted & flags )
+ {
+ ret |= wxFSW_EVENT_DELETE;
+ }
+ if ( renamed & flags )
+ {
+ ret |= wxFSW_EVENT_RENAME;
+ }
+ if ( modified & flags )
+ {
+ ret |= wxFSW_EVENT_MODIFY;
+ }
+ if ( attrib & flags )
+ {
+ ret |= wxFSW_EVENT_ATTRIB;
+ }
+ if ( kFSEventStreamEventFlagUnmount & flags )
+ {
+ ret |= wxFSW_EVENT_UNMOUNT;
+ }
+ if ( warnings & flags )
+ {
+ warning = wxFSW_WARNING_GENERAL;
+ ret |= wxFSW_EVENT_WARNING;
+ if (flags & kFSEventStreamEventFlagMustScanSubDirs)
+ {
+ msg += "Must re-scan sub directories.";
+ }
+ if (flags & kFSEventStreamEventFlagUserDropped)
+ {
+ msg += "User dropped events";
+ warning = wxFSW_WARNING_OVERFLOW;
+ }
+ if (flags & kFSEventStreamEventFlagKernelDropped)
+ {
+ msg += "Kernel dropped events";
+ warning = wxFSW_WARNING_OVERFLOW;
+ }
+ if (flags & kFSEventStreamEventFlagMount)
+ {
+ msg += "A volume was mounted underneath the watched directory.";
+ }
+ }
+ if ( errors & flags )
+ {
+ ret |= wxFSW_EVENT_ERROR;
+ msg = "Path being watched has been renamed";
+ }
+
+ // don't think that FSEvents tells us about wxFSW_EVENT_ACCESS
+ return ret;
+}
+
+// Fills in eventFileName appropriately based on whether the
+// event was on a file or a directory.
+static void FileNameFromEvent(wxFileName& eventFileName, char* path,
+ FSEventStreamEventFlags flags)
+{
+ wxString strPath(path);
+ if ( flags & kFSEventStreamEventFlagItemIsFile )
+ {
+ eventFileName.Assign(strPath);
+ }
+ if ( flags & kFSEventStreamEventFlagItemIsDir )
+ {
+ eventFileName.AssignDir(strPath);
+ }
+}
+
+// This is the function that the FsEvents API
+// will call to notify us that a file has been changed.
+static void wxFSEventCallback(ConstFSEventStreamRef WXUNUSED(streamRef), void *clientCallBackInfo,
+ size_t numEvents, void *eventPaths, const FSEventStreamEventFlags eventFlags[],
+ const FSEventStreamEventId WXUNUSED(eventIds)[])
+{
+ wxFSEventWatcherContext* context =
+ (wxFSEventWatcherContext*) clientCallBackInfo;
+
+ char** paths = (char**) eventPaths;
+ int lastWxEventFlags = 0;
+ wxFileName lastEventFileName;
+ wxString msg;
+ wxFSWWarningType warning = wxFSW_WARNING_NONE;
+ wxFileName eventFileName;
+ for ( size_t i = 0; i < numEvents; i++ )
+ {
+ FSEventStreamEventFlags flags = eventFlags[i];
+ FileNameFromEvent(eventFileName, paths[i], flags);
+ int wxEventFlags = wxFSEventsToWatcherFlags(flags, warning, msg);
+ if ( context->IsDesiredEvent(eventFileName, wxEventFlags) )
+ {
+ // This is a naive way of looking for file renames
+ // wx presents a renames with a from and to paths
+ // but fs events events do not give us this (it only
+ // provides that a file was renamed, not what the new
+ // name is).
+ // We deduce the old and new paths by looking for consecutive
+ // renames. This is very naive and won't catch simulatenous
+ // renames inside the latency period, nor renames from/to
+ // a directory that is not inside the watched paths.
+ if (wxEventFlags == wxFSW_EVENT_RENAME && lastWxEventFlags == wxFSW_EVENT_RENAME)
+ {
+ context->m_watcher->PostChange(lastEventFileName, eventFileName, wxEventFlags);
+ }
+ else if (flags == kFSEventStreamEventFlagRootChanged)
+ {
+ // send two events: the delete event and the error event
+ context->m_watcher->PostChange(eventFileName, eventFileName, wxFSW_EVENT_DELETE);
+ context->m_watcher->PostError(msg);
+ }
+ else if (wxEventFlags != wxFSW_EVENT_RENAME)
+ {
+ context->m_watcher->PostChange(eventFileName, eventFileName, wxEventFlags);
+ }
+ else
+ {
+ // This is a "rename" event that we only saw once, meaning that
+ // a file was renamed to somewhere inside the watched tree
+ // OR a file was renamed to somewhere outside the watched tree.
+ if (!eventFileName.IsDir())
+ {
+ int fileEventType = eventFileName.FileExists() ? wxFSW_EVENT_CREATE : wxFSW_EVENT_DELETE;
+ context->m_watcher->PostChange(eventFileName, eventFileName, fileEventType);
+ }
+ if (eventFileName.IsDir())
+ {
+ int dirEventType = eventFileName.DirExists() ? wxFSW_EVENT_CREATE : wxFSW_EVENT_DELETE;
+ context->m_watcher->PostChange(eventFileName, eventFileName, dirEventType);
+ }
+ }
+
+ if (wxEventFlags & wxFSW_EVENT_WARNING)
+ {
+ context->m_watcher->PostWarning(warning, msg);
+ }
+
+ // A single rename (without the second rename) may be due
+ // to the file being renamed into a directory outside of the
+ // watch path.
+ lastWxEventFlags = wxEventFlags;
+ lastEventFileName = eventFileName;
+ }
+ }
+}
+
+static void wxDeleteContext(const void* context)
+{
+ wxFSEventWatcherContext* watcherContext =
+ (wxFSEventWatcherContext*) context;
+ delete watcherContext;
+}
+
+wxFsEventsFileSystemWatcher::wxFsEventsFileSystemWatcher()
+: wxKqueueFileSystemWatcher()
+{
+
+}
+
+wxFsEventsFileSystemWatcher::wxFsEventsFileSystemWatcher(const wxFileName& path,
+ int events)
+: wxKqueueFileSystemWatcher(path, events)
+{
+
+}
+
+wxFsEventsFileSystemWatcher::~wxFsEventsFileSystemWatcher()
+{
+
+}
+
+bool wxFsEventsFileSystemWatcher::AddTree(const wxFileName& path, int events,
+ const wxString& filespec)
+{
+ if (!path.DirExists())
+ {
+ return false;
+ }
+ wxString canonical = GetCanonicalPath(path);
+ if ( canonical.empty() )
+ {
+ return false;
+ }
+ CFRunLoopRef cfLoop = CFRunLoopGetCurrent();
+ wxASSERT_MSG(
+ cfLoop,
+ "there must be a current event loop; this file watcher needs it."
+ );
+ if ( ! cfLoop )
+ {
+ return false;
+ }
+
+ if ( m_streams.find(canonical) != m_streams.end() )
+ {
+ // How to take into account filespec
+ // if client adds a watch for /home/*.cpp
+ // and then on another call wants to add a
+ // call to /home/*.h
+ // Ideally we should not create another watch
+ // however we would need to keep both filespecs
+ // around, which we don't do now.
+ return false;
+ }
+
+ // Will need to pass the desired event flags
+ // and filespec to our callback via the context
+ // we make sure to give the context a cleanup
+ // callback.
+ FSEventStreamContext ctx;
+ wxFSEventWatcherContext* watcherContext = new wxFSEventWatcherContext(
+ this, events, filespec.Clone()
+ );
+ ctx.version = 0;
+ ctx.info = watcherContext;
+ ctx.retain = NULL;
+ ctx.release = &wxDeleteContext;
+ ctx.copyDescription = NULL;
+ CFTimeInterval latency = 0.2;
+
+ wxMacUniCharBuffer pathChars(path.GetPath());
+ CFStringRef pathRef = CFStringCreateWithCharacters(
+ kCFAllocatorDefault,
+ pathChars.GetBuffer(),
+ pathChars.GetChars()
+ );
+ CFArrayRef pathRefs = CFArrayCreate(
+ kCFAllocatorDefault, (const void**)&pathRef, 1, NULL
+ );
+ FSEventStreamCreateFlags flags = kFSEventStreamCreateFlagWatchRoot
+ | kFSEventStreamCreateFlagFileEvents;
+
+ FSEventStreamRef stream = FSEventStreamCreate(
+ kCFAllocatorDefault,
+ &wxFSEventCallback,
+ &ctx,
+ pathRefs, kFSEventStreamEventIdSinceNow,
+ latency, flags);
+ bool started = false;
+ if ( stream )
+ {
+ FSEventStreamScheduleWithRunLoop(stream, cfLoop, kCFRunLoopDefaultMode);
+ started = FSEventStreamStart(stream);
+ if ( started )
+ {
+ m_streams[canonical] = stream;
+ }
+ }
+
+ // cleanup the paths, as we own the pointers
+ CFRelease(pathRef);
+ CFRelease(pathRefs);
+
+ wxASSERT_MSG(stream, "could not create FS stream");
+ return started;
+}
+
+bool wxFsEventsFileSystemWatcher::RemoveTree(const wxFileName& path)
+{
+ wxString canonical = GetCanonicalPath(path);
+ if ( canonical.empty() )
+ {
+ return false;
+ }
+
+ // Remove any kqueue watches created with Add()
+ // RemoveTree() should remove all watches no matter
+ // if they are tree watches or single directory watches.
+ wxArrayString dirsWatched;
+ wxKqueueFileSystemWatcher::GetWatchedPaths(&dirsWatched);
+ for ( size_t i = 0; i < dirsWatched.size(); i++ )
+ {
+ if (dirsWatched[i].Find(canonical) == 0)
+ {
+ wxKqueueFileSystemWatcher::Remove(dirsWatched[i]);
+ }
+ }
+
+ FSEventStreamRefMap::iterator it = m_streams.find(canonical);
+ bool removed = false;
+ if ( it != m_streams.end() )
+ {
+ FSEventStreamStop(it->second);
+ FSEventStreamInvalidate(it->second);
+ FSEventStreamRelease(it->second);
+ m_streams.erase(it);
+ removed = true;
+ }
+ return removed;
+}
+
+bool wxFsEventsFileSystemWatcher::RemoveAll()
+{
+ // remove all watches created with Add()
+ bool ret = wxKqueueFileSystemWatcher::RemoveAll();
+ FSEventStreamRefMap::iterator it = m_streams.begin();
+ while ( it != m_streams.end() )
+ {
+ FSEventStreamStop(it->second);
+ FSEventStreamInvalidate(it->second);
+ FSEventStreamRelease(it->second);
+ it++;
+ ret |= true;
+ }
+ m_streams.clear();
+ return ret;
+}
+
+void wxFsEventsFileSystemWatcher::PostChange(const wxFileName& oldFileName,
+ const wxFileName& newFileName, int event)
+{
+ wxASSERT_MSG(this->GetOwner(), "owner must exist");
+ if ( !this->GetOwner() )
+ {
+ return;
+ }
+
+ // FSEvents flags are a bit mask, but wx FSW events
+ // are not, meaning that FSEvent flag might be
+ // kFSEventStreamEventFlagItemCreated | kFSEventStreamEventFlagItemInodeMetaMod
+ // this means we must send 2 events not 1.
+ int allEvents[6] = {
+ wxFSW_EVENT_CREATE,
+ wxFSW_EVENT_DELETE,
+ wxFSW_EVENT_RENAME,
+ wxFSW_EVENT_MODIFY,
+ wxFSW_EVENT_ACCESS,
+ wxFSW_EVENT_ATTRIB
+ };
+
+ for ( int i = 0; i < WXSIZEOF(allEvents); i++ )
+ {
+ if ( event & allEvents[i] )
+ {
+ wxFileSystemWatcherEvent* evt = new wxFileSystemWatcherEvent(
+ allEvents[i], oldFileName, newFileName
+ );
+ wxQueueEvent(this->GetOwner(), evt);
+ }
+ }
+}
+
+void wxFsEventsFileSystemWatcher::PostWarning(wxFSWWarningType warning,
+ const wxString& msg)
+{
+ wxFileSystemWatcherEvent* evt = new wxFileSystemWatcherEvent(
+ wxFSW_EVENT_WARNING, warning, msg
+ );
+ wxASSERT_MSG(this->GetOwner(), "owner must exist");
+ if (this->GetOwner())
+ {
+ wxQueueEvent(this->GetOwner(), evt);
+ }
+}
+
+void wxFsEventsFileSystemWatcher::PostError(const wxString& msg)
+{
+ wxFileSystemWatcherEvent* evt = new wxFileSystemWatcherEvent(
+ wxFSW_EVENT_ERROR, wxFSW_WARNING_NONE, msg
+ );
+ wxASSERT_MSG(this->GetOwner(), "owner must exist");
+ if (this->GetOwner())
+ {
+ wxQueueEvent(this->GetOwner(), evt);
+ }
+}
+
+int wxFsEventsFileSystemWatcher::GetWatchedPathsCount() const
+{
+ return m_streams.size() + wxFileSystemWatcherBase::GetWatchedPathsCount();
+}
+
+int wxFsEventsFileSystemWatcher::GetWatchedPaths(wxArrayString* paths) const
+{
+ wxCHECK_MSG( paths != NULL, -1, "Null array passed to retrieve paths");
+ if ( !paths )
+ {
+ return 0;
+ }
+ wxFileSystemWatcherBase::GetWatchedPaths(paths);
+ FSEventStreamRefMap::const_iterator it = m_streams.begin();
+ for ( ; it != m_streams.end(); it++ )
+ {
+ paths->push_back(it->first);
+ }
+ return paths->size();
+}
+
+#endif // wxUSE_FSWATCHER
diff --git a/src/unix/fswatcher_kqueue.cpp b/src/unix/fswatcher_kqueue.cpp
index 5ac07964b7..0fd8023474 100644
--- a/src/unix/fswatcher_kqueue.cpp
+++ b/src/unix/fswatcher_kqueue.cpp
@@ -279,8 +279,8 @@ protected:
{
wxASSERT_MSG(e.udata, "Null user data associated with kevent!");
- wxLogTrace(wxTRACE_FSWATCHER, "Event: ident=%d, filter=%d, flags=%u, "
- "fflags=%u, data=%d, user_data=%p",
+ wxLogTrace(wxTRACE_FSWATCHER, "Event: ident=%llu, filter=%d, flags=%u, "
+ "fflags=%u, data=%lld, user_data=%lp",
e.ident, e.filter, e.flags, e.fflags, e.data, e.udata);
// for ease of use
diff --git a/tests/fswatcher/fswatchertest.cpp b/tests/fswatcher/fswatchertest.cpp
index 5968eae7d8..6561eb46ee 100644
--- a/tests/fswatcher/fswatchertest.cpp
+++ b/tests/fswatcher/fswatchertest.cpp
@@ -831,12 +831,12 @@ void FileSystemWatcherTestCase::TestTrees()
CPPUNIT_ASSERT(m_watcher);
size_t treeitems = 1; // the trunk
-#ifndef __WINDOWS__
- // When there's no file mask, wxMSW sets a single watch
+#if !defined(__WINDOWS__) && !defined(wxHAVE_FSEVENTS_FILE_NOTIFICATIONS)
+ // When there's no file mask, wxMSW and wxOSX set a single watch
// on the trunk which is implemented recursively.
// wxGTK always sets an additional watch for each subdir
treeitems += subdirs + 1; // +1 for 'child'
-#endif // __WINDOWS__
+#endif // !__WINDOWS__ && !wxHAVE_FSEVENTS_FILE_NOTIFICATIONS
// Store the initial count; there may already be some watches
const int initial = m_watcher->GetWatchedPathsCount();
@@ -862,9 +862,9 @@ void FileSystemWatcherTestCase::TestTrees()
// Except that in wxMSW this isn't true: each watch will be a
// single, recursive dir; so fudge the count
size_t fudge = 0;
-#ifdef __WINDOWS__
+#if defined(__WINDOWS__) || defined(wxHAVE_FSEVENTS_FILE_NOTIFICATIONS)
fudge = 1;
-#endif // __WINDOWS__
+#endif // __WINDOWS__ || wxHAVE_FSEVENTS_FILE_NOTIFICATIONS
m_watcher->AddTree(dir);
CPPUNIT_ASSERT_EQUAL(plustree + fudge, m_watcher->GetWatchedPathsCount());
m_watcher->RemoveTree(child);
@@ -900,7 +900,12 @@ void FileSystemWatcherTestCase::TestTrees()
// When we use a filter, both wxMSW and wxGTK implementations set
// an additional watch for each subdir (+1 for the root dir itself
// and another +1 for "child").
+ // On OS X, if we use FSEvents then we still only have 1 watch.
+#ifdef wxHAVE_FSEVENTS_FILE_NOTIFICATIONS
+ const size_t treeitems = 1;
+#else
const size_t treeitems = subdirs + 2;
+#endif
m_watcher->AddTree(dir, wxFSW_EVENT_ALL, "*.txt");
const int plustree = m_watcher->GetWatchedPathsCount();