Graphical User Interface (GUI)

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

The GUI is written using XML UI files suitable for reading by GtkBuilder.

Until recently there was a single enormous GUI file but this caused problems as it was slow to read and process at startup and very cumbersome indeed to edit by hand: it also meant that merge requests that each made substantial GUI changes tended to conflict horribly.

Now each GTK toplevel (each GtkDialog, GtkColorChooserDialog and GtkApplicationDialog) has its own UI file. The exception is the menus, which are all together in a file called menus.ui. These live in src/gui/uifiles. If you are modifying a part of the GUI, now you only risk conflicts with anyone else working on the same part of the GUI rather than any of it. There might still be issues around the Preferences dialog, but the problem should be much reduced.

If you add a new UI file (you should do so if you add another GtkDialog to provide a GUI for a new tool, for example) you must add it in src/gui/uifiles.h. The order doesn't matter too much but the best place to add it is just before siril.ui.

When handling your on_apply callback, you should follow a similar approach to the command processing approach and use the generic_image_worker framework, however in this case you will need to consider whether you need a custom idle function and you do not need to set args->command = TRUE (you can set it FALSE if you wish, but since generic_img_args are allocated using calloc() then it is already effectively FALSE).

Separation of GUI and Processing Code

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

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

  • No code outside src/gui may include a header file inside src/gui [0]

  • No use of the global struct guiinfo gui from outside src/gui. If a variable is required outside src/gui, it must live in com instead or be accessed via gui_iface. siril-cli is not linked with gui so use of it outside src/gui will cause the headless job to fail.

  • If you write some new processing code that features a GUI, you must write two separate files: one that lives in src/filters or src/algos or wherever, and another that lives inside src/gui and contains the GUI callbacks etc.

  • Some tightly integrated code outside src/gui may require to interact with the GUI outside of callbacks and idles, for example to trigger progress updates. A global struct of function pointers gui_iface enables this. gui_iface is populated at startup, either with a set of functions that interact with the GUI or - in headless mode and siril-cli - stubs that perform a no-op or return a sensible default value for headless operation.

[0] There are a very small number (4, I think) of architecturally necessary exceptions to

this rule, which are whitelisted in the CI lint job.

The header rules are enforced within the CI by two measures: first by compiling a job with the GUI disabled, and second by running a lint job to check for violations of the rules on GTK and src/gui includes outside src/gui.

There are several purposes of the strict GUI / processing code separation:

  • It will make migration of the GUI to GTK4 easier in due course, as all the changes are restricted to a single directory. (It could even be imagined that the GTK4 migration could be progressed in a directory src/gui-gtk4 alongside the legacy GTK3 UI, with separate population of gui_iface.)

  • It means that siril-cli doesn't need to be linked with GTK. This results in a 4MB smaller executable and a smaller code footprint.

  • Internally, meson splits the compilation into:

    • siril_lib: this is the core non-GUI processing code library and includes guiinfo gui

    • siril_headless_lib: this is a tiny library of headless stubs

    • src_files_gui: these are compiled as individual files

    • siril: this links siril_lib and the compiled src_files_gui

    • siril-cli: this links siril_lib and siril_headless_lib. It does not link any of the src/gui files and it does not include guiinfo gui.

    This means that in theory the siril_lib component may be more eaily usable for projects such as Vincent's (TODO: though it could probably use an init function to mimic the headless initialization that occurs at application startup)

gui_iface

This section 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 in different call sites, all members are listed. Note that this table was correct when added but it will not necessarily remain up-to-date if gui_iface evolves. It serves as a reference for developers needing to look up the replacement for old direct GUI code calls.

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

Plans

Currently some code still assumes that all of the UI is loaded all at once at startup. It might be advantageous to refactor this and then move to a position where each dialog is loaded on demand the first time it is needed. This should further improve startup time, at the cost of a small lag the first time each dialog is opened (subsequent uses will be just as fast as at present).

Notes on editing UI files:

  • Glade can now be problematic to use for editing existing UI files, as it will discard elements that are not available in the file. So if using a property that is defined in another UI file (e.g. one of the file filters) this will be discarded on save. Glade doesn't work at all with GTK4 anyway, so encouraging moving away from it is a good thing as we start to think about migrating to GTK4.

  • In the short term Glade should still be okay for designing complex new GtkDialogs, however if you need to use elements defined in other UI files you will have to do some manual editing afterwards.

To edit UI files without relying on Glade, your options are:

  • Write your UI as code. This is fine, but can be a bit painful for complex UIs.

  • Write your UI as XML, by hand. As above, this is okay as long as you are handy with XML but can be painful for complex UIs.

  • Continue to use Glade, but once you have finished the UI, run it through

    gtk4-builder-tool validate [filename]
    

    This will force GTK4 compatibility but you may have some patching up to do at the end. This is probably what I'll do for complex dialogs, as even with the final patching up I think being able to use Glade makes the initial writing of the UI much easier.

  • Cross your fingers that the GTK4 team realise how nuts it is not to have a graphical GUI editor, and provide one. Despite a couple of apparent efforts, nothing seems to be happening along these lines and the official recommendation from the GTK developers is to write your UI programatically or in hand-crafted XML!