Much work later, and I now have what I'd be prepared to accept as a
working version of DBus integration for PCB.

The patches I posted before are still necessary. It works under Lesstif
and GTK, (and any other suitable hid you'd care to invent which has
mainloop hooking functions).

I may still need to patch PCB to allow return-codes from its actions,
however if we are prepared to accept all operations succeed - we don't
ask PCB to load a file which has errors etc.., xgsch2pcb's  main
requirement - of synchronisation is achieved.

Once this is in PCB, xgsch2pcb (DBus version) can progress to where it
was originally intended.

The opportunities for IPC are now far greater, and I hope to see similar
work in gschem able to access its scheme scripting. (I have no plans to
implement that just yet, as I'm enjoying tinkering with the drawing and
data-structures).

-- 
Peter Clifton

Electrical Engineering Division,
Engineering Department,
University of Cambridge,
9, JJ Thomson Avenue,
Cambridge
CB3 0FA

Tel: +44 (0)7729 980173 - (No signal in the lab!)
/*
 * PCB, an interactive printed circuit board editor
 * D-Bus IPC logic
 * Copyright (C) 2006 University of Cambridge
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/*
 *  D-Bus code originally derrived from example-service.c in the dbus-glib bindings
 */

#define DBUS_API_SUBJECT_TO_CHANGE
#include <dbus/dbus.h>
#include <string.h>

#include "dbus.h"
#include "dbus-pcbmain.h"
#include "dbus-introspect.h"
#include "hid.h"
#include "data.h"

// For lrealpath
#include <libiberty.h>


#define PCB_DBUS_CANONICAL_NAME    "org.seul.geda.pcb"
#define PCB_DBUS_OBJECT_PATH       "/org/seul/geda/pcb"
#define PCB_DBUS_INTERFACE         "org.seul.geda.pcb"
#define PCB_DBUS_ACTIONS_INTERFACE "org.seul.geda.pcb.actions"

static DBusConnection* pcb_dbus_conn;


static DBusHandlerResult
handle_get_filename( DBusConnection *connection, DBusMessage *message, void *data)
{
  DBusMessage *reply;
  DBusMessageIter iter;
  DBusHandlerResult result;
  char *filename;

  result = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;

  // TODO: Should check the message signature matches what we expect?

  reply = dbus_message_new_method_return(message);
  if (reply == NULL)
    {
      fprintf( stderr, "pcb_dbus: Couldn't create reply message\n" );
      return result;
    }
  dbus_message_iter_init_append(reply, &iter);

  if (PCB->Filename)
    filename = lrealpath( PCB->Filename );
  else
    filename = NULL;

  if (filename == NULL)
    {
#ifdef DEBUG
      fprintf(stderr, "pcb_dbus: DEBUG: Couldn't get working filename, assuming none\n");
#endif
      filename = strdup( "" );
      if (filename == NULL)
        {
          fprintf(stderr, "pcb_dbus: Couldn't strdup( \"\" ) for the filename\n");
          goto out;
        }
    }
  if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &filename))
    {
      fprintf(stderr, "pcb_dbus: Couldn't append return filename string to message reply, Out Of Memory!\n");
      free( filename );
      goto out;
    }
  free( filename );
  if (!dbus_connection_send(connection, reply, NULL))
    {
      fprintf(stderr, "pcb_dbus: Couldn't send message, Out Of Memory!\n");
      goto out;
    }
  result = DBUS_HANDLER_RESULT_HANDLED;

out:
  dbus_message_unref( reply );
  return result;
}


