diff --git a/DOCS/man/en/vo.rst b/DOCS/man/en/vo.rst index 4e4e08ee80..9a342acb68 100644 --- a/DOCS/man/en/vo.rst +++ b/DOCS/man/en/vo.rst @@ -449,6 +449,13 @@ Available video output drivers are: property, as using both is somewhat redundant. It also enables linear light scaling. + + ``icc-profile-auto`` + Automatically select the ICC display profile currently specified by + the display settings of the operating system. + + NOTE: Only implemented on OS X with Cocoa. + ``icc-cache=`` Store and load the 3D LUT created from the ICC profile in this file. This can be used to speed up loading, since LittleCMS2 can take a while diff --git a/video/out/cocoa/mpvadapter.h b/video/out/cocoa/mpvadapter.h index 9833993988..af536d2f56 100644 --- a/video/out/cocoa/mpvadapter.h +++ b/video/out/cocoa/mpvadapter.h @@ -26,6 +26,7 @@ - (void)putCommand:(char*)cmd; - (void)performAsyncResize:(NSSize)size; - (void)handleFilesArray:(NSArray *)files; +- (void)didChangeWindowedScreenProfile:(NSScreen *)screen; - (BOOL)isInFullScreenMode; - (NSScreen *)fsScreen; diff --git a/video/out/cocoa/window.m b/video/out/cocoa/window.m index e760fd184d..009315d7ac 100644 --- a/video/out/cocoa/window.m +++ b/video/out/cocoa/window.m @@ -54,10 +54,16 @@ [self.adapter setNeedsResize]; } -- (void)windowDidChangeBackingProperties:(NSNotification *)notification { +- (void)windowDidChangeBackingProperties:(NSNotification *)notification +{ [self.adapter setNeedsResize]; } +- (void)windowDidChangeScreenProfile:(NSNotification *)notification +{ + [self.adapter didChangeWindowedScreenProfile:[self screen]]; +} + - (BOOL)isInFullScreenMode { return (([self styleMask] & NSFullScreenWindowMask) == diff --git a/video/out/cocoa_common.m b/video/out/cocoa_common.m index 5780af3bdc..7e660e9fae 100644 --- a/video/out/cocoa_common.m +++ b/video/out/cocoa_common.m @@ -43,8 +43,12 @@ #include "common/msg.h" +#define CF_RELEASE(a) if ((a) != NULL) CFRelease(a) + static void vo_cocoa_fullscreen(struct vo *vo); static void vo_cocoa_ontop(struct vo *vo); +static void cocoa_change_profile(struct vo *vo, char **store, NSScreen *screen); +static void cocoa_rm_fs_screen_profile_observer(struct vo *vo); struct vo_cocoa_state { MpvVideoWindow *window; @@ -73,6 +77,11 @@ struct vo_cocoa_state { uint32_t old_dwidth; uint32_t old_dheight; + + bool icc_profile_path_changed; + char *icc_wnd_profile_path; + char *icc_fs_profile_path; + id fs_icc_changed_ns_observer; }; void *vo_cocoa_glgetaddr(const char *s) @@ -118,6 +127,7 @@ int vo_cocoa_init(struct vo *vo) .lock = [[NSLock alloc] init], .enable_resize_redraw = NO, .log = mp_log_new(s, vo->log, "cocoa"), + .icc_profile_path_changed = false, }; vo->cocoa = s; return 1; @@ -141,6 +151,7 @@ void vo_cocoa_uninit(struct vo *vo) dispatch_sync(dispatch_get_main_queue(), ^{ struct vo_cocoa_state *s = vo->cocoa; enable_power_management(vo); + cocoa_rm_fs_screen_profile_observer(vo); [NSApp setPresentationOptions:NSApplicationPresentationDefault]; // XXX: It looks like there are some circular retain cycles for the @@ -377,6 +388,35 @@ static void vo_cocoa_resize_redraw(struct vo *vo, int width, int height) vo_cocoa_set_current_context(vo, false); } +static void cocoa_rm_fs_screen_profile_observer(struct vo *vo) +{ + struct vo_cocoa_state *s = vo->cocoa; + [[NSNotificationCenter defaultCenter] + removeObserver:s->fs_icc_changed_ns_observer]; +} + +static void cocoa_add_fs_screen_profile_observer(struct vo *vo) +{ + struct vo_cocoa_state *s = vo->cocoa; + + if (s->fs_icc_changed_ns_observer) + cocoa_rm_fs_screen_profile_observer(vo); + + if (vo->opts->fsscreen_id < 0) + return; + + void (^nblock)(NSNotification *n) = ^(NSNotification *n) { + cocoa_change_profile(vo, &s->icc_fs_profile_path, s->fs_screen); + s->icc_profile_path_changed = true; + }; + + s->fs_icc_changed_ns_observer = [[NSNotificationCenter defaultCenter] + addObserverForName:NSScreenColorSpaceDidChangeNotification + object:s->fs_screen + queue:nil + usingBlock:nblock]; +} + int vo_cocoa_config_window(struct vo *vo, uint32_t width, uint32_t height, uint32_t flags, int gl3profile) { @@ -419,6 +459,7 @@ int vo_cocoa_config_window(struct vo *vo, uint32_t width, uint32_t height, [s->window queueNewVideoSize:NSMakeSize(width, height)]; cocoa_set_window_title(vo, vo_get_window_title(vo)); vo_cocoa_fullscreen(vo); + cocoa_add_fs_screen_profile_observer(vo); } s->inside_sync_section = false; @@ -474,6 +515,11 @@ int vo_cocoa_check_events(struct vo *vo) return VO_EVENT_RESIZE; } + if (s->icc_profile_path_changed) { + s->icc_profile_path_changed = false; + return VO_EVENT_ICC_PROFILE_PATH_CHANGED; + } + return 0; } @@ -503,12 +549,124 @@ static void vo_cocoa_fullscreen(struct vo *vo) [s->view setFullScreen:opts->fullscreen]; } + if (s->icc_fs_profile_path != s->icc_wnd_profile_path) + s->icc_profile_path_changed = true; + [s->window didChangeFullScreenState]; // Make the core aware of the view size change. resize_window(vo); } +static char *cocoa_get_icc_profile_path(struct vo *vo, NSScreen *screen) +{ + assert(screen); + + struct vo_cocoa_state *s = vo->cocoa; + char *result = NULL; + CFDictionaryRef device_info = NULL; + + CGDirectDisplayID displayID = (CGDirectDisplayID) + [[screen deviceDescription][@"NSScreenNumber"] unsignedLongValue]; + + CFUUIDRef uuid = CGDisplayCreateUUIDFromDisplayID(displayID); + if (CFGetTypeID(uuid) == CFNullGetTypeID()) { + MP_ERR(s, "cannot get display UUID.\n"); + goto get_icc_profile_path_err_out; + } + + device_info = + ColorSyncDeviceCopyDeviceInfo(kColorSyncDisplayDeviceClass, uuid); + + CFRelease(uuid); + + if (!device_info) { + MP_ERR(s, "cannot get display info.\n"); + goto get_icc_profile_path_err_out; + } + + CFDictionaryRef factory_info = + CFDictionaryGetValue(device_info, kColorSyncFactoryProfiles); + if (!factory_info) { + MP_ERR(s, "cannot get display factory settings.\n"); + goto get_icc_profile_path_err_out; + } + + CFStringRef default_profile_id = + CFDictionaryGetValue(factory_info, kColorSyncDeviceDefaultProfileID); + if (!default_profile_id) { + MP_ERR(s, "cannot get display default profile ID.\n"); + goto get_icc_profile_path_err_out; + } + + CFURLRef icc_url; + CFDictionaryRef custom_profile_info = + CFDictionaryGetValue(device_info, kColorSyncCustomProfiles); + if (custom_profile_info) { + icc_url = CFDictionaryGetValue(custom_profile_info, default_profile_id); + // If icc_url is NULL, the ICC profile URL could not be retrieved + // although a custom profile was specified. This points to a + // configuration error, so we should not fall back to the factory + // profile, but return an error instead. + if (!icc_url) { + MP_ERR(s, "cannot get display profile URL\n"); + goto get_icc_profile_path_err_out; + } + } else { + // No custom profile specified; try factory profile for the device + CFDictionaryRef factory_profile_info = + CFDictionaryGetValue(factory_info, default_profile_id); + if (!factory_profile_info) { + MP_ERR(s, "cannot get display profile info\n"); + goto get_icc_profile_path_err_out; + } + + icc_url = CFDictionaryGetValue(factory_profile_info, + kColorSyncDeviceProfileURL); + if (!icc_url) { + MP_ERR(s, "cannot get display factory profile URL.\n"); + goto get_icc_profile_path_err_out; + } + } + + result = talloc_strdup(vo, (char *)[[(NSURL *)icc_url path] UTF8String]); + if (!result) + MP_ERR(s, "cannot get display profile path.\n"); + +get_icc_profile_path_err_out: + CF_RELEASE(device_info); + return result; +} + +static void cocoa_change_profile(struct vo *vo, char **store, NSScreen *screen) +{ + if (*store) + talloc_free(*store); + *store = cocoa_get_icc_profile_path(vo, screen); +} + +static void vo_cocoa_control_get_icc_profile_path(struct vo *vo, void *arg) +{ + struct vo_cocoa_state *s = vo->cocoa; + char **p = arg; + + vo_cocoa_update_screen_info(vo); + + NSScreen *screen; + char **path; + + if (vo->opts->fullscreen) { + screen = s->fs_screen; + path = &s->icc_fs_profile_path; + } else { + screen = s->current_screen; + path = &s->icc_wnd_profile_path; + } + + cocoa_change_profile(vo, path, screen); + *p = *path; +} + int vo_cocoa_control(struct vo *vo, int *events, int request, void *arg) { switch (request) { @@ -557,6 +715,9 @@ int vo_cocoa_control(struct vo *vo, int *events, int request, void *arg) case VOCTRL_KILL_SCREENSAVER: disable_power_management(vo); return VO_TRUE; + case VOCTRL_GET_ICC_PROFILE_PATH: + vo_cocoa_control_get_icc_profile_path(vo, arg); + return VO_TRUE; } return VO_NOTIMPL; } @@ -652,4 +813,11 @@ int vo_cocoa_cgl_color_size(struct vo *vo) { [mpv_shared_app() handleFilesArray:files]; } + +- (void)didChangeWindowedScreenProfile:(NSScreen *)screen +{ + struct vo_cocoa_state *s = self.vout->cocoa; + cocoa_change_profile(self.vout, &s->icc_wnd_profile_path, screen); + s->icc_profile_path_changed = true; +} @end diff --git a/video/out/gl_lcms.c b/video/out/gl_lcms.c index 08f89b81d9..d2f59306f6 100644 --- a/video/out/gl_lcms.c +++ b/video/out/gl_lcms.c @@ -71,6 +71,7 @@ static int validate_3dlut_size_opt(struct mp_log *log, const m_option_t *opt, const struct m_sub_options mp_icc_conf = { .opts = (m_option_t[]) { OPT_STRING("icc-profile", profile, 0), + OPT_FLAG("icc-profile-auto", profile_auto, 0), OPT_STRING("icc-cache", cache, 0), OPT_INT("icc-intent", intent, 0), OPT_STRING_VALIDATE("3dlut-size", size_str, 0, validate_3dlut_size_opt), @@ -106,6 +107,17 @@ static struct bstr load_file(void *talloc_ctx, const char *filename, return res; } +bool mp_icc_set_profile(struct mp_icc_opts *opts, char *profile) +{ + if (!opts->profile || strcmp(opts->profile, profile) != 0) { + if (opts->profile) + talloc_free(opts->profile); + opts->profile = talloc_strdup(opts, profile); + return true; + } + return false; +} + #define LUT3D_CACHE_HEADER "mpv 3dlut cache 1.0\n" struct lut3d *mp_load_icc(struct mp_icc_opts *opts, struct mp_log *log, diff --git a/video/out/gl_lcms.h b/video/out/gl_lcms.h index a579b78f43..f1ab069574 100644 --- a/video/out/gl_lcms.h +++ b/video/out/gl_lcms.h @@ -1,10 +1,13 @@ #ifndef MP_GL_LCMS_H #define MP_GL_LCMS_H +#include + extern const struct m_sub_options mp_icc_conf; struct mp_icc_opts { char *profile; + int profile_auto; char *cache; char *size_str; int intent; @@ -13,6 +16,7 @@ struct mp_icc_opts { struct lut3d; struct mp_log; struct mpv_global; +bool mp_icc_set_profile(struct mp_icc_opts *opts, char *profile); struct lut3d *mp_load_icc(struct mp_icc_opts *opts, struct mp_log *log, struct mpv_global *global); diff --git a/video/out/vo.h b/video/out/vo.h index c53e788a69..dd98636189 100644 --- a/video/out/vo.h +++ b/video/out/vo.h @@ -32,6 +32,7 @@ #define VO_EVENT_EXPOSE 1 #define VO_EVENT_RESIZE 2 +#define VO_EVENT_ICC_PROFILE_PATH_CHANGED 4 enum mp_voctrl { /* signal a device reset seek */ @@ -82,6 +83,8 @@ enum mp_voctrl { VOCTRL_SCREENSHOT, // struct voctrl_screenshot_args* VOCTRL_SET_COMMAND_LINE, // char** + + VOCTRL_GET_ICC_PROFILE_PATH, // char** }; // VOCTRL_SET_EQUALIZER diff --git a/video/out/vo_opengl.c b/video/out/vo_opengl.c index 485fbdf8fa..c243cdeb85 100644 --- a/video/out/vo_opengl.c +++ b/video/out/vo_opengl.c @@ -275,6 +275,25 @@ static bool update_icc_profile(struct gl_priv *p, struct mp_icc_opts *opts) return true; } +static bool get_and_update_icc_profile(struct vo *vo, + struct mp_icc_opts *opts) +{ + struct gl_priv *p = vo->priv; + + if (!opts->profile_auto) + return update_icc_profile(p, opts); + + char *icc = NULL; + int r = p->glctx->vo_control(vo, NULL, VOCTRL_GET_ICC_PROFILE_PATH, &icc); + if (r != VO_TRUE) + return false; + + if (mp_icc_set_profile(opts, icc)) + return update_icc_profile(p, opts); + + return true; +} + static bool reparse_cmdline(struct gl_priv *p, char *args) { struct m_config *cfg = NULL; @@ -377,6 +396,10 @@ static int control(struct vo *vo, uint32_t request, void *data) resize(p); if (events & VO_EVENT_EXPOSE) vo->want_redraw = true; + if (events & VO_EVENT_ICC_PROFILE_PATH_CHANGED) { + get_and_update_icc_profile(vo, p->icc_opts); + vo->want_redraw = true; + } mpgl_unlock(p->glctx); return r; @@ -416,7 +439,7 @@ static int preinit(struct vo *vo) gl_video_set_output_depth(p->renderer, p->glctx->depth_r, p->glctx->depth_g, p->glctx->depth_b); gl_video_set_options(p->renderer, p->renderer_opts); - if (!update_icc_profile(p, p->icc_opts)) + if (!get_and_update_icc_profile(vo, p->icc_opts)) goto err_out; mpgl_unset_context(p->glctx);