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

« back to all changes in this revision

Viewing changes to src/hooks.c

  • Committer: Balint Reczey
  • Date: 2020-06-11 18:46:02 UTC
  • Revision ID: balint.reczey@canonical.com-20200611184602-2rv1zan3xu723x2u
Moved to git at https://git.launchpad.net/update-notifier

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
 
}