aboutsummaryrefslogtreecommitdiffstats
path: root/common/core
diff options
context:
space:
mode:
Diffstat (limited to 'common/core')
-rw-r--r--common/core/CMakeLists.txt32
-rw-r--r--common/core/Configuration.cxx857
-rw-r--r--common/core/Configuration.h348
-rw-r--r--common/core/Exception.cxx134
-rw-r--r--common/core/Exception.h75
-rw-r--r--common/core/LogWriter.cxx135
-rw-r--r--common/core/LogWriter.h114
-rw-r--r--common/core/Logger.cxx95
-rw-r--r--common/core/Logger.h71
-rw-r--r--common/core/Logger_file.cxx123
-rw-r--r--common/core/Logger_file.h53
-rw-r--r--common/core/Logger_stdio.cxx37
-rw-r--r--common/core/Logger_stdio.h39
-rw-r--r--common/core/Logger_syslog.cxx68
-rw-r--r--common/core/Logger_syslog.h41
-rw-r--r--common/core/Rect.h120
-rw-r--r--common/core/Region.cxx203
-rw-r--r--common/core/Region.h85
-rw-r--r--common/core/Timer.cxx155
-rw-r--r--common/core/Timer.h136
-rw-r--r--common/core/string.cxx650
-rw-r--r--common/core/string.h80
-rw-r--r--common/core/time.cxx88
-rw-r--r--common/core/time.h58
-rw-r--r--common/core/winerrno.h91
-rw-r--r--common/core/xdgdirs.cxx162
-rw-r--r--common/core/xdgdirs.h74
27 files changed, 4124 insertions, 0 deletions
diff --git a/common/core/CMakeLists.txt b/common/core/CMakeLists.txt
new file mode 100644
index 00000000..7e58acc0
--- /dev/null
+++ b/common/core/CMakeLists.txt
@@ -0,0 +1,32 @@
+add_library(core STATIC
+ Configuration.cxx
+ Exception.cxx
+ Logger.cxx
+ Logger_file.cxx
+ Logger_stdio.cxx
+ LogWriter.cxx
+ Region.cxx
+ Timer.cxx
+ string.cxx
+ time.cxx
+ xdgdirs.cxx)
+
+target_include_directories(core PUBLIC ${CMAKE_SOURCE_DIR}/common)
+target_include_directories(core SYSTEM PUBLIC ${PIXMAN_INCLUDE_DIRS})
+target_link_libraries(core ${PIXMAN_LIBRARIES})
+
+if(UNIX)
+ target_sources(core PRIVATE Logger_syslog.cxx)
+endif()
+
+if(WIN32)
+ target_link_libraries(core ws2_32)
+endif()
+
+if(UNIX)
+ target_sources(core PRIVATE Logger_syslog.cxx)
+endif()
+
+if(UNIX)
+ libtool_create_control_file(core)
+endif()
diff --git a/common/core/Configuration.cxx b/common/core/Configuration.cxx
new file mode 100644
index 00000000..e6affb06
--- /dev/null
+++ b/common/core/Configuration.cxx
@@ -0,0 +1,857 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
+ * Copyright 2004-2005 Cendio AB.
+ * Copyright 2017 Peter Astrand <astrand@cendio.se> for Cendio AB
+ * Copyright 2011-2025 Pierre Ossman for Cendio AB
+ *
+ * This 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software 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 software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+// -=- Configuration.cxx
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <assert.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+
+#include <algorithm>
+#include <stdexcept>
+
+#include <core/Configuration.h>
+#include <core/LogWriter.h>
+#include <core/string.h>
+
+#include <rdr/HexOutStream.h>
+#include <rdr/HexInStream.h>
+
+using namespace core;
+
+static LogWriter vlog("Config");
+
+
+// -=- The Global Configuration object
+Configuration* Configuration::global_ = nullptr;
+
+Configuration* Configuration::global() {
+ if (!global_)
+ global_ = new Configuration();
+ return global_;
+}
+
+// -=- Configuration implementation
+
+bool Configuration::set(const char* paramName, const char* val,
+ bool immutable)
+{
+ for (VoidParameter* current: params) {
+ if (strcasecmp(current->getName(), paramName) == 0) {
+ bool b = current->setParam(val);
+ if (b && immutable)
+ current->setImmutable();
+ return b;
+ }
+ }
+ return false;
+}
+
+VoidParameter* Configuration::get(const char* param)
+{
+ for (VoidParameter* current: params) {
+ if (strcasecmp(current->getName(), param) == 0)
+ return current;
+ }
+ return nullptr;
+}
+
+void Configuration::list(int width, int nameWidth) {
+ for (VoidParameter* current: params) {
+ std::string def_str = current->getDefaultStr();
+ std::string desc_str = current->getDescription();
+ if (!def_str.empty())
+ desc_str += " (default=" + def_str + ")";
+ const char* desc = desc_str.c_str();
+ fprintf(stderr," %-*s -", nameWidth, current->getName());
+ int column = strlen(current->getName());
+ if (column < nameWidth) column = nameWidth;
+ column += 4;
+ while (true) {
+ if (desc[0] == '\0')
+ break;
+
+ int wordLen = strcspn(desc, " \f\n\r\t\v,");
+ if (wordLen == 0) {
+ desc++;
+ continue;
+ }
+
+ if (desc[wordLen] == ',')
+ wordLen++;
+
+ if (column + wordLen + 1 > width) {
+ fprintf(stderr,"\n%*s",nameWidth+4,"");
+ column = nameWidth+4;
+ }
+ fprintf(stderr, " ");
+ column++;
+
+ fprintf(stderr, "%.*s", wordLen, desc);
+ column += wordLen;
+ desc += wordLen;
+ }
+ fprintf(stderr,"\n");
+ }
+}
+
+
+bool Configuration::remove(const char* param) {
+ std::list<VoidParameter*>::iterator iter;
+
+ iter = std::find_if(params.begin(), params.end(),
+ [param](VoidParameter* p) {
+ return strcasecmp(p->getName(), param) == 0;
+ });
+ if (iter == params.end())
+ return false;
+
+ params.erase(iter);
+ return true;
+}
+
+int Configuration::handleArg(int argc, char* argv[], int index)
+{
+ std::string param, val;
+ const char* equal = strchr(argv[index], '=');
+
+ if (equal == argv[index])
+ return 0;
+
+ if (equal) {
+ param.assign(argv[index], equal-argv[index]);
+ val.assign(equal+1);
+ } else {
+ param.assign(argv[index]);
+ }
+
+ if ((param.length() > 0) && (param[0] == '-')) {
+ // allow gnu-style --<option>
+ if ((param.length() > 1) && (param[1] == '-'))
+ param = param.substr(2);
+ else
+ param = param.substr(1);
+ } else {
+ // All command line arguments need either an initial '-', or an '='
+ if (!equal)
+ return 0;
+ }
+
+ if (equal)
+ return set(param.c_str(), val.c_str()) ? 1 : 0;
+
+ for (VoidParameter* current: params) {
+ if (strcasecmp(current->getName(), param.c_str()) != 0)
+ continue;
+
+ // We need to resolve an ambiguity for booleans
+ if (dynamic_cast<BoolParameter*>(current) != nullptr) {
+ if (index+1 < argc) {
+ // FIXME: Should not duplicate the list of values here
+ if ((strcasecmp(argv[index+1], "0") == 0) ||
+ (strcasecmp(argv[index+1], "1") == 0) ||
+ (strcasecmp(argv[index+1], "on") == 0) ||
+ (strcasecmp(argv[index+1], "off") == 0) ||
+ (strcasecmp(argv[index+1], "true") == 0) ||
+ (strcasecmp(argv[index+1], "false") == 0) ||
+ (strcasecmp(argv[index+1], "yes") == 0) ||
+ (strcasecmp(argv[index+1], "no") == 0)) {
+ return current->setParam(argv[index+1]) ? 2 : 0;
+ }
+ }
+ }
+
+ if (current->setParam())
+ return 1;
+
+ if (index+1 >= argc)
+ return 0;
+
+ return current->setParam(argv[index+1]) ? 2 : 0;
+ }
+
+ return 0;
+}
+
+
+// -=- VoidParameter
+
+VoidParameter::VoidParameter(const char* name_, const char* desc_)
+ : immutable(false), name(name_), description(desc_)
+{
+ Configuration *conf;
+
+ conf = Configuration::global();
+ conf->params.push_back(this);
+ conf->params.sort([](const VoidParameter* a, const VoidParameter* b) {
+ return strcasecmp(a->getName(), b->getName()) < 0;
+ });
+}
+
+VoidParameter::~VoidParameter() {
+ Configuration *conf;
+
+ conf = Configuration::global();
+ conf->params.remove(this);
+}
+
+const char*
+VoidParameter::getName() const {
+ return name;
+}
+
+const char*
+VoidParameter::getDescription() const {
+ return description;
+}
+
+bool VoidParameter::setParam() {
+ return false;
+}
+
+bool VoidParameter::isDefault() const {
+ return getDefaultStr() == getValueStr();
+}
+
+void
+VoidParameter::setImmutable() {
+ vlog.debug("Set immutable %s", getName());
+ immutable = true;
+}
+
+// -=- AliasParameter
+
+AliasParameter::AliasParameter(const char* name_, const char* desc_,
+ VoidParameter* param_)
+ : VoidParameter(name_, desc_), param(param_) {
+}
+
+bool
+AliasParameter::setParam(const char* v) {
+ return param->setParam(v);
+}
+
+bool AliasParameter::setParam() {
+ return param->setParam();
+}
+
+std::string AliasParameter::getDefaultStr() const {
+ return "";
+}
+
+std::string AliasParameter::getValueStr() const {
+ return param->getValueStr();
+}
+
+void
+AliasParameter::setImmutable() {
+ vlog.debug("Set immutable %s (Alias)", getName());
+ param->setImmutable();
+}
+
+
+// -=- BoolParameter
+
+BoolParameter::BoolParameter(const char* name_, const char* desc_, bool v)
+: VoidParameter(name_, desc_), value(v), def_value(v) {
+}
+
+bool
+BoolParameter::setParam(const char* v) {
+ if (immutable) return true;
+
+ if (*v == 0 || strcasecmp(v, "1") == 0 || strcasecmp(v, "on") == 0
+ || strcasecmp(v, "true") == 0 || strcasecmp(v, "yes") == 0)
+ setParam(true);
+ else if (strcasecmp(v, "0") == 0 || strcasecmp(v, "off") == 0
+ || strcasecmp(v, "false") == 0 || strcasecmp(v, "no") == 0)
+ setParam(false);
+ else {
+ vlog.error("Bool parameter %s: Invalid value '%s'", getName(), v);
+ return false;
+ }
+
+ return true;
+}
+
+bool BoolParameter::setParam() {
+ setParam(true);
+ return true;
+}
+
+void BoolParameter::setParam(bool b) {
+ if (immutable) return;
+ value = b;
+ vlog.debug("Set %s(Bool) to %s", getName(), getValueStr().c_str());
+}
+
+std::string BoolParameter::getDefaultStr() const {
+ return def_value ? "on" : "off";
+}
+
+std::string BoolParameter::getValueStr() const {
+ return value ? "on" : "off";
+}
+
+BoolParameter::operator bool() const {
+ return value;
+}
+
+// -=- IntParameter
+
+IntParameter::IntParameter(const char* name_, const char* desc_, int v,
+ int minValue_, int maxValue_)
+ : VoidParameter(name_, desc_), value(v), def_value(v),
+ minValue(minValue_), maxValue(maxValue_)
+{
+ if (v < minValue || v > maxValue) {
+ vlog.error("Invalid default value %d for %s", v, getName());
+ throw std::invalid_argument("Invalid default value");
+ }
+}
+
+bool
+IntParameter::setParam(const char* v) {
+ char* end;
+ long n;
+ if (immutable) return true;
+ n = strtol(v, &end, 0);
+ if ((*end != 0) || (n < INT_MIN) || (n > INT_MAX)) {
+ vlog.error("Int parameter %s: Invalid value '%s'", getName(), v);
+ return false;
+ }
+ return setParam(n);
+}
+
+bool
+IntParameter::setParam(int v) {
+ if (immutable) return true;
+ if (v < minValue || v > maxValue) {
+ vlog.error("Int parameter %s: Invalid value '%d'", getName(), v);
+ return false;
+ }
+ vlog.debug("Set %s(Int) to %d", getName(), v);
+ value = v;
+ return true;
+}
+
+std::string IntParameter::getDefaultStr() const {
+ char result[16];
+ sprintf(result, "%d", def_value);
+ return result;
+}
+
+std::string IntParameter::getValueStr() const {
+ char result[16];
+ sprintf(result, "%d", value);
+ return result;
+}
+
+IntParameter::operator int() const {
+ return value;
+}
+
+// -=- StringParameter
+
+StringParameter::StringParameter(const char* name_, const char* desc_,
+ const char* v)
+ : VoidParameter(name_, desc_), value(v?v:""), def_value(v?v:"")
+{
+ if (!v) {
+ vlog.error("Default value <null> for %s not allowed",name_);
+ throw std::invalid_argument("Default value <null> not allowed");
+ }
+}
+
+bool StringParameter::setParam(const char* v) {
+ if (immutable) return true;
+ if (!v)
+ throw std::invalid_argument("setParam(<null>) not allowed");
+ vlog.debug("Set %s(String) to %s", getName(), v);
+ value = v;
+ return true;
+}
+
+std::string StringParameter::getDefaultStr() const {
+ return def_value;
+}
+
+std::string StringParameter::getValueStr() const {
+ return value;
+}
+
+StringParameter::operator const char *() const {
+ return value.c_str();
+}
+
+// -=- EnumParameter
+
+EnumParameter::EnumParameter(const char* name_, const char* desc_,
+ const std::set<const char*>& enums_,
+ const char* v)
+ : VoidParameter(name_, desc_), value(v?v:""), def_value(v?v:"")
+{
+ if (!v) {
+ vlog.error("Default value <null> for %s not allowed", name_);
+ throw std::invalid_argument("Default value <null> not allowed");
+ }
+
+ for (const char* e: enums_) {
+ if (!e) {
+ vlog.error("Enumeration <null> for %s not allowed", name_);
+ throw std::invalid_argument("Enumeration <null> not allowed");
+ }
+ enums.insert(e);
+ }
+
+ if (std::find(enums.begin(), enums.end(), def_value) == enums.end()) {
+ vlog.error("Default value %s for %s is not in list of valid values",
+ def_value.c_str(), name_);
+ throw std::invalid_argument("Default value is not in list of valid values");
+ }
+}
+
+bool EnumParameter::setParam(const char* v)
+{
+ std::set<std::string>::const_iterator iter;
+ if (immutable) return true;
+ if (!v)
+ throw std::invalid_argument("setParam(<null>) not allowed");
+ iter = std::find_if(enums.begin(), enums.end(),
+ [v](const std::string& e) {
+ return strcasecmp(e.c_str(), v) == 0;
+ });
+ if (iter == enums.end()) {
+ vlog.error("Enum parameter %s: Invalid value '%s'", getName(), v);
+ return false;
+ }
+ vlog.debug("Set %s(Enum) to %s", getName(), iter->c_str());
+ value = *iter;
+ return true;
+}
+
+std::string EnumParameter::getDefaultStr() const
+{
+ return def_value;
+}
+
+std::string EnumParameter::getValueStr() const
+{
+ return value;
+}
+
+bool EnumParameter::operator==(const char* other) const
+{
+ return strcasecmp(value.c_str(), other) == 0;
+}
+
+bool EnumParameter::operator==(const std::string& other) const
+{
+ return *this == other.c_str();
+}
+
+bool EnumParameter::operator!=(const char* other) const
+{
+ return strcasecmp(value.c_str(), other) != 0;
+}
+
+bool EnumParameter::operator!=(const std::string& other) const
+{
+ return *this != other.c_str();
+}
+
+// -=- BinaryParameter
+
+BinaryParameter::BinaryParameter(const char* name_, const char* desc_,
+ const uint8_t* v, size_t l)
+: VoidParameter(name_, desc_),
+ value(nullptr), length(0), def_value(nullptr), def_length(0) {
+ if (l) {
+ assert(v);
+ value = new uint8_t[l];
+ length = l;
+ memcpy(value, v, l);
+ def_value = new uint8_t[l];
+ def_length = l;
+ memcpy(def_value, v, l);
+ }
+}
+BinaryParameter::~BinaryParameter() {
+ delete [] value;
+ delete [] def_value;
+}
+
+bool BinaryParameter::setParam(const char* v) {
+ if (immutable) return true;
+ std::vector<uint8_t> newValue = hexToBin(v, strlen(v));
+ if (newValue.empty() && strlen(v) > 0)
+ return false;
+ setParam(newValue.data(), newValue.size());
+ return true;
+}
+
+void BinaryParameter::setParam(const uint8_t* v, size_t len) {
+ if (immutable) return;
+ vlog.debug("Set %s(Binary)", getName());
+ delete [] value;
+ value = nullptr;
+ length = 0;
+ if (len) {
+ assert(v);
+ value = new uint8_t[len];
+ length = len;
+ memcpy(value, v, len);
+ }
+}
+
+std::string BinaryParameter::getDefaultStr() const {
+ return binToHex(def_value, def_length);
+}
+
+std::string BinaryParameter::getValueStr() const {
+ return binToHex(value, length);
+}
+
+std::vector<uint8_t> BinaryParameter::getData() const {
+ std::vector<uint8_t> out(length);
+ memcpy(out.data(), value, length);
+ return out;
+}
+
+// -=- ListParameter template
+
+template<typename ValueType>
+ListParameter<ValueType>::ListParameter(const char* name_,
+ const char* desc_,
+ const ListType& v)
+ : VoidParameter(name_, desc_), value(v), def_value(v)
+{
+}
+
+template<typename ValueType>
+bool ListParameter<ValueType>::setParam(const char* v)
+{
+ std::vector<std::string> entries;
+ ListType new_value;
+
+ if (immutable)
+ return true;
+
+ // setParam({}) ends up as setParam(nullptr)
+ if (v != nullptr)
+ entries = split(v, ',');
+
+ for (std::string& entry : entries) {
+ ValueType e;
+
+ entry.erase(0, entry.find_first_not_of(" \f\n\r\t\v"));
+ entry.erase(entry.find_last_not_of(" \f\n\r\t\v")+1);
+
+ // Special case, entire v was just whitespace
+ if (entry.empty() && (entries.size() == 1))
+ break;
+
+ if (!decodeEntry(entry.c_str(), &e)) {
+ vlog.error("List parameter %s: Invalid value '%s'",
+ getName(), entry.c_str());
+ return false;
+ }
+
+ new_value.push_back(e);
+ }
+
+ return setParam(new_value);
+}
+
+template<typename ValueType>
+bool ListParameter<ValueType>::setParam(const ListType& v)
+{
+ ListType vnorm;
+ if (immutable)
+ return true;
+ for (const ValueType& entry : v) {
+ if (!validateEntry(entry)) {
+ vlog.error("List parameter %s: Invalid value '%s'", getName(),
+ encodeEntry(entry).c_str());
+ return false;
+ }
+ vnorm.push_back(normaliseEntry(entry));
+ }
+ value = vnorm;
+ vlog.debug("set %s(List) to %s", getName(), getValueStr().c_str());
+ return true;
+}
+
+template<typename ValueType>
+std::string ListParameter<ValueType>::getDefaultStr() const
+{
+ std::string result;
+
+ for (ValueType entry : def_value) {
+ // FIXME: Might want to add a space here as well for readability,
+ // but this would sacrifice backward compatibility
+ if (!result.empty())
+ result += ',';
+ result += encodeEntry(entry);
+ }
+
+ return result;
+}
+
+template<typename ValueType>
+std::string ListParameter<ValueType>::getValueStr() const
+{
+ std::string result;
+
+ for (ValueType entry : value) {
+ // FIXME: Might want to add a space here as well for readability,
+ // but this would sacrifice backward compatibility
+ if (!result.empty())
+ result += ',';
+ result += encodeEntry(entry);
+ }
+
+ return result;
+}
+
+template<typename ValueType>
+typename ListParameter<ValueType>::const_iterator ListParameter<ValueType>::begin() const
+{
+ return value.begin();
+}
+
+template<typename ValueType>
+typename ListParameter<ValueType>::const_iterator ListParameter<ValueType>::end() const
+{
+ return value.end();
+}
+
+template<typename ValueType>
+bool ListParameter<ValueType>::validateEntry(const ValueType& /*entry*/) const
+{
+ return true;
+}
+
+template<typename ValueType>
+ValueType ListParameter<ValueType>::normaliseEntry(const ValueType& entry) const
+{
+ return entry;
+}
+
+// -=- IntListParameter
+
+template class core::ListParameter<int>;
+
+IntListParameter::IntListParameter(const char* name_, const char* desc_,
+ const ListType& v,
+ int minValue_, int maxValue_)
+ : ListParameter<int>(name_, desc_, v),
+ minValue(minValue_), maxValue(maxValue_)
+{
+ for (int entry : v) {
+ if (!validateEntry(entry)) {
+ vlog.error("Invalid default value %d for %s", entry, getName());
+ throw std::invalid_argument("Invalid default value");
+ }
+ }
+}
+
+bool IntListParameter::decodeEntry(const char* entry, int* out) const
+{
+ long n;
+ char *end;
+
+ assert(entry);
+ assert(out);
+
+ if (entry[0] == '\0')
+ return false;
+
+ n = strtol(entry, &end, 0);
+ if ((*end != 0) || (n < INT_MIN) || (n > INT_MAX))
+ return false;
+
+ *out = n;
+
+ return true;
+}
+
+std::string IntListParameter::encodeEntry(const int& entry) const
+{
+ char valstr[16];
+ sprintf(valstr, "%d", entry);
+ return valstr;
+}
+
+bool IntListParameter::validateEntry(const int& entry) const
+{
+ return (entry >= minValue) && (entry <= maxValue);
+}
+
+// -=- StringListParameter
+
+template class core::ListParameter<std::string>;
+
+StringListParameter::StringListParameter(const char* name_,
+ const char* desc_,
+ const std::list<const char*>& v_)
+ : ListParameter<std::string>(name_, desc_, {})
+{
+ for (const char* v: v_) {
+ if (!v) {
+ vlog.error("Default value <null> for %s not allowed", name_);
+ throw std::invalid_argument("Default value <null> not allowed");
+ }
+ value.push_back(v);
+ def_value.push_back(v);
+ }
+}
+
+StringListParameter::const_iterator StringListParameter::begin() const
+{
+ return ListParameter<std::string>::begin();
+}
+
+StringListParameter::const_iterator StringListParameter::end() const
+{
+ return ListParameter<std::string>::end();
+}
+
+bool StringListParameter::decodeEntry(const char* entry, std::string* out) const
+{
+ *out = entry;
+ return true;
+}
+
+std::string StringListParameter::encodeEntry(const std::string& entry) const
+{
+ return entry;
+}
+
+// -=- EnumListEntry
+
+EnumListEntry::EnumListEntry(const std::string& v)
+ : value(v)
+{
+}
+
+std::string EnumListEntry::getValueStr() const
+{
+ return value;
+}
+
+bool EnumListEntry::operator==(const char* other) const
+{
+ return strcasecmp(value.c_str(), other) == 0;
+}
+
+bool EnumListEntry::operator==(const std::string& other) const
+{
+ return *this == other.c_str();
+}
+
+bool EnumListEntry::operator!=(const char* other) const
+{
+ return strcasecmp(value.c_str(), other) != 0;
+}
+
+bool EnumListEntry::operator!=(const std::string& other) const
+{
+ return *this != other.c_str();
+}
+
+// -=- EnumListParameter
+
+EnumListParameter::EnumListParameter(const char* name_,
+ const char* desc_,
+ const std::set<const char*>& enums_,
+ const std::list<const char*>& v_)
+ : ListParameter<std::string>(name_, desc_, {})
+{
+ for (const char* v: v_) {
+ if (!v) {
+ vlog.error("Default value <null> for %s not allowed", name_);
+ throw std::invalid_argument("Default value <null> not allowed");
+ }
+ value.push_back(v);
+ def_value.push_back(v);
+ }
+
+ for (const char* e: enums_) {
+ if (!e) {
+ vlog.error("Enumeration <null> for %s not allowed", name_);
+ throw std::invalid_argument("Enumeration <null> not allowed");
+ }
+ enums.insert(e);
+ }
+
+ for (const std::string& def_entry : def_value) {
+ if (std::find(enums.begin(), enums.end(), def_entry) == enums.end()) {
+ vlog.error("Default value %s for %s is not in list of valid values",
+ def_entry.c_str(), name_);
+ throw std::invalid_argument("Default value is not in list of valid values");
+ }
+ }
+}
+
+EnumListParameter::const_iterator EnumListParameter::begin() const
+{
+ return ListParameter<std::string>::begin();
+}
+
+EnumListParameter::const_iterator EnumListParameter::end() const
+{
+ return ListParameter<std::string>::end();
+}
+
+bool EnumListParameter::decodeEntry(const char* entry, std::string* out) const
+{
+ *out = entry;
+ return true;
+}
+
+std::string EnumListParameter::encodeEntry(const std::string& entry) const
+{
+ return entry;
+}
+
+bool EnumListParameter::validateEntry(const std::string& entry) const
+{
+ for (const std::string& e : enums) {
+ if (strcasecmp(e.c_str(), entry.c_str()) == 0)
+ return true;
+ }
+ return false;
+}
+
+std::string EnumListParameter::normaliseEntry(const std::string& entry) const
+{
+ for (const std::string& e : enums) {
+ if (strcasecmp(e.c_str(), entry.c_str()) == 0)
+ return e;
+ }
+ throw std::logic_error("Entry is not in list of valid values");
+}
diff --git a/common/core/Configuration.h b/common/core/Configuration.h
new file mode 100644
index 00000000..431dd0c5
--- /dev/null
+++ b/common/core/Configuration.h
@@ -0,0 +1,348 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
+ * Copyright 2011-2025 Pierre Ossman for Cendio AB
+ *
+ * This 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software 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 software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+// -=- Configuration.h
+//
+// This header defines a set of classes used to represent configuration
+// parameters of different types. Instances of the different parameter
+// types are associated with instances of the Configuration class, and
+// are each given a unique name. The Configuration class provides a
+// generic API through which parameters may be located by name and their
+// value set, thus removing the need to write platform-specific code.
+// Simply defining a new parameter and associating it with a Configuration
+// will allow it to be configured by the user.
+//
+// If no Configuration is specified when creating a Parameter, then the
+// global Configuration will be assumed.
+//
+// Configurations can be "chained" into groups. Each group has a root
+// Configuration, a pointer to which should be passed to the constructors
+// of the other group members. set() and get() operations called on the
+// root will iterate through all of the group's members.
+//
+// NB: On platforms that support Threading, locking is performed to protect
+// complex parameter types from concurrent access (e.g. strings).
+// NB: NO LOCKING is performed when linking Configurations to groups
+// or when adding Parameters to Configurations.
+
+#ifndef __CORE_CONFIGURATION_H__
+#define __CORE_CONFIGURATION_H__
+
+#include <limits.h>
+#include <stdint.h>
+
+#include <list>
+#include <set>
+#include <string>
+#include <vector>
+
+namespace core {
+
+ class VoidParameter;
+
+ // -=- Configuration
+ // Class used to access parameters.
+
+ class Configuration {
+ public:
+ // - Create a new Configuration object
+ Configuration() {}
+
+ // - Set named parameter to value
+ bool set(const char* param, const char* value, bool immutable=false);
+
+ // - Get named parameter
+ VoidParameter* get(const char* param);
+
+ // - List the parameters of this Configuration group
+ void list(int width=79, int nameWidth=10);
+
+ // - Remove a parameter from this Configuration group
+ bool remove(const char* param);
+
+ // - handleArg
+ // Parse a command line argument into a parameter, returning how
+ // many arguments were consumed
+ int handleArg(int argc, char* argv[], int index);
+
+
+ // - Iterate over all parameters
+ std::list<VoidParameter*>::iterator begin() { return params.begin(); }
+ std::list<VoidParameter*>::iterator end() { return params.end(); }
+
+ // - Returns the number of parameters
+ int size() { return params.size(); }
+
+ // - Get the Global Configuration object
+ // NB: This call does NOT lock the Configuration system.
+ // ALWAYS ensure that if you have ANY global Parameters,
+ // then they are defined as global objects, to ensure that
+ // global() is called when only the main thread is running.
+ static Configuration* global();
+
+ // - Container for process-wide Global parameters
+ static bool setParam(const char* param, const char* value, bool immutable=false) {
+ return global()->set(param, value, immutable);
+ }
+ static VoidParameter* getParam(const char* param) { return global()->get(param); }
+ static void listParams(int width=79, int nameWidth=10) {
+ global()->list(width, nameWidth);
+ }
+ static bool removeParam(const char* param) {
+ return global()->remove(param);
+ }
+ static int handleParamArg(int argc, char* argv[], int index) {
+ return global()->handleArg(argc, argv, index);
+ }
+
+ private:
+ friend class VoidParameter;
+
+ // - List of Parameters
+ std::list<VoidParameter*> params;
+
+ // The process-wide, Global Configuration object
+ static Configuration* global_;
+ };
+
+ // -=- VoidParameter
+ // Configuration parameter base-class.
+
+ class VoidParameter {
+ public:
+ VoidParameter(const char* name_, const char* desc_);
+ virtual ~VoidParameter();
+ const char* getName() const;
+ const char* getDescription() const;
+
+ virtual bool setParam(const char* value) = 0;
+ virtual bool setParam();
+ virtual std::string getDefaultStr() const = 0;
+ virtual std::string getValueStr() const = 0;
+
+ virtual bool isDefault() const;
+
+ virtual void setImmutable();
+
+ protected:
+ friend class Configuration;
+
+ VoidParameter* _next;
+ bool immutable;
+ const char* name;
+ const char* description;
+ };
+
+ class AliasParameter : public VoidParameter {
+ public:
+ AliasParameter(const char* name_, const char* desc_,VoidParameter* param_);
+ bool setParam(const char* value) override;
+ bool setParam() override;
+ std::string getDefaultStr() const override;
+ std::string getValueStr() const override;
+ void setImmutable() override;
+ private:
+ VoidParameter* param;
+ };
+
+ class BoolParameter : public VoidParameter {
+ public:
+ BoolParameter(const char* name_, const char* desc_, bool v);
+ bool setParam(const char* value) override;
+ bool setParam() override;
+ virtual void setParam(bool b);
+ std::string getDefaultStr() const override;
+ std::string getValueStr() const override;
+ operator bool() const;
+ protected:
+ bool value;
+ bool def_value;
+ };
+
+ class IntParameter : public VoidParameter {
+ public:
+ IntParameter(const char* name_, const char* desc_, int v,
+ int minValue=INT_MIN, int maxValue=INT_MAX);
+ using VoidParameter::setParam;
+ bool setParam(const char* value) override;
+ virtual bool setParam(int v);
+ std::string getDefaultStr() const override;
+ std::string getValueStr() const override;
+ operator int() const;
+ protected:
+ int value;
+ int def_value;
+ int minValue, maxValue;
+ };
+
+ class StringParameter : public VoidParameter {
+ public:
+ StringParameter(const char* name_, const char* desc_, const char* v);
+ bool setParam(const char* value) override;
+ std::string getDefaultStr() const override;
+ std::string getValueStr() const override;
+ operator const char*() const;
+ protected:
+ std::string value;
+ std::string def_value;
+ };
+
+ class EnumParameter : public VoidParameter {
+ public:
+ EnumParameter(const char* name_, const char* desc_,
+ const std::set<const char*>& enums, const char* v);
+ bool setParam(const char* value) override;
+ std::string getDefaultStr() const override;
+ std::string getValueStr() const override;
+ bool operator==(const char* other) const;
+ bool operator==(const std::string& other) const;
+ bool operator!=(const char* other) const;
+ bool operator!=(const std::string& other) const;
+ // operator const char*() omitted on purpose to force usage of above
+ // comparison operators
+ protected:
+ std::string value;
+ std::string def_value;
+ std::set<std::string> enums;
+ };
+
+ class BinaryParameter : public VoidParameter {
+ public:
+ BinaryParameter(const char* name_, const char* desc_,
+ const uint8_t* v, size_t l);
+ using VoidParameter::setParam;
+ ~BinaryParameter() override;
+ bool setParam(const char* value) override;
+ virtual void setParam(const uint8_t* v, size_t l);
+ std::string getDefaultStr() const override;
+ std::string getValueStr() const override;
+
+ std::vector<uint8_t> getData() const;
+
+ protected:
+ uint8_t* value;
+ size_t length;
+ uint8_t* def_value;
+ size_t def_length;
+ };
+
+ template<typename ValueType>
+ class ListParameter : public VoidParameter {
+ public:
+ typedef std::list<ValueType> ListType;
+ typedef typename ListType::const_iterator const_iterator;
+
+ ListParameter(const char* name_, const char* desc_,
+ const ListType& v);
+ using VoidParameter::setParam;
+ bool setParam(const char* value) override;
+ virtual bool setParam(const ListType& v);
+ std::string getDefaultStr() const override;
+ std::string getValueStr() const override;
+
+ const_iterator begin() const;
+ const_iterator end() const;
+
+ protected:
+ virtual bool decodeEntry(const char* entry, ValueType* out) const = 0;
+ virtual std::string encodeEntry(const ValueType& entry) const = 0;
+ virtual bool validateEntry(const ValueType& entry) const;
+ virtual ValueType normaliseEntry(const ValueType& entry) const;
+
+ protected:
+ ListType value;
+ ListType def_value;
+ };
+
+ class IntListParameter : public ListParameter<int> {
+ public:
+ IntListParameter(const char* name_, const char* desc_,
+ const ListType& v,
+ int minValue=INT_MIN, int maxValue=INT_MAX);
+ protected:
+ bool decodeEntry(const char* entry, int* out) const override;
+ std::string encodeEntry(const int& entry) const override;
+ bool validateEntry(const int& entry) const override;
+
+ protected:
+ int minValue, maxValue;
+ };
+
+ class StringListParameter : public ListParameter<std::string> {
+ public:
+ StringListParameter(const char* name_, const char* desc_,
+ const std::list<const char*>& v);
+
+ class const_iterator : public ListType::const_iterator {
+ public:
+ const_iterator(const ListType::const_iterator& it) : ListType::const_iterator(it) {}
+ const char* operator*() const { return (ListType::const_iterator::operator*()).c_str(); }
+ };
+
+ const_iterator begin() const;
+ const_iterator end() const;
+
+ protected:
+ bool decodeEntry(const char* entry, std::string* out) const override;
+ std::string encodeEntry(const std::string& entry) const override;
+ };
+
+ class EnumListEntry {
+ public:
+ EnumListEntry(const std::string& v);
+ std::string getValueStr() const;
+ bool operator==(const char* other) const;
+ bool operator==(const std::string& other) const;
+ bool operator!=(const char* other) const;
+ bool operator!=(const std::string& other) const;
+ // operator const char*() omitted on purpose to force usage of above
+ // comparison operators
+ protected:
+ std::string value;
+ };
+
+ class EnumListParameter : public ListParameter<std::string> {
+ public:
+ EnumListParameter(const char* name_, const char* desc_,
+ const std::set<const char*>& enums,
+ const std::list<const char*>& v);
+
+ class const_iterator : public ListType::const_iterator {
+ public:
+ const_iterator(const ListType::const_iterator& it) : ListType::const_iterator(it) {}
+ const EnumListEntry operator*() const { return EnumListEntry(ListType::const_iterator::operator*()); }
+ const EnumListEntry operator->() const { return EnumListEntry(*ListType::const_iterator::operator->()); }
+ };
+
+ const_iterator begin() const;
+ const_iterator end() const;
+
+ protected:
+ bool decodeEntry(const char* entry, std::string* out) const override;
+ std::string encodeEntry(const std::string& entry) const override;
+ bool validateEntry(const std::string& entry) const override;
+ std::string normaliseEntry(const std::string& entry) const override;
+
+ protected:
+ std::set<std::string> enums;
+ };
+
+};
+
+#endif // __CORE_CONFIGURATION_H__
diff --git a/common/core/Exception.cxx b/common/core/Exception.cxx
new file mode 100644
index 00000000..5b4f0599
--- /dev/null
+++ b/common/core/Exception.cxx
@@ -0,0 +1,134 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
+ * Copyright (C) 2004 Red Hat Inc.
+ * Copyright (C) 2010 TigerVNC Team
+ * Copyright 2014-2024 Pierre Ossman for Cendio AB
+ *
+ * This 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software 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 software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdarg.h>
+
+#include <core/Exception.h>
+#include <core/string.h>
+
+#ifdef _WIN32
+#include <winsock2.h>
+#include <windows.h>
+#include <ws2tcpip.h>
+#else
+#include <netdb.h>
+#endif
+
+#include <string.h>
+
+using namespace core;
+
+
+getaddrinfo_error::getaddrinfo_error(const char* s, int err_) noexcept
+ : std::runtime_error(core::format("%s: %s (%d)", s,
+ strerror(err_).c_str(), err_)),
+ err(err_)
+{
+}
+
+getaddrinfo_error::getaddrinfo_error(const std::string& s,
+ int err_) noexcept
+ : std::runtime_error(core::format("%s: %s (%d)", s.c_str(),
+ strerror(err_).c_str(), err_)),
+ err(err_)
+{
+}
+
+std::string getaddrinfo_error::strerror(int err_) const noexcept
+{
+#ifdef _WIN32
+ char str[256];
+
+ WideCharToMultiByte(CP_UTF8, 0, gai_strerrorW(err_), -1, str,
+ sizeof(str), nullptr, nullptr);
+
+ return str;
+#else
+ return gai_strerror(err_);
+#endif
+}
+
+posix_error::posix_error(const char* what_arg, int err_) noexcept
+ : std::runtime_error(core::format("%s: %s (%d)", what_arg,
+ strerror(err_).c_str(), err_)),
+ err(err_)
+{
+}
+
+posix_error::posix_error(const std::string& what_arg, int err_) noexcept
+ : std::runtime_error(core::format("%s: %s (%d)", what_arg.c_str(),
+ strerror(err_).c_str(), err_)),
+ err(err_)
+{
+}
+
+std::string posix_error::strerror(int err_) const noexcept
+{
+#ifdef _WIN32
+ char str[256];
+
+ WideCharToMultiByte(CP_UTF8, 0, _wcserror(err_), -1, str,
+ sizeof(str), nullptr, nullptr);
+
+ return str;
+#else
+ return ::strerror(err_);
+#endif
+}
+
+#ifdef WIN32
+win32_error::win32_error(const char* what_arg, unsigned err_) noexcept
+ : std::runtime_error(core::format("%s: %s (%d)", what_arg,
+ strerror(err_).c_str(), err_)),
+ err(err_)
+{
+}
+
+win32_error::win32_error(const std::string& what_arg,
+ unsigned err_) noexcept
+ : std::runtime_error(core::format("%s: %s (%d)", what_arg.c_str(),
+ strerror(err_).c_str(), err_)),
+ err(err_)
+{
+}
+
+std::string win32_error::strerror(unsigned err_) const noexcept
+{
+ wchar_t wstr[256];
+ char str[256];
+
+ FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
+ nullptr, err_, 0, wstr, sizeof(wstr), nullptr);
+ WideCharToMultiByte(CP_UTF8, 0, wstr, -1, str,
+ sizeof(str), nullptr, nullptr);
+
+ int l = strlen(str);
+ if ((l >= 2) && (str[l-2] == '\r') && (str[l-1] == '\n'))
+ str[l-2] = 0;
+
+ return str;
+}
+#endif
diff --git a/common/core/Exception.h b/common/core/Exception.h
new file mode 100644
index 00000000..04463a17
--- /dev/null
+++ b/common/core/Exception.h
@@ -0,0 +1,75 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
+ * Copyright (C) 2004 Red Hat Inc.
+ * Copyright (C) 2010 TigerVNC Team
+ * Copyright 2015-2024 Pierre Ossman for Cendio AB
+ *
+ * This 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software 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 software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+#ifndef __CORE_EXCEPTION_H__
+#define __CORE_EXCEPTION_H__
+
+#include <stdexcept>
+#include <string>
+
+namespace core {
+
+ class posix_error : public std::runtime_error {
+ public:
+ int err;
+ posix_error(const char* what_arg, int err_) noexcept;
+ posix_error(const std::string& what_arg, int err_) noexcept;
+ private:
+ std::string strerror(int err_) const noexcept;
+ };
+
+#ifdef WIN32
+ class win32_error : public std::runtime_error {
+ public:
+ unsigned err;
+ win32_error(const char* what_arg, unsigned err_) noexcept;
+ win32_error(const std::string& what_arg, unsigned err_) noexcept;
+ private:
+ std::string strerror(unsigned err_) const noexcept;
+ };
+#endif
+
+#ifdef WIN32
+ class socket_error : public win32_error {
+ public:
+ socket_error(const char* what_arg, unsigned err_) noexcept : win32_error(what_arg, err_) {}
+ socket_error(const std::string& what_arg, unsigned err_) noexcept : win32_error(what_arg, err_) {}
+ };
+#else
+ class socket_error : public posix_error {
+ public:
+ socket_error(const char* what_arg, unsigned err_) noexcept : posix_error(what_arg, err_) {}
+ socket_error(const std::string& what_arg, unsigned err_) noexcept : posix_error(what_arg, err_) {}
+ };
+#endif
+
+ class getaddrinfo_error : public std::runtime_error {
+ public:
+ int err;
+ getaddrinfo_error(const char* s, int err_) noexcept;
+ getaddrinfo_error(const std::string& s, int err_) noexcept;
+ private:
+ std::string strerror(int err_) const noexcept;
+ };
+
+}
+
+#endif
diff --git a/common/core/LogWriter.cxx b/common/core/LogWriter.cxx
new file mode 100644
index 00000000..98e5201c
--- /dev/null
+++ b/common/core/LogWriter.cxx
@@ -0,0 +1,135 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
+ *
+ * This 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software 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 software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+// -=- LogWriter.cxx - client-side logging interface
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <core/Configuration.h>
+#include <core/LogWriter.h>
+#include <core/string.h>
+
+using namespace core;
+
+LogParameter core::logParams;
+
+LogWriter::LogWriter(const char* name)
+ : m_name(name), m_level(0), m_log(nullptr), m_next(log_writers) {
+ log_writers = this;
+}
+
+LogWriter::~LogWriter() {
+ // *** Should remove this logger here!
+}
+
+void LogWriter::setLog(Logger *logger) {
+ m_log = logger;
+}
+
+void LogWriter::setLevel(int level) {
+ m_level = level;
+}
+
+void
+LogWriter::listLogWriters(int /*width*/) {
+ // *** make this respect width...
+ LogWriter* current = log_writers;
+ fprintf(stderr, " ");
+ while (current) {
+ fprintf(stderr, "%s", current->m_name);
+ current = current->m_next;
+ if (current) fprintf(stderr, ", ");
+ }
+ fprintf(stderr, "\n");
+}
+
+LogWriter* LogWriter::log_writers;
+
+LogWriter*
+LogWriter::getLogWriter(const char* name) {
+ LogWriter* current = log_writers;
+ while (current) {
+ if (strcasecmp(name, current->m_name) == 0) return current;
+ current = current->m_next;
+ }
+ return nullptr;
+}
+
+bool LogWriter::setLogParams(const char* params) {
+ std::vector<std::string> parts;
+ parts = split(params, ':');
+ if (parts.size() != 3) {
+ fprintf(stderr, "Failed to parse log params:%s\n",params);
+ return false;
+ }
+ int level = atoi(parts[2].c_str());
+ Logger* logger = nullptr;
+ if (!parts[1].empty()) {
+ logger = Logger::getLogger(parts[1].c_str());
+ if (!logger)
+ fprintf(stderr, "No logger found! %s\n", parts[1].c_str());
+ }
+ if (parts[0] == "*") {
+ LogWriter* current = log_writers;
+ while (current) {
+ current->setLog(logger);
+ current->setLevel(level);
+ current = current->m_next;
+ }
+ return true;
+ } else {
+ LogWriter* logwriter = getLogWriter(parts[0].c_str());
+ if (!logwriter) {
+ fprintf(stderr, "No logwriter found! %s\n", parts[0].c_str());
+ } else {
+ logwriter->setLog(logger);
+ logwriter->setLevel(level);
+ return true;
+ }
+ }
+ return false;
+}
+
+
+LogParameter::LogParameter()
+ : StringListParameter("Log",
+ "Specifies which log output should be directed to "
+ "which target logger, and the level of output to log. "
+ "Format is <log>:<target>:<level>[, ...].",
+ {})
+{
+}
+
+bool LogParameter::setParam(const char* v) {
+ if (immutable) return true;
+ LogWriter::setLogParams("*::0");
+ if (!StringListParameter::setParam(v))
+ return false;
+ for (const char* part : *this) {
+ if (part[0] == '\0')
+ continue;
+ if (!LogWriter::setLogParams(part))
+ return false;
+ }
+ return true;
+}
diff --git a/common/core/LogWriter.h b/common/core/LogWriter.h
new file mode 100644
index 00000000..58dcc063
--- /dev/null
+++ b/common/core/LogWriter.h
@@ -0,0 +1,114 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
+ *
+ * This 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software 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 software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+// -=- LogWriter.h - The Log writer class.
+
+#ifndef __CORE_LOG_WRITER_H__
+#define __CORE_LOG_WRITER_H__
+
+#include <stdarg.h>
+
+#include <core/Configuration.h>
+#include <core/Logger.h>
+
+// Each log writer instance has a unique textual name,
+// and is attached to a particular Log instance and
+// is assigned a particular log level.
+
+#define DEF_LOGFUNCTION(name, level) \
+ inline void v##name(const char* fmt, va_list ap) \
+ __attribute__((__format__ (__printf__, 2, 0))) \
+ { \
+ if (m_log && (level <= m_level)) \
+ m_log->write(level, m_name, fmt, ap); \
+ } \
+ inline void name(const char* fmt, ...) \
+ __attribute__((__format__ (__printf__, 2, 3))) \
+ { \
+ if (m_log && (level <= m_level)) { \
+ va_list ap; va_start(ap, fmt); \
+ m_log->write(level, m_name, fmt, ap);\
+ va_end(ap); \
+ } \
+ }
+
+namespace core {
+
+ class LogWriter {
+ public:
+ LogWriter(const char* name);
+ ~LogWriter();
+
+ const char *getName() {return m_name;}
+
+ void setLog(Logger *logger);
+ void setLevel(int level);
+ int getLevel(void) { return m_level; }
+
+ inline void write(int level, const char* format, ...)
+ __attribute__((__format__ (__printf__, 3, 4)))
+ {
+ if (m_log && (level <= m_level)) {
+ va_list ap;
+ va_start(ap, format);
+ m_log->write(level, m_name, format, ap);
+ va_end(ap);
+ }
+ }
+
+ static const int LEVEL_ERROR = 0;
+ static const int LEVEL_STATUS = 10;
+ static const int LEVEL_INFO = 30;
+ static const int LEVEL_DEBUG = 100;
+
+ DEF_LOGFUNCTION(error, LEVEL_ERROR)
+ DEF_LOGFUNCTION(status, LEVEL_STATUS)
+ DEF_LOGFUNCTION(info, LEVEL_INFO)
+ DEF_LOGFUNCTION(debug, LEVEL_DEBUG)
+
+ // -=- DIAGNOSTIC & HELPER ROUTINES
+
+ static void listLogWriters(int width=79);
+
+ // -=- CLASS FIELDS & FUNCTIONS
+
+ static LogWriter* log_writers;
+
+ static LogWriter* getLogWriter(const char* name);
+
+ static bool setLogParams(const char* params);
+
+ private:
+ const char* m_name;
+ int m_level;
+ Logger* m_log;
+ LogWriter* m_next;
+ };
+
+ class LogParameter : public StringListParameter {
+ public:
+ LogParameter();
+ bool setParam(const char* v) override;
+ // We explicitly don't inherit setParam(std::list) as we want to
+ // force callers to use the above method to parse the values
+ };
+ extern LogParameter logParams;
+
+};
+
+#endif // __CORE_LOG_WRITER_H__
diff --git a/common/core/Logger.cxx b/common/core/Logger.cxx
new file mode 100644
index 00000000..ad9a3d63
--- /dev/null
+++ b/common/core/Logger.cxx
@@ -0,0 +1,95 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
+ *
+ * This 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software 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 software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+// -=- Logger.cxx - support for the Logger and LogWriter classes
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <core/Logger.h>
+#include <core/LogWriter.h>
+
+using namespace core;
+
+Logger* Logger::loggers = nullptr;
+
+Logger::Logger(const char* name)
+ : registered(false), m_name(name), m_next(nullptr)
+{
+}
+
+Logger::~Logger() {
+ // *** Should remove this logger here!
+}
+
+void Logger::write(int level, const char *logname, const char* format,
+ va_list ap)
+{
+ // - Format the supplied data, and pass it to the
+ // actual log_message function
+ // The log level is included as a hint for loggers capable of representing
+ // different log levels in some way.
+ char buf1[4096];
+ vsnprintf(buf1, sizeof(buf1)-1, format, ap);
+ buf1[sizeof(buf1)-1] = 0;
+ char *buf = buf1;
+ while (true) {
+ char *end = strchr(buf, '\n');
+ if (end)
+ *end = '\0';
+ write(level, logname, buf);
+ if (!end)
+ break;
+ buf = end + 1;
+ }
+}
+
+void
+Logger::registerLogger() {
+ if (!registered) {
+ registered = true;
+ m_next = loggers;
+ loggers=this;
+ }
+}
+
+Logger*
+Logger::getLogger(const char* name) {
+ Logger* current = loggers;
+ while (current) {
+ if (strcasecmp(name, current->m_name) == 0) return current;
+ current = current->m_next;
+ }
+ return nullptr;
+}
+
+void
+Logger::listLoggers() {
+ Logger* current = loggers;
+ while (current) {
+ printf(" %s\n", current->m_name);
+ current = current->m_next;
+ }
+}
+
+
diff --git a/common/core/Logger.h b/common/core/Logger.h
new file mode 100644
index 00000000..5291915f
--- /dev/null
+++ b/common/core/Logger.h
@@ -0,0 +1,71 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
+ *
+ * This 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software 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 software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+// -=- Logger.h - The Logger class.
+
+#ifndef __CORE_LOGGER_H__
+#define __CORE_LOGGER_H__
+
+#include <stdarg.h>
+#include <stdio.h>
+
+// Each log writer instance has a unique textual name,
+// and is attached to a particular Logger instance and
+// is assigned a particular log level.
+
+namespace core {
+
+ class Logger {
+ public:
+
+ // -=- Create / Destroy a logger
+
+ Logger(const char* name);
+ virtual ~Logger();
+
+ // -=- Get the name of a logger
+
+ const char *getName() {return m_name;}
+
+ // -=- Write data to a log
+
+ virtual void write(int level, const char *logname, const char *text) = 0;
+ void write(int level, const char *logname, const char* format, va_list ap)
+ __attribute__((__format__ (__printf__, 4, 0)));
+
+ // -=- Register a logger
+
+ void registerLogger();
+
+ // -=- CLASS FIELDS & FUNCTIONS
+
+ static Logger* loggers;
+
+ static Logger* getLogger(const char* name);
+
+ static void listLoggers();
+
+ private:
+ bool registered;
+ const char *m_name;
+ Logger *m_next;
+ };
+
+};
+
+#endif // __CORE_LOGGER_H__
diff --git a/common/core/Logger_file.cxx b/common/core/Logger_file.cxx
new file mode 100644
index 00000000..b8c72270
--- /dev/null
+++ b/common/core/Logger_file.cxx
@@ -0,0 +1,123 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
+ *
+ * This 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software 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 software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+// -=- Logger_file.cxx - Logger instance for a file
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <core/Logger_file.h>
+
+using namespace core;
+
+Logger_File::Logger_File(const char* loggerName)
+ : Logger(loggerName), indent(13), width(79), m_file(nullptr),
+ m_lastLogTime(0)
+{
+ m_filename[0] = '\0';
+}
+
+Logger_File::~Logger_File()
+{
+ closeFile();
+}
+
+void Logger_File::write(int /*level*/, const char *logname, const char *message)
+{
+ if (!m_file) {
+ if (m_filename[0] == '\0')
+ return;
+ char bakFilename[PATH_MAX];
+ if (snprintf(bakFilename, sizeof(bakFilename),
+ "%s.bak", m_filename) >= (int)sizeof(bakFilename)) {
+ remove(m_filename);
+ } else {
+ remove(bakFilename);
+ rename(m_filename, bakFilename);
+ }
+ m_file = fopen(m_filename, "w+");
+ if (!m_file) return;
+ }
+
+ time_t current = time(nullptr);
+ if (current != m_lastLogTime) {
+ m_lastLogTime = current;
+ fprintf(m_file, "\n%s", ctime(&m_lastLogTime));
+ }
+
+ fprintf(m_file," %s:", logname);
+ int column = strlen(logname) + 2;
+ if (column < indent) {
+ fprintf(m_file,"%*s",indent-column,"");
+ column = indent;
+ }
+ while (true) {
+ const char* s = strchr(message, ' ');
+ int wordLen;
+ if (s) wordLen = s-message;
+ else wordLen = strlen(message);
+
+ if (column + wordLen + 1 > width) {
+ fprintf(m_file,"\n%*s",indent,"");
+ column = indent;
+ }
+ fprintf(m_file," %.*s",wordLen,message);
+ column += wordLen + 1;
+ message += wordLen + 1;
+ if (!s) break;
+ }
+ fprintf(m_file,"\n");
+ fflush(m_file);
+}
+
+void Logger_File::setFilename(const char* filename)
+{
+ closeFile();
+ m_filename[0] = '\0';
+ if (strlen(filename) >= sizeof(m_filename))
+ return;
+ strcpy(m_filename, filename);
+}
+
+void Logger_File::setFile(FILE* file)
+{
+ closeFile();
+ m_file = file;
+}
+
+void Logger_File::closeFile()
+{
+ if (m_file) {
+ fclose(m_file);
+ m_file = nullptr;
+ }
+}
+
+static Logger_File logger("file");
+
+bool core::initFileLogger(const char* filename)
+{
+ logger.setFilename(filename);
+ logger.registerLogger();
+ return true;
+}
diff --git a/common/core/Logger_file.h b/common/core/Logger_file.h
new file mode 100644
index 00000000..6bce3e2d
--- /dev/null
+++ b/common/core/Logger_file.h
@@ -0,0 +1,53 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
+ *
+ * This 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software 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 software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+// -=- Logger_file - log to a file
+
+#ifndef __CORE_LOGGER_FILE_H__
+#define __CORE_LOGGER_FILE_H__
+
+#include <time.h>
+#include <limits.h>
+
+#include <core/Logger.h>
+
+namespace core {
+
+ class Logger_File : public Logger {
+ public:
+ Logger_File(const char* loggerName);
+ ~Logger_File();
+
+ void write(int level, const char *logname, const char *message) override;
+ void setFilename(const char* filename);
+ void setFile(FILE* file);
+
+ int indent;
+ int width;
+
+ protected:
+ void closeFile();
+ char m_filename[PATH_MAX];
+ FILE* m_file;
+ time_t m_lastLogTime;
+ };
+
+ bool initFileLogger(const char* filename);
+};
+
+#endif
diff --git a/common/core/Logger_stdio.cxx b/common/core/Logger_stdio.cxx
new file mode 100644
index 00000000..f27fc35d
--- /dev/null
+++ b/common/core/Logger_stdio.cxx
@@ -0,0 +1,37 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
+ *
+ * This 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software 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 software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+// -=- Logger_stdio.cxx - Logger instances for stderr and stdout
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <core/Logger_stdio.h>
+
+using namespace core;
+
+static Logger_StdIO logStdErr("stderr", stderr);
+static Logger_StdIO logStdOut("stdout", stdout);
+
+bool core::initStdIOLoggers()
+{
+ logStdErr.registerLogger();
+ logStdOut.registerLogger();
+ return true;
+}
diff --git a/common/core/Logger_stdio.h b/common/core/Logger_stdio.h
new file mode 100644
index 00000000..7613a20b
--- /dev/null
+++ b/common/core/Logger_stdio.h
@@ -0,0 +1,39 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
+ *
+ * This 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software 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 software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+// -=- Logger_stdio - standard output logger instances
+
+#ifndef __CORE_LOGGER_STDIO_H__
+#define __CORE_LOGGER_STDIO_H__
+
+#include <core/Logger_file.h>
+
+namespace core {
+
+ class Logger_StdIO : public Logger_File {
+ public:
+ Logger_StdIO(const char *name, FILE* file) : Logger_File(name) {
+ setFile(file);
+ }
+ };
+
+ bool initStdIOLoggers();
+
+};
+
+#endif
diff --git a/common/core/Logger_syslog.cxx b/common/core/Logger_syslog.cxx
new file mode 100644
index 00000000..cf7d065d
--- /dev/null
+++ b/common/core/Logger_syslog.cxx
@@ -0,0 +1,68 @@
+/* Copyright (C) 2015 TigerVNC
+ *
+ * This 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software 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 software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+// -=- Logger_syslog.cxx - Logger instance for a syslog
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+
+#include <core/Logger_syslog.h>
+#include <core/LogWriter.h>
+
+using namespace core;
+
+
+Logger_Syslog::Logger_Syslog(const char* loggerName)
+ : Logger(loggerName)
+{
+ openlog(nullptr, LOG_CONS | LOG_PID, LOG_USER);
+}
+
+Logger_Syslog::~Logger_Syslog()
+{
+ closelog();
+}
+
+void Logger_Syslog::write(int level, const char *logname, const char *message)
+{
+ // Convert our priority level into syslog level
+ int priority;
+ if (level >= LogWriter::LEVEL_DEBUG) {
+ priority = LOG_DEBUG;
+ } else if (level >= LogWriter::LEVEL_INFO) {
+ priority = LOG_INFO;
+ } else if (level >= LogWriter::LEVEL_STATUS) {
+ priority = LOG_NOTICE;
+ } else {
+ priority = LOG_ERR;
+ }
+
+ syslog(priority, "%s: %s", logname, message);
+}
+
+static Logger_Syslog logger("syslog");
+
+void core::initSyslogLogger()
+{
+ logger.registerLogger();
+}
diff --git a/common/core/Logger_syslog.h b/common/core/Logger_syslog.h
new file mode 100644
index 00000000..46adf932
--- /dev/null
+++ b/common/core/Logger_syslog.h
@@ -0,0 +1,41 @@
+/* Copyright (C) 2015 TigerVNC
+ *
+ * This 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software 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 software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+// -=- Logger_syslog - log to syslog
+
+#ifndef __CORE_LOGGER_SYSLOG_H__
+#define __CORE_LOGGER_SYSLOG_H__
+
+#include <time.h>
+
+#include <core/Logger.h>
+
+namespace core {
+
+ class Logger_Syslog : public Logger {
+ public:
+ Logger_Syslog(const char* loggerName);
+ virtual ~Logger_Syslog();
+
+ void write(int level, const char *logname, const char *message) override;
+ };
+
+ void initSyslogLogger();
+};
+
+#endif
diff --git a/common/core/Rect.h b/common/core/Rect.h
new file mode 100644
index 00000000..e4cb1634
--- /dev/null
+++ b/common/core/Rect.h
@@ -0,0 +1,120 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
+ *
+ * This 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software 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 software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+// core::Rect and core::Point structures
+
+#ifndef __CORE_RECT_INCLUDED__
+#define __CORE_RECT_INCLUDED__
+
+#include <algorithm>
+
+namespace core {
+
+ // core::Point
+ //
+ // Represents a point in 2D space, by X and Y coordinates.
+ // Can also be used to represent a delta, or offset, between
+ // two Points.
+ // Functions are provided to allow Points to be compared for
+ // equality and translated by a supplied offset.
+ // Functions are also provided to negate offset Points.
+
+ struct Point {
+ Point() : x(0), y(0) {}
+ Point(int x_, int y_) : x(x_), y(y_) {}
+ inline Point negate() const
+ __attribute__ ((warn_unused_result))
+ {return Point(-x, -y);}
+ inline bool operator==(const Point &p) const {return x==p.x && y==p.y;}
+ inline bool operator!=(const Point &p) const {return x!=p.x || y!=p.y;}
+ inline Point translate(const Point &p) const
+ __attribute__ ((warn_unused_result))
+ {return Point(x+p.x, y+p.y);}
+ inline Point subtract(const Point &p) const
+ __attribute__ ((warn_unused_result))
+ {return Point(x-p.x, y-p.y);}
+ int x, y;
+ };
+
+ // core::Rect
+ //
+ // Represents a rectangular region defined by its top-left (tl)
+ // and bottom-right (br) Points.
+ // Rects may be compared for equality, checked to determine whether
+ // or not they are empty, cleared (made empty), or intersected with
+ // one another. The bounding rectangle of two existing Rects
+ // may be calculated, as may the area of a Rect.
+ // Rects may also be translated, in the same way as Points, by
+ // an offset specified in a Point structure.
+
+ struct Rect {
+ Rect() {}
+ Rect(Point tl_, Point br_) : tl(tl_), br(br_) {}
+ Rect(int x1, int y1, int x2, int y2) : tl(x1, y1), br(x2, y2) {}
+ inline void setXYWH(int x, int y, int w, int h) {
+ tl.x = x; tl.y = y; br.x = x+w; br.y = y+h;
+ }
+ inline Rect intersect(const Rect &r) const
+ __attribute__ ((warn_unused_result))
+ {
+ Rect result;
+ result.tl.x = std::max(tl.x, r.tl.x);
+ result.tl.y = std::max(tl.y, r.tl.y);
+ result.br.x = std::max(std::min(br.x, r.br.x), result.tl.x);
+ result.br.y = std::max(std::min(br.y, r.br.y), result.tl.y);
+ return result;
+ }
+ inline Rect union_boundary(const Rect &r) const
+ __attribute__ ((warn_unused_result))
+ {
+ if (r.is_empty()) return *this;
+ if (is_empty()) return r;
+ Rect result;
+ result.tl.x = std::min(tl.x, r.tl.x);
+ result.tl.y = std::min(tl.y, r.tl.y);
+ result.br.x = std::max(br.x, r.br.x);
+ result.br.y = std::max(br.y, r.br.y);
+ return result;
+ }
+ inline Rect translate(const Point &p) const
+ __attribute__ ((warn_unused_result))
+ {
+ return Rect(tl.translate(p), br.translate(p));
+ }
+ inline bool operator==(const Rect &r) const {return r.tl == tl && r.br == br;}
+ inline bool operator!=(const Rect &r) const {return r.tl != tl || r.br != br;}
+ inline bool is_empty() const {return (tl.x >= br.x) || (tl.y >= br.y);}
+ inline void clear() {tl = Point(); br = Point();}
+ inline bool enclosed_by(const Rect &r) const {
+ return (tl.x>=r.tl.x) && (tl.y>=r.tl.y) && (br.x<=r.br.x) && (br.y<=r.br.y);
+ }
+ inline bool overlaps(const Rect &r) const {
+ return tl.x < r.br.x && tl.y < r.br.y && br.x > r.tl.x && br.y > r.tl.y;
+ }
+ inline int area() const {return is_empty() ? 0 : (br.x-tl.x)*(br.y-tl.y);}
+ inline Point dimensions() const {return Point(width(), height());}
+ inline int width() const {return br.x-tl.x;}
+ inline int height() const {return br.y-tl.y;}
+ inline bool contains(const Point &p) const {
+ return (tl.x<=p.x) && (tl.y<=p.y) && (br.x>p.x) && (br.y>p.y);
+ }
+ Point tl;
+ Point br;
+ };
+}
+#endif // __CORE_RECT_INCLUDED__
diff --git a/common/core/Region.cxx b/common/core/Region.cxx
new file mode 100644
index 00000000..03d788a9
--- /dev/null
+++ b/common/core/Region.cxx
@@ -0,0 +1,203 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
+ * Copyright 2016-2020 Pierre Ossman for Cendio AB
+ *
+ * This 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software 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 software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <core/LogWriter.h>
+#include <core/Region.h>
+
+extern "C" {
+#include <pixman.h>
+}
+
+using namespace core;
+
+static LogWriter vlog("Region");
+
+Region::Region()
+{
+ rgn = new struct pixman_region16;
+ pixman_region_init(rgn);
+}
+
+Region::Region(const Rect& r)
+{
+ rgn = new struct pixman_region16;
+ pixman_region_init_rect(rgn, r.tl.x, r.tl.y, r.width(), r.height());
+}
+
+Region::Region(const Region& r)
+{
+ rgn = new struct pixman_region16;
+ pixman_region_init(rgn);
+ pixman_region_copy(rgn, r.rgn);
+}
+
+Region::~Region()
+{
+ pixman_region_fini(rgn);
+ delete rgn;
+}
+
+Region& Region::operator=(const Region& r)
+{
+ pixman_region_copy(rgn, r.rgn);
+ return *this;
+}
+
+void Region::clear()
+{
+ // pixman_region_clear() isn't available on some older systems
+ pixman_region_fini(rgn);
+ pixman_region_init(rgn);
+}
+
+void Region::reset(const Rect& r)
+{
+ pixman_region_fini(rgn);
+ pixman_region_init_rect(rgn, r.tl.x, r.tl.y, r.width(), r.height());
+}
+
+void Region::translate(const Point& delta)
+{
+ pixman_region_translate(rgn, delta.x, delta.y);
+}
+
+void Region::assign_intersect(const Region& r)
+{
+ pixman_region_intersect(rgn, rgn, r.rgn);
+}
+
+void Region::assign_union(const Region& r)
+{
+ pixman_region_union(rgn, rgn, r.rgn);
+}
+
+void Region::assign_subtract(const Region& r)
+{
+ pixman_region_subtract(rgn, rgn, r.rgn);
+}
+
+Region Region::intersect(const Region& r) const
+{
+ Region ret;
+ pixman_region_intersect(ret.rgn, rgn, r.rgn);
+ return ret;
+}
+
+Region Region::union_(const Region& r) const
+{
+ Region ret;
+ pixman_region_union(ret.rgn, rgn, r.rgn);
+ return ret;
+}
+
+Region Region::subtract(const Region& r) const
+{
+ Region ret;
+ pixman_region_subtract(ret.rgn, rgn, r.rgn);
+ return ret;
+}
+
+bool Region::operator==(const Region& r) const
+{
+ return pixman_region_equal(rgn, r.rgn);
+}
+
+bool Region::operator!=(const Region& r) const
+{
+ return !pixman_region_equal(rgn, r.rgn);
+}
+
+int Region::numRects() const
+{
+ return pixman_region_n_rects(rgn);
+}
+
+bool Region::get_rects(std::vector<Rect>* rects,
+ bool left2right, bool topdown) const
+{
+ int nRects;
+ const pixman_box16_t* boxes;
+ int xInc, yInc, i;
+
+ boxes = pixman_region_rectangles(rgn, &nRects);
+
+ rects->clear();
+ rects->reserve(nRects);
+
+ xInc = left2right ? 1 : -1;
+ yInc = topdown ? 1 : -1;
+ i = topdown ? 0 : nRects-1;
+
+ while (nRects > 0) {
+ int firstInNextBand = i;
+ int nRectsInBand = 0;
+
+ while (nRects > 0 && boxes[firstInNextBand].y1 == boxes[i].y1)
+ {
+ firstInNextBand += yInc;
+ nRects--;
+ nRectsInBand++;
+ }
+
+ if (xInc != yInc)
+ i = firstInNextBand - yInc;
+
+ while (nRectsInBand > 0) {
+ Rect r(boxes[i].x1, boxes[i].y1, boxes[i].x2, boxes[i].y2);
+ rects->push_back(r);
+ i += xInc;
+ nRectsInBand--;
+ }
+
+ i = firstInNextBand;
+ }
+
+ return !rects->empty();
+}
+
+Rect Region::get_bounding_rect() const
+{
+ const pixman_box16_t* extents;
+ extents = pixman_region_extents(rgn);
+ return Rect(extents->x1, extents->y1, extents->x2, extents->y2);
+}
+
+
+void Region::debug_print(const char* prefix) const
+{
+ Rect extents;
+ std::vector<Rect> rects;
+ std::vector<Rect>::const_iterator iter;
+
+ extents = get_bounding_rect();
+ get_rects(&rects);
+
+ vlog.debug("%s num rects %3ld extents %3d,%3d %3dx%3d",
+ prefix, (long)rects.size(), extents.tl.x, extents.tl.y,
+ extents.width(), extents.height());
+
+ for (iter = rects.begin(); iter != rects.end(); ++iter) {
+ vlog.debug(" rect %3d,%3d %3dx%3d",
+ iter->tl.x, iter->tl.y, iter->width(), iter->height());
+ }
+}
diff --git a/common/core/Region.h b/common/core/Region.h
new file mode 100644
index 00000000..729f1475
--- /dev/null
+++ b/common/core/Region.h
@@ -0,0 +1,85 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
+ * Copyright 2016-2020 Pierre Ossman for Cendio AB
+ *
+ * This 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software 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 software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+// Region class wrapper around pixman's region operations
+
+#ifndef __CORE_REGION_INCLUDED__
+#define __CORE_REGION_INCLUDED__
+
+#include <vector>
+
+#include <core/Rect.h>
+
+struct pixman_region16;
+
+namespace core {
+
+ struct Point;
+ struct Rect;
+
+ class Region {
+ public:
+ // Create an empty region
+ Region();
+ // Create a rectangular region
+ Region(const Rect& r);
+
+ Region(const Region& r);
+ Region &operator=(const Region& src);
+
+ ~Region();
+
+ // the following methods alter the region in place:
+
+ void clear();
+ void reset(const Rect& r);
+ void translate(const Point& delta);
+
+ void assign_intersect(const Region& r);
+ void assign_union(const Region& r);
+ void assign_subtract(const Region& r);
+
+ // the following three operations return a new region:
+
+ Region intersect(const Region& r) const
+ __attribute__ ((warn_unused_result));
+ Region union_(const Region& r) const
+ __attribute__ ((warn_unused_result));
+ Region subtract(const Region& r) const
+ __attribute__ ((warn_unused_result));
+
+ bool operator==(const Region& b) const;
+ bool operator!=(const Region& b) const;
+ int numRects() const;
+ bool is_empty() const { return numRects() == 0; }
+
+ bool get_rects(std::vector<Rect>* rects, bool left2right=true,
+ bool topdown=true) const;
+ Rect get_bounding_rect() const;
+
+ void debug_print(const char *prefix) const;
+
+ protected:
+
+ struct pixman_region16* rgn;
+ };
+
+};
+
+#endif // __CORE_REGION_INCLUDED__
diff --git a/common/core/Timer.cxx b/common/core/Timer.cxx
new file mode 100644
index 00000000..77e98daf
--- /dev/null
+++ b/common/core/Timer.cxx
@@ -0,0 +1,155 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
+ * Copyright 2016-2024 Pierre Ossman for Cendio AB
+ *
+ * This 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software 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 software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+// -=- Timer.cxx
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <sys/time.h>
+
+#include <algorithm>
+
+#include <core/LogWriter.h>
+#include <core/Timer.h>
+#include <core/time.h>
+
+using namespace core;
+
+#ifndef __NO_DEFINE_VLOG__
+static LogWriter vlog("Timer");
+#endif
+
+std::list<Timer*> Timer::pending;
+
+int Timer::checkTimeouts() {
+ timeval start;
+
+ if (pending.empty())
+ return -1;
+
+ gettimeofday(&start, nullptr);
+ while (pending.front()->isBefore(start)) {
+ Timer* timer;
+
+ timer = pending.front();
+ pending.pop_front();
+
+ timer->lastDueTime = timer->dueTime;
+ timer->cb->handleTimeout(timer);
+
+ if (pending.empty())
+ return -1;
+ }
+ return getNextTimeout();
+}
+
+int Timer::getNextTimeout() {
+ timeval now;
+ gettimeofday(&now, nullptr);
+
+ if (pending.empty())
+ return -1;
+
+ int toWait = pending.front()->getRemainingMs();
+
+ if (toWait > pending.front()->timeoutMs) {
+ if (toWait - pending.front()->timeoutMs < 1000) {
+ vlog.info("gettimeofday is broken...");
+ return toWait;
+ }
+ // Time has jumped backwards!
+ vlog.info("Time has moved backwards!");
+ pending.front()->dueTime = now;
+ toWait = 0;
+ }
+
+ return toWait;
+}
+
+void Timer::insertTimer(Timer* t) {
+ std::list<Timer*>::iterator i;
+ for (i=pending.begin(); i!=pending.end(); i++) {
+ if (t->isBefore((*i)->dueTime)) {
+ pending.insert(i, t);
+ return;
+ }
+ }
+ pending.push_back(t);
+}
+
+void Timer::start(int timeoutMs_) {
+ timeval now;
+ gettimeofday(&now, nullptr);
+ stop();
+ timeoutMs = timeoutMs_;
+ dueTime = addMillis(now, timeoutMs);
+ insertTimer(this);
+}
+
+void Timer::repeat(int timeoutMs_) {
+ timeval now;
+
+ gettimeofday(&now, nullptr);
+
+ if (isStarted()) {
+ vlog.error("Incorrectly repeating already running timer");
+ stop();
+ }
+
+ if (msBetween(&lastDueTime, &dueTime) != 0)
+ vlog.error("Timer incorrectly modified whilst repeating");
+
+ if (timeoutMs_ != -1)
+ timeoutMs = timeoutMs_;
+
+ dueTime = addMillis(lastDueTime, timeoutMs);
+ if (isBefore(now)) {
+ // Time has jumped forwards, or we're not getting enough
+ // CPU time for the timers
+ dueTime = now;
+ }
+
+ insertTimer(this);
+}
+
+void Timer::stop() {
+ pending.remove(this);
+}
+
+bool Timer::isStarted() {
+ return std::find(pending.begin(), pending.end(),
+ this) != pending.end();
+}
+
+int Timer::getTimeoutMs() {
+ return timeoutMs;
+}
+
+int Timer::getRemainingMs() {
+ return msUntil(&dueTime);
+}
+
+bool Timer::isBefore(timeval other) {
+ return (dueTime.tv_sec < other.tv_sec) ||
+ ((dueTime.tv_sec == other.tv_sec) &&
+ (dueTime.tv_usec < other.tv_usec));
+}
diff --git a/common/core/Timer.h b/common/core/Timer.h
new file mode 100644
index 00000000..cde672b2
--- /dev/null
+++ b/common/core/Timer.h
@@ -0,0 +1,136 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
+ * Copyright 2018-2024 Pierre Ossman for Cendio AB
+ *
+ * This 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software 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 software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+#ifndef __CORE_TIMER_H__
+#define __CORE_TIMER_H__
+
+#include <list>
+#include <sys/time.h>
+
+namespace core {
+
+ /* Timer
+
+ Cross-platform timeout handling. The caller creates instances of
+ Timer and passes a Callback implementation to each. The Callback
+ will then be called with a pointer to the Timer instance that
+ timed-out when the timeout occurs.
+
+ The static methods of Timer are used by the main loop of the
+ application both to dispatch elapsed Timer callbacks and to
+ determine how long to wait in select() for the next timeout to
+ occur.
+
+ For classes that can be derived it's best to use MethodTimer which
+ can call a specific method on the class, thus avoiding conflicts
+ when subclassing.
+ */
+
+ struct Timer {
+
+ struct Callback {
+ // handleTimeout
+ // Passed a pointer to the Timer that has timed out. If the
+ // handler returns true then the Timer is reset and left
+ // running, causing another timeout after the appropriate
+ // interval.
+ // If the handler returns false then the Timer is cancelled.
+ virtual void handleTimeout(Timer* t) = 0;
+
+ virtual ~Callback() {}
+ };
+
+ // checkTimeouts()
+ // Dispatches any elapsed Timers, and returns the number of
+ // milliseconds until the next Timer will timeout.
+ static int checkTimeouts();
+
+ // getNextTimeout()
+ // Returns the number of milliseconds until the next timeout,
+ // without dispatching any elapsed Timers.
+ static int getNextTimeout();
+
+ // Create a Timer with the specified callback handler
+ Timer(Callback* cb_) {cb = cb_;}
+ ~Timer() {stop();}
+
+ // start()
+ // Starts the timer, causing a timeout after the specified number
+ // of milliseconds. If the timer is already active then it will
+ // be implicitly cancelled and re-started.
+ void start(int timeoutMs_);
+
+ // repeat()
+ // Restarts the timer in a way that repeats that last timeout.
+ // This allows you to have a periodic timer without the risk of
+ // accumulating drift caused by processing delays.
+ // A new interval can be specified, otherwise the previous
+ // interval is reused.
+ void repeat(int timeoutMs_=-1);
+
+ // stop()
+ // Cancels the timer.
+ void stop();
+
+ // isStarted()
+ // Determines whether the timer is started.
+ bool isStarted();
+
+ // getTimeoutMs()
+ // Determines the previously used timeout value, if any.
+ // Usually used with isStarted() to get the _current_ timeout.
+ int getTimeoutMs();
+
+ // getRemainingMs()
+ // Determines how many milliseconds are left before the Timer
+ // will timeout. Only valid for an active timer.
+ int getRemainingMs();
+
+ // isBefore()
+ // Determine whether the Timer will timeout before the specified
+ // time.
+ bool isBefore(timeval other);
+
+ protected:
+ timeval dueTime, lastDueTime;
+ int timeoutMs;
+ Callback* cb;
+
+ static void insertTimer(Timer* t);
+ // The list of currently active Timers, ordered by time left until
+ // timeout.
+ static std::list<Timer*> pending;
+ };
+
+ template<class T> class MethodTimer
+ : public Timer, public Timer::Callback {
+ public:
+ MethodTimer(T* obj_, void (T::*cb_)(Timer*))
+ : Timer(this), obj(obj_), cb(cb_) {}
+
+ void handleTimeout(Timer* t) override { return (obj->*cb)(t); }
+
+ private:
+ T* obj;
+ void (T::*cb)(Timer*);
+ };
+
+};
+
+#endif
diff --git a/common/core/string.cxx b/common/core/string.cxx
new file mode 100644
index 00000000..091836db
--- /dev/null
+++ b/common/core/string.cxx
@@ -0,0 +1,650 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
+ * Copyright 2011-2023 Pierre Ossman for Cendio AB
+ *
+ * This 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software 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 software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <core/string.h>
+
+namespace core {
+
+ std::string format(const char *fmt, ...)
+ {
+ va_list ap;
+ int len;
+ char *buf;
+ std::string out;
+
+ va_start(ap, fmt);
+ len = vsnprintf(nullptr, 0, fmt, ap);
+ va_end(ap);
+
+ if (len < 0)
+ return "";
+
+ buf = new char[len+1];
+
+ va_start(ap, fmt);
+ vsnprintf(buf, len+1, fmt, ap);
+ va_end(ap);
+
+ out = buf;
+
+ delete [] buf;
+
+ return out;
+ }
+
+ std::vector<std::string> split(const char* src,
+ const char delimiter)
+ {
+ std::vector<std::string> out;
+ const char *start, *stop;
+
+ if (src[0] == '\0')
+ return out;
+
+ start = src;
+ do {
+ stop = strchr(start, delimiter);
+ if (stop == nullptr) {
+ out.push_back(start);
+ } else {
+ out.push_back(std::string(start, stop-start));
+ start = stop + 1;
+ }
+ } while (stop != nullptr);
+
+ return out;
+ }
+
+ static char intToHex(uint8_t i) {
+ if (i<=9)
+ return '0'+i;
+ else if ((i>=10) && (i<=15))
+ return 'a'+(i-10);
+ assert(false);
+ return '\0';
+ }
+
+ void binToHex(const uint8_t* in, size_t inlen,
+ char* out, size_t outlen) {
+ if (inlen > outlen/2)
+ inlen = outlen/2;
+
+ if (inlen > 0) {
+ assert(in);
+ assert(out);
+ }
+
+ for (size_t i=0; i<inlen; i++) {
+ out[i*2] = intToHex((in[i] >> 4) & 15);
+ out[i*2+1] = intToHex((in[i] & 15));
+ }
+ }
+
+ std::string binToHex(const uint8_t* in, size_t inlen) {
+ char* buffer = new char[inlen*2+1]();
+ std::string out;
+ binToHex(in, inlen, buffer, inlen*2);
+ out = buffer;
+ delete [] buffer;
+ return out;
+ }
+
+ static bool readHexAndShift(char c, uint8_t* v) {
+ c=tolower(c);
+ if ((c >= '0') && (c <= '9'))
+ *v = (*v << 4) + (c - '0');
+ else if ((c >= 'a') && (c <= 'f'))
+ *v = (*v << 4) + (c - 'a' + 10);
+ else
+ return false;
+ return true;
+ }
+
+ bool hexToBin(const char* in, size_t inlen,
+ uint8_t* out, size_t outlen) {
+ assert(in || inlen == 0);
+ assert(out || outlen == 0);
+
+ if (inlen & 1)
+ return false;
+
+ if (inlen > outlen*2)
+ inlen = outlen*2;
+
+ for(size_t i=0; i<inlen; i+=2) {
+ uint8_t byte = 0;
+ if (!readHexAndShift(in[i], &byte) ||
+ !readHexAndShift(in[i+1], &byte))
+ return false;
+ out[i/2] = byte;
+ }
+
+ return true;
+ }
+
+ std::vector<uint8_t> hexToBin(const char* in, size_t inlen) {
+ std::vector<uint8_t> out(inlen/2);
+ if (!hexToBin(in, inlen, out.data(), inlen/2))
+ return std::vector<uint8_t>();
+ return out;
+ }
+
+ std::string convertLF(const char* src, size_t bytes)
+ {
+ size_t sz;
+ std::string out;
+
+ const char* in;
+ size_t in_len;
+
+ // Compute output size
+ sz = 0;
+ in = src;
+ in_len = bytes;
+ while ((in_len > 0) && (*in != '\0')) {
+ if (*in != '\r') {
+ sz++;
+ in++;
+ in_len--;
+ continue;
+ }
+
+ if ((in_len < 2) || (*(in+1) != '\n'))
+ sz++;
+
+ in++;
+ in_len--;
+ }
+
+ // Reserve space
+ out.reserve(sz);
+
+ // And convert
+ in = src;
+ in_len = bytes;
+ while ((in_len > 0) && (*in != '\0')) {
+ if (*in != '\r') {
+ out += *in++;
+ in_len--;
+ continue;
+ }
+
+ if ((in_len < 2) || (*(in+1) != '\n'))
+ out += '\n';
+
+ in++;
+ in_len--;
+ }
+
+ return out;
+ }
+
+ std::string convertCRLF(const char* src, size_t bytes)
+ {
+ std::string out;
+ size_t sz;
+
+ const char* in;
+ size_t in_len;
+
+ // Compute output size
+ sz = 0;
+ in = src;
+ in_len = bytes;
+ while ((in_len > 0) && (*in != '\0')) {
+ sz++;
+
+ if (*in == '\r') {
+ if ((in_len < 2) || (*(in+1) != '\n'))
+ sz++;
+ } else if (*in == '\n') {
+ if ((in == src) || (*(in-1) != '\r'))
+ sz++;
+ }
+
+ in++;
+ in_len--;
+ }
+
+ // Reserve space
+ out.reserve(sz);
+
+ // And convert
+ in = src;
+ in_len = bytes;
+ while ((in_len > 0) && (*in != '\0')) {
+ if (*in == '\n') {
+ if ((in == src) || (*(in-1) != '\r'))
+ out += '\r';
+ }
+
+ out += *in;
+
+ if (*in == '\r') {
+ if ((in_len < 2) || (*(in+1) != '\n'))
+ out += '\n';
+ }
+
+ in++;
+ in_len--;
+ }
+
+ return out;
+ }
+
+ size_t ucs4ToUTF8(unsigned src, char dst[5]) {
+ if (src < 0x80) {
+ *dst++ = src;
+ *dst++ = '\0';
+ return 1;
+ } else if (src < 0x800) {
+ *dst++ = 0xc0 | (src >> 6);
+ *dst++ = 0x80 | (src & 0x3f);
+ *dst++ = '\0';
+ return 2;
+ } else if ((src >= 0xd800) && (src < 0xe000)) {
+ return ucs4ToUTF8(0xfffd, dst);
+ } else if (src < 0x10000) {
+ *dst++ = 0xe0 | (src >> 12);
+ *dst++ = 0x80 | ((src >> 6) & 0x3f);
+ *dst++ = 0x80 | (src & 0x3f);
+ *dst++ = '\0';
+ return 3;
+ } else if (src < 0x110000) {
+ *dst++ = 0xf0 | (src >> 18);
+ *dst++ = 0x80 | ((src >> 12) & 0x3f);
+ *dst++ = 0x80 | ((src >> 6) & 0x3f);
+ *dst++ = 0x80 | (src & 0x3f);
+ *dst++ = '\0';
+ return 4;
+ } else {
+ return ucs4ToUTF8(0xfffd, dst);
+ }
+ }
+
+ size_t utf8ToUCS4(const char* src, size_t max, unsigned* dst) {
+ size_t count, consumed;
+
+ *dst = 0xfffd;
+
+ if (max == 0)
+ return 0;
+
+ consumed = 1;
+
+ if ((*src & 0x80) == 0) {
+ *dst = *src;
+ count = 0;
+ } else if ((*src & 0xe0) == 0xc0) {
+ *dst = *src & 0x1f;
+ count = 1;
+ } else if ((*src & 0xf0) == 0xe0) {
+ *dst = *src & 0x0f;
+ count = 2;
+ } else if ((*src & 0xf8) == 0xf0) {
+ *dst = *src & 0x07;
+ count = 3;
+ } else {
+ // Invalid sequence, consume all continuation characters
+ src++;
+ max--;
+ while ((max-- > 0) && ((*src++ & 0xc0) == 0x80))
+ consumed++;
+ return consumed;
+ }
+
+ src++;
+ max--;
+
+ while (count--) {
+ consumed++;
+
+ // Invalid or truncated sequence?
+ if ((max == 0) || ((*src & 0xc0) != 0x80)) {
+ *dst = 0xfffd;
+ return consumed;
+ }
+
+ *dst <<= 6;
+ *dst |= *src & 0x3f;
+
+ src++;
+ max--;
+ }
+
+ // UTF-16 surrogate code point?
+ if ((*dst >= 0xd800) && (*dst < 0xe000))
+ *dst = 0xfffd;
+
+ return consumed;
+ }
+
+ size_t ucs4ToUTF16(unsigned src, wchar_t dst[3]) {
+ if ((src < 0xd800) || ((src >= 0xe000) && (src < 0x10000))) {
+ *dst++ = src;
+ *dst++ = L'\0';
+ return 1;
+ } else if ((src >= 0x10000) && (src < 0x110000)) {
+ src -= 0x10000;
+ *dst++ = 0xd800 | ((src >> 10) & 0x03ff);
+ *dst++ = 0xdc00 | (src & 0x03ff);
+ *dst++ = L'\0';
+ return 2;
+ } else {
+ return ucs4ToUTF16(0xfffd, dst);
+ }
+ }
+
+ size_t utf16ToUCS4(const wchar_t* src, size_t max, unsigned* dst) {
+ *dst = 0xfffd;
+
+ if (max == 0)
+ return 0;
+
+ if ((*src < 0xd800) || (*src >= 0xe000)) {
+ *dst = *src;
+ return 1;
+ }
+
+ if (*src & 0x0400) {
+ size_t consumed;
+
+ // Invalid sequence, consume all continuation characters
+ consumed = 0;
+ while ((max > 0) && (*src & 0x0400)) {
+ src++;
+ max--;
+ consumed++;
+ }
+
+ return consumed;
+ }
+
+ *dst = *src++;
+ max--;
+
+ // Invalid or truncated sequence?
+ if ((max == 0) || ((*src & 0xfc00) != 0xdc00)) {
+ *dst = 0xfffd;
+ return 1;
+ }
+
+ *dst = 0x10000 + ((*dst & 0x03ff) << 10);
+ *dst |= *src & 0x3ff;
+
+ return 2;
+ }
+
+ std::string latin1ToUTF8(const char* src, size_t bytes) {
+ std::string out;
+ size_t sz;
+
+ const char* in;
+ size_t in_len;
+
+ // Compute output size
+ sz = 0;
+ in = src;
+ in_len = bytes;
+ while ((in_len > 0) && (*in != '\0')) {
+ char buf[5];
+ sz += ucs4ToUTF8(*(const unsigned char*)in, buf);
+ in++;
+ in_len--;
+ }
+
+ // Reserve space
+ out.reserve(sz);
+
+ // And convert
+ in = src;
+ in_len = bytes;
+ while ((in_len > 0) && (*in != '\0')) {
+ char buf[5];
+ ucs4ToUTF8(*(const unsigned char*)in, buf);
+ out += buf;
+ in++;
+ in_len--;
+ }
+
+ return out;
+ }
+
+ std::string utf8ToLatin1(const char* src, size_t bytes) {
+ std::string out;
+ size_t sz;
+
+ const char* in;
+ size_t in_len;
+
+ // Compute output size
+ sz = 0;
+ in = src;
+ in_len = bytes;
+ while ((in_len > 0) && (*in != '\0')) {
+ size_t len;
+ unsigned ucs;
+
+ len = utf8ToUCS4(in, in_len, &ucs);
+ in += len;
+ in_len -= len;
+ sz++;
+ }
+
+ // Reserve space
+ out.reserve(sz);
+
+ // And convert
+ in = src;
+ in_len = bytes;
+ while ((in_len > 0) && (*in != '\0')) {
+ size_t len;
+ unsigned ucs;
+
+ len = utf8ToUCS4(in, in_len, &ucs);
+ in += len;
+ in_len -= len;
+
+ if (ucs > 0xff)
+ out += '?';
+ else
+ out += (unsigned char)ucs;
+ }
+
+ return out;
+ }
+
+ std::string utf16ToUTF8(const wchar_t* src, size_t units)
+ {
+ std::string out;
+ size_t sz;
+
+ const wchar_t* in;
+ size_t in_len;
+
+ // Compute output size
+ sz = 0;
+ in = src;
+ in_len = units;
+ while ((in_len > 0) && (*in != '\0')) {
+ size_t len;
+ unsigned ucs;
+ char buf[5];
+
+ len = utf16ToUCS4(in, in_len, &ucs);
+ in += len;
+ in_len -= len;
+
+ sz += ucs4ToUTF8(ucs, buf);
+ }
+
+ // Reserve space
+ out.reserve(sz);
+
+ // And convert
+ in = src;
+ in_len = units;
+ while ((in_len > 0) && (*in != '\0')) {
+ size_t len;
+ unsigned ucs;
+ char buf[5];
+
+ len = utf16ToUCS4(in, in_len, &ucs);
+ in += len;
+ in_len -= len;
+
+ ucs4ToUTF8(ucs, buf);
+ out += buf;
+ }
+
+ return out;
+ }
+
+ std::wstring utf8ToUTF16(const char* src, size_t bytes)
+ {
+ std::wstring out;
+ size_t sz;
+
+ const char* in;
+ size_t in_len;
+
+ // Compute output size
+ sz = 0;
+ in = src;
+ in_len = bytes;
+ while ((in_len > 0) && (*in != '\0')) {
+ size_t len;
+ unsigned ucs;
+ wchar_t buf[3];
+
+ len = utf8ToUCS4(in, in_len, &ucs);
+ in += len;
+ in_len -= len;
+
+ sz += ucs4ToUTF16(ucs, buf);
+ }
+
+ // Reserve space
+ out.reserve(sz);
+
+ // And convert
+ in = src;
+ in_len = bytes;
+ while ((in_len > 0) && (*in != '\0')) {
+ size_t len;
+ unsigned ucs;
+ wchar_t buf[3];
+
+ len = utf8ToUCS4(in, in_len, &ucs);
+ in += len;
+ in_len -= len;
+
+ ucs4ToUTF16(ucs, buf);
+ out += buf;
+ }
+
+ return out;
+ }
+
+ bool isValidUTF8(const char* str, size_t bytes)
+ {
+ while ((bytes > 0) && (*str != '\0')) {
+ size_t len;
+ unsigned ucs;
+
+ len = utf8ToUCS4(str, bytes, &ucs);
+ str += len;
+ bytes -= len;
+
+ if (ucs == 0xfffd)
+ return false;
+ }
+
+ return true;
+ }
+
+ bool isValidUTF16(const wchar_t* wstr, size_t units)
+ {
+ while ((units > 0) && (*wstr != '\0')) {
+ size_t len;
+ unsigned ucs;
+
+ len = utf16ToUCS4(wstr, units, &ucs);
+ wstr += len;
+ units -= len;
+
+ if (ucs == 0xfffd)
+ return false;
+ }
+
+ return true;
+ }
+
+ static std::string doPrefix(long long value, const char *unit,
+ unsigned divisor, const char **prefixes,
+ size_t prefixCount, int precision) {
+ char buffer[256];
+ double newValue;
+ size_t prefix;
+
+ newValue = value;
+ prefix = 0;
+ while (newValue >= divisor) {
+ if (prefix >= prefixCount)
+ break;
+ newValue /= divisor;
+ prefix++;
+ }
+
+ snprintf(buffer, sizeof(buffer), "%.*g %s%s", precision, newValue,
+ (prefix == 0) ? "" : prefixes[prefix-1], unit);
+ buffer[sizeof(buffer)-1] = '\0';
+
+ return buffer;
+ }
+
+ static const char *siPrefixes[] =
+ { "k", "M", "G", "T", "P", "E", "Z", "Y" };
+ static const char *iecPrefixes[] =
+ { "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi" };
+
+ std::string siPrefix(long long value, const char *unit,
+ int precision) {
+ return doPrefix(value, unit, 1000, siPrefixes,
+ sizeof(siPrefixes)/sizeof(*siPrefixes),
+ precision);
+ }
+
+ std::string iecPrefix(long long value, const char *unit,
+ int precision) {
+ return doPrefix(value, unit, 1024, iecPrefixes,
+ sizeof(iecPrefixes)/sizeof(*iecPrefixes),
+ precision);
+ }
+};
diff --git a/common/core/string.h b/common/core/string.h
new file mode 100644
index 00000000..1465484d
--- /dev/null
+++ b/common/core/string.h
@@ -0,0 +1,80 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
+ * Copyright 2011-2023 Pierre Ossman for Cendio AB
+ *
+ * This 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software 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 software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+//
+// string.h - string utility functions
+//
+
+#ifndef __CORE_STRING_H__
+#define __CORE_STRING_H__
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+namespace core {
+
+ // Formats according to printf(), with a dynamic allocation
+ std::string format(const char *fmt, ...)
+ __attribute__((__format__ (__printf__, 1, 2)));
+
+ // Splits a string with the specified delimiter
+ std::vector<std::string> split(const char* src,
+ const char delimiter);
+
+ // Conversion to and from a hex string
+
+ void binToHex(const uint8_t* in, size_t inlen, char* out, size_t outlen);
+ std::string binToHex(const uint8_t* in, size_t inlen);
+ bool hexToBin(const char* in, size_t inlen, uint8_t* out, size_t outlen);
+ std::vector<uint8_t> hexToBin(const char* in, size_t inlen);
+
+ // Makes sure line endings are in a certain format
+
+ std::string convertLF(const char* src, size_t bytes = (size_t)-1);
+ std::string convertCRLF(const char* src, size_t bytes = (size_t)-1);
+
+ // Convertions between various Unicode formats
+
+ size_t ucs4ToUTF8(unsigned src, char dst[5]);
+ size_t utf8ToUCS4(const char* src, size_t max, unsigned* dst);
+
+ size_t ucs4ToUTF16(unsigned src, wchar_t dst[3]);
+ size_t utf16ToUCS4(const wchar_t* src, size_t max, unsigned* dst);
+
+ std::string latin1ToUTF8(const char* src, size_t bytes = (size_t)-1);
+ std::string utf8ToLatin1(const char* src, size_t bytes = (size_t)-1);
+
+ std::string utf16ToUTF8(const wchar_t* src, size_t units = (size_t)-1);
+ std::wstring utf8ToUTF16(const char* src, size_t bytes = (size_t)-1);
+
+ bool isValidUTF8(const char* str, size_t bytes = (size_t)-1);
+ bool isValidUTF16(const wchar_t* wstr, size_t units = (size_t)-1);
+
+ // Convert a value to a string using the correct prefix to reduce
+ // the length of the string
+
+ std::string siPrefix(long long value, const char *unit,
+ int precision=6);
+ std::string iecPrefix(long long value, const char *unit,
+ int precision=6);
+}
+
+#endif
diff --git a/common/core/time.cxx b/common/core/time.cxx
new file mode 100644
index 00000000..47a0ff8a
--- /dev/null
+++ b/common/core/time.cxx
@@ -0,0 +1,88 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
+ * Copyright 2011-2019 Pierre Ossman for Cendio AB
+ *
+ * This 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software 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 software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stddef.h>
+#include <sys/time.h>
+
+#include <core/time.h>
+
+namespace core {
+
+ unsigned msBetween(const struct timeval *first,
+ const struct timeval *second)
+ {
+ unsigned udiff;
+
+ if (isBefore(second, first))
+ return 0;
+
+ udiff = (second->tv_sec - first->tv_sec) * 1000000 +
+ (second->tv_usec - first->tv_usec);
+
+ return (udiff + 999) / 1000;
+ }
+
+ unsigned msSince(const struct timeval *then)
+ {
+ struct timeval now;
+
+ gettimeofday(&now, nullptr);
+
+ return msBetween(then, &now);
+ }
+
+ unsigned msUntil(const struct timeval *then)
+ {
+ struct timeval now;
+
+ gettimeofday(&now, nullptr);
+
+ return msBetween(&now, then);
+ }
+
+ bool isBefore(const struct timeval *first,
+ const struct timeval *second)
+ {
+ if (first->tv_sec < second->tv_sec)
+ return true;
+ if (first->tv_sec > second->tv_sec)
+ return false;
+ if (first->tv_usec < second->tv_usec)
+ return true;
+ return false;
+ }
+
+ struct timeval addMillis(struct timeval inTime, int millis)
+ {
+ int secs = millis / 1000;
+ millis = millis % 1000;
+ inTime.tv_sec += secs;
+ inTime.tv_usec += millis * 1000;
+ if (inTime.tv_usec >= 1000000) {
+ inTime.tv_sec++;
+ inTime.tv_usec -= 1000000;
+ }
+ return inTime;
+ }
+
+}
diff --git a/common/core/time.h b/common/core/time.h
new file mode 100644
index 00000000..319f336f
--- /dev/null
+++ b/common/core/time.h
@@ -0,0 +1,58 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
+ * Copyright 2011-2019 Pierre Ossman for Cendio AB
+ *
+ * This 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software 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 software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+//
+// time.h - time helper functions
+//
+
+#ifndef __CORE_TIME_H__
+#define __CORE_TIME_H__
+
+#include <limits.h>
+
+struct timeval;
+
+namespace core {
+
+ // secsToMillis() turns seconds into milliseconds, capping the value so it
+ // can't wrap round and become -ve
+ inline int secsToMillis(int secs) {
+ return (secs < 0 || secs > (INT_MAX/1000) ? INT_MAX : secs * 1000);
+ }
+
+ // Returns time elapsed between two moments in milliseconds.
+ unsigned msBetween(const struct timeval *first,
+ const struct timeval *second);
+
+ // Returns time elapsed since given moment in milliseconds.
+ unsigned msSince(const struct timeval *then);
+
+ // Returns time until the given moment in milliseconds.
+ unsigned msUntil(const struct timeval *then);
+
+ // Returns true if first happened before seconds
+ bool isBefore(const struct timeval *first,
+ const struct timeval *second);
+
+ // Returns a new timeval a specified number of milliseconds later than
+ // the given timeval
+ struct timeval addMillis(struct timeval inTime, int millis);
+}
+
+#endif
diff --git a/common/core/winerrno.h b/common/core/winerrno.h
new file mode 100644
index 00000000..a81fdc94
--- /dev/null
+++ b/common/core/winerrno.h
@@ -0,0 +1,91 @@
+
+/* Generated with:
+cat /usr/i686-pc-mingw32/sys-root/mingw/include/winerror.h \
+ | awk '/#define WSAE.*WSABASE/{gsub("WSA", ""); print "#undef " $2 "\n#define " $2 " WSA" $2}' \
+ | egrep -v 'EINTR|EBADF|EACCES|EFAULT|EINVAL|EMFILE|_QOS|PROVIDER|PROCTABLE'
+*/
+
+#include <winsock2.h>
+
+#undef EWOULDBLOCK
+#define EWOULDBLOCK WSAEWOULDBLOCK
+#undef EINPROGRESS
+#define EINPROGRESS WSAEINPROGRESS
+#undef EALREADY
+#define EALREADY WSAEALREADY
+#undef ENOTSOCK
+#define ENOTSOCK WSAENOTSOCK
+#undef EDESTADDRREQ
+#define EDESTADDRREQ WSAEDESTADDRREQ
+#undef EMSGSIZE
+#define EMSGSIZE WSAEMSGSIZE
+#undef EPROTOTYPE
+#define EPROTOTYPE WSAEPROTOTYPE
+#undef ENOPROTOOPT
+#define ENOPROTOOPT WSAENOPROTOOPT
+#undef EPROTONOSUPPORT
+#define EPROTONOSUPPORT WSAEPROTONOSUPPORT
+#undef ESOCKTNOSUPPORT
+#define ESOCKTNOSUPPORT WSAESOCKTNOSUPPORT
+#undef EOPNOTSUPP
+#define EOPNOTSUPP WSAEOPNOTSUPP
+#undef EPFNOSUPPORT
+#define EPFNOSUPPORT WSAEPFNOSUPPORT
+#undef EAFNOSUPPORT
+#define EAFNOSUPPORT WSAEAFNOSUPPORT
+#undef EADDRINUSE
+#define EADDRINUSE WSAEADDRINUSE
+#undef EADDRNOTAVAIL
+#define EADDRNOTAVAIL WSAEADDRNOTAVAIL
+#undef ENETDOWN
+#define ENETDOWN WSAENETDOWN
+#undef ENETUNREACH
+#define ENETUNREACH WSAENETUNREACH
+#undef ENETRESET
+#define ENETRESET WSAENETRESET
+#undef ECONNABORTED
+#define ECONNABORTED WSAECONNABORTED
+#undef ECONNRESET
+#define ECONNRESET WSAECONNRESET
+#undef ENOBUFS
+#define ENOBUFS WSAENOBUFS
+#undef EISCONN
+#define EISCONN WSAEISCONN
+#undef ENOTCONN
+#define ENOTCONN WSAENOTCONN
+#undef ESHUTDOWN
+#define ESHUTDOWN WSAESHUTDOWN
+#undef ETOOMANYREFS
+#define ETOOMANYREFS WSAETOOMANYREFS
+#undef ETIMEDOUT
+#define ETIMEDOUT WSAETIMEDOUT
+#undef ECONNREFUSED
+#define ECONNREFUSED WSAECONNREFUSED
+#undef ELOOP
+#define ELOOP WSAELOOP
+#undef ENAMETOOLONG
+#define ENAMETOOLONG WSAENAMETOOLONG
+#undef EHOSTDOWN
+#define EHOSTDOWN WSAEHOSTDOWN
+#undef EHOSTUNREACH
+#define EHOSTUNREACH WSAEHOSTUNREACH
+#undef ENOTEMPTY
+#define ENOTEMPTY WSAENOTEMPTY
+#undef EPROCLIM
+#define EPROCLIM WSAEPROCLIM
+#undef EUSERS
+#define EUSERS WSAEUSERS
+#undef EDQUOT
+#define EDQUOT WSAEDQUOT
+#undef ESTALE
+#define ESTALE WSAESTALE
+#undef EREMOTE
+#define EREMOTE WSAEREMOTE
+#undef EDISCON
+#define EDISCON WSAEDISCON
+#undef ENOMORE
+#define ENOMORE WSAENOMORE
+#undef ECANCELLED
+#define ECANCELLED WSAECANCELLED
+#undef EREFUSED
+#define EREFUSED WSAEREFUSED
diff --git a/common/core/xdgdirs.cxx b/common/core/xdgdirs.cxx
new file mode 100644
index 00000000..2628f317
--- /dev/null
+++ b/common/core/xdgdirs.cxx
@@ -0,0 +1,162 @@
+/* Copyright (C) 2010 TightVNC Team. All Rights Reserved.
+ * Copyright 2021-2023 Pierre Ossman for Cendio AB
+ *
+ * This 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software 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 software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <assert.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifndef WIN32
+#include <pwd.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#else
+#include <windows.h>
+#include <wininet.h> /* MinGW needs it */
+#include <shlobj.h>
+#define stat _stat
+#define mkdir(path, mode) mkdir(path)
+#endif
+
+#include <core/xdgdirs.h>
+
+static const char* getvncdir(bool userDir, const char *xdg_env, const char *xdg_def)
+{
+ static char dir[PATH_MAX], legacy[PATH_MAX];
+ struct stat st;
+
+#ifndef WIN32
+ char *homedir, *xdgdir;
+ uid_t uid;
+ struct passwd *passwd;
+#else
+ BOOL ret;
+#endif
+
+#ifndef WIN32
+ homedir = getenv("HOME");
+ if (homedir == nullptr) {
+ uid = getuid();
+ passwd = getpwuid(uid);
+ if (passwd == nullptr) {
+ /* Do we want emit error msg here? */
+ return nullptr;
+ }
+ homedir = passwd->pw_dir;
+ }
+
+ if (userDir)
+ return homedir;
+
+ xdgdir = getenv(xdg_env);
+ if (xdgdir != nullptr && xdgdir[0] == '/')
+ snprintf(dir, sizeof(dir), "%s/tigervnc", xdgdir);
+ else
+ snprintf(dir, sizeof(dir), "%s/%s/tigervnc", homedir, xdg_def);
+
+ snprintf(legacy, sizeof(legacy), "%s/.vnc", homedir);
+#else
+ (void) xdg_def;
+ (void) xdg_env;
+
+ if (userDir)
+ ret = SHGetSpecialFolderPath(nullptr, dir, CSIDL_PROFILE, FALSE);
+ else
+ ret = SHGetSpecialFolderPath(nullptr, dir, CSIDL_APPDATA, FALSE);
+
+ if (ret == FALSE)
+ return nullptr;
+
+ if (userDir)
+ return dir;
+
+ ret = SHGetSpecialFolderPath(nullptr, legacy, CSIDL_APPDATA, FALSE);
+
+ if (ret == FALSE)
+ return nullptr;
+
+ if (strlen(dir) + strlen("\\TigerVNC") >= sizeof(dir))
+ return nullptr;
+ if (strlen(legacy) + strlen("\\vnc") >= sizeof(legacy))
+ return nullptr;
+
+ strcat(dir, "\\TigerVNC");
+ strcat(legacy, "\\vnc");
+#endif
+ return (stat(dir, &st) != 0 && stat(legacy, &st) == 0) ? legacy : dir;
+}
+
+const char* core::getuserhomedir()
+{
+ return getvncdir(true, nullptr, nullptr);
+}
+
+const char* core::getvncconfigdir()
+{
+ return getvncdir(false, "XDG_CONFIG_HOME", ".config");
+}
+
+const char* core::getvncdatadir()
+{
+ return getvncdir(false, "XDG_DATA_HOME", ".local/share");
+}
+
+const char* core::getvncstatedir()
+{
+ return getvncdir(false, "XDG_STATE_HOME", ".local/state");
+}
+
+int core::mkdir_p(const char *path_, mode_t mode)
+{
+ char *path = strdup(path_);
+ char *p;
+
+#ifdef WIN32
+ (void)mode;
+#endif
+
+ for (p = path + 1; *p; p++) {
+ if (*p == '/') {
+ *p = '\0';
+ if (mkdir(path, mode) == -1) {
+ if (errno != EEXIST) {
+ free(path);
+ return -1;
+ }
+ }
+ *p = '/';
+ }
+ }
+
+ if (mkdir(path, mode) == -1) {
+ free(path);
+ return -1;
+ }
+
+ free(path);
+
+ return 0;
+}
diff --git a/common/core/xdgdirs.h b/common/core/xdgdirs.h
new file mode 100644
index 00000000..0769ba8b
--- /dev/null
+++ b/common/core/xdgdirs.h
@@ -0,0 +1,74 @@
+/* Copyright (C) 2010 TightVNC Team. All Rights Reserved.
+ * Copyright 2021-2023 Pierre Ossman for Cendio AB
+ *
+ * This 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software 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 software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+#ifndef CORE_XDGDIRS_H
+#define CORE_XDGDIRS_H
+
+#include <sys/stat.h>
+
+namespace core {
+
+ /*
+ * Get user home directory.
+ * If HOME environment variable is set then it is used.
+ * Otherwise home directory is obtained via getpwuid function.
+ *
+ * Returns NULL on failure.
+ */
+ const char* getuserhomedir();
+
+ /*
+ * Get VNC config directory. On Unix-like systems, this is either:
+ * - $XDG_CONFIG_HOME/tigervnc
+ * - $HOME/.config/tigervnc
+ * On Windows, this is simply %APPDATA%/TigerVNC/.
+ *
+ * Returns NULL on failure.
+ */
+ const char* getvncconfigdir();
+
+ /*
+ * Get VNC data directory used for X.509 known hosts.
+ * On Unix-like systems, this is either:
+ * - $XDG_DATA_HOME/tigervnc
+ * - $HOME/.local/share/tigervnc
+ * On Windows, this is simply %APPDATA%/TigerVNC/.
+ *
+ * Returns NULL on failure.
+ */
+ const char* getvncdatadir();
+
+ /*
+ * Get VNC state (logs) directory. On Unix-like systems, this is either:
+ * - $XDG_STATE_HOME/tigervnc
+ * - $HOME/.local/state/tigervnc
+ * On Windows, this is simply %APPDATA%/TigerVNC/.
+ *
+ * Returns NULL on failure.
+ */
+ const char* getvncstatedir();
+
+ /*
+ * Create directory recursively. Useful to create the nested directory
+ * structures needed for the above directories.
+ */
+ int mkdir_p(const char *path, mode_t mode);
+}
+
+#endif /* CORE_XDGDIRS_H */