Skip to content

Commit

Permalink
macOS: Implement file monitoring with NSFilePresenter
Browse files Browse the repository at this point in the history
wxFileSystemWatcher / FSEvents monitoring requires monitoring the entire
directory, not just single file. This in turn triggers privacy warnings
if the file opened is in ~/Desktop, ~/Downloads or ~/Documents - even
when the file is opened explicitly, which normally shouldn’t trigger it.

Fix this by using a minimal implementation of NSFilePresenter and its
presentedItemDidChange: method.
  • Loading branch information
vslavik committed Sep 23, 2023
1 parent 1bf4c40 commit 67829fb
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 23 deletions.
2 changes: 1 addition & 1 deletion Poedit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@
B2380F981A9B821200B7D8C9 /* crowdin_gui.cpp */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; path = crowdin_gui.cpp; sourceTree = "<group>"; };
B2380F991A9B821200B7D8C9 /* crowdin_gui.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = crowdin_gui.h; sourceTree = "<group>"; };
B238F67326123166002D6845 /* filemonitor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = filemonitor.h; sourceTree = "<group>"; };
B238F674261237C4002D6845 /* filemonitor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = filemonitor.cpp; sourceTree = "<group>"; };
B238F674261237C4002D6845 /* filemonitor.cpp */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; path = filemonitor.cpp; sourceTree = "<group>"; };
B240FFC519C6E32900777AFE /* suggestions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = suggestions.h; path = tm/suggestions.h; sourceTree = "<group>"; };
B240FFC619C6F1A600777AFE /* suggestions.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = suggestions.cpp; path = tm/suggestions.cpp; sourceTree = "<group>"; };
B248B2DE170D765100EBA58E /* GettextTools.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GettextTools.bundle; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down
133 changes: 113 additions & 20 deletions src/filemonitor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include "filemonitor.h"

#include "edframe.h"
#include "str_helpers.h"

#include <wx/fswatcher.h>

Expand All @@ -34,6 +35,72 @@
#include <vector>


#ifdef __WXOSX__

// We can't use wxFileSystemWatcher, because it monitors the directory, not the file, and that
// triggers scary warnings on macOS if the directory is ~/Desktop, ~/Downloads etc.
// So instead, use a minimal sufficient NSFilePresenter for the monitoring

#include <Foundation/Foundation.h>

@interface POFilePresenter : NSObject<NSFilePresenter>
@property(readwrite, copy) NSURL* presentedItemURL;
@property(readwrite, assign) NSOperationQueue* presentedItemOperationQueue;
@property FileMonitor::Impl* owner;

-(instancetype)initWithOwner:(FileMonitor::Impl*)owner URL:(NSURL*)url;
@end

class FileMonitor::Impl
{
public:
Impl(const wxFileName& fn)
{
m_path = fn.GetFullPath();
NSURL *url = [NSURL fileURLWithPath:str::to_NS(m_path)];
m_presenter = [[POFilePresenter alloc] initWithOwner:this URL:url];
[NSFileCoordinator addFilePresenter:m_presenter];
}

~Impl()
{
[NSFileCoordinator removeFilePresenter:m_presenter];
m_presenter = nil;
}

void OnChanged()
{
FileMonitor::NotifyFileChanged(m_path);
}

private:
wxString m_path;
POFilePresenter *m_presenter;
};

@implementation POFilePresenter

-(instancetype)initWithOwner:(FileMonitor::Impl*)owner URL:(NSURL*)url
{
self = [super init];
if (self)
{
self.owner = owner;
self.presentedItemURL = url;
self.presentedItemOperationQueue = NSOperationQueue.mainQueue;
}
return self;
}

- (void)presentedItemDidChange
{
self.owner->OnChanged();
}

@end

#else // !__WXOSX__

namespace
{

Expand All @@ -53,12 +120,7 @@ class FSWatcher
{
if (m_watcher)
{
#ifdef __WXOSX__
// kqueue-based monitoring is unreliable on macOS, we need to use AddTree() to force FSEvents usage
m_watcher->AddTree(dir, MONITORING_FLASG);
#else
m_watcher->Add(dir, MONITORING_FLASG);
#endif
}
else
{
Expand All @@ -71,11 +133,7 @@ class FSWatcher
{
if (m_watcher)
{
#ifdef __WXOSX__
m_watcher->RemoveTree(dir);
#else
m_watcher->Remove(dir);
#endif
}
else
{
Expand All @@ -95,9 +153,7 @@ class FSWatcher
auto fn = event.GetNewPath();
if (!fn.IsOk())
return;
auto window = PoeditFrame::Find(fn.GetFullPath());
if (window)
window->ReloadFileIfChanged();
FileMonitor::NotifyFileChanged(fn.GetFullPath());
});

for (auto& dir : m_pending)
Expand All @@ -120,21 +176,54 @@ std::unique_ptr<FSWatcher> FSWatcher::ms_instance;

} // anonymous namespace

class FileMonitor::Impl
{
public:
Impl(const wxFileName& fn)
{
m_dir = wxFileName::DirName(fn.GetPath());
FSWatcher::Get().Add(m_dir);
}

~Impl()
{
FSWatcher::Get().Remove(m_dir);
}

private:
wxFileName m_dir;
};

#endif // !__WXOSX__


void FileMonitor::EventLoopStarted()
{
#ifndef __WXOSX__
FSWatcher::Get().EventLoopStarted();
#endif
}

void FileMonitor::CleanUp()
{
#ifndef __WXOSX__
FSWatcher::CleanUp();
#endif
}


FileMonitor::FileMonitor() : m_isRespondingGuard(false)
{
}

FileMonitor::~FileMonitor()
{
Reset();
}

void FileMonitor::SetFile(wxFileName file)
{
// unmonitor first (needed even if the filename didn't change)xx
// unmonitor first (needed even if the filename didn't change)
if (file == m_file)
{
m_loadTime = m_file.GetModificationTime();
Expand All @@ -146,17 +235,21 @@ void FileMonitor::SetFile(wxFileName file)
m_file = file;
if (!m_file.IsOk())
return;
m_dir = wxFileName::DirName(m_file.GetPath());

FSWatcher::Get().Add(m_dir);
m_impl = std::make_unique<Impl>(m_file);
m_loadTime = m_file.GetModificationTime();
}

void FileMonitor::Reset()
{
if (m_file.IsOk())
{
FSWatcher::Get().Remove(m_dir);
m_file.Clear();
}
m_impl.reset();
m_file.Clear();
}

void FileMonitor::NotifyFileChanged(const wxString& path)
{
auto window = PoeditFrame::Find(path);
if (window)
window->ReloadFileIfChanged();
}

12 changes: 10 additions & 2 deletions src/filemonitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,14 @@

#include <wx/filename.h>

#include <memory>


class FileMonitor
{
public:
FileMonitor() : m_isRespondingGuard(false) {}
~FileMonitor() { Reset(); }
FileMonitor();
~FileMonitor();

void SetFile(wxFileName file);

Expand Down Expand Up @@ -87,6 +89,10 @@ class FileMonitor
static void EventLoopStarted();
static void CleanUp();

// the following is public only for the needs of filemonitor.cpp implementations:
class Impl;
static void NotifyFileChanged(const wxString& path);

private:
void Reset();

Expand All @@ -95,6 +101,8 @@ class FileMonitor
wxString m_monitoredPath;
wxFileName m_file, m_dir;
wxDateTime m_loadTime;

std::unique_ptr<Impl> m_impl;
};


Expand Down

0 comments on commit 67829fb

Please sign in to comment.