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-gtk4andsrc/main.c.No code outside
src/gui-gtk4may include a header from inside it.No use of the global
guiinfo guistruct from outsidesrc/gui-gtk4. If a variable is needed outside, it must live incominstead, or be reached throughgui_iface.siril-cliis not linked withgui, so using it outsidesrc/gui-gtk4breaks 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 insrc/gui-gtk4.Tightly integrated code outside
src/gui-gtk4that must reach the GUI (e.g. progress updates) does so through thegui_ifacevtable, 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-clineed not link GTK — a smaller executable and code footprint.Meson splits the build accordingly (see The two binaries):
siril_lib(core, includesguiinfo guiand thegui_ifacestubs),siril_headless_lib(headless stubs), thesrc/gui-gtk4files compiled intosiril, andsiril-clilinking 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 insrc/gui-gtk4/gui_iface_impl.c.Registration.
siril_register_gui_ifaceis called fromsrc/main.cafter GTK initialisation to install the real implementations over the stubs.siril-clinever calls it, so the stubs remain.Naming conventions.
*_asyncslots are fire-and-forget (they schedule the work on the main thread and return);*_syncslots block until the main thread has run the work. The genericexecute_idle_syncruns 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 ingui_iface_impl.c, and a no-op default ingui_iface_stubs.c. All three, or one build or the other fails to link. Expose a lock throughgui_ifaceonly 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 |
|
|---|---|
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.
Adding a new dialog
The mechanical steps, referenced from Add the GUI version:
Design the
.uifile and place it insrc/gui-gtk4/uifiles.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 manifestsrc/siril_resource_gtk4.xml. If it uses primary-modifier accelerators, add it to the macOS-rewrite list instead.Register it in the
entries[]table insrc/gui-gtk4/dialogs.cwith the rightDialogTypeand, for a preview dialog, its cancel handler.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).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]