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