/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ /* dbus-launch.h dbus-launch utility * * Copyright (C) 2006 Thiago Macieira <thiago@kde.org> * * Licensed under the Academic Free License version 2.1 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include <config.h> #include "dbus-launch.h" #ifdef DBUS_BUILD_X11 #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <stdio.h> #include <string.h> #include <pwd.h> #include <X11/Xlib.h> #include <X11/Xatom.h> Display *xdisplay = NULL; static Atom selection_atom; static Atom address_atom; static Atom pid_atom; static int x_io_error_handler (Display *xdisplay) { verbose ("X IO error\n"); kill_bus_and_exit (0); return 0; } static void remove_prefix (char *s, char *prefix) { int plen; plen = strlen (prefix); if (strncmp (s, prefix, plen) == 0) { memmove (s, s + plen, strlen (s) - plen + 1); } } static const char* get_homedir (void) { const char *home; home = getenv ("HOME"); if (home == NULL) { /* try from the user database */ struct passwd *user = getpwuid (getuid()); if (user != NULL) home = user->pw_dir; } if (home == NULL) { fprintf (stderr, "Can't get user home directory\n"); exit (1); } return home; } #define DBUS_DIR ".dbus" #define DBUS_SESSION_BUS_DIR "session-bus" static char * get_session_file (void) { static const char prefix[] = "/" DBUS_DIR "/" DBUS_SESSION_BUS_DIR "/"; const char *machine; const char *home; char *display; char *result; char *p; machine = get_machine_uuid (); if (machine == NULL) return NULL; display = xstrdup (getenv ("DISPLAY")); if (display == NULL) { verbose ("X11 integration disabled because X11 is not running\n"); return NULL; } /* remove the screen part of the display name */ p = strrchr (display, ':'); if (p != NULL) { for ( ; *p; ++p) { if (*p == '.') { *p = '\0'; break; } } } /* Note that we leave the hostname in the display most of the * time. The idea is that we want to be per-(machine,display,user) * triplet to be extra-sure we get a bus we can connect to. Ideally * we'd recognize when the hostname matches the machine we're on in * all cases; we do try to drop localhost and localhost.localdomain * as a special common case so that alternate spellings of DISPLAY * don't result in extra bus instances. * * We also kill the ":" if there's nothing in front of it. This * avoids an ugly double underscore in the filename. */ remove_prefix (display, "localhost.localdomain:"); remove_prefix (display, "localhost:"); remove_prefix (display, ":"); /* replace the : in the display with _ if the : is still there. * use _ instead of - since it can't be in hostnames. */ for (p = display; *p; ++p) { if (*p == ':') *p = '_'; } home = get_homedir (); result = malloc (strlen (home) + strlen (prefix) + strlen (machine) + strlen (display) + 2); if (result == NULL) { /* out of memory */ free (display); return NULL; } strcpy (result, home); strcat (result, prefix); strcat (result, machine); strcat (result, "-"); strcat (result, display); free (display); verbose ("session file: %s\n", result); return result; } static void ensure_session_directory (void) { const char *home; char *dir; home = get_homedir (); /* be sure we have space for / and nul */ dir = malloc (strlen (home) + strlen (DBUS_DIR) + strlen (DBUS_SESSION_BUS_DIR) + 3); if (dir == NULL) { fprintf (stderr, "no memory\n"); exit (1); } strcpy (dir, home); strcat (dir, "/"); strcat (dir, DBUS_DIR); if (mkdir (dir, 0700) < 0) { /* only print a warning here, writing the session file itself will fail later */ if (errno != EEXIST) fprintf (stderr, "Unable to create %s\n", dir); } strcat (dir, "/"); strcat (dir, DBUS_SESSION_BUS_DIR); if (mkdir (dir, 0700) < 0) { /* only print a warning here, writing the session file itself will fail later */ if (errno != EEXIST) fprintf (stderr, "Unable to create %s\n", dir); } free (dir); } static Display * open_x11 (void) { if (xdisplay != NULL) return xdisplay; xdisplay = XOpenDisplay (NULL); if (xdisplay != NULL) { verbose ("Connected to X11 display '%s'\n", DisplayString (xdisplay)); XSetIOErrorHandler (x_io_error_handler); } return xdisplay; } static int init_x_atoms (Display *display) { static const char selection_prefix[] = "_DBUS_SESSION_BUS_SELECTION_"; static const char address_prefix[] = "_DBUS_SESSION_BUS_ADDRESS"; static const char pid_prefix[] = "_DBUS_SESSION_BUS_PID"; static int init = FALSE; char *atom_name; const char *machine; char *user_name; struct passwd *user; if (init) return TRUE; machine = get_machine_uuid (); if (machine == NULL) return FALSE; user = getpwuid (getuid ()); if (user == NULL) { verbose ("Could not determine user information; aborting X11 integration.\n"); return FALSE; } user_name = xstrdup(user->pw_name); atom_name = malloc (strlen (machine) + strlen (user_name) + 2 + MAX (strlen (selection_prefix), MAX (strlen (address_prefix), strlen (pid_prefix)))); if (atom_name == NULL) { verbose ("Could not create X11 atoms; aborting X11 integration.\n"); free (user_name); return FALSE; } /* create the selection atom */ strcpy (atom_name, selection_prefix); strcat (atom_name, user_name); strcat (atom_name, "_"); strcat (atom_name, machine); selection_atom = XInternAtom (display, atom_name, FALSE); /* create the address property atom */ strcpy (atom_name, address_prefix); address_atom = XInternAtom (display, atom_name, FALSE); /* create the PID property atom */ strcpy (atom_name, pid_prefix); pid_atom = XInternAtom (display, atom_name, FALSE); free (atom_name); free (user_name); init = TRUE; return TRUE; } /* * Gets the daemon address from the X11 display. * Returns FALSE if there was an error. Returning * TRUE does not mean the address exists. */ int x11_get_address (char **paddress, pid_t *pid, long *wid) { int result; Atom type; Window owner; int format; unsigned long items; unsigned long after; char *data; *paddress = NULL; /* locate the selection owner */ owner = XGetSelectionOwner (xdisplay, selection_atom); if (owner == None) return TRUE; /* no owner */ if (wid != NULL) *wid = (long) owner; /* get the bus address */ result = XGetWindowProperty (xdisplay, owner, address_atom, 0, 1024, False, XA_STRING, &type, &format, &items, &after, (unsigned char **) &data); if (result != Success || type == None || after != 0 || data == NULL || format != 8) return FALSE; /* error */ *paddress = xstrdup (data); XFree (data); /* get the PID */ if (pid != NULL) { *pid = 0; result = XGetWindowProperty (xdisplay, owner, pid_atom, 0, sizeof pid, False, XA_CARDINAL, &type, &format, &items, &after, (unsigned char **) &data); if (result == Success && type != None && after == 0 && data != NULL && format == 32) *pid = (pid_t) *(long*) data; XFree (data); } return TRUE; /* success */ } /* * Saves the address in the X11 display. Returns 0 on success. * If an error occurs, returns -1. If the selection already exists, * returns 1. (i.e. another daemon is already running) */ static Window set_address_in_x11(char *address, pid_t pid) { char *current_address; Window wid = None; unsigned long pid32; /* Xlib property functions want _long_ not 32-bit for format "32" */ /* lock the X11 display to make sure we're doing this atomically */ XGrabServer (xdisplay); if (!x11_get_address (¤t_address, NULL, NULL)) { /* error! */ goto out; } if (current_address != NULL) { /* someone saved the address in the meantime */ free (current_address); goto out; } /* Create our window */ wid = XCreateWindow (xdisplay, RootWindow (xdisplay, 0), -20, -20, 10, 10, 0, CopyFromParent, InputOnly, CopyFromParent, 0, NULL); verbose ("Created window %d\n", wid); /* Save the property in the window */ XChangeProperty (xdisplay, wid, address_atom, XA_STRING, 8, PropModeReplace, (unsigned char *)address, strlen (address)); pid32 = pid; XChangeProperty (xdisplay, wid, pid_atom, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&pid32, 1); /* Now grab the selection */ XSetSelectionOwner (xdisplay, selection_atom, wid, CurrentTime); out: /* Ungrab the server to let other people use it too */ XUngrabServer (xdisplay); /* And make sure that the ungrab gets sent to X11 */ XFlush (xdisplay); return wid; } /* * Saves the session address in session file. Returns TRUE on * success, FALSE if an error occurs. */ static int set_address_in_file (char *address, pid_t pid, Window wid) { char *session_file; FILE *f; ensure_session_directory (); session_file = get_session_file(); if (session_file == NULL) return FALSE; f = fopen (session_file, "w"); free (session_file); if (f == NULL) return FALSE; /* some kind of error */ fprintf (f, "# This file allows processes on the machine with id %s using \n" "# display %s to find the D-Bus session bus with the below address.\n" "# If the DBUS_SESSION_BUS_ADDRESS environment variable is set, it will\n" "# be used rather than this file.\n" "# See \"man dbus-launch\" for more details.\n" "DBUS_SESSION_BUS_ADDRESS=%s\n" "DBUS_SESSION_BUS_PID=%ld\n" "DBUS_SESSION_BUS_WINDOWID=%ld\n", get_machine_uuid (), getenv ("DISPLAY"), address, (long)pid, (long)wid); fclose (f); return TRUE; } int x11_save_address (char *address, pid_t pid, long *wid) { Window id = set_address_in_x11 (address, pid); if (id != None) { if (!set_address_in_file (address, pid, id)) return FALSE; if (wid != NULL) *wid = (long) id; return TRUE; } return FALSE; } int x11_init (void) { return open_x11 () != NULL && init_x_atoms (xdisplay); } void x11_handle_event (void) { if (xdisplay != NULL) { while (XPending (xdisplay)) { XEvent ignored; XNextEvent (xdisplay, &ignored); } } } #else void dummy_dbus_launch_x11 (void); void dummy_dbus_launch_x11 (void) { } #endif