static DBusHandlerResult
handle_exec_action( DBusConnection *connection, DBusMessage *message, void *data)
{
  DBusMessage *reply;
  DBusMessageIter iter;
  DBusHandlerResult result;
  DBusError err;
  dbus_uint32_t retval;
  char *action_name;
  char **argv;
  int argc;
  int i;

  result = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;

  // TODO: Should check the message signature matches what we expect?

  // initialise the error struct
  dbus_error_init(&err);

  /* DON'T FREE action_name, as it belongs to DBUS,
   * DO    FREE argv, using dbus_free_string_array()
   */
  argv = NULL;
  if (!dbus_message_get_args( message,
                              &err, 
                              DBUS_TYPE_STRING, &action_name,
                              DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &argv, &argc,
                              DBUS_TYPE_INVALID
                            ) )
    {
      fprintf(stderr, "Failed to read method arguments\n");
      if ( argv )
        dbus_free_string_array( argv );
      return result;
    }

#ifdef DEBUG
  fprintf( stderr, "pcb_dbus: DEBUG: Executing action: %s(", action_name );
  if ( argc > 0 )
    fprintf( stderr, " \"%s\"", argv[0] );
  for ( i = 1; i < argc; i++ )
    fprintf( stderr, ", \"%s\"", argv[i] );
  fprintf( stderr, " )\n" );
#endif

  // TODO: Proper return value from actions
  hid_actionv( action_name, argc, argv ); 
  retval = 0;

  dbus_free_string_array( argv );

  reply = dbus_message_new_method_return(message);
  if (reply == NULL)
    {
      fprintf( stderr, "pcb_dbus: Couldn't create reply message\n" );
      return result;
    }
  dbus_message_iter_init_append(reply, &iter);
  if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_UINT32, &retval))
    {
     fprintf(stderr, "pcb_dbus: Couldn't sent message, Out Of Memory!\n");
     goto out;
    }

  if (!dbus_connection_send(connection, reply, NULL))
    {
     fprintf(stderr, "pcb_dbus: Couldn't send message, Out Of Memory!\n");
     goto out;
    }

  result = DBUS_HANDLER_RESULT_HANDLED;
out:
  dbus_message_unref( reply );
  return result;
}


static DBusHandlerResult
handle_introspect( DBusConnection *connection, DBusMessage *message, void *data)
{
  DBusMessage *reply;
  DBusMessageIter iter;
  DBusHandlerResult result;

  result = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
  reply = dbus_message_new_method_return (message);
  if (reply == NULL)
    {
      fprintf( stderr, "pcb_dbus: Couldn't create reply message\n" );
      return result;
    }
  dbus_message_iter_init_append (reply, &iter);
  if (!dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &pcb_dbus_introspect_xml))
    {
      fprintf( stderr, "pcb_dbus: Couldn't add introspect XML to message return\n" );
      goto out;
    }
  if (! dbus_connection_send (pcb_dbus_conn, reply, NULL))
    {
      fprintf( stderr, "pcb_dbus: Couldn't queue reply message for sending\n" );
      goto out;
    }
  result = DBUS_HANDLER_RESULT_HANDLED;
out:
  dbus_message_unref( reply );
  return result;
}

static void
unregister_dbus_handler (DBusConnection *connection, void *data)
{
}


static DBusHandlerResult
handle_dbus_message (DBusConnection *connection, DBusMessage *message, void *data)
{
  int msg_type;
  msg_type = dbus_message_get_type( message );

  switch( msg_type )
    {
  case DBUS_MESSAGE_TYPE_METHOD_CALL:
      {
        const char *method_name;
        const char *interface_name;

        method_name = dbus_message_get_member( message );
        if ( method_name == NULL )
          {
            fprintf( stderr, "pcb_dbus: Method had no name specified\n");
            break;
          }

        interface_name = dbus_message_get_interface( message );
        if ( interface_name == NULL )
          {
            fprintf( stderr, "pcb_dbus: Method had no interface specified\n");
            break;
          }

        if ( strcmp( interface_name, PCB_DBUS_INTERFACE ) == 0 )
          {
            if ( strcmp( method_name, "GetFilename" ) == 0 )
              {
                return handle_get_filename( connection, message, data );
              }
            fprintf( stderr, "pcb_dbus: Interface '%s' has no method '%s'\n", interface_name, method_name );
            break;
          }
        else if ( strcmp( interface_name, PCB_DBUS_ACTIONS_INTERFACE ) == 0 )
          {
            if ( strcmp( method_name, "ExecAction" ) == 0 )
              {
                return handle_exec_action( connection, message, data );
              }
            fprintf( stderr, "pcb_dbus: Interface '%s' has no method '%s'\n", interface_name, method_name );
            break;
          }
        else if ( strcmp( interface_name, DBUS_INTERFACE_INTROSPECTABLE ) == 0 )
          {
            if ( strcmp( method_name, "Introspect" ) == 0 )
              {
                return handle_introspect( connection, message, data );
              }
            fprintf( stderr, "pcb_dbus: Interface '%s' has no method '%s'\n", interface_name, method_name );
            break;
          }
        else
          {
            fprintf( stderr, "pcb_dbus: Interface '%s' was not recognised\n", interface_name );
            break;
          }
      }
      break;

  case DBUS_MESSAGE_TYPE_METHOD_RETURN:
      fprintf( stderr, "pcb_dbus: DBUG: Method return message\n" );
      // WON'T ACTUALLY BE ANY UNLESS WE MAKE AN ASYNCRONOUS CALL?
      break;

  case DBUS_MESSAGE_TYPE_ERROR:
      fprintf( stderr, "pcb_dbus: DEBUG: Error message\n" );
      // HOPE NOT!
      break;

  case DBUS_MESSAGE_TYPE_SIGNAL:
      fprintf( stderr, "pcb_dbus: DEBUG: Signal message\n" );
      // NONE AT PRESENT
      break;

  default:
      fprintf( stderr, "pcb_dbus: DEBUG: Message type wasn't one we know about!\n" );
      return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
    }

  return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}


