1 /**
2  * @file   motion.c
3  * @brief
4  *
5  * Copyright (C) 2009 Gummi Developers
6  * All Rights reserved.
7  *
8  * Permission is hereby granted, free of charge, to any person
9  * obtaining a copy of this software and associated documentation
10  * files (the "Software"), to deal in the Software without
11  * restriction, including without limitation the rights to use,
12  * copy, modify, merge, publish, distribute, sublicense, and/or sell
13  * copies of the Software, and to permit persons to whom the
14  * Software is furnished to do so, subject to the following
15  * conditions:
16  *
17  * The above copyright notice and this permission notice shall be
18  * included in all copies or substantial portions of the Software.
19  *
20  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
22  * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
24  * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
25  * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27  * OTHER DEALINGS IN THE SOFTWARE.
28  */
29 
30 #include <errno.h>
31 #include <stdlib.h>
32 #include <stdio.h>
33 #include <string.h>
34 #include <unistd.h>
35 #include <signal.h>
36 
37 #ifndef WIN32
38     #include <sys/types.h>
39     #include <sys/wait.h>
40 #endif
41 
42 #ifdef WIN32
43     #include <windows.h>
44 #endif
45 
46 #include <glib.h>
47 #include <gtk/gtk.h>
48 
49 #include "configfile.h"
50 #include "editor.h"
51 #include "environment.h"
52 #include "gui/gui-main.h"
53 #include "gui/gui-preview.h"
54 #include "latex.h"
55 #include "snippets.h"
56 #include "utils.h"
57 
58 extern GummiGui* gui;
59 extern Gummi* gummi;
60 
61 /* Typesetter pid */
62 pid_t typesetter_pid = 0;
63 
motion_init(void)64 GuMotion* motion_init (void) {
65     GuMotion* m = g_new0 (GuMotion, 1);
66 
67     m->key_press_timer = 0;
68     g_mutex_init(&m->signal_mutex);
69     g_mutex_init(&m->compile_mutex);
70     g_cond_init(&m->compile_cv);
71     m->keep_running = TRUE;
72     m->keep_running = FALSE;
73     m->typesetter_pid = &typesetter_pid;
74 
75     return m;
76 }
77 
motion_start_compile_thread(GuMotion * m)78 void motion_start_compile_thread (GuMotion* m) {
79     m->keep_running = TRUE;
80     m->compile_thread = g_thread_new ("motion", motion_compile_thread, m);
81 }
82 
motion_stop_compile_thread(GuMotion * m)83 void motion_stop_compile_thread (GuMotion* m) {
84     L_F_DEBUG;
85 
86     m->keep_running = FALSE;
87     motion_do_compile(m);
88     g_thread_join(m->compile_thread);
89 }
90 
motion_pause_compile_thread(GuMotion * m)91 void motion_pause_compile_thread (GuMotion* m) {
92     L_F_DEBUG;
93 
94     m->pause = TRUE;
95     motion_do_compile(m);
96 }
97 
motion_resume_compile_thread(GuMotion * m)98 void motion_resume_compile_thread (GuMotion* m) {
99     L_F_DEBUG;
100 
101     m->pause = FALSE;
102     motion_do_compile(m);
103 }
104 
motion_kill_typesetter(GuMotion * m)105 void motion_kill_typesetter (GuMotion* m) {
106     if (*m->typesetter_pid) {
107         gchar* command = NULL;
108         /* Kill children spawned by typesetter command/script, don't know
109          * how to do this programatically yet(glib doesn't not provides any
110          * function for killing a process), so use pkill for now. For
111          * win32 there's currently nothing we can do about it. */
112 #ifndef WIN32
113         command = g_strdup_printf("pkill -15 -P %d", *m->typesetter_pid);
114         system(command);
115         g_free(command);
116 
117         /* Make sure typesetter command is terminated */
118         if (kill(*m->typesetter_pid, 15)) {
119             slog(L_ERROR, "Could not kill process: %s\n",
120                                     g_strerror(errno));
121         }
122 #else
123         if (!TerminateProcess(*m->typesetter_pid, 0)) {
124             gchar *msg = g_win32_error_message(GetLastError());
125             slog (L_ERROR, "Could not kill process: %s\n",
126                                     msg ? msg : "(null)");
127             g_free(msg);
128         }
129 
130 #endif
131 
132 
133         slog(L_DEBUG, "Typeseter[pid=%d]: Killed\n", *m->typesetter_pid);
134         *m->typesetter_pid = 0;
135 
136         /* XXX: Ugly hack: delay compile signal */
137         motion_start_timer (m);
138     }
139 }
140 
motion_do_compile(gpointer user)141 gboolean motion_do_compile (gpointer user) {
142     L_F_DEBUG;
143     GuMotion* mc = GU_MOTION (user);
144 
145     if (!g_mutex_trylock (&mc->signal_mutex)) goto ret;
146     g_cond_signal (&mc->compile_cv);
147     g_mutex_unlock (&mc->signal_mutex);
148 
149 ret:
150     return (config_value_as_str_equals ("Compile", "scheme", "real_time"));
151 }
152 
motion_compile_thread(gpointer data)153 gpointer motion_compile_thread (gpointer data) {
154     L_F_DEBUG;
155     GuMotion* mc = GU_MOTION (data);
156     GuEditor* editor = NULL;
157     GuLatex* latex = NULL;
158     gboolean precompile_ok = FALSE;
159     gchar *editortext;
160 
161     latex = gummi_get_latex ();
162 
163     while (TRUE) {
164         if (!g_mutex_trylock (&mc->compile_mutex)) continue;
165         slog (L_DEBUG, "Compile thread sleeping...\n");
166         g_cond_wait (&mc->compile_cv, &mc->compile_mutex);
167         slog (L_DEBUG, "Compile thread awoke.\n");
168 
169         if (!(editor = gummi_get_active_editor ())) {
170             g_mutex_unlock (&mc->compile_mutex);
171             continue;
172         }
173         if (!mc->keep_running) {
174             g_mutex_unlock (&mc->compile_mutex);
175             g_thread_exit (NULL);
176         }
177 
178         if (mc->pause) {
179             g_mutex_unlock (&mc->compile_mutex);
180             continue;
181         }
182 
183         gdk_threads_enter ();
184         editortext = latex_update_workfile (editor);
185         precompile_ok = latex_precompile_check (editortext);
186         g_free (editortext);
187         gdk_threads_leave ();
188 
189         if (!precompile_ok) {
190             gdk_threads_add_idle (on_document_error, "document_error");
191             g_mutex_unlock (&mc->compile_mutex);
192             continue;
193         }
194 
195         latex_update_pdffile (latex, editor);
196 
197         *mc->typesetter_pid = 0;
198         g_mutex_unlock (&mc->compile_mutex);
199 
200         if (!mc->keep_running)
201             g_thread_exit (NULL);
202 
203         gdk_threads_add_idle (on_document_compiled, editor);
204         continue;
205     }
206 }
207 
motion_force_compile(GuMotion * mc)208 void motion_force_compile (GuMotion *mc) {
209     /* sort-of signal to force a compile run after certain actions that
210      * don't trigger the regular editor content change signals */
211     gummi->latex->modified_since_compile = TRUE;
212     motion_do_compile (mc);
213 }
214 
motion_idle_cb(gpointer user)215 gboolean motion_idle_cb (gpointer user) {
216     GU_MOTION(user)->key_press_timer = 0;
217     if (gui->previewgui->preview_on_idle)
218         motion_do_compile (GU_MOTION (user));
219     return FALSE;
220 }
221 
motion_start_timer(GuMotion * mc)222 void motion_start_timer (GuMotion* mc) {
223     motion_stop_timer (mc);
224     mc->key_press_timer = g_timeout_add_seconds (
225                                 config_get_integer ("Compile", "timer"),
226                                 motion_idle_cb, mc);
227 }
228 
motion_stop_timer(GuMotion * mc)229 void motion_stop_timer (GuMotion* mc) {
230     if (mc->key_press_timer > 0) {
231         g_source_remove (mc->key_press_timer);
232         mc->key_press_timer = 0;
233     }
234 }
235 
on_key_press_cb(GtkWidget * widget,GdkEventKey * event,void * user)236 gboolean on_key_press_cb (GtkWidget* widget, GdkEventKey* event, void* user) {
237     if (!event->is_modifier) {
238         motion_stop_timer (GU_MOTION (user));
239     }
240     if (config_get_boolean ("Interface", "snippets") &&
241         snippets_key_press_cb (gummi_get_snippets (),
242                                gummi_get_active_editor (), event))
243         return TRUE;
244     return FALSE;
245 }
246 
on_key_release_cb(GtkWidget * widget,GdkEventKey * event,void * user)247 gboolean on_key_release_cb (GtkWidget* widget, GdkEventKey* event, void* user) {
248     if (!event->is_modifier) {
249         motion_start_timer (GU_MOTION (user));
250     }
251     if (config_get_boolean ("Interface", "snippets") &&
252         snippets_key_release_cb (gummi_get_snippets (),
253                                  gummi_get_active_editor (), event))
254         return TRUE;
255     return FALSE;
256 }
257