/*----------------------------------------------------------------------------* * .---. * * PornView (_,/\ \ * * photo/movie collection viewer and manager (`a a( ) * * trem0r ) \= ) ( * * (c) 2002 (.--' '--.) * * / (_)^(_) \ * *----------------------------------------------------------------------------*/ /* * These codes are based on widget/gimv_player.c in GImageView. * GImageView author: Takuro Ashie */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "gtkmplayer.h" #ifdef ENABLE_MPLAYER #include #include #include #include #include #include #include #include #include #include #include #include #include #define GTK_MPLAYER_REFRESH_RATE 100 /* 1/10 [sec] */ #define GTK_MPLAYER_BUF_SIZE 1024 #ifdef USE_GTK2 # define gtk_object_class_add_signals(class, func, type) #else # ifndef GTK_CLASS_TYPE # define GTK_CLASS_TYPE(object_class) object_class->type # endif #endif enum { PLAY_SIGNAL, STOP_SIGNAL, PAUSE_SIGNAL, POS_CHANGED_SIGNAL, LAST_SIGNAL }; typedef struct ChildContext_Tag ChildContext; typedef void (*ProcessLineFunc) (ChildContext * context, const gchar * line, gint len, gboolean is_stderr); struct ChildContext_Tag { pid_t pid; GList *args; gint checker_id; GtkMPlayer *player; int stdout_fd; int stderr_fd; int stdin_fd; gchar stdoutb[GTK_MPLAYER_BUF_SIZE]; gint stdout_size; gchar stderrb[GTK_MPLAYER_BUF_SIZE]; gint stderr_size; ProcessLineFunc process_line_fn; gpointer data; GtkDestroyNotify destroy_fn; }; typedef struct GetDriversContext_Tag { const gchar *separator; gint sep_len; gboolean passing_header; GList *drivers_list; } GetDriversContext; /* object class */ static void gtk_mplayer_class_init (GtkMPlayerClass * class); static void gtk_mplayer_init (GtkMPlayer * player); static void gtk_mplayer_destroy (GtkObject * object); /* widget class */ static void gtk_mplayer_realize (GtkWidget * widget); static void gtk_mplayer_unrealize (GtkWidget * widget); static void gtk_mplayer_size_allocate (GtkWidget * widget, GtkAllocation * allocation); /* mplayer class */ static ChildContext *gtk_mplayer_get_player_child (GtkMPlayer * player); /* MPlayer handling */ static ChildContext *start_command (GtkMPlayer * player, /* * include command string */ GList * arg_list, ProcessLineFunc func, gpointer data, GtkDestroyNotify destroy_fn); static gint timeout_check_child (gpointer data); static gboolean process_output (ChildContext * context); static void child_context_destroy (ChildContext * context); /* line process functions */ static void process_line (ChildContext * context, const gchar * line, gint len, gboolean is_stderr); static void process_line_get_drivers (ChildContext * context, const gchar * line, gint len, gboolean is_stderr); static GtkWidgetClass *parent_class = NULL; static gint gtk_mplayer_signals[LAST_SIGNAL] = { 0 }; static GHashTable *player_context_table = NULL; static GHashTable *vo_drivers_table = NULL; static GHashTable *ao_drivers_table = NULL; GtkType gtk_mplayer_get_type (void) { static GtkType gtk_mplayer_type = 0; #if (GTK_MAJOR_VERSION >= 2) if (!gtk_mplayer_type) { static const GTypeInfo gtk_mplayer_info = { sizeof (GtkMPlayerClass), NULL, /* base_init */ NULL, /* base_finalize */ (GClassInitFunc) gtk_mplayer_class_init, NULL, /* class_finalize */ NULL, /* class_data */ sizeof (GtkMPlayer), 0, /* n_preallocs */ (GInstanceInitFunc) gtk_mplayer_init, }; gtk_mplayer_type = g_type_register_static (GTK_TYPE_WIDGET, "GtkMPlayer", >k_mplayer_info, 0); } #else /* (GTK_MAJOR_VERSION >= 2) */ if (!gtk_mplayer_type) { static const GtkTypeInfo gtk_mplayer_info = { "GtkMPlayer", sizeof (GtkMPlayer), sizeof (GtkMPlayerClass), (GtkClassInitFunc) gtk_mplayer_class_init, (GtkObjectInitFunc) gtk_mplayer_init, /* * reserved_1 */ NULL, /* * reserved_2 */ NULL, (GtkClassInitFunc) NULL, }; gtk_mplayer_type = gtk_type_unique (gtk_widget_get_type (), >k_mplayer_info); } #endif /* (GTK_MAJOR_VERSION >= 2) */ return gtk_mplayer_type; } static void gtk_mplayer_class_init (GtkMPlayerClass * class) { GtkObjectClass *object_class; GtkWidgetClass *widget_class; object_class = (GtkObjectClass *) class; widget_class = (GtkWidgetClass *) class; parent_class = gtk_type_class (gtk_widget_get_type ()); #if (defined USE_GTK2) && (defined GTK_DISABLE_DEPRECATED) gtk_mplayer_signals[PLAY_SIGNAL] = g_signal_new ("play", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GtkMPlayerClass, play), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); gtk_mplayer_signals[STOP_SIGNAL] = g_signal_new ("stop", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GtkMPlayerClass, stop), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); gtk_mplayer_signals[PAUSE_SIGNAL] = g_signal_new ("pause", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GtkMPlayerClass, pause), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); gtk_mplayer_signals[POS_CHANGED_SIGNAL] = g_signal_new ("position-chaned", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GtkMPlayerClass, position_changed), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); #else gtk_mplayer_signals[PLAY_SIGNAL] = gtk_signal_new ("play", GTK_RUN_FIRST, GTK_CLASS_TYPE (object_class), GTK_SIGNAL_OFFSET (GtkMPlayerClass, play), gtk_signal_default_marshaller, GTK_TYPE_NONE, 0); gtk_mplayer_signals[STOP_SIGNAL] = gtk_signal_new ("stop", GTK_RUN_FIRST, GTK_CLASS_TYPE (object_class), GTK_SIGNAL_OFFSET (GtkMPlayerClass, stop), gtk_signal_default_marshaller, GTK_TYPE_NONE, 0); gtk_mplayer_signals[PAUSE_SIGNAL] = gtk_signal_new ("pause", GTK_RUN_FIRST, GTK_CLASS_TYPE (object_class), GTK_SIGNAL_OFFSET (GtkMPlayerClass, pause), gtk_signal_default_marshaller, GTK_TYPE_NONE, 0); gtk_mplayer_signals[POS_CHANGED_SIGNAL] = gtk_signal_new ("position-changed", GTK_RUN_FIRST, GTK_CLASS_TYPE (object_class), GTK_SIGNAL_OFFSET (GtkMPlayerClass, position_changed), gtk_signal_default_marshaller, GTK_TYPE_NONE, 0); gtk_object_class_add_signals (object_class, gtk_mplayer_signals, LAST_SIGNAL); #endif /* * object class methods */ object_class->destroy = gtk_mplayer_destroy; /* * widget class methods */ widget_class->realize = gtk_mplayer_realize; widget_class->unrealize = gtk_mplayer_unrealize; widget_class->size_allocate = gtk_mplayer_size_allocate; /* * mplayer class methods */ class->play = NULL; class->stop = NULL; class->pause = NULL; } /* object class methods */ static void gtk_mplayer_media_info_init (GtkMPlayerMediaInfo * info) { g_return_if_fail (info); info->flags = 0; info->length = -1; info->width = -1; info->height = -1; } static void gtk_mplayer_init (GtkMPlayer * player) { player->filename = NULL; player->pos = 0.0; player->speed = 1.0; player->status = GtkMPlayerStatusStop; player->flags = GtkMPlayerEmbedFlag | GtkMPlayerAllowFrameDroppingFlag; player->command = g_strdup ("mplayer"); player->vo = NULL; player->ao = NULL; player->args = NULL; gtk_mplayer_media_info_init (&player->media_info); } static void gtk_mplayer_destroy (GtkObject * object) { GtkMPlayer *player = GTK_MPLAYER (object); g_return_if_fail (GTK_IS_MPLAYER (player)); g_free (player->filename); player->filename = NULL; g_free (player->command); player->command = NULL; g_free (player->vo); player->vo = NULL; g_free (player->ao); player->ao = NULL; /* * FIXME: free player->args */ if (GTK_OBJECT_CLASS (parent_class)->destroy) GTK_OBJECT_CLASS (parent_class)->destroy (object); } /* widget class methods */ static void gtk_mplayer_send_configure (GtkMPlayer * player); static void gtk_mplayer_realize (GtkWidget * widget) { GtkMPlayer *player; GdkWindowAttr attributes; gint attributes_mask; g_return_if_fail (widget != NULL); g_return_if_fail (GTK_IS_MPLAYER (widget)); player = GTK_MPLAYER (widget); GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); attributes.window_type = GDK_WINDOW_CHILD; attributes.x = widget->allocation.x; attributes.y = widget->allocation.y; attributes.width = widget->allocation.width; attributes.height = widget->allocation.height; attributes.wclass = GDK_INPUT_OUTPUT; attributes.visual = gtk_widget_get_visual (widget); attributes.colormap = gtk_widget_get_colormap (widget); attributes.event_mask = gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK; attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP; widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask); gdk_window_set_user_data (widget->window, player); widget->style = gtk_style_attach (widget->style, widget->window); gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL); gtk_mplayer_send_configure (player); gdk_window_set_background (widget->window, &widget->style->black); } static void gtk_mplayer_unrealize (GtkWidget * widget) { GtkMPlayer *player = GTK_MPLAYER (widget); if (gtk_mplayer_is_running (player)) gtk_mplayer_stop (player); if (parent_class->unrealize) (*parent_class->unrealize) (widget); } static void gtk_mplayer_size_allocate (GtkWidget * widget, GtkAllocation * allocation) { g_return_if_fail (widget != NULL); g_return_if_fail (GTK_IS_MPLAYER (widget)); g_return_if_fail (allocation != NULL); widget->allocation = *allocation; /* * FIXME, TODO-1.3: back out the MAX() statements */ widget->allocation.width = MAX (1, widget->allocation.width); widget->allocation.height = MAX (1, widget->allocation.height); if (GTK_WIDGET_REALIZED (widget)) { gdk_window_move_resize (widget->window, allocation->x, allocation->y, allocation->width, allocation->height); gtk_mplayer_send_configure (GTK_MPLAYER (widget)); } } static void gtk_mplayer_send_configure (GtkMPlayer * player) { GtkWidget *widget; GdkEventConfigure event; widget = GTK_WIDGET (player); event.type = GDK_CONFIGURE; event.window = widget->window; event.send_event = TRUE; event.x = widget->allocation.x; event.y = widget->allocation.y; event.width = widget->allocation.width; event.height = widget->allocation.height; gtk_widget_event (widget, (GdkEvent *) & event); } /* mplayer class methods */ gfloat gtk_mplayer_get_position (GtkMPlayer * player) { g_return_val_if_fail (GTK_IS_MPLAYER (player), 0.0); return player->pos; } GtkMPlayerStatus gtk_mplayer_get_status (GtkMPlayer * player) { g_return_val_if_fail (GTK_IS_MPLAYER (player), GtkMPlayerStatusStop); return player->status; } static ChildContext * gtk_mplayer_get_player_child (GtkMPlayer * player) { if (!player_context_table) return NULL; return g_hash_table_lookup (player_context_table, player); } gboolean gtk_mplayer_is_running (GtkMPlayer * player) { if (gtk_mplayer_get_player_child (player)) return TRUE; return FALSE; } GtkWidget * gtk_mplayer_new (void) { GtkMPlayer *player = GTK_MPLAYER (gtk_type_new (gtk_mplayer_get_type ())); return GTK_WIDGET (player); } static void playback_done (gpointer data) { GtkMPlayer *player = data; g_return_if_fail (GTK_IS_MPLAYER (player)); player->pos = 0.0; player->status = GtkMPlayerStatusStop; gtk_signal_emit (GTK_OBJECT (player), gtk_mplayer_signals[STOP_SIGNAL]); } void gtk_mplayer_play (GtkMPlayer * player) { gtk_mplayer_start (player, 0.0, 1.0); } void gtk_mplayer_start (GtkMPlayer * player, gfloat pos, gfloat speed) { GList *arg_list = NULL; ChildContext *context; gchar buf[16]; gint buf_size = sizeof (buf) / sizeof (gchar); g_return_if_fail (GTK_IS_MPLAYER (player)); g_return_if_fail (player->filename); if (player->status == GtkMPlayerStatusPause) { gtk_mplayer_toggle_pause (player); return; } if (gtk_mplayer_is_running (player)) return; /* * create argument list */ arg_list = g_list_append (arg_list, g_strdup (player->command)); arg_list = g_list_append (arg_list, g_strdup ("-slave")); if (GTK_WIDGET_REALIZED (GTK_WIDGET (player))) { if (player->flags & GtkMPlayerEmbedFlag) { g_snprintf (buf, buf_size, "%ld", GDK_WINDOW_XWINDOW (GTK_WIDGET (player)->window)); arg_list = g_list_append (arg_list, g_strdup ("-wid")); arg_list = g_list_append (arg_list, g_strdup (buf)); } g_snprintf (buf, buf_size, "scale=%d:%d", GTK_WIDGET (player)->allocation.width, GTK_WIDGET (player)->allocation.height); arg_list = g_list_append (arg_list, g_strdup ("-vop")); arg_list = g_list_append (arg_list, g_strdup (buf)); } if (player->vo) { arg_list = g_list_append (arg_list, g_strdup ("-vo")); arg_list = g_list_append (arg_list, g_strdup (player->vo)); } if (player->ao) { arg_list = g_list_append (arg_list, g_strdup ("-ao")); arg_list = g_list_append (arg_list, g_strdup (player->ao)); } if (player->flags & GtkMPlayerAllowFrameDroppingFlag) { arg_list = g_list_append (arg_list, g_strdup ("-framedrop")); } /* * pos */ if (pos > 0.01) { arg_list = g_list_append (arg_list, g_strdup ("-ss")); g_snprintf (buf, buf_size, "%f", pos); arg_list = g_list_append (arg_list, g_strdup (buf)); } /* * speed */ if (speed < 0.01 && speed > 100.01) { player->speed = 1.0; } else { player->speed = speed; } arg_list = g_list_append (arg_list, g_strdup ("-speed")); g_snprintf (buf, buf_size, "%f", player->speed); arg_list = g_list_append (arg_list, g_strdup (buf)); /* * FIXME: add player->args */ arg_list = g_list_append (arg_list, g_strdup (player->filename)); /* * real start */ context = start_command (player, arg_list, process_line, player, playback_done); /* * add to hash table */ if (!player_context_table) player_context_table = g_hash_table_new (g_direct_hash, g_direct_equal); g_hash_table_insert (player_context_table, player, context); } void gtk_mplayer_stop (GtkMPlayer * player) { ChildContext *context; g_return_if_fail (GTK_IS_MPLAYER (player)); context = gtk_mplayer_get_player_child (player); if (!context) return; g_hash_table_remove (player_context_table, context->player); child_context_destroy (context); gtk_widget_queue_draw (GTK_WIDGET (player)); } void gtk_mplayer_toggle_pause (GtkMPlayer * player) { ChildContext *context; const gchar *command = "pause\n"; g_return_if_fail (GTK_IS_MPLAYER (player)); if (!gtk_mplayer_is_running (player)) return; if (!player_context_table) return; context = g_hash_table_lookup (player_context_table, player); if (!context) return; g_return_if_fail (context->stdin_fd > 0); write (context->stdin_fd, command, strlen (command)); } void gtk_mplayer_set_speed (GtkMPlayer * player, gfloat speed) { g_return_if_fail (GTK_IS_MPLAYER (player)); g_return_if_fail (speed > 0.00999 && speed < 100.00001); if (player->status == GtkMPlayerStatusPlay || player->status == GtkMPlayerStatusPause) { gfloat pos = player->pos; gtk_mplayer_stop (player); gtk_mplayer_start (player, pos, speed); } else { player->speed = speed; } } gfloat gtk_mplayer_get_speed (GtkMPlayer * player) { g_return_val_if_fail (GTK_IS_MPLAYER (player), 1.0); return player->speed; } void gtk_mplayer_seek (GtkMPlayer * player, gfloat percentage) { ChildContext *context; gchar buf[64]; g_return_if_fail (GTK_IS_MPLAYER (player)); if (!gtk_mplayer_is_running (player)) return; g_return_if_fail (percentage > -0.000001 && percentage < 100.000001); if (!player_context_table) return; context = g_hash_table_lookup (player_context_table, player); if (!context) return; g_return_if_fail (context->stdin_fd > 0); g_snprintf (buf, 64, "seek %f 1\n", percentage); write (context->stdin_fd, buf, strlen (buf)); } void gtk_mplayer_seek_by_time (GtkMPlayer * player, gfloat second) { ChildContext *context; gchar buf[64]; g_return_if_fail (GTK_IS_MPLAYER (player)); if (!gtk_mplayer_is_running (player)) return; g_return_if_fail (second >= 0); if (!player_context_table) return; context = g_hash_table_lookup (player_context_table, player); if (!context) return; g_return_if_fail (context->stdin_fd > 0); g_snprintf (buf, 64, "seek %f 2\n", second); write (context->stdin_fd, buf, strlen (buf)); } void gtk_mplayer_seek_relative (GtkMPlayer * player, gfloat second) { ChildContext *context; gchar buf[64]; g_return_if_fail (GTK_IS_MPLAYER (player)); if (!gtk_mplayer_is_running (player)) return; g_return_if_fail (second >= 0); if (!player_context_table) return; context = g_hash_table_lookup (player_context_table, player); if (!context) return; g_return_if_fail (context->stdin_fd > 0); g_snprintf (buf, 64, "seek %f 0\n", second); write (context->stdin_fd, buf, strlen (buf)); } void gtk_mplayer_set_audio_delay (GtkMPlayer * player, gfloat second) { ChildContext *context; gchar buf[64]; g_return_if_fail (GTK_IS_MPLAYER (player)); if (!gtk_mplayer_is_running (player)) return; g_return_if_fail (second >= 0); if (!player_context_table) return; context = g_hash_table_lookup (player_context_table, player); if (!context) return; g_return_if_fail (context->stdin_fd > 0); g_snprintf (buf, 64, "audio_delay %f\n", second); write (context->stdin_fd, buf, strlen (buf)); } void gtk_mplayer_set_file (GtkMPlayer * player, const gchar * file) { g_return_if_fail (GTK_IS_MPLAYER (player)); g_return_if_fail (!gtk_mplayer_is_running (player)); g_free (player->filename); gtk_mplayer_media_info_init (&player->media_info); if (file && *file) player->filename = g_strdup (file); else player->filename = NULL; } void gtk_mplayer_set_flags (GtkMPlayer * player, GtkMPlayerFlags flags) { g_return_if_fail (GTK_IS_MPLAYER (player)); player->flags |= flags; } void gtk_mplayer_unset_flags (GtkMPlayer * player, GtkMPlayerFlags flags) { g_return_if_fail (GTK_IS_MPLAYER (player)); player->flags &= ~flags; } GtkMPlayerFlags gtk_mplayer_get_flags (GtkMPlayer * player, GtkMPlayerFlags flags) { g_return_val_if_fail (GTK_IS_MPLAYER (player), 0); return player->flags; } static void get_drivers_done (gpointer data) { gtk_main_quit (); } static GList * get_drivers (GtkMPlayer * player, gboolean refresh, GHashTable * drivers_table, const gchar * separator, const gchar * option) { GList *drivers_list = NULL; const gchar *command = player ? player->command : "mplayer"; gchar *key; gboolean found; g_return_val_if_fail (drivers_table, NULL); g_return_val_if_fail (separator && option, NULL); found = g_hash_table_lookup_extended (drivers_table, command, (gpointer) & key, (gpointer) & drivers_list); if (!found) drivers_list = NULL; if (drivers_list && refresh) { g_hash_table_remove (drivers_table, command); g_free (key); key = NULL; g_list_foreach (drivers_list, (GFunc) g_free, NULL); g_list_free (drivers_list); drivers_list = NULL; } if (!drivers_list) { GetDriversContext dcontext; GList *args = NULL; args = g_list_append (args, g_strdup (command)); args = g_list_append (args, g_strdup (option)); args = g_list_append (args, g_strdup ("help")); dcontext.separator = separator; dcontext.sep_len = strlen (separator); dcontext.passing_header = TRUE; dcontext.drivers_list = g_list_append (NULL, g_strdup ("default")); start_command (player, args, process_line_get_drivers, &dcontext, get_drivers_done); gtk_main (); if (dcontext.drivers_list) g_hash_table_insert (drivers_table, g_strdup (command), dcontext.drivers_list); drivers_list = dcontext.drivers_list; } return drivers_list; } const GList * gtk_mplayer_get_video_out_drivers (GtkMPlayer * player, gboolean refresh) { if (player) g_return_val_if_fail (GTK_IS_MPLAYER (player), NULL); if (!vo_drivers_table) vo_drivers_table = g_hash_table_new (g_str_hash, g_str_equal); return get_drivers (player, refresh, vo_drivers_table, "Available video output drivers:", "-vo"); } const GList * gtk_mplayer_get_audio_out_drivers (GtkMPlayer * player, gboolean refresh) { if (player) g_return_val_if_fail (GTK_IS_MPLAYER (player), NULL); if (!ao_drivers_table) ao_drivers_table = g_hash_table_new (g_str_hash, g_str_equal); return get_drivers (player, refresh, ao_drivers_table, "Available audio output drivers:", "-ao"); } void gtk_mplayer_set_video_out_driver (GtkMPlayer * player, const gchar * vo_driver) { const GList *list, *node; g_return_if_fail (GTK_IS_MPLAYER (player)); g_free (player->vo); if (!vo_driver || !*vo_driver || !strcasecmp (vo_driver, "default")) { player->vo = NULL; return; } list = gtk_mplayer_get_video_out_drivers (player, FALSE); /* * validate */ for (node = list; node; node = g_list_next (node)) { gchar *str = node->data; if (*str && !strcmp (str, vo_driver)) { player->vo = g_strdup (vo_driver); return; } } player->vo = NULL; } void gtk_mplayer_set_audio_out_driver (GtkMPlayer * player, const gchar * ao_driver) { const GList *list, *node; g_return_if_fail (GTK_IS_MPLAYER (player)); g_free (player->ao); list = gtk_mplayer_get_audio_out_drivers (player, FALSE); if (!ao_driver | !*ao_driver || !strcasecmp (ao_driver, "default")) { player->ao = NULL; return; } /* * validate */ for (node = list; node; node = g_list_next (node)) { gchar *str = node->data; if (*str && !strcmp (str, ao_driver)) { player->ao = g_strdup (ao_driver); return; } } player->ao = NULL; } /****************************************************************************** * * MPlayer handling. * ******************************************************************************/ static ChildContext * start_command (GtkMPlayer * player, GList * arg_list, /* include command */ ProcessLineFunc func, gpointer data, GtkDestroyNotify destroy_fn) { ChildContext *context; pid_t pid; int in_fd[2], out_fd[2], err_fd[2]; if (pipe (out_fd) < 0) goto ERROR0; if (pipe (err_fd) < 0) goto ERROR1; if (pipe (in_fd) < 0) goto ERROR2; pid = fork (); if (pid < 0) goto ERROR3; /* * child process */ if (pid == 0) { GList *list; gchar **argv; gint i = 0, n_args; /* * set pipe */ close (out_fd[0]); close (err_fd[0]); close (in_fd[1]); dup2 (out_fd[1], STDOUT_FILENO); dup2 (err_fd[1], STDERR_FILENO); dup2 (in_fd[0], STDIN_FILENO); /* * set args */ n_args = g_list_length (arg_list); argv = g_new0 (gchar *, n_args + 1); for (list = arg_list; list; list = g_list_next (list)) argv[i++] = list->data; argv[i] = NULL; /* * exec */ putenv ("LC_ALL=C"); execvp (argv[0], argv); _exit (255); } /* * parent process */ context = g_new0 (ChildContext, 1); context->pid = pid; context->args = arg_list; context->player = player; context->process_line_fn = func; context->data = data; context->destroy_fn = destroy_fn; context->stdout_fd = out_fd[0]; fcntl (context->stdout_fd, F_SETFL, O_NONBLOCK); close (out_fd[1]); context->stdoutb[0] = '\0'; context->stdout_size = 0; context->stderr_fd = err_fd[0]; fcntl (context->stderr_fd, F_SETFL, O_NONBLOCK); close (err_fd[1]); context->stderrb[0] = '\0'; context->stderr_size = 0; context->stdin_fd = in_fd[1]; close (in_fd[0]); /* * observe output */ context->checker_id = gtk_timeout_add (GTK_MPLAYER_REFRESH_RATE, timeout_check_child, context); return context; ERROR3: close (in_fd[0]); close (in_fd[1]); ERROR2: close (err_fd[0]); close (err_fd[1]); ERROR1: close (out_fd[0]); close (out_fd[1]); ERROR0: g_list_foreach (arg_list, (GFunc) g_free, NULL); g_list_free (arg_list); return NULL; } static gint timeout_check_child (gpointer data) { ChildContext *context = data; GtkMPlayer *player = context->player; if (context->pid <= 0) return FALSE; if (!process_output (context)) { pid_t pid; int status; pid = waitpid (context->pid, &status, WNOHANG); context->pid = 0; context->checker_id = 0; if (context == gtk_mplayer_get_player_child (player)) g_hash_table_remove (player_context_table, context->player); child_context_destroy (context); gtk_widget_queue_draw (GTK_WIDGET (player)); return FALSE; } return TRUE; } #define LINE_END(end, str) \ { \ gchar *end1, *end2; \ if (!str || !*str) { \ end = NULL; \ } else { \ end1 = strchr (str, '\n'); \ end2 = strchr (str, '\r'); \ if (end1 && end2) \ end = (end2 < end1) ? end2 : end1; \ else \ end = end1 ? end1 : end2; \ } \ } static void process_lines (ChildContext * context, gchar buf[GTK_MPLAYER_BUF_SIZE * 2], gint size, gchar stock_buf[GTK_MPLAYER_BUF_SIZE], gint * remain_size, gboolean is_stderr) { gint len; gchar *src, *end; g_return_if_fail (buf && stock_buf); g_return_if_fail (size > 0 || size < GTK_MPLAYER_BUF_SIZE); g_return_if_fail (remain_size); src = buf; LINE_END (end, src); while (end && src) { *end = '\0'; len = end - src; if (context->process_line_fn) context->process_line_fn (context, src, len, is_stderr); src = end + 1; size -= (end - src); if (src) { LINE_END (end, src); } else { end = NULL; } } if (size <= 0 || size > GTK_MPLAYER_BUF_SIZE - 1 || !src || !*src) { stock_buf[0] = '\0'; *remain_size = 0; } else { memcpy (stock_buf, src, size); stock_buf[size] = '\0'; *remain_size = size; } } static gboolean process_output (ChildContext * context) { gint n, size; gchar buf[GTK_MPLAYER_BUF_SIZE * 2], *next; gfloat pos; /* * stderr */ size = context->stderr_size; if (size > 0 && size < GTK_MPLAYER_BUF_SIZE) { memcpy (buf, context->stderrb, size); next = buf + size; } else { next = buf; } n = read (context->stderr_fd, buf, GTK_MPLAYER_BUF_SIZE - 1); if (n == 0) { } else if (n > 0) { next[MIN (GTK_MPLAYER_BUF_SIZE - 1, n)] = '\0'; size += MIN (GTK_MPLAYER_BUF_SIZE - 1, n); process_lines (context, buf, size, context->stderrb, &context->stderr_size, TRUE); } /* * stdout */ size = context->stdout_size; if (size > 0 && size < GTK_MPLAYER_BUF_SIZE) { memcpy (buf, context->stdoutb, size); next = buf + size; } else { next = buf; } n = read (context->stdout_fd, next, GTK_MPLAYER_BUF_SIZE - 1); if (n == 0) return FALSE; else if (n < 0) return TRUE; next[MIN (GTK_MPLAYER_BUF_SIZE - 1, n)] = '\0'; size += MIN (GTK_MPLAYER_BUF_SIZE - 1, n); pos = context->player->pos; process_lines (context, buf, size, context->stdoutb, &context->stdout_size, FALSE); if (fabs (context->player->pos - pos) > 0.1) gtk_signal_emit (GTK_OBJECT (context->player), gtk_mplayer_signals[POS_CHANGED_SIGNAL]); return TRUE; } static void child_context_destroy (ChildContext * context) { if (context->checker_id > 0) { gtk_timeout_remove (context->checker_id); context->checker_id = 0; } if (context->pid > 0) { kill (context->pid, SIGKILL); waitpid (context->pid, NULL, WUNTRACED); context->pid = 0; } g_list_foreach (context->args, (GFunc) g_free, NULL); g_list_free (context->args); context->args = NULL; if (context->stdout_fd) close (context->stdout_fd); context->stdout_fd = 0; if (context->stderr_fd) close (context->stderr_fd); context->stderr_fd = 0; if (context->stdin_fd) close (context->stdin_fd); context->stdin_fd = 0; if (context->data && context->destroy_fn) context->destroy_fn (context->data); context->data = NULL; g_free (context); } /****************************************************************************** * * Line process funcs. * ******************************************************************************/ static void process_line (ChildContext * context, const gchar * line, gint len, gboolean is_stderr) { GtkMPlayer *player = context->player; if (is_stderr) { } else { if (!strcmp (line, "------ PAUSED -------")) { player->status = GtkMPlayerStatusPause; gtk_signal_emit (GTK_OBJECT (player), gtk_mplayer_signals[PAUSE_SIGNAL]); } else if (len > 2 && (!strncmp (line, "A:", 2) || !strncmp (line, "V:", 2))) { gchar timestr[16]; const gchar *begin = line + 2, *end; /* * set status */ if (player->status != GtkMPlayerStatusPlay) { player->status = GtkMPlayerStatusPlay; gtk_signal_emit (GTK_OBJECT (player), gtk_mplayer_signals[PLAY_SIGNAL]); } /* * get movie position */ while (isspace (*begin) && *begin) begin++; if (*begin) { end = begin + 1; while (!isspace (*end) && *end) end++; len = end - begin; if (len > 0 && len < 15) { memcpy (timestr, begin, len); timestr[len] = '\0'; player->pos = atof (timestr); } } } else if (!strncasecmp (line, "VIDEO: ", 7)) { if (!strncmp (line + 7, "no video!!!", 11)) { context->player->media_info.flags |= GtkMPlayerMediaInfoNoVideoFlag; } else { } } else if (!strncasecmp (line, "Audio: ", 7)) { if (!strncmp (line + 7, "no sound!!!", 11)) { context->player->media_info.flags |= GtkMPlayerMediaInfoNoAudioFlag; } else { } } else { } } } static void process_line_get_drivers (ChildContext * context, const gchar * line, gint len, gboolean is_stderr) { GetDriversContext *dcontext; gchar *str, *id_str, *exp_str = NULL, *end; g_return_if_fail (context); g_return_if_fail (context->data); if (!line || !*line || len <= 0) return; dcontext = context->data; if (dcontext->passing_header) { if (len >= dcontext->sep_len && !strncmp (line, dcontext->separator, dcontext->sep_len)) { dcontext->passing_header = FALSE; } return; } str = id_str = g_strdup (line); /* * get id */ while (isspace (*id_str) && *id_str) id_str++; if (!*id_str) { g_free (str); return; } end = id_str + 1; while (!isspace (*end) && *end) end++; if (*end) exp_str = end + 1; *end = '\0'; /* * get name */ if (exp_str) { /* * exp_str = g_strstrip (exp_str); */ } /* * append to list */ dcontext->drivers_list = g_list_append (dcontext->drivers_list, g_strdup (id_str)); g_free (str); } #endif /* ENABLE_MPLAYER */