void
pcb_dbus_setup ( void )
{
  DBusError err;
  int ret;
  const DBusObjectPathVTable object_vtable = {
    unregister_dbus_handler,
    handle_dbus_message,
    NULL, NULL, NULL, NULL
  };

  // Initialise the error variable
  dbus_error_init(&err);

  // Connect to the bus
  pcb_dbus_conn = dbus_bus_get_private(DBUS_BUS_SESSION, &err);
  if (dbus_error_is_set(&err))
    {
     fprintf(stderr, "pcb_dbus: DBus connection Error (%s)\n", err.message);
     dbus_error_free(&err);
    }
  if (pcb_dbus_conn == NULL)
    return;

  // Request the canonical name for PCB on the bus
  ret = dbus_bus_request_name(pcb_dbus_conn, PCB_DBUS_CANONICAL_NAME,
        DBUS_NAME_FLAG_REPLACE_EXISTING
        , &err);
  if (dbus_error_is_set(&err))
    {
     fprintf(stderr, "pcb_dbus: DBus name error (%s)\n", err.message);
     dbus_error_free(&err);
    }
  if (ret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER 
      && ret != DBUS_REQUEST_NAME_REPLY_IN_QUEUE) {
    fprintf(stderr, "pcb_dbus: Couldn't gain ownership or queued ownership of the canonical DBus name\n");
    return;
  }

  if ( ! dbus_connection_register_object_path(
                                               pcb_dbus_conn,
                                               PCB_DBUS_OBJECT_PATH,
                                               &object_vtable,
                                               NULL // void * user_data
                                               ) )
    {
      fprintf(stderr, "pcb_dbus: Couldn't register DBUS handler for %s\n", PCB_DBUS_OBJECT_PATH);
      return;
    }

  // Setup intergration with the pcb mainloop
  pcb_dbus_connection_setup_with_mainloop( pcb_dbus_conn );

//  dbus_error_free(&err);
  return;
}


void
pcb_dbus_finish( void )
{
  DBusError err;

  // Initialise the error variable
  dbus_error_init(&err);

  // TODO: Could emit a "goodbye" signal here?

  dbus_connection_flush( pcb_dbus_conn );

  dbus_connection_unregister_object_path( pcb_dbus_conn, PCB_DBUS_OBJECT_PATH );

  dbus_bus_release_name( pcb_dbus_conn, PCB_DBUS_CANONICAL_NAME, &err );

  dbus_error_free(&err);

  pcb_dbus_connection_finish_with_mainloop( pcb_dbus_conn );

  dbus_connection_close( pcb_dbus_conn );
  dbus_connection_unref( pcb_dbus_conn );

  // Call DBus shutdown. This doesn't work with shared connections,
  // only private ones (like we took out earlier).
  // If any future module / plugin to PCB wants to use DBus too,
  // we must remove this call. DBus will get shut-down when the app exits.
  dbus_shutdown();
}

/*
 * PCB, an interactive printed circuit board editor
 * D-Bus IPC logic
 * Copyright (C) 2006 University of Cambridge
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
#ifndef _DBUS_H_
#define _DBUS_H_

/* Carry out all actions to setup the D-Bus and register appropriate callbacks */
void pcb_dbus_setup();

/* Carry out all actions to finalise the D-Bus connection */
void pcb_dbus_finish();


#endif /* !_DBUS_H */

Attachment: dbus.xml
Description: application/xml

