~ubuntu-core-dev/update-notifier/ubuntu

« back to all changes in this revision

Viewing changes to src/hooks.c

  • Committer: Julian Andres Klode
  • Date: 2018-04-09 11:33:56 UTC
  • Revision ID: juliank@ubuntu.com-20180409113356-qtuaie5oiwhl8jqj
Tags: 3.189
releasing package update-notifier version 3.189

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#ifdef HAVE_CONFIG_H
 
2
#include "config.h"
 
3
#endif
 
4
 
 
5
#include <fcntl.h>
 
6
#include <unistd.h>
 
7
#include <stdlib.h>
 
8
 
 
9
#include <glib.h>
 
10
#include <gtk/gtk.h>
 
11
#include <glib/gstdio.h>
 
12
#include <libnotify/notify.h>
 
13
 
 
14
#include <locale.h>
 
15
#include <langinfo.h>
 
16
 
 
17
#include "update-notifier.h"
 
18
#include "hooks.h"
 
19
#include "rfc822.h"
 
20
#include "assert.h"
 
21
#include "trayappletui.h"
 
22
 
 
23
/* relative to the home dir */
 
24
#define HOOKS_SEEN_DEPRECATED ".update-notifier/hooks_seen"
 
25
/* relative to the XDG_CONFIG_HOME dir */
 
26
#define HOOKS_SEEN "update-notifier/hooks_seen"
 
27
 
 
28
/* used by e.g. the installer to mark stuff that's already done */
 
29
#define GLOBAL_HOOKS_SEEN "/etc/update-notifier/hooks_seen"
 
30
 
 
31
 
 
32
// the size of the md5 hash digest for the duplicated note detection
 
33
static const int DIGEST_SIZE=16;
 
34
 
 
35
static inline void
 
36
g_debug_hooks(const char *msg, ...)
 
37
{
 
38
   va_list va;
 
39
   va_start(va, msg);
 
40
   g_logv("hooks",G_LOG_LEVEL_DEBUG, msg, va);
 
41
   va_end(va);
 
42
}
 
43
 
 
44
// compare a HookFile with a filename and find the HookFile that matches
 
45
static gint
 
46
compare_hook_func(gconstpointer a, gconstpointer b)
 
47
{
 
48
   //g_debug_hooks("compare: %s %s",(char*)(((HookFileSeen*)a)->filename),(char*)b);
 
49
   g_assert(a);
 
50
   g_assert(b);
 
51
 
 
52
   return strcmp(((HookFile*)a)->filename, b);
 
53
}
 
54
 
 
55
// return the most recent mtime or ctime of the file
 
56
static time_t
 
57
hook_file_time(const gchar *filename)
 
58
{
 
59
   struct stat buf;
 
60
   char *file = g_strdup_printf("%s/%s",HOOKS_DIR, filename);
 
61
   if(g_stat(file, &buf) <0) {
 
62
      g_warning("can't stat %s",file);
 
63
      g_free(file);
 
64
      return 0;
 
65
   }
 
66
   g_free(file);
 
67
 
 
68
   time_t mtime = buf.st_mtime;
 
69
   time_t ctime = buf.st_ctime;
 
70
 
 
71
   return mtime > ctime ? mtime : ctime;
 
72
}
 
73
 
 
74
static gboolean
 
75
hook_file_md5(const gchar *filename, guint8 *md5)
 
76
{
 
77
   guchar buf[512];
 
78
   FILE *f;
 
79
   char *file;
 
80
   gsize n;
 
81
   GChecksum *checksum;
 
82
 
 
83
   file = g_strdup_printf("%s/%s",HOOKS_DIR, filename);
 
84
   f = fopen(file,"r");
 
85
   if(f == NULL) {
 
86
      g_warning("can't read %s",file);
 
87
      g_free(file);
 
88
      return FALSE;
 
89
   }
 
90
   checksum = g_checksum_new(G_CHECKSUM_MD5);
 
91
   do {
 
92
      n = fread(buf, 1, sizeof(buf), f);
 
93
      g_checksum_update(checksum, buf, n);
 
94
   } while(n > 0);
 
95
   
 
96
   n=DIGEST_SIZE;
 
97
   g_checksum_get_digest(checksum, md5, &n);
 
98
   //g_debug_hooks("md5: %s -> '%s'", filename, md5);
 
99
 
 
100
   g_free(file);
 
101
   g_checksum_free(checksum);
 
102
   return TRUE;
 
103
}
 
104
 
 
105
/* mark a given hook file as seen 
 
106
  (actually implemented to write out all the information we have)
 
107
*/
 
108
static gboolean
 
109
hook_file_mark_as_seen(HookTrayAppletPrivate *priv, HookFile *hf)
 
