1 /*
2  * Copyright (C) 2010 Marco Diego Aurélio Mesquita
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU Lesser General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * Authors:
19  *   Marco Diego Aurélio Mesquita <marcodiegomesquita@gmail.com>
20  */
21 
22 #include <config.h>
23 
24 /**
25  * SECTION:glade-preview
26  * @Short_Description: The glade preview launch/kill interface.
27  *
28  * This object owns all data that is needed to keep a preview. It stores
29  * the GIOChannel used for comunnication between glade and glade-previewer,
30  * the event source id for a watch  (in the case a watch is used to monitor
31  * the communication channel), the previewed widget and the pid of the
32  * corresponding glade-previewer.
33  *
34  */
35 
36 
37 #include <string.h>
38 #include <stdlib.h>
39 #include <glib.h>
40 #include <glib/gi18n-lib.h>
41 #include <glib/gstdio.h>
42 
43 #include "glade.h"
44 #include "glade-preview.h"
45 #include "glade-project.h"
46 #include "glade-app.h"
47 
48 #include "glade-preview-tokens.h"
49 
50 #ifdef G_OS_WIN32
51 #define GLADE_PREVIEWER "glade-previewer.exe"
52 #else
53 #define GLADE_PREVIEWER "glade-previewer"
54 #endif
55 
56 /* Private data for glade-preview */
57 struct _GladePreviewPrivate
58 {
59   GIOChannel *channel;          /* Channel user for communication between glade and glade-previewer */
60   guint watch;                  /* Event source id used to monitor the channel */
61   GladeWidget *previewed_widget;
62   GPid pid;                     /* Pid of the corresponding glade-previewer process */
63 };
64 
65 G_DEFINE_TYPE_WITH_PRIVATE (GladePreview, glade_preview, G_TYPE_OBJECT);
66 
67 enum
68 {
69   PREVIEW_EXITS,
70   LAST_SIGNAL
71 };
72 
73 static guint glade_preview_signals[LAST_SIGNAL] = { 0 };
74 
75 /**
76  * glade_preview_kill
77  * @preview: a #GladePreview that will be killed.
78  *
79  * Uses the communication channel and protocol to send the "<quit>" token to the
80  * glade-previewer telling it to commit suicide.
81  *
82  */
83 static void
glade_preview_kill(GladePreview * preview)84 glade_preview_kill (GladePreview *preview)
85 {
86   const gchar *quit = QUIT_TOKEN;
87   GIOChannel *channel;
88   GError *error = NULL;
89   gsize size;
90 
91   channel = preview->priv->channel;
92   g_io_channel_write_chars (channel, quit, strlen (quit), &size, &error);
93 
94   if (size != strlen (quit) && error != NULL)
95     {
96       g_warning ("Error passing quit signal trough pipe: %s", error->message);
97       g_error_free (error);
98     }
99 
100   g_io_channel_flush (channel, &error);
101   if (error != NULL)
102     {
103       g_warning ("Error flushing channel: %s", error->message);
104       g_error_free (error);
105     }
106 
107   g_io_channel_shutdown (channel, TRUE, &error);
108   if (error != NULL)
109     {
110       g_warning ("Error shutting down channel: %s", error->message);
111       g_error_free (error);
112     }
113 }
114 
115 static void
glade_preview_dispose(GObject * gobject)116 glade_preview_dispose (GObject *gobject)
117 {
118   GladePreview *self = GLADE_PREVIEW (gobject);
119 
120   if (self->priv->watch)
121     {
122       g_source_remove (self->priv->watch);
123       glade_preview_kill (self);
124     }
125 
126   if (self->priv->channel)
127     {
128       g_io_channel_unref (self->priv->channel);
129       self->priv->channel = NULL;
130     }
131 
132   G_OBJECT_CLASS (glade_preview_parent_class)->dispose (gobject);
133 }
134 
135 /* We have to use finalize because of the signal that is sent in dispose */
136 static void
glade_preview_finalize(GObject * gobject)137 glade_preview_finalize (GObject *gobject)
138 {
139   G_OBJECT_CLASS (glade_preview_parent_class)->finalize (gobject);
140 }
141 
142 static void
glade_preview_class_init(GladePreviewClass * klass)143 glade_preview_class_init (GladePreviewClass *klass)
144 {
145   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
146 
147   gobject_class->dispose  = glade_preview_dispose;
148   gobject_class->finalize = glade_preview_finalize;
149 
150   /**
151    * GladePreview::exits:
152    * @gladepreview: the #GladePreview which received the signal.
153    * @gladeproject: the #GladeProject associated with the preview.
154    *
155    * Emitted when @preview exits.
156    */
157   glade_preview_signals[PREVIEW_EXITS] =
158       g_signal_new ("exits",
159                     G_TYPE_FROM_CLASS (gobject_class),
160                     G_SIGNAL_RUN_FIRST,
161                     0, NULL, NULL,
162                     g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
163 }
164 
165 static void
glade_preview_init(GladePreview * self)166 glade_preview_init (GladePreview *self)
167 {
168   GladePreviewPrivate *priv;
169 
170   self->priv = priv = glade_preview_get_instance_private (self);
171   priv->channel = NULL;
172 }
173 
174 static void
glade_preview_internal_watch(GPid pid,gint status,gpointer data)175 glade_preview_internal_watch (GPid pid, gint status, gpointer data)
176 {
177   GladePreview *preview = GLADE_PREVIEW (data);
178 
179   preview->priv->watch = 0;
180 
181   /* This means a preview exited. We'll now signal it */
182   g_signal_emit (preview, glade_preview_signals[PREVIEW_EXITS], 0);
183 }
184 
185 /**
186  * glade_preview_launch:
187  * @widget: Pointer to a local instance of the widget that will be previewed.
188  * @buffer: Contents of an xml definition of the interface which will be previewed
189  *
190  * Creates a new #GladePreview and launches glade-previewer to preview it.
191  *
192  * Returns: a new #GladePreview or NULL if launch fails.
193  *
194  */
195 GladePreview *
glade_preview_launch(GladeWidget * widget,const gchar * buffer)196 glade_preview_launch (GladeWidget *widget, const gchar *buffer)
197 {
198   GPid pid;
199   GError *error = NULL;
200   gchar *argv[10], *executable;
201   gint child_stdin;
202   gsize bytes_written;
203   GIOChannel *output;
204   GladePreview *preview = NULL;
205   const gchar *css_provider, *filename;
206   GladeProject *project;
207   gchar *name;
208   gint i;
209 
210   g_return_val_if_fail (GLADE_IS_WIDGET (widget), NULL);
211 
212   executable = g_find_program_in_path (GLADE_PREVIEWER);
213 
214   project = glade_widget_get_project (widget);
215   filename = glade_project_get_path (project);
216   name = (filename) ? NULL : glade_project_get_name (project);
217 
218   argv[0] = executable;
219   argv[1] = "--listen";
220   argv[2] = "--toplevel";
221   argv[3] = (gchar *) glade_widget_get_name (widget);
222   argv[4] = "--filename";
223   argv[5] = (filename) ? (gchar *) filename : name;
224 
225   i = 5;
226   if (glade_project_get_template (project))
227     argv[++i] = "--template";
228 
229   argv[++i] = NULL;
230 
231   css_provider = glade_project_get_css_provider_path (glade_widget_get_project (widget));
232   if (css_provider)
233     {
234       argv[i] = "--css";
235       argv[++i] = (gchar *) css_provider;
236       argv[++i] = NULL;
237     }
238 
239   if (g_spawn_async_with_pipes (NULL,
240                                 argv,
241                                 NULL, G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL,
242                                 &pid, &child_stdin, NULL, NULL,
243                                 &error) == FALSE)
244     {
245       g_warning (_("Error launching previewer: %s\n"), error->message);
246       glade_util_ui_message (glade_app_get_window (),
247                              GLADE_UI_ERROR, NULL,
248                              _("Failed to launch preview: %s.\n"),
249                              error->message);
250       g_error_free (error);
251       g_free (executable);
252       g_free (name);
253       return NULL;
254     }
255 
256 #ifdef G_OS_WIN32
257   output = g_io_channel_win32_new_fd (child_stdin);
258 #else
259   output = g_io_channel_unix_new (child_stdin);
260 #endif
261 
262   g_io_channel_write_chars (output, buffer, strlen (buffer), &bytes_written,
263                             &error);
264 
265   if (bytes_written != strlen (buffer) && error != NULL)
266     {
267       g_warning ("Error passing UI trough pipe: %s", error->message);
268       g_error_free (error);
269     }
270 
271   g_io_channel_flush (output, &error);
272   if (error != NULL)
273     {
274       g_warning ("Error flushing UI trough pipe: %s", error->message);
275       g_error_free (error);
276     }
277 
278   /* Setting up preview data */
279   preview                         = g_object_new (GLADE_TYPE_PREVIEW, NULL);
280   preview->priv->channel          = output;
281   preview->priv->previewed_widget = widget;
282   preview->priv->pid              = pid;
283 
284   preview->priv->watch =
285     g_child_watch_add (preview->priv->pid,
286 		       glade_preview_internal_watch,
287 		       preview);
288 
289   g_free (executable);
290   g_free (name);
291 
292   return preview;
293 }
294 
295 void
glade_preview_update(GladePreview * preview,const gchar * buffer)296 glade_preview_update (GladePreview *preview, const gchar  *buffer)
297 {
298   const gchar *update_token = UPDATE_TOKEN;
299   gchar *update;
300   GIOChannel *channel;
301   GError *error = NULL;
302   gsize size;
303   gsize bytes_written;
304   GladeWidget *gwidget;
305 
306   g_return_if_fail (GLADE_IS_PREVIEW (preview));
307   g_return_if_fail (buffer && buffer[0]);
308 
309   gwidget = glade_preview_get_widget (preview);
310 
311   update = g_strdup_printf ("%s%s\n", update_token,
312                             glade_widget_get_name (gwidget));
313 
314   channel = preview->priv->channel;
315   g_io_channel_write_chars (channel, update, strlen (update), &size, &error);
316 
317   if (size != strlen (update) && error != NULL)
318     {
319       g_warning ("Error passing quit signal trough pipe: %s", error->message);
320       g_error_free (error);
321     }
322 
323   g_io_channel_flush (channel, &error);
324   if (error != NULL)
325     {
326       g_warning ("Error flushing channel: %s", error->message);
327       g_error_free (error);
328     }
329 
330   /* We'll now send the interface: */
331   g_io_channel_write_chars (channel, buffer, strlen (buffer), &bytes_written,
332                             &error);
333 
334   if (bytes_written != strlen (buffer) && error != NULL)
335     {
336       g_warning ("Error passing UI trough pipe: %s", error->message);
337       g_error_free (error);
338     }
339 
340   g_io_channel_flush (channel, &error);
341   if (error != NULL)
342     {
343       g_warning ("Error flushing UI trough pipe: %s", error->message);
344       g_error_free (error);
345     }
346 
347   g_free (update);
348 }
349 
350 GladeWidget *
glade_preview_get_widget(GladePreview * preview)351 glade_preview_get_widget (GladePreview *preview)
352 {
353   g_return_val_if_fail (GLADE_IS_PREVIEW (preview), NULL);
354   return preview->priv->previewed_widget;
355 }
356 
357 GPid
glade_preview_get_pid(GladePreview * preview)358 glade_preview_get_pid (GladePreview *preview)
359 {
360   g_return_val_if_fail (GLADE_IS_PREVIEW (preview), 0);
361   return preview->priv->pid;
362 }
363