/* -*- mode: C; c-file-style: "gnu" -*- */
/* dbus-pcbmain.c PCB HID main loop integration
 *
 * Adapted from dbus-gmain.c from dbus-glib bindings:
 * Copyright (C) 2002, 2003 CodeFactory AB
 * Copyright (C) 2005 Red Hat, Inc.
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#define DBUS_API_SUBJECT_TO_CHANGE
#include <dbus/dbus.h>
#include <stdio.h>

#include "global.h"
#include "dbus-pcbmain.h"

typedef struct _IOWatchHandler IOWatchHandler;
typedef struct _TimeoutHandler TimeoutHandler;

struct _IOWatchHandler
{
  DBusWatch *dbus_watch;
  hidval pcb_watch;
};

struct _TimeoutHandler
{
  DBusTimeout *dbus_timeout;
  hidval pcb_timer;
  int interval;
};


static void
block_hook_cb (hidval data)
{
  DBusConnection *connection = (DBusConnection*)data.ptr;
  if (dbus_connection_get_dispatch_status (connection) != DBUS_DISPATCH_DATA_REMAINS)
    return;

  // TODO: IS THIS NEEDED?
  // dbus_connection_ref (connection);

  /* Only dispatch once - we don't want to starve other mainloop users */
  dbus_connection_dispatch (connection);

  // dbus_connection_unref (connection);
  return;
}

static void
io_watch_handler_dbus_freed (void *data)
{
  IOWatchHandler *handler;
  handler = data;

  // Remove the watch registered with the HID
  gui->unwatch_file( handler->pcb_watch );
  free( handler );
}


void
io_watch_handler_cb (hidval pcb_watch,
                     int fd,
                     unsigned int condition,
                     hidval data)
{
  IOWatchHandler *handler;
  unsigned int dbus_condition = 0;

  handler = (IOWatchHandler *)data.ptr;

  // TODO: IS THIS NEEDED?
  //if (connection)
  //  dbus_connection_ref (connection);

  if (condition & PCB_WATCH_READABLE)
    dbus_condition |= DBUS_WATCH_READABLE;
  if (condition & PCB_WATCH_WRITABLE)
    dbus_condition |= DBUS_WATCH_WRITABLE;
  if (condition & PCB_WATCH_ERROR)
    dbus_condition |= DBUS_WATCH_ERROR;
  if (condition & PCB_WATCH_HANGUP)
    dbus_condition |= DBUS_WATCH_HANGUP;

  /* We don't touch the handler after this, because DBus
   * may have disabled the watch and thus killed the handler
   */
  dbus_watch_handle (handler->dbus_watch, dbus_condition);
  handler = NULL;

  //if (connection)
  //  dbus_connection_unref (connection);

  return;
}


static void
timeout_handler_dbus_freed (void *data)
{
  TimeoutHandler *handler;
  handler = data;

  // Remove the timeout registered with the HID
  gui->stop_timer( handler->pcb_timer );
  free( handler );
}


void
timeout_handler_cb (hidval data)
{
  TimeoutHandler *handler;
  handler = data.ptr;

  // Re-add the timeout, as PCB will remove the current one
  // Do this before calling to dbus, incase DBus removes the timeout.
  // We can't touch handler after libdbus has been run for this reason.
  handler->pcb_timer = gui->add_timer( timeout_handler_cb, handler->interval, data);

  dbus_timeout_handle (handler->dbus_timeout);
}


static dbus_bool_t
watch_add (DBusWatch *dbus_watch, void *data)
{
  IOWatchHandler *handler;
  int fd;
  unsigned int pcb_condition;
  unsigned int dbus_flags;

  // We won't create a watch until it becomes enabled.
  if (!dbus_watch_get_enabled (dbus_watch))
    return TRUE;

  dbus_flags = dbus_watch_get_flags( dbus_watch );

  pcb_condition = PCB_WATCH_ERROR | PCB_WATCH_HANGUP;
  if (dbus_flags & DBUS_WATCH_READABLE)
    pcb_condition |= PCB_WATCH_READABLE;
  if (dbus_flags & DBUS_WATCH_WRITABLE)
    pcb_condition |= PCB_WATCH_READABLE;

  fd = dbus_watch_get_fd (dbus_watch);

  handler = malloc( sizeof( IOWatchHandler ));
  handler->dbus_watch = dbus_watch;
  handler->pcb_watch = gui->watch_file(fd, pcb_condition, io_watch_handler_cb, (hidval)(void *)handler);

  dbus_watch_set_data (dbus_watch, handler, io_watch_handler_dbus_freed);
  return TRUE;
}

static void
watch_remove (DBusWatch *dbus_watch, void *data)
{
  // Free the associated data. Its destroy callback removes the watch
  dbus_watch_set_data( dbus_watch, NULL, NULL );
}

static void
watch_toggled (DBusWatch *dbus_watch, void *data)
{
  /* Simply add/remove the watch completely */
  if (dbus_watch_get_enabled (dbus_watch))
    watch_add (dbus_watch, data);
  else
    watch_remove (dbus_watch, data);
}