110
{
 
111
   g_debug_hooks("mark_hook_file_as_seen: %s", hf->filename);
 
112
 
 
113
   // copy the md5
 
114
   guint8 md5[DIGEST_SIZE];
 
115
   hook_file_md5(hf->filename, md5);
 
116
 
 
117
   // mark as seen 
 
118
   hf->seen = TRUE;
 
119
 
 
120
   // update the time (extra paranoia, shouldn't be needed)
 
121
   hf->mtime = hook_file_time(hf->filename);
 
122
 
 
123
   // write out the list of known files
 
124
   gchar *filename = g_strdup_printf("%s/%s",
 
125
                                     g_get_user_config_dir(),
 
126
                                     HOOKS_SEEN);
 
127
   FILE *f = fopen(filename, "w");
 
128
   if(f==NULL) {
 
129
      g_warning("Something went wrong writing the user's hook file");
 
130
      return FALSE;
 
131
   }
 
132
 
 
133
   // write out all the hooks that are seen 
 
134
   //
 
135
   // and any hooks that are not yet seen but have the same md5sum 
 
136
   // as the one just marked (avoids showing duplicated hooks effectively). 
 
137
   //
 
138
   // For hooks with the same md5sum use the mtime of the just displayed hook
 
139
   GList *elm = g_list_first(priv->hook_files);
 
140
   for(;elm != NULL; elm = g_list_next(elm)) {
 
141
      HookFile *e = (HookFile*)elm->data;
 
142
      g_debug_hooks("will write out: %s (%s)",e->filename, e->md5);
 
143
      if(e->seen == TRUE) {
 
144
         g_debug_hooks("e->seen: %s %li %x", e->filename,e->mtime, (int)(e->cmd_run));
 
145
         fprintf(f,"%s %li %x\n", e->filename, e->mtime, (int)(e->cmd_run));
 
146
      } else if(memcmp(e->md5,md5,DIGEST_SIZE) == 0) {
 
147
         e->seen = TRUE;
 
148
         fprintf(f,"%s %li %x\n", e->filename,hf->mtime, (int)(e->cmd_run));
 
149
         g_debug_hooks("same md5: %s %li %x",e->filename,hf->mtime,(int)(e->cmd_run));
 
150
      }
 
151
   }
 
152
 
 
153
   fclose(f);
 
154
   g_free(filename);
 
155
 
 
156
   return TRUE;
 
157
}
 
158
 
 
159
/* mark a given HookFile as run */
 
160
static gboolean
 
161
mark_hook_file_as_run(HookTrayAppletPrivate *priv, HookFile *hf)
 
162
{
 
163
   if(hf == NULL)
 
164
      return FALSE;
 
165
 
 
166
   g_debug_hooks("mark_hook_file_as_run: %s",hf->filename);
 
167
   hf->cmd_run = TRUE;
 
168
 
 
169
   return TRUE;
 
170
}
 
171
 
 
172
 
 
173
/* get the language code in a static allocated buffer
 
174
 * short_form: only return the languagecode if true, otherwise
 
175
 *             languagecode_countrycode
 
176
 */
 
177
static char *
 
178
get_lang_code(gboolean short_form)
 
179
{
 
180
   /* make own private copy */
 
181
   static gchar locale[51];
 
182
   strncpy(locale, setlocale(LC_MESSAGES, NULL), 50);
 
183
   
 
184
   // FIXME: we need to be more inteligent here
 
185
   // _and_ we probably want to look into the "LANGUAGE" enviroment too
 
186
   if(short_form) {
 
187
      locale[2] = 0;
 
188
      return locale;
 
189
   } else {
 
190
      locale[5] = 0;
 
191
      return locale;
 
192
   }
 
193
}
 
194
 
 
195
/*
 
196
 * get a i18n field of the rfc822 header
 
197
 * Return Value: a pointer that must not be freed (part of the rfc822 struct)
 
198
 */
 
199
static char *
 
200
hook_file_lookup_i18n(struct rfc822_header *rfc822, char *field)
 
201
{
 
202
   gchar *s, *entry, *text;
 
203
 
 
204
   /* first check for langpacks, then i18n fields */
 
205
   entry = rfc822_header_lookup(rfc822, "GettextDomain");
 
206
   if(entry != NULL) {
 
207
      text =  rfc822_header_lookup(rfc822, field);
 
208
      s = dgettext(entry, text);
 
209
      if(text != s)
 
210
         return s;
 
211
   }
 
212
 
 
213
   /* try $field-$languagecode_$countrycode.$codeset first */
 
214
   s = g_strdup_printf("%s-%s.%s", field, get_lang_code(FALSE), nl_langinfo(CODESET));
 
215
   entry = rfc822_header_lookup(rfc822, s);
 
216
   //g_debug_hooks("Looking for: %s ; found: %s",s,entry);
 
217
   g_free(s);
 
218
   if(entry != NULL)
 
219
      return entry;
 
220
 
 
221
   /* try $field-$languagecode_$countrycode (and assume utf-8) */
 
222
   s = g_strdup_printf("%s-%s", field, get_lang_code(FALSE));
 
223
   entry = rfc822_header_lookup(rfc822, s);
 
224
   //g_debug_hooks("Looking for: %s ; found: %s",s,entry);
 
225
   g_free(s);
 
226
   if(entry != NULL)
 
227
      return entry;
 
228
 
 
229
   /* try $field-$languagecode.$codeset next */
 
230
   s = g_strdup_printf("%s-%s.%s", field, get_lang_code(TRUE), nl_langinfo(CODESET));
 
231
   //g_debug_hooks("Looking for: %s ; found: %s",s,entry);
 
232
   entry = rfc822_header_lookup(rfc822, s);
 
233
   g_free(s);
 
234
   if(entry != NULL)
 
235
      return entry;
 
236
 
 
237
   /* try $field-$languagecode (and assume utf-8 codeset) */
 
238
   s = g_strdup_printf("%s-%s", field, get_lang_code(TRUE));
 
239
   //g_debug_hooks("Looking for: %s ; found: %s",s,entry);
 
240
   entry = rfc822_header_lookup(rfc822, s);
 
241
   g_free(s);
 
242
   if(entry != NULL)
 
243
      return entry;
 
244
 
 
245
   /* now try translating it with gettext */
 
246
   entry = rfc822_header_lookup(rfc822, field);
 
247
   return entry;
 
248
}
 
