ExternalUIExtension

From Lv2

Jump to: navigation, search

[edit] C header file (lv2_external_ui.h)

/* -*- Mode: C ; c-basic-offset: 2 -*- */
/*****************************************************************************
 *
 *  This work is in public domain.
 *
 *  This file 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.
 *
 *  If you have questions, contact Nedko Arnaudov <nedko@arnaudov.name> or
 *  ask in #lad channel, FreeNode IRC network.
 *
 *****************************************************************************/

#ifndef LV2_EXTERNAL_UI_H__5AFE09A5_0FB7_47AF_924E_2AF0F8DE8873__INCLUDED
#define LV2_EXTERNAL_UI_H__5AFE09A5_0FB7_47AF_924E_2AF0F8DE8873__INCLUDED

/** UI extension suitable for out-of-process UIs */
#define LV2_EXTERNAL_UI_URI "http://lv2plug.in/ns/extensions/ui#external"

#ifdef __cplusplus
extern "C" {
#endif
#if 0
} /* Adjust editor indent */
#endif

/**
 * When LV2_EXTERNAL_UI_URI UI is instantiated, the returned
 * LV2UI_Widget handle must be cast to pointer to struct lv2_external_ui.
 * UI is created in invisible state.
 */
struct lv2_external_ui
{
  /**
   * Host calls this function regulary. UI library implementing the
   * callback may do IPC or redraw the UI.
   *
   * @param _this_ the UI context
   */
  void (* run)(struct lv2_external_ui * _this_);

  /**
   * Host calls this function to make the plugin UI visible.
   *
   * @param _this_ the UI context
   */
  void (* show)(struct lv2_external_ui * _this_);

  /**
   * Host calls this function to make the plugin UI invisible again.
   *
   * @param _this_ the UI context
   */
  void (* hide)(struct lv2_external_ui * _this_);
};

#define LV2_EXTERNAL_UI_RUN(ptr) (ptr)->run(ptr)
#define LV2_EXTERNAL_UI_SHOW(ptr) (ptr)->show(ptr)
#define LV2_EXTERNAL_UI_HIDE(ptr) (ptr)->hide(ptr)

/**
 * On UI instantiation, host must supply LV2_EXTERNAL_UI_URI
 * feature. LV2_Feature::data must be pointer to struct lv2_external_ui_host. */
struct lv2_external_ui_host
{
  /**
   * Callback that plugin UI will call
   * when UI (GUI window) is closed by user.
   * This callback wil; be called during execution of lv2_external_ui::run()
   * (i.e. not from background thread).
   *
   * After this callback is called, UI is defunct. Host must call
   * LV2UI_Descriptor::cleanup(). If host wants to make the UI visible
   * again UI must be reinstantiated.
   *
   * @param controller Host context associated with plugin UI, as
   * supplied to LV2UI_Descriptor::instantiate()
   */
  void (* ui_closed)(LV2UI_Controller controller);

  /**
   * Optional (may be NULL) "user friendly" identifier which the UI
   * may display to allow a user to easily associate this particular
   * UI instance with the correct plugin instance as it is represented
   * by the host (e.g. "track 1" or "channel 4").
   *
   * If supplied by host, the string will be referenced only during
   * LV2UI_Descriptor::instantiate()
   */
  const char * plugin_human_id;
};

#if 0
{ /* Adjust editor indent */
#endif
#ifdef __cplusplus
} /* extern "C" */
#endif

#endif /* #ifndef LV2_EXTERNAL_UI_H__5AFE09A5_0FB7_47AF_924E_2AF0F8DE8873__INCLUDED */

[edit] DSSI-like universal "wrapper"

Implementation of DSSI like implementation of external UI (made originally for lv2 nekobee). Must be built with defines for UI_EXECUTABLE and UI_URI (strings)

/* -*- Mode: C ; c-basic-offset: 2 -*- */
/*****************************************************************************
 *
 * Copyright (C) 2009 Nedko Arnaudov <nedko@arnaudov.name>
 *
 * LV2 UI bundle shared library for communicating with a DSSI UI
 *
 * 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.
 *
 *****************************************************************************/

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <sys/types.h>
#include <unistd.h>
#include <errno.h>

#include <lo/lo.h>

