root/lv2_ui.c

Revision 76f68515bb24d4cd53a3f9e991e50f29eef7bfa3, 13.2 KB (checked in by Nedko Arnaudov <nedko@…>, 8 months ago)

hide on cleanup

  • Property mode set to 100644
Line 
1/* -*- Mode: C ; c-basic-offset: 2 -*- */
2/*****************************************************************************
3 *
4 * Copyright (C) 2009 Nedko Arnaudov <nedko@arnaudov.name>
5 *
6 * LV2 UI bundle shared library for communicating with a DSSI UI
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License as
10 * published by the Free Software Foundation; either version 2 of
11 * the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be
14 * useful, but WITHOUT ANY WARRANTY; without even the implied
15 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
16 * PURPOSE.  See the GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public
19 * License along with this program; if not, write to the Free
20 * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
21 * MA 02111-1307, USA.
22 *
23 *****************************************************************************/
24
25#define UI_EXECUTABLE "ui"
26#define UI_URI        "http://nedko.aranaudov.org/soft/filter/2/gui"
27
28#define WAIT_START_TIMEOUT  3000 /* ms */
29#define WAIT_ZOMBIE_TIMEOUT 3000 /* ms */
30#define WAIT_STEP 100            /* ms */
31
32//#define FORK_TIME_MEASURE
33
34#define USE_VFORK
35//#define USE_CLONE
36//#define USE_CLONE2
37
38#if defined(USE_VFORK)
39#define FORK vfork
40#define FORK_STR "vfork"
41#elif defined(USE_CLONE)
42#define FORK_STR "clone"
43#elif defined(USE_CLONE2)
44#define FORK_STR "clone2"
45#else
46#define FORK fork
47#define FORK_STR "fork"
48#endif
49
50#include <stdbool.h>
51#include <stdio.h>
52#include <stdlib.h>
53#include <string.h>
54
55#include <sys/types.h>
56#include <sys/wait.h>
57#if defined(FORK_TIME_MEASURE)
58# include <sys/time.h>
59#endif
60#include <unistd.h>
61#if defined(USE_CLONE) || defined(USE_CLONE2)
62# include <sched.h>
63#endif
64#include <fcntl.h>
65#include <locale.h>
66#include <errno.h>
67
68#include <lv2.h>
69#include "lv2_ui.h"
70#include "lv2_external_ui.h"
71
72struct control
73{
74  struct lv2_external_ui virt;  /* WARNING: code assumes this is the first struct member */
75
76  LV2UI_Controller controller;
77  LV2UI_Write_Function write_function;
78  void (* ui_closed)(LV2UI_Controller controller);
79
80  bool running;              /* true if UI launched and 'exiting' not received */
81  bool visible;              /* true if 'show' sent */
82
83  int send_pipe;             /* the pipe end that is used for sending messages to UI */
84  int recv_pipe;             /* the pipe end that is used for receiving messages from UI */
85
86  pid_t pid;
87};
88
89static
90char *
91read_line(
92  struct control * control_ptr)
93{
94  ssize_t ret;
95  char ch;
96  char buf[100];
97  char * ptr;
98
99  ptr = buf;
100
101loop:
102  ret = read(control_ptr->recv_pipe, &ch, 1);
103  if (ret == 1 && ch != '\n')
104  {
105    *ptr++ = ch;
106    goto loop;
107  }
108
109  if (ptr != buf)
110  {
111    *ptr = 0;
112    //printf("recv: \"%s\"\n", buf);
113    return strdup(buf);
114  }
115
116  return NULL;
117}
118
119static
120bool
121wait_child(
122  pid_t pid)
123{
124  pid_t ret;
125  int i;
126
127  if (pid == -1)
128  {
129    fprintf(stderr, "Can't wait for pid -1\n");
130    return false;
131  }
132
133  for (i = 0; i < WAIT_ZOMBIE_TIMEOUT / WAIT_STEP; i++)
134  {
135    //printf("waitpid(%d): %d\n", (int)pid, i);
136
137    ret = waitpid(pid, NULL, WNOHANG);
138    if (ret != 0)
139    {
140      if (ret == pid)
141      {
142        //printf("child zombie with pid %d was consumed.\n", (int)pid);
143        return true;
144      }
145
146      if (ret == -1)
147      {
148        fprintf(stderr, "waitpid(%d) failed: %s\n", (int)pid, strerror(errno));
149        return false;
150      }
151
152      fprintf(stderr, "we have waited for child pid %d to exit but we got pid %d instead\n", (int)pid, (int)ret);
153
154      return false;
155    }
156
157    //printf("zombie wait %d ms ...\n", WAIT_STEP);
158    usleep(WAIT_STEP * 1000);   /* wait 100 ms */
159  }
160
161  fprintf(
162    stderr,
163    "we have waited for child with pid %d to exit for %.1f seconds and we are giving up\n",
164    (int)pid,
165    (float)((float)WAIT_START_TIMEOUT / 1000));
166
167  return false;
168}
169
170#define control_ptr ((struct control *)_this_)
171
172static
173void
174run(
175  struct lv2_external_ui * _this_)
176{
177  char * msg;
178  char * port_index_str;
179  char * port_value_str;
180  int port;
181  float value;
182  char * locale;
183
184  //printf("run() called\n");
185
186  msg = read_line(control_ptr);
187  if (msg == NULL)
188  {
189    return;
190  }
191
192  locale = strdup(setlocale(LC_NUMERIC, NULL));
193  setlocale(LC_NUMERIC, "POSIX");
194
195  if (!strcmp(msg, "port_value"))
196  {
197    port_index_str = read_line(control_ptr);
198    port_value_str = read_line(control_ptr);
199
200    port = atoi(port_index_str);
201    if (sscanf(port_value_str, "%f", &value) == 1)
202    {
203      //printf("port %d = %f\n", port, value);
204      control_ptr->write_function(control_ptr->controller, (uint32_t)port, sizeof(float), 0, &value);
205    }
206    else
207    {
208      fprintf(stderr, "failed to convert \"%s\" to float\n", port_value_str);
209    }
210
211    free(port_index_str);
212    free(port_value_str);
213  }
214  else if (!strcmp(msg, "exiting"))
215  {
216    //printf("got UI exit notification\n");
217
218    /* for a while wait child to exit, we dont like zombie processes */
219    if (!wait_child(control_ptr->pid))
220    {
221      fprintf(stderr, "force killing misbehaved child %d (exit)\n", (int)control_ptr->pid);
222      if (kill(control_ptr->pid, SIGKILL) == -1)
223      {
224        fprintf(stderr, "kill() failed: %s (exit)\n", strerror(errno));
225      }
226      else
227      {
228        wait_child(control_ptr->pid);
229      }
230    }
231
232    control_ptr->running = false;
233    control_ptr->visible = false;
234    control_ptr->ui_closed(control_ptr->controller);
235  }
236  else
237  {
238    printf("unknown message: \"%s\"\n", msg);
239  }
240
241  setlocale(LC_NUMERIC, locale);
242  free(locale);
243
244  free(msg);
245}
246
247static
248void
249show(
250  struct lv2_external_ui * _this_)
251{
252  //printf("show() called\n");
253
254  if (control_ptr->visible)
255  {
256    return;
257  }
258
259  write(control_ptr->send_pipe, "show\n", 5);
260  control_ptr->visible = true;
261}
262
263static
264void
265hide(
266  struct lv2_external_ui * _this_)
267{
268  //printf("hide() called\n");
269
270  if (!control_ptr->visible)
271  {
272    return;
273  }
274
275  write(control_ptr->send_pipe, "hide\n", 5);
276  control_ptr->visible = false;
277}
278
279#undef control_ptr
280
281#if defined(FORK_TIME_MEASURE)
282static
283uint64_t
284get_current_time()
285{
286   struct timeval time;
287
288   if (gettimeofday(&time, NULL) != 0)
289       return 0;
290
291   return (uint64_t)time.tv_sec * 1000000 + (uint64_t)time.tv_usec;
292}
293
294#define FORK_TIME_MEASURE_VAR_NAME  ____t
295
296#define FORK_TIME_MEASURE_VAR       uint64_t FORK_TIME_MEASURE_VAR_NAME
297#define FORK_TIME_MEASURE_BEGIN     FORK_TIME_MEASURE_VAR_NAME = get_current_time()
298#define FORK_TIME_MEASURE_END(msg)                                                       \
299  {                                                                                      \
300    FORK_TIME_MEASURE_VAR_NAME = get_current_time() - FORK_TIME_MEASURE_VAR_NAME;        \
301    fprintf(stderr, msg ": %llu us\n", (unsigned long long)FORK_TIME_MEASURE_VAR_NAME);  \
302  }
303
304#else
305
306#define FORK_TIME_MEASURE_VAR
307#define FORK_TIME_MEASURE_BEGIN
308#define FORK_TIME_MEASURE_END(msg)
309
310#endif
311
312#if defined(USE_CLONE) || defined(USE_CLONE2)
313
314static int clone_fn(void * context)
315{
316    execvp(*(const char **)context, (char **)context);
317    return -1;
318}
319
320#endif
321
322static
323LV2UI_Handle
324instantiate(
325  const struct _LV2UI_Descriptor * descriptor,
326  const char * plugin_uri,
327  const char * bundle_path,
328  LV2UI_Write_Function write_function,
329  LV2UI_Controller controller,
330  LV2UI_Widget * widget,
331  const LV2_Feature * const * features)
332{
333  struct control * control_ptr;
334  struct lv2_external_ui_host * ui_host_ptr;
335  char * filename;
336  int pipe1[2]; /* written by host process, read by plugin UI process */
337  int pipe2[2]; /* written by plugin UI process, read by host process */
338  char ui_recv_pipe[100];
339  char ui_send_pipe[100];
340  int oldflags;
341  FORK_TIME_MEASURE_VAR;
342  const char * argv[8];
343  int ret;
344  int i;
345  char ch;
346
347  //printf("instantiate('%s', '%s') called\n", plugin_uri, bundle_path);
348
349  ui_host_ptr = NULL;
350  while (*features != NULL)
351  {
352    if (strcmp((*features)->URI, LV2_EXTERNAL_UI_URI) == 0)
353    {
354      ui_host_ptr = (*features)->data;
355    }
356
357    features++;
358  }
359
360  if (ui_host_ptr == NULL)
361  {
362    goto fail;
363  }
364
365  control_ptr = malloc(sizeof(struct control));
366  if (control_ptr == NULL)
367  {
368    goto fail;
369  }
370
371  control_ptr->virt.run = run;
372  control_ptr->virt.show = show;
373  control_ptr->virt.hide = hide;
374
375  control_ptr->controller = controller;
376  control_ptr->write_function = write_function;
377  control_ptr->ui_closed = ui_host_ptr->ui_closed;
378
379  if (pipe(pipe1) != 0)
380  {
381    fprintf(stderr, "pipe1 creation failed.\n");
382  }
383
384  if (pipe(pipe2) != 0)
385  {
386    fprintf(stderr, "pipe2 creation failed.\n");
387  }
388
389  snprintf(ui_recv_pipe, sizeof(ui_recv_pipe), "%d", pipe1[0]); /* [0] means reading end */
390  snprintf(ui_send_pipe, sizeof(ui_send_pipe), "%d", pipe2[1]); /* [1] means writting end */
391
392  filename = malloc(strlen(bundle_path) + strlen(UI_EXECUTABLE) + 1);
393  if (filename == NULL)
394  {
395    goto fail_free_control;
396  }
397
398  strcpy(filename, bundle_path);
399  strcat(filename, UI_EXECUTABLE);
400
401  control_ptr->running = false;
402  control_ptr->visible = false;
403
404  control_ptr->pid = -1;
405
406  argv[0] = "python";
407  argv[1] = filename;
408  argv[2] = plugin_uri;
409  argv[3] = bundle_path;
410  argv[4] = ui_host_ptr->plugin_human_id != NULL ? ui_host_ptr->plugin_human_id : "";
411  argv[5] = ui_recv_pipe;       /* reading end */
412  argv[6] = ui_send_pipe;       /* writting end */
413  argv[7] = NULL;
414
415  FORK_TIME_MEASURE_BEGIN;
416
417#if defined(USE_CLONE)
418  {
419    int stack[8000];
420
421    ret = clone(clone_fn, stack + 4000, CLONE_VFORK, argv);
422    if (ret == -1)
423    {
424      fprintf(stderr, "clone() failed: %s\n", strerror(errno));
425      goto fail_free_control;
426    }
427  }
428#elif defined(USE_CLONE2)
429  fprintf(stderr, "clone2() exec not implemented yet\n");
430  goto fail_free_control;
431#else
432  ret = FORK();
433  switch (ret)
434  {
435  case 0:                       /* child process */
436    /* fork duplicated the handles, close pipe ends that are used by parent process */
437#if !defined(USE_VFORK)
438    /* it looks we cant do this for vfork() */
439    close(pipe1[1]);
440    close(pipe2[0]);
441#endif
442
443    execvp(argv[0], (char **)argv);
444    fprintf(stderr, "exec of UI failed: %s\n", strerror(errno));
445    exit(1);
446  case -1:
447    fprintf(stderr, "fork() failed to create new process for plugin UI\n");
448    goto fail_free_control;
449  }
450
451#endif
452
453  FORK_TIME_MEASURE_END(FORK_STR "() time");
454
455  //fprintf(stderr, FORK_STR "()-ed child process: %d\n", ret);
456  control_ptr->pid = ret;
457
458  /* fork duplicated the handles, close pipe ends that are used by the child process */
459  close(pipe1[0]);
460  close(pipe2[1]);
461
462  control_ptr->send_pipe = pipe1[1]; /* [1] means writting end */
463  control_ptr->recv_pipe = pipe2[0]; /* [0] means reading end */
464
465  oldflags = fcntl(control_ptr->recv_pipe, F_GETFL);
466  fcntl(control_ptr->recv_pipe, F_SETFL, oldflags | O_NONBLOCK);
467
468  /* wait a while for child process to confirm it is alive */
469  //printf("waiting UI start\n");
470  i = 0;
471loop:
472  ret = read(control_ptr->recv_pipe, &ch, 1);
473  switch (ret)
474  {
475  case -1:
476    if (errno == EAGAIN)
477    {
478      if (i < WAIT_START_TIMEOUT / WAIT_STEP)
479      {
480        //printf("start wait %d ms ...\n", WAIT_STEP);
481        usleep(WAIT_STEP * 1000);
482        i++;
483        goto loop;
484      }
485
486      fprintf(
487        stderr,
488        "we have waited for child with pid %d to appear for %.1f seconds and we are giving up\n",
489        (int)control_ptr->pid,
490        (float)((float)WAIT_START_TIMEOUT / 1000));
491    }
492    else
493    {
494      fprintf(stderr, "read() failed: %s\n", strerror(errno));
495    }
496    break;
497  case 1:
498    if (ch == '\n')
499    {
500      *widget = (LV2UI_Widget)control_ptr;
501      return (LV2UI_Handle)control_ptr;
502    }
503
504    fprintf(stderr, "read() wrong first char '%c'\n", ch);
505
506    break;
507  default:
508    fprintf(stderr, "read() returned %d\n", ret);
509  }
510
511  fprintf(stderr, "force killing misbehaved child %d (start)\n", (int)control_ptr->pid);
512
513  if (kill(control_ptr->pid, SIGKILL) == -1)
514  {
515      fprintf(stderr, "kill() failed: %s (start)\n", strerror(errno));
516  }
517
518  /* wait a while child to exit, we dont like zombie processes */
519  wait_child(control_ptr->pid);
520
521fail_free_control:
522  free(control_ptr);
523
524fail:
525  fprintf(stderr, "lv2fil UI launch failed\n");
526  return NULL;
527}
528
529#define control_ptr ((struct control *)ui)
530
531static
532void
533cleanup(
534  LV2UI_Handle ui)
535{
536  //printf("cleanup() called\n");
537  hide(&control_ptr->virt);
538  free(control_ptr);
539}
540
541static
542void
543port_event(
544  LV2UI_Handle ui,
545  uint32_t port_index,
546  uint32_t buffer_size,
547  uint32_t format,
548  const void * buffer)
549{
550  char buf[100];
551  int len;
552  char * locale;
553
554  //printf("port_event(%u, %f) called\n", (unsigned int)port_index, *(float *)buffer);
555
556  locale = strdup(setlocale(LC_NUMERIC, NULL));
557  setlocale(LC_NUMERIC, "POSIX");
558
559  write(control_ptr->send_pipe, "port_value\n", 11);
560  len = sprintf(buf, "%u\n", (unsigned int)port_index);
561  write(control_ptr->send_pipe, buf, len);
562  len = sprintf(buf, "%.10f\n", *(float *)buffer);
563  write(control_ptr->send_pipe, buf, len);
564  fsync(control_ptr->send_pipe);
565
566  setlocale(LC_NUMERIC, locale);
567  free(locale);
568}
569
570#undef control_ptr
571
572static LV2UI_Descriptor descriptors[] =
573{
574  {UI_URI, instantiate, cleanup, port_event, NULL}
575};
576
577const LV2UI_Descriptor *
578lv2ui_descriptor(
579  uint32_t index)
580{
581  //printf("lv2ui_descriptor(%u) called\n", (unsigned int)index);
582
583  if (index >= sizeof(descriptors) / sizeof(descriptors[0]))
584  {
585    return NULL;
586  }
587
588  return descriptors + index;
589}
Note: See TracBrowser for help on using the browser.