249
 
 
250
static char *
 
251
hook_description_get_summary(struct rfc822_header *rfc822)
 
252
{
 
253
   char *summary = hook_file_lookup_i18n(rfc822, "Name");
 
254
   return summary;
 
255
}
 
256
 
 
257
static char *
 
258
hook_description_get_description(struct rfc822_header *rfc822)
 
259
{
 
260
   char *description = hook_file_lookup_i18n(rfc822, "Description");
 
261
   return description;
 
262
}
 
263
 
 
264
/*
 
265
 * show the given hook file
 
266
 */
 
267
static gboolean
 
268
show_next_hook(TrayApplet *ta, GList *hooks)
 
269
{
 
270
   //g_debug_hooks("show_next_hook()");
 
271
 
 
272
   HookTrayAppletPrivate *priv = (HookTrayAppletPrivate *)ta->user_data;
 
273
 
 
274
   // init some vars
 
275
   HookFile *hf = NULL;
 
276
   char *hook_file = NULL;
 
277
 
 
278
   // find the next unseen hook
 
279
   GList *elm = g_list_first(hooks);
 
280
   for(;elm != NULL; elm = g_list_next(elm)) {
 
281
      hf = (HookFile*)elm->data;
 
282
      if(hf->seen == FALSE) {
 
283
         hook_file = hf->filename;
 
284
         //g_debug_hooks("next_hook is: %s",hook_file);
 
285
         break;
 
286
      }
 
287
   }
 
288
 
 
289
   if(hook_file == NULL) {
 
290
      g_debug_hooks("no unseen hookfile found in hook list of len (%i)",
 
291
              g_list_length(hooks));
 
292
      return FALSE;
 
293
   }
 
294
 
 
295
   /* if there's another unseen hook, show the "next" button */
 
296
   gboolean show_next_button = FALSE;
 
297
   if(elm) {
 
298
      for(elm = g_list_next(elm); elm != NULL; elm = g_list_next(elm)) {
 
299
         if(((HookFile*)elm->data)->seen == FALSE) {
 
300
            show_next_button = TRUE;
 
301
            break;
 
302
         }
 
303
      }
 
304
   }
 
305
   if(show_next_button)
 
306
      gtk_widget_show(priv->button_next);
 
307
   else
 
308
      gtk_widget_hide(priv->button_next);
 
309
 
 
310
   char *filename = g_strdup_printf("%s%s",HOOKS_DIR,hook_file);
 
311
 
 
312
   /* setup the message */
 
313
   GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->textview_hook));
 
314
   FILE *f = fopen(filename, "r");
 
315
   if(f == NULL) {
 
316
      g_warning("can't open %s", filename);
 
317
      g_free(filename);
 
318
      return FALSE;
 
319
   }
 
320
   struct rfc822_header *rfc822 = rfc822_parse_stanza(f);
 
321
 
 
322
   char *cmd = rfc822_header_lookup(rfc822, "Command");
 
323
   char *term = rfc822_header_lookup(rfc822, "Terminal");
 
324
   if(term)
 
325
      term = g_strstrip(term);
 
326
   g_object_set_data(G_OBJECT(priv->button_run),"cmd", g_strdup(cmd));
 
327
   g_object_set_data(G_OBJECT(priv->button_run),"term", g_strdup(term));
 
328
   g_object_set_data(G_OBJECT(priv->button_run),"hook_file", hf);
 
329
   if(cmd != NULL) {
 
330
      gtk_widget_show(priv->button_run);
 
331
   } else {
 
332
      gtk_widget_hide(priv->button_run);
 
333
   }
 
334
   char *title_str = hook_file_lookup_i18n(rfc822, "Title");
 
335
   if(title_str != NULL) {
 
336
      gchar *s = g_strdup_printf("<span weight=\"bold\" size=\"larger\">%s</span>", title_str);
 
337
      gtk_label_set_markup(GTK_LABEL(priv->label_title), s);
 
338
      g_free(s);
 
339
   }
 
340
   char *summary = hook_description_get_summary(rfc822);
 
341
   char *descr = hook_description_get_description(rfc822);
 
342
   char *s;
 
