Resource editor plugin examples

This chapter contains examples of using the PhAB plugin API to create resource editors. There are two examples:

A string editor

The following is an example resource editor plugin for editing a string resource. This is taken from the actual PhAB source code. Not shown is the PhAB project which contains this source file and implements the widgets within a picture module.

#include <Pt.h>
#include <Ap.h>


/* Local headers */
#include "rstring.h"
#include "abimport.h"
#include "proto.h"

static int plugin_loading( const char *path );
static void plugin_unloading( void );
static void plugin_frugal_set_data( ResPluginHandle_t handle, int n,
    const void *value, const ResPluginFormatData_t *format );
static void plugin_full_set_data( ResPluginHandle_t handle, int n,
    const void *value, const ResPluginFormatData_t *format );
static void plugin_frugal_destroy( ResPluginHandle_t handle );
static void plugin_full_destroy( ResPluginHandle_t handle );
static ResPluginHandle_t plugin_frugal_create
        ( PhABHandle_t phab , const PhABResExportFrugal_t *exp,
          const ResPluginFormatData_t *format,
          int n_default, const void *default_value,
          PtWidget_t *parent,
          int n, const void *value );
static ResPluginHandle_t plugin_full_create
        ( PhABHandle_t phab , const PhABResExportFull_t *exp,
          const ResPluginFormatData_t *format,
          int n_default, const void *default_value,
          PhArea_t *area, char *caption,
          int n, const void *value );
static void plugin_full_disable( ResPluginHandle_t handle );
static void plugin_full_block( ResPluginHandle_t handle, int block );
static void plugin_full_to_front( ResPluginHandle_t handle );
static int plugin_full_any_changes( ResPluginHandle_t handle );
static void plugin_full_get_changes( ResPluginHandle_t handle, int *pn, void **pvalue );
static void plugin_full_get_area( ResPluginHandle_t handle, PhArea_t *area );
static void plugin_full_notify( ResPluginAction_t action, void *data );

/* callbacks */
static int plugin_frugal_changed( PtWidget_t *widget, void *data, PtCallbackInfo_t *cbinfo );
static int plugin_full_resize( PtWidget_t *widget, void *data, PtCallbackInfo_t *cbinfo );
static int plugin_full_changed( PtWidget_t *widget, void *data, PtCallbackInfo_t *cbinfo );
static int plugin_full_done( PtWidget_t *widget, void *data, PtCallbackInfo_t *cbinfo );
static void set_state( PluginFullInstance_t *instance, char *value );

static ResPluginFullEditor_t full_editors[1] =
    {
        {
        {   "string",
            RES_DATATYPE_STRING,
            RESPLUGIN_DATATYPE_VERSION,
            plugin_full_set_data,
            plugin_full_destroy },
        plugin_full_create,
        plugin_full_disable,
        plugin_full_block,
        plugin_full_to_front,
        plugin_full_any_changes,
        plugin_full_get_changes,
        plugin_full_get_area
        }
    };

static ResPluginFrugalEditor_t frugal_editors[1] =
    {
        {
        {   "string",
            RES_DATATYPE_STRING,
            RESPLUGIN_DATATYPE_VERSION,
            plugin_frugal_set_data,
            plugin_frugal_destroy },
        plugin_frugal_create,
        }
    };

static ResPlugin_t tab = {
    plugin_loading,
    plugin_unloading,
    1,
    full_editors,
    1,
    frugal_editors
    };

AOInterface_t string_interfaces[] = {
    { "PhAB Resource Editors", RESPLUGIN_VERSION, &tab },
    { 0, 0, 0 }
    };


static int loaded_count = 0;
static ApDBase_t *db = NULL;

static int plugin_loading( const char *path ) {
  if( loaded_count == 0 && ApAddContext( &AbContext, path ) )
    return -1;

  db = ApOpenDBase( ABM_string_db );
  ++loaded_count;
  return 0;
    }

static void plugin_unloading( void ) {
  if( -- loaded_count == 0 ) {
    ApCloseDBase( db );
    db = NULL;
    ApRemoveContext( &AbContext );
    }
    }

