Architecture ------------ This page is the map of the world. Read it first: it tells you where everything lives and how a keypress becomes pixels on screen. Every later chapter zooms into one region of this map. Directory map ============= All the source lives under :file:`src/`. The important subdirectories are: - :file:`src/core` — the heart of Siril. Command parsing and dispatch, the generic processing workers, the persistent worker thread and its job queue, settings/preferences, the global structures (``com``, ``gfit``), the mask system, the memory allocator, the abstract GUI interface (``gui_iface``) and all the OS glue (``OS_utils.c``, ``siril_spawn.c``, ``signals.c``). - :file:`src/algos` — image-processing algorithms (background extraction, star detection, PSF, geometry, statistics, …). - :file:`src/filters` — user-facing filters (asinh, banding, deconvolution, wavelets, …). These are the classic "write a feature" targets. - :file:`src/io` — everything that reads or writes bytes: FITS, SER, fitseq, film formats, the ``.seq`` files, sequence handling, the sequence writer, and the **Python IPC** layer (:file:`siril_pythonmodule.c`, :file:`siril_pythoncommands.c`). - :file:`src/gui-gtk4` — **all** GTK code. Every widget, callback, dialog, the tiled renderer, the CSS, the ROI/preview machinery. Nothing outside this directory (and :file:`src/main.c`) is allowed to include GTK headers (see :ref:`gui:the separation rules`). - :file:`src/registration` — sequence registration (global, planetary/MPP, apply-existing-registration). - :file:`src/stacking` — the stacking engines and their memory hooks. - :file:`src/opencv` — the C++/OpenCV bridge (geometry, resampling). - :file:`src/rt` — RawTherapee-derived code used by some filters. - :file:`src/tests` — unit tests and diagnostic harnesses. - :file:`python_module/sirilpy` — the ``sirilpy`` Python package that scripts import; it talks to the C core over the IPC socket. .. versionchanged:: 1.5 All GTK code moved from :file:`src/gui` (GTK3, Glade-based) to :file:`src/gui-gtk4` (GTK4). If a document or a stale patch references :file:`src/gui`, treat it as pre-1.5. The two binaries ================ Meson builds two executables from a shared core library. The build strategy is documented in :file:`src/meson.build`; in summary: - ``siril_lib`` — a static library of the core (non-GUI) sources. It includes :file:`core/gui_iface_stubs.c`, which provides **no-op** implementations of every ``gui_iface`` slot. - ``siril_headless_lib`` — :file:`core/headless_stubs.c` alone. It provides no-op implementations of the GUI functions that core code still calls *directly* (i.e. those not yet routed through ``gui_iface``). It is linked on demand, so its symbols are only pulled in where they would otherwise be unresolved. - ``siril`` — the GUI binary. Links ``siril_lib`` plus the :file:`src/gui-gtk4` sources compiled directly; those provide the real GTK implementations (including :file:`gui_iface_impl.c`), and :file:`main.c` defines the ``gui`` structure. Only built with ``-Dgtk=true`` (the default). - ``siril-cli`` — the headless binary. Links ``siril_lib`` plus ``siril_headless_lib``; :file:`gui_iface_impl.c` is **never** linked, so the stub vtable from :file:`gui_iface_stubs.c` is what runs. :file:`main-cli.c` does not define ``gui``. The consequence for you as a contributor: **core code must never call GTK directly.** It calls through ``gui_iface`` (see :ref:`gui:gui_iface in depth`), which is a real implementation in the GUI binary and a no-op in ``siril-cli``. The headless build is what CI uses to catch separation violations. The three access paths to a feature ==================================== Any user-visible operation is reachable three ways, and they converge on the same worker: 1. **GUI**: a widget callback in :file:`src/gui-gtk4` gathers parameters and submits a worker. 2. **Command**: :file:`command.c` parses the command line and submits the same worker. 3. **Python script**: a ``sirilpy`` call crosses the IPC socket, is handled in :file:`siril_pythoncommands.c`, and issues the same command / worker. Because all three funnel into one place, you normally implement the logic once (a worker hook plus a parameter struct) and wire the three front-ends to it. This is exactly the arc that :ref:`writingfeature:writing a feature` walks you through. Threads ======= At runtime Siril has several kinds of thread; knowing which code runs on which is essential (details in :ref:`threading:threading`): - **GTK main thread** — the only thread allowed to touch GTK. Runs callbacks, idle functions, and the renderer. - **The persistent processing worker thread** — one thread for the whole application lifetime, fed by a FIFO job queue. Every heavy operation (a filter, a stack, a registration) runs here, one at a time. See :file:`src/core/processing_thread.c`. - **OpenMP pools** — spun up *inside* a job for data-parallel loops. The thread budget for these is handed to the worker, not read from globals. - **Python worker threads** — one C thread per running Python script, each servicing that script's IPC connection. .. warning:: Only the GTK main thread may call GTK. Worker and Python threads reach the GUI through ``gui_iface`` slots that dispatch to the main thread via idle functions (``siril_add_idle`` / ``execute_idle_sync``). Never call ``execute_idle_sync`` from the main thread — it deadlocks. See :ref:`locking:locking`. End-to-end: a filter, from click to pixels =========================================== Following one operation all the way through ties the pieces together. When a user clicks **Apply** in, say, the asinh dialog: 1. The dialog's apply callback (in :file:`src/gui-gtk4`) fills a parameter struct, wraps it in a ``generic_img_args``, and calls ``start_in_new_thread`` (see :ref:`genericworkers:generic_image_worker`). 2. From the GTK main thread this is fire-and-forget: the job is queued and the callback returns, keeping the UI responsive. 3. The worker thread picks up the job, snapshots ``gfit``, runs the operation's ``image_hook`` on the copy without holding the fits lock, then takes a brief writer lock only to swap the result back in. 4. The worker requests undo creation, then schedules an idle on the main thread. 5. The idle calls ``notify_gfit_data_modified``, which invalidates the statistics cache, recomputes the histogram, merges any ROI, and remaps every viewport — the tiled renderer turns the new pixels into a ``GdkTexture`` and the widget redraws (see :ref:`displayrendering:display and rendering`). The command and Python paths differ only at step 1 (who fills the struct and submits) and in whether the submission blocks; steps 3–5 are identical.