343
   if(summary) {
 
344
      s = g_strdup_printf("%s\n\n%s\n",gettext(summary), gettext(descr));
 
345
      gtk_text_buffer_set_text(buf, s, -1);
 
346
      // set the name to bold
 
347
      GtkTextIter start, end;
 
348
      gtk_text_buffer_get_iter_at_offset(buf, &start, 0);
 
349
      gtk_text_buffer_get_iter_at_offset(buf, &end, g_utf8_strlen(gettext(summary),-1));
 
350
      gtk_text_buffer_apply_tag_by_name(buf, "bold_tag", &start, &end);
 
351
 
 
352
   } else {
 
353
      s = g_strdup_printf("%s\n",gettext(descr));
 
354
      gtk_text_buffer_set_text(buf, s, -1);
 
355
   }
 
356
 
 
357
   // set button name (if needed)
 
358
   char *b = hook_file_lookup_i18n(rfc822, "ButtonText");
 
359
   if(b)
 
360
      gtk_button_set_label(GTK_BUTTON (priv->button_run), b);
 
361
   else
 
362
      gtk_button_set_label(GTK_BUTTON (priv->button_run), _("_Run this action now"));
 
363
 
 
364
   // clean up
 
365
   fclose(f);
 
366
   g_free(filename);
 
367
   g_free(s);
 
368
   rfc822_header_free_all(rfc822);
 
369
 
 
370
   /* mark the current hook file as seen */
 
371
   g_object_set_data(G_OBJECT(priv->button_next), "HookFile", hf);
 
372
 
 
373
   return TRUE;
 
374
}
 
375
 
 
376
 
 
377
void on_button_run_clicked(GtkWidget *self, gpointer *data)
 
378
{
 
379
   //g_debug_hooks("cb_button_run()");
 
380
   gchar *cmdline;
 
381
 
 
382
   TrayApplet *ta = (TrayApplet *)data;
 
383
   HookTrayAppletPrivate *priv = (HookTrayAppletPrivate *)ta->user_data;
 
384
 
 
385
   /* mark the current hook file as run */
 
386
   HookFile *hf = g_object_get_data(G_OBJECT(self), "hook_file");
 
387
   mark_hook_file_as_run(priv, hf);
 
388
 
 
389
   gchar *cmd = g_object_get_data(G_OBJECT(self), "cmd");
 
390
   gchar *term = g_object_get_data(G_OBJECT(self), "term");
 
391
 
 
392
   if(cmd == NULL) {
 
393
      g_warning("cmd is NULL");
 
394
      return;
 
395
   }
 
396
 
 
397
   if(term != NULL && !g_ascii_strncasecmp(term, "true",-1)) {
 
398
      cmdline = g_strdup_printf("x-terminal-emulator -e %s",cmd);
 
399
   } else 
 
400
      cmdline = g_strdup(cmd);
 
401
 
 
402
   g_spawn_command_line_async(cmdline, NULL);
 
403
   g_free(cmdline);
 
404
}
 
405
 
 
406
void on_button_next_clicked(GtkWidget *self, gpointer *data)
 
407
{
 
408
   g_debug_hooks("cb_button_next()");
 
409
   TrayApplet *ta = (TrayApplet *)data;
 
410
   HookTrayAppletPrivate *priv = (HookTrayAppletPrivate *)ta->user_data;
 
411
 
 
412
   HookFile *hf;
 
413
   hf = (HookFile*)g_object_get_data(G_OBJECT(self),"HookFile");
 
414
   if(hf == NULL) {
 
415
      g_warning("button_next called without HookFile");
 
416
      return;
 
417
   }
 
418
   
 
419
   // mark as seen 
 
420
   hook_file_mark_as_seen(priv, hf);
 
421
 
 
422
   if(priv->hook_files != NULL)
 
423
      show_next_hook(ta, priv->hook_files);
 
424
}
 
425
 
 
426
static void
 
427
on_insert_text(GtkTextBuffer *buffer,
 
428
               GtkTextIter   *iter_end,
 
429
               gchar         *text,
 
430
               gint           arg3,
 
431
               gpointer       user_data)
 
432
{
 
433
   GtkTextIter iter, match_start, match_end, match_tmp;
 
434
 
 
435
   // get where we start
 
436
   gtk_text_buffer_get_iter_at_offset(buffer, &iter,
 
437
                                      gtk_text_iter_get_offset(iter_end) - g_utf8_strlen(text,-1));
 
438
   // search for http:// uris
 
439
   while(gtk_text_iter_forward_search(&iter, 
 
440
                                      "http://",  GTK_TEXT_SEARCH_VISIBLE_ONLY, 
 
441
                                      &match_start, &match_end, iter_end))  {
 
442
      match_tmp = match_end;
 
443
      // we found a uri, iterate it until the end is found
 
444
      while(gtk_text_iter_forward_char(&match_tmp)) {
 
445
         gchar *s = gtk_text_iter_get_text(&match_end,&match_tmp);
 
446
         if(strlen(s) < 1)
 
447
            break;
 
448
         char c = s[strlen(s)-1];
 
449
         if(c == ' ' || c == ')' || c == ']' || c == '\n' || c == '\t' || c == '>')
 
450
            break;
 
451
         match_end = match_tmp;
 
452
      }
 
453
      gchar *url = gtk_text_iter_get_text(&match_start, &match_end);
 
454
      //g_print("url: '%s'\n",url);
 
455
      GtkTextTag *tag;
 
456
      tag = gtk_text_buffer_create_tag(buffer, NULL,
 
457
                                       "foreground","blue",
 
458
                                       "underline", PANGO_UNDERLINE_SINGLE,
 
459
                                       NULL);
 
460
      g_object_set_data(G_OBJECT(tag), "url", url);
 
461
      gtk_text_buffer_apply_tag(buffer, tag, &match_start, &match_end);
 
462
      iter = match_end;
 
463
   }
 
464
}
 