static ResPluginHandle_t plugin_frugal_create
        ( PhABHandle_t phab , const PhABResExportFrugal_t *exp,
          const ResPluginFormatData_t *format,
          int n_default, const void *default_value,
          PtWidget_t *parent,
          int n, const void *value ) {

    PtArg_t args[2];
    PhRect_t offset = { { -1, -1 }, { -1, -1 } };
    PluginFrugalInstance_t *instance;

    instance = calloc( 1, sizeof( *instance ) );
    if( !instance ) return NULL;

    instance->n_default = n_default;
    instance->default_value = default_value;
    instance->n_master = n;
    instance->value_master = value;

    instance->phab = phab;
    instance->exp = exp;

    PtSetArg( &args[0], Pt_ARG_ANCHOR_OFFSETS, &offset, 0 );
    PtSetArg( &args[1], Pt_ARG_TEXT_STRING, value, 0 );
    instance->frugal = ApCreateWidget( db, "string_frugal", 0, 0, 2, args );
    PtAddCallback( instance->frugal, Pt_CB_TEXT_CHANGED, plugin_frugal_changed,
        instance );
    return ( ResPluginHandle_t ) instance;
    }

static void plugin_frugal_set_data( ResPluginHandle_t handle, int n,
    const void *value, const ResPluginFormatData_t *format )
{
    PluginFrugalInstance_t *instance = ( PluginFrugalInstance_t * ) handle;
    instance->n_master = n;
    instance->value_master = value;
    PtSetResource( instance->frugal, Pt_ARG_TEXT_STRING, value, 0 );
}


static int plugin_frugal_changed( PtWidget_t *widget, void *data,
    PtCallbackInfo_t *cbinfo ) {
    PluginFrugalInstance_t *instance = ( PluginFrugalInstance_t * ) data;
    char *p;
    PtGetResource( widget, Pt_ARG_TEXT_STRING, &p, 0 );
    instance->n_master = strlen( p );
    instance->value_master = strdup( p );
    if( instance->exp->common.apply( instance->phab, instance->n_master,
        instance->value_master ) ) {
        /* we matched the default */
        free( instance->value_master );
        instance->value_master = instance->default_value;
        instance->n_master = instance->n_default;
        }
    return Pt_CONTINUE;
    }

static void plugin_frugal_destroy( ResPluginHandle_t handle )
{
    free( ( PluginFrugalInstance_t * ) handle );
}


static ResPluginHandle_t plugin_full_create
        ( PhABHandle_t phab , const PhABResExportFull_t *exp,
          const ResPluginFormatData_t *format,
          int n_default, const void *default_value,
          PhArea_t *area, char *caption,
          int n, const void *value )
{
    PluginFullInstance_t *instance;
    PtWidget_t *parent;
    PhRect_t offset = { { 0, 0 }, { 0, 0 } };
    PhArea_t tarea;
    PtArg_t args[1];
    int start = 0, end = n;

    instance = calloc( 1, sizeof( *instance ) );
    if( !instance ) return NULL;

    instance->n_default = n_default;
    instance->default_value = default_value;
    instance->n_master = n;
    instance->value_master = value;

    instance->phab = phab;
    instance->exp = exp;

    if( !area ) {
        PhRect_t rect;
        PhWindowQueryVisible( 0, 0, 0, &rect );
        tarea.pos.x = rect.ul.x + 100;;
        tarea.pos.y = rect.ul.y + 100;
        tarea.size.w = 340;
        tarea.size.h = 150;
        area = &tarea;
        }
    instance->convenience_handle = exp->create_window( area, caption, 0, &parent,
        plugin_full_notify, instance );

    PtSetParentWidget( parent );
    PtSetArg( &args[0], Pt_ARG_ANCHOR_OFFSETS, &offset, 0 );
    instance->full= ApCreateWidgetFamily( db, "string_full_container", 0, 0, 1, args );
    instance->full_widget = PtWidgetChildFront( instance->full );
    PtAddCallback( instance->full, Pt_CB_RESIZE, plugin_full_resize, instance );
    PtAddCallback( instance->full_widget, Pt_CB_TEXT_CHANGED, plugin_full_changed, instance );
    PtAddCallback( instance->full_widget, Pt_CB_ACTIVATE, plugin_full_done, instance );

    PtSetResource( instance->full_widget, Pt_ARG_TEXT_STRING, value, 0 );
    PtTextSetSelection( instance->full_widget, &start, &end );
    PtRealizeWidget( instance->full );

    set_state( instance, value );

    return ( ResPluginHandle_t ) instance;
}

