Graphical User Interface (GUI)

Changed in version 1.5: The GUI was ported from GTK3 (Glade-based, in src/gui) to GTK4 (in src/gui-gtk4). Everywhere this page previously said "GTK3" or "src/gui", read GTK4 / src/gui-gtk4. The port-specific pitfalls have their own chapter, GTK4 quirks; the renderer has Display and rendering.

Siril has two modes of operation: headless using siril-cli, and with a GTK4 GUI using the usual siril command.

The GUI is written as XML UI files read by GtkBuilder. Each GTK toplevel (each window/dialog) has its own UI file under src/gui-gtk4/uifiles; the menus live together in menus.ui. Splitting the UI per-dialog keeps merge conflicts local to the dialog you are editing rather than one enormous file.

When handling your Apply callback, follow the same approach as the command path and use the generic_image_worker framework — but consider whether you need a custom idle function, and do not set args->command = TRUE (leave it FALSE; generic_img_args is calloc-allocated, so it already is). The full apply-callback pattern is in Add the GUI version.

The separation rules

In Siril the core and image-processing code is strictly separated from the GUI. The rules:

  • No GTK includes outside src/gui-gtk4 and src/main.c.

  • No code outside src/gui-gtk4 may include a header from inside it.

  • No use of the global guiinfo gui struct from outside src/gui-gtk4. If a variable is needed outside, it must live in com instead, or be reached through gui_iface. siril-cli is not linked with gui, so using it outside src/gui-gtk4 breaks the headless build.

  • New processing code with a GUI is split into two files: the logic in src/filters / src/algos / etc., and the GUI callbacks in a same-named file in src/gui-gtk4.

  • Tightly integrated code outside src/gui-gtk4 that must reach the GUI (e.g. progress updates) does so through the gui_iface vtable, populated at startup with real functions in the GUI build or no-op stubs headless.

A very small number of architecturally necessary exceptions are whitelisted in the CI lint job. The rules are enforced by CI: a headless build (which fails to link on any stray GUI call) plus a lint job that greps for GTK / gui-gtk4 includes outside src/gui-gtk4.

The purposes of the separation:

  • siril-cli need not link GTK — a smaller executable and code footprint.

  • Meson splits the build accordingly (see The two binaries): siril_lib (core, includes guiinfo gui and the gui_iface stubs), siril_headless_lib (headless stubs), the src/gui-gtk4 files compiled into siril, and siril-cli linking core + headless stubs only.

gui_iface in depth

gui_iface (declared in the GTK-free header src/core/gui_iface.h) is a global struct of function pointers — a vtable — that is the residual bridge from the core to the GUI. It is central to the separation.

  • Stubs vs implementation. Every slot defaults to a no-op stub in src/core/gui_iface_stubs.c (linked everywhere, so headless just does nothing or returns a sensible default). The GUI build overrides them with the real GTK implementations in src/gui-gtk4/gui_iface_impl.c.

  • Registration. siril_register_gui_iface is called from src/main.c after GTK initialisation to install the real implementations over the stubs. siril-cli never calls it, so the stubs remain.

  • Naming conventions. *_async slots are fire-and-forget (they schedule the work on the main thread and return); *_sync slots block until the main thread has run the work. The generic execute_idle_sync runs an idle synchronously — never call it from the GTK main thread, it self-deadlocks (see Locking).

  • Adding a member. Add the pointer to the struct in gui_iface.h, a real implementation in gui_iface_impl.c, and a no-op default in gui_iface_stubs.c. All three, or one build or the other fails to link. Expose a lock through gui_iface only when both sides genuinely need it (the ROI mutex is the precedent).

Replaced-function reference

This table lists the functions replaced by gui_iface members during the GUI separation refactor, sorted alphabetically by the replaced function name. Where a function was replaced by different members at different call sites, all members are listed. It was correct when added but will not necessarily stay up-to-date as gui_iface evolves — treat it as a lookup aid for old direct GUI calls, not as gospel.

Replaced function

gui_iface member

adjust_refimage

gui_iface.adjust_refimage

adjust_reginfo

gui_iface.adjust_reginfo

adjust_sellabel

gui_iface.adjust_sellabel

apply_cut_to_sequence

gui_iface.apply_cut_to_sequence

build_save_filename

gui_iface.build_save_filename

cfa_cut

gui_iface.run_cfa_cut

chain_channels_idle_callback

gui_iface.set_channels_linked

