1 /*
2  * Copyright (C) 2002 Red Hat, Inc.
3  *
4  * Permission is hereby granted, free of charge, to any person
5  * obtaining a copy of this software and associated documentation
6  * files (the "Software"), to deal in the Software without
7  * restriction, including without limitation the rights to use, copy,
8  * modify, merge, publish, distribute, sublicense, and/or sell copies
9  * of the Software, and to permit persons to whom the Software is
10  * furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be
13  * included in all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
19  * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
20  * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22  * SOFTWARE.
23  */
24 #include "sn-monitor.h"
25 #include "sn-internals.h"
26 #include "sn-xmessages.h"
27 #include <sys/time.h>
28 
29 struct SnMonitorContext
30 {
31   int refcount;
32   SnDisplay *display;
33   int screen;
34   SnMonitorEventFunc event_func;
35   void *event_func_data;
36   SnFreeFunc free_data_func;
37   /* a context doesn't get events for sequences
38    * started prior to context creation
39    */
40   int creation_serial;
41 };
42 
43 struct SnMonitorEvent
44 {
45   int refcount;
46   SnMonitorEventType type;
47   SnMonitorContext *context;
48   SnStartupSequence *sequence;
49 };
50 
51 struct SnStartupSequence
52 {
53   int refcount;
54 
55   SnDisplay *display;
56   int screen;
57 
58   char *id;
59 
60   char *name;
61   char *description;
62 
63   char *wmclass;
64 
65   int workspace;
66   Time timestamp;
67 
68   char *binary_name;
69   char *icon_name;
70   char *application_id;
71 
72   unsigned int completed : 1;
73   unsigned int canceled : 1;
74   unsigned int timestamp_set : 1;
75 
76   int creation_serial;
77 
78   struct timeval initiation_time;
79 };
80 
81 static SnList *context_list = NULL;
82 static SnList *sequence_list = NULL;
83 static int next_sequence_serial = 0;
84 
85 static void xmessage_func (SnDisplay       *display,
86                            const char      *message_type,
87                            const char      *message,
88                            void            *user_data);
89 
90 /**
91  * sn_monitor_context_new:
92  * @display: an #SnDisplay
93  * @screen: an X screen number
94  * @event_func: function to call when an event is received
95  * @event_func_data: extra data to pass to @event_func
96  * @free_data_func: function to free @event_func_data when the context is freed
97  *
98  * Creates a new context for monitoring startup sequences.  Normally
99  * only launch feedback indicator applications such as the window
100  * manager or task manager would use #SnMonitorContext.
101  * #SnLauncherContext and #SnLauncheeContext are more often used by
102  * applications.
103  *
104  * To detect startup sequence initiations, PropertyChangeMask must be
105  * selected on all root windows for a display.  libsn does not do this
106  * for you because it's pretty likely to mess something up. So you
107  * have to do it yourself in programs that use #SnMonitorContext.
108  *
109  * Return value: a new #SnMonitorContext
110  **/
111 SnMonitorContext*
sn_monitor_context_new(SnDisplay * display,int screen,SnMonitorEventFunc event_func,void * event_func_data,SnFreeFunc free_data_func)112 sn_monitor_context_new (SnDisplay           *display,
113                         int                  screen,
114                         SnMonitorEventFunc   event_func,
115                         void                *event_func_data,
116                         SnFreeFunc           free_data_func)
117 {
118   SnMonitorContext *context;
119 
120   context = sn_new0 (SnMonitorContext, 1);
121 
122   context->refcount = 1;
123   context->event_func = event_func;
124   context->event_func_data = event_func_data;
125   context->free_data_func = free_data_func;
126 
127   context->display = display;
128   sn_display_ref (context->display);
129   context->screen = screen;
130 
131   if (context_list == NULL)
132     context_list = sn_list_new ();
133 
134   if (sn_list_empty (context_list))
135     sn_internal_add_xmessage_func (display,
136                                    screen,
137                                    "_NET_STARTUP_INFO",
138                                    "_NET_STARTUP_INFO_BEGIN",
139                                    xmessage_func,
140                                    NULL, NULL);
141 
142   sn_list_prepend (context_list, context);
143 
144   /* We get events for serials >= creation_serial */
145   context->creation_serial = next_sequence_serial;
146 
147   return context;
148 }
149 
150 /**
151  * sn_monitor_context_ref:
152  * @context: an #SnMonitorContext
153  *
154  * Increments the reference count on @context.
155  *
156  **/
157 void
sn_monitor_context_ref(SnMonitorContext * context)158 sn_monitor_context_ref (SnMonitorContext *context)
159 {
160   context->refcount += 1;
161 }
162 
163 /**
164  * sn_monitor_context_unref:
165  * @context: an #SnMonitorContext
166  *
167  * Decrements the reference count on @context and frees the
168  * context if the count reaches 0.
169  **/
170 void
sn_monitor_context_unref(SnMonitorContext * context)171 sn_monitor_context_unref (SnMonitorContext *context)
172 {
173   context->refcount -= 1;
174 
175   if (context->refcount == 0)
176     {
177       sn_list_remove (context_list, context);
178 
179       if (sn_list_empty (context_list))
180         sn_internal_remove_xmessage_func (context->display,
181                                           context->screen,
182                                           "_NET_STARTUP_INFO",
183                                           xmessage_func,
184                                           NULL);
185 
186       if (context->free_data_func)
187         (* context->free_data_func) (context->event_func_data);
188 
189       sn_display_unref (context->display);
190       sn_free (context);
191     }
192 }
193 
194 void
sn_monitor_event_ref(SnMonitorEvent * event)195 sn_monitor_event_ref (SnMonitorEvent *event)
196 {
197   event->refcount += 1;
198 }
199 
200 void
sn_monitor_event_unref(SnMonitorEvent * event)201 sn_monitor_event_unref (SnMonitorEvent *event)
202 {
203   event->refcount -= 1;
204 
205   if (event->refcount == 0)
206     {
207       if (event->context)
208         sn_monitor_context_unref (event->context);
209       if (event->sequence)
210         sn_startup_sequence_unref (event->sequence);
211       sn_free (event);
212     }
213 }
214 
215 SnMonitorEvent*
sn_monitor_event_copy(SnMonitorEvent * event)216 sn_monitor_event_copy (SnMonitorEvent *event)
217 {
218   SnMonitorEvent *copy;
219 
220   copy = sn_new0 (SnMonitorEvent, 1);
221 
222   copy->refcount = 1;
223 
224   copy->type = event->type;
225   copy->context = event->context;
226   if (copy->context)
227     sn_monitor_context_ref (copy->context);
228   copy->sequence = event->sequence;
229   if (copy->sequence)
230     sn_startup_sequence_ref (copy->sequence);
231 
232   return copy;
233 }
234 
235 SnMonitorEventType
sn_monitor_event_get_type(SnMonitorEvent * event)236 sn_monitor_event_get_type (SnMonitorEvent *event)
237 {
238   return event->type;
239 }
240 
241 SnStartupSequence*
sn_monitor_event_get_startup_sequence(SnMonitorEvent * event)242 sn_monitor_event_get_startup_sequence (SnMonitorEvent *event)
243 {
244   return event->sequence;
245 }
246 
247 SnMonitorContext*
sn_monitor_event_get_context(SnMonitorEvent * event)248 sn_monitor_event_get_context (SnMonitorEvent *event)
249 {
250   return event->context;
251 }
252 
253 void
sn_startup_sequence_ref(SnStartupSequence * sequence)254 sn_startup_sequence_ref (SnStartupSequence *sequence)
255 {
256   sequence->refcount += 1;
257 }
258 
259 void
sn_startup_sequence_unref(SnStartupSequence * sequence)260 sn_startup_sequence_unref (SnStartupSequence *sequence)
261 {
262   sequence->refcount -= 1;
263 
264   if (sequence->refcount == 0)
265     {
266       sn_free (sequence->id);
267 
268       sn_free (sequence->name);
269       sn_free (sequence->description);
270       sn_free (sequence->wmclass);
271       sn_free (sequence->binary_name);
272       sn_free (sequence->icon_name);
273       sn_free (sequence->application_id);
274 
275       sn_display_unref (sequence->display);
276       sn_free (sequence);
277     }
278 }
279 
280 const char*
sn_startup_sequence_get_id(SnStartupSequence * sequence)281 sn_startup_sequence_get_id (SnStartupSequence *sequence)
282 {
283   return sequence->id;
284 }
285 
286 sn_bool_t
sn_startup_sequence_get_completed(SnStartupSequence * sequence)287 sn_startup_sequence_get_completed (SnStartupSequence *sequence)
288 {
289   return sequence->completed;
290 }
291 
292 const char*
sn_startup_sequence_get_name(SnStartupSequence * sequence)293 sn_startup_sequence_get_name (SnStartupSequence *sequence)
294 {
295   return sequence->name;
296 }
297 
298 const char*
sn_startup_sequence_get_description(SnStartupSequence * sequence)299 sn_startup_sequence_get_description (SnStartupSequence *sequence)
300 {
301   return sequence->description;
302 }
303 
304 int
sn_startup_sequence_get_workspace(SnStartupSequence * sequence)305 sn_startup_sequence_get_workspace (SnStartupSequence *sequence)
306 {
307   return sequence->workspace;
308 }
309 
310 Time
sn_startup_sequence_get_timestamp(SnStartupSequence * sequence)311 sn_startup_sequence_get_timestamp (SnStartupSequence *sequence)
312 {
313   if (!sequence->timestamp_set)
314     {
315       fprintf (stderr,
316                "libsn: Buggy startup-notification launcher!  No timestamp!\n");
317       /* Unfortunately, all values are valid; let's just return -1 */
318       return -1;
319     }
320   else
321     return sequence->timestamp;
322 }
323 
324 const char*
sn_startup_sequence_get_wmclass(SnStartupSequence * sequence)325 sn_startup_sequence_get_wmclass (SnStartupSequence *sequence)
326 {
327   return sequence->wmclass;
328 }
329 
330 const char*
sn_startup_sequence_get_binary_name(SnStartupSequence * sequence)331 sn_startup_sequence_get_binary_name (SnStartupSequence *sequence)
332 {
333   return sequence->binary_name;
334 }
335 
336 const char*
sn_startup_sequence_get_icon_name(SnStartupSequence * sequence)337 sn_startup_sequence_get_icon_name (SnStartupSequence *sequence)
338 {
339   return sequence->icon_name;
340 }
341 
342 const char*
sn_startup_sequence_get_application_id(SnStartupSequence * sequence)343 sn_startup_sequence_get_application_id (SnStartupSequence *sequence)
344 {
345   return sequence->application_id;
346 }
347 
348 int
sn_startup_sequence_get_screen(SnStartupSequence * sequence)349 sn_startup_sequence_get_screen (SnStartupSequence *sequence)
350 {
351   return sequence->screen;
352 }
353 
354 /**
355  * sn_startup_sequence_get_initiated_time:
356  * @sequence: an #SnStartupSequence
357  * @tv_sec: seconds as in struct timeval
358  * @tv_usec: microseconds as struct timeval
359  *
360  * When a startup sequence is first monitored, libstartup-notification
361  * calls gettimeofday() and records the time, this function
362  * returns that recorded time.
363  *
364  **/
365 void
sn_startup_sequence_get_initiated_time(SnStartupSequence * sequence,long * tv_sec,long * tv_usec)366 sn_startup_sequence_get_initiated_time (SnStartupSequence *sequence,
367                                         long              *tv_sec,
368                                         long              *tv_usec)
369 {
370   if (tv_sec)
371     *tv_sec = sequence->initiation_time.tv_sec;
372   if (tv_usec)
373     *tv_usec = sequence->initiation_time.tv_usec;
374 }
375 
376 /**
377  * sn_startup_sequence_get_last_active_time:
378  * @sequence: an #SnStartupSequence
379  * @tv_sec: seconds as in struct timeval
380  * @tv_usec: microseconds as in struct timeval
381  *
382  * Returns the last time we had evidence the startup was active.
383  * This function should be used to decide whether a sequence
384  * has timed out.
385  *
386  **/
387 void
sn_startup_sequence_get_last_active_time(SnStartupSequence * sequence,long * tv_sec,long * tv_usec)388 sn_startup_sequence_get_last_active_time (SnStartupSequence *sequence,
389                                           long              *tv_sec,
390                                           long              *tv_usec)
391 {
392   /* for now the same as get_initiated_time */
393   if (tv_sec)
394     *tv_sec = sequence->initiation_time.tv_sec;
395   if (tv_usec)
396     *tv_usec = sequence->initiation_time.tv_usec;
397 }
398 
399 void
sn_startup_sequence_complete(SnStartupSequence * sequence)400 sn_startup_sequence_complete (SnStartupSequence *sequence)
401 {
402   char *keys[2];
403   char *vals[2];
404   char *message;
405 
406   if (sequence->id == NULL)
407     return;
408 
409   if (sequence->screen < 0)
410     return;
411 
412   keys[0] = "ID";
413   keys[1] = NULL;
414   vals[0] = sequence->id;
415   vals[1] = NULL;
416 
417   message = sn_internal_serialize_message ("remove",
418                                            (const char**) keys,
419                                            (const char **) vals);
420 
421   sn_internal_broadcast_xmessage (sequence->display,
422                                   sequence->screen,
423                                   sn_internal_get_net_startup_info_atom(sequence->display),
424                                   sn_internal_get_net_startup_info_begin_atom(sequence->display),
425                                   message);
426 
427   sn_free (message);
428 
429 }
430 
431 static SnStartupSequence*
sn_startup_sequence_new(SnDisplay * display)432 sn_startup_sequence_new (SnDisplay *display)
433 {
434   SnStartupSequence *sequence;
435 
436   sequence = sn_new0 (SnStartupSequence, 1);
437 
438   sequence->refcount = 1;
439 
440   sequence->creation_serial = next_sequence_serial;
441   ++next_sequence_serial;
442 
443   sequence->id = NULL;
444   sequence->display = display;
445   sn_display_ref (display);
446 
447   sequence->screen = -1; /* not set */
448   sequence->workspace = -1; /* not set */
449   sequence->timestamp = 0;
450   sequence->timestamp_set = FALSE;
451 
452   sequence->initiation_time.tv_sec = 0;
453   sequence->initiation_time.tv_usec = 0;
454   gettimeofday (&sequence->initiation_time, NULL);
455 
456   return sequence;
457 }
458 
459 typedef struct
460 {
461   SnMonitorEvent *base_event;
462   SnList *events;
463 } CreateContextEventsData;
464 
465 static sn_bool_t
create_context_events_foreach(void * value,void * data)466 create_context_events_foreach (void *value,
467                                void *data)
468 {
469   /* Make a list of events holding a ref to the context they'll go to,
470    * for reentrancy robustness
471    */
472   SnMonitorContext *context = value;
473   CreateContextEventsData *ced = data;
474 
475   /* Don't send events for startup sequences initiated before the
476    * context was created
477    */
478   if (ced->base_event->sequence->creation_serial >=
479       context->creation_serial)
480     {
481       SnMonitorEvent *copy;
482 
483       copy = sn_monitor_event_copy (ced->base_event);
484       copy->context = context;
485       sn_monitor_context_ref (copy->context);
486 
487       sn_list_prepend (ced->events, copy);
488     }
489 
490   return TRUE;
491 }
492 
493 static sn_bool_t
dispatch_event_foreach(void * value,void * data)494 dispatch_event_foreach (void *value,
495                         void *data)
496 {
497   SnMonitorEvent *event = value;
498 
499   /* Dispatch and free events */
500 
501   if (event->context->event_func)
502     (* event->context->event_func) (event,
503                                     event->context->event_func_data);
504 
505   sn_monitor_event_unref (event);
506 
507   return TRUE;
508 }
509 
510 static sn_bool_t
filter_event(SnMonitorEvent * event)511 filter_event (SnMonitorEvent *event)
512 {
513   sn_bool_t retval;
514 
515   retval = FALSE;
516 
517   /* Filter out duplicate events and update flags */
518   switch (event->type)
519     {
520     case SN_MONITOR_EVENT_CANCELED:
521       if (event->sequence->canceled)
522         {
523           retval = TRUE;
524         }
525       else
526         {
527           event->sequence->canceled = TRUE;
528         }
529       break;
530     case SN_MONITOR_EVENT_COMPLETED:
531       if (event->sequence->completed)
532         {
533           retval = TRUE;
534         }
535       else
536         {
537           event->sequence->completed = TRUE;
538         }
539       break;
540 
541     default:
542       break;
543     }
544 
545   return retval;
546 }
547 
548 static SnStartupSequence*
add_sequence(SnDisplay * display)549 add_sequence (SnDisplay *display)
550 {
551   SnStartupSequence *sequence;
552 
553   sequence =
554     sn_startup_sequence_new (display);
555 
556   if (sequence)
557     {
558       sn_startup_sequence_ref (sequence); /* ref held by sequence list */
559       if (sequence_list == NULL)
560         sequence_list = sn_list_new ();
561       sn_list_prepend (sequence_list, sequence);
562     }
563 
564   return sequence;
565 }
566 
567 static void
remove_sequence(SnStartupSequence * sequence)568 remove_sequence (SnStartupSequence *sequence)
569 {
570   sn_list_remove (sequence_list, sequence);
571   sn_startup_sequence_unref (sequence);
572 }
573 
574 static void
dispatch_monitor_event(SnDisplay * display,SnMonitorEvent * event)575 dispatch_monitor_event (SnDisplay      *display,
576                         SnMonitorEvent *event)
577 {
578   if (event->type == SN_MONITOR_EVENT_INITIATED)
579     {
580       if (event->sequence == NULL)
581         event->sequence = add_sequence (display);
582     }
583 
584   if (event->sequence != NULL &&
585       !filter_event (event))
586     {
587       CreateContextEventsData cced;
588 
589       cced.base_event = event;
590       cced.events = sn_list_new ();
591 
592       sn_list_foreach (context_list, create_context_events_foreach,
593                        &cced);
594 
595       sn_list_foreach (cced.events, dispatch_event_foreach, NULL);
596 
597       /* values in the events list freed on dispatch */
598       sn_list_free (cced.events);
599 
600       /* remove from sequence list */
601       if (event->type == SN_MONITOR_EVENT_COMPLETED)
602         remove_sequence (event->sequence);
603     }
604 }
605 
606 sn_bool_t
sn_internal_monitor_process_event(SnDisplay * display)607 sn_internal_monitor_process_event (SnDisplay *display)
608 {
609   sn_bool_t retval;
610 
611   if (context_list == NULL ||
612       sn_list_empty (context_list))
613     return FALSE; /* no one cares */
614 
615   retval = FALSE;
616 
617   return retval;
618 }
619 
620 typedef struct
621 {
622   SnDisplay *display;
623   const char *id;
624   SnStartupSequence *found;
625 } FindSequenceByIdData;
626 
627 static sn_bool_t
find_sequence_by_id_foreach(void * value,void * data)628 find_sequence_by_id_foreach (void *value,
629                              void *data)
630 {
631   SnStartupSequence *sequence = value;
632   FindSequenceByIdData *fsd = data;
633 
634   if (strcmp (sequence->id, fsd->id) == 0 &&
635       sn_internal_display_get_id (sequence->display) ==
636       sn_internal_display_get_id (fsd->display))
637     {
638       fsd->found = sequence;
639       return FALSE;
640     }
641 
642   return TRUE;
643 }
644 
645 static SnStartupSequence*
find_sequence_for_id(SnDisplay * display,const char * id)646 find_sequence_for_id (SnDisplay      *display,
647                       const char     *id)
648 {
649   FindSequenceByIdData fsd;
650 
651   if (sequence_list == NULL)
652     return NULL;
653 
654   fsd.display = display;
655   fsd.id = id;
656   fsd.found = NULL;
657 
658   sn_list_foreach (sequence_list, find_sequence_by_id_foreach, &fsd);
659 
660   return fsd.found;
661 }
662 
663 static sn_bool_t
do_xmessage_event_foreach(void * value,void * data)664 do_xmessage_event_foreach (void *value,
665                            void *data)
666 {
667   SnMonitorEvent *event = value;
668   SnDisplay *display = data;
669 
670   dispatch_monitor_event (display, event);
671 
672   return TRUE;
673 }
674 
675 static sn_bool_t
unref_event_foreach(void * value,void * data)676 unref_event_foreach (void *value,
677                      void *data)
678 {
679   sn_monitor_event_unref (value);
680   return TRUE;
681 }
682 
683 static void
xmessage_func(SnDisplay * display,const char * message_type,const char * message,void * user_data)684 xmessage_func (SnDisplay  *display,
685                const char *message_type,
686                const char *message,
687                void       *user_data)
688 {
689   /* assert (strcmp (message_type, KDE_STARTUP_INFO_ATOM) == 0); */
690   char *prefix;
691   char **names;
692   char **values;
693   int i;
694   const char *launch_id;
695   SnStartupSequence *sequence;
696   SnList *events;
697 
698   prefix = NULL;
699   names = NULL;
700   values = NULL;
701   if (!sn_internal_unserialize_message (message, &prefix, &names, &values))
702     return;
703 
704   launch_id = NULL;
705   i = 0;
706   while (names[i])
707     {
708       if (strcmp (names[i], "ID") == 0)
709         {
710           launch_id = values[i];
711           break;
712         }
713       ++i;
714     }
715 
716   events = sn_list_new ();
717 
718   if (launch_id == NULL)
719     goto out;
720 
721   sequence = find_sequence_for_id (display, launch_id);
722 
723   if (strcmp (prefix, "new") == 0)
724     {
725       if (sequence == NULL)
726         {
727           SnMonitorEvent *event;
728           char *time_str;
729 
730           sequence = add_sequence (display);
731           if (sequence == NULL)
732             goto out;
733 
734           sequence->id = sn_internal_strdup (launch_id);
735 
736           /* Current spec says timestamp is part of the startup id; so we need
737            * to get the timestamp here if the launcher is using the current spec
738            */
739           time_str = sn_internal_find_last_occurrence (sequence->id, "_TIME");
740           if (time_str != NULL)
741             {
742               /* Skip past the "_TIME" part */
743               time_str += 5;
744 
745               sequence->timestamp = sn_internal_string_to_ulong (time_str);
746               sequence->timestamp_set = TRUE;
747             }
748 
749           event = sn_new (SnMonitorEvent, 1);
750 
751           event->refcount = 1;
752           event->type = SN_MONITOR_EVENT_INITIATED;
753           event->context = NULL;
754           event->sequence = sequence; /* ref from add_sequence goes here */
755 
756           sn_list_append (events, event);
757         }
758     }
759 
760   if (sequence == NULL)
761     goto out;
762 
763   if (strcmp (prefix, "change") == 0 ||
764       strcmp (prefix, "new") == 0)
765     {
766       sn_bool_t changed = FALSE;
767 
768       i = 0;
769       while (names[i])
770         {
771           if (strcmp (names[i], "BIN") == 0)
772             {
773               if (sequence->binary_name == NULL)
774                 {
775                   sequence->binary_name = sn_internal_strdup (values[i]);
776                   changed = TRUE;
777                 }
778             }
779           else if (strcmp (names[i], "NAME") == 0)
780             {
781               if (sequence->name == NULL)
782                 {
783                   sequence->name = sn_internal_strdup (values[i]);
784                   changed = TRUE;
785                 }
786             }
787           else if (strcmp (names[i], "SCREEN") == 0)
788             {
789               if (sequence->screen < 0)
790                 {
791                   int n;
792                   n = atoi (values[i]);
793                   if (n >= 0 && n < sn_internal_display_get_screen_number (sequence->display))
794                     {
795                       sequence->screen = n;
796                       changed = TRUE;
797                     }
798                 }
799             }
800           else if (strcmp (names[i], "DESCRIPTION") == 0)
801             {
802               if (sequence->description == NULL)
803                 {
804                   sequence->description = sn_internal_strdup (values[i]);
805                   changed = TRUE;
806                 }
807             }
808           else if (strcmp (names[i], "ICON") == 0)
809             {
810               if (sequence->icon_name == NULL)
811                 {
812                   sequence->icon_name = sn_internal_strdup (values[i]);
813                   changed = TRUE;
814                 }
815             }
816           else if (strcmp (names[i], "APPLICATION_ID") == 0)
817             {
818               if (sequence->application_id == NULL)
819                 {
820                   sequence->application_id = sn_internal_strdup (values[i]);
821                   changed = TRUE;
822                 }
823             }
824           else if (strcmp (names[i], "DESKTOP") == 0)
825             {
826               int workspace;
827 
828               workspace = sn_internal_string_to_ulong (values[i]);
829 
830               sequence->workspace = workspace;
831               changed = TRUE;
832             }
833           else if (strcmp (names[i], "TIMESTAMP") == 0 &&
834                    !sequence->timestamp_set)
835             {
836               /* Old version of the spec says that the timestamp was
837                * sent as part of a TIMESTAMP message.  We try to
838                * handle that to enable backwards compatibility with
839                * older launchers.
840                */
841               Time timestamp;
842 
843               timestamp = sn_internal_string_to_ulong (values[i]);
844 
845               sequence->timestamp = timestamp;
846               sequence->timestamp_set = TRUE;
847               changed = TRUE;
848             }
849           else if (strcmp (names[i], "WMCLASS") == 0)
850             {
851               if (sequence->wmclass == NULL)
852                 {
853                   sequence->wmclass = sn_internal_strdup (values[i]);
854                   changed = TRUE;
855                 }
856             }
857 
858           ++i;
859         }
860 
861       if (strcmp (prefix, "new") == 0)
862         {
863           if (sequence->screen < 0)
864             {
865               SnMonitorEvent *event;
866 
867               event = sn_new (SnMonitorEvent, 1);
868 
869               event->refcount = 1;
870               event->type = SN_MONITOR_EVENT_COMPLETED;
871               event->context = NULL;
872               event->sequence = sequence;
873               sn_startup_sequence_ref (sequence);
874 
875               sn_list_append (events, event);
876 
877               fprintf (stderr,
878                        "Ending startup notification for %s (%s) because SCREEN "
879                        "field was not provided; this is a bug in the launcher "
880                        "application\n",
881                        sequence->name ? sequence->name : "???",
882                        sequence->binary_name ? sequence->binary_name : "???");
883             }
884         }
885       else if (changed)
886         {
887           SnMonitorEvent *event;
888 
889           event = sn_new (SnMonitorEvent, 1);
890 
891           event->refcount = 1;
892           event->type = SN_MONITOR_EVENT_CHANGED;
893           event->context = NULL;
894           event->sequence = sequence;
895           sn_startup_sequence_ref (sequence);
896 
897           sn_list_append (events, event);
898         }
899     }
900   else if (strcmp (prefix, "remove") == 0)
901     {
902       SnMonitorEvent *event;
903 
904       event = sn_new (SnMonitorEvent, 1);
905 
906       event->refcount = 1;
907       event->type = SN_MONITOR_EVENT_COMPLETED;
908       event->context = NULL;
909       event->sequence = sequence;
910       sn_startup_sequence_ref (sequence);
911 
912       sn_list_append (events, event);
913     }
914 
915   sn_list_foreach (events,
916                    do_xmessage_event_foreach,
917                    display);
918 
919  out:
920   if (events != NULL)
921     {
922       sn_list_foreach (events, unref_event_foreach, NULL);
923       sn_list_free (events);
924     }
925 
926   sn_free (prefix);
927   sn_internal_strfreev (names);
928   sn_internal_strfreev (values);
929 }
930