static void plugin_full_destroy( ResPluginHandle_t handle )
{
    PluginFullInstance_t *instance = ( PluginFullInstance_t * ) handle;
    instance->exp->destroy( instance->convenience_handle );
    free( instance );
}

static void plugin_full_disable( ResPluginHandle_t handle )
{
    PluginFullInstance_t *instance = ( PluginFullInstance_t * ) handle;
    PtArg_t args[3];
    instance->disabled = 1;
    PtSetArg( &args[0], Pt_ARG_FLAGS, Pt_GHOST|Pt_BLOCKED, Pt_GHOST|Pt_BLOCKED );
    PtSetArg( &args[1], Pt_ARG_CURSOR_TYPE, Ph_CURSOR_NOINPUT, 0 );
    PtSetArg( &args[2], Pt_ARG_CURSOR_OVERRIDE, 1, 0 );
    PtSetResources( instance->full, 3, args );
    PtSetResource( instance->full_widget, Pt_ARG_FLAGS, Pt_GHOST|Pt_BLOCKED,
        Pt_GHOST|Pt_BLOCKED );
    instance->exp->set_state( instance->convenience_handle, RESPLUGIN_STATE_DISABLED );
}

static void plugin_full_block( ResPluginHandle_t handle, int block )
{
    PluginFullInstance_t *instance = ( PluginFullInstance_t * ) handle;
    instance->exp->set_state( instance->convenience_handle,
        block ? RESPLUGIN_STATE_BLOCKED : RESPLUGIN_STATE_UNBLOCKED );
}

static int plugin_full_any_changes( ResPluginHandle_t handle )
{
    PluginFullInstance_t *instance = ( PluginFullInstance_t * ) handle;
    char *p;
    PtGetResource( instance->full_widget, Pt_ARG_TEXT_STRING, &p, 0 );
    if( !strcmp( p, instance->value_master ) ) return RESPLUGIN_NO_CHANGES;
    return RESPLUGIN_CHANGES;
}

static void plugin_full_get_changes( ResPluginHandle_t handle, int *pn, void **pvalue )
{
    PluginFullInstance_t *instance = ( PluginFullInstance_t * ) handle;
    char *p;
    PtGetResource( instance->full_widget, Pt_ARG_TEXT_STRING, &p, 0 );
    *pn = strlen( p );
    *pvalue = strdup( p );
}

static void plugin_full_get_area( ResPluginHandle_t handle, PhArea_t *area )
{
    PluginFullInstance_t *instance = ( PluginFullInstance_t * ) handle;
    instance->exp->get_area( instance->convenience_handle, area );
}

static void plugin_full_to_front( ResPluginHandle_t handle )
{
    PluginFullInstance_t *instance = ( PluginFullInstance_t * ) handle;
    instance->exp->to_front( instance->convenience_handle );
}

static void plugin_full_set_data( ResPluginHandle_t handle, int n, const void *value,
    const ResPluginFormatData_t *format ) {
    PluginFullInstance_t *instance = ( PluginFullInstance_t * ) handle;
    int start = 0, end = n;

    PtSetResource( instance->full_widget, Pt_ARG_TEXT_STRING, value, 0 );
    PtTextSetSelection( instance->full_widget, &start, &end );

    instance->n_master = n;
    instance->value_master = value;
    set_state( instance, value );
    if( instance->disabled ) {
        PtArg_t args[3];
        instance->disabled = 0;
        PtSetArg( &args[0], Pt_ARG_FLAGS, 0, Pt_GHOST|Pt_BLOCKED );
        PtSetArg( &args[1], Pt_ARG_CURSOR_TYPE, Ph_CURSOR_INHERIT, 0 );
        PtSetArg( &args[2], Pt_ARG_CURSOR_OVERRIDE, 0, 0 );
        PtSetResources( instance->full, 3, args );
        PtSetResource( instance->full_widget, Pt_ARG_FLAGS, 0, Pt_GHOST|Pt_BLOCKED );
        }
    }

