Bug 21161 - Update the FSF address
[platform/upstream/dbus.git] / tools / dbus-launch-x11.c
1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  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   machine = get_machine_uuid ();
102   if (machine == NULL)
103     return NULL;
104
105   display = xstrdup (getenv ("DISPLAY"));
106   if (display == NULL)
107     {
108       verbose ("X11 integration disabled because X11 is not running\n");
109       return NULL;
110     }
111
112   /* remove the screen part of the display name */
113   p = strrchr (display, ':');
114   if (p != NULL)
115     {
116       for ( ; *p; ++p)
117         {
118           if (*p == '.')
119             {
120               *p = '\0';
121               break;
122             }
123         }
124     }
125
126   /* Note that we leave the hostname in the display most of the
127    * time. The idea is that we want to be per-(machine,display,user)
128    * triplet to be extra-sure we get a bus we can connect to. Ideally
129    * we'd recognize when the hostname matches the machine we're on in
130    * all cases; we do try to drop localhost and localhost.localdomain
131    * as a special common case so that alternate spellings of DISPLAY
132    * don't result in extra bus instances.
133    *
134    * We also kill the ":" if there's nothing in front of it. This
135    * avoids an ugly double underscore in the filename.
136    */
137   remove_prefix (display, "localhost.localdomain:");
138   remove_prefix (display, "localhost:");
139   remove_prefix (display, ":");
140
141   /* replace the : in the display with _ if the : is still there.
142    * use _ instead of - since it can't be in hostnames.
143    */
144   for (p = display; *p; ++p)
145     {
146       if (*p == ':')
147         *p = '_';
148     }
149   
150   home = get_homedir ();
151   
152   result = malloc (strlen (home) + strlen (prefix) + strlen (machine) +
153                    strlen (display) + 2);
154   if (result == NULL)
155     {
156       /* out of memory */
157       free (display);
158       return NULL;
159     }
160
161   strcpy (result, home);
162   strcat (result, prefix);
163   strcat (result, machine);
164   strcat (result, "-");
165   strcat (result, display);
166   free (display);
167
168   verbose ("session file: %s\n", result);
169   return result;
170 }
171
172 static void
173 ensure_session_directory (void)
174 {
175   const char *home;
176   char *dir;
177   
178   home = get_homedir ();
179
180   /* be sure we have space for / and nul */
181   dir = malloc (strlen (home) + strlen (DBUS_DIR) + strlen (DBUS_SESSION_BUS_DIR) + 3);
182   if (dir == NULL)
183     {
184       fprintf (stderr, "no memory\n");
185       exit (1);
186     }
187   
188   strcpy (dir, home);
189   strcat (dir, "/");
190   strcat (dir, DBUS_DIR);
191
192   if (mkdir (dir, 0700) < 0)
193     {
194       /* only print a warning here, writing the session file itself will fail later */
195       if (errno != EEXIST)
196         fprintf (stderr, "Unable to create %s\n", dir);
197     }
198
199   strcat (dir, "/");
200   strcat (dir, DBUS_SESSION_BUS_DIR);
201
202   if (mkdir (dir, 0700) < 0)
203     {
204       /* only print a warning here, writing the session file itself will fail later */
205       if (errno != EEXIST)
206         fprintf (stderr, "Unable to create %s\n", dir);
207     }
208   
209   free (dir);
210 }
211
212 static Display *
213 open_x11 (void)
214 {
215   if (xdisplay != NULL)
216     return xdisplay;
217
218   xdisplay = XOpenDisplay (NULL);
219   if (xdisplay != NULL)
220     {
221       verbose ("Connected to X11 display '%s'\n", DisplayString (xdisplay));
222       XSetIOErrorHandler (x_io_error_handler);
223     }
224   return xdisplay;
225 }
226
227 static int
228 init_x_atoms (Display *display)
229 {
230   static const char selection_prefix[] = "_DBUS_SESSION_BUS_SELECTION_";
231   static const char address_prefix[] = "_DBUS_SESSION_BUS_ADDRESS";
232   static const char pid_prefix[] = "_DBUS_SESSION_BUS_PID";
233   static int init = FALSE;
234   char *atom_name;
235   const char *machine;
236   char *user_name;
237   struct passwd *user;
238
239   if (init)
240     return TRUE;
241
242   machine = get_machine_uuid ();
243   if (machine == NULL)
244     return FALSE;
245
246   user = getpwuid (getuid ());
247   if (user == NULL)
248     {
249       verbose ("Could not determine the user informations; aborting X11 integration.\n");
250       return FALSE;
251     }
252   user_name = xstrdup(user->pw_name);
253
254   atom_name = malloc (strlen (machine) + strlen (user_name) + 2 +
255                       MAX (strlen (selection_prefix),
256                            MAX (strlen (address_prefix),
257                                 strlen (pid_prefix))));
258   if (atom_name == NULL)
259     {
260       verbose ("Could not create X11 atoms; aborting X11 integration.\n");
261       free (user_name);
262       return FALSE;
263     }
264
265   /* create the selection atom */
266   strcpy (atom_name, selection_prefix);
267   strcat (atom_name, user_name);
268   strcat (atom_name, "_");
269   strcat (atom_name, machine);
270   selection_atom = XInternAtom (display, atom_name, FALSE);
271
272   /* create the address property atom */
273   strcpy (atom_name, address_prefix);
274   address_atom = XInternAtom (display, atom_name, FALSE);
275
276   /* create the PID property atom */
277   strcpy (atom_name, pid_prefix);
278   pid_atom = XInternAtom (display, atom_name, FALSE);
279
280   free (atom_name);
281   free (user_name);
282   init = TRUE;
283   return TRUE;
284 }
285
286 /*
287  * Gets the daemon address from the X11 display.
288  * Returns FALSE if there was an error. Returning
289  * TRUE does not mean the address exists.
290  */
291 int
292 x11_get_address (char **paddress, pid_t *pid, long *wid)
293 {
294   Atom type;
295   Window owner;
296   int format;
297   unsigned long items;
298   unsigned long after;
299   char *data;
300
301   *paddress = NULL;
302
303   /* locate the selection owner */
304   owner = XGetSelectionOwner (xdisplay, selection_atom);
305   if (owner == None)
306     return TRUE;                /* no owner */
307   if (wid != NULL)
308     *wid = (long) owner;
309
310   /* get the bus address */
311   XGetWindowProperty (xdisplay, owner, address_atom, 0, 1024, False,
312                       XA_STRING, &type, &format, &items, &after,
313                       (unsigned char **) &data);
314   if (type == None || after != 0 || data == NULL || format != 8)
315     return FALSE;               /* error */
316
317   *paddress = xstrdup (data);
318   XFree (data);
319
320   /* get the PID */
321   if (pid != NULL)
322     {
323       *pid = 0;
324       XGetWindowProperty (xdisplay, owner, pid_atom, 0, sizeof pid, False,
325                           XA_CARDINAL, &type, &format, &items, &after,
326                           (unsigned char **) &data);
327       if (type != None && after == 0 && data != NULL && format == 32)
328         *pid = (pid_t) *(long*) data;
329       XFree (data);
330     }
331
332   return TRUE;                  /* success */
333 }
334
335 /*
336  * Saves the address in the X11 display. Returns 0 on success.
337  * If an error occurs, returns -1. If the selection already exists,
338  * returns 1. (i.e. another daemon is already running)
339  */
340 static Window
341 set_address_in_x11(char *address, pid_t pid)
342 {
343   char *current_address;
344   Window wid = None;
345   unsigned long pid32; /* Xlib property functions want _long_ not 32-bit for format "32" */
346   
347   /* lock the X11 display to make sure we're doing this atomically */
348   XGrabServer (xdisplay);
349
350   if (!x11_get_address (&current_address, NULL, NULL))
351     {
352       /* error! */
353       goto out;
354     }
355
356   if (current_address != NULL)
357     {
358       /* someone saved the address in the meantime */
359       free (current_address);
360       goto out;
361     }
362
363   /* Create our window */
364   wid = XCreateWindow (xdisplay, RootWindow (xdisplay, 0), -20, -20, 10, 10,
365                        0, CopyFromParent, InputOnly, CopyFromParent,
366                        0, NULL);
367   verbose ("Created window %d\n", wid);
368
369   /* Save the property in the window */
370   XChangeProperty (xdisplay, wid, address_atom, XA_STRING, 8, PropModeReplace,
371                    (unsigned char *)address, strlen (address));
372   pid32 = pid;
373   XChangeProperty (xdisplay, wid, pid_atom, XA_CARDINAL, 32, PropModeReplace,
374                    (unsigned char *)&pid32, 1);
375
376   /* Now grab the selection */
377   XSetSelectionOwner (xdisplay, selection_atom, wid, CurrentTime);
378
379  out:
380   /* Ungrab the server to let other people use it too */
381   XUngrabServer (xdisplay);
382
383   /* And make sure that the ungrab gets sent to X11 */
384   XFlush (xdisplay);
385
386   return wid;
387 }
388
389 /*
390  * Saves the session address in session file. Returns TRUE on
391  * success, FALSE if an error occurs.
392  */
393 static int
394 set_address_in_file (char *address, pid_t pid, Window wid)
395 {
396   char *session_file;
397   FILE *f;
398
399   ensure_session_directory ();
400   session_file = get_session_file();
401   if (session_file == NULL)
402     return FALSE;
403
404   f = fopen (session_file, "w");
405   if (f == NULL)
406     return FALSE;               /* some kind of error */
407   fprintf (f,
408            "# This file allows processes on the machine with id %s using \n"
409            "# display %s to find the D-Bus session bus with the below address.\n"
410            "# If the DBUS_SESSION_BUS_ADDRESS environment variable is set, it will\n"
411            "# be used rather than this file.\n"
412            "# See \"man dbus-launch\" for more details.\n"
413            "DBUS_SESSION_BUS_ADDRESS=%s\n"
414            "DBUS_SESSION_BUS_PID=%ld\n"
415            "DBUS_SESSION_BUS_WINDOWID=%ld\n",
416            get_machine_uuid (),
417            getenv ("DISPLAY"),
418            address, (long)pid, (long)wid);
419
420   fclose (f);
421   free (session_file);
422
423   return TRUE;
424 }
425
426 int
427 x11_save_address (char *address, pid_t pid, long *wid)
428 {
429   Window id = set_address_in_x11 (address, pid);
430   if (id != None)
431     {
432       if (!set_address_in_file (address, pid, id))
433         return FALSE;
434
435       if (wid != NULL)
436         *wid = (long) id;
437       return TRUE;
438     }
439   return FALSE;
440 }
441
442 int
443 x11_init (void)
444 {
445   return open_x11 () != NULL && init_x_atoms (xdisplay);
446 }
447
448 void
449 x11_handle_event (void)
450 {
451   if (xdisplay != NULL)
452     {      
453       while (XPending (xdisplay))
454         {
455           XEvent ignored;
456           XNextEvent (xdisplay, &ignored);
457         }
458     }
459 }  
460
461 #else
462 void dummy_dbus_launch_x11 (void) { }
463 #endif