#include <lv2.h>
#include "lv2_ui.h"
#include "lv2_external_ui.h"

#define MAX_OSC_PATH 1024

struct control
{
  struct lv2_external_ui virt;  /* WARNING: code assumes this is the first struct member */

  LV2UI_Controller controller;
  LV2UI_Write_Function write_function;
  void (* ui_closed)(LV2UI_Controller controller);

  lo_server osc_server;

  bool running;              /* true if UI launched and 'exiting' not received */
  bool visible;              /* true if 'show' sent */

  lo_address osc_address;    /* non-NULL if 'update' received */

  char osc_control_path[MAX_OSC_PATH];
  char osc_hide_path[MAX_OSC_PATH];
  char osc_quit_path[MAX_OSC_PATH];
  char osc_show_path[MAX_OSC_PATH];
};

#undef control_ptr

static
int
osc_debug_handler(
  const char * path,
  const char * types,
  lo_arg ** argv,
  int argc,
  void * data,
  void * user_data)
{
  int i;

  printf("got unhandled OSC message:\n");
  printf("path: <%s>\n", path);
  fflush(stdout);
  for (i = 0; i < argc; i++)
  {
    printf("arg %d '%c' ", i, types[i]);
    lo_arg_pp(types[i], argv[i]);
    printf("\n");
  }
  fflush(stdout);

  return 1;
}

int
osc_exiting_handler(
  struct control * control_ptr,
  lo_arg ** argv)
{
  //printf("OSC: got UI exit notification\n");

  control_ptr->running = false;
  control_ptr->visible = false;

  if (control_ptr->osc_address)
  {
    lo_address_free(control_ptr->osc_address);
  }

  control_ptr->ui_closed(control_ptr->controller);

  return 0;
}

int
osc_control_handler(
  struct control * control_ptr,
  lo_arg ** argv)
{
  int port = argv[0]->i;
  float value = argv[1]->f;

  //printf("OSC control handler: port %d = %f\n", port, value);

  control_ptr->write_function(control_ptr->controller, (uint32_t)port, sizeof(float), 0, &value);

  return 0;
}

int
osc_update_handler(
  struct control * control_ptr,
  lo_arg ** argv)
{
  const char * url = &argv[0]->s;
  char * path;
  char * host;
  char * port;

  //printf("OSC: got update request from <%s>\n", url);

  if (control_ptr->osc_address)
  {
    return 0;
  }

  host = lo_url_get_hostname(url);
  port = lo_url_get_port(url);
  control_ptr->osc_address = lo_address_new(host, port);
  free(host);
  free(port);

  path = lo_url_get_path(url);

  sprintf(control_ptr->osc_control_path, "%scontrol", path);
  sprintf(control_ptr->osc_hide_path, "%shide", path);
  sprintf(control_ptr->osc_show_path, "%sshow", path);
  sprintf(control_ptr->osc_quit_path, "%squit", path);

  free(path);

  control_ptr->running = true;

  return 0;
}

#define control_ptr ((struct control *)user_data)

static
int
osc_message_handler(
  const char * path,
  const char * types,
  lo_arg ** argv,
  int argc,
  void * data,
  void * user_data)
{
  const char *method;

  method = path;
  if (method[0] != '/' || method[1] != '/')
    return osc_debug_handler(path, types, argv, argc, data, user_data);
  method += 2;

  if (!strcmp(method, "update") && argc == 1 && !strcmp(types, "s"))
  {
    return osc_update_handler(control_ptr, argv);
  }
  else if (!strcmp(method, "control") && argc == 2 && !strcmp(types, "if"))
  {
    return osc_control_handler(control_ptr, argv);
  }
  else if (!strcmp(method, "exiting") && argc == 0)
  {
    return osc_exiting_handler(control_ptr, argv);
  }

  return osc_debug_handler(path, types, argv, argc, data, user_data);
}

#undef control_ptr

#define control_ptr ((struct control *)_this_)

static
void
run(
  struct lv2_external_ui * _this_)
{
  //printf("run() called\n");
  while (lo_server_recv_noblock(control_ptr->osc_server, 0) != 0) {}
}

static
void
show(
  struct lv2_external_ui * _this_)
{
  //printf("show() called\n");

  if (control_ptr->visible)
  {
    return;
  }