465
 
 
466
static void
 
467
on_event_after(GtkWidget *widget,
 
468
               GdkEventButton  *event,
 
469
               gpointer   user_data)      
 
470
{
 
471
   if(event->type != GDK_BUTTON_RELEASE)
 
472
      return;
 
473
   if(event->button != 1)
 
474
      return;
 
475
   gint x,y;
 
476
   gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(widget),
 
477
                                         GTK_TEXT_WINDOW_WIDGET,
 
478
                                         event->x, event->y,
 
479
                                         &x, &y);
 
480
   GtkTextIter iter;
 
481
   gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(widget), &iter, x, y);
 
482
   GSList *tags = gtk_text_iter_get_tags(&iter);
 
483
   for( ; tags != NULL ; tags = tags->next) {
 
484
      gchar *url = g_object_get_data(G_OBJECT(tags->data), "url");
 
485
      if(url != NULL) {
 
486
         //g_print("click: '%s'\n",url);
 
487
         char *argv[] = { "/usr/bin/gnome-open", url, NULL };
 
488
         g_spawn_async(NULL, argv, NULL, 0, NULL, NULL, NULL, NULL);
 
489
         break;
 
490
      }
 
491
   }
 
492
}
 
493
 
 
494
static void
 
495
show_hooks(TrayApplet *ta, gboolean focus_on_map)
 
496
{
 
497
   HookTrayAppletPrivate *priv = (HookTrayAppletPrivate *)ta->user_data;
 
498
   GtkBuilder *builder;
 
499
   GError *error = NULL;
 
500
 
 
501
   g_debug_hooks("show_hooks()");
 
502
 
 
503
   builder = gtk_builder_new ();
 
504
   assert (builder);
 
505
 
 
506
   if (!gtk_builder_add_from_file (builder, UIDIR"hooks-dialog.ui", &error)) {
 
507
      g_warning ("Couldn't load builder file: %s", error->message);
 
508
      g_error_free (error);
 
509
   }
 
510
 
 
511
   gtk_builder_connect_signals (builder, (gpointer)ta);
 
512
 
 
513
   priv->dialog_hooks = GTK_WIDGET (gtk_builder_get_object (builder, "dialog_hooks"));
 
514
   gtk_window_set_title (GTK_WINDOW (priv->dialog_hooks), _("Information available"));
 
515
   assert (priv->dialog_hooks);
 
516
   priv->label_title = GTK_WIDGET (gtk_builder_get_object (builder, "label_title"));
 
517
   assert (priv->label_title);
 
518
   priv->textview_hook = GTK_WIDGET (gtk_builder_get_object (builder, "textview_hook"));
 
519
   assert (priv->textview_hook);
 
520
   priv->button_run = GTK_WIDGET (gtk_builder_get_object (builder, "button_run"));
 
521
   assert (priv->button_run);
 
522
   priv->button_next = GTK_WIDGET (gtk_builder_get_object (builder, "button_next"));
 
523
   assert (priv->button_next);
 
524
 
 
525
   g_object_unref (G_OBJECT (builder));
 
526
 
 
527
   // create a bold tag
 
528
   GtkTextBuffer *buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->textview_hook));
 
529
   gtk_text_buffer_create_tag (buf, "bold_tag",
 
530
                               "scale", PANGO_SCALE_LARGE, 
 
531
                               "weight", PANGO_WEIGHT_BOLD,
 
532
                               NULL);  
 
533
   g_signal_connect_after (G_OBJECT (buf), "insert-text",
 
534
                           G_CALLBACK (on_insert_text), NULL);
 
535
   g_signal_connect_after (G_OBJECT (priv->textview_hook), "event-after",
 
536
                           G_CALLBACK (on_event_after), NULL);
 
537
 
 
538
   gtk_window_set_focus_on_map (GTK_WINDOW (priv->dialog_hooks), focus_on_map);
 
539
 
 
540
   // if show_next_hook() fails for some reason don't do anything
 
541
   if(!show_next_hook(ta, priv->hook_files))
 
542
      goto out;
 
543
 
 
544
   int res = gtk_dialog_run (GTK_DIALOG (priv->dialog_hooks));
 