static dbus_bool_t
timeout_add (DBusTimeout *timeout, void *data)
{
  TimeoutHandler *handler;

  // We won't create a timeout until it becomes enabled.
  if (!dbus_timeout_get_enabled (timeout))
    return TRUE;

  //FIXME: Need to store the interval, as PCB requires us
  //       to manually re-add the timer each time it expires.
  //       This is non-ideal, and hopefully can be changed?

  handler = malloc( sizeof(TimeoutHandler ) );
  handler->dbus_timeout = timeout;
  handler->interval = dbus_timeout_get_interval( timeout );
  handler->pcb_timer = gui->add_timer( timeout_handler_cb, handler->interval, (hidval)(void *)handler);

  dbus_timeout_set_data (timeout, handler, timeout_handler_dbus_freed);
  return TRUE;
}

static void
timeout_remove (DBusTimeout *timeout, void *data)
{
  // Free the associated data. Its destroy callback removes the timer
  dbus_timeout_set_data( timeout, NULL, NULL );
}

static void
timeout_toggled (DBusTimeout *timeout, void *data)
{
  /* Simply add/remove the timeout completely */
  if (dbus_timeout_get_enabled (timeout))
    timeout_add (timeout, data);
  else
    timeout_remove (timeout, data);
}

void
dispatch_status_changed (DBusConnection *conn, DBusDispatchStatus new_status, void *data)
{
  // TODO: Can use this eventually to add one-shot idle work-functions to dispatch
  //       remaining IO. It could possibly replace the block_hook polling mechanism.
  //       (We could use a one-shot block_book to dispatch the work though.)
  //
  //       *** NO DISPATCHING TO BE DONE INSIDE THIS FUNCTION ***
}

// END INTERNALS


/**
 * Sets the watch and timeout functions of a #DBusConnection
 * to integrate the connection with the GUI HID's main loop.
 *
 * @param connection the connection
 */
void
pcb_dbus_connection_setup_with_mainloop (DBusConnection *connection)
{
  //ConnectionSetup *cs;

  /* FIXME we never free the slot, so its refcount just keeps growing,
   * which is kind of broken.
   */
  //dbus_connection_allocate_data_slot (&connection_slot);
  //if (connection_slot < 0)
  //  goto nomem;

#if 0
  cs = connection_setup_new (connection);

  if (!dbus_connection_set_data (connection, connection_slot, cs,
                                 (DBusFreeFunction)connection_setup_free))
    goto nomem;
#endif

  if (!dbus_connection_set_watch_functions (connection,
                                            watch_add,
                                            watch_remove,
                                            watch_toggled,
                                            NULL, NULL))
//                                            cs, NULL))
    goto nomem;

  if (!dbus_connection_set_timeout_functions (connection,
                                              timeout_add,
                                              timeout_remove,
                                              timeout_toggled,
                                              NULL, NULL))
//                                              cs, NULL))
    goto nomem;

  dbus_connection_set_dispatch_status_function (connection,
                                                dispatch_status_changed,
                                                NULL, NULL);
//                                                cs, NULL);

  /* Register a new mainloop hook to mop up any unfinished IO. */
  gui->add_block_hook( block_hook_cb, (hidval)(void*)connection );

  return;
nomem:
  fprintf(stderr, "Not enough memory to set up DBusConnection for use with PCB\n");
}

void
pcb_dbus_connection_finish_with_mainloop (DBusConnection *connection)
{
  //ConnectionSetup *cs;

  //cs = dbus_connection_get_data (connection, connection_slot );

  // Replace the stored data with NULL, thus freeing the old data
  // DBus will call the function connection_setup_free() which we registered earlier
  //dbus_connection_set_data (connection, connection_slot, NULL, NULL );

  //dbus_connection_free_data_slot( &connection_slot );

  if (!dbus_connection_set_watch_functions (connection,
                                            NULL,
                                            NULL,
                                            NULL,
                                            NULL, NULL))
    goto nomem;

  if (!dbus_connection_set_timeout_functions (connection,
                                              NULL,
                                              NULL,
                                              NULL,
                                              NULL, NULL))
    goto nomem;

  dbus_connection_set_dispatch_status_function (connection,
                                                NULL,
                                                NULL, NULL);
  return;
nomem:
  fprintf(stderr, "Not enough memory when cleaning up DBusConnection mainloop integration\n");

}

Attachment: dbus-pcbmain.o
Description: application/object


_______________________________________________
geda-dev mailing list
[email protected]
http://www.seul.org/cgi-bin/mailman/listinfo/geda-dev

Reply via email to