  if (control_ptr->osc_address)
  {
    lo_send(control_ptr->osc_address, control_ptr->osc_show_path, "");
    control_ptr->visible = true;
  }
}

static
void
hide(
  struct lv2_external_ui * _this_)
{
  //printf("hide() called\n");

  if (!control_ptr->visible || !control_ptr->osc_address)
  {
    return;
  }

  lo_send(control_ptr->osc_address, control_ptr->osc_hide_path, "");
  control_ptr->visible = false;
}

#undef control_ptr

static
LV2UI_Handle
instantiate(
  const struct _LV2UI_Descriptor * descriptor,
  const char * plugin_uri,
  const char * bundle_path,
  LV2UI_Write_Function write_function,
  LV2UI_Controller controller,
  LV2UI_Widget * widget,
  const LV2_Feature * const * features)
{
  struct control * control_ptr;
  struct lv2_external_ui_host * ui_host_ptr;
  char * filename;
  char * osc_url;

  //printf("instantiate('%s', '%s') called\n", plugin_uri, bundle_path);

  ui_host_ptr = NULL;
  while (*features != NULL)
  {
    if (strcmp((*features)->URI, LV2_EXTERNAL_UI_URI) == 0)
    {
      ui_host_ptr = (*features)->data;
    }

    features++;
  }

  if (ui_host_ptr == NULL)
  {
    goto fail;
  }

  control_ptr = malloc(sizeof(struct control));
  if (control_ptr == NULL)
  {
    goto fail;
  }

  control_ptr->virt.run = run;
  control_ptr->virt.show = show;
  control_ptr->virt.hide = hide;

  control_ptr->controller = controller;
  control_ptr->write_function = write_function;
  control_ptr->ui_closed = ui_host_ptr->ui_closed;

  filename = malloc(strlen(bundle_path) + strlen(UI_EXECUTABLE) + 1);
  if (filename == NULL)
  {
    goto fail_free_control;
  }

  strcpy(filename, bundle_path);
  strcat(filename, UI_EXECUTABLE);

  control_ptr->running = false;
  control_ptr->visible = false;
  control_ptr->osc_address = NULL;

  control_ptr->osc_server = lo_server_new(NULL, NULL);
  osc_url = lo_server_get_url(control_ptr->osc_server);
  //printf("host OSC URL is %s\n", osc_url);
  lo_server_add_method(control_ptr->osc_server, NULL, NULL, osc_message_handler, control_ptr);

  if (fork() == 0)
  {
    execlp(
      filename,
      filename,
      osc_url,
      plugin_uri,
      plugin_uri,
      ui_host_ptr->plugin_human_id != NULL ? ui_host_ptr->plugin_human_id : "",
      NULL);
    fprintf(stderr, "exec of UI failed: %s", strerror(errno));
    exit(1);
  }

  while (!control_ptr->running)
  {
    if (lo_server_recv_noblock(control_ptr->osc_server, 0) == 0)
    {
      usleep(300000);
    }
  }

  *widget = (LV2UI_Widget)control_ptr;

  return (LV2UI_Handle)control_ptr;

fail_free_control:
  free(control_ptr);

fail:
  return NULL;
}

#define control_ptr ((struct control *)ui)

static
void
cleanup(
  LV2UI_Handle ui)
{
  //printf("cleanup() called\n");
  free(control_ptr);
}

static
void
port_event(
  LV2UI_Handle ui,
  uint32_t port_index,
  uint32_t buffer_size,
  uint32_t format,
  const void * buffer)
{
  //printf("port_event(%u, %f) called\n", (unsigned int)port_index, *(float *)buffer);

  lo_send(control_ptr->osc_address, control_ptr->osc_control_path, "if", (int)port_index, *(float *)buffer);
}

#undef control_ptr

static LV2UI_Descriptor descriptors[] =
{
  {UI_URI, instantiate, cleanup, port_event, NULL}
};

const LV2UI_Descriptor *
lv2ui_descriptor(
  uint32_t index)
{
  //printf("lv2ui_descriptor(%u) called\n", (unsigned int)index);

  if (index >= sizeof(descriptors) / sizeof(descriptors[0]))
  {
    return NULL;
  }

  return descriptors + index;
}
Personal tools