static int plugin_full_resize( PtWidget_t *widget, void *data, PtCallbackInfo_t *cbinfo ) {
    PtContainerCallback_t *cb = cbinfo->cbdata;
    if( cb->old_dim.w != cb->new_dim.w || cb->old_dim.h != cb->new_dim.h ) {
        PluginFullInstance_t *instance = ( PluginFullInstance_t * ) data;
        PhDim_t dim;
        PhPoint_t pos;
        /* center the instance->full_widget */
        PtWidgetDim( instance->full_widget, &dim );
        pos.x = ( cb->new_dim.w - dim.w ) / 2;
        pos.y = ( cb->new_dim.h - dim.h ) / 2;
        PtSetResource( instance->full_widget, Pt_ARG_POS, &pos, 0 );
        }
    return Pt_CONTINUE;
    }

static int plugin_full_changed( PtWidget_t *widget, void *data, PtCallbackInfo_t *cbinfo ) {
    PluginFullInstance_t *instance = ( PluginFullInstance_t * ) data;
    char *p;
    PtGetResource( widget, Pt_ARG_TEXT_STRING, &p, 0 );
    set_state( instance, p );
    return Pt_CONTINUE;
    }


static void get_value_and_apply( PluginFullInstance_t *instance ) {
    char *p;
    PtGetResource( instance->full_widget, Pt_ARG_TEXT_STRING, &p, 0 );
    instance->n_master = strlen( p );
    instance->value_master = strdup( p );
    if( instance->exp->common.apply( instance->phab, instance->n_master,
        instance->value_master ) ) {
        /* we matched the default */
        free( instance->value_master );
        instance->value_master = instance->default_value;
        instance->n_master = instance->n_default;
        }
    }


static int plugin_full_done( PtWidget_t *widget, void *data, PtCallbackInfo_t *cbinfo ) {
    PluginFullInstance_t *instance = ( PluginFullInstance_t * ) data;
    get_value_and_apply( instance );
    instance->exp->closing( instance->phab );
    instance->exp->destroy( instance->convenience_handle );
    free( instance );
    return Pt_CONTINUE;
    }

static void plugin_full_notify( ResPluginAction_t action, void *notify_data ) {
  PluginFullInstance_t *instance = ( PluginFullInstance_t * ) notify_data;
  switch( action ) {
    case RESPLUGIN_ACTION_APPLY: {
            if( instance->state != RESPLUGIN_STATE_MATCH_MASTER &&
                instance->state != RESPLUGIN_STATE_MATCH_DEFAULT_MASTER ) {
                get_value_and_apply( instance );
        set_state( instance, instance->value_master );
                }
      }
      break;
    case RESPLUGIN_ACTION_CLOSE: {
      instance->exp->closing( instance->phab );
      instance->exp->destroy( instance->convenience_handle );
      free( instance );
      }
      break;
    case RESPLUGIN_ACTION_DEFAULT: {
      PtSetResource( instance->full_widget, Pt_ARG_TEXT_STRING,
        instance->default_value, 0 );
      set_state( instance, instance->default_value );
      }
      break;
    }
  }


static void set_state( PluginFullInstance_t *instance, char *value ) {
    if( !strcmp( value, instance->default_value ) ) {
        if( strcmp( value, instance->value_master ) )
            instance->state = RESPLUGIN_STATE_MATCH_DEFAULT;
        else instance->state = RESPLUGIN_STATE_MATCH_DEFAULT_MASTER;
        }
    else {
        if( strcmp( value, instance->value_master ) )
            instance->state = RESPLUGIN_STATE_NO_MATCH;
        else instance->state = RESPLUGIN_STATE_MATCH_MASTER;
        }
    instance->exp->set_state( instance->convenience_handle, instance->state );
    }

An external editor

