1 /*
2  * This file is part of Siril, an astronomy image processor.
3  * Copyright (C) 2005-2011 Francois Meyer (dulle at free.fr)
4  * Copyright (C) 2012-2021 team free-astro (see more in AUTHORS file)
5  * Reference site is https://free-astro.org/index.php/Siril
6  *
7  * Siril is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * Siril is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with Siril. If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #ifdef _WIN32
22 #include <windows.h>
23 /* Constant available since Shell32.dll 4.72 */
24 #ifndef CSIDL_APPDATA
25 #define CSIDL_APPDATA 0x001a
26 #endif
27 #endif
28 #include <string.h>
29 #include <locale.h>
30 
31 #include "core/siril.h"
32 #include "core/proto.h"
33 #include "core/initfile.h"
34 #include "core/command.h" // for process_close()
35 #include "core/command_line_processor.h"
36 #include "core/processing.h"
37 #include "core/OS_utils.h"
38 #include "core/siril_app_dirs.h"
39 #include "gui/utils.h"
40 #include "gui/message_dialog.h"
41 #include "gui/progress_and_log.h"
42 #include "algos/sorting.h"
43 #include "script_menu.h"
44 
45 #define SCRIPT_EXT ".ssf"
46 #define CONFIRM_RUN_SCRIPTS _("You are about to use scripts. Running automatic scripts is something that is easy and generally it provides a nice image. However you have to keep in mind that scripts are not magic; automatic choices are made where human decision would probably be better. Also, every commands used in a script are available on the interface with a better parameter control.")
47 
initialize_script_paths()48 static GSList *initialize_script_paths(){
49 	GSList *list = NULL;
50 #ifdef _WIN32
51 	list = g_slist_prepend(list, g_build_filename(get_special_folder(CSIDL_APPDATA), "siril",
52 					"scripts", NULL));
53 
54 	gchar *execpath = g_win32_get_package_installation_directory_of_module(NULL);
55 
56 	list = g_slist_prepend(list, g_build_filename(execpath, "scripts", NULL));
57 	g_free(execpath);
58 #else
59 	list = g_slist_prepend(list, g_build_filename(siril_get_system_data_dir(), "scripts", NULL));
60 	list = g_slist_prepend(list, g_build_filename(g_get_home_dir(), ".siril", "scripts", NULL));
61 	list = g_slist_prepend(list, g_build_filename(g_get_home_dir(), "siril", "scripts", NULL));
62 #endif
63 	list = g_slist_reverse(list);
64 	return list;
65 }
66 
add_path_to_gtkText(gchar * path)67 static void add_path_to_gtkText(gchar *path) {
68 	static GtkTextBuffer *tbuf = NULL;
69 	static GtkTextView *text = NULL;
70 	GtkTextIter iter;
71 
72 	if (!tbuf) {
73 		text = GTK_TEXT_VIEW(lookup_widget("GtkTxtScriptPath"));
74 		tbuf = gtk_text_view_get_buffer(text);
75 	}
76 
77 	gtk_text_buffer_get_end_iter(tbuf, &iter);
78 	gtk_text_buffer_insert(tbuf, &iter, path, strlen(path));
79 	gtk_text_buffer_insert(tbuf, &iter, "\n", strlen("\n"));
80 
81 	/* scroll to end */
82 	gtk_text_buffer_get_end_iter(tbuf, &iter);
83 	GtkTextMark *insert_mark = gtk_text_buffer_get_insert(tbuf);
84 	gtk_text_buffer_place_cursor(tbuf, &iter);
85 	gtk_text_view_scroll_to_mark(text, insert_mark, 0.0, TRUE, 0.0, 1.0);
86 }
87 
clear_gtk_list()88 static void clear_gtk_list() {
89 	GtkTextView *text = GTK_TEXT_VIEW(lookup_widget("GtkTxtScriptPath"));
90 	GtkTextBuffer *tbuf = gtk_text_view_get_buffer(text);
91 	GtkTextIter start_iter, end_iter;
92 	gtk_text_buffer_get_start_iter(tbuf, &start_iter);
93 	gtk_text_buffer_get_end_iter(tbuf, &end_iter);
94 	gtk_text_buffer_delete(tbuf, &start_iter, &end_iter);
95 }
96 
search_script(const char * path)97 static GSList *search_script(const char *path) {
98 	GSList *list = NULL;
99 	GDir *dir;
100 	GError *error = NULL;
101 	const gchar *file;
102 
103 	dir = g_dir_open(path, 0, &error);
104 	if (!dir) {
105 		fprintf(stderr, "scripts: %s\n", error->message);
106 		g_clear_error(&error);
107 		return NULL;
108 	}
109 	while ((file = g_dir_read_name(dir)) != NULL) {
110 		if (g_str_has_suffix(file, SCRIPT_EXT)) {
111 			gchar *str = (gchar*) remove_ext_from_filename(file);
112 
113 			list = g_slist_prepend(list, str);
114 		}
115 	}
116 	list = g_slist_sort(list, (GCompareFunc) strcompare);
117 	g_dir_close(dir);
118 
119 	return list;
120 }
121 
on_script_execution(GtkMenuItem * menuitem,gpointer user_data)122 static void on_script_execution(GtkMenuItem *menuitem, gpointer user_data) {
123 	GError *error = NULL;
124 
125 	if (get_thread_run()) {
126 		PRINT_ANOTHER_THREAD_RUNNING;
127 		return;
128 	}
129 
130 	if (com.pref.save.warn_script) {
131 		gboolean dont_show_again;
132 		gboolean confirm = siril_confirm_dialog_and_remember(
133 				_("Please read me before using scripts"), CONFIRM_RUN_SCRIPTS, _("Run Script"), &dont_show_again);
134 		com.pref.save.warn_script = !dont_show_again;
135 		/* We do not use set_GUI_misc because some button state can be in an unsaved state if the
136 		 * preference dialog is opened
137 		 */
138 		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(lookup_widget("miscAskScript")), com.pref.save.warn_script);
139 		/* update config file */
140 		writeinitfile();
141 		if (!confirm) {
142 			return;
143 		}
144 	}
145 
146 	if (com.script_thread)
147 		g_thread_join(com.script_thread);
148 
149 	/* Switch to console tab */
150 	control_window_switch_to_tab(OUTPUT_LOGS);
151 
152 	gchar *script_file = g_strdup_printf("%s%s", (gchar *) user_data, SCRIPT_EXT);
153 	GFile *file = g_file_new_for_path(script_file);
154 
155     GFileInfo *info;
156 
157 	info = g_file_query_info(file, G_FILE_ATTRIBUTE_STANDARD_SIZE,
158 			G_FILE_QUERY_INFO_NONE, NULL, &error);
159 	if (info) {
160 		GInputStream *input_stream = (GInputStream*) g_file_read(file, NULL, &error);
161 
162 		if (input_stream == NULL) {
163 			if (error != NULL) {
164 				g_clear_error(&error);
165 				siril_log_message(_("File [%s] does not exist\n"), script_file);
166 			}
167 
168 			g_free(script_file);
169 			g_object_unref(file);
170 			return;
171 		}
172 		/* ensure that everything is closed */
173 		process_close(0);
174 		/* Then, run script */
175 		siril_log_message(_("Starting script %s\n"), script_file);
176 		com.script_thread = g_thread_new("script", execute_script, input_stream);
177 	}
178 
179 	g_free(script_file);
180 	g_object_unref(file);
181 }
182 
initialize_script_menu()183 int initialize_script_menu() {
184 	static GtkWidget *menuscript = NULL;
185 	GSList *list, *script, *s;
186 	GtkWidget *menu;
187 	gint nb_item = 0;
188 
189 	if (!menuscript) {
190 		menuscript = lookup_widget("header_scripts_button");
191 	}
192 
193 	script = set_list_to_preferences_dialog(com.pref.script_path);
194 
195 	menu = gtk_menu_new();
196 	gtk_widget_hide(menuscript);
197 
198 	for (s = script; s; s = s->next) {
199 		list = search_script(s->data);
200 		if (list) {
201 			GSList *l;
202 			if (!gtk_widget_get_visible(menuscript)) {
203 				gtk_widget_show(menuscript);
204 				gtk_menu_button_set_popup(GTK_MENU_BUTTON(menuscript), menu);
205 			}
206 			/* write separator but not for the first one */
207 			if (nb_item != 0) {
208 				GtkWidget *separator = gtk_separator_menu_item_new();
209 				gtk_menu_shell_append(GTK_MENU_SHELL(menu), separator);
210 				gtk_widget_show(separator);
211 			}
212 			siril_log_color_message(_("Searching scripts in: \"%s\"...\n"), "green", s->data);
213 			for (l = list; l; l = l->next) {
214 				nb_item ++;
215 				/* write an item per script file */
216 				GtkWidget *menu_item;
217 
218 				menu_item = gtk_menu_item_new_with_label(l->data);
219 				gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
220 				gchar *full_path = g_build_filename(s->data, l->data,
221 						NULL);
222 				g_signal_connect(G_OBJECT(menu_item), "activate",
223 						G_CALLBACK(on_script_execution), (gchar * ) full_path);
224 				siril_log_message(_("Loading script: %s\n"), l->data);
225 				gtk_widget_show(menu_item);
226 			}
227 			g_slist_free_full(list, g_free);
228 		}
229 	}
230 
231 	writeinitfile();
232 
233 	return 0;
234 }
235 
refresh_scripts(gboolean update_list,gchar ** error)236 int refresh_scripts(gboolean update_list, gchar **error) {
237 	gchar *err = NULL;
238 	int retval;
239 	GSList *list = get_list_from_preferences_dialog();
240 	if (list == NULL) {
241 		err = siril_log_color_message(_("Cannot refresh the scripts if the list is empty.\n"), "red");
242 		retval = 1;
243 	} else {
244 		g_slist_free_full(com.pref.script_path, g_free);
245 		com.pref.script_path = list;
246 		retval = initialize_script_menu();
247 	}
248 	if (error) {
249 		*error = err;
250 	}
251 	return retval;
252 }
253 
get_list_from_preferences_dialog()254 GSList *get_list_from_preferences_dialog() {
255 	GSList *list = NULL;
256 	static GtkTextBuffer *tbuf = NULL;
257 	static GtkTextView *text = NULL;
258 	GtkTextIter start, end;
259 	gchar *txt;
260 	gint i = 0;
261 
262 	if (!tbuf) {
263 		text = GTK_TEXT_VIEW(lookup_widget("GtkTxtScriptPath"));
264 		tbuf = gtk_text_view_get_buffer(text);
265 	}
266 	gtk_text_buffer_get_bounds(tbuf, &start, &end);
267 	txt = gtk_text_buffer_get_text(tbuf, &start, &end, TRUE);
268 	if (txt) {
269 		gchar **token = g_strsplit(txt, "\n", -1);
270 		while (token[i]) {
271 			if (*token[i] != '\0')
272 				list = g_slist_prepend(list, g_strdup(token[i]));
273 			i++;
274 		}
275 		list = g_slist_reverse(list);
276 		g_strfreev(token);
277 	}
278 
279 	return list;
280 }
281 
set_list_to_preferences_dialog(GSList * list)282 GSList *set_list_to_preferences_dialog(GSList *list) {
283 	clear_gtk_list();
284 	if (list == NULL) {
285 		list = initialize_script_paths();
286 	}
287 	for (GSList *l = list; l; l = l->next) {
288 		add_path_to_gtkText((gchar *) l->data);
289 	}
290 	return list;
291 }
292 
293 /* Get Scripts menu */
294 
295 #define GET_SCRIPTS_URL "https://free-astro.org/index.php?title=Siril:scripts"
296 
siril_get_on_script_pages()297 void siril_get_on_script_pages() {
298 	gboolean ret;
299 	const char *locale;
300 	const char *supported_languages[] = { "fr", NULL }; // en is NULL: default language
301 	gchar *lang = NULL;
302 	int i = 0;
303 
304 	if (!g_strcmp0(com.pref.combo_lang, "")) {
305 		locale = setlocale(LC_MESSAGES, NULL);
306 	} else {
307 		locale = com.pref.combo_lang;
308 	}
309 
310 	if (locale) {
311 		while (supported_languages[i]) {
312 			if (!strncmp(locale, supported_languages[i], 2)) {
313 				lang = g_strndup(locale, 2);
314 				break;
315 			}
316 			i++;
317 		}
318 	}
319 	gchar *url = g_build_path("/", GET_SCRIPTS_URL, lang, NULL);
320 
321 #if GTK_CHECK_VERSION(3, 22, 0)
322 	GtkWidget* win = lookup_widget("control_window");
323 	ret = gtk_show_uri_on_window(GTK_WINDOW(GTK_APPLICATION_WINDOW(win)), url,
324 			gtk_get_current_event_time(), NULL);
325 #else
326 	ret = gtk_show_uri(gdk_screen_get_default(), url,
327 			gtk_get_current_event_time(), NULL);
328 #endif
329 	if (!ret) {
330 		siril_message_dialog(GTK_MESSAGE_ERROR, _("Could not show link"),
331 				_("Please go to <a href=\""GET_SCRIPTS_URL"\">"GET_SCRIPTS_URL"</a> "
332 								"by copying the link."));
333 	}
334 	g_free(url);
335 	g_free(lang);
336 }
337 
338