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