The following is an example of using an external application as a resource editor in PhAB. We will use ped to edit the Pt_ARG_TEXT_STRING resource for the PtButton widget.

To use this example, compile the two files listed below, external_multi.h and external_multi.c, into a DLL named external_multi.so.Copy the external_multi.so file into the plugins/resource directory below the location that contains PhAB's executable (usually /usr/photon/appbuilder/plugins/resource).Edit the res_editors.def in the PhAB executable directory to contain the lines:

e=external_multi
p=
F=multi@external_multi.so

Edit the file def_res.def in the same directory to contain the line:

external_multi

Edit the ptpalette.pal file in the same directory to specify external_multi as the resource editor for Pt_ARG_TEXT_STRING for the PtButton widget. To do this, in the PtButton section of the file, change the Pt_ARG_TEXT_STRING line to:

r=Pt_ARG_TEXT_STRING,Button  Text,3011,3002,0,multi/external_multi,NULL

Code listing for external_multi.h

#ifndef __ALREADY_INCLUDED_H
#define __ALREADY_INCLUDED_H

#include <photon/res_plugin_api.h>
#include <aoi/aoi.h>

#include <pthread.h>

typedef struct {
  int n_master;
  void *value_master;
  int n_default;
    void *default_value;

    PhABHandle_t phab;
    PhABResExportFull_t *exp;

    int pid, phab_waiting;
    char *path;
    pthread_t monitor;
  } PluginFullInstance_t;

#endif

Code listing for external_multi.c

#include <Pt.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/netmgr.h>
#include <time.h>
#include <signal.h>


/* Local headers */
#include "external_multi.h"

static int plugin_loading( const char *path );
static void plugin_unloading( void );
static void plugin_full_set_data( ResPluginHandle_t handle, int n, const void *value,
                                  const ResPluginFormatData_t *format );
static void plugin_full_destroy( ResPluginHandle_t handle );
static ResPluginHandle_t plugin_full_create
        ( PhABHandle_t phab , const PhABResExportFull_t *exp,
          const ResPluginFormatData_t *format,
          int n_default, const void *default_value,
          PhArea_t *area, char *caption,
          int n, const void *value );
static void plugin_full_disable( ResPluginHandle_t handle );
static void plugin_full_block( ResPluginHandle_t handle, int block );
static void plugin_full_to_front( ResPluginHandle_t handle );
static int plugin_full_any_changes( ResPluginHandle_t handle );
static void plugin_full_get_changes( ResPluginHandle_t handle, int *pn, void **pvalue );
static void plugin_full_get_area( ResPluginHandle_t handle, PhArea_t *area );


static void child_exit( int sig );
static void *monitor_f( void * data );
static void get_changes_from_file( PluginFullInstance_t *instance, int *pn,
                                   char **value );
static void apply_from_file( PluginFullInstance_t *instance );
static void emit_wm_event( PluginFullInstance_t *instance, unsigned long event_type );
static void was_file_changed( PluginFullInstance_t *instance, int *modification_time );

static ResPluginFullEditor_t full_editors[1] =
    {
        {
        {   "multi",
            RES_DATATYPE_MULTI,
            RESPLUGIN_DATATYPE_VERSION,
            plugin_full_set_data,
            plugin_full_destroy },
        plugin_full_create,
        plugin_full_disable,
        plugin_full_block,
        plugin_full_to_front,
        plugin_full_any_changes,
        plugin_full_get_changes,
        plugin_full_get_area
        }
    };

static ResPlugin_t tab = {
    plugin_loading,
    plugin_unloading,
    1,
    full_editors,
    0,
    NULL
    };

AOInterface_t interfaces[] = {
    { "PhAB Resource Editors", RESPLUGIN_VERSION, &tab },
    { 0, 0, 0 }
    };


static int plugin_loading( const char *path ) {
    signal( SIGCHLD, child_exit );
    return 0;
    }

static void plugin_unloading( void ) {
    }

