/ src / gui / engine.cc
engine.cc
  1  #include "engine.h"
  2  #include "hw.h"
  3  #include "main.h"
  4  #include "print-gui.h"
  5  #include "print.h"
  6  #include "osutils.h"
  7  #include "options.h"
  8  
  9  #include <cstring>
 10  #include <cstdlib>
 11  #include <iostream>
 12  #include <fstream>
 13  #include <sys/utsname.h>
 14  #include <stdlib.h>
 15  #include <string.h>
 16  #include <libgen.h>
 17  
 18  static const char *id = "@(#) $Id$";
 19  
 20  extern "C"
 21  {
 22  #include "support.h"
 23  };
 24  
 25  #define AUTOMATIC "automatic file format"
 26  #define LSHW_XML "lshw XML format (.lshw, .xml)"
 27  #define PLAIN_TEXT "plain text document (.text, .txt)"
 28  #define JSON "JavaScript Object Notation document (.json)"
 29  #define HTML "HTML document (.html, .htm)"
 30  
 31  #define YIELD()  while(gtk_events_pending()) gtk_main_iteration()
 32  
 33  static hwNode container("container", hw::generic);
 34  static hwNode *displayed = NULL;
 35  static hwNode *selected1 = NULL;
 36  static hwNode *selected2 = NULL;
 37  static hwNode *selected3 = NULL;
 38  
 39  extern GtkWidget *mainwindow;
 40  extern GtkWidget *list1, *list2, *list3;
 41  extern GtkWidget *description;
 42  extern GtkWidget *statusbar;
 43  extern GHashTable *pixbufs;
 44  extern GSimpleAction *go_up_action;
 45  extern GSimpleAction *save_action;
 46  
 47  enum
 48  {
 49    COL_NAME = 0,
 50    COL_NODE,
 51    COL_WEIGHT,
 52    COL_CONTINUATION,
 53    NUM_COLS
 54  };
 55  
 56  static void clear_list(GtkWidget * list1)
 57  {
 58    GtkTreeViewColumn *col;
 59  
 60    while((col = gtk_tree_view_get_column(GTK_TREE_VIEW(list1), 0)))
 61      gtk_tree_view_remove_column(GTK_TREE_VIEW(list1), col);
 62  }
 63  
 64  
 65  static void populate_sublist(GtkWidget * list1, hwNode * root, hwNode *current=NULL)
 66  {
 67    GtkListStore *list_store = NULL;
 68    GtkTreeViewColumn   *col = NULL;
 69    GtkCellRenderer     *renderer = NULL;
 70    GtkTreeIter iter;
 71    GtkTreeIter current_iter;
 72  
 73    clear_list(list1);
 74  
 75    if(!root) return;
 76  
 77    list_store = gtk_list_store_new (NUM_COLS, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_INT, G_TYPE_STRING);
 78  
 79    for(unsigned i = 0; i<root->countChildren(); i++)
 80    {
 81      string text;
 82  
 83      gtk_list_store_append(list_store, &iter);
 84  
 85      if(root->getChild(i) == current) current_iter = iter;
 86  
 87      text = root->getChild(i)->getDescription();
 88      if(text == "")
 89        text = root->getChild(i)->getProduct();
 90      if(text == "")
 91        text = root->getChild(i)->getId();
 92  
 93      gtk_list_store_set (list_store, &iter,
 94        COL_NAME, text.c_str(),
 95        COL_NODE, root->getChild(i),
 96        COL_WEIGHT, (root->getChild(i)->countChildren()>0)?PANGO_WEIGHT_BOLD:PANGO_WEIGHT_NORMAL,
 97        COL_CONTINUATION, (root->getChild(i)->countChildren()>0)?"\342\226\270":"",
 98        -1);
 99    }
100  
101    col = gtk_tree_view_column_new();
102    gtk_tree_view_append_column(GTK_TREE_VIEW(list1), col);
103  
104    renderer = gtk_cell_renderer_text_new();
105  
106    gtk_tree_view_column_pack_start(col, renderer, TRUE);
107    gtk_tree_view_column_add_attribute(col, renderer, "text", COL_NAME);
108    gtk_tree_view_column_add_attribute(col, renderer, "weight", COL_WEIGHT);
109  
110    col = gtk_tree_view_column_new();
111    gtk_tree_view_append_column(GTK_TREE_VIEW(list1), col);
112    renderer = gtk_cell_renderer_text_new();
113    g_object_set(renderer, "xalign", (gfloat)1, NULL);
114    gtk_tree_view_column_pack_start(col, renderer, TRUE);
115    gtk_tree_view_column_add_attribute(col, renderer, "text", COL_CONTINUATION);
116    gtk_tree_view_column_add_attribute(col, renderer, "weight", COL_WEIGHT);
117  
118    gtk_tree_view_set_model(GTK_TREE_VIEW(list1), GTK_TREE_MODEL(list_store));
119    g_object_unref(list_store);
120  
121    gtk_tree_selection_set_mode(GTK_TREE_SELECTION(gtk_tree_view_get_selection(GTK_TREE_VIEW(list1))), GTK_SELECTION_BROWSE);
122    if(current)
123      gtk_tree_selection_select_iter(GTK_TREE_SELECTION(gtk_tree_view_get_selection(GTK_TREE_VIEW(list1))), &current_iter);
124  }
125  
126  
127  static void create_tags (GtkTextBuffer *buffer)
128  {
129    static bool initialized = false;
130  
131    if(initialized) return;
132  
133    initialized = true;
134  
135    gtk_text_buffer_create_tag (buffer, "heading",
136      "weight", PANGO_WEIGHT_BOLD,
137      "size", 15 * PANGO_SCALE,
138      NULL);
139  
140    gtk_text_buffer_create_tag (buffer, "italic",
141      "style", PANGO_STYLE_ITALIC, NULL);
142  
143    gtk_text_buffer_create_tag (buffer, "bold",
144      "weight", PANGO_WEIGHT_BOLD, NULL);
145  
146    gtk_text_buffer_create_tag (buffer, "big",
147  /* points times the PANGO_SCALE factor */
148      "size", 20 * PANGO_SCALE, NULL);
149  
150    gtk_text_buffer_create_tag (buffer, "xx-small",
151      "scale", PANGO_SCALE_XX_SMALL, NULL);
152  
153    gtk_text_buffer_create_tag (buffer, "x-large",
154      "scale", PANGO_SCALE_X_LARGE, NULL);
155  
156    gtk_text_buffer_create_tag (buffer, "monospace",
157      "family", "monospace", NULL);
158  
159    gtk_text_buffer_create_tag (buffer, "blue_foreground",
160      "foreground", "blue", NULL);
161  
162    gtk_text_buffer_create_tag (buffer, "red_background",
163      "background", "red", NULL);
164  
165    gtk_text_buffer_create_tag (buffer, "big_gap_before_line",
166      "pixels_above_lines", 30, NULL);
167  
168    gtk_text_buffer_create_tag (buffer, "big_gap_after_line",
169      "pixels_below_lines", 30, NULL);
170  
171    gtk_text_buffer_create_tag (buffer, "double_spaced_line",
172      "pixels_inside_wrap", 10, NULL);
173  
174    gtk_text_buffer_create_tag (buffer, "not_editable",
175      "editable", FALSE, NULL);
176  
177    gtk_text_buffer_create_tag (buffer, "word_wrap",
178      "wrap_mode", GTK_WRAP_WORD, NULL);
179  
180    gtk_text_buffer_create_tag (buffer, "char_wrap",
181      "wrap_mode", GTK_WRAP_CHAR, NULL);
182  
183    gtk_text_buffer_create_tag (buffer, "no_wrap",
184      "wrap_mode", GTK_WRAP_NONE, NULL);
185  
186    gtk_text_buffer_create_tag (buffer, "center",
187      "justification", GTK_JUSTIFY_CENTER, NULL);
188  
189    gtk_text_buffer_create_tag (buffer, "right_justify",
190      "justification", GTK_JUSTIFY_RIGHT, NULL);
191  
192    gtk_text_buffer_create_tag (buffer, "wide_margins",
193      "left_margin", 50, "right_margin", 50,
194      NULL);
195  
196    gtk_text_buffer_create_tag (buffer, "strikethrough",
197      "strikethrough", TRUE, NULL);
198  
199    gtk_text_buffer_create_tag (buffer, "underline",
200      "underline", PANGO_UNDERLINE_SINGLE, NULL);
201  
202    gtk_text_buffer_create_tag (buffer, "double_underline",
203      "underline", PANGO_UNDERLINE_DOUBLE, NULL);
204  
205    gtk_text_buffer_create_tag (buffer, "superscript",
206      "rise", 10 * PANGO_SCALE,                     /* 10 pixels */
207      "size", 8 * PANGO_SCALE,                      /* 8 points */
208      NULL);
209  
210    gtk_text_buffer_create_tag (buffer, "subscript",
211      "rise", -10 * PANGO_SCALE,                    /* 10 pixels */
212      "size", 8 * PANGO_SCALE,                      /* 8 points */
213      NULL);
214  }
215  
216  
217  static void display(GtkWidget * mainwindow)
218  {
219    GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (description));
220  
221    gtk_text_buffer_set_text(buffer, "", -1);
222  
223    if(!displayed)
224      gtk_text_buffer_set_text(buffer, "Please select a node to display.", -1);
225    else
226    {
227      create_tags(buffer);
228  
229      string hwpath = gethwpath(*displayed, container);
230      printmarkup(*displayed, GTK_TEXT_VIEW(description), hwpath, pixbufs);
231    }
232  }
233  
234  
235  void status(const char *message)
236  {
237    static guint context_id = 0;
238  
239    if(!GTK_IS_WIDGET(statusbar)) return;
240  
241    if(!context_id)
242      context_id = gtk_statusbar_get_context_id(GTK_STATUSBAR(statusbar), "scanning");
243    else
244      gtk_statusbar_pop(GTK_STATUSBAR(statusbar), context_id);
245  
246    if(message) gtk_statusbar_push(GTK_STATUSBAR(statusbar), context_id, message);
247  
248    YIELD();
249  }
250  
251  
252  void refresh(GtkWidget *mainwindow)
253  {
254    hwNode computer("computer", hw::system);
255    static bool lock = false;
256  
257    if(lock) return;
258  
259    lock = true;
260    g_simple_action_set_enabled(save_action, FALSE);
261  
262    populate_sublist(list1, NULL);
263    populate_sublist(list2, NULL);
264    populate_sublist(list3, NULL);
265    displayed = NULL;
266    gtk_text_buffer_set_text(gtk_text_view_get_buffer(GTK_TEXT_VIEW(description)), "scanning...", -1);
267  
268    disable("output:sanitize");
269    container = hwNode("container", hw::generic);
270    status("Scanning...");
271    scan_system(computer);
272    status(NULL);
273    displayed = container.addChild(computer);
274  
275    g_simple_action_set_enabled(go_up_action, FALSE);
276    g_simple_action_set_enabled(save_action, TRUE);
277  
278    selected1 = NULL;
279    selected2 = NULL;
280    selected3 = NULL;
281  
282    populate_sublist(list1, &container);
283    populate_sublist(list2, NULL);
284    populate_sublist(list3, NULL);
285    display(mainwindow);
286  
287    lock = false;
288  }
289  
290  
291  void change_selection(unsigned list, GtkTreeView *treeview)
292  {
293    GtkTreeSelection *selection;
294    GtkTreeModel *model;
295    GtkTreeIter   iter;
296  
297    model = gtk_tree_view_get_model(treeview);
298  
299    displayed = NULL;
300  
301    selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
302    if (gtk_tree_selection_get_selected(selection, &model, &iter))
303      gtk_tree_model_get (model, &iter, COL_NODE, &displayed, -1);
304  
305    if(list<2) populate_sublist(list2, NULL);
306    if(list<3) populate_sublist(list3, NULL);
307  
308    display(mainwindow);
309  }
310  
311  
312  static hwNode * find_parent(hwNode * n, hwNode *sub)
313  {
314    if(!n) return NULL;
315  
316    if(n == sub) return n;
317  
318    for(unsigned i=0; i<sub->countChildren(); i++)
319    {
320      if(sub->getChild(i) == n) return sub;
321  
322      hwNode *r = find_parent(n, sub->getChild(i));
323      if(r) return r;
324    }
325  
326    return NULL;
327  }
328  
329  
330  void browse(unsigned list, GtkTreeView *treeview)
331  {
332    GtkTreeSelection *selection;
333    GtkTreeModel     *model;
334    GtkTreeIter       iter;
335    hwNode *n = NULL;
336  
337    selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
338    if (gtk_tree_selection_get_selected(selection, &model, &iter))
339      gtk_tree_model_get (model, &iter, COL_NODE, &n, -1);
340  
341    if(n)
342      switch(list)
343      {
344        case 1:
345  //if(n!=selected1)
346          {
347            selected1 = n;
348            selected2 = NULL;
349            selected3 = NULL;
350            populate_sublist(list2, selected1, selected2);
351            populate_sublist(list3, selected2, selected3);
352          }
353        break;
354      case 2:
355  //if(n!=selected2)
356        {
357          selected2 = n;
358          selected3 = NULL;
359          populate_sublist(list3, selected2, selected3);
360        }
361        break;
362      case 3:
363  //if(n!=selected3)
364        {
365          selected3 = n;
366          if(n->countChildren()>0)
367          {
368            hwNode *oldselected1 = selected1;
369            selected1 = selected2;
370            selected2 = n;
371            selected3 = NULL;
372            populate_sublist(list1, oldselected1, selected1);
373            populate_sublist(list2, selected1, selected2);
374            populate_sublist(list3, selected2, selected3);
375          }
376        }
377        break;
378    }
379  
380    g_simple_action_set_enabled(go_up_action, selected1 && (find_parent(selected1, &container)!= &container));
381  
382    (void) &::id;                                   // avoid warning "id defined but not used"
383  }
384  
385  
386  void go_back(GtkWidget *mainwindow)
387  {
388    if(selected1 && (find_parent(selected1, &container)!= &container))
389    {
390      selected3 = selected2;
391      selected2 = selected1;
392      selected1 = find_parent(selected1, &container);
393      if(selected1 == &container) selected1 = container.getChild(0);
394      populate_sublist(list1, find_parent(selected1, &container), selected1);
395      populate_sublist(list2, selected1, selected2);
396      populate_sublist(list3, selected2, selected3);
397  
398      if(find_parent(displayed, &container)!= &container)
399        displayed = find_parent(displayed, &container);
400    }
401  
402    g_simple_action_set_enabled(go_up_action, selected1 && (find_parent(selected1, &container)!= &container));
403  
404    display(mainwindow);
405  }
406  
407  static const char *guess_format(char *s)
408  {
409    char *dot = strrchr(s, '.');
410  
411    if(!dot)
412      return LSHW_XML;
413  
414    if(!strcasecmp(".html", dot) || !strcasecmp(".htm", dot))
415      return HTML;
416  
417    if(!strcasecmp(".text", dot) || !strcasecmp(".txt", dot))
418      return PLAIN_TEXT;
419  
420    if(!strcasecmp(".json", dot))
421      return JSON;
422  
423    return LSHW_XML;
424  }
425  
426  static char *fix_filename(char *s, const char *extension)
427  {
428    char *dot = strrchr(s, '.');
429  
430    if(dot)
431      return s;
432  
433    s = (char*)realloc(s, strlen(s) + 1 + strlen(extension) + 1);
434    strcat(s, ".");
435    strcat(s, extension);
436  
437    return s;
438  }
439  
440  static void redirect_cout(std::ofstream &out, bool enable = true)
441  {
442    static std::streambuf* old_cout;
443    
444    if(enable)
445    {
446      old_cout = cout.rdbuf();
447      cout.rdbuf(out.rdbuf());
448    }
449    else
450      cout.rdbuf(old_cout);
451  }
452  
453  void save_as(GtkWidget *mainwindow)
454  {
455    struct utsname buf;
456    GtkFileChooserNative *dialog = NULL;
457    GtkWidget *sanitize = NULL;
458    GtkFileFilter *filter = NULL;
459    bool proceed = true;
460    hwNode *computer = container.getChild(0);
461  
462    if(!computer)		// nothing to save
463      return;
464  
465    dialog = gtk_file_chooser_native_new ("Save hardware configuration",
466  				      GTK_WINDOW(mainwindow),
467  				      GTK_FILE_CHOOSER_ACTION_SAVE,
468  				      "_Save",
469  				      "_Cancel");
470    //gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog), TRUE);
471    sanitize = gtk_check_button_new_with_label("Anonymize output");
472    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(sanitize), enabled("output:sanitize")?TRUE:FALSE);
473    gtk_file_chooser_set_extra_widget(GTK_FILE_CHOOSER(dialog), sanitize);
474  
475    filter = gtk_file_filter_new ();
476    gtk_file_filter_set_name(filter, AUTOMATIC);
477    gtk_file_filter_add_pattern(filter, "*");
478    gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
479    filter = gtk_file_filter_new ();
480    gtk_file_filter_set_name(filter, LSHW_XML);
481    gtk_file_filter_add_pattern(filter, "*.lshw");
482    gtk_file_filter_add_pattern(filter, "*.xml");
483    gtk_file_filter_add_mime_type(filter, "text/xml");
484    gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
485    filter = gtk_file_filter_new ();
486    gtk_file_filter_set_name(filter, HTML);
487    gtk_file_filter_add_pattern(filter, "*.html");
488    gtk_file_filter_add_pattern(filter, "*.htm");
489    gtk_file_filter_add_mime_type(filter, "text/html");
490    gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
491    filter = gtk_file_filter_new ();
492    gtk_file_filter_set_name(filter, PLAIN_TEXT);
493    gtk_file_filter_add_pattern(filter, "*.text");
494    gtk_file_filter_add_pattern(filter, "*.txt");
495    gtk_file_filter_add_mime_type(filter, "text/plain");
496    gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
497    filter = gtk_file_filter_new ();
498    gtk_file_filter_set_name(filter, JSON);
499    gtk_file_filter_add_pattern(filter, "*.json");
500    gtk_file_filter_add_mime_type(filter, "application/json");
501    gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
502  
503    if(uname(&buf)==0)
504      gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), buf.nodename);
505  
506    if (gtk_native_dialog_run (GTK_NATIVE_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
507    {
508      char *filename;
509  
510      if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(sanitize)))
511        enable("output:sanitize");
512      else
513        disable("output:sanitize");
514  
515      filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
516      filter = gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog));
517      if(filename && filter)
518      {
519        const gchar *filtername = gtk_file_filter_get_name(filter);
520  
521        if(strcmp(filtername, AUTOMATIC)==0)
522          filtername = guess_format(filename);
523  
524        if(!exists(filename))		// creating a new file
525        {
526          if(strcmp(filtername, LSHW_XML)==0)
527            filename = fix_filename(filename, "lshw");
528          else
529          if(strcmp(filtername, HTML)==0)
530            filename = fix_filename(filename, "html");
531          else
532          if(strcmp(filtername, PLAIN_TEXT)==0)
533            filename = fix_filename(filename, "txt");
534          else
535          if(strcmp(filtername, JSON)==0)
536            filename = fix_filename(filename, "json");
537        }
538  
539        if(exists(filename))		// existing file
540        {
541          char * buffer1 = g_strdup(filename);
542          char * buffer2 = g_strdup(filename);
543  
544          GtkWidget *dialog = gtk_message_dialog_new_with_markup (GTK_WINDOW(mainwindow),
545                                    GTK_DIALOG_DESTROY_WITH_PARENT,
546                                    GTK_MESSAGE_WARNING,
547                                    GTK_BUTTONS_NONE,
548                                    "A file named <i><tt>%s</tt></i> already exists in folder <tt>%s</tt>.\n\nDo you want to overwrite it?",
549                                    basename(buffer1), dirname(buffer2));
550          gtk_dialog_add_buttons(GTK_DIALOG(dialog), 
551  				  "_Cancel", GTK_RESPONSE_CANCEL,
552  				  "_Overwrite", GTK_RESPONSE_ACCEPT,
553                                    NULL);
554          proceed = (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT);
555          gtk_widget_destroy (dialog);
556          g_free(buffer1);
557          g_free(buffer2);
558        }
559  
560        if(proceed)
561        {
562          if(strcmp(filtername, LSHW_XML)==0)
563          {
564            std::ofstream out(filename);
565            redirect_cout(out);
566            cout << computer->asXML();
567            redirect_cout(out, false);
568          }
569          else
570          if(strcmp(filtername, HTML)==0)
571          {
572            std::ofstream out(filename);
573            redirect_cout(out);
574            print(*computer, true);
575            redirect_cout(out, false);
576          }
577          else
578          if(strcmp(filtername, PLAIN_TEXT)==0)
579          {
580            std::ofstream out(filename);
581            redirect_cout(out);
582            print(*computer, false);
583            redirect_cout(out, false);
584          }
585  	else
586          if(strcmp(filtername, JSON)==0)
587          {
588            std::ofstream out(filename);
589            redirect_cout(out);
590            cout << computer->asJSON() << endl;
591            redirect_cout(out, false);
592          }
593        }
594        g_free (filename);
595      }
596    }
597  
598    g_object_unref (dialog);
599  }