2006-10-01 Havoc Pennington <hp@redhat.com>
[platform/upstream/dbus.git] / tools / dbus-launch-x11.c
1 /* -*- mode: C; c-file-style: "gnu" -*- */
2 /* dbus-launch.h  dbus-launch utility
3  *
4  * Copyright (C) 2006 Thiago Macieira <thiago@kde.org>
5  *
6  * Licensed under the Academic Free License version 2.1
7  * 
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  * 
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  *
22  */
23 #include "dbus-launch.h"
24
25 #ifdef DBUS_BUILD_X11
26 #include <stdlib.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <unistd.h>
30 #include <fcntl.h>
31 #include <errno.h>
32 #include <stdio.h>
33 #include <string.h>
34 #include <pwd.h>
35 #include <X11/Xlib.h>
36 #include <X11/Xatom.h>
37
38 Display *xdisplay = NULL;
39 static Atom selection_atom;
40 static Atom address_atom;
41 static Atom pid_atom;
42
43 static int
44 x_io_error_handler (Display *xdisplay)
45 {
46   verbose ("X IO error\n");
47   kill_bus_and_exit (0);
48   return 0;
49 }
50
51 static void
52 remove_prefix (char *s,
53                char *prefix)
54 {
55   int plen;
56
57   plen = strlen (prefix);
58
59   if (strncmp (s, prefix, plen) == 0)
60     {
61       memmove (s, s + plen, strlen (s) - plen + 1);
62     }
63 }
64
65 static const char*
66 get_homedir (void)
67 {
68   const char *home;
69   
70   home = getenv ("HOME");
71   if (home == NULL)
72     {
73       /* try from the user database */
74       struct passwd *user = getpwuid (getuid());
75       if (user != NULL)
76         home = user->pw_dir;
77     }
78
79   if (home == NULL)
80     {
81       fprintf (stderr, "Can't get user home directory\n");
82       exit (1);
83     }
84
85   return home;
86 }
87
88 #define DBUS_DIR ".dbus"
89 #define DBUS_SESSION_BUS_DIR "session-bus"
90
91 static char *
92 get_session_file (void)
93 {
94   static const char prefix[] = "/" DBUS_DIR "/" DBUS_SESSION_BUS_DIR "/";
95   const char *machine;
96   const char *home;
97   char *display;
98   char *result;
99   char *p;
100
101   display = xstrdup (getenv ("DISPLAY"));
102   if (display == NULL)
103     {
104       verbose ("X11 integration disabled because X11 is not running\n");
105       return NULL;
106     }
107
108   /* remove the screen part of the display name */
109   p = strrchr (display, ':');
110   if (p != NULL)
111     {
112       for ( ; *p; ++p)
113         {
114           if (*p == '.')
115             {
116               *p = '\0';
117               break;
118             }
119         }
120     }
121
122   /* Note that we leave the hostname in the display most of the
123    * time. The idea is that we want to be per-(machine,display,user)
124    * triplet to be extra-sure we get a bus we can connect to. Ideally
125    * we'd recognize when the hostname matches the machine we're on in
126    * all cases; we do try to drop localhost and localhost.localdomain
127    * as a special common case so that alternate spellings of DISPLAY
128    * don't result in extra bus instances.
129    *
130    * We also kill the ":" if there's nothing in front of it. This
131    * avoids an ugly double underscore in the filename.
132    */
133   remove_prefix (display, "localhost.localdomain:");
134   remove_prefix (display, "localhost:");
135   remove_prefix (display, ":");
136
137   /* replace the : in the display with _ if the : is still there.
138    * use _ instead of - since it can't be in hostnames.
139    */
140   for (p = display; *p; ++p)
141     {
142       if (*p == ':')
143         *p = '_';
144     }
145   
146   machine = get_machine_uuid ();
147
148   home = get_homedir ();
149   
150   result = malloc (strlen (home) + strlen (prefix) + strlen (machine) +
151                    strlen (display) + 2);
152   if (result == NULL)
153     {
154       /* out of memory */
155       free (display);
156       return NULL;
157     }
158
159   strcpy (result, home);
160   strcat (result, prefix);
161   strcat (result, machine);
162   strcat (result, "-");
163   strcat (result, display);
164   free (display);
165
166   verbose ("session file: %s\n", result);
167   return result;
168 }
169
170 static void
171 ensure_session_directory (void)
172 {
173   const char *home;
174   char *dir;
175   
176   home = get_homedir ();
177
178   /* be sure we have space for / and nul */
179   dir = malloc (strlen (home) + strlen (DBUS_DIR) + strlen (DBUS_SESSION_BUS_DIR) + 3);
180   if (dir == NULL)
181     {
182       fprintf (stderr, "no memory\n");
183       exit (1);
184     }
185   
186   strcpy (dir, home);
187   strcat (dir, "/");
188   strcat (dir, DBUS_DIR);
189
190   if (mkdir (dir, 0700) < 0)
191     {
192       /* only print a warning here, writing the session file itself will fail later */
193       if (errno != EEXIST)
194         fprintf (stderr, "Unable to create %s\n", dir);
195     }
196
197   strcat (dir, "/");
198   strcat (dir, DBUS_SESSION_BUS_DIR);
199
200   if (mkdir (dir, 0700) < 0)
201     {
202       /* only print a warning here, writing the session file itself will fail later */
203       if (errno != EEXIST)
204         fprintf (stderr, "Unable to create %s\n", dir);
205     }
206   
207   free (dir);
208 }
209
210 static Display *
211 open_x11 (void)
212 {
213   if (xdisplay != NULL)
214     return xdisplay;
215
216   xdisplay = XOpenDisplay (NULL);
217   if (xdisplay != NULL)
218     {
219       verbose ("Connected to X11 display '%s'\n", DisplayString (xdisplay));
220       XSetIOErrorHandler (x_io_error_handler);
221     }
222   return xdisplay;
223 }
224
225 static int
226 init_x_atoms (Display *display)
227 {
228   static const char selection_prefix[] = "_DBUS_SESSION_BUS_SELECTION_";
229   static const char address_prefix[] = "_DBUS_SESSION_BUS_ADDRESS";
230   static const char pid_prefix[] = "_DBUS_SESSION_BUS_PID";
231   static int init = FALSE;
232   char *atom_name;
233   const char *machine;
234   char *user_name;
235   struct passwd *user;
236
237   if (init)
238     return TRUE;
239
240   user = getpwuid (getuid ());
241   if (user == NULL)
242     {
243       verbose ("Could not determine the user informations; aborting X11 integration.\n");
244       return FALSE;
245     }
246   user_name = xstrdup(user->pw_name);
247
248   machine = get_machine_uuid ();
249
250   atom_name = malloc (strlen (machine) + strlen (user_name) + 2 +
251                       MAX (strlen (selection_prefix),
252                            MAX (strlen (address_prefix),
253                                 strlen (pid_prefix))));
254   if (atom_name == NULL)
255     {
256       verbose ("Could not create X11 atoms; aborting X11 integration.\n");
257       free (user_name);
258       return FALSE;
259     }
260
261   /* create the selection atom */
262   strcpy (atom_name, selection_prefix);
263   strcat (atom_name, user_name);
264   strcat (atom_name, "_");
265   strcat (atom_name, machine);
266   selection_atom = XInternAtom (display, atom_name, FALSE);
267
268   /* create the address property atom */
269   strcpy (atom_name, address_prefix);
270   address_atom = XInternAtom (display, atom_name, FALSE);
271
272   /* create the PID property atom */
273   strcpy (atom_name, pid_prefix);
274   pid_atom = XInternAtom (display, atom_name, FALSE);
275
276   free (atom_name);
277   free (user_name);
278   init = TRUE;
279   return TRUE;
280 }
281
282 /*
283  * Gets the daemon address from the X11 display.
284  * Returns FALSE if there was an error. Returning
285  * TRUE does not mean the address exists.
286  */
287 int
288 x11_get_address (char **paddress, pid_t *pid, long *wid)
289 {
290   Atom type;
291   Window owner;
292   int format;
293   unsigned long items;
294   unsigned long after;
295   char *data;
296
297   *paddress = NULL;
298
299   /* locate the selection owner */
300   owner = XGetSelectionOwner (xdisplay, selection_atom);
301   if (owner == None)
302     return TRUE;                /* no owner */
303   if (wid != NULL)
304     *wid = (long) owner;
305
306   /* get the bus address */
307   XGetWindowProperty (xdisplay, owner, address_atom, 0, 1024, False,
308                       XA_STRING, &type, &format, &items, &after,
309                       (unsigned char **) &data);
310   if (type == None || after != 0 || data == NULL || format != 8)
311     return FALSE;               /* error */
312
313   *paddress = xstrdup (data);
314   XFree (data);
315
316   /* get the PID */
317   if (pid != NULL)
318     {
319       *pid = 0;
320       XGetWindowProperty (xdisplay, owner, pid_atom, 0, sizeof pid, False,
321                           XA_CARDINAL, &type, &format, &items, &after,
322                           (unsigned char **) &data);
323       if (type != None && after == 0 && data != NULL && format == 32)
324         *pid = (pid_t) *(long*) data;
325       XFree (data);
326     }
327
328   return TRUE;                  /* success */
329 }
330
331 /*
332  * Saves the address in the X11 display. Returns 0 on success.
333  * If an error occurs, returns -1. If the selection already exists,
334  * returns 1. (i.e. another daemon is already running)
335  */
336 static Window
337 set_address_in_x11(char *address, pid_t pid)
338 {
339   char *current_address;
340   Window wid;
341   int pid32;
342   
343   /* lock the X11 display to make sure we're doing this atomically */
344   XGrabServer (xdisplay);
345
346   if (!x11_get_address (&current_address, NULL, NULL))
347     {
348       /* error! */
349       XUngrabServer (xdisplay);
350       return None;
351     }
352
353   if (current_address != NULL)
354     {
355       /* someone saved the address in the meantime */
356       XUngrabServer (xdisplay);
357       free (current_address);
358       return None;
359     }
360
361   /* Create our window */
362   wid = XCreateSimpleWindow (xdisplay, RootWindow (xdisplay, 0), -20, -20, 10, 10,
363                              0, WhitePixel (xdisplay, 0),
364                              BlackPixel (xdisplay, 0));
365   verbose ("Created window %d\n", wid);
366
367   /* Save the property in the window */
368   XChangeProperty (xdisplay, wid, address_atom, XA_STRING, 8, PropModeReplace,
369                    (unsigned char *)address, strlen (address));
370   pid32 = pid;
371   if (sizeof(pid32) != 4)
372     {
373       fprintf (stderr, "int is not 32 bits!\n");
374       exit (1);
375     }
376   XChangeProperty (xdisplay, wid, pid_atom, XA_CARDINAL, 32, PropModeReplace,
377                    (unsigned char *)&pid32, 1);
378
379   /* Now grab the selection */
380   XSetSelectionOwner (xdisplay, selection_atom, wid, CurrentTime);
381
382   /* Ungrab the server to let other people use it too */
383   XUngrabServer (xdisplay);
384
385   XFlush (xdisplay);
386
387   return wid;
388 }
389
390 /*
391  * Saves the session address in session file. Returns TRUE on
392  * success, FALSE if an error occurs.
393  */
394 static int
395 set_address_in_file (char *address, pid_t pid, Window wid)
396 {
397   char *session_file;
398   FILE *f;
399
400   ensure_session_directory ();
401   session_file = get_session_file();
402   if (session_file == NULL)
403     return FALSE;
404
405   f = fopen (session_file, "w");
406   if (f == NULL)
407     return FALSE;               /* some kind of error */
408   fprintf (f,
409            "# This file allows processes on the machine with id %s using \n"
410            "# display %s to find the D-Bus session bus with the below address.\n"
411            "# If the DBUS_SESSION_BUS_ADDRESS environment variable is set, it will\n"
412            "# be used rather than this file.\n"
413            "# See \"man dbus-launch\" for more details.\n"
414            "DBUS_SESSION_BUS_ADDRESS=%s\n"
415            "DBUS_SESSION_BUS_PID=%ld\n"
416            "DBUS_SESSION_BUS_WINDOWID=%ld\n",
417            get_machine_uuid (),
418            getenv ("DISPLAY"),
419            address, (long)pid, (long)wid);
420
421   fclose (f);
422   free (session_file);
423
424   return TRUE;
425 }
426
427 int
428 x11_save_address (char *address, pid_t pid, long *wid)
429 {
430   Window id = set_address_in_x11 (address, pid);
431   if (id != None)
432     {
433       if (!set_address_in_file (address, pid, id))
434         return FALSE;
435
436       if (wid != NULL)
437         *wid = (long) id;
438       return TRUE;
439     }
440   return FALSE;
441 }
442
443 int
444 x11_init (void)
445 {
446   return open_x11 () != NULL && init_x_atoms (xdisplay);
447 }
448
449 void
450 x11_handle_event (void)
451 {
452   if (xdisplay != NULL)
453     {      
454       while (XPending (xdisplay))
455         {
456           XEvent ignored;
457           XNextEvent (xdisplay, &ignored);
458         }
459     }
460 }  
461
462 #else
463 void dummy_dbus_launch_x11 (void) { }
464 #endif