static ResPluginHandle_t plugin_full_create
        ( PhABHandle_t phab , const PhABResExportFull_t *exp,
          const ResPluginFormatData_t *format,
          int n_default, const void *default_value,
          PhArea_t *area, char *caption,
          int n, const void *value )
{
    PluginFullInstance_t *instance;
    FILE *fp;
    char path[PATH_MAX];
    time_t t;
    char *argv[3];

    time( &t );

    /* put the data in a file */
    sprintf( path, "/tmp/%ld.txt", (long) t );
    fp = fopen( path, "w" );
    if( !fp ) return NULL;
    if( value ) fwrite( (char*)value, 1, n, fp );
    fclose( fp );

    instance = calloc( 1, sizeof( *instance ) );
    if( !instance ) return NULL;

    instance->n_default = n_default;
    instance->default_value = default_value;
    instance->n_master = n;
    instance->value_master = value;

    instance->phab = phab;
    instance->exp = exp;

    argv[0] = "ped";
    argv[1] = path;
    argv[2] = NULL;
    instance->pid = spawnp( argv[0], 0, NULL, NULL, argv, NULL );
    if( instance->pid == -1 ) {
        free( instance );
        unlink( path );
        return NULL;
        }

    instance->path = strdup( path );
    pthread_create( &instance->monitor, NULL, monitor_f, (void*) instance );

    return ( ResPluginHandle_t ) instance;
}

static void plugin_full_destroy( ResPluginHandle_t handle )
{
    PluginFullInstance_t *instance = ( PluginFullInstance_t * ) handle;

    if( instance->pid != -1 ) {
        /* the child process is still alive, but the data inside has been asked
           for already */
        kill( instance->pid, SIGTERM );
        }

    pthread_cancel( instance->monitor );
    pthread_detach( instance->monitor );

    unlink( instance->path );
    free( instance->path );
    free( instance );
}

static void plugin_full_disable( ResPluginHandle_t handle )
{
    PluginFullInstance_t *instance = ( PluginFullInstance_t * ) handle;

    /* the changes inside the editor has already been taken into account */

    /* close the external application if still open */
    /* we need to do this because we cannot put a new data into the external
       application ( ped ), without re-spawing it */
    if( instance->pid != -1 ) {
        kill( instance->pid, SIGTERM );
        instance->pid = -1;

        pthread_cancel( instance->monitor );
        pthread_detach( instance->monitor );
        }

    /* we have a disabled instance ( the instance pointer still valid ),
       but the pid and monitor have been destroyed */
}

static void plugin_full_block( ResPluginHandle_t handle, int block )
{
}

static int plugin_full_any_changes( ResPluginHandle_t handle )
{
    PluginFullInstance_t *instance = ( PluginFullInstance_t * ) handle;

    if ( instance->pid == -1 ) return RESPLUGIN_NO_CHANGES;
    else {
        emit_wm_event( instance, Ph_WM_TOFRONT );
        emit_wm_event( instance, Ph_WM_CLOSE );
        instance->phab_waiting = 1;
        return RESPLUGIN_WAIT;
        }
}

static void plugin_full_get_changes( ResPluginHandle_t handle, int *pn, void **pvalue )
{
    PluginFullInstance_t *instance = ( PluginFullInstance_t * ) handle;

    /* the changes are in the instance->path */
    get_changes_from_file( instance, pn, ( char ** ) pvalue );
}

static void plugin_full_get_area( ResPluginHandle_t handle, PhArea_t *area )
{
    /* not used, let the external editor memorize/restore its own area */
}

static void plugin_full_to_front( ResPluginHandle_t handle )
{
    PluginFullInstance_t *instance = ( PluginFullInstance_t * ) handle;
    emit_wm_event( instance, Ph_WM_TOFRONT );
}

static void plugin_full_set_data( ResPluginHandle_t handle, int n, const void *value,
                                  const ResPluginFormatData_t *format ) {
    PluginFullInstance_t *instance = ( PluginFullInstance_t * ) handle;
    FILE *fp;

    instance->n_master = n;
    instance->value_master = value;

    /* re-spawn the external application and the monitor thread */
    fp = fopen( instance->path, "w" );
    if( value ) fwrite( (char*)value, 1, n, fp );
    fclose( fp );

    instance->pid = spawnl( P_NOWAIT, "ped", "ped", instance->path, NULL );
    pthread_create( &instance->monitor, NULL, monitor_f, (void*) instance );
    }