check_gaia_archive_status

gui_iface.check_gaia_status

check_gfit_profile_identical_to_monitor

gui_iface.check_icc_identical_to_monitor

clear_all_photometry_and_plot

gui_iface.clear_all_photometry_and_plot

clear_backup

gui_iface.clear_backup

clear_log_buffer_idle

gui_iface.clear_log_buffer

clear_previews

gui_iface.clear_previews

clear_psf_list_display

gui_iface.clear_star_list

close_sequence_idle

gui_iface.on_sequence_closed

close_tab

gui_iface.close_tab, gui_iface.on_channel_count_changed

cm_worker

gui_iface.update_icc_status_icon

cmsCloseProfile

gui_iface.apply_display_icc_compensation

cmsDeleteTransform

gui_iface.reset_display_transform

compute_aberration_inspector

gui_iface.compute_aberration_inspector

compute_histo_for_fit

gui_iface.compute_histo_for_fit

computeStat

gui_iface.on_stats_ready

console_clear_status_bar

gui_iface.console_clear_status

console_log_status

gui_iface.console_set_status

control_window_switch_to_tab

gui_iface.show_panel, gui_iface.switch_to_tab

copy_backup_to_gfit

gui_iface.copy_backup_to_gfit

copy_gfit_icc_to_backup

gui_iface.copy_gfit_icc_to_backup

copy_gfit_to_backup

gui_iface.copy_gfit_to_backup

copy_roi_into_gfit

gui_iface.copy_roi_into_gfit

copyICCProfile

gui_iface.apply_display_icc_compensation

create_new_siril_plot_window

gui_iface.show_siril_plot

crop_gui_updates

gui_iface.on_crop_complete

cut_profile

gui_iface.run_cut_profile

delete_selected_area

gui_iface.delete_selection

display_filename

gui_iface.display_filename

drawPlot

gui_iface.draw_plot, gui_iface.on_photometry_changed

enable_view_reference_checkbox

gui_iface.enable_view_reference_checkbox

end_script

gui_iface.end_script_gui

ensure_seqlist_dialog_closed

gui_iface.ensure_seqlist_dialog_closed

execute_idle_and_wait_for_it

gui_iface.execute_idle_sync

fill_sequence_list

gui_iface.fill_sequence_list

fits_change_depth

gui_iface.apply_display_icc_compensation

force_unlinked_channels

gui_iface.livestacking_setup_gui

free_image_data_gui

gui_iface.on_image_closed

g_slist_append

gui_iface.add_user_polygon_to_list

gaia_check

gui_iface.trigger_gaia_check

get_log_as_string

gui_iface.get_log_as_string

get_preview_gfit_backup

gui_iface.get_preview_gfit_backup

get_registration_layer_from_GUI

gui_iface.get_reg_layer

get_roi_fit

gui_iface.get_roi_fit

get_zoom_val

gui_iface.get_zoom_value

gtk_main_quit

gui_iface.quit_application

gtk_toggle_button_get_active

gui_iface.get_star_follow_state

gtk_widget_set_sensitive

gui_iface.enable_display_mode_menu

gui_log_message

gui_iface.log_message

heif_dialog

gui_iface.heif_dialog

init_draw_poly

gui_iface.set_poly_drawing

init_plot_colors

gui_iface.init_plot_colors

init_right_tab

gui_iface.init_right_tab

initialize_display_mode

gui_iface.initialize_display_mode

invalidate_gfit_histogram

gui_iface.invalidate_histogram

is_an_image_processing_dialog_opened

gui_iface.is_dialog_open

is_preview_active

gui_iface.is_preview_active

launch_clipboard_survey

gui_iface.launch_clipboard_survey

livestacking_display_config

gui_iface.livestacking_setup_gui

lock_display_transform

gui_iface.reset_display_transform

lock_roi_mutex

gui_iface.lock_roi_mutex

match_drawing_area_widget

gui_iface.get_channel_for_vport

new_selection_zone

gui_iface.new_selection_zone

notify_new_photometry

gui_iface.notify_new_photometry, gui_iface.on_photometry_changed

number_of_dialogs

gui_iface.number_of_dialogs

on_clear_roi

gui_iface.clear_roi, gui_iface.on_geometry_changed

on_set_roi

gui_iface.restore_roi

open_single_image_from_gfit

gui_iface.on_image_loaded, gui_iface.open_single_image_from_gfit

populate_roi

gui_iface.populate_roi

