From 86790494d38c4f33f74875e1ab7a9dc1bdae1772 Mon Sep 17 00:00:00 2001 From: Stefano Pigozzi Date: Tue, 3 Apr 2012 08:13:12 +0200 Subject: [PATCH] OSX, input: implement wakeup in response to Cocoa events Add code to wake up the select() call in input.c when an OSX event is available and a Cocoa OpenGL backend is initialized. Fixes the slow response to input or other events in Cocoa-based VOs during long select() sleeps (e.g., when mplayer2 is paused) introduced by commit 7040968. --- Makefile | 1 + configure | 5 ++ input/input.c | 24 ++++++++- libvo/cocoa_common.h | 2 + libvo/cocoa_common.m | 6 +++ osdep/cocoa_events.h | 30 +++++++++++ osdep/cocoa_events.m | 137 +++++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 osdep/cocoa_events.h create mode 100644 osdep/cocoa_events.m diff --git a/Makefile b/Makefile index dacff8b690..36420a881f 100644 --- a/Makefile +++ b/Makefile @@ -100,6 +100,7 @@ SRCS_COMMON-$(LIVE555) += libmpdemux/demux_rtp.cpp \ libmpdemux/demux_rtp_codec.cpp \ stream/stream_live555.c SRCS_COMMON-$(MACOSX_FINDER) += osdep/macosx_finder_args.m +SRCS_COMMON-$(COCOA) += osdep/cocoa_events.m SRCS_COMMON-$(MNG) += libmpdemux/demux_mng.c SRCS_COMMON-$(MPG123) += libmpcodecs/ad_mpg123.c diff --git a/configure b/configure index 22926751e9..4a6e209d3a 100755 --- a/configure +++ b/configure @@ -4205,6 +4205,9 @@ EOF fi if test "$_cocoa" = yes ; then libs_mplayer="$libs_mplayer -framework Cocoa -framework OpenGL" + def_cocoa='#define CONFIG_COCOA 1' +else + def_cocoa='#undef CONFIG_COCOA' fi echores "$_cocoa" @@ -6383,6 +6386,7 @@ BL = $_bl CACA = $_caca CDDA = $_cdda CDDB = $_cddb +COCOA = $_cocoa COREAUDIO = $_coreaudio COREVIDEO = $_corevideo SHAREDBUFFER = $_sharedbuffer @@ -6785,6 +6789,7 @@ $def_aa $def_bl $def_caca $def_corevideo +$def_cocoa $def_sharedbuffer $def_dga $def_dga1 diff --git a/input/input.c b/input/input.c index e422e9f1a2..191b378deb 100644 --- a/input/input.c +++ b/input/input.c @@ -59,6 +59,10 @@ #include "ar.h" +#ifdef CONFIG_COCOA +#include "osdep/cocoa_events.h" +#endif + #define MP_MAX_KEY_DOWN 32 struct cmd_bind { @@ -1436,7 +1440,7 @@ static void read_events(struct input_ctx *ictx, int time) * every source until it's known to be empty. Instead we use this wrapper * to run select() again. */ -static void read_all_events(struct input_ctx *ictx, int time) +static void read_all_fd_events(struct input_ctx *ictx, int time) { while (1) { read_events(ictx, time); @@ -1446,6 +1450,15 @@ static void read_all_events(struct input_ctx *ictx, int time) } } +static void read_all_events(struct input_ctx *ictx, int time) +{ +#ifdef CONFIG_COCOA + cocoa_events_read_all_events(ictx, time); +#else + read_all_fd_events(ictx, time); +#endif +} + int mp_input_queue_cmd(struct input_ctx *ictx, mp_cmd_t *cmd) { ictx->got_new_events = true; @@ -1743,6 +1756,10 @@ struct input_ctx *mp_input_init(struct input_conf *input_conf) .wakeup_pipe = {-1, -1}, }; +#ifdef CONFIG_COCOA + cocoa_events_init(ictx, read_all_fd_events); +#endif + #ifndef __MINGW32__ long ret = pipe(ictx->wakeup_pipe); for (int i = 0; i < 2 && ret >= 0; i++) { @@ -1848,11 +1865,16 @@ struct input_ctx *mp_input_init(struct input_conf *input_conf) mp_tmsg(MSGT_INPUT, MSGL_ERR, "Can't open %s: %s\n", input_conf->in_file, strerror(errno)); } + return ictx; } void mp_input_uninit(struct input_ctx *ictx) { +#ifdef CONFIG_COCOA + cocoa_events_uninit(); +#endif + if (!ictx) return; diff --git a/libvo/cocoa_common.h b/libvo/cocoa_common.h index a118f37149..89b0ea663b 100644 --- a/libvo/cocoa_common.h +++ b/libvo/cocoa_common.h @@ -22,6 +22,8 @@ #include "video_out.h" +bool vo_cocoa_gui_running(void); + int vo_cocoa_init(struct vo *vo); void vo_cocoa_uninit(struct vo *vo); diff --git a/libvo/cocoa_common.m b/libvo/cocoa_common.m index 71a8072b7c..63e8e90db4 100644 --- a/libvo/cocoa_common.m +++ b/libvo/cocoa_common.m @@ -112,6 +112,11 @@ struct vo_cocoa_state *vo_cocoa_init_state(void) return s; } +bool vo_cocoa_gui_running(void) +{ + return !!s; +} + int vo_cocoa_init(struct vo *vo) { s = vo_cocoa_init_state(); @@ -137,6 +142,7 @@ void vo_cocoa_uninit(struct vo *vo) s->pool = nil; talloc_free(s); + s = nil; } void update_screen_info(void) diff --git a/osdep/cocoa_events.h b/osdep/cocoa_events.h new file mode 100644 index 0000000000..3f0e775e03 --- /dev/null +++ b/osdep/cocoa_events.h @@ -0,0 +1,30 @@ +/* + * Cocoa Event Handling + * + * This file is part of mplayer2. + * + * mplayer2 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. + * + * mplayer2 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 mplayer2. If not, see . + */ + +#ifndef MPLAYER_COCOA_EVENTS_H +#define MPLAYER_COCOA_EVENTS_H + +#include "input/input.h" + +void cocoa_events_init(struct input_ctx *ictx, + void (*read_all_fd_events)(struct input_ctx *ictx, int time)); +void cocoa_events_uninit(void); +void cocoa_events_read_all_events(struct input_ctx *ictx, int time); + +#endif /* MPLAYER_COCOA_EVENTS_H */ diff --git a/osdep/cocoa_events.m b/osdep/cocoa_events.m new file mode 100644 index 0000000000..ba09c13cad --- /dev/null +++ b/osdep/cocoa_events.m @@ -0,0 +1,137 @@ +/* + * Cocoa Event Handling + * + * This file is part of mplayer2. + * + * mplayer2 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. + * + * mplayer2 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 mplayer2. If not, see . + */ + +/* + * Implementation details: + * This file deals with custom event polling on MacOSX. When mplayer2 is paused + * it will asynchronously poll for events using select. This works correctly on + * Linux with X11 since the events are notified through the file descriptors + * where mplayer2 is listening on. On the other hand, the OSX window server + * notifies the processes for events using mach ports. + * + * The code below uses functionality from Cocoa that abstracts the async polling + * of events from the window server. When a Cocoa event comes in, the polling is + * interrupted and the event is dealt with in the next vo_check_events. + * + * To keep the select fd polling code working, that functionality is executed + * from another thread. Whoever finishes polling before the given time, be it + * Cocoa or the original select code, notifies the other for an immediate wake. + */ + +#include "cocoa_events.h" +#include "libvo/cocoa_common.h" +#include "talloc.h" + +#import +#include + +// Bogus event subtype to wake the Cocoa code from polling state +#define MP_EVENT_SUBTYPE_WAKE_EVENTLOOP 100 + +// This is the threshold in milliseconds below which the Cocoa polling is not +// executed. There is some overhead caused by the synchronization between +// threads. Even if in practice it isn't noticeable, we try to avoid the useless +// waste of resources. +#define MP_ASYNC_THRESHOLD 50 + +struct priv { + dispatch_queue_t select_queue; + bool is_runloop_polling; + void (*read_all_fd_events)(struct input_ctx *ictx, int time); +}; + +static struct priv *p; + +static void cocoa_wait_events(int mssleeptime) +{ + NSTimeInterval sleeptime = mssleeptime / 1000.0; + NSEvent *event; + p->is_runloop_polling = YES; + event = [NSApp nextEventMatchingMask:NSAnyEventMask + untilDate:[NSDate dateWithTimeIntervalSinceNow:sleeptime] + inMode:NSEventTrackingRunLoopMode dequeue:NO]; + + // dequeue the next event if it is a fake to wake the cocoa polling + if (event && [event type] == NSApplicationDefined && + [event subtype] == MP_EVENT_SUBTYPE_WAKE_EVENTLOOP) { + [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:nil + inMode:NSEventTrackingRunLoopMode dequeue:YES]; + } + p->is_runloop_polling = NO; +} + +static void cocoa_wake_runloop() +{ + if (p->is_runloop_polling) { + NSAutoreleasePool *pool = [NSAutoreleasePool new]; + NSEvent *event; + + /* Post an event so we'll wake the run loop that is async polling */ + event = [NSEvent otherEventWithType: NSApplicationDefined + location: NSZeroPoint + modifierFlags: 0 + timestamp: 0 + windowNumber: 0 + context: nil + subtype: MP_EVENT_SUBTYPE_WAKE_EVENTLOOP + data1: 0 + data2: 0]; + + [NSApp postEvent:event atStart:NO]; + [pool release]; + } +} + +void cocoa_events_init(struct input_ctx *ictx, + void (*read_all_fd_events)(struct input_ctx *ictx, int time)) +{ + NSApplicationLoad(); + p = talloc_ptrtype(NULL, p); + *p = (struct priv){ + .is_runloop_polling = NO, + .read_all_fd_events = read_all_fd_events, + .select_queue = dispatch_queue_create("org.mplayer2.select_queue", + NULL), + }; +} + +void cocoa_events_uninit(void) +{ + talloc_free(p); +} + +void cocoa_events_read_all_events(struct input_ctx *ictx, int time) +{ + // don't bother delegating the select to the async queue if the blocking + // time is really low or if we are not running a GUI + if (time > MP_ASYNC_THRESHOLD && vo_cocoa_gui_running()) { + dispatch_async(p->select_queue, ^{ + p->read_all_fd_events(ictx, time); + cocoa_wake_runloop(); + }); + + cocoa_wait_events(time); + mp_input_wakeup(ictx); + + // wait for the async queue to get empty. + dispatch_sync(p->select_queue, ^{}); + } else { + p->read_all_fd_events(ictx, time); + } +} -- 2.11.4.GIT