From 5eeab4f50d1e3c7eee3a30a7aea3849a9ee530c8 Mon Sep 17 00:00:00 2001 From: jose-pr Date: Sat, 1 Aug 2026 20:51:03 -0400 Subject: [PATCH 01/10] Wrap the GCC function attributes in macros __attribute__((format(printf,...))), format_arg and warn_unused_result are used directly across the tree, and inconsistently: i18n.h already guarded some of its declarations and not others. There is no MSVC spelling for any of them, so a compiler without them cannot compile these headers at all. Add core/compiler.h with CORE_FORMAT_PRINTF, CORE_FORMAT_ARG and CORE_WARN_UNUSED_RESULT, which expand to the attribute on GCC and Clang and to nothing elsewhere, and use them at the existing sites. No warning is lost on the compilers that had them. --- common/core/LogWriter.h | 7 ++++--- common/core/Logger.h | 4 +++- common/core/Rect.h | 14 ++++++++------ common/core/Region.h | 7 ++++--- common/core/compiler.h | 37 +++++++++++++++++++++++++++++++++++++ common/core/i18n.h | 20 +++++++++++--------- common/core/string.h | 4 +++- vncviewer/DesktopWindow.h | 2 ++ vncviewer/vncviewer.h | 6 ++++-- 9 files changed, 76 insertions(+), 25 deletions(-) create mode 100644 common/core/compiler.h diff --git a/common/core/LogWriter.h b/common/core/LogWriter.h index b247bbec46..646b050e6e 100644 --- a/common/core/LogWriter.h +++ b/common/core/LogWriter.h @@ -25,6 +25,7 @@ #include #include +#include // Each log writer instance has a unique textual name, // and is attached to a particular Log instance and @@ -32,13 +33,13 @@ #define DEF_LOGFUNCTION(name, level) \ inline void v##name(const char* fmt, va_list ap) \ - __attribute__((__format__ (__printf__, 2, 0))) \ + CORE_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))) \ + CORE_FORMAT_PRINTF(2, 3) \ { \ if (m_log && (level <= m_level)) { \ va_list ap; va_start(ap, fmt); \ @@ -61,7 +62,7 @@ namespace core { int getLevel(void) { return m_level; } inline void write(int level, const char* format, ...) - __attribute__((__format__ (__printf__, 3, 4))) + CORE_FORMAT_PRINTF(3, 4) { if (m_log && (level <= m_level)) { va_list ap; diff --git a/common/core/Logger.h b/common/core/Logger.h index 5291915f80..e81787d559 100644 --- a/common/core/Logger.h +++ b/common/core/Logger.h @@ -24,6 +24,8 @@ #include #include +#include + // Each log writer instance has a unique textual name, // and is attached to a particular Logger instance and // is assigned a particular log level. @@ -46,7 +48,7 @@ namespace core { 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))); + CORE_FORMAT_PRINTF(4, 0); // -=- Register a logger diff --git a/common/core/Rect.h b/common/core/Rect.h index e4cb1634b7..ec9b142901 100644 --- a/common/core/Rect.h +++ b/common/core/Rect.h @@ -23,6 +23,8 @@ #include +#include + namespace core { // core::Point @@ -38,15 +40,15 @@ namespace core { Point() : x(0), y(0) {} Point(int x_, int y_) : x(x_), y(y_) {} inline Point negate() const - __attribute__ ((warn_unused_result)) + CORE_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)) + CORE_WARN_UNUSED_RESULT {return Point(x+p.x, y+p.y);} inline Point subtract(const Point &p) const - __attribute__ ((warn_unused_result)) + CORE_WARN_UNUSED_RESULT {return Point(x-p.x, y-p.y);} int x, y; }; @@ -70,7 +72,7 @@ namespace core { 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)) + CORE_WARN_UNUSED_RESULT { Rect result; result.tl.x = std::max(tl.x, r.tl.x); @@ -80,7 +82,7 @@ namespace core { return result; } inline Rect union_boundary(const Rect &r) const - __attribute__ ((warn_unused_result)) + CORE_WARN_UNUSED_RESULT { if (r.is_empty()) return *this; if (is_empty()) return r; @@ -92,7 +94,7 @@ namespace core { return result; } inline Rect translate(const Point &p) const - __attribute__ ((warn_unused_result)) + CORE_WARN_UNUSED_RESULT { return Rect(tl.translate(p), br.translate(p)); } diff --git a/common/core/Region.h b/common/core/Region.h index 729f14754a..87b2458e69 100644 --- a/common/core/Region.h +++ b/common/core/Region.h @@ -25,6 +25,7 @@ #include #include +#include struct pixman_region16; @@ -58,11 +59,11 @@ namespace core { // the following three operations return a new region: Region intersect(const Region& r) const - __attribute__ ((warn_unused_result)); + CORE_WARN_UNUSED_RESULT; Region union_(const Region& r) const - __attribute__ ((warn_unused_result)); + CORE_WARN_UNUSED_RESULT; Region subtract(const Region& r) const - __attribute__ ((warn_unused_result)); + CORE_WARN_UNUSED_RESULT; bool operator==(const Region& b) const; bool operator!=(const Region& b) const; diff --git a/common/core/compiler.h b/common/core/compiler.h new file mode 100644 index 0000000000..5eded87a43 --- /dev/null +++ b/common/core/compiler.h @@ -0,0 +1,37 @@ +/* Copyright 2026 jose-pr + * + * 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_COMPILER_H__ +#define __CORE_COMPILER_H__ + +/* + * A handful of GCC/Clang-only attributes have no MSVC equivalent; these + * macros expand to the attribute where supported and to nothing otherwise. + */ +#if defined(__GNUC__) || defined(__clang__) +#define CORE_FORMAT_PRINTF(fmt_idx, args_idx) \ + __attribute__((__format__ (__printf__, fmt_idx, args_idx))) +#define CORE_FORMAT_ARG(idx) __attribute__ ((format_arg (idx))) +#define CORE_WARN_UNUSED_RESULT __attribute__ ((warn_unused_result)) +#else +#define CORE_FORMAT_PRINTF(fmt_idx, args_idx) +#define CORE_FORMAT_ARG(idx) +#define CORE_WARN_UNUSED_RESULT +#endif + +#endif // __CORE_COMPILER_H__ diff --git a/common/core/i18n.h b/common/core/i18n.h index 83c05d9781..11a06bd67c 100644 --- a/common/core/i18n.h +++ b/common/core/i18n.h @@ -58,6 +58,8 @@ extern int swprintf (wchar_t *, size_t, const wchar_t *, ...) /* __attribute__((__format__ (__wprintf__, 3, 4))) */; #endif +#include + #define _(String) gettext (String) #define C_(Context, String) pgettext (Context, String) #define N_(String) gettext_noop (String) @@ -81,31 +83,31 @@ extern "C" { #endif const char *dgettext_rfb(const char *domainname, const char *msgid) - __attribute__ ((format_arg (2))); + CORE_FORMAT_ARG(2); const char *dcgettext_rfb(const char *domainname, const char *msgid, int category) - __attribute__ ((format_arg (2))); + CORE_FORMAT_ARG(2); const char *dngettext_rfb(const char *domainname, const char *msgid, const char *msgid_plural, unsigned long int n) - __attribute__ ((format_arg (2))) - __attribute__ ((format_arg (3))); + CORE_FORMAT_ARG(2) + CORE_FORMAT_ARG(3); const char *dcngettext_rfb(const char *domainname, const char *msgid, const char *msgid_plural, unsigned long int n, int category) - __attribute__ ((format_arg (2))) - __attribute__ ((format_arg (3))); + CORE_FORMAT_ARG(2) + CORE_FORMAT_ARG(3); const char *pgettext_rfb(const char *domain, const char *msg_ctxt_id, const char *msgid, int category) - __attribute__ ((format_arg (3))); + CORE_FORMAT_ARG(3); const char *npgettext_rfb(const char *domain, const char *msg_ctxt_id, const char *msgid, const char *msgid_plural, unsigned long int n, int category) - __attribute__ ((format_arg (3))) - __attribute__ ((format_arg (4))); + CORE_FORMAT_ARG(3) + CORE_FORMAT_ARG(4); #ifdef __cplusplus } diff --git a/common/core/string.h b/common/core/string.h index a67ade3b5a..af5beb2c5d 100644 --- a/common/core/string.h +++ b/common/core/string.h @@ -29,11 +29,13 @@ #include #include +#include + namespace core { // Formats according to printf(), with a dynamic allocation std::string format(const char *fmt, ...) - __attribute__((__format__ (__printf__, 1, 2))); + CORE_FORMAT_PRINTF(1, 2); // Splits a string with the specified delimiter std::vector split(const char* src, diff --git a/vncviewer/DesktopWindow.h b/vncviewer/DesktopWindow.h index af1b7474df..a41f97445c 100644 --- a/vncviewer/DesktopWindow.h +++ b/vncviewer/DesktopWindow.h @@ -26,6 +26,8 @@ #include +#include + #include namespace rfb { class ModifiablePixelBuffer; } diff --git a/vncviewer/vncviewer.h b/vncviewer/vncviewer.h index 44c5f53e97..74e5c75130 100644 --- a/vncviewer/vncviewer.h +++ b/vncviewer/vncviewer.h @@ -19,12 +19,14 @@ #ifndef __VNCVIEWER_H__ #define __VNCVIEWER_H__ +#include + #define VNCSERVERNAMELEN 256 void abort_vncviewer(const char *error, ...) - __attribute__((__format__ (__printf__, 1, 2))); + CORE_FORMAT_PRINTF(1, 2); void abort_connection(const char *error, ...) - __attribute__((__format__ (__printf__, 1, 2))); + CORE_FORMAT_PRINTF(1, 2); void abort_connection_with_unexpected_error(const std::exception &); void disconnect(); From 353e8739e4c53404df325c78b3e30aa055da4b58 Mon Sep 17 00:00:00 2001 From: jose-pr Date: Sat, 1 Aug 2026 20:51:22 -0400 Subject: [PATCH 02/10] Add core/os.h for the POSIX interfaces MSVC lacks MinGW supplies these transparently, so the tree calls them unguarded. MSVC supplies none of them: gettimeofday(), strcasecmp()/strncasecmp(), PATH_MAX, mode_t, and dirname() -- there is no at all. Add core/os.h with the minimal shims (gettimeofday via GetSystemTimeAsFileTime, the string comparisons via _stricmp/_strnicmp, PATH_MAX as _MAX_PATH) and include it where those names are actually used. Two related fixes come along, because they are the same problem: was included unconditionally by TcpSocket.cxx and vncviewer.cxx, and by ServerDialog.cxx, none of which exist on MSVC; vncviewer.cxx also called _open_osfhandle() without . And xdgdirs' own compatibility macro expanded mkdir(path, mode) to mkdir(path), a symbol MinGW has and MSVC does not -- it needs _mkdir from . --- common/core/Configuration.cxx | 1 + common/core/LogWriter.cxx | 1 + common/core/Logger.cxx | 1 + common/core/Logger_file.h | 1 + common/core/Timer.cxx | 1 + common/core/i18n.cxx | 1 + common/core/os.h | 88 ++++++++++++++++++++++++++++++++ common/core/time.cxx | 1 + common/core/xdgdirs.cxx | 5 +- common/core/xdgdirs.h | 2 + common/network/TcpSocket.cxx | 2 + common/rdr/BufferedInStream.cxx | 1 + common/rdr/BufferedOutStream.cxx | 1 + common/rdr/FdOutStream.cxx | 1 + common/rfb/Congestion.cxx | 1 + common/rfb/Security.cxx | 1 + common/rfb/encodings.cxx | 1 + tests/perf/decperf.cxx | 1 + tests/perf/encperf.cxx | 1 + tests/perf/fbperf.cxx | 1 + vncviewer/BaseTouchHandler.cxx | 1 + vncviewer/DesktopWindow.cxx | 1 + vncviewer/ServerDialog.cxx | 4 ++ vncviewer/ShortcutHandler.cxx | 1 + vncviewer/vncviewer.cxx | 4 +- 25 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 common/core/os.h diff --git a/common/core/Configuration.cxx b/common/core/Configuration.cxx index 234e112f96..571162f006 100644 --- a/common/core/Configuration.cxx +++ b/common/core/Configuration.cxx @@ -36,6 +36,7 @@ #include #include #include +#include #include #include diff --git a/common/core/LogWriter.cxx b/common/core/LogWriter.cxx index 4490c874d7..7b0e17a665 100644 --- a/common/core/LogWriter.cxx +++ b/common/core/LogWriter.cxx @@ -26,6 +26,7 @@ #include #include +#include #include #include #include diff --git a/common/core/Logger.cxx b/common/core/Logger.cxx index bb494acc7b..e506417538 100644 --- a/common/core/Logger.cxx +++ b/common/core/Logger.cxx @@ -26,6 +26,7 @@ #include #include +#include #include #include #include diff --git a/common/core/Logger_file.h b/common/core/Logger_file.h index 6bce3e2df5..e7080d20ef 100644 --- a/common/core/Logger_file.h +++ b/common/core/Logger_file.h @@ -25,6 +25,7 @@ #include #include +#include namespace core { diff --git a/common/core/Timer.cxx b/common/core/Timer.cxx index e50792cde6..25a5c9f054 100644 --- a/common/core/Timer.cxx +++ b/common/core/Timer.cxx @@ -30,6 +30,7 @@ #include #include +#include #include using namespace core; diff --git a/common/core/i18n.cxx b/common/core/i18n.cxx index a75b766739..b4a29f2511 100644 --- a/common/core/i18n.cxx +++ b/common/core/i18n.cxx @@ -32,6 +32,7 @@ #include #endif +#include #include // Restore original functions diff --git a/common/core/os.h b/common/core/os.h new file mode 100644 index 0000000000..a49f09cb8b --- /dev/null +++ b/common/core/os.h @@ -0,0 +1,88 @@ +/* Copyright 2026 jose-pr + * + * 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_OS_H__ +#define __CORE_OS_H__ + +/* + * MSVC lacks a number of POSIX APIs that this project's previous Windows + * toolchain (mingw, a GCC/glibc-compatible environment) provided + * transparently. Centralize the portable equivalents/shims here rather + * than scattering per-file fixes. + */ +#ifdef _WIN32 + +#include // _MAX_PATH +#include // _stricmp, _strnicmp +#include // struct timeval, FILETIME + +#ifndef PATH_MAX +#define PATH_MAX _MAX_PATH +#endif + +#define strcasecmp _stricmp +#define strncasecmp _strnicmp + +typedef int mode_t; + +inline int gettimeofday(struct timeval *tv, void *) +{ + /* FILETIME is in 100ns intervals since 1601-01-01; convert to + * microseconds since the Unix epoch (1970-01-01). */ + static const unsigned long long kEpochDiff = 116444736000000000ULL; + FILETIME ft; + unsigned long long t; + + GetSystemTimeAsFileTime(&ft); + t = ((unsigned long long)ft.dwHighDateTime << 32) | ft.dwLowDateTime; + t = (t - kEpochDiff) / 10; + + tv->tv_sec = (long)(t / 1000000ULL); + tv->tv_usec = (long)(t % 1000000ULL); + + return 0; +} + +/* POSIX dirname(), minimally: strip the trailing filename component, + * accepting both path separators since Windows paths use either. Modifies + * and returns its argument, matching the (permitted) in-place POSIX form. */ +inline char *dirname(char *path) +{ + char *slash = strrchr(path, '\\'); + char *fwdslash = strrchr(path, '/'); + + if (fwdslash > slash) + slash = fwdslash; + + if (slash == nullptr) { + static char dot[] = "."; + return dot; + } + + if (slash == path) { + *(slash + 1) = '\0'; + return path; + } + + *slash = '\0'; + return path; +} + +#endif // _WIN32 + +#endif // __CORE_OS_H__ diff --git a/common/core/time.cxx b/common/core/time.cxx index 47a0ff8ad6..628144d620 100644 --- a/common/core/time.cxx +++ b/common/core/time.cxx @@ -24,6 +24,7 @@ #include #include +#include #include namespace core { diff --git a/common/core/xdgdirs.cxx b/common/core/xdgdirs.cxx index 2628f317b5..e6c748c5ba 100644 --- a/common/core/xdgdirs.cxx +++ b/common/core/xdgdirs.cxx @@ -37,10 +37,13 @@ #include #include /* MinGW needs it */ #include +#include /* _mkdir */ #define stat _stat -#define mkdir(path, mode) mkdir(path) +/* MSVC only provides _mkdir (no bare mkdir symbol at all, unlike mingw) */ +#define mkdir(path, mode) _mkdir(path) #endif +#include #include static const char* getvncdir(bool userDir, const char *xdg_env, const char *xdg_def) diff --git a/common/core/xdgdirs.h b/common/core/xdgdirs.h index 0769ba8bae..7f642f1ec4 100644 --- a/common/core/xdgdirs.h +++ b/common/core/xdgdirs.h @@ -22,6 +22,8 @@ #include +#include + namespace core { /* diff --git a/common/network/TcpSocket.cxx b/common/network/TcpSocket.cxx index 3d6f22858a..dca9cd23e4 100644 --- a/common/network/TcpSocket.cxx +++ b/common/network/TcpSocket.cxx @@ -39,7 +39,9 @@ #include #include #include +#ifndef WIN32 #include +#endif #include #include diff --git a/common/rdr/BufferedInStream.cxx b/common/rdr/BufferedInStream.cxx index 0ebbd65508..885dae4672 100644 --- a/common/rdr/BufferedInStream.cxx +++ b/common/rdr/BufferedInStream.cxx @@ -23,6 +23,7 @@ #include +#include #include #include diff --git a/common/rdr/BufferedOutStream.cxx b/common/rdr/BufferedOutStream.cxx index ee423eba3b..b2372b871c 100644 --- a/common/rdr/BufferedOutStream.cxx +++ b/common/rdr/BufferedOutStream.cxx @@ -22,6 +22,7 @@ #include #endif +#include #include #include diff --git a/common/rdr/FdOutStream.cxx b/common/rdr/FdOutStream.cxx index 416926c1ec..61efb3dd4e 100644 --- a/common/rdr/FdOutStream.cxx +++ b/common/rdr/FdOutStream.cxx @@ -44,6 +44,7 @@ #include #endif +#include #include #include diff --git a/common/rfb/Congestion.cxx b/common/rfb/Congestion.cxx index 46bae00da4..4c95fbd9e8 100644 --- a/common/rfb/Congestion.cxx +++ b/common/rfb/Congestion.cxx @@ -50,6 +50,7 @@ #endif #include +#include #include #include diff --git a/common/rfb/Security.cxx b/common/rfb/Security.cxx index 58eea010b3..7841d835f7 100644 --- a/common/rfb/Security.cxx +++ b/common/rfb/Security.cxx @@ -27,6 +27,7 @@ #include #include +#include #include #include diff --git a/common/rfb/encodings.cxx b/common/rfb/encodings.cxx index 21bd874c0d..fb03229960 100644 --- a/common/rfb/encodings.cxx +++ b/common/rfb/encodings.cxx @@ -22,6 +22,7 @@ #include +#include #include #include diff --git a/tests/perf/decperf.cxx b/tests/perf/decperf.cxx index 1ac3a535a1..519bb07274 100644 --- a/tests/perf/decperf.cxx +++ b/tests/perf/decperf.cxx @@ -42,6 +42,7 @@ #include #include +#include #include "util.h" // FIXME: Files are always in this format diff --git a/tests/perf/encperf.cxx b/tests/perf/encperf.cxx index 7eb3898407..ee37af2c4e 100644 --- a/tests/perf/encperf.cxx +++ b/tests/perf/encperf.cxx @@ -52,6 +52,7 @@ #include #include +#include #include "util.h" static core::IntParameter width("width", "Frame buffer width", 0); diff --git a/tests/perf/fbperf.cxx b/tests/perf/fbperf.cxx index 1a5b5ba713..fd15c11a01 100644 --- a/tests/perf/fbperf.cxx +++ b/tests/perf/fbperf.cxx @@ -33,6 +33,7 @@ #include "../vncviewer/PlatformPixelBuffer.h" +#include #include "util.h" class TestWindow: public Fl_Window { diff --git a/vncviewer/BaseTouchHandler.cxx b/vncviewer/BaseTouchHandler.cxx index 3100f86b98..f49d09bd19 100644 --- a/vncviewer/BaseTouchHandler.cxx +++ b/vncviewer/BaseTouchHandler.cxx @@ -30,6 +30,7 @@ #include #include "GestureHandler.h" +#include #include "BaseTouchHandler.h" // Sensitivity threshold for gestures diff --git a/vncviewer/DesktopWindow.cxx b/vncviewer/DesktopWindow.cxx index 199b2e8118..5985d65b78 100644 --- a/vncviewer/DesktopWindow.cxx +++ b/vncviewer/DesktopWindow.cxx @@ -37,6 +37,7 @@ #include #include +#include #include "DesktopWindow.h" #include "OptionsDialog.h" #include "parameters.h" diff --git a/vncviewer/ServerDialog.cxx b/vncviewer/ServerDialog.cxx index 2dc2824b5f..dc34426d9a 100644 --- a/vncviewer/ServerDialog.cxx +++ b/vncviewer/ServerDialog.cxx @@ -23,7 +23,11 @@ #include #include +#ifndef WIN32 #include +#endif + +#include // FIXME: Workaround for FLTK including windows.h #ifdef WIN32 diff --git a/vncviewer/ShortcutHandler.cxx b/vncviewer/ShortcutHandler.cxx index 05ba75fb6a..af7cfa02e0 100644 --- a/vncviewer/ShortcutHandler.cxx +++ b/vncviewer/ShortcutHandler.cxx @@ -25,6 +25,7 @@ #define XK_MISCELLANY #include +#include #include "ShortcutHandler.h" ShortcutHandler::ShortcutHandler() : diff --git a/vncviewer/vncviewer.cxx b/vncviewer/vncviewer.cxx index 20a0174b8c..ae68062b8d 100644 --- a/vncviewer/vncviewer.cxx +++ b/vncviewer/vncviewer.cxx @@ -30,12 +30,14 @@ #include #include #include -#include #include #ifdef WIN32 #include #include +#include // _open_osfhandle +#else +#include #endif #include From cb0b6dc20cf3de25d4a33446d45df2a85cf6c2a0 Mon Sep 17 00:00:00 2001 From: jose-pr Date: Sat, 1 Aug 2026 20:51:35 -0400 Subject: [PATCH 03/10] Take struct timeval from on Windows is not a Windows header. MinGW ships a shim, which is why these direct includes have never been a problem; without one, struct timeval comes from instead. Guard each include accordingly. FdInStream.cxx is the odd one out: its sat above the platform #ifdef rather than inside it, unlike its FdOutStream.cxx sibling, so the include is moved into the existing #else branch rather than wrapped. --- common/core/Timer.cxx | 4 ++++ common/core/Timer.h | 4 ++++ common/core/time.cxx | 4 ++++ common/rdr/BufferedInStream.h | 4 ++++ common/rdr/BufferedOutStream.h | 4 ++++ common/rdr/FdInStream.cxx | 2 +- common/rdr/FdOutStream.h | 4 ++++ common/rfb/Congestion.cxx | 4 ++++ common/rfb/VNCServerST.h | 4 ++++ tests/perf/decperf.cxx | 4 ++++ tests/perf/encperf.cxx | 4 ++++ tests/perf/fbperf.cxx | 4 ++++ vncviewer/BaseTouchHandler.h | 4 ++++ vncviewer/DesktopWindow.cxx | 4 ++++ vncviewer/DesktopWindow.h | 8 ++++++-- 15 files changed, 59 insertions(+), 3 deletions(-) diff --git a/common/core/Timer.cxx b/common/core/Timer.cxx index 25a5c9f054..698b39575c 100644 --- a/common/core/Timer.cxx +++ b/common/core/Timer.cxx @@ -24,7 +24,11 @@ #endif #include +#ifdef _WIN32 +#include +#else #include +#endif #include diff --git a/common/core/Timer.h b/common/core/Timer.h index cde672b24c..10b0c18237 100644 --- a/common/core/Timer.h +++ b/common/core/Timer.h @@ -21,7 +21,11 @@ #define __CORE_TIMER_H__ #include +#ifdef _WIN32 +#include +#else #include +#endif namespace core { diff --git a/common/core/time.cxx b/common/core/time.cxx index 628144d620..ffdedb73c6 100644 --- a/common/core/time.cxx +++ b/common/core/time.cxx @@ -22,7 +22,11 @@ #endif #include +#ifdef _WIN32 +#include +#else #include +#endif #include #include diff --git a/common/rdr/BufferedInStream.h b/common/rdr/BufferedInStream.h index b3d6115e08..2d4a961c69 100644 --- a/common/rdr/BufferedInStream.h +++ b/common/rdr/BufferedInStream.h @@ -24,7 +24,11 @@ #ifndef __RDR_BUFFEREDINSTREAM_H__ #define __RDR_BUFFEREDINSTREAM_H__ +#ifdef _WIN32 +#include +#else #include +#endif #include diff --git a/common/rdr/BufferedOutStream.h b/common/rdr/BufferedOutStream.h index dd765dc9ed..ed18cf84e0 100644 --- a/common/rdr/BufferedOutStream.h +++ b/common/rdr/BufferedOutStream.h @@ -24,7 +24,11 @@ #ifndef __RDR_BUFFEREDOUTSTREAM_H__ #define __RDR_BUFFEREDOUTSTREAM_H__ +#ifdef _WIN32 +#include +#else #include +#endif #include diff --git a/common/rdr/FdInStream.cxx b/common/rdr/FdInStream.cxx index 25542a0149..a4be35659c 100644 --- a/common/rdr/FdInStream.cxx +++ b/common/rdr/FdInStream.cxx @@ -23,7 +23,6 @@ #include #include #include -#include #ifdef _WIN32 #include #define errorNumber WSAGetLastError() @@ -32,6 +31,7 @@ #else #include #include +#include #include #define errorNumber errno #endif diff --git a/common/rdr/FdOutStream.h b/common/rdr/FdOutStream.h index d9f16efb71..f37fe97f7c 100644 --- a/common/rdr/FdOutStream.h +++ b/common/rdr/FdOutStream.h @@ -24,7 +24,11 @@ #ifndef __RDR_FDOUTSTREAM_H__ #define __RDR_FDOUTSTREAM_H__ +#ifdef _WIN32 +#include +#else #include +#endif #include diff --git a/common/rfb/Congestion.cxx b/common/rfb/Congestion.cxx index 4c95fbd9e8..278263220c 100644 --- a/common/rfb/Congestion.cxx +++ b/common/rfb/Congestion.cxx @@ -39,7 +39,11 @@ #include #include +#ifdef _WIN32 +#include +#else #include +#endif #ifdef __linux__ #include diff --git a/common/rfb/VNCServerST.h b/common/rfb/VNCServerST.h index 07392794fe..1f79d80b26 100644 --- a/common/rfb/VNCServerST.h +++ b/common/rfb/VNCServerST.h @@ -24,7 +24,11 @@ #ifndef __RFB_VNCSERVERST_H__ #define __RFB_VNCSERVERST_H__ +#ifdef _WIN32 +#include +#else #include +#endif #include diff --git a/tests/perf/decperf.cxx b/tests/perf/decperf.cxx index 519bb07274..6976b43fc8 100644 --- a/tests/perf/decperf.cxx +++ b/tests/perf/decperf.cxx @@ -31,7 +31,11 @@ #include #include #include +#ifdef _WIN32 +#include +#else #include +#endif #include #include diff --git a/tests/perf/encperf.cxx b/tests/perf/encperf.cxx index ee37af2c4e..da3de44131 100644 --- a/tests/perf/encperf.cxx +++ b/tests/perf/encperf.cxx @@ -35,7 +35,11 @@ #include #include #include +#ifdef _WIN32 +#include +#else #include +#endif #include diff --git a/tests/perf/fbperf.cxx b/tests/perf/fbperf.cxx index fd15c11a01..512492c4be 100644 --- a/tests/perf/fbperf.cxx +++ b/tests/perf/fbperf.cxx @@ -21,7 +21,11 @@ #endif #include +#ifdef _WIN32 +#include +#else #include +#endif #include #include diff --git a/vncviewer/BaseTouchHandler.h b/vncviewer/BaseTouchHandler.h index 8db658c0fd..9a30446d0e 100644 --- a/vncviewer/BaseTouchHandler.h +++ b/vncviewer/BaseTouchHandler.h @@ -22,7 +22,11 @@ #include "GestureEvent.h" +#ifdef _WIN32 +#include +#else #include +#endif class BaseTouchHandler { public: diff --git a/vncviewer/DesktopWindow.cxx b/vncviewer/DesktopWindow.cxx index 5985d65b78..afbd230a30 100644 --- a/vncviewer/DesktopWindow.cxx +++ b/vncviewer/DesktopWindow.cxx @@ -27,7 +27,11 @@ #include #include #include +#ifdef _WIN32 +#include +#else #include +#endif #include #include diff --git a/vncviewer/DesktopWindow.h b/vncviewer/DesktopWindow.h index a41f97445c..5eb936313f 100644 --- a/vncviewer/DesktopWindow.h +++ b/vncviewer/DesktopWindow.h @@ -24,7 +24,11 @@ #include #include +#ifdef _WIN32 +#include +#else #include +#endif #include @@ -88,9 +92,9 @@ class DesktopWindow : public Fl_Window { private: void addOverlayTip(const char *text, ...) - __attribute__((__format__ (__printf__, 2, 3))); + CORE_FORMAT_PRINTF(2, 3); void addOverlayError(const char *text, ...) - __attribute__((__format__ (__printf__, 2, 3))); + CORE_FORMAT_PRINTF(2, 3); void addOverlay(const char *text); static void updateOverlay(void *data); From 04dde519f7c4590f01b42b9e430f0fef029f5545 Mon Sep 17 00:00:00 2001 From: jose-pr Date: Sat, 1 Aug 2026 20:51:37 -0400 Subject: [PATCH 04/10] Avoid two constructs MSVC rejects in vncviewer usage() puts #ifndef WIN32 / #endif inside the argument list of the _() macro. GCC and Clang accept that as an extension, but the standard leaves a directive spanning a macro invocation undefined and MSVC refuses it. Write the two message variants out in full instead. DesktopWindow.cxx uses the alternative operator tokens (and, not). They are standard C++, but MSVC only recognises them via , where GCC and Clang know them in the lexer. Including it is a no-op on the compilers that do not need it. --- vncviewer/DesktopWindow.cxx | 7 +++++++ vncviewer/vncviewer.cxx | 21 +++++++++++++++++---- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/vncviewer/DesktopWindow.cxx b/vncviewer/DesktopWindow.cxx index afbd230a30..55b0f6fec3 100644 --- a/vncviewer/DesktopWindow.cxx +++ b/vncviewer/DesktopWindow.cxx @@ -23,6 +23,13 @@ #include +/* + * MSVC only recognizes the and/or/not alternative operator tokens via this + * standard header; GCC/Clang support them natively and treat this as a + * no-op, so it's safe to include unconditionally. + */ +#include + #include #include #include diff --git a/vncviewer/vncviewer.cxx b/vncviewer/vncviewer.cxx index ae68062b8d..410c0f9e62 100644 --- a/vncviewer/vncviewer.cxx +++ b/vncviewer/vncviewer.cxx @@ -380,20 +380,33 @@ static void usage(const char *programName) } #endif + /* + * MSVC rejects preprocessor directives inside a macro argument list + * (the _() call below); GCC/Clang tolerate it as an extension, but it's + * undefined behavior per the standard. Duplicate the two variants + * in full instead of straddling the macro call with #ifndef/#endif. + */ +#ifndef WIN32 fprintf(stderr, _( "\n" "Usage: %s [parameters] [host][:displayNum]\n" " %s [parameters] [host][::port]\n" -#ifndef WIN32 " %s [parameters] [unix socket]\n" -#endif " %s [parameters] -listen [port]\n" " %s [parameters] [.tigervnc file]\n"), programName, programName, -#ifndef WIN32 programName, -#endif programName, programName); +#else + fprintf(stderr, _( + "\n" + "Usage: %s [parameters] [host][:displayNum]\n" + " %s [parameters] [host][::port]\n" + " %s [parameters] -listen [port]\n" + " %s [parameters] [.tigervnc file]\n"), + programName, programName, + programName, programName); +#endif #if !defined(WIN32) && !defined(__APPLE__) fprintf(stderr, _("\n" From 7ba20292da93c95bbe774c83e5fd1dfff39d53e3 Mon Sep 17 00:00:00 2001 From: jose-pr Date: Sat, 1 Aug 2026 20:51:38 -0400 Subject: [PATCH 05/10] Fix the gettext wrappers when NLS is disabled core/i18n.h redirects the rest of the tree into this file's wrappers with "#define dgettext dgettext_rfb" and friends. dgettext_rfb() then tries to reach the real function underneath with its own #undef -- but #undef only removes the current definition, it cannot restore whatever the name meant before. With ENABLE_NLS on, that happens to work: libintl's dgettext is a genuine extern function, and #undef does not touch functions. With ENABLE_NLS off, gettext.h's fallback is itself a macro, so the #undef destroys it and dgettext_rfb() ends up calling an identifier that is not declared anywhere. Implement the disabled-NLS pass-through semantics directly rather than routing through a macro that is already gone. This is not compiler specific; it only shows up because almost nobody configures with NLS genuinely off. --- common/core/i18n.cxx | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/common/core/i18n.cxx b/common/core/i18n.cxx index b4a29f2511..4a17750894 100644 --- a/common/core/i18n.cxx +++ b/common/core/i18n.cxx @@ -131,17 +131,38 @@ static void initTranslations() bind_textdomain_codeset("libc", "UTF-8"); } +/* + * When ENABLE_NLS is off, gettext.h's own dgettext/dcgettext/dngettext/ + * dcngettext fallbacks are plain macros (not real functions), and those + * macros were already permanently clobbered by core/i18n.h's earlier + * "#define dgettext dgettext_rfb"-style aliasing (used to redirect the + * rest of the codebase's calls here) -- a #undef can't restore a macro's + * prior definition, only remove the current one. So with NLS disabled + * there is nothing left upstream to call into; implement the same + * pass-through semantics gettext.h itself would have, directly. + */ + const char *dgettext_rfb(const char *domainname, const char *msgid) { initTranslations(); +#if defined ENABLE_NLS && ENABLE_NLS return dgettext(domainname, msgid); +#else + (void)domainname; + return msgid; +#endif } const char *dcgettext_rfb(const char *domainname, const char *msgid, int category) { initTranslations(); +#if defined ENABLE_NLS && ENABLE_NLS return dcgettext(domainname, msgid, category); +#else + (void)domainname; (void)category; + return msgid; +#endif } const char *dngettext_rfb(const char *domainname, const char *msgid, @@ -149,7 +170,12 @@ const char *dngettext_rfb(const char *domainname, const char *msgid, unsigned long int n) { initTranslations(); +#if defined ENABLE_NLS && ENABLE_NLS return dngettext(domainname, msgid, msgid_plural, n); +#else + (void)domainname; + return (n == 1) ? msgid : msgid_plural; +#endif } const char *dcngettext_rfb(const char *domainname, const char *msgid, @@ -157,7 +183,12 @@ const char *dcngettext_rfb(const char *domainname, const char *msgid, unsigned long int n, int category) { initTranslations(); +#if defined ENABLE_NLS && ENABLE_NLS return dcngettext(domainname, msgid, msgid_plural, n, category); +#else + (void)domainname; (void)category; + return (n == 1) ? msgid : msgid_plural; +#endif } const char *pgettext_rfb(const char *domain, From 6b32c75b3f1763859b124a128bc23530a2e7055c Mon Sep 17 00:00:00 2001 From: jose-pr Date: Sat, 1 Aug 2026 20:51:40 -0400 Subject: [PATCH 06/10] Stop probing for CLSID_VideoProcessorMFT with a link test Recent Windows SDKs only declare CLSID_VideoProcessorMFT in when building against WINVER < _WIN32_WINNT_WINTHRESHOLD; above that the declaration is gone, even though the GUID data still ships in wmcodecdspuuid.lib for compatibility. check_variable_exists() tests whether the symbol links, not whether the header declares it, so it reports the symbol found and suppresses this file's own fallback declaration -- leaving the identifier undeclared at compile time. The probe cannot answer the question being asked, so remove it and declare a distinctly named constant unconditionally. The rename is what makes that safe: an unconditional CLSID_VideoProcessorMFT of our own would collide wherever the SDK does still declare it. --- CMakeLists.txt | 4 ---- common/rfb/H264WinDecoderContext.cxx | 18 +++++++++++++----- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fba2dff66e..866a29b3ce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -185,10 +185,6 @@ trioption(ENABLE_H264 "Enable H.264 RFB encoding") if(ENABLE_H264) if(WIN32) set(HAVE_H264 1) - - set(CMAKE_REQUIRED_LIBRARIES ole32 mfplat mfuuid wmcodecdspuuid) - check_variable_exists(CLSID_VideoProcessorMFT HAVE_VIDEO_PROCESSOR_MFT) - set(CMAKE_REQUIRED_LIBRARIES) else() if(ENABLE_H264 STREQUAL "AUTO") find_package(AVCodec) diff --git a/common/rfb/H264WinDecoderContext.cxx b/common/rfb/H264WinDecoderContext.cxx index 1bc7968d5c..99a80f54c0 100644 --- a/common/rfb/H264WinDecoderContext.cxx +++ b/common/rfb/H264WinDecoderContext.cxx @@ -36,10 +36,18 @@ using namespace rfb; -// Older MinGW lacks this definition -#ifndef HAVE_VIDEO_PROCESSOR_MFT -static GUID CLSID_VideoProcessorMFT = { 0x88753b26, 0x5b24, 0x49bd, { 0xb2, 0xe7, 0xc, 0x44, 0x5c, 0x78, 0xc9, 0x82 } }; -#endif +/* + * CLSID_VideoProcessorMFT's GUID data ships in wmcodecdspuuid.lib + * regardless of target, but recent Windows SDKs (mfidl.h) only *declare* + * it when building against WINVER < _WIN32_WINNT_WINTHRESHOLD (i.e. pre- + * Windows-10); above that the declaration is removed from the header even + * though the linkable symbol still exists. Older MinGW headers never + * declared it either. Use our own name unconditionally instead of trying + * to detect header visibility (CMake's check_variable_exists only tests + * linkability, not header visibility, so it can't tell the two cases + * apart -- see CMakeLists.txt history). + */ +static const GUID kCLSID_VideoProcessorMFT = { 0x88753b26, 0x5b24, 0x49bd, { 0xb2, 0xe7, 0xc, 0x44, 0x5c, 0x78, 0xc9, 0x82 } }; H264WinDecoderContext::H264WinDecoderContext(const core::Rect &r) : H264DecoderContext(r) @@ -50,7 +58,7 @@ H264WinDecoderContext::H264WinDecoderContext(const core::Rect &r) if (FAILED(CoCreateInstance(CLSID_CMSH264DecoderMFT, nullptr, CLSCTX_INPROC_SERVER, IID_IMFTransform, (LPVOID*)&decoder))) throw std::runtime_error(_("Could not find video codec")); - if (FAILED(CoCreateInstance(CLSID_VideoProcessorMFT, nullptr, CLSCTX_INPROC_SERVER, IID_IMFTransform, (LPVOID*)&converter))) + if (FAILED(CoCreateInstance(kCLSID_VideoProcessorMFT, nullptr, CLSCTX_INPROC_SERVER, IID_IMFTransform, (LPVOID*)&converter))) { if (FAILED(CoCreateInstance(CLSID_CColorConvertDMO, nullptr, CLSCTX_INPROC_SERVER, IID_IMFTransform, (LPVOID*)&converter))) { From b3aa5c41d5352ae732a29916f6100f58508ab51d Mon Sep 17 00:00:00 2001 From: jose-pr Date: Sat, 1 Aug 2026 20:51:41 -0400 Subject: [PATCH 07/10] Add a BUILD_SERVER option add_subdirectory(win) and add_subdirectory(unix) run unconditionally, so there is no way to build only the viewer. That matters on Windows, where rfb_win32 has portability problems of its own that a viewer-only build has no reason to care about. Defaults to ON, alongside the existing BUILD_VIEWER and BUILD_JAVA, so no existing build changes. --- CMakeLists.txt | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 866a29b3ce..9745184767 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -435,12 +435,15 @@ include(cmake/StaticBuild.cmake) add_subdirectory(common) -if(WIN32) - add_subdirectory(win) -else() - # No interest in building x related parts on Apple - if(NOT APPLE) - add_subdirectory(unix) +option(BUILD_SERVER "Build the platform-specific VNC server components" ON) +if(BUILD_SERVER) + if(WIN32) + add_subdirectory(win) + else() + # No interest in building x related parts on Apple + if(NOT APPLE) + add_subdirectory(unix) + endif() endif() endif() From 4c75753a77176a05156d1bf6f1b90f48f131d4c4 Mon Sep 17 00:00:00 2001 From: jose-pr Date: Sat, 1 Aug 2026 20:51:42 -0400 Subject: [PATCH 08/10] Allow opting in to a Visual Studio build With the portability work in the preceding commits in place, relax the outright refusal to configure under MSVC into an opt-in, -DALLOW_MSVC_EXPERIMENTAL=ON, and fix the last few things that are wrong once cl.exe is actually allowed to run. The GNU-only flag spellings (-Og, -std=gnu99/gnu++11, -Wall -Wextra -Wformat=2 -Wvla and friends, _FORTIFY_SOURCE) were applied unconditionally; cl.exe rejects them outright ("D8021: invalid numeric argument '/Wextra'") or collides with its own defaults ("D8016: '/RTC1' and '/Og' command-line options are incompatible"). They are now behind if(NOT MSVC), which also covers the -Wno-error handed to the FLTK version probe. Most of that hunk is the re-indent this forces; git diff -w shows the real change is small. The NOMINMAX definition named the rfb target roughly 340 lines before add_subdirectory(common) creates it, so target_compile_definitions() there could only ever have failed. It has never run, because the hard MSVC gate above aborted first. Make it a global add_compile_definitions() instead. WIN32_LEAN_AND_MEAN is new. MinGW's own defines it by default, so on that toolchain nothing pulled in the legacy ; under MSVC it does, and it then conflicts with in any translation unit that sees both, regardless of include order. This is deliberately last: every commit before it leaves MSVC refused, so nothing bisects into a half-ported state, and none of them change what any currently supported toolchain builds. It is also not a claim that Visual Studio is supported -- it is the smallest change that lets someone try. --- CMakeLists.txt | 73 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 47 insertions(+), 26 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9745184767..ae4b82f87b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,8 +35,8 @@ endif() option(INSTALL_SYSTEMD_UNITS "Install TigerVNC systemd units" ON) -if(MSVC) - message(FATAL_ERROR "TigerVNC cannot be built with Visual Studio. Please use MinGW") +if(MSVC AND NOT ALLOW_MSVC_EXPERIMENTAL) + message(FATAL_ERROR "TigerVNC cannot be built with Visual Studio. Please use MinGW (or pass -DALLOW_MSVC_EXPERIMENTAL=ON to try anyway)") endif() if(NOT BUILD_TIMESTAMP) @@ -67,27 +67,36 @@ set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -D_DEBUG") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -D_DEBUG") # Enable debug friendly optimizations for debug builds -set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Og") -set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Og") - -# Make sure we get a sane C and C++ version -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11") - -# Tell the compiler to be stringent -add_compile_definitions(_FORTIFY_SOURCE=2) -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wformat=2 -Wvla") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wformat=2 -Wvla") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wzero-as-null-pointer-constant") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsuggest-override") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wshadow") -# Make sure we catch these issues whilst developing -set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Werror") -set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Werror") -# clang doesn't support format_arg, which breaks this warning -if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-format-nonliteral -Wno-format-security") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-format-nonliteral -Wno-format-security") +# (GCC/Clang-only spelling; MSVC's own /RTC1 default conflicts with it — see +# "D8016: '/RTC1' and '/Og' command-line options are incompatible") +if(NOT MSVC) + set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Og") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Og") +endif() + +# These are GCC/Clang-only flag spellings; MSVC's cl.exe rejects them outright +# (e.g. "D8021: invalid numeric argument '/Wextra'"), so keep the whole GNU-style +# stringency stanza out of MSVC builds and rely on CMake's own MSVC defaults. +if(NOT MSVC) + # Make sure we get a sane C and C++ version + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11") + + # Tell the compiler to be stringent + add_compile_definitions(_FORTIFY_SOURCE=2) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wformat=2 -Wvla") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wformat=2 -Wvla") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wzero-as-null-pointer-constant") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsuggest-override") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wshadow") + # Make sure we catch these issues whilst developing + set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Werror") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Werror") + # clang doesn't support format_arg, which breaks this warning + if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-format-nonliteral -Wno-format-security") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-format-nonliteral -Wno-format-security") + endif() endif() option(ENABLE_ASAN "Enable address sanitizer support" OFF) @@ -103,8 +112,12 @@ if(ENABLE_TSAN AND NOT WIN32 AND NOT APPLE AND CMAKE_SIZEOF_VOID_P MATCHES 8) endif() if(MSVC) - # undef min and max macro - target_compile_definitions(rfb PRIVATE NOMINMAX) + # undef min and max macro -- global, not per-target: the "rfb" target + # doesn't exist yet at this point in the file (add_subdirectory(common) + # runs later), so target_compile_definitions(rfb ...) here always failed; + # this MSVC branch was untestable before the hard MSVC gate above was + # relaxed, so the ordering bug was never caught. + add_compile_definitions(NOMINMAX) endif() if(NOT DEFINED BUILD_WINVNC) @@ -121,6 +134,12 @@ endif() # Minimum version is Windows 7 if(WIN32) add_definitions(-D_WIN32_WINNT=0x0601) + # Without this, pulls in the legacy , which then + # conflicts with wherever both end up included in the same + # translation unit regardless of include order (duplicate struct/function + # definitions with "different linkage"). MinGW builds never hit this since + # mingw's own already defines this by default. + add_definitions(-DWIN32_LEAN_AND_MEAN) endif() # Legacy macros (macOS 10.12 and older) conflict with our code @@ -285,7 +304,9 @@ if(BUILD_VIEWER) endif() if(FLTK_FOUND) - set(CMAKE_REQUIRED_FLAGS "-Wno-error") + if(NOT MSVC) + set(CMAKE_REQUIRED_FLAGS "-Wno-error") + endif() set(CMAKE_REQUIRED_INCLUDES ${FLTK_INCLUDE_DIR}) set(CMAKE_REQUIRED_LIBRARIES ${FLTK_LIBRARIES}) From 47911edafa23dfc4d40a3f47578b108661bd4ef9 Mon Sep 17 00:00:00 2001 From: jose-pr Date: Sun, 2 Aug 2026 09:28:30 -0400 Subject: [PATCH 09/10] Build the Windows server and tests with MSVC too The MSVC opt-in only covered the client libraries and the viewer; turning BUILD_SERVER on still failed, so the option read as a workaround for an unported tree rather than a genuine choice. core/os.h gains ffs(), ssize_t and usleep(), joining the shims already there. mingw supplies all three and MSVC none of them. wm_hooks put its shared variables in a named section with a gcc attribute. MSVC has no per-variable equivalent, so the two groups are bracketed with data_seg pragmas instead. The linker directive is what makes the section genuinely shared -- without it the section still exists but each process gets its own copy, which would have been a silent behavioural difference rather than a build failure. VNCServerService.cxx wrote the calling convention before the pointer rather than on it: 'typedef void WINAPI (*SendSAS_proto)(BOOL)'. gcc accepts the transposed form; MSVC does not. vncconfig needs explicitly, because WIN32_LEAN_AND_MEAN stops pulling it in and mingw's headers had been supplying it transitively. SSecurityRSAAES.cxx uses inline variables, so MSVC needs /std:c++17. The GNU standard flags sit behind if(NOT MSVC), which left MSVC with no standard flag at all and its own older default. GestureHandler.cxx needs _USE_MATH_DEFINES for M_PI. Verified on Windows ARM64: a full build with BUILD_SERVER=ON produces winvnc4.exe, vncconfig.exe and wm_hooks.dll, and every unit test compiles. --- CMakeLists.txt | 6 ++++- common/core/os.h | 37 +++++++++++++++++++++++++++++ common/rfb/SSecurityRSAAES.cxx | 1 + tests/unit/emulatemb.cxx | 4 ++++ tests/unit/gesturehandler.cxx | 4 ++++ vncviewer/GestureHandler.cxx | 8 ++++++- win/rfb_win32/DeviceFrameBuffer.cxx | 7 +++--- win/vncconfig/Authentication.h | 4 ++++ win/winvnc/VNCServerService.cxx | 4 +++- win/wm_hooks/wm_hooks.cxx | 19 +++++++++++++++ 10 files changed, 88 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ae4b82f87b..5022dd05c0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,6 +72,11 @@ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -D_DEBUG") if(NOT MSVC) set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Og") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Og") +else() + # cl.exe rejects every GNU spelling above, so MSVC would otherwise get + # no standard flag at all and fall back to its default -- which is too + # old for the inline variables in SSecurityRSAAES.cxx. + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /std:c++17") endif() # These are GCC/Clang-only flag spellings; MSVC's cl.exe rejects them outright @@ -81,7 +86,6 @@ if(NOT MSVC) # Make sure we get a sane C and C++ version set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11") - # Tell the compiler to be stringent add_compile_definitions(_FORTIFY_SOURCE=2) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wformat=2 -Wvla") diff --git a/common/core/os.h b/common/core/os.h index a49f09cb8b..c3a52eb515 100644 --- a/common/core/os.h +++ b/common/core/os.h @@ -40,6 +40,12 @@ typedef int mode_t; +/* POSIX ssize_t: a signed size. MSVC only spells it SSIZE_T, from + * , which above already pulls in. */ +#ifdef _MSC_VER +typedef SSIZE_T ssize_t; +#endif + inline int gettimeofday(struct timeval *tv, void *) { /* FILETIME is in 100ns intervals since 1601-01-01; convert to @@ -58,6 +64,37 @@ inline int gettimeofday(struct timeval *tv, void *) return 0; } +/* POSIX ffs(): one plus the index of the least significant set bit, or + * zero if there is none. mingw supplies it from ; MSVC has no + * equivalent, so map it onto the intrinsic. + * + * Guarded on _MSC_VER rather than _WIN32 on purpose: declaring it under + * mingw would collide with the real one. */ +#ifdef _MSC_VER +#include + +inline int ffs(int i) +{ + unsigned long bit; + + if (!_BitScanForward(&bit, (unsigned long)i)) + return 0; + + return (int)bit + 1; +} +#endif + +/* POSIX usleep(). Windows only offers millisecond granularity, so a + * sub-millisecond request rounds up to 1ms rather than to 0 -- sleeping + * for less than asked is the one behaviour a caller cannot correct for. */ +#ifdef _MSC_VER +inline int usleep(unsigned long usec) +{ + Sleep((DWORD)((usec + 999) / 1000)); + return 0; +} +#endif + /* POSIX dirname(), minimally: strip the trailing filename component, * accepting both path separators since Windows paths use either. Modifies * and returns its argument, matching the (permitted) in-place POSIX form. */ diff --git a/common/rfb/SSecurityRSAAES.cxx b/common/rfb/SSecurityRSAAES.cxx index 398eff4de1..d08d670aa0 100644 --- a/common/rfb/SSecurityRSAAES.cxx +++ b/common/rfb/SSecurityRSAAES.cxx @@ -38,6 +38,7 @@ #include #include +#include #include #include #include diff --git a/tests/unit/emulatemb.cxx b/tests/unit/emulatemb.cxx index d59ce509d3..3f0c80e8e3 100644 --- a/tests/unit/emulatemb.cxx +++ b/tests/unit/emulatemb.cxx @@ -21,7 +21,11 @@ #include #endif +#ifdef _WIN32 +#include +#else #include +#endif #include diff --git a/tests/unit/gesturehandler.cxx b/tests/unit/gesturehandler.cxx index cf2e5b6bd4..1d0c284c01 100644 --- a/tests/unit/gesturehandler.cxx +++ b/tests/unit/gesturehandler.cxx @@ -20,7 +20,11 @@ #include #endif +#ifdef _WIN32 +#include +#else #include +#endif #include diff --git a/vncviewer/GestureHandler.cxx b/vncviewer/GestureHandler.cxx index 2ede991b22..4b435c62b3 100644 --- a/vncviewer/GestureHandler.cxx +++ b/vncviewer/GestureHandler.cxx @@ -18,7 +18,13 @@ */ #ifdef HAVE_CONFIG_H - #include + /* MSVC only defines M_PI and friends when asked, and this must come + * before any math header. gettimeofday() comes from core/os.h. */ +#ifdef _WIN32 +#define _USE_MATH_DEFINES +#include +#endif +#include #endif #include diff --git a/win/rfb_win32/DeviceFrameBuffer.cxx b/win/rfb_win32/DeviceFrameBuffer.cxx index d20b6dc764..7bc08d77cd 100644 --- a/win/rfb_win32/DeviceFrameBuffer.cxx +++ b/win/rfb_win32/DeviceFrameBuffer.cxx @@ -29,6 +29,7 @@ #include #include +#include #include #include @@ -185,9 +186,9 @@ void DeviceFrameBuffer::setCursor(HCURSOR hCursor, VNCServer* server) // We may not get the RGBA order we want, so shuffle things around int ridx, gidx, bidx, aidx; - ridx = __builtin_ffs(bi.bV5RedMask) / 8; - gidx = __builtin_ffs(bi.bV5GreenMask) / 8; - bidx = __builtin_ffs(bi.bV5BlueMask) / 8; + ridx = ffs(bi.bV5RedMask) / 8; + gidx = ffs(bi.bV5GreenMask) / 8; + bidx = ffs(bi.bV5BlueMask) / 8; // Usually not set properly aidx = 6 - ridx - gidx - bidx; diff --git a/win/vncconfig/Authentication.h b/win/vncconfig/Authentication.h index 3ee7b049bc..79263e2956 100644 --- a/win/vncconfig/Authentication.h +++ b/win/vncconfig/Authentication.h @@ -21,6 +21,10 @@ // FIXME: The implementation should be moved to Authentication.cxx so we // don't have all of these #includes polluting everything +/* WIN32_LEAN_AND_MEAN keeps from pulling this in, and the + * common-dialog types below need it. mingw's headers included it + * transitively, which is why this was never needed before. */ +#include #include #include diff --git a/win/winvnc/VNCServerService.cxx b/win/winvnc/VNCServerService.cxx index 4da0a5dc08..9a8ac50745 100644 --- a/win/winvnc/VNCServerService.cxx +++ b/win/winvnc/VNCServerService.cxx @@ -43,7 +43,9 @@ const char* winvnc::VNCServerService::Name = "TigerVNC"; // SendSAS is not available until Windows 7, and missing from MinGW static HMODULE sasLibrary = nullptr; -typedef void WINAPI (*SendSAS_proto)(BOOL AsUser); +/* The calling convention belongs on the pointer, not before it. gcc + * accepts the transposed form; MSVC rejects it outright. */ +typedef void (WINAPI *SendSAS_proto)(BOOL AsUser); static SendSAS_proto _SendSAS = nullptr; VNCServerService::VNCServerService() diff --git a/win/wm_hooks/wm_hooks.cxx b/win/wm_hooks/wm_hooks.cxx index 2f04b85173..4e95d69bcf 100644 --- a/win/wm_hooks/wm_hooks.cxx +++ b/win/wm_hooks/wm_hooks.cxx @@ -26,7 +26,22 @@ #include +/* These live in a shared data section so every process the hook DLL is + * injected into sees one copy. gcc marks each variable with an attribute; + * MSVC has no per-variable equivalent, so the groups below are bracketed + * by data_seg pragmas instead and SHARED itself expands to nothing. The + * linker directive is what actually makes the segment shared -- without + * it the section exists but every process gets its own copy. */ +#ifdef _MSC_VER +#pragma comment(linker, "/SECTION:shared,RWS") +#define SHARED +#define SHARED_BEGIN __pragma(data_seg("shared")) +#define SHARED_END __pragma(data_seg()) +#else #define SHARED __attribute__((section ("shared"), shared)) +#define SHARED_BEGIN +#define SHARED_END +#endif UINT WM_HK_PingThread = RegisterWindowMessage("RFB.WM_Hooks.PingThread"); @@ -89,6 +104,7 @@ BOOL WINAPI DllMain(HANDLE instance, ULONG reason, LPVOID /*reserved*/) { // -=- Display update hooks // +SHARED_BEGIN DWORD hook_owner SHARED = 0; DWORD hook_target SHARED = 0; HHOOK hook_CallWndProc SHARED = nullptr; @@ -100,6 +116,7 @@ HCURSOR cursor SHARED = nullptr; #ifdef _DEBUG UINT diagnostic_min SHARED =1; UINT diagnostic_max SHARED =0; +SHARED_END #endif #ifdef _DEBUG @@ -375,12 +392,14 @@ BOOL WM_Hooks_Remove(DWORD owner) { // -=- User input hooks // +SHARED_BEGIN HHOOK hook_keyboard SHARED = nullptr; HHOOK hook_pointer SHARED = nullptr; bool enable_real_ptr SHARED = true; bool enable_synth_ptr SHARED = true; bool enable_real_kbd SHARED = true; bool enable_synth_kbd SHARED = true; +SHARED_END #ifdef WH_KEYBOARD_LL LRESULT CALLBACK HookKeyboardHook(int nCode, WPARAM wParam, LPARAM lParam) { From 76191447ef6b96dd7d515ab1999766b6e5a36160 Mon Sep 17 00:00:00 2001 From: jose-pr Date: Thu, 6 Aug 2026 10:06:13 -0400 Subject: [PATCH 10/10] Keep the MSVC shims away from mingw os.h guarded its POSIX replacements on _WIN32, so mingw picked them up too and collided with the declarations it already provides: mode_t is unsigned short in pthread_compat.h, and strcasecmp, gettimeofday and dirname all exist there as well. That broke the existing mingw build at common/core/Logger_file.cxx. mingw does supply all of these for real, just from the POSIX headers rather than the ones a Windows source file would otherwise include, so include those on that side and keep the shims for MSVC only. Both halves are needed: dropping the shims without adding the includes leaves gettimeofday undeclared in common/core/Timer.cxx. PATH_MAX stays under _WIN32 as it is #ifndef-guarded and wanted by both toolchains. --- common/core/os.h | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/common/core/os.h b/common/core/os.h index c3a52eb515..6177acffe9 100644 --- a/common/core/os.h +++ b/common/core/os.h @@ -35,6 +35,22 @@ #define PATH_MAX _MAX_PATH #endif +/* mingw is a GCC/glibc-compatible environment that provides all of the + * below for real, just from the POSIX headers rather than the ones a + * Windows source file would otherwise include. Pull those in here so + * callers get the genuine declarations, and shim only for MSVC -- both + * halves matter, as redefining them under mingw collides with the real + * ones. */ +#ifndef _MSC_VER + +#include // strcasecmp, strncasecmp +#include // gettimeofday +#include // mode_t +#include // dirname +#include // usleep + +#else + #define strcasecmp _stricmp #define strncasecmp _strnicmp @@ -42,9 +58,7 @@ typedef int mode_t; /* POSIX ssize_t: a signed size. MSVC only spells it SSIZE_T, from * , which above already pulls in. */ -#ifdef _MSC_VER typedef SSIZE_T ssize_t; -#endif inline int gettimeofday(struct timeval *tv, void *) { @@ -66,11 +80,7 @@ inline int gettimeofday(struct timeval *tv, void *) /* POSIX ffs(): one plus the index of the least significant set bit, or * zero if there is none. mingw supplies it from ; MSVC has no - * equivalent, so map it onto the intrinsic. - * - * Guarded on _MSC_VER rather than _WIN32 on purpose: declaring it under - * mingw would collide with the real one. */ -#ifdef _MSC_VER + * equivalent, so map it onto the intrinsic. */ #include inline int ffs(int i) @@ -82,18 +92,15 @@ inline int ffs(int i) return (int)bit + 1; } -#endif /* POSIX usleep(). Windows only offers millisecond granularity, so a * sub-millisecond request rounds up to 1ms rather than to 0 -- sleeping * for less than asked is the one behaviour a caller cannot correct for. */ -#ifdef _MSC_VER inline int usleep(unsigned long usec) { Sleep((DWORD)((usec + 999) / 1000)); return 0; } -#endif /* POSIX dirname(), minimally: strip the trailing filename component, * accepting both path separators since Windows paths use either. Modifies @@ -120,6 +127,8 @@ inline char *dirname(char *path) return path; } +#endif // _MSC_VER + #endif // _WIN32 #endif // __CORE_OS_H__