populate_seqcombo

gui_iface.populate_seq_combo

queue_activate_action_if_enabled

gui_iface.activate_action

queue_redraw

gui_iface.redraw_image_async

queue_redraw_and_wait_for_it

gui_iface.redraw_image_sync

queue_redraw_mask

gui_iface.queue_redraw_mask

redraw

gui_iface.redraw_image, gui_iface.on_photometry_changed

redraw_mask_idle

gui_iface.redraw_mask_idle

redraw_previews

gui_iface.redraw_previews

refresh_annotation_visibility

gui_iface.activate_annotation_display

refresh_found_objects

gui_iface.activate_annotation_display

refresh_keywords_dialog

gui_iface.refresh_keywords_dialog

refresh_script_menu_idle

gui_iface.refresh_script_menu

refresh_scripts_in_thread

gui_iface.refresh_scripts_in_thread

registration_update_label

gui_iface.update_registration_status

remap_all

gui_iface.remap_all_vports

reset_3stars

gui_iface.reset_3stars_gui

reset_cut_gui_filedependent

gui_iface.reset_cut_gui_filedependent

reset_display_offset

gui_iface.reset_display_offset

roi_is_active

gui_iface.roi_is_active

roi_operation_supports

gui_iface.roi_operation_supports

save_siril_plot_to_clipboard

gui_iface.save_siril_plot_to_clipboard

script_widgets_enable

gui_iface.script_widgets_enable

script_widgets_idle

gui_iface.script_widgets_async

select_vport

gui_iface.get_active_vport

seq_load_image_in_thread

gui_iface.seq_redisplay_frame

sequence_list_change_current

gui_iface.sequence_list_change_current

set_cursor_waiting

gui_iface.set_busy

set_cutoff_sliders_max_values

gui_iface.set_cutoff_sliders_max_values

set_cutoff_sliders_values

gui_iface.set_cutoff_sliders_values

set_display_mode

gui_iface.update_display_mode_state, gui_iface.livestacking_setup_gui

set_display_mode_idle

gui_iface.set_rendering_mode

set_display_mode_menu_sensitive_idle

gui_iface.set_suppress_redraws

set_GUI_CAMERA

gui_iface.set_GUI_CAMERA

set_GUI_CWD

gui_iface.set_gui_cwd

set_GUI_DiskSpace

gui_iface.update_disk_space

set_GUI_MEM

gui_iface.update_mem_usage

set_layers_for_registration

gui_iface.set_layers_for_registration

set_mask_active_idle

gui_iface.update_mask_enable

set_precision_switch

gui_iface.set_precision_switch

set_progress_bar_data

gui_iface.set_progress

set_seq_browser_active

gui_iface.set_seq_browser_active

set_seq_gui

gui_iface.on_sequence_opened, gui_iface.on_stack_complete

set_source_information

gui_iface.set_source_information

show_child_process_selection_dialog

gui_iface.select_child_process

show_command_help_popup

gui_iface.show_command_help

show_hide_toolbox

gui_iface.livestacking_setup_gui, gui_iface.livestacking_teardown_gui

show_or_hide_mask_tab

gui_iface.show_or_hide_mask_tab, gui_iface.on_mask_state_changed

show_or_hide_mask_tab_idle

gui_iface.show_or_hide_mask_tab_async

siril_close_dialog

gui_iface.close_dialog

siril_colorspace_transform

gui_iface.apply_display_icc_compensation

siril_confirm_dialog

gui_iface.confirm_dialog

siril_data_dialog

gui_iface.data_dialog

siril_open_dialog

gui_iface.open_dialog

siril_preview_hide

gui_iface.hide_preview

sliders_mode_set_state

gui_iface.sliders_mode_set_state

sliders_mode_set_state_idle

gui_iface.set_sliders_mode

toggle_remixer_window_visibility

gui_iface.toggle_remixer_window_visibility

tri_cut

gui_iface.run_tri_cut

uint32_to_gdk_rgba

gui_iface.set_poly_drawing

unlock_display_transform

gui_iface.reset_display_transform

unlock_roi_mutex

gui_iface.unlock_roi_mutex

update_display_fwhm

gui_iface.update_display_fwhm

update_gfit_histogram_if_needed

gui_iface.update_histogram

update_MenuItem

gui_iface.update_menu_item, gui_iface.update_menu_state

update_prepro_interface

gui_iface.update_prepro_interface

update_reg_interface

