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 |
|
|---|---|
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!