diff --git a/components/common/cpp/CMakeLists.txt b/components/common/cpp/CMakeLists.txt
index 71715801d4e30b57f440d49b65de6445024a46de..69eaa390885295c10157144ee12fcf3e055662be 100644
--- a/components/common/cpp/CMakeLists.txt
+++ b/components/common/cpp/CMakeLists.txt
@@ -12,6 +12,7 @@ set(COMMONSRC
 	src/timer.cpp
 	src/profiler.cpp
 	src/exception.cpp
+	src/file.cpp
 	src/utility/base64.cpp
 )
 
@@ -27,7 +28,11 @@ target_include_directories(ftlcommon PUBLIC
 	$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
 	$<INSTALL_INTERFACE:include>
 	PRIVATE src)
-target_link_libraries(ftlcommon Threads::Threads Eigen3::Eigen ${OS_LIBS} ${OpenCV_LIBS} ${URIPARSER_LIBRARIES} ${CUDA_LIBRARIES})
+# for gcc < 9, not required for newer versions
+if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
+	set(CXX_FILESYSTEM_LIBRARIES "stdc++fs")
+endif()
+target_link_libraries(ftlcommon Threads::Threads Eigen3::Eigen ${OS_LIBS} ${OpenCV_LIBS} ${URIPARSER_LIBRARIES} ${CUDA_LIBRARIES} ${CXX_FILESYSTEM_LIBRARIES})
 
 target_precompile_headers(ftlcommon
 	PRIVATE include/ftl/cuda_common.hpp
diff --git a/components/common/cpp/include/ftl/configuration.hpp b/components/common/cpp/include/ftl/configuration.hpp
index 3f9118947a003c722c66e0023e8ec20c6424c1e5..821eb3386db355f8eb8d3df6ac43916f8427c0c4 100644
--- a/components/common/cpp/include/ftl/configuration.hpp
+++ b/components/common/cpp/include/ftl/configuration.hpp
@@ -20,7 +20,6 @@ extern std::string branch_name;
 class Configurable;
 
 bool is_directory(const std::string &path);
-bool is_file(const std::string &path);
 bool create_directory(const std::string &path);
 bool is_video(const std::string &file);
 std::vector<std::string> directory_listing(const std::string &path);
diff --git a/components/common/cpp/include/ftl/file.hpp b/components/common/cpp/include/ftl/file.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..bd72b63a734279ef8ec18618e5a766e602f9832c
--- /dev/null
+++ b/components/common/cpp/include/ftl/file.hpp
@@ -0,0 +1,26 @@
+#ifndef _FTL_FILES_HPP_
+#define _FTL_FILES_HPP_
+
+#if defined(__GNUC__) && __GNUC__ < 8
+#include <experimental/filesystem>
+namespace std {
+namespace filesystem = experimental::filesystem;
+}
+
+#else
+#include <filesystem>
+#endif
+
+namespace ftl {
+namespace file {
+
+std::filesystem::path home_dir();
+std::filesystem::path config_dir();
+std::filesystem::path config_dir(const std::filesystem::path& subdirectory);
+
+bool is_file(const std::filesystem::path &path);
+
+}
+}
+
+#endif
\ No newline at end of file
diff --git a/components/common/cpp/src/configuration.cpp b/components/common/cpp/src/configuration.cpp
index 0e327b539c284381d30834572814e6523b7ae992..f03d77e16e4b3bf21253f3c01ff8e7984bf55773 100644
--- a/components/common/cpp/src/configuration.cpp
+++ b/components/common/cpp/src/configuration.cpp
@@ -23,6 +23,7 @@
 #include <ftl/threads.hpp>
 #include <ftl/timer.hpp>
 #include <ftl/cuda_common.hpp>
+#include <ftl/file.hpp>
 
 #include <ftl/profiler.hpp>
 
@@ -39,7 +40,10 @@ using std::string;
 using std::map;
 using std::vector;
 using std::optional;
-using ftl::is_file;
+
+using ftl::file::is_file;
+using ftl::file::config_dir;
+
 using ftl::is_directory;
 using ftl::Configurable;
 
@@ -77,21 +81,6 @@ bool ftl::is_directory(const std::string &path) {
 #endif
 }
 
-bool ftl::is_file(const std::string &path) {
-#ifdef WIN32
-	DWORD attrib = GetFileAttributesA(path.c_str());
-	if (attrib == INVALID_FILE_ATTRIBUTES) return false;
-	else return !(attrib & FILE_ATTRIBUTE_DIRECTORY);
-#else
-	struct stat s;
-	if (::stat(path.c_str(), &s) == 0) {
-		return S_ISREG(s.st_mode);
-	} else {
-		return false;
-	}
-#endif
-}
-
 std::vector<std::string> ftl::directory_listing(const std::string &path) {
 	std::vector<std::string> res;
 
@@ -125,7 +114,7 @@ std::vector<std::string> ftl::directory_listing(const std::string &path) {
 }
 
 static bool endsWith(const string &s, const string &e) {
-	return s.size() >= e.size() && 
+	return s.size() >= e.size() &&
 				s.compare(s.size() - e.size(), e.size(), e) == 0;
 }
 
@@ -155,7 +144,7 @@ optional<string> ftl::config::locateFile(const string &name) {
 	if (is_file(name)) return name;
 
 	auto paths = rootCFG->getConfig()["paths"];
-	
+
 	if (paths.is_array()) {
 		vector<string> vpaths = paths.get<vector<string>>();
 		for (string p : vpaths) {
@@ -163,16 +152,16 @@ optional<string> ftl::config::locateFile(const string &name) {
 				if (is_file(p+"/"+name)) {
 					return p+"/"+name;
 				}
-			} else if (p.size() >= name.size() && 
+			} else if (p.size() >= name.size() &&
 					p.compare(p.size() - name.size(), name.size(), name) == 0 &&
 					is_file(p)) {
 				return p;
 			}
 		}
 	}
-	
+
 	if (is_file("./"+name)) return "./"+name;
-	if (is_file(string(FTL_LOCAL_CONFIG_ROOT) +"/"+ name)) return string(FTL_LOCAL_CONFIG_ROOT) +"/"+ name;
+	if (is_file(config_dir("ftl") / name)) return (ftl::file::config_dir("ftl") / name).string();
 	if (is_file(string(FTL_GLOBAL_CONFIG_ROOT) +"/"+ name)) return string(FTL_GLOBAL_CONFIG_ROOT) +"/"+ name;
 	return {};
 }
@@ -287,7 +276,7 @@ ftl::Configurable *ftl::config::find(const std::string &uri) {
 	}
 
 	SHARED_LOCK(mutex, lk);
-	
+
 	auto ix = config_instance.find(actual_uri);
 	if (ix == config_instance.end()) {
 		auto ix = config_alias.find(actual_uri);
@@ -512,7 +501,7 @@ json_t &ftl::config::resolve(json_t &j) {
 static bool findConfiguration(const string &file, const vector<string> &paths) {
 	bool f = false;
 	bool found = false;
-	
+
 	if (file.length() > 0) {
 		f = mergeConfig(file.substr(1,file.length()-2));
 		found |= f;
@@ -546,7 +535,7 @@ static bool findConfiguration(const string &file, const vector<string> &paths) {
 		f = mergeConfig("./config.jsonc");
 		found |= f;
 		if (f) LOG(INFO) << "Loaded config: " << "./config.jsonc";
-		
+
 		for (auto p : paths) {
 			if (is_directory(p)) {
 				f = mergeConfig(p+"/config.json");
@@ -648,8 +637,8 @@ static void signalIntHandler( int signum ) {
    if (sig_int_called) quick_exit(-1);
    sig_int_called = true;
 
-   // cleanup and close up stuff here  
-   // terminate program  
+   // cleanup and close up stuff here
+   // terminate program
 
    ftl::running = false;
 }
@@ -871,7 +860,7 @@ Configurable *ftl::config::configure(int argc, char **argv, const std::string &r
 
 	// Process Arguments
 	auto options = ftl::config::read_options(&argv, &argc);
-	
+
 	vector<string> paths(argc);
 	while (argc-- > 0) {
 		paths.push_back(argv[0]);
diff --git a/components/common/cpp/src/file.cpp b/components/common/cpp/src/file.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b595e09d0694e8d362dbaf73177e2092672521a6
--- /dev/null
+++ b/components/common/cpp/src/file.cpp
@@ -0,0 +1,41 @@
+#include "ftl/file.hpp"
+
+#include <cstdlib>
+
+using std::filesystem::path;
+
+namespace ftl {
+namespace file {
+
+path home_dir() {
+	char* home;
+	#if defined(_MSC_VER)
+	home = std::getenv("HOMEPATH");
+	#elif defined(__GNUC__)
+	home = std::getenv("HOME");
+	#else
+	static_assert(false, "unsupported compiler");
+	#endif
+	return std::filesystem::absolute(path(home));
+}
+
+path config_dir(const path& subdir) {
+	#if defined(_MSC_VER)
+	return path(std::getenv("APPDATA")) / subdir;
+	#elif defined(__GNUC__)
+	return home_dir() / ".config";
+	#else
+	static_assert(false, "unsupported compiler");
+	#endif
+}
+
+path config_dir() {
+	return config_dir(path());
+}
+
+bool is_file(const path& p) {
+	return std::filesystem::is_regular_file(p);
+}
+
+}
+}
diff --git a/components/rgbd-sources/src/source.cpp b/components/rgbd-sources/src/source.cpp
index 4a4d5e92549042e606f9241e687b817205387e20..564b95043345ea07a89b754e645052d3969209a7 100644
--- a/components/rgbd-sources/src/source.cpp
+++ b/components/rgbd-sources/src/source.cpp
@@ -1,4 +1,6 @@
 #include <loguru.hpp>
+#include <ftl/file.hpp>
+
 #include <ftl/rgbd/source.hpp>
 #include "basesource.hpp"
 #include <ftl/threads.hpp>
@@ -29,6 +31,8 @@ using ftl::rgbd::detail::ScreenCapture;
 using ftl::codecs::Channel;
 using ftl::rgbd::Camera;
 
+using ftl::file::is_file;
+
 Source::Source(ftl::config::json_t &cfg) : Configurable(cfg) {
 	impl_ = nullptr;
 	stream_ = 0;
@@ -57,7 +61,7 @@ static ftl::rgbd::BaseSourceImpl *createFileImpl(const ftl::URI &uri, Source *ho
 	if (eix == string::npos) {
 		// Might be a directory
 		if (ftl::is_directory(path)) {
-			if (ftl::is_file(path + "/video.mp4")) {
+			if (is_file(path + "/video.mp4")) {
 				return new StereoVideoSource(host, path);
 //			} else if (ftl::is_file(path + "/im0.png")) {
 //				return new MiddleburySource(this, path);
@@ -67,7 +71,7 @@ static ftl::rgbd::BaseSourceImpl *createFileImpl(const ftl::URI &uri, Source *ho
 		} else {
 			return nullptr;
 		}
-	} else if (ftl::is_file(path)) {
+	} else if (is_file(path)) {
 		string ext = path.substr(eix+1);
 
 		if (ext == "ftl") {
@@ -81,7 +85,7 @@ static ftl::rgbd::BaseSourceImpl *createFileImpl(const ftl::URI &uri, Source *ho
 		} else if (ext == "mp4") {
 			return new StereoVideoSource(host, path);
 		} else {
-			LOG(WARNING) << "Unrecognised file type: " << path;	
+			LOG(WARNING) << "Unrecognised file type: " << path;
 		}
 	} else {
 		LOG(WARNING) << "File does not exist: " << path;
diff --git a/components/rgbd-sources/src/sources/stereovideo/stereovideo.cpp b/components/rgbd-sources/src/sources/stereovideo/stereovideo.cpp
index b572e0e4e7d5f43cbee8fd8dfb352555d5c02dfb..aade34c89124470862ed3bc7cd7a72c506f777f2 100644
--- a/components/rgbd-sources/src/sources/stereovideo/stereovideo.cpp
+++ b/components/rgbd-sources/src/sources/stereovideo/stereovideo.cpp
@@ -1,5 +1,7 @@
 #include <loguru.hpp>
 
+#include <ftl/file.hpp>
+
 #include <unordered_set>
 
 #include <Eigen/Eigen>
@@ -42,6 +44,7 @@ using ftl::codecs::Channel;
 using std::string;
 using ftl::rgbd::Capability;
 
+using ftl::file::config_dir;
 
 static cv::Mat rmat(const cv::Vec3d &rvec) {
 	cv::Mat R(cv::Size(3, 3), CV_64FC1);
@@ -165,10 +168,12 @@ void StereoVideoSource::init(const string &file) {
 	}
 	else {
 		fname_calib_ = fname_config ?	*fname_config :
-										string(FTL_LOCAL_CONFIG_ROOT) + "/"
-										+ std::string("calibration.yml");
+										(config_dir() / "ftl" / "calibration.yml").string();
 
-		LOG(ERROR) << "No calibration file found, calibration will be saved to " + fname;
+		LOG(ERROR)	<< "No calibration file found in "
+					<< fname_calib_
+					<< ". Calibration will be saved to "
+					<< fname;
 	}
 
 	// Generate camera parameters for next frame