static void child_exit( int sig ) {
    int status;
    wait( &status );
    }

static void *monitor_f( void * data ) {
    PluginFullInstance_t *instance = ( PluginFullInstance_t * ) data;
    int mod = 0;

    for( ; ; ) {

        /* check if instance->pid still exists */
        if( kill( instance->pid, 0 ) == -1 ) {
            /* the child has exited */
            instance->pid = -1;
            if( instance->phab_waiting ) {
                int n, answer;
                char *value;
                get_changes_from_file( instance, &n, &value );
                if( n == instance->n_master && !memcmp( value,
                                                                   instance->value_master,
                                                                   n ) )
                    answer = RESPLUGIN_NO_CHANGES;
                else answer = RESPLUGIN_CHANGES;

                PtEnter( Pt_EVENT_PROCESS_ALLOW );
                instance->exp->answer_changes( instance->phab, answer, n, value );
                /* the answer_changes() is implying that the editor has been closed */
                PtLeave( Pt_EVENT_PROCESS_ALLOW );
                }
            else {

                /* check the modification time on the file again,
                   because maybe the user did a Exit->Save or not->Save */
                was_file_changed( instance, &mod );

                PtEnter( Pt_EVENT_PROCESS_ALLOW );
                instance->exp->closing( instance->phab );
                PtLeave( Pt_EVENT_PROCESS_ALLOW );
                }

            unlink( instance->path );
            free( instance->path );
            free( instance );
            pthread_detach( pthread_self() );
            return NULL;
            }

        /* check if the instance->path has changed */
        was_file_changed( instance, &mod );

        delay( 200 );
        }

    pthread_detach( pthread_self() );
    return NULL;
    }

static void was_file_changed( PluginFullInstance_t *instance, int *modification_time ) {
    struct stat st;
    if( stat( instance->path, &st ) == 0 ) {
        if( *modification_time != st.st_mtime ) {

            if( *modification_time ) {
                /* the file was changed - call into phab with the apply method */
                apply_from_file( instance );
                }

            *modification_time = st.st_mtime;
            }
        }
    }

static void get_changes_from_file( PluginFullInstance_t *instance, int *pn,
                                   char **value ) {
    FILE *fp;
    int n;
    char *v;

    fp = fopen( instance->path, "r" );
    if( !fp ) return;

    fseek( fp, 0, SEEK_END );
    n = ftell( fp );
    fseek( fp, 0, SEEK_SET );

    v = malloc( n + 1 );
    fread( v, 1, n, fp );
    v[ n ] = 0;

    fclose( fp );

    *pn = n;
    *value = v;
    }

static void apply_from_file( PluginFullInstance_t *instance ) {
    int n;
    char *values;

    get_changes_from_file( instance, &n, &values );
    instance->n_master = n;
    instance->value_master = values;

    PtEnter( Pt_EVENT_PROCESS_ALLOW );
  if( instance->exp->common.apply( instance->phab, instance->n_master,
      instance->value_master ) ) {
    /* we matched the default */
    free( instance->value_master );
    instance->value_master = instance->default_value;
    instance->n_master = instance->n_default;
    }
    PtLeave( Pt_EVENT_PROCESS_ALLOW );
    }

static PhConnectId_t get_connect_id( pid_t pid )
{
   PhConnectInfo_t buf;
   PhConnectId_t id = 0;

   while ((id = PhGetConnectInfo(id, &buf)) != -1
          && (buf.pid != pid ||
                         ND_NODE_CMP(buf.nid, ND_LOCAL_NODE)))
      ++id;

   return id;
}

static void emit_wm_event( PluginFullInstance_t *instance, unsigned long event_type ) {
    PhWindowEvent_t event;
    PhConnectId_t connection_id;

    connection_id = get_connect_id( instance->pid );

    memset( &event, 0, sizeof (event) );
    event.event_f = event_type;
    PtForwardWindowTaskEvent( connection_id, &event );
    }