gui_iface.update_reg_interface

update_seq_gui_idle_thread_func

gui_iface.update_sequence_overlay_async

update_seqlist

gui_iface.update_seqlist

update_sequences_list

gui_iface.update_sequences_list

update_single_image_from_gfit

gui_iface.update_single_image_display

update_spinCPU

gui_iface.update_spin_cpu

update_stack_interface

gui_iface.update_stack_interface

update_star_list

gui_iface.update_star_list

update_zoom_label_idle

gui_iface.update_zoom_label, gui_iface.update_status_bar

UI loading

All UI files are loaded into a single global GtkBuilder (gui.builder) at startup by load_ui_files in src/main.c, in the order listed in src/gui-gtk4/ui_files.h. That order matters: a widget referencing a GObject defined in another file must be loaded after it, or the builder fails and the program refuses to start. The convention is dependencies first (file filters, menus, shortcuts), then the mostly-independent dialogs in alphabetical order, then the main application window, then the few dialogs that depend on the application window.

Look widgets up by id with lookup_widget (src/gui-gtk4/utils.h) rather than caching pointers. The .ui files are shipped as a compiled GResource; the manifest is src/siril_resource_gtk4.xml — a new .ui file must be added there as well as to ui_files.h.

Added in version 1.5: macOS accelerator rewrite at load time. Glade writes primary-modifier shortcuts as GDK_CONTROL_MASK. On macOS these must become GDK_META_MASK (Cmd). Files containing such accelerators are loaded through builder_add_from_resource_with_replace, which rewrites the string under #ifdef __APPLE__ before handing it to the builder; they are listed separately in ui_files.h.

The dialog registry

Dialogs are registered in the entries[] table in src/gui-gtk4/dialogs.c. Each SirilDialogEntry gives the toplevel's builder id (or a getter for windows created in code), a DialogType (INFORMATION_DIALOG, IMAGE_PROCESSING_DIALOG, SEARCH_ENTRY_DIALOG or OTHER_DIALOG), a has_preview flag, and — for preview dialogs — an apply_function used as the cancel handler.

Open and close dialogs with siril_open_dialog / siril_close_dialog (they set the transient parent and handle focus return — see GTK4 quirks). Image-processing dialogs are tracked so other subsystems can tell whether one is open (is_an_image_processing_dialog_opened), which matters for the Python thread claim (Threading).

Styling

src/gui-gtk4/siril.css is the single global stylesheet, loaded by siril_css.c.

Tip

Prefer one global CSS rule over per-.ui fixes for spacing or appearance regressions (maintainer policy) — a single rule in siril.css is easier to maintain than the same tweak scattered across dialogs.

Watch the font-scale trap: the root rule applies font-size to window, popover only (window, popover { font-size: 1.0em; }). Applying an em scale to a broader selector compounds the scale on nested widgets, so keep font scaling on that root selector.

Actions and menus

Menus are defined declaratively (GMenu) in menus.ui and driven by GAction. Application-wide actions are registered on the GApplication from app_entries[] in src/main.c; window-scoped actions are registered in src/gui-gtk4/callbacks.c.

Note

GtkPopoverMenu submenu/section label alignment issues are fixed in C (by equalising the section nesting), not in CSS.

Adding a new dialog

The mechanical steps, referenced from Add the GUI version:

  1. Design the .ui file and place it in src/gui-gtk4/uifiles.

  2. Add it to src/gui-gtk4/ui_files.h (respecting the ordering above — usually among the alphabetical dialogs, before the main window) and to the resource manifest src/siril_resource_gtk4.xml. If it uses primary-modifier accelerators, add it to the macOS-rewrite list instead.

  3. Register it in the entries[] table in src/gui-gtk4/dialogs.c with the right DialogType and, for a preview dialog, its cancel handler.

  4. Write the GUI callbacks in a same-named file in src/gui-gtk4, wiring the Apply button to your worker (see Add the GUI version).

  5. Open and close it via siril_open_dialog / siril_close_dialog.

Editing UI files

Changed in version 1.5: Glade is dead for GTK4. It does not support GTK4, and even for GTK3 it silently discards elements it does not recognise (e.g. properties defined in another UI file). Do not use it for new work.

Your options for editing UI files are:

  • Write the UI as code (fine, a bit painful for complex UIs).

  • Write the UI as hand-crafted XML.

  • Whichever you choose, validate the result before committing:

    gtk4-builder-tool validate [filename]