--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/FileMonitor.cpp Sat Dec 02 15:02:56 2023 +0100
@@ -0,0 +1,127 @@
+/**
+ * ShaderShark
+ * Copyright © 2023 František Kučera (Frantovo.cz, GlobalCode.info)
+ *
+ * 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, version 3 of the License.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string>
+#include <stdexcept>
+#include <map>
+#include <sys/inotify.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "FileMonitor.h"
+
+void checkError(bool hasError, const std::string& message) {
+ if (hasError) throw std::logic_error(message + strerror(errno));
+}
+
+class WatchedFile::Impl {
+public:
+ int fd;
+ int wd;
+ std::string fileName;
+ uint32_t mask;
+ std::shared_ptr<FileMonitor::Impl> fileMonitorImpl;
+
+ virtual ~Impl();
+};
+
+WatchedFile::WatchedFile() {
+ impl = std::make_shared<Impl>();
+}
+
+WatchedFile::~WatchedFile() {
+}
+
+class FileMonitor::Impl {
+public:
+ int fd;
+ std::map<int, std::string> fileNames;
+
+ void unwatch(int wd) {
+ int result = inotify_rm_watch(fd, wd);
+ checkError(result < 0, "unable to stop monitoring a file: ");
+ fileNames.erase(wd);
+ }
+
+ virtual ~Impl() {
+ close(fd);
+ }
+
+};
+
+FileMonitor::FileMonitor() : FileMonitor(std::make_shared<Impl>()) {
+}
+
+FileMonitor::FileMonitor(std::shared_ptr<Impl> impl) {
+ this->impl = impl;
+ impl->fd = inotify_init1(IN_NONBLOCK);
+ checkError(impl->fd < 0, "unable to initialize FileMonitor: ");
+}
+
+FileMonitor::~FileMonitor() {
+}
+
+int FileMonitor::getFD() const {
+ return impl->fd;
+}
+
+WatchedFile FileMonitor::watch(const std::string& fileName) {
+ return watch(fileName, IN_CLOSE_WRITE);
+}
+
+WatchedFile FileMonitor::watch(const std::string& fileName, uint32_t mask) {
+ int wd = inotify_add_watch(impl->fd, fileName.c_str(), mask);
+ checkError(wd < 0, "unable to monitor a file: ");
+ impl->fileNames[wd] = fileName;
+ WatchedFile wf;
+ wf.impl->fd = impl->fd;
+ wf.impl->wd = wd;
+ wf.impl->fileMonitorImpl = impl;
+ wf.impl->fileName = fileName;
+ wf.impl->mask = mask;
+ return wf;
+}
+
+bool FileMonitor::readEvent(FileEvent& event) {
+ struct inotify_event rawEvent;
+ int result = ::read(impl->fd, &rawEvent, sizeof (rawEvent));
+ if (result == sizeof (rawEvent)) {
+ event.fileName = impl->fileNames[rawEvent.wd];
+ event.mask = rawEvent.mask;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+WatchedFile::Impl::~Impl() {
+ fileMonitorImpl->unwatch(wd);
+}
+
+FileMonitor WatchedFile::getFileMonitor() const {
+ return FileMonitor(impl->fileMonitorImpl);
+}
+
+std::string WatchedFile::getFileName() const {
+ return impl->fileName;
+}
+
+uint32_t WatchedFile::getMask() const {
+ return impl->mask;
+}
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/FileMonitor.h Sat Dec 02 15:02:56 2023 +0100
@@ -0,0 +1,70 @@
+/**
+ * ShaderShark
+ * Copyright © 2023 František Kučera (Frantovo.cz, GlobalCode.info)
+ *
+ * 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, version 3 of the License.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <string>
+#include <memory>
+
+class FileMonitor;
+
+class WatchedFile {
+public:
+ class Impl;
+ WatchedFile();
+ virtual ~WatchedFile();
+ std::string getFileName() const;
+ uint32_t getMask() const;
+ FileMonitor getFileMonitor() const;
+private:
+ std::shared_ptr<Impl> impl;
+ friend FileMonitor;
+};
+
+class FileEvent {
+public:
+ std::string fileName;
+ uint32_t mask;
+ // TODO: more information, see: man 7 inotify
+};
+
+class FileMonitor {
+public:
+ class Impl;
+ FileMonitor();
+ virtual ~FileMonitor();
+ /**
+ * @return file descriptor that can be monitored by e.g. epoll
+ */
+ int getFD() const;
+ /**
+ * @param fileName file to be monitored
+ * @param mask IN_CLOSE_WRITE and other IN_*
+ * @return monitoring will continue until any copy of this object exists
+ */
+ WatchedFile watch(const std::string& fileName);
+ WatchedFile watch(const std::string& fileName, uint32_t mask);
+ /**
+ * @param event object to be filled with information
+ * @return true if event was filled, call this method until false returned
+ */
+ bool readEvent(FileEvent& event);
+private:
+ std::shared_ptr<Impl> impl;
+ FileMonitor(std::shared_ptr<Impl> impl);
+ friend WatchedFile;
+};
--- a/Makefile Fri Dec 01 21:02:02 2023 +0100
+++ b/Makefile Sat Dec 02 15:02:56 2023 +0100
@@ -26,7 +26,13 @@
build:
mkdir -p $(@)
-SRC=Shark.cpp shader-shark.cpp ImageLoader.cpp Shader.cpp Program.cpp
+SRC= \
+ Shark.cpp \
+ shader-shark.cpp \
+ ImageLoader.cpp \
+ Shader.cpp \
+ Program.cpp \
+ FileMonitor.cpp
build/shader-shark: $(SRC) build *.h
$(CXX) -std=c++20 -g -o $(@) $(SRC) $$(pkg-config --cflags --libs \
--- a/Shark.cpp Fri Dec 01 21:02:02 2023 +0100
+++ b/Shark.cpp Sat Dec 02 15:02:56 2023 +0100
@@ -102,6 +102,7 @@
int x11fd = XConnectionNumber(dpy);
EPoll epoll;
epoll.add(x11fd);
+ epoll.add(fileMonitor.getFD());
try {
epoll.add(setNonBlocking(STDIN_FILENO));
} catch (const EPoll::Exception& e) {
@@ -211,6 +212,14 @@
}
logOutput << std::endl;
+ } else if (epoll[epollEvent].data.fd == fileMonitor.getFD()) {
+ std::cout << "FileMonitor event:" << std::endl;
+ for (FileEvent fe; fileMonitor.readEvent(fe);) {
+ std::cout << " "
+ << " file: " << fe.fileName
+ << " mask: " << fe.mask
+ << std::endl;
+ }
} else {
logOutput
<< "error: event on an unexpected FD: "
@@ -353,6 +362,7 @@
void Shark::loadTextures() {
for (const Configuration::Texture& tex : cfg.textures) {
textures.push_back(loadTexture(tex.fileName));
+ watchedFiles.push_back(fileMonitor.watch(tex.fileName));
// TODO: review texture loading and binding
// works even without this - default texture
// glUniform1i(ProgAttr.jazz, jazz);
@@ -400,6 +410,7 @@
program->attachShader(*shader.get());
shaders.push_back(shader);
+ watchedFiles.push_back(fileMonitor.watch(fileName));
std::cerr << "GLSL loaded: " << fileName.c_str() << std::endl;
}
--- a/Shark.h Fri Dec 01 21:02:02 2023 +0100
+++ b/Shark.h Sat Dec 02 15:02:56 2023 +0100
@@ -37,6 +37,7 @@
#include "ImageLoader.h"
#include "Shader.h"
#include "Program.h"
+#include "FileMonitor.h"
class Shark {
private:
@@ -174,7 +175,9 @@
Window win;
XVisualInfo* vi;
GLXContext glc;
-
+
+ FileMonitor fileMonitor;
+ std::vector<WatchedFile> watchedFiles;
ImageLoader imageLoader;
std::vector<std::shared_ptr<Shader>> shaders;
std::shared_ptr<Program> shaderProgram;
--- a/nbproject/configurations.xml Fri Dec 01 21:02:02 2023 +0100
+++ b/nbproject/configurations.xml Sat Dec 02 15:02:56 2023 +0100
@@ -2,6 +2,7 @@
<configurationDescriptor version="100">
<logicalFolder name="root" displayName="root" projectFiles="true" kind="ROOT">
<df root="." name="0">
+ <in>FileMonitor.cpp</in>
<in>ImageLoader.cpp</in>
<in>Program.cpp</in>
<in>Shader.cpp</in>
@@ -55,6 +56,10 @@
<preBuildCommand></preBuildCommand>
</preBuild>
</makefileType>
+ <item path="FileMonitor.cpp" ex="false" tool="1" flavor2="0">
+ <ccTool flags="0">
+ </ccTool>
+ </item>
<item path="ImageLoader.cpp" ex="false" tool="1" flavor2="0">
<ccTool flags="0">
</ccTool>