OS differences
Siril runs on Linux, Windows, macOS and the BSDs, and most of the code is written against glib so that it stays portable. There is, however, a residue of genuinely per-OS behaviour: memory accounting, path handling at API boundaries, process spawning, crash handling and a handful of macOS-specific GTK4 workarounds. This chapter collects those differences and organises them by concern rather than by operating system, so that when you touch one of these areas you can see all of the platforms at once.
Most of the per-OS code lives in src/core/OS_utils.c, with allocation in
src/core/siril_alloc.c, process spawning in
src/core/siril_spawn.c, signal handling in src/core/signals.c
and application-directory resolution in src/core/siril_app_dirs.c. The
canonical compile-time switches are _WIN32, OS_OSX, __linux__ and
BSD (the last of these is derived from <sys/param.h>).
Memory reporting
Siril budgets its work against the memory the OS will actually give it, so the
"available memory" query is deliberately conservative and platform-specific. The
full budgeting logic is described in Memory management;
here we only summarise the per-OS sources that feed it. Both
get_available_memory() and get_used_RAM_memory() live in
src/core/OS_utils.c.
Linux first tries cgroups.
get_available_mem_cgroups()locates the process's controller directory viafind_cgroups_path()(parsing/proc/self/cgroup) and reads the memory limit from either the v1 files (memory.limit_in_bytes,memory.soft_limit_in_bytes) or the v2 files (memory.max,memory.high,memory.low). If no cgroup limit is found it falls back to theMemAvailable:line of/proc/meminfo(with aMemFree:fallback for old kernels and WSL2). Used memory comes from/proc/self/statm.macOS uses Mach:
host_statistics64()withHOST_VM_INFO64gives the free, purgeable and external page counts, and Siril also derives a second estimate fromsysctl(HW_MEMSIZE)minus the in-use pages, returning the smaller of the two. Note that purgeable pages are counted as available. Used memory is the task'sphys_footprint(orresident_sizeon older SDKs) viatask_info().Windows has no overcommit, so the binding constraint is the commit charge, not free physical RAM.
get_available_memory()callsGlobalMemoryStatusEx()and takes the smaller ofullAvailPhysandullAvailPageFile— otherwise a budget derived from free RAM alone would promise memory the OS refuses to deliver once the pagefile is small or disabled. Used memory is theWorkingSetSizefromGetProcessMemoryInfo().BSD reads
avail memoryfrom/var/run/dmesg.bootonce and caches it. This is the boot-time figure, not the current runtime free memory (there is aFIXMEin the source acknowledging this), so BSD budgeting is effectively static.
Allocation
src/core/siril_alloc.c provides siril_malloc(), siril_calloc()
and siril_free(). On most platforms these are thin wrappers over malloc,
calloc and free. On Windows they are instead backed by
VirtualAlloc (with MEM_COMMIT | MEM_RESERVE and PAGE_READWRITE) and
released with VirtualFree and MEM_RELEASE, which handles very large
allocations more gracefully than the CRT heap. Because VirtualAlloc already
zeroes its pages, siril_calloc() does not memset on Windows. There is no
siril_realloc(): a block obtained from siril_malloc() must be released
with siril_free() and cannot be handed to the CRT free() on Windows.
Filesystems and paths
The portability rules for paths are simple and non-negotiable:
Always use the glib path/filename helpers (
g_build_filename(),g_path_get_dirname(),g_get_user_config_dir()and friends). Do not hand-assemble paths with separators.Paths inside Siril are always UTF-8. Convert to UTF-16 only at the Windows API boundary, using
g_utf8_to_utf16()on the way in andg_utf16_to_utf8()on the way out. You can see this pattern infind_space(),siril_real_path()andget_special_folder()insrc/core/OS_utils.c, all of which take UTF-8 in, widen it for the…WAPI and narrow the result back.Open files with
g_fopen()rather than the barefopen(). On Windowsg_fopen()performs the UTF-8 → UTF-16 conversion for you, so filenames with non-ASCII characters work regardless of the console code page.Symlink and junction resolution on Windows is handled by
siril_real_path()insrc/core/OS_utils.c. It checks forFILE_ATTRIBUTE_REPARSE_POINTand, if present, resolves the target withGetFinalPathNameByHandleW(), stripping the\\?\prefix from the result. On macOSfind_space()resolves symbolic links itself throughNSFileManagerbefore querying free space.Large files. Catalogue reads need 64-bit file offsets. Rather than rely on a portable
fseeko,src/io/local_catalogues.cdefines anfseek64macro:_fseeki64on Windows andfseekoelsewhere. Usefseek64when seeking within potentially large files.
Free-space and path-length queries are likewise platform-split in
src/core/OS_utils.c: find_space() has variants for macOS
(NSURL resource values), statvfs, statfs and
GetDiskFreeSpaceExW on Windows; and get_pathmax() returns MAX_PATH
on Windows but queries pathconf(_PC_PATH_MAX) elsewhere.
Processes
Added in version 1.5.
Never call g_spawn_sync() or g_spawn_async_with_pipes() directly to run
an external program. Use the host wrappers declared in
src/core/siril_spawn.h:
siril_spawn_host_sync()siril_spawn_host_async_with_pipes()
These exist so that Siril behaves identically whether or not it is running
inside a Flatpak sandbox. When built with a Flatpak id (the FLATPAK_ID
macro, set from the flatpak-id meson option), the wrappers prepend
/bin/flatpak-spawn --host -- to the argument vector — see
siril_flatpak_argv() in src/core/siril_spawn.c — so that the child
runs on the host rather than inside the sandbox. On non-Flatpak builds the
wrappers are effectively straight passthroughs to the glib functions.
To keep the sandboxed and non-sandboxed paths consistent, both wrappers
assert that the caller has set G_SPAWN_SEARCH_PATH (via
g_return_val_if_fail).
If you do not want a PATH search, pass an absolute executable path but still
set the flag; the Flatpak branch clears G_SPAWN_SEARCH_PATH internally once
it has rewritten the argument vector.
There is also a small compatibility macro, siril_spawn_check_wait_status,
which maps to g_spawn_check_wait_status() on glib ≥ 2.70 and to the
deprecated g_spawn_check_exit_status() on older glib.
Console and standard I/O on Windows
On Windows a GUI application is not attached to a console, so stdout and
stderr go nowhere by default. ReconnectIO() in
src/core/OS_utils.c fixes this: it tries AttachConsole(ATTACH_PARENT_PROCESS)
to reuse the parent's console (for example when Siril was launched from a
terminal), and only if that fails and a new console was requested does it call
AllocConsole(). It then reopens stdout, stdin and stderr onto the
console handles with unbuffered I/O. This is called from the Windows startup
paths in src/main.c and src/main-cli.c. Reading from standard
input is abstracted by siril_input_stream_from_stdin(), which builds a
g_win32_input_stream on Windows and a g_unix_input_stream elsewhere.
Signals and crash handling
Signal setup lives in signals_init() in src/core/signals.c. The
common set installed everywhere is SIGABRT, SIGFPE, SIGSEGV,
SIGTERM and SIGILL. On non-Windows platforms Siril additionally handles
SIGHUP, SIGQUIT, SIGBUS, SIGINT and SIGTRAP — and SIGINT
is treated as a clean quit request, which is convenient for developers who
Ctrl+C the CLI.
When a fatal signal fires, signal_handled() prints a short backtrace before
exiting. The backtrace source is platform-specific:
Windows uses
CaptureStackBackTrace()together withSymInitialize()/SymFromAddr()fromdbghelp.hto resolve symbol names.Everywhere else (where
execinfo.his available) it usesbacktrace()andbacktrace_symbols_fd().
Application directories and relocation
Directory resolution is in src/core/siril_app_dirs.c, driven by
initialize_siril_directories(). The share/data and locale directories are
found differently per platform:
Windows derives the installation directory at runtime with
g_win32_get_package_installation_directory_of_module()and looks forshare/…beneath it, so the install is fully relocatable.macOS bundles set the
SIRIL_RELOCATED_RES_DIRenvironment variable (guarded byENABLE_RELOCATABLE_RESOURCES); the share and locale directories are built relative to it.AppImage (Linux, also under
ENABLE_RELOCATABLE_RESOURCES) uses theAPPDIRenvironment variable and looks underusr/share/….Otherwise Siril falls back to the compile-time
PACKAGE_DATA_DIRandLOCALEDIR.
The startup directory (search_for_startup_dir()) prefers the user's
Pictures directory, then Documents, then the home directory, all via glib's
g_get_user_special_dir(). Config, scripts, SPCC and user-data directories
are built from glib's XDG-aware g_get_user_config_dir() and
g_get_user_data_dir().
Flatpak detection is a build-time matter: the FLATPAK_ID macro is defined
when the flatpak-id meson option is set, and is what the process-spawning
wrappers key off (see Processes).
Windows also has a bundle-path helper, get_siril_bundle_path(), and an
executable finder, find_executable_in_path(), both in
src/core/OS_utils.c; the latter deliberately strips mingw64 and
msys64 entries out of PATH before searching, so that Siril finds
host-installed tools rather than its own MSYS2 build dependencies.
Special-folder queries
On Windows, get_special_folder() in src/core/OS_utils.c wraps
SHGetSpecialFolderLocation() / SHGetPathFromIDListW() to resolve a CSIDL
to a UTF-8 path. On other platforms the equivalent logical directories come from
glib's g_get_user_special_dir() (used in search_for_startup_dir()).
Dark-mode detection
Added in version 1.5.
siril_system_is_dark_mode() and siril_watch_system_appearance_changes()
in src/core/OS_utils.c provide a uniform interface over three backends:
macOS queries
[NSApp effectiveAppearance]againstNSAppearanceNameDarkAquaand subscribes to theAppleInterfaceThemeChangedNotificationdistributed notification for live updates.Windows reads the
AppsUseLightThemevalue underHKCU\…\Themes\Personalizein the registry and polls it every few seconds (there is no cheap change notification), reporting changes through the registered callback.Linux / Unix reads the
color-schemekey of theorg.gnome.desktop.interfaceGSettings schema (prefer-darkmeaning dark) and connects to itschanged::color-schemesignal. If the schema is not installed on the current desktop, both functions become no-ops.
macOS GTK4 workarounds
GTK4 on macOS has a couple of rough edges that Siril patches from Cocoa code in
src/core/OS_utils.c (called from src/main.c at startup). These
are the OS-specific companions to the general notes in
GTK4 quirks.
Keyboard shortcuts. GTK4's
GdkMacosViewdoes not overrideperformKeyEquivalent:, soCmd+keyevents never reach GTK4's shortcut controller.siril_macos_fix_keyboard_shortcuts()installs a localNSEventmonitor for key-down events, and for events carrying the Command modifier on a GDK window (the content view's class name has a"Gdk"prefix) forwards them viakeyDown:. Because of this, Siril registers its accelerators with<Meta>on macOS so thatGDK_META_MASK(Command) matches. The<Primary>→<Meta>rewrite is done bysiril_remap_accel()insrc/gui-gtk4/utils.c, applied fromset_accel_map()insrc/gui-gtk4/callbacks.c.Popover autohide. An autohide
GtkPopovermaps as a separate focusedNSWindow; once its outside-click grab is lost, clicking elsewhere no longer dismisses it.siril_macos_fix_popover_autohide()installs anNSEventmonitor for mouse-down events and, when the click lands on a GTK window other than the focused popover, runs the same dismissal path the Wayland workaround uses (viagui_iface.dismiss_autohide_popovers, so the Cocoa code needs no GTK headers). Native panels such asNSOpenPanelandNSAlertare left untouched because their content views do not carry the"Gdk"class-name prefix.
Tip
The classic windows.h include-order rule. windows.h pollutes the
global namespace with macros (min, max, near, ERROR and many
more) that collide with glib, GTK and C++ standard-library identifiers. When
a source file needs windows.h, include it first, before the glib and
Siril headers, exactly as src/core/OS_utils.c,
src/core/siril_alloc.c and src/algos/astrometry_solver.c do.
Getting the order wrong produces baffling compile errors deep inside
unrelated headers.
Note
Windows builds are produced under MSYS2 / mingw64. If you are adding platform-specific dependencies or need to understand how the Windows and macOS images are assembled, see the continuous-integration builds chapter, which documents the MSYS2 environment, the dependency freeze and the bundling jobs.