545
   if(res == GTK_RESPONSE_CLOSE) {
 
546
      // mark the currently current hookfile as seen
 
547
      HookFile *hf;
 
548
      hf = (HookFile*)g_object_get_data (G_OBJECT (priv->button_next), "HookFile");
 
549
      if(hf == NULL) {
 
550
         g_warning("show_hooks called without HookFile");
 
551
         goto out;
 
552
      }
 
553
   
 
554
      // mark as seen 
 
555
      hook_file_mark_as_seen(priv, hf);
 
556
      check_update_hooks(ta);
 
557
   }
 
558
 
 
559
out:
 
560
   g_clear_pointer (&priv->dialog_hooks, gtk_widget_destroy);
 
561
   priv->label_title = NULL;
 
562
   priv->textview_hook = NULL;
 
563
   priv->button_run = NULL;
 
564
   priv->button_next = NULL;
 
565
}
 
566
 
 
567
static gboolean
 
568
button_release_cb (GtkWidget *widget, 
 
569
                   TrayApplet *ta)
 
570
{
 
571
   HookTrayAppletPrivate *priv = (HookTrayAppletPrivate*)ta->user_data;
 
572
 
 
573
   if(priv->active_notification != NULL) {
 
574
      notify_notification_close(priv->active_notification, NULL);
 
575
      g_clear_object(&priv->active_notification);
 
576
   }
 
577
   
 
578
   //g_debug_hooks("left click on hook applet");
 
579
   show_hooks(ta, TRUE);
 
580
   return TRUE;
 
581
}
 
582
 
 
583
static gboolean
 
584
is_hook_relevant(const gchar *hook_file)
 
585
{
 
586
   g_debug_hooks("is_hook_relevant(): %s", hook_file);
 
587
   gboolean res = TRUE;
 
588
 
 
589
   char *filename = g_strdup_printf("%s%s",HOOKS_DIR,hook_file);
 
590
   FILE *f = fopen(filename, "r");
 
591
   if(f == NULL) {
 
592
      // can't open, can't be relevant
 
593
      return FALSE; 
 
594
   }
 
595
   struct rfc822_header *rfc822 = rfc822_parse_stanza(f);
 
596
 
 
597
   // check if its a note that is relevant only for admin users
 
598
   // (this is the default)
 
599
   gchar *b = rfc822_header_lookup(rfc822, "OnlyAdminUsers");
 
600
   if(b == NULL || g_ascii_strncasecmp(b, "true",-1) == 0) {
 
601
      if (!in_admin_group())
 
602
         return FALSE;
 
603
   }
 
604
 
 
605
   // check the DontShowAfterReboot flag
 
606
   b = rfc822_header_lookup(rfc822, "DontShowAfterReboot");
 
607
   if(b != NULL && g_ascii_strncasecmp(b, "true",-1) == 0) {
 
608
      g_debug_hooks("found DontShowAfterReboot");
 
609
 
 
610
      // read the uptime information
 
611
      double uptime=0, idle=0;
 
612
      char buf[1024];
 
613
      int fd = open("/proc/uptime", 0);
 
614
      int local_n = read(fd, buf, sizeof(buf)-1);
 
615
      buf[local_n] = '\0';
 
616
      char *savelocale = setlocale(LC_NUMERIC, NULL);
 
617
      setlocale(LC_NUMERIC,"C");
 
618
      sscanf(buf, "%lf %lf", &uptime,&idle);
 
619
      close(fd);
 
620
      setlocale(LC_NUMERIC,savelocale);
 
621
 
 
622
      time_t mtime = hook_file_time(hook_file);
 
623
      time_t now = time(NULL);
 
624
 
 
625
      g_debug_hooks("now: %li mtime: %li uptime: %f",now,mtime,uptime);
 
626
      g_debug_hooks("diff: %li  uptime: %f",now-mtime,uptime);
 
627
      if((int)uptime > 0 && (now - mtime) > (int)uptime) {
 
628
         g_debug_hooks("not relevant because of reboot: %s",hook_file);
 
629
         res = FALSE;
 
630
      }  else
 
631
         g_debug_hooks("hook is relevant");
 
632
   }
 
633
   fclose(f);
 
634
 
 
635
   // check for DisplayIf test
 
636
   b = rfc822_header_lookup(rfc822, "DisplayIf");
 
637
   if(b != NULL) {
 
638
      g_debug_hooks("found DisplayIf command: '%s'", b);
 
639
      int exitc = system(b);
 
640
      g_debug_hooks("'%s' returned: %i", b, exitc);
 
641
      res = (exitc == 0);
 
642
   }
 
643
 
 
644
   g_free(filename);
 
645
   rfc822_header_free_all(rfc822);
 
646
   return res;
 
647
}
 
648
 
 
649
static gboolean show_notification(void *data)
 
650
{
 
651
   TrayApplet *ta = (TrayApplet*)data;
 
652
   HookTrayAppletPrivate *priv = (HookTrayAppletPrivate*)ta->user_data;
 
653
 
 
654
   if(!tray_applet_ui_get_visible(ta))
 
655
      return FALSE;
 
656
 
 
657
   if((priv->dialog_hooks && gtk_widget_get_visible(priv->dialog_hooks)) ||
 
658
      priv->active_notification != NULL)
 
659
      return FALSE;
 
660
 
 
661
   NotifyNotification *n;
 
662
   GdkPixbuf* pix;
 
663
   n = notify_notification_new(
 
664
                               _("Information available"),
 
665
                               _("Click on the notification icon"
 
666
                                 " to show the available information.\n"),
 
667
                               NULL);
 
668
   
 
669
   pix = gtk_icon_theme_load_icon(gtk_icon_theme_get_default(), 
 
670
                                  GTK_STOCK_DIALOG_INFO, 48,0,NULL);
 
671
   notify_notification_set_icon_from_pixbuf (n, pix);
 
672
   g_object_unref(pix);
 
673
   notify_notification_set_timeout (n, 60*1000);
 
674
   notify_notification_show(n, NULL);
 
675
   if (priv->active_notification)
 
676
      g_object_unref(priv->active_notification);
 
677
   priv->active_notification = n;
 
678
 
 
679
   return FALSE;
 
680
}
 
681
 
 
682
gboolean check_update_hooks(TrayApplet *ta)
 
683
{
 
684
   GList *elm;
 
685
   HookFile *hf;
 
686
   GDir* dir;
 
687
   const gchar *hook_file;
 
688
 
 
689
   g_debug_hooks("check_update_hooks()");
 
690
   HookTrayAppletPrivate *priv = (HookTrayAppletPrivate*)ta->user_data;
 
691
 
 
692
   dir=g_dir_open(HOOKS_DIR, 0, NULL);
 
693
   if(dir == NULL) {
 
694
      g_warning("can't read %s directory",HOOKS_DIR);
 
695
      return FALSE;
 
696
   }
 
697
 
 
698
   g_debug_hooks("reading '%s' dir", HOOKS_DIR);
 
699
   int unseen_count = 0;
 
700
   while((hook_file=g_dir_read_name(dir)) != NULL) {
 
701
      g_debug_hooks("investigating file '%s'", hook_file); 
 
702
 
 
703
      // check if the hook still applies (for e.g. DontShowAfterReboot)
 
704
      if(!is_hook_relevant(hook_file)) {
 
705
         g_debug_hooks("not relevant: '%s'",hook_file);
 
706
         continue;
 
707
      }
 
708
      // see if we already know about this hook filename
 
709
      elm = g_list_find_custom(priv->hook_files,hook_file,
 
710
                                      compare_hook_func);
 
711
 
 
712
      // not seen before, add to the list
 
713
      if(elm == NULL) {
 
714
         g_debug_hooks("never seen before: %s",hook_file);
 
715
         HookFile *t = g_new0(HookFile, 1);
 
716
         t->filename = strdup(hook_file);
 
717
         t->mtime = hook_file_time(hook_file);
 
718
         hook_file_md5(hook_file, t->md5);
 
719
         t->cmd_run = FALSE;
 
720
         t->seen = FALSE;
 
721
         priv->hook_files = g_list_append(priv->hook_files, (gpointer)t);
 
722
         // init elm with the just added record (will be needed below)
 
723
         elm = g_list_find_custom(priv->hook_files,hook_file,
 
724
                                  compare_hook_func);
 
725
         assert(elm != NULL);
 
726
      }
 
727
      
 
728
      // this is the hook file information we have (either because it was
 
729
      // availabe already or because we added it)
 
730
      hf = (HookFile*)elm->data;
 
731
 
 
732
      // file has changed since we last saw it
 
733
      time_t new_mtime = hook_file_time(hook_file);
 
734
      if(new_mtime > hf->mtime) {
 
735
         g_debug_hooks("newer mtime: %s (%li > %li))",hook_file, new_mtime, hf->mtime);
 
736
         hf->seen = FALSE;
 
737
      }
 
738
 
 
739
      // we have not seen it yet (because e.g. it was just added)
 
740
      if(hf->seen == FALSE) {
 
741
         g_debug_hooks("the file '%s' was NOT SEEN yet",hook_file);
 
742
 
 
743
         // update mtime (because we haven't seen the old one and there
 
744
         // is a new one now)
 
745
         hf->mtime = new_mtime;
 
746
 
 
747
         // check if there is already another notification that is 
 
748
         // a) not this one
 
749
         // b) not seen yet
 
750
         // c) the same
 
751
         // if so, we don't increase the unseen count as all identical
 
752
         // ones will maked as unseen at onces
 
753
         gboolean md5match = FALSE;
 
754
         GList *x = g_list_first(priv->hook_files);
 
755
         while(x!=NULL) {
 
756
            HookFile *e = (HookFile*)x->data;
 
757
            if((elm != x)  &&
 
758
               (e->seen == FALSE) &&
 
759
               (memcmp(hf->md5,e->md5,DIGEST_SIZE)==0) )
 
760
              {
 
761
                 g_debug_hooks("%s (%s) was seen also in %s (%s)",
 
762
                         hf->filename, hf->md5, e->filename, e->md5);
 
763
                 md5match = TRUE;
 
764
              }
 
765
            x = g_list_next(x);
 
766
         }
 
767
         if(!md5match) {
 
768
            g_debug_hooks("%s increases unseen_count",hf->filename);
 
769
            unseen_count++;
 
770
         }
 
771
      } else 
 
772
         g_debug_hooks("already seen: '%s'",hook_file);
 
773
   }
 
774
   g_dir_close(dir);
 
775
 
 
776
   //g_debug_hooks("hooks: %i (new: %i)", g_list_length(hook_files), unseen_count);
 
777
 
 
778
   if (unseen_count > 0) {
 
779
      // we only do a dialog
 
780
      g_debug_hooks("showing hooks with focus on map == FALSE");
 
781
      show_hooks(ta, FALSE);
 
782
      return TRUE;
 
783
   } else
 
784
      tray_applet_ui_destroy (ta);
 
785
 
 
786
   return TRUE;
 
787
}
 
788
 
 
789
static void
 
790
hook_read_seen_file(HookTrayAppletPrivate *priv, const char* filename)
 
791
{
 
792
   HookFile *t;
 
793
   char buf[512];
 
794
   int time, was_run;
 
795
   FILE *f = fopen(filename, "r");
 
796
   if(f==NULL)
 
797
      return;
 
798
 
 
799
   g_debug_hooks("reading hook_file: %s ", filename);
 
800
 
 
801
   while(fscanf(f, "%s %i %i",buf,&time,&was_run) == 3) {
 
802
 
 
803
      // first check if the file actually exists, if not skip it
 
804
      // (may already be delete when e.g. a package was removed)
 
805
      char *filename = g_strdup_printf("%s%s",HOOKS_DIR,buf);
 
806
      gboolean res = g_file_test(filename, G_FILE_TEST_EXISTS);
 
807
      g_free(filename);
 
808
      if(!res)
 
809
         continue;
 
810
 
 
811
      // now check if we already have that filename in the list
 
812
      GList *elm = g_list_find_custom(priv->hook_files,buf,
 
813
                                      compare_hook_func);
 
814
      if(elm != NULL) {
 
815
         g_debug_hooks("hookfile: %s already in the list", buf);
 
816
 
 
817
         // we have that file already in the list with a newer mtime,
 
818
         // than the file in the config file. ignore the config file
 
819
         HookFile *exisiting = (HookFile *)elm->data;
 
820
         // and it's more current
 
821
         if(exisiting->mtime > time) {
 
822
            g_debug_hooks("existing is newer, ignoring the read one");
 
823
            continue;
 
824
         }
 
825
 
 
826
         // we have it in the list, but the the file in the list is older
 
827
         // than the one in the config file. use this config-file instead
 
828
         // and update the values in the list
 
829
         exisiting->cmd_run = was_run;
 
830
         exisiting->seen = TRUE;
 
831
         exisiting->mtime = time;
 
832
         hook_file_md5(exisiting->filename,exisiting->md5);
 
833
 
 
834
      } else {
 
835
         // not in the list yet
 
836
         // add the just read hook file to the list
 
837
         g_debug_hooks("got: %s %i %i ",buf,time,was_run);
 
838
         t = g_new0(HookFile, 1);
 
839
         t->filename = strdup(buf);
 
840
         t->mtime = time;
 
841
         t->cmd_run = was_run;
 
842
         t->seen = TRUE;
 
843
         hook_file_md5(t->filename,t->md5);
 
844
 
 
845
         priv->hook_files = g_list_append(priv->hook_files, (gpointer)t);
 
846
      }
 
847
   }
 
848
   fclose(f);
 
849
}
 
850
 
 
851
static gboolean 
 
852
init_already_seen_hooks(TrayApplet *ta)
 
853
{
 
854
   g_debug_hooks("init_already_seen_hooks");
 
855
 
 
856
   HookTrayAppletPrivate* priv = (HookTrayAppletPrivate*)ta->user_data;
 
857
   char *filename, *old_filename;
 
858
 
 
859
   // init with default value
 
860
   priv->hook_files = NULL;  
 
861
 
 
862
   // read global hook file
 
863
   hook_read_seen_file(priv, GLOBAL_HOOKS_SEEN);
 
864
   
 
865
   filename = g_strdup_printf("%s/%s", 
 
866
                              g_get_user_config_dir(),
 
867
                              HOOKS_SEEN);
 
868
   // compat with the previous version
 
869
   old_filename = g_strdup_printf("%s/%s", 
 
870
                                  g_get_home_dir(),
 
871
                                  HOOKS_SEEN_DEPRECATED);
 
872
   if (g_file_test(old_filename, G_FILE_TEST_IS_REGULAR)) {
 
873
     g_rename(old_filename, filename);
 
874
   }
 
875
   // read user hook file
 
876
   hook_read_seen_file(priv,filename);
 
877
   g_free(filename);
 
878
   g_free(old_filename);
 
879
 
 
880
   return TRUE;
 
881
}
 
882
 
 
883
void hook_tray_icon_init(TrayApplet *ta)
 
884
{
 
885
   HookTrayAppletPrivate *priv = g_new0(HookTrayAppletPrivate, 1);
 
886
   ta->user_data = priv;
 
887
 
 
888
   /* read already seen hooks */
 
889
   init_already_seen_hooks(ta);
 
890
   
 
891
   /* Check for hooks */
 
892
   check_update_hooks(ta);
 
893
}