update trace control
[lttv.git] / trunk / lttv / lttv / modules / gui / tracecontrol / tracecontrol.c
1 /* This file is part of the Linux Trace Toolkit viewer
2 * Copyright (C) 2005 Mathieu Desnoyers
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License Version 2 as
6 * published by the Free Software Foundation;
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place - Suite 330, Boston,
16 * MA 02111-1307, USA.
17 */
18
19 #ifdef HAVE_CONFIG_H
20 #include <config.h>
21 #endif
22
23 #include <glib.h>
24 #include <glib/gprintf.h>
25 #include <string.h>
26 #include <gtk/gtk.h>
27 #include <gdk/gdk.h>
28 #include <gdk/gdkkeysyms.h>
29
30 #include <lttv/lttv.h>
31 #include <lttv/module.h>
32 #include <lttv/hook.h>
33
34 #include <lttvwindow/lttvwindow.h>
35 #include <lttvwindow/lttvwindowtraces.h>
36 #include <lttvwindow/callbacks.h>
37 #include <lttvwindow/lttv_plugin_tab.h>
38
39 #include "hTraceControlInsert.xpm"
40 #include "TraceControlStart.xpm"
41 #include "TraceControlPause.xpm"
42 #include "TraceControlStop.xpm"
43
44 #include <sys/types.h>
45 #include <unistd.h>
46 #include <stdlib.h>
47 #include <pty.h>
48 #include <utmp.h>
49 #include <sys/wait.h>
50 #include <sys/poll.h>
51 #include <errno.h>
52 #include <fcntl.h>
53 #include <sched.h>
54
55 #define MAX_ARGS_LEN PATH_MAX * 10
56
57 GSList *g_control_list = NULL ;
58
59 /*! \file lttv/modules/gui/tracecontrol/tracecontrol.c
60 * \brief Graphic trace start/stop control interface.
61 *
62 * This plugin interacts with lttctl to start/stop tracing. It needs to take the
63 * root password to be able to interact with lttctl.
64 *
65 */
66
67 typedef struct _ControlData ControlData;
68
69 /*
70 * Prototypes
71 */
72 GtkWidget *guicontrol_get_widget(ControlData *tcd);
73 ControlData *gui_control(LttvPluginTab *ptab);
74 void gui_control_destructor(ControlData *tcd);
75 GtkWidget* h_guicontrol(LttvPlugin *plugin);
76 void control_destroy_walk(gpointer data, gpointer user_data);
77
78 /*
79 * Callback functions
80 */
81
82 static void start_clicked (GtkButton *button, gpointer user_data);
83 static void pause_clicked (GtkButton *button, gpointer user_data);
84 static void unpause_clicked (GtkButton *button, gpointer user_data);
85 static void stop_clicked (GtkButton *button, gpointer user_data);
86
87
88 /**
89 * @struct _ControlData
90 *
91 * @brief Main structure of gui control
92 */
93 struct _ControlData {
94 Tab *tab; /**< current tab of module */
95
96 GtkWidget *window; /**< window */
97
98 GtkWidget *main_box; /**< main container */
99 GtkWidget *start_button;
100 GtkWidget *pause_button;
101 GtkWidget *unpause_button;
102 GtkWidget *stop_button;
103 GtkWidget *username_label;
104 GtkWidget *username_entry;
105 GtkWidget *password_label;
106 GtkWidget *password_entry;
107 GtkWidget *channel_dir_label;
108 GtkWidget *channel_dir_entry;
109 GtkWidget *trace_dir_label;
110 GtkWidget *trace_dir_entry;
111 GtkWidget *trace_name_label;
112 GtkWidget *trace_name_entry;
113 GtkWidget *trace_mode_label;
114 GtkWidget *trace_mode_combo;
115 GtkWidget *start_daemon_label;
116 GtkWidget *start_daemon_check;
117 GtkWidget *append_label;
118 GtkWidget *append_check;
119 GtkWidget *optional_label;
120 GtkWidget *subbuf_size_label;
121 GtkWidget *subbuf_size_entry;
122 GtkWidget *subbuf_num_label;
123 GtkWidget *subbuf_num_entry;
124 GtkWidget *lttd_threads_label;
125 GtkWidget *lttd_threads_entry;
126 GtkWidget *lttctl_path_label;
127 GtkWidget *lttctl_path_entry;
128 GtkWidget *lttd_path_label;
129 GtkWidget *lttd_path_entry;
130 };
131
132 /**
133 * @fn GtkWidget* guicontrol_get_widget(ControlData*)
134 *
135 * This function returns the current main widget
136 * used by this module
137 * @param tcd the module struct
138 * @return The main widget
139 */
140 GtkWidget*
141 guicontrol_get_widget(ControlData *tcd)
142 {
143 return tcd->window;
144 }
145
146 /**
147 * @fn ControlData* gui_control(Tab*)
148 *
149 * Constructor is used to create ControlData data structure.
150 * @param tab The tab structure used by the widget
151 * @return The Filter viewer data created.
152 */
153 ControlData*
154 gui_control(LttvPluginTab *ptab)
155 {
156 Tab *tab = ptab->tab;
157 g_debug("filter::gui_control()");
158
159 unsigned i;
160 GtkCellRenderer *renderer;
161 GtkTreeViewColumn *column;
162
163 ControlData* tcd = g_new(ControlData,1);
164
165 tcd->tab = tab;
166
167 tcd->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
168 gtk_window_set_title(GTK_WINDOW(tcd->window), "LTTng Trace Control");
169 /*
170 * Initiating GtkTable layout
171 * starts with 2 rows and 5 columns and
172 * expands when expressions added
173 */
174 tcd->main_box = gtk_table_new(14,7,FALSE);
175 gtk_table_set_row_spacings(GTK_TABLE(tcd->main_box),5);
176 gtk_table_set_col_spacings(GTK_TABLE(tcd->main_box),5);
177
178 gtk_container_add(GTK_CONTAINER(tcd->window), GTK_WIDGET(tcd->main_box));
179
180 GList *focus_chain = NULL;
181
182 /*
183 * start/pause/stop buttons
184 */
185 GdkPixbuf *pixbuf;
186 GtkWidget *image;
187 pixbuf = gdk_pixbuf_new_from_xpm_data((const char **)TraceControlStart_xpm);
188 image = gtk_image_new_from_pixbuf(pixbuf);
189 tcd->start_button = gtk_button_new_with_label("start");
190 //2.6 gtk_button_set_image(GTK_BUTTON(tcd->start_button), image);
191 g_object_set(G_OBJECT(tcd->start_button), "image", image, NULL);
192 gtk_button_set_alignment(GTK_BUTTON(tcd->start_button), 0.0, 0.0);
193 gtk_widget_show (tcd->start_button);
194 gtk_table_attach( GTK_TABLE(tcd->main_box),tcd->start_button,6,7,0,1,GTK_FILL,GTK_FILL,2,2);
195
196 pixbuf = gdk_pixbuf_new_from_xpm_data((const char **)TraceControlPause_xpm);
197 image = gtk_image_new_from_pixbuf(pixbuf);
198 tcd->pause_button = gtk_button_new_with_label("pause");
199 //2.6 gtk_button_set_image(GTK_BUTTON(tcd->pause_button), image);
200 g_object_set(G_OBJECT(tcd->pause_button), "image", image, NULL);
201 gtk_button_set_alignment(GTK_BUTTON(tcd->pause_button), 0.0, 0.0);
202 gtk_widget_show (tcd->pause_button);
203 gtk_table_attach( GTK_TABLE(tcd->main_box),tcd->pause_button,6,7,1,2,GTK_FILL,GTK_FILL,2,2);
204
205 pixbuf = gdk_pixbuf_new_from_xpm_data((const char **)TraceControlPause_xpm);
206 image = gtk_image_new_from_pixbuf(pixbuf);
207 tcd->unpause_button = gtk_button_new_with_label("unpause");
208 //2.6 gtk_button_set_image(GTK_BUTTON(tcd->unpause_button), image);
209 g_object_set(G_OBJECT(tcd->unpause_button), "image", image, NULL);
210 gtk_button_set_alignment(GTK_BUTTON(tcd->unpause_button), 0.0, 0.0);
211 gtk_widget_show (tcd->unpause_button);
212 gtk_table_attach( GTK_TABLE(tcd->main_box),tcd->unpause_button,6,7,2,3,GTK_FILL,GTK_FILL,2,2);
213
214 pixbuf = gdk_pixbuf_new_from_xpm_data((const char **)TraceControlStop_xpm);
215 image = gtk_image_new_from_pixbuf(pixbuf);
216 tcd->stop_button = gtk_button_new_with_label("stop");
217 //2.6 gtk_button_set_image(GTK_BUTTON(tcd->stop_button), image);
218 g_object_set(G_OBJECT(tcd->stop_button), "image", image, NULL);
219 gtk_button_set_alignment(GTK_BUTTON(tcd->stop_button), 0.0, 0.0);
220 gtk_widget_show (tcd->stop_button);
221 gtk_table_attach( GTK_TABLE(tcd->main_box),tcd->stop_button,6,7,3,4,GTK_FILL,GTK_FILL,2,2);
222
223 /*
224 * First half of the filter window
225 * - textual entry of filter expression
226 * - processing button
227 */
228 tcd->username_label = gtk_label_new("Username:");
229 gtk_widget_show (tcd->username_label);
230 tcd->username_entry = gtk_entry_new();
231 gtk_entry_set_text(GTK_ENTRY(tcd->username_entry),"root");
232 gtk_widget_show (tcd->username_entry);
233 gtk_table_attach( GTK_TABLE(tcd->main_box),tcd->username_label,0,2,0,1,GTK_FILL,GTK_FILL,2,2);
234 gtk_table_attach( GTK_TABLE(tcd->main_box),tcd->username_entry,2,6,0,1,GTK_FILL|GTK_EXPAND|GTK_SHRINK,GTK_FILL,0,0);
235
236
237
238 tcd->password_label = gtk_label_new("Password:");
239 gtk_widget_show (tcd->password_label);
240 tcd->password_entry = gtk_entry_new();
241 gtk_entry_set_visibility(GTK_ENTRY(tcd->password_entry), FALSE);
242 gtk_widget_show (tcd->password_entry);
243 gtk_table_attach( GTK_TABLE(tcd->main_box),tcd->password_label,0,2,1,2,GTK_FILL,GTK_FILL,2,2);
244 gtk_table_attach( GTK_TABLE(tcd->main_box),tcd->password_entry,2,6,1,2,GTK_FILL|GTK_EXPAND|GTK_SHRINK,GTK_FILL,0,0);
245
246
247 tcd->channel_dir_label = gtk_label_new("Channel directory:");
248 gtk_widget_show (tcd->channel_dir_label);
249 tcd->channel_dir_entry = gtk_entry_new();
250 gtk_entry_set_text(GTK_ENTRY(tcd->channel_dir_entry),"/mnt/debugfs/ltt");
251 gtk_widget_show (tcd->channel_dir_entry);
252 gtk_table_attach( GTK_TABLE(tcd->main_box),tcd->channel_dir_label,0,2,2,3,GTK_FILL,GTK_FILL,2,2);
253 gtk_table_attach( GTK_TABLE(tcd->main_box),tcd->channel_dir_entry,2,6,2,3,GTK_FILL|GTK_EXPAND|GTK_SHRINK,GTK_FILL,0,0);
254
255 tcd->trace_dir_label = gtk_label_new("Trace directory:");
256 gtk_widget_show (tcd->trace_dir_label);
257 tcd->trace_dir_entry = gtk_entry_new();
258 gtk_entry_set_text(GTK_ENTRY(tcd->trace_dir_entry),"/tmp/trace1");
259 gtk_widget_show (tcd->trace_dir_entry);
260 gtk_table_attach( GTK_TABLE(tcd->main_box),tcd->trace_dir_label,0,2,3,4,GTK_FILL,GTK_FILL,2,2);
261 gtk_table_attach( GTK_TABLE(tcd->main_box),tcd->trace_dir_entry,2,6,3,4,GTK_FILL|GTK_EXPAND|GTK_SHRINK,GTK_FILL,0,0);
262
263 tcd->trace_name_label = gtk_label_new("Trace name:");
264 gtk_widget_show (tcd->trace_name_label);
265 tcd->trace_name_entry = gtk_entry_new();
266 gtk_entry_set_text(GTK_ENTRY(tcd->trace_name_entry),"trace");
267 gtk_widget_show (tcd->trace_name_entry);
268 gtk_table_attach( GTK_TABLE(tcd->main_box),tcd->trace_name_label,0,2,4,5,GTK_FILL,GTK_FILL,2,2);
269 gtk_table_attach( GTK_TABLE(tcd->main_box),tcd->trace_name_entry,2,6,4,5,GTK_FILL|GTK_EXPAND|GTK_SHRINK,GTK_FILL,0,0);
270
271 tcd->trace_mode_label = gtk_label_new("Trace mode ");
272 gtk_widget_show (tcd->trace_mode_label);
273 tcd->trace_mode_combo = gtk_combo_box_new_text();
274 gtk_combo_box_append_text(GTK_COMBO_BOX(tcd->trace_mode_combo),
275 "normal");
276 gtk_combo_box_append_text(GTK_COMBO_BOX(tcd->trace_mode_combo),
277 "flight recorder");
278 gtk_combo_box_set_active(GTK_COMBO_BOX(tcd->trace_mode_combo), 0);
279 gtk_widget_show (tcd->trace_mode_combo);
280 gtk_table_attach( GTK_TABLE(tcd->main_box),tcd->trace_mode_label,0,2,5,6,GTK_FILL,GTK_FILL,2,2);
281 gtk_table_attach( GTK_TABLE(tcd->main_box),tcd->trace_mode_combo,2,6,5,6,GTK_FILL|GTK_EXPAND|GTK_SHRINK,GTK_FILL,0,0);
282
283 tcd->start_daemon_label = gtk_label_new("Start daemon ");
284 gtk_widget_show (tcd->start_daemon_label);
285 tcd->start_daemon_check = gtk_check_button_new();
286 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tcd->start_daemon_check), TRUE);
287 gtk_widget_show (tcd->start_daemon_check);
288 gtk_table_attach( GTK_TABLE(tcd->main_box),tcd->start_daemon_label,0,2,6,7,GTK_FILL,GTK_FILL,2,2);
289 gtk_table_attach( GTK_TABLE(tcd->main_box),tcd->start_daemon_check,2,6,6,7,GTK_FILL,GTK_FILL,0,0);
290
291 tcd->append_label = gtk_label_new("Append to trace ");
292 gtk_widget_show (tcd->append_label);
293 tcd->append_check = gtk_check_button_new();
294 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tcd->append_check), FALSE);
295 gtk_widget_show (tcd->append_check);
296 gtk_table_attach( GTK_TABLE(tcd->main_box),tcd->append_label,0,2,7,8,GTK_FILL,GTK_FILL,2,2);
297 gtk_table_attach( GTK_TABLE(tcd->main_box),tcd->append_check,2,6,7,8,GTK_FILL,GTK_FILL,0,0);
298
299
300 tcd->optional_label = gtk_label_new("Optional fields ");
301 gtk_widget_show (tcd->optional_label);
302 gtk_table_attach( GTK_TABLE(tcd->main_box),tcd->optional_label,0,6,8,9,GTK_FILL,GTK_FILL,2,2);
303
304 tcd->subbuf_size_label = gtk_label_new("Subbuffer size:");
305 gtk_widget_show (tcd->subbuf_size_label);
306 tcd->subbuf_size_entry = gtk_entry_new();
307 gtk_widget_show (tcd->subbuf_size_entry);
308 gtk_table_attach( GTK_TABLE(tcd->main_box),tcd->subbuf_size_label,0,2,9,10,GTK_FILL,GTK_FILL,2,2);
309 gtk_table_attach( GTK_TABLE(tcd->main_box),tcd->subbuf_size_entry,2,6,9,10,GTK_FILL|GTK_EXPAND|GTK_SHRINK,GTK_FILL,0,0);
310
311 tcd->subbuf_num_label = gtk_label_new("Number of subbuffers:");
312 gtk_widget_show (tcd->subbuf_num_label);
313 tcd->subbuf_num_entry = gtk_entry_new();
314 gtk_widget_show (tcd->subbuf_num_entry);
315 gtk_table_attach( GTK_TABLE(tcd->main_box),tcd->subbuf_num_label,0,2,10,11,GTK_FILL,GTK_FILL,2,2);
316 gtk_table_attach( GTK_TABLE(tcd->main_box),tcd->subbuf_num_entry,2,6,10,11,GTK_FILL|GTK_EXPAND|GTK_SHRINK,GTK_FILL,0,0);
317
318 tcd->lttd_threads_label = gtk_label_new("Number of lttd threads:");
319 gtk_widget_show (tcd->lttd_threads_label);
320 tcd->lttd_threads_entry = gtk_entry_new();
321 gtk_entry_set_text(GTK_ENTRY(tcd->lttd_threads_entry), "1");
322 gtk_widget_show (tcd->lttd_threads_entry);
323 gtk_table_attach( GTK_TABLE(tcd->main_box),tcd->lttd_threads_label,0,2,11,12,GTK_FILL,GTK_FILL,2,2);
324 gtk_table_attach( GTK_TABLE(tcd->main_box),tcd->lttd_threads_entry,2,6,11,12,GTK_FILL|GTK_EXPAND|GTK_SHRINK,GTK_FILL,0,0);
325
326 tcd->lttctl_path_label = gtk_label_new("path to lttctl:");
327 gtk_widget_show (tcd->lttctl_path_label);
328 tcd->lttctl_path_entry = gtk_entry_new();
329 gtk_entry_set_text(GTK_ENTRY(tcd->lttctl_path_entry),PACKAGE_BIN_DIR "/lttctl");
330 gtk_widget_show (tcd->lttctl_path_entry);
331 gtk_table_attach( GTK_TABLE(tcd->main_box),tcd->lttctl_path_label,0,2,12,13,GTK_FILL,GTK_FILL,2,2);
332 gtk_table_attach( GTK_TABLE(tcd->main_box),tcd->lttctl_path_entry,2,6,12,13,GTK_FILL|GTK_EXPAND|GTK_SHRINK,GTK_FILL,0,0);
333
334
335 tcd->lttd_path_label = gtk_label_new("path to lttd:");
336 gtk_widget_show (tcd->lttd_path_label);
337 tcd->lttd_path_entry = gtk_entry_new();
338 gtk_entry_set_text(GTK_ENTRY(tcd->lttd_path_entry),PACKAGE_BIN_DIR "/lttd");
339 gtk_widget_show (tcd->lttd_path_entry);
340 gtk_table_attach( GTK_TABLE(tcd->main_box),tcd->lttd_path_label,0,2,13,14,GTK_FILL,GTK_FILL,2,2);
341 gtk_table_attach( GTK_TABLE(tcd->main_box),tcd->lttd_path_entry,2,6,13,14,GTK_FILL|GTK_EXPAND|GTK_SHRINK,GTK_FILL,0,0);
342
343
344 focus_chain = g_list_append (focus_chain, tcd->username_entry);
345 focus_chain = g_list_append (focus_chain, tcd->password_entry);
346 focus_chain = g_list_append (focus_chain, tcd->start_button);
347 focus_chain = g_list_append (focus_chain, tcd->pause_button);
348 focus_chain = g_list_append (focus_chain, tcd->unpause_button);
349 focus_chain = g_list_append (focus_chain, tcd->stop_button);
350 focus_chain = g_list_append (focus_chain, tcd->channel_dir_entry);
351 focus_chain = g_list_append (focus_chain, tcd->trace_dir_entry);
352 focus_chain = g_list_append (focus_chain, tcd->trace_name_entry);
353 focus_chain = g_list_append (focus_chain, tcd->trace_mode_combo);
354 focus_chain = g_list_append (focus_chain, tcd->start_daemon_check);
355 focus_chain = g_list_append (focus_chain, tcd->append_check);
356 focus_chain = g_list_append (focus_chain, tcd->subbuf_size_entry);
357 focus_chain = g_list_append (focus_chain, tcd->subbuf_num_entry);
358 focus_chain = g_list_append (focus_chain, tcd->lttd_threads_entry);
359 focus_chain = g_list_append (focus_chain, tcd->lttctl_path_entry);
360 focus_chain = g_list_append (focus_chain, tcd->lttd_path_entry);
361
362 gtk_container_set_focus_chain(GTK_CONTAINER(tcd->main_box), focus_chain);
363
364 g_list_free(focus_chain);
365
366 g_signal_connect(G_OBJECT(tcd->start_button), "clicked",
367 (GCallback)start_clicked, tcd);
368 g_signal_connect(G_OBJECT(tcd->pause_button), "clicked",
369 (GCallback)pause_clicked, tcd);
370 g_signal_connect(G_OBJECT(tcd->unpause_button), "clicked",
371 (GCallback)unpause_clicked, tcd);
372 g_signal_connect(G_OBJECT(tcd->stop_button), "clicked",
373 (GCallback)stop_clicked, tcd);
374
375 /*
376 * show main container
377 */
378 gtk_widget_show(tcd->main_box);
379 gtk_widget_show(tcd->window);
380
381
382 g_object_set_data_full(
383 G_OBJECT(guicontrol_get_widget(tcd)),
384 "control_viewer_data",
385 tcd,
386 (GDestroyNotify)gui_control_destructor);
387
388 g_control_list = g_slist_append(
389 g_control_list,
390 tcd);
391
392 return tcd;
393 }
394
395
396 /**
397 * @fn void gui_control_destructor(ControlData*)
398 *
399 * Destructor for the filter gui module
400 * @param tcd The module structure
401 */
402 void
403 gui_control_destructor(ControlData *tcd)
404 {
405 Tab *tab = tcd->tab;
406
407 /* May already been done by GTK window closing */
408 if(GTK_IS_WIDGET(guicontrol_get_widget(tcd))){
409 g_info("widget still exists");
410 }
411 // if(tab != NULL) {
412 // lttvwindow_unregister_traceset_notify(tcd->tab,
413 // filter_traceset_changed,
414 // filter_viewer_data);
415 // }
416 lttvwindowtraces_background_notify_remove(tcd);
417
418 g_control_list = g_slist_remove(g_control_list, tcd);
419
420 g_free(tcd);
421 }
422
423 static int execute_command(const gchar *command, const gchar *username,
424 const gchar *password, const gchar *lttd_path)
425 {
426 pid_t pid;
427 int fdpty;
428 pid = forkpty(&fdpty, NULL, NULL, NULL);
429 int retval = 0;
430
431 if(pid > 0) {
432 /* parent */
433 gchar buf[256];
434 int status;
435 ssize_t count;
436 /* discuss with su */
437 struct timeval timeout;
438 timeout.tv_sec = 1;
439 timeout.tv_usec = 0;
440
441 struct pollfd pollfd;
442 int num_rdy;
443 int num_hup = 0;
444 enum read_state { GET_LINE, GET_SEMI, GET_SPACE } read_state = GET_LINE;
445
446 retval = fcntl(fdpty, F_SETFL, O_WRONLY);
447 if(retval == -1) {
448 perror("Error in fcntl");
449 goto wait_child;
450 }
451
452 /* Read the output from the child terminal before the prompt. If no data in
453 * 200 ms, we stop reading to give the password */
454 g_info("Reading from child console...");
455 while(1) {
456 pollfd.fd = fdpty;
457 pollfd.events = POLLIN|POLLPRI|POLLERR|POLLHUP|POLLNVAL;
458
459 num_rdy = poll(&pollfd, 1, -1);
460 if(num_rdy == -1) {
461 perror("Poll error");
462 goto wait_child;
463 }
464
465 /* Timeout : Stop waiting for chars */
466 if(num_rdy == 0) goto wait_child;
467
468 switch(pollfd.revents) {
469 case POLLERR:
470 g_warning("Error returned in polling fd\n");
471 num_hup++;
472 break;
473 case POLLHUP:
474 g_info("Polling FD : hung up.");
475 num_hup++;
476 break;
477 case POLLNVAL:
478 g_warning("Polling fd tells it is not open");
479 num_hup++;
480 break;
481 case POLLPRI:
482 case POLLIN:
483 count = read (fdpty, buf, 256);
484 if(count > 0) {
485 unsigned int i;
486 buf[count] = '\0';
487 g_printf("%s", buf);
488 for(i=0; i<count; i++) {
489 switch(read_state) {
490 case GET_LINE:
491 if(buf[i] == '\n') {
492 read_state = GET_SEMI;
493 g_debug("Tracecontrol input line skip\n");
494 }
495 break;
496 case GET_SEMI:
497 if(buf[i] == ':') {
498 g_debug("Tracecontrol input : marker found\n");
499 read_state = GET_SPACE;
500 }
501 break;
502 case GET_SPACE:
503 if(buf[i] == ' ') {
504 g_debug("Tracecontrol input space marker found\n");
505 goto write_password;
506 }
507 break;
508 }
509 }
510 } else if(count == -1) {
511 perror("Error in read");
512 goto wait_child;
513 }
514 break;
515 }
516 if(num_hup > 0) {
517 g_warning("Child hung up too fast");
518 goto wait_child;
519 }
520 }
521 write_password:
522 fsync(fdpty);
523 pollfd.fd = fdpty;
524 pollfd.events = POLLOUT|POLLERR|POLLHUP|POLLNVAL;
525
526 num_rdy = poll(&pollfd, 1, -1);
527 if(num_rdy == -1) {
528 perror("Poll error");
529 goto wait_child;
530 }
531
532 /* Write the password */
533 g_info("Got su prompt, now writing password...");
534 int ret;
535 sleep(1);
536 ret = write(fdpty, password, strlen(password));
537 if(ret < 0) perror("Error in write");
538 ret = write(fdpty, "\n", 1);
539 if(ret < 0) perror("Error in write");
540 fsync(fdpty);
541 /* Take the output from the terminal and show it on the real console */
542 g_info("Getting data from child terminal...");
543 while(1) {
544 int num_hup = 0;
545 pollfd.fd = fdpty;
546 pollfd.events = POLLIN|POLLPRI|POLLERR|POLLHUP|POLLNVAL;
547
548 num_rdy = poll(&pollfd, 1, -1);
549 if(num_rdy == -1) {
550 perror("Poll error");
551 goto wait_child;
552 }
553 if(num_rdy == 0) break;
554
555 if(pollfd.revents & (POLLERR|POLLNVAL)) {
556 g_warning("Error returned in polling fd\n");
557 num_hup++;
558 }
559
560 if(pollfd.revents & (POLLIN|POLLPRI) ) {
561 count = read (fdpty, buf, 256);
562 if(count > 0) {
563 buf[count] = '\0';
564 printf("%s", buf);
565 } else if(count == -1) {
566 perror("Error in read");
567 goto wait_child;
568 }
569 }
570
571 if(pollfd.revents & POLLHUP) {
572 g_info("Polling FD : hung up.");
573 num_hup++;
574 }
575
576 if(num_hup > 0) goto wait_child;
577 }
578 wait_child:
579 g_info("Waiting for child exit...");
580
581 ret = waitpid(pid, &status, 0);
582
583 if(ret == -1) {
584 g_warning("An error occured in wait : %s",
585 strerror(errno));
586 } else {
587 if(WIFEXITED(status))
588 if(WEXITSTATUS(status) != 0) {
589 retval = WEXITSTATUS(status);
590 g_warning("An error occured in the su command : %s",
591 strerror(retval));
592 }
593 }
594
595 g_info("Child exited.");
596
597 } else if(pid == 0) {
598 /* Setup environment variables */
599 if(strcmp(lttd_path, "") != 0)
600 setenv("LTT_DAEMON", lttd_path, 1);
601
602 /* One comment line (must be only one) */
603 g_printf("Executing (as %s) : %s\n", username, command);
604
605 execlp("su", "su", "-p", "-c", command, username, NULL);
606 exit(-1); /* not supposed to happen! */
607
608 //gint ret = execvp();
609
610 } else {
611 /* error */
612 g_warning("Error happened when forking for su");
613 }
614
615 return retval;
616 }
617
618
619 /* Callbacks */
620
621 void start_clicked (GtkButton *button, gpointer user_data)
622 {
623 ControlData *tcd = (ControlData*)user_data;
624
625 const gchar *username = gtk_entry_get_text(GTK_ENTRY(tcd->username_entry));
626 const gchar *password = gtk_entry_get_text(GTK_ENTRY(tcd->password_entry));
627 const gchar *channel_dir =
628 gtk_entry_get_text(GTK_ENTRY(tcd->channel_dir_entry));
629 const gchar *trace_dir = gtk_entry_get_text(GTK_ENTRY(tcd->trace_dir_entry));
630 const gchar *trace_name =
631 gtk_entry_get_text(GTK_ENTRY(tcd->trace_name_entry));
632
633 const gchar *trace_mode_sel;
634 GtkTreeIter iter;
635
636 gtk_combo_box_get_active_iter(GTK_COMBO_BOX(tcd->trace_mode_combo), &iter);
637 gtk_tree_model_get(
638 gtk_combo_box_get_model(GTK_COMBO_BOX(tcd->trace_mode_combo)),
639 &iter, 0, &trace_mode_sel, -1);
640 //const gchar *trace_mode_sel =
641 //2.6+ gtk_combo_box_get_active_text(GTK_COMBO_BOX(tcd->trace_mode_combo));
642 const gchar *trace_mode;
643 if(strcmp(trace_mode_sel, "normal") == 0)
644 trace_mode = "normal";
645 else
646 trace_mode = "flight";
647
648 gboolean start_daemon =
649 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(tcd->start_daemon_check));
650
651 gboolean append =
652 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(tcd->append_check));
653
654 const gchar *subbuf_size =
655 gtk_entry_get_text(GTK_ENTRY(tcd->subbuf_size_entry));
656 const gchar *subbuf_num =
657 gtk_entry_get_text(GTK_ENTRY(tcd->subbuf_num_entry));
658 const gchar *threads_num =
659 gtk_entry_get_text(GTK_ENTRY(tcd->lttd_threads_entry));
660 const gchar *lttctl_path =
661 gtk_entry_get_text(GTK_ENTRY(tcd->lttctl_path_entry));
662 const gchar *lttd_path = gtk_entry_get_text(GTK_ENTRY(tcd->lttd_path_entry));
663
664 /* Setup arguments to su */
665 /* child */
666 gchar args[MAX_ARGS_LEN];
667 gint args_left = MAX_ARGS_LEN - 1; /* for \0 */
668
669 args[0] = '\0';
670
671 /* Command */
672 strncat(args, "exec", args_left);
673 args_left = MAX_ARGS_LEN - strlen(args) - 1;
674
675 /* space */
676 strncat(args, " ", args_left);
677 args_left = MAX_ARGS_LEN - strlen(args) - 1;
678
679 if(strcmp(lttctl_path, "") == 0)
680 strncat(args, "lttctl", args_left);
681 else
682 strncat(args, lttctl_path, args_left);
683 args_left = MAX_ARGS_LEN - strlen(args) - 1;
684
685 /* space */
686 strncat(args, " ", args_left);
687 args_left = MAX_ARGS_LEN - strlen(args) - 1;
688
689 /* Start daemon ? */
690 if(start_daemon) {
691 strncat(args, "-C", args_left);
692 args_left = MAX_ARGS_LEN - strlen(args) - 1;
693 } else {
694 /* Simply create the channel and then start tracing */
695 //strncat(args, "-b", args_left);
696 //args_left = MAX_ARGS_LEN - strlen(args) - 1;
697 }
698
699 /* space */
700 strncat(args, " ", args_left);
701 args_left = MAX_ARGS_LEN - strlen(args) - 1;
702
703 /* channel dir */
704 strncat(args, "--channel_root ", args_left);
705 args_left = MAX_ARGS_LEN - strlen(args) - 1;
706 strncat(args, channel_dir, args_left);
707 args_left = MAX_ARGS_LEN - strlen(args) - 1;
708
709 /* space */
710 strncat(args, " ", args_left);
711 args_left = MAX_ARGS_LEN - strlen(args) - 1;
712
713 /* trace dir */
714 strncat(args, "-w ", args_left);
715 args_left = MAX_ARGS_LEN - strlen(args) - 1;
716 strncat(args, trace_dir, args_left);
717 args_left = MAX_ARGS_LEN - strlen(args) - 1;
718
719 /* space */
720 strncat(args, " ", args_left);
721 args_left = MAX_ARGS_LEN - strlen(args) - 1;
722
723 if(strcmp(trace_mode, "flight") == 0) {
724 strncat(args, "-o channel.all.overwrite=1", args_left);
725 args_left = MAX_ARGS_LEN - strlen(args) - 1;
726 } else {
727 strncat(args, "-o channel.all.overwrite=0", args_left);
728 args_left = MAX_ARGS_LEN - strlen(args) - 1;
729 }
730
731 /* Append to trace ? */
732 if(append) {
733 /* space */
734 strncat(args, " ", args_left);
735 args_left = MAX_ARGS_LEN - strlen(args) - 1;
736 strncat(args, "-a", args_left);
737 args_left = MAX_ARGS_LEN - strlen(args) - 1;
738 }
739
740 /* optional arguments */
741 /* subbuffer size */
742 if(strcmp(subbuf_size, "") != 0) {
743 /* space */
744 strncat(args, " ", args_left);
745 args_left = MAX_ARGS_LEN - strlen(args) - 1;
746
747 strncat(args, "-o channel.all.bufsize=", args_left);
748 args_left = MAX_ARGS_LEN - strlen(args) - 1;
749 strncat(args, subbuf_size, args_left);
750 args_left = MAX_ARGS_LEN - strlen(args) - 1;
751 }
752
753 /* number of subbuffers */
754 if(strcmp(subbuf_num, "") != 0) {
755 /* space */
756 strncat(args, " ", args_left);
757 args_left = MAX_ARGS_LEN - strlen(args) - 1;
758
759 strncat(args, "-o channel.all.bufnum=", args_left);
760 args_left = MAX_ARGS_LEN - strlen(args) - 1;
761 strncat(args, subbuf_num, args_left);
762 args_left = MAX_ARGS_LEN - strlen(args) - 1;
763 }
764
765 /* number of lttd threads */
766 if(strcmp(threads_num, "") != 0) {
767 /* space */
768 strncat(args, " ", args_left);
769 args_left = MAX_ARGS_LEN - strlen(args) - 1;
770
771 strncat(args, "-n ", args_left);
772 args_left = MAX_ARGS_LEN - strlen(args) - 1;
773 strncat(args, threads_num, args_left);
774 args_left = MAX_ARGS_LEN - strlen(args) - 1;
775 }
776
777 /* space */
778 strncat(args, " ", args_left);
779 args_left = MAX_ARGS_LEN - strlen(args) - 1;
780
781 /* name */
782 strncat(args, trace_name, args_left);
783 args_left = MAX_ARGS_LEN - strlen(args) - 1;
784
785 int retval = execute_command(args, username, password, lttd_path);
786
787 if(retval) {
788 gchar msg[256];
789 guint msg_left = 256;
790
791 strcpy(msg, "A problem occured when executing the su command : ");
792 msg_left = 256 - strlen(msg) - 1;
793 strncat(msg, strerror(retval), msg_left);
794 GtkWidget *dialogue =
795 gtk_message_dialog_new(
796 GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(button))),
797 GTK_DIALOG_MODAL|GTK_DIALOG_DESTROY_WITH_PARENT,
798 GTK_MESSAGE_ERROR,
799 GTK_BUTTONS_OK,
800 msg);
801 gtk_dialog_run(GTK_DIALOG(dialogue));
802 gtk_widget_destroy(dialogue);
803 }
804
805 }
806
807
808 void pause_clicked (GtkButton *button, gpointer user_data)
809 {
810 ControlData *tcd = (ControlData*)user_data;
811
812 const gchar *username = gtk_entry_get_text(GTK_ENTRY(tcd->username_entry));
813 const gchar *password = gtk_entry_get_text(GTK_ENTRY(tcd->password_entry));
814 const gchar *trace_name =
815 gtk_entry_get_text(GTK_ENTRY(tcd->trace_name_entry));
816 const gchar *lttd_path = "";
817
818 const gchar *lttctl_path =
819 gtk_entry_get_text(GTK_ENTRY(tcd->lttctl_path_entry));
820
821 /* Setup arguments to su */
822 /* child */
823 gchar args[MAX_ARGS_LEN];
824 gint args_left = MAX_ARGS_LEN - 1; /* for \0 */
825
826 args[0] = '\0';
827
828 /* Command */
829 strncat(args, "exec", args_left);
830 args_left = MAX_ARGS_LEN - strlen(args) - 1;
831
832 /* space */
833 strncat(args, " ", args_left);
834 args_left = MAX_ARGS_LEN - strlen(args) - 1;
835
836 if(strcmp(lttctl_path, "") == 0)
837 strncat(args, "lttctl", args_left);
838 else
839 strncat(args, lttctl_path, args_left);
840 args_left = MAX_ARGS_LEN - strlen(args) - 1;
841
842 /* space */
843 strncat(args, " ", args_left);
844 args_left = MAX_ARGS_LEN - strlen(args) - 1;
845
846 /* Simply pause tracing */
847 strncat(args, "-p", args_left);
848 args_left = MAX_ARGS_LEN - strlen(args) - 1;
849
850 /* space */
851 strncat(args, " ", args_left);
852 args_left = MAX_ARGS_LEN - strlen(args) - 1;
853
854 /* name */
855 strncat(args, trace_name, args_left);
856 args_left = MAX_ARGS_LEN - strlen(args) - 1;
857
858 int retval = execute_command(args, username, password, lttd_path);
859 if(retval) {
860 gchar msg[256];
861 guint msg_left = 256;
862
863 strcpy(msg, "A problem occured when executing the su command : ");
864 msg_left = 256 - strlen(msg) - 1;
865 strncat(msg, strerror(retval), msg_left);
866 GtkWidget *dialogue =
867 gtk_message_dialog_new(
868 GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(button))),
869 GTK_DIALOG_MODAL|GTK_DIALOG_DESTROY_WITH_PARENT,
870 GTK_MESSAGE_ERROR,
871 GTK_BUTTONS_OK,
872 msg);
873 gtk_dialog_run(GTK_DIALOG(dialogue));
874 gtk_widget_destroy(dialogue);
875 }
876
877 }
878
879 void unpause_clicked (GtkButton *button, gpointer user_data)
880 {
881 ControlData *tcd = (ControlData*)user_data;
882
883 const gchar *username = gtk_entry_get_text(GTK_ENTRY(tcd->username_entry));
884 const gchar *password = gtk_entry_get_text(GTK_ENTRY(tcd->password_entry));
885 const gchar *trace_name =
886 gtk_entry_get_text(GTK_ENTRY(tcd->trace_name_entry));
887 const gchar *lttd_path = "";
888
889 const gchar *lttctl_path =
890 gtk_entry_get_text(GTK_ENTRY(tcd->lttctl_path_entry));
891
892 /* Setup arguments to su */
893 /* child */
894 gchar args[MAX_ARGS_LEN];
895 gint args_left = MAX_ARGS_LEN - 1; /* for \0 */
896
897 args[0] = '\0';
898
899 /* Command */
900 strncat(args, "exec", args_left);
901 args_left = MAX_ARGS_LEN - strlen(args) - 1;
902
903 /* space */
904 strncat(args, " ", args_left);
905 args_left = MAX_ARGS_LEN - strlen(args) - 1;
906
907 if(strcmp(lttctl_path, "") == 0)
908 strncat(args, "lttctl", args_left);
909 else
910 strncat(args, lttctl_path, args_left);
911 args_left = MAX_ARGS_LEN - strlen(args) - 1;
912
913 /* space */
914 strncat(args, " ", args_left);
915 args_left = MAX_ARGS_LEN - strlen(args) - 1;
916
917 /* Simply unpause tracing */
918 strncat(args, "-s", args_left);
919 args_left = MAX_ARGS_LEN - strlen(args) - 1;
920
921 /* space */
922 strncat(args, " ", args_left);
923 args_left = MAX_ARGS_LEN - strlen(args) - 1;
924
925 /* name */
926 strncat(args, trace_name, args_left);
927 args_left = MAX_ARGS_LEN - strlen(args) - 1;
928
929 int retval = execute_command(args, username, password, lttd_path);
930 if(retval) {
931 gchar msg[256];
932 guint msg_left = 256;
933
934 strcpy(msg, "A problem occured when executing the su command : ");
935 msg_left = 256 - strlen(msg) - 1;
936 strncat(msg, strerror(retval), msg_left);
937 GtkWidget *dialogue =
938 gtk_message_dialog_new(
939 GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(button))),
940 GTK_DIALOG_MODAL|GTK_DIALOG_DESTROY_WITH_PARENT,
941 GTK_MESSAGE_ERROR,
942 GTK_BUTTONS_OK,
943 msg);
944 gtk_dialog_run(GTK_DIALOG(dialogue));
945 gtk_widget_destroy(dialogue);
946 }
947
948 }
949
950 void stop_clicked (GtkButton *button, gpointer user_data)
951 {
952 ControlData *tcd = (ControlData*)user_data;
953
954 const gchar *username = gtk_entry_get_text(GTK_ENTRY(tcd->username_entry));
955 const gchar *password = gtk_entry_get_text(GTK_ENTRY(tcd->password_entry));
956 const gchar *trace_name =
957 gtk_entry_get_text(GTK_ENTRY(tcd->trace_name_entry));
958 const gchar *lttd_path = "";
959 const gchar *trace_mode;
960 const gchar *trace_mode_sel;
961 GtkTreeIter iter;
962
963 gtk_combo_box_get_active_iter(GTK_COMBO_BOX(tcd->trace_mode_combo), &iter);
964 gtk_tree_model_get(
965 gtk_combo_box_get_model(GTK_COMBO_BOX(tcd->trace_mode_combo)),
966 &iter, 0, &trace_mode_sel, -1);
967 if(strcmp(trace_mode_sel, "normal") == 0)
968 trace_mode = "normal";
969 else
970 trace_mode = "flight";
971
972 const gchar *lttctl_path =
973 gtk_entry_get_text(GTK_ENTRY(tcd->lttctl_path_entry));
974 gchar *trace_dir = gtk_entry_get_text(GTK_ENTRY(tcd->trace_dir_entry));
975 GSList * trace_list = NULL;
976
977 trace_list = g_slist_append(trace_list, trace_dir);
978
979 /* Setup arguments to su */
980 /* child */
981 gchar args[MAX_ARGS_LEN];
982 gint args_left = MAX_ARGS_LEN - 1; /* for \0 */
983
984 args[0] = '\0';
985
986 /* Command */
987 strncat(args, "exec", args_left);
988 args_left = MAX_ARGS_LEN - strlen(args) - 1;
989
990 /* space */
991 strncat(args, " ", args_left);
992 args_left = MAX_ARGS_LEN - strlen(args) - 1;
993
994 if(strcmp(lttctl_path, "") == 0)
995 strncat(args, "lttctl", args_left);
996 else
997 strncat(args, lttctl_path, args_left);
998 args_left = MAX_ARGS_LEN - strlen(args) - 1;
999
1000 /* space */
1001 strncat(args, " ", args_left);
1002 args_left = MAX_ARGS_LEN - strlen(args) - 1;
1003
1004 /* Simply stop tracing and destroy channel */
1005 strncat(args, "-D", args_left);
1006 args_left = MAX_ARGS_LEN - strlen(args) - 1;
1007
1008 if(strcmp(trace_mode, "flight") == 0) {
1009 /* space */
1010 strncat(args, " ", args_left);
1011 args_left = MAX_ARGS_LEN - strlen(args) - 1;
1012
1013 /* trace dir */
1014 strncat(args, "-w ", args_left);
1015 args_left = MAX_ARGS_LEN - strlen(args) - 1;
1016 strncat(args, trace_dir, args_left);
1017 args_left = MAX_ARGS_LEN - strlen(args) - 1;
1018 }
1019
1020 /* space */
1021 strncat(args, " ", args_left);
1022 args_left = MAX_ARGS_LEN - strlen(args) - 1;
1023
1024 /* name */
1025 strncat(args, trace_name, args_left);
1026 args_left = MAX_ARGS_LEN - strlen(args) - 1;
1027
1028 int retval = execute_command(args, username, password, lttd_path);
1029 if(retval) {
1030 gchar msg[256];
1031 guint msg_left = 256;
1032
1033 strcpy(msg, "A problem occured when executing the su command : ");
1034 msg_left = 256 - strlen(msg) - 1;
1035 strncat(msg, strerror(retval), msg_left);
1036 GtkWidget *dialogue =
1037 gtk_message_dialog_new(
1038 GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(button))),
1039 GTK_DIALOG_MODAL|GTK_DIALOG_DESTROY_WITH_PARENT,
1040 GTK_MESSAGE_ERROR,
1041 GTK_BUTTONS_OK,
1042 msg);
1043 gtk_dialog_run(GTK_DIALOG(dialogue));
1044 gtk_widget_destroy(dialogue);
1045 return;
1046 }
1047
1048
1049 /* Ask to the user if he wants to open the trace in a new window */
1050 GtkWidget *dialogue;
1051 GtkWidget *label;
1052 gint id;
1053
1054 dialogue = gtk_dialog_new_with_buttons("Open trace ?",
1055 GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(button))),
1056 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
1057 GTK_STOCK_YES,GTK_RESPONSE_ACCEPT,
1058 GTK_STOCK_NO,GTK_RESPONSE_REJECT,
1059 NULL);
1060 label = gtk_label_new("Do you want to open the trace in LTTV ?");
1061 gtk_widget_show(label);
1062
1063 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialogue)->vbox),
1064 label);
1065
1066 id = gtk_dialog_run(GTK_DIALOG(dialogue));
1067
1068 switch(id){
1069 case GTK_RESPONSE_ACCEPT:
1070 {
1071 create_main_window_with_trace_list(trace_list);
1072 }
1073 break;
1074 case GTK_RESPONSE_REJECT:
1075 default:
1076 break;
1077 }
1078 gtk_widget_destroy(dialogue);
1079 g_slist_free(trace_list);
1080 }
1081
1082
1083 /**
1084 * @fn GtkWidget* h_guicontrol(Tab*)
1085 *
1086 * Control Module's constructor hook
1087 *
1088 * This constructor is given as a parameter to the menuitem and toolbar button
1089 * registration. It creates the list.
1090 * @param tab A pointer to the parent window.
1091 * @return The widget created.
1092 */
1093 GtkWidget *
1094 h_guicontrol(LttvPlugin *plugin)
1095 {
1096 LttvPluginTab *ptab = LTTV_PLUGIN_TAB(plugin);
1097 ControlData* f = gui_control(ptab);
1098
1099 return NULL;
1100 }
1101
1102 /**
1103 * @fn static void init()
1104 *
1105 * This function initializes the Filter Viewer functionnality through the
1106 * gtkTraceSet API.
1107 */
1108 static void init() {
1109
1110 lttvwindow_register_constructor("guicontrol",
1111 "/",
1112 "Insert Tracing Control Module",
1113 hTraceControlInsert_xpm,
1114 "Insert Tracing Control Module",
1115 h_guicontrol);
1116 }
1117
1118 /**
1119 * @fn void control_destroy_walk(gpointer,gpointer)
1120 *
1121 * Initiate the destruction of the current gui module
1122 * on the GTK Interface
1123 */
1124 void
1125 control_destroy_walk(gpointer data, gpointer user_data)
1126 {
1127 ControlData *tcd = (ControlData*)data;
1128
1129 g_debug("traceontrol.c : control_destroy_walk, %p", tcd);
1130
1131 /* May already have been done by GTK window closing */
1132 if(GTK_IS_WIDGET(guicontrol_get_widget(tcd)))
1133 gtk_widget_destroy(guicontrol_get_widget(tcd));
1134 }
1135
1136 /**
1137 * @fn static void destroy()
1138 * @brief plugin's destroy function
1139 *
1140 * This function releases the memory reserved by the module and unregisters
1141 * everything that has been registered in the gtkTraceSet API.
1142 */
1143 static void destroy() {
1144
1145 g_slist_foreach(g_control_list, control_destroy_walk, NULL );
1146
1147 lttvwindow_unregister_constructor(h_guicontrol);
1148
1149 }
1150
1151
1152 LTTV_MODULE("guitracecontrol", "Trace Control Window", \
1153 "Graphical module that let user control kernel tracing", \
1154 init, destroy, "lttvwindow")
1155
This page took 0.057888 seconds and 4 git commands to generate.