Compare commits

...

12 Commits

Author SHA1 Message Date
rcombs 5e1da6ccab
Merge 3a851741e9 into 69d70148c7 2024-04-27 20:21:51 +08:00
Kacper Michajłow 69d70148c7 TOOLS/lua/autoload: make ignore_patterns more generic 2024-04-27 03:14:31 +02:00
oficsu 3ca71b0c0e TOOLS/lua/autoload: allow multiple ignore_patterns 2024-04-27 03:14:31 +02:00
oficsu 96b34148f1 TOOLS/lua/autoload: add ignore_pattern option
Autoload script can now exclude certain files

Why? Sometimes you want to ignore thumbnails,
auto-generated backups or just unwanted files

A non-exhaustive list of real-world examples:
- user backup files: '%.bak%.mp4$' or '^bak-'
- telegram-desktop thumbnails: '_thumb%.jpg$'
- a krita graphics editor backup suffix: '^~'

See documentation here: lua.org/pil/20.2.html
2024-04-27 03:14:31 +02:00
nanahi 93708a9d38 w32_common: fix show-in-taskbar toggling after explorer is restarted
After explorer is restarted while show-in-taskbar is false, toggling
show-in-taskbar no longer puts mpv back to the taskbar until it's
unfocused and refocused.

My guess of how this works is that the HWND of the taskbar is cached,
and setting the WS_EX_TOOLWINDOW style internally uses this value to
show/hide the taskbar button. But after explorer is restarted it no
longer works until its taskbar state needs to change (such as focusing).
Only then it realizes the HWND is no longer valid and refreshes it.

Fix this by following MS documentation on this: the window needs to be
hidden before changing the style, and be shown after that. This
unfortunately can sometimes introduce a brief window flash, but it
fixes the problem.
2024-04-27 03:02:00 +02:00
Guido Cella 2f4c550b4b zsh-completion: complete --gpu-context
This is made by possible by 96e1f1dfa5 standardizing --gpu-context's
help output. This changes the check to complete any Object settings list
so it will automatically work with future options of this kind.
2024-04-27 03:01:06 +02:00
Kacper Michajłow fbfc9d22c7 ci: add fuzzers build test 2024-04-27 02:47:47 +02:00
Kacper Michajłow 47dbc3a74e fuzzers: add new fuzzer targets
fuzzer_set_property.c:

fuzz mpv_set_property in both initialized and non-initialized state.
Useful for user provided values sanitization test. I've already seen
some memory leaks in parsing code, good to drill it.

fuzzer_loadfile.c:

mpv_command "loadfile" test. Good for testing demuxers, decoding and
playback loop. Sadly in headless mode we can't really test AO and VO,
but at least all the code around can be fuzzed. Especially our custom
demuxers like demux_mkv.

fuzzer_loadfile_direct.c:

Similar to loadfile above, but instead of saving the data to file, it
passes the fuzz input in the command. Generated protocol specific
versions (mf:// and memory:// for now) and generic one.

Nothing really complex, but good start and even those few targets should
give good coverage of the most common code paths in libmpv.
2024-04-27 02:47:47 +02:00
Kacper Michajłow 0b234af113 player/command: mark sub-text-ass as deprecated
Fixes: 437fff9f21
2024-04-27 01:23:52 +02:00
rcombs 3a851741e9 player/command: add clipboard property 2024-04-17 01:12:23 -07:00
rcombs 1e37197cd5 screenshot: add screenshot-to-clipboard command 2024-04-17 01:12:23 -07:00
rcombs 960faddda3 osdep: add clipboard facilities (currently mac-only) 2024-04-17 01:12:23 -07:00
21 changed files with 1280 additions and 10 deletions

View File

@ -205,6 +205,30 @@ jobs:
run: |
cat ./build/meson-logs/testlog.txt
linux-fuzz:
runs-on: "ubuntu-latest"
container:
image: "registry.opensuse.org/home/mia/images/images/mpv-ci:stable-deps"
env:
CC: "clang"
CXX: "clang++"
steps:
- uses: actions/checkout@v4
- name: Build with meson
id: build
run: |
meson setup build \
--werror \
-Dc_args="-Wno-error=deprecated -Wno-error=deprecated-declarations" \
-Dfuzzers=true -Dlibmpv=true -Dcplayer=false
meson compile -C build
- name: Print meson log
if: ${{ failure() && steps.build.outcome == 'failure' }}
run: |
cat ./build/meson-logs/meson-log.txt
linux-ffmpeg-4-4:
runs-on: ubuntu-22.04
container:

View File

@ -402,6 +402,12 @@ Remember to quote string arguments in input.conf (see `Flat command syntax`_).
Like all input command parameters, the filename is subject to property
expansion as described in `Property Expansion`_.
``screenshot-to-clipboard [<flags>]``
Take a screenshot and save it to the system clipboard.
The ``flags`` argument is like the first argument to ``screenshot`` and
supports ``subtitles``, ``video``, ``window``.
``playlist-next <flags>``
Go to the next entry on the playlist.
@ -3434,6 +3440,35 @@ Property list
``current-ao``
Current audio output driver (name as used with ``--ao``).
``clipboard`` (RW)
Access to the system clipboard as a key/value map of types and data.
Sub-paths can be accessed directly; e.g. ``clipboard/text`` can be read, written,
or observed.
The top-level object itself can be written directly as a shortcut; the intended
type will be guessed.
Converting this property to a string will give a JSON representation of all types.
Unset types will be null.
Writing to one type may cause other types to also be updated.
Currently, the following types are available:
``text``
Arbitrary plain text
``url``
A URL; if ``path`` is populated, this will be a corresponding file:// URL
``paths``
A list of absolute paths to files on disk.
``path``
A single absolute path to a file on disk.
Exposed for convenience and not included in the top-level map.
``user-data`` (RW)
This is a recursive key/value map of arbitrary nodes shared between clients for
general use (i.e. scripts, IPC clients, host applications, etc).

View File

@ -10,6 +10,12 @@
To configure this script use file autoload.conf in directory script-opts (the "script-opts"
directory must be in the mpv configuration directory, typically ~/.config/mpv/).
Option `ignore_patterns` is a comma-separated list of patterns (see lua.org/pil/20.2.html).
Additionally to the standard lua patterns, you can also escape commas with `%`,
for example, the option `bak%,x%,,another` will be resolved as patterns `bak,x,` and `another`.
But it does not mean you need to escape all lua patterns twice,
so the option `bak%%,%.mp4,` will be resolved as two patterns `bak%%` and `%.mp4`.
Example configuration would be:
disabled=no
@ -22,6 +28,7 @@ additional_audio_exts=list,of,ext
ignore_hidden=yes
same_type=yes
directory_mode=recursive
ignore_patterns=^~,^bak-,%.bak$
--]]
@ -42,7 +49,8 @@ o = {
additional_audio_exts = "",
ignore_hidden = true,
same_type = false,
directory_mode = "auto"
directory_mode = "auto",
ignore_patterns = ""
}
options.read_options(o, nil, function(list)
split_option_exts(list.additional_video_exts, list.additional_audio_exts, list.additional_image_exts)
@ -67,9 +75,45 @@ function SetUnion (a,b)
return a
end
function Split (s)
-- Returns first and last positions in string or past-to-end indices
function FindOrPastTheEnd (string, pattern, start_at)
local pos1, pos2 = string.find(string, pattern, start_at)
return pos1 or #string + 1,
pos2 or #string + 1
end
function Split (list)
local set = {}
for v in string.gmatch(s, '([^,]+)') do set[v] = true end
local item_pos = 1
local item = ""
while item_pos <= #list do
local pos1, pos2 = FindOrPastTheEnd(list, "%%*,", item_pos)
local pattern_length = pos2 - pos1
local is_comma_escaped = pattern_length % 2
local pos_before_escape = pos1 - 1
local item_escape_count = pattern_length - is_comma_escaped
item = item .. string.sub(list, item_pos, pos_before_escape + item_escape_count)
if is_comma_escaped == 1 then
item = item .. ","
else
set[item] = true
item = ""
end
item_pos = pos2 + 1
end
set[item] = true
-- exclude empty items
set[""] = nil
return set
end
@ -95,6 +139,11 @@ function split_option_exts(video, audio, image)
end
split_option_exts(true, true, true)
function split_patterns()
o.ignore_patterns = Split(o.ignore_patterns)
end
split_patterns()
function create_extensions()
EXTENSIONS = {}
EXTENSIONS_VIDEO = {}
@ -139,6 +188,16 @@ function get_extension(path)
end
end
function is_ignored(file)
for pattern, _ in pairs(o.ignore_patterns) do
if string.match(file, pattern) then
return true
end
end
return false
end
table.filter = function(t, iter)
for i = #t, 1, -1 do
if not iter(t[i]) then
@ -189,9 +248,14 @@ function scan_dir(path, current_file, dir_mode, separator, dir_depth, total_file
table.filter(files, function (v)
-- The current file could be a hidden file, ignoring it doesn't load other
-- files from the current directory.
if (o.ignore_hidden and not (prefix .. v == current_file) and string.match(v, "^%.")) then
local current = prefix .. v == current_file
if o.ignore_hidden and not current and string.match(v, "^%.") then
return false
end
if not current and is_ignored(v) then
return false
end
local ext = get_extension(v)
if ext == nil then
return false

View File

@ -101,7 +101,7 @@ function _mpv_generate_arguments {
entry+='->files'
elif [[ $name == (ao|vo|af|vf|profile|audio-device|vulkan-device) ]]; then
elif [[ $desc = 'Object settings list'* || $name == (profile|audio-device|vulkan-device) ]]; then
entry+="->parse-help-$name"

42
fuzzers/common.h Normal file
View File

@ -0,0 +1,42 @@
/*
* This file is part of mpv.
*
* mpv is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* mpv 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MPV_STRINGIFY_(X) #X
#define MPV_STRINGIFY(X) MPV_STRINGIFY_(X)
static inline void check_error(int status)
{
if (status < 0) {
fprintf(stderr, "mpv API error: %s\n", mpv_error_string(status));
exit(1);
}
}
static inline bool str_startswith(const char *str, size_t str_len,
const char *prefix, size_t prefix_len)
{
if (str_len < prefix_len)
return false;
return !memcmp(str, prefix, prefix_len);
}

71
fuzzers/fuzzer_loadfile.c Normal file
View File

@ -0,0 +1,71 @@
/*
* This file is part of mpv.
*
* mpv is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* mpv 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <libmpv/client.h>
#include "common.h"
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
if (size == 0)
return -1;
char filename[15 + 10 + 1];
sprintf(filename, "/tmp/libfuzzer.%d", getpid());
FILE *fp = fopen(filename, "wb");
if (!fp)
exit(1);
if (fwrite(data, size, 1, fp) != 1)
exit(1);
if (fclose(fp))
exit(1);
mpv_handle *ctx = mpv_create();
if (!ctx)
exit(1);
check_error(mpv_set_option_string(ctx, "vo", "null"));
check_error(mpv_set_option_string(ctx, "ao", "null"));
check_error(mpv_set_option_string(ctx, "ao-null-untimed", "yes"));
check_error(mpv_set_option_string(ctx, "untimed", "yes"));
check_error(mpv_set_option_string(ctx, "video-osd", "no"));
check_error(mpv_set_option_string(ctx, "msg-level", "all=trace"));
check_error(mpv_initialize(ctx));
const char *cmd[] = {"loadfile", filename, NULL};
check_error(mpv_command(ctx, cmd));
while (1) {
mpv_event *event = mpv_wait_event(ctx, 10000);
if (event->event_id == MPV_EVENT_IDLE)
break;
}
mpv_terminate_destroy(ctx);
return 0;
}

View File

@ -0,0 +1,77 @@
/*
* This file is part of mpv.
*
* mpv is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* mpv 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <libmpv/client.h>
#include "common.h"
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
if (size <= 1 || data[size - 1] != '\0')
return -1;
// Exlude data with null bytes inside
if (strlen(data) != size - 1)
return -1;
#ifdef MPV_PROTO
if (!str_startswith(data, size - 1, MPV_STRINGIFY(MPV_PROTO) "://", strlen(MPV_STRINGIFY(MPV_PROTO) "://")))
return -1;
#else
// Exclude some common paths that are not useful for testing.
// Exclude -
if (size == 2 && !strncmp(data, "-", 1))
return -1;
// Exclude relative paths
if (str_startswith(data, size - 1, ".", 1))
return -1;
// Exclude absolute paths
if (str_startswith(data, size - 1, "/", 1))
return -1;
#endif
mpv_handle *ctx = mpv_create();
if (!ctx)
exit(1);
check_error(mpv_set_option_string(ctx, "vo", "null"));
check_error(mpv_set_option_string(ctx, "ao", "null"));
check_error(mpv_set_option_string(ctx, "ao-null-untimed", "yes"));
check_error(mpv_set_option_string(ctx, "untimed", "yes"));
check_error(mpv_set_option_string(ctx, "video-osd", "no"));
check_error(mpv_set_option_string(ctx, "msg-level", "all=trace"));
check_error(mpv_initialize(ctx));
const char *cmd[] = {"loadfile", data, NULL};
check_error(mpv_command(ctx, cmd));
while (1) {
mpv_event *event = mpv_wait_event(ctx, 10000);
if (event->event_id == MPV_EVENT_IDLE)
break;
}
mpv_terminate_destroy(ctx);
return 0;
}

View File

@ -0,0 +1,89 @@
/*
* This file is part of mpv.
*
* mpv is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* mpv 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#include <libmpv/client.h>
#include "common.h"
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
size_t value_len;
switch (MPV_FORMAT)
{
case MPV_FORMAT_STRING:
value_len = strnlen(data, size);
if (!value_len || value_len == size)
return -1;
value_len += 1;
break;
case MPV_FORMAT_FLAG:
value_len = sizeof(int);
break;
case MPV_FORMAT_INT64:
value_len = sizeof(int64_t);
break;
case MPV_FORMAT_DOUBLE:
value_len = sizeof(double);
break;
default:
exit(1);
break;
}
// at least two bytes for the name
if (size < value_len + 2)
return -1;
const char *name = (const char *)data + value_len;
size_t name_len = strnlen(name, size - value_len);
if (!name_len || name_len != size - value_len - 1)
return -1;
mpv_handle *ctx = mpv_create();
if (!ctx)
exit(1);
#if MPV_RUN
check_error(mpv_set_option_string(ctx, "vo", "null"));
check_error(mpv_set_option_string(ctx, "ao", "null"));
check_error(mpv_set_option_string(ctx, "ao-null-untimed", "yes"));
check_error(mpv_set_option_string(ctx, "untimed", "yes"));
check_error(mpv_set_option_string(ctx, "video-osd", "no"));
check_error(mpv_set_option_string(ctx, "msg-level", "all=trace"));
check_error(mpv_initialize(ctx));
#endif
const void *value = data;
mpv_set_property(ctx, name, MPV_FORMAT, &value);
#if MPV_RUN
check_error(mpv_set_option_string(ctx, "audio-files", "av://lavfi:sine=d=0.1"));
const char *cmd[] = {"loadfile", "av://lavfi:yuvtestsrc=d=0.1", NULL};
check_error(mpv_command(ctx, cmd));
while (1) {
mpv_event *event = mpv_wait_event(ctx, 10000);
if (event->event_id == MPV_EVENT_IDLE)
break;
}
#endif
mpv_terminate_destroy(ctx);
return 0;
}

26
fuzzers/meson.build Normal file
View File

@ -0,0 +1,26 @@
incdir = include_directories('../')
executable('fuzzer_loadfile', 'fuzzer_loadfile.c',
include_directories: incdir, link_with: libmpv)
executable('fuzzer_loadfile_direct', 'fuzzer_loadfile_direct.c',
include_directories: incdir, link_with: libmpv)
foreach p : ['bd', 'cdda', 'dvb', 'dvd', 'edl', 'file', 'hex', 'lavf', 'memory',
'mf', 'slice', 'smb']
executable('fuzzer_protocol_' + p,
'fuzzer_loadfile_direct.c',
c_args: ['-DMPV_PROTO=' + p],
include_directories: incdir,
link_with: libmpv)
endforeach
foreach f : ['MPV_FORMAT_STRING', 'MPV_FORMAT_FLAG', 'MPV_FORMAT_INT64', 'MPV_FORMAT_DOUBLE']
foreach i : ['0', '1']
executable('fuzzer_set_property_' + f + '_' + i,
'fuzzer_set_property.c',
c_args: ['-DMPV_FORMAT=' + f, '-DMPV_RUN=' + i],
include_directories: incdir,
link_with: libmpv)
endforeach
endforeach

View File

@ -379,6 +379,15 @@ pthread_debug = get_option('pthread-debug').require(
)
features += {'pthread-debug': pthread_debug.allowed()}
if get_option('fuzzers')
if get_option('cplayer') or not get_option('libmpv')
error('fuzzers require !cplayer and libmpv')
endif
# Adding flags manually until https://github.com/mesonbuild/meson/pull/9825
flags += ['-fsanitize=address,undefined,fuzzer', '-fno-omit-frame-pointer']
link_flags += ['-fsanitize=address,undefined,fuzzer', '-fno-omit-frame-pointer']
endif
add_project_arguments(flags, language: 'c')
add_project_arguments(['-Wno-unused-parameter'], language: 'objc')
add_project_link_arguments(link_flags, language: ['c', 'objc'])
@ -390,7 +399,8 @@ cocoa = dependency('appleframeworks', modules: ['Cocoa', 'IOKit', 'QuartzCore'],
features += {'cocoa': cocoa.found()}
if features['cocoa']
dependencies += cocoa
sources += files('osdep/language-mac.c',
sources += files('osdep/clipboard-mac.m',
'osdep/language-mac.c',
'osdep/path-mac.m',
'osdep/utils-mac.c',
'osdep/mac/app_bridge.m')
@ -514,6 +524,10 @@ if not posix and not features['win32-desktop']
'osdep/terminal-dummy.c')
endif
if not features['cocoa']
sources += files('osdep/clipboard-dummy.c')
endif
features += {'glob-posix': cc.has_function('glob', prefix: '#include <glob.h>')}
features += {'glob-win32': win32 and not features['glob-posix']}
@ -1803,6 +1817,10 @@ if get_option('tests')
subdir('test')
endif
if get_option('fuzzers')
subdir('fuzzers')
endif
summary({'d3d11': features['d3d11'],
'javascript': features['javascript'],
'libmpv': get_option('libmpv'),

View File

@ -4,6 +4,7 @@ option('cplayer', type: 'boolean', value: true, description: 'mpv CLI player')
option('libmpv', type: 'boolean', value: false, description: 'libmpv library')
option('build-date', type: 'boolean', value: true, description: 'include compile timestamp in binary')
option('tests', type: 'boolean', value: false, description: 'meson unit tests')
option('fuzzers', type: 'boolean', value: false, description: 'fuzzer binaries')
# Reminder: normally always built, but enabled by MPV_LEAK_REPORT.
# Building it can be disabled only by defining NDEBUG through CFLAGS.
option('ta-leak-report', type: 'boolean', value: false, description: 'enable ta leak report by default (development only)')

35
osdep/clipboard-dummy.c Normal file
View File

@ -0,0 +1,35 @@
/*
* Dummy implementations of clipboard access routines
*
* This file is part of mpv.
*
* mpv is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* mpv 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#include "clipboard.h"
int m_clipboard_set(struct MPContext *ctx, const struct m_clipboard_item* item)
{
return CLIPBOARD_FAILED;
}
int m_clipboard_get(struct MPContext *ctx, struct m_clipboard_item* item)
{
return CLIPBOARD_FAILED;
}
bool m_clipboard_poll(struct MPContext *ctx)
{
return false;
}

477
osdep/clipboard-mac.m Normal file
View File

@ -0,0 +1,477 @@
/*
* Clipboard access for macOS
*
* This file is part of mpv.
*
* mpv is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* mpv 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#import <AppKit/AppKit.h>
#import <CoreGraphics/CoreGraphics.h>
#include <libavutil/buffer.h>
#include <libswscale/swscale.h>
#include <stdint.h>
#include "clipboard.h"
#include "video/sws_utils.h"
static void release_ns_obj(void* ptr)
{
[(id)ptr release];
}
static void free_ns_obj(void *opaque, uint8_t *data)
{
[(id)opaque release];
}
NSPasteboardItem *make_file_item(const char* path)
{
NSString *str = [NSString stringWithUTF8String:path];
NSURL *url = [NSURL fileURLWithPath:str];
NSPasteboardItem *pbi = [[NSPasteboardItem alloc] init];
[pbi setString:[url lastPathComponent] forType:@"public.utf8-plain-text"];
[pbi setData:[[url absoluteString] dataUsingEncoding:NSUTF8StringEncoding] forType:NSPasteboardTypeFileURL];
return pbi;
}
NSArray *clipboard_item_to_ns(const struct m_clipboard_item *item)
{
switch (item->type) {
case CLIPBOARD_TEXT:
return @[[NSString stringWithUTF8String:item->string]];
case CLIPBOARD_URL: {
NSString *str = [NSString stringWithUTF8String:item->string];
NSURL *url = [NSURL URLWithString:str];
return @[url, str];
}
case CLIPBOARD_PATH: {
return @[make_file_item(item->string)];
}
case CLIPBOARD_PATHS: {
NSMutableArray *ret = [NSMutableArray array];
for (size_t i = 0; item->string_list[i]; i++) {
[ret addObject:make_file_item(item->string_list[i])];
}
return ret;
}
case CLIPBOARD_IMAGE: {
struct mp_image *image = item->image;
enum mp_imgfmt imgfmt = image->imgfmt;
bool compatible = false;
switch (imgfmt) {
case IMGFMT_YAP8:
case IMGFMT_YAP16:
case IMGFMT_Y8:
case IMGFMT_Y16:
case IMGFMT_ARGB:
case IMGFMT_RGBA:
case IMGFMT_RGB0:
case IMGFMT_RGBA64:
compatible = true;
default:
break;
}
if (image->params.repr.levels != PL_COLOR_LEVELS_FULL)
compatible = false;
static_assert(MP_MAX_PLANES <= 5, "Too many MP_MAX_PLANES");
unsigned char *planes[5] = {NULL};
NSInteger bps = image->fmt.comps[0].size;
NSInteger spp = mp_imgfmt_desc_get_num_comps(&image->fmt);
bool alpha = (image->fmt.flags & MP_IMGFLAG_ALPHA);
bool gray = (image->fmt.flags & MP_IMGFLAG_GRAY);
bool alphaFirst = alpha && (image->fmt.comps[3].plane == 0 &&
image->fmt.comps[3].offset == 0);
NSColorSpaceName csp = gray ? NSCalibratedWhiteColorSpace : NSCalibratedRGBColorSpace;
NSBitmapFormat formatFlags = (alphaFirst ? NSBitmapFormatAlphaFirst : 0) |
((image->params.repr.alpha == PL_ALPHA_INDEPENDENT) ? NSBitmapFormatAlphaNonpremultiplied : 0);
NSInteger bpp = image->fmt.bpp[0];
NSInteger bytesPerRow = image->stride[0];
bool planar = image->num_planes > 1;
for (int i = 0; i < image->num_planes; i++) {
if (image->stride[0] != bytesPerRow) {
compatible = false;
break;
}
}
if (compatible) {
for (int i = 0; i < image->num_planes; i++) {
planes[i] = image->planes[i];
}
if (bpp == 24)
bpp = 32;
} else {
bps = bps <= 8 ? 8 : 16;
formatFlags &= ~NSBitmapFormatAlphaFirst;
if (gray) {
if (bps > 8) {
imgfmt = alpha ? IMGFMT_YAP16 : IMGFMT_Y16;
bpp = 16;
} else {
imgfmt = alpha ? IMGFMT_YAP8 : IMGFMT_Y8;
bpp = 8;
}
} else {
if (bps > 8) {
imgfmt = IMGFMT_RGBA64;
bpp = 64;
} else {
imgfmt = alpha ? IMGFMT_RGBA : IMGFMT_RGB0;
bpp = 32;
}
}
bytesPerRow = 0;
planar = (gray && alpha);
spp = (gray ? 1 : 3) + (alpha ? 1 : 0);
}
NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:planes
pixelsWide:image->w
pixelsHigh:image->h
bitsPerSample:bps
samplesPerPixel:spp
hasAlpha:alpha
isPlanar:planar
colorSpaceName:csp
bitmapFormat:formatFlags
bytesPerRow:bytesPerRow
bitsPerPixel:bpp];
if (!rep)
return nil;
CFStringRef cgSpaceName = NULL;
struct pl_color_space plcsp = image->params.color;
if (gray) {
plcsp.primaries = PL_COLOR_PRIM_BT_709;
if (image->params.color.transfer == PL_COLOR_TRC_LINEAR) {
cgSpaceName = kCGColorSpaceLinearGray;
} else if (image->params.color.transfer == PL_COLOR_TRC_GAMMA22) {
cgSpaceName = kCGColorSpaceGenericGrayGamma2_2;
} else {
plcsp.transfer = PL_COLOR_TRC_GAMMA22;
compatible = false;
}
} else {
switch (image->params.color.primaries) {
case PL_COLOR_PRIM_DISPLAY_P3:
if (image->params.color.transfer == PL_COLOR_TRC_BT_1886) {
cgSpaceName = kCGColorSpaceDisplayP3;
} else if (image->params.color.transfer == PL_COLOR_TRC_HLG) {
cgSpaceName = kCGColorSpaceDisplayP3_HLG;
}
break;
case PL_COLOR_PRIM_BT_709:
if (image->params.color.transfer == PL_COLOR_TRC_LINEAR) {
cgSpaceName = kCGColorSpaceLinearSRGB;
} else if (image->params.color.transfer == PL_COLOR_TRC_BT_1886) {
cgSpaceName = kCGColorSpaceITUR_709;
} else if (image->params.color.transfer == PL_COLOR_TRC_SRGB) {
cgSpaceName = kCGColorSpaceSRGB;
}
break;
case PL_COLOR_PRIM_DCI_P3:
if (image->params.color.transfer == PL_COLOR_TRC_BT_1886) {
cgSpaceName = kCGColorSpaceDCIP3;
}
break;
case PL_COLOR_PRIM_BT_2020:
if (image->params.color.transfer == PL_COLOR_TRC_BT_1886) {
cgSpaceName = kCGColorSpaceITUR_2020;
}
break;
case PL_COLOR_PRIM_ADOBE:
cgSpaceName = kCGColorSpaceAdobeRGB1998;
break;
case PL_COLOR_PRIM_APPLE:
if (image->params.color.transfer == PL_COLOR_TRC_LINEAR) {
cgSpaceName = kCGColorSpaceGenericRGBLinear;
}
break;
default:
break;
}
if (!cgSpaceName) {
compatible = false;
cgSpaceName = kCGColorSpaceSRGB;
plcsp.primaries = PL_COLOR_PRIM_BT_709;
plcsp.transfer = PL_COLOR_TRC_SRGB;
}
}
NSColorSpace *nscsp = nil;
if (!gray && image->icc_profile) {
nscsp = [[NSColorSpace alloc] initWithICCProfileData:[NSData dataWithBytes:image->icc_profile->data length:image->icc_profile->size]];
} else if (cgSpaceName) {
CGColorSpaceRef cgspace = CGColorSpaceCreateWithName(cgSpaceName);
nscsp = [[NSColorSpace alloc] initWithCGColorSpace:cgspace];
CFRelease(cgspace);
}
if (nscsp) {
rep = [rep bitmapImageRepByRetaggingWithColorSpace:nscsp];
}
if (!compatible) {
struct mp_image dest = {0};
mp_image_setfmt(&dest, imgfmt);
mp_image_set_size(&dest, image->w, image->h);
[rep getBitmapDataPlanes:planes];
for (int i = 0; i < MP_MAX_PLANES; i++) {
dest.planes[i] = planes[i];
dest.stride[i] = rep.bytesPerRow;
}
dest.params.repr = (struct pl_color_repr){
.sys = PL_COLOR_SYSTEM_RGB,
.levels = PL_COLOR_LEVELS_FULL,
.alpha = rep.alpha ? ((rep.bitmapFormat & NSBitmapFormatAlphaNonpremultiplied) ? PL_ALPHA_INDEPENDENT : PL_ALPHA_PREMULTIPLIED) : PL_ALPHA_UNKNOWN,
.bits = (struct pl_bit_encoding){
.sample_depth = bps,
.color_depth = bps,
.bit_shift = 0,
},
};
dest.params.color = plcsp;
if (mp_image_swscale(&dest, image, SWS_FULL_CHR_H_INT | SWS_FULL_CHR_H_INP | SWS_ACCURATE_RND) < 0)
return nil;
}
return @[[[NSImage alloc] initWithCGImage:[rep CGImage] size:NSZeroSize]];
}
default:
return nil;
}
}
int m_clipboard_set(struct MPContext *ctx, const struct m_clipboard_item *item)
{
@autoreleasepool {
id arr = clipboard_item_to_ns(item);
if (!arr)
return CLIPBOARD_FAILED;
[[NSPasteboard generalPasteboard] clearContents];
bool success = [[NSPasteboard generalPasteboard] writeObjects:arr];
return success ? CLIPBOARD_OK : CLIPBOARD_FAILED;
}
}
static enum mp_imgfmt lookup_imgfmt(NSBitmapImageRep *rep)
{
if (rep.samplesPerPixel > MP_NUM_COMPONENTS || rep.numberOfPlanes > MP_MAX_PLANES ||
(rep.bitmapFormat & (NSBitmapFormatSixteenBitBigEndian | NSBitmapFormatThirtyTwoBitBigEndian)))
return IMGFMT_NONE;
NSInteger sample_bits = rep.bitsPerPixel / (rep.planar ? 1 : rep.samplesPerPixel);
struct mp_regular_imgfmt reg = {
.component_type = (rep.bitmapFormat & NSBitmapFormatFloatingPointSamples) ? MP_COMPONENT_TYPE_FLOAT : MP_COMPONENT_TYPE_UINT,
.forced_csp = PL_COLOR_SYSTEM_UNKNOWN,
.component_size = (sample_bits + 7) / 8,
.component_pad = sample_bits % 8,
.num_planes = rep.numberOfPlanes,
};
NSInteger alphaChannel = rep.alpha ? ((rep.bitmapFormat & NSBitmapFormatAlphaFirst) ? 0 : rep.samplesPerPixel - 1) : -1;
int alphaShift = alphaChannel == 0;
for (NSInteger i = 0; i < rep.samplesPerPixel; i++) {
if (rep.planar) {
reg.planes[i].num_components = 1;
} else {
reg.planes[0].num_components = rep.samplesPerPixel;
}
reg.planes[rep.planar ? 0 : i].components[rep.planar ? i : 0] = (i == alphaChannel) ? 0 : (i - alphaShift);
}
return mp_find_regular_imgfmt(&reg);
}
int m_clipboard_get(struct MPContext *ctx, struct m_clipboard_item *item)
{
@autoreleasepool {
switch (item->type) {
case CLIPBOARD_IMAGE: {
NSImage *image;
@try {
image = [[NSImage alloc] initWithPasteboard:[NSPasteboard generalPasteboard]];
} @catch (id ex) {
}
if (!image)
return CLIPBOARD_NONE;
assert(!item->image);
CGImageRef cgi = [image CGImageForProposedRect:nil context:nil hints:nil];
NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithCGImage:cgi];
enum mp_imgfmt fmt = lookup_imgfmt(rep);
if (!fmt)
return CLIPBOARD_NONE;
item->image = mp_image_new_custom_ref(NULL, image, &release_ns_obj);
mp_image_set_size(item->image, rep.pixelsWide, rep.pixelsHigh);
mp_image_setfmt(item->image, fmt);
item->image->num_planes = rep.numberOfPlanes;
unsigned char* planes[5];
[rep getBitmapDataPlanes:planes];
static_assert(MP_MAX_PLANES <= 5, "Too many MP_MAX_PLANES");
for (int i = 0; i < MP_MAX_PLANES; i++) {
item->image->planes[i] = planes[i];
item->image->stride[i] = rep.bytesPerRow;
}
item->image->params.repr = (struct pl_color_repr){
.sys = PL_COLOR_SYSTEM_RGB,
.levels = PL_COLOR_LEVELS_FULL,
.alpha = rep.alpha ? ((rep.bitmapFormat & NSBitmapFormatAlphaNonpremultiplied) ? PL_ALPHA_INDEPENDENT : PL_ALPHA_PREMULTIPLIED) : PL_ALPHA_UNKNOWN,
.bits = (struct pl_bit_encoding){
.sample_depth = rep.bitsPerPixel / (rep.planar ? 1 : rep.samplesPerPixel),
.color_depth = rep.bitsPerPixel / (rep.planar ? 1 : rep.samplesPerPixel),
.bit_shift = 0,
},
};
// Default color to a reasonable guess
item->image->params.color = (struct pl_color_space){
.primaries = PL_COLOR_PRIM_BT_709,
.transfer = PL_COLOR_TRC_SRGB,
};
CGColorSpaceRef cgspace = rep.colorSpace.CGColorSpace;
if (cgspace) {
CFStringRef name = CGColorSpaceCopyName(cgspace);
if (CFEqual(name, kCGColorSpaceDisplayP3)) {
item->image->params.color.primaries = PL_COLOR_PRIM_DISPLAY_P3;
item->image->params.color.transfer = PL_COLOR_TRC_BT_1886;
} else if (CFEqual(name, kCGColorSpaceDisplayP3_HLG)) {
item->image->params.color.primaries = PL_COLOR_PRIM_DISPLAY_P3;
item->image->params.color.transfer = PL_COLOR_TRC_HLG;
} else if (CFEqual(name, kCGColorSpaceExtendedLinearDisplayP3)) {
item->image->params.color.primaries = PL_COLOR_PRIM_DISPLAY_P3;
item->image->params.color.transfer = PL_COLOR_TRC_LINEAR;
} else if (CFEqual(name, kCGColorSpaceLinearSRGB) ||
CFEqual(name, kCGColorSpaceExtendedLinearSRGB)) {
item->image->params.color.transfer = PL_COLOR_TRC_LINEAR;
} else if (CFEqual(name, kCGColorSpaceGenericGrayGamma2_2) ||
CFEqual(name, kCGColorSpaceExtendedGray)) {
item->image->params.color.transfer = PL_COLOR_TRC_GAMMA22;
} else if (CFEqual(name, kCGColorSpaceLinearGray) ||
CFEqual(name, kCGColorSpaceExtendedLinearGray)) {
item->image->params.color.transfer = PL_COLOR_TRC_LINEAR;
} else if (CFEqual(name, kCGColorSpaceGenericRGBLinear)) {
item->image->params.color.primaries = PL_COLOR_PRIM_APPLE;
item->image->params.color.transfer = PL_COLOR_TRC_LINEAR;
} else if (CFEqual(name, kCGColorSpaceAdobeRGB1998)) {
item->image->params.color.primaries = PL_COLOR_PRIM_ADOBE;
} else if (CFEqual(name, kCGColorSpaceDCIP3)) {
item->image->params.color.primaries = PL_COLOR_PRIM_DCI_P3;
item->image->params.color.transfer = PL_COLOR_TRC_BT_1886;
} else if (CFEqual(name, kCGColorSpaceITUR_709)) {
item->image->params.color.transfer = PL_COLOR_TRC_BT_1886;
} else if (CFEqual(name, kCGColorSpaceITUR_2020) ||
CFEqual(name, kCGColorSpaceExtendedLinearITUR_2020)) {
item->image->params.color.primaries = PL_COLOR_PRIM_BT_2020;
item->image->params.color.transfer = PL_COLOR_TRC_BT_1886;
}
}
NSData *icc = rep.colorSpace.ICCProfileData;
if (icc) {
item->image->icc_profile = av_buffer_create((void*)icc.bytes, icc.length, free_ns_obj, [icc retain], AV_BUFFER_FLAG_READONLY);
}
return CLIPBOARD_OK;
}
case CLIPBOARD_TEXT: {
NSString* contents = [[NSPasteboard generalPasteboard] stringForType:@"public.url-name"];
if (!contents)
contents = [[NSPasteboard generalPasteboard] stringForType:NSPasteboardTypeString];
if (!contents)
return CLIPBOARD_NONE;
item->string = ta_strdup(NULL, [contents UTF8String]);
return CLIPBOARD_OK;
}
case CLIPBOARD_PATH:
case CLIPBOARD_URL: {
bool path = item->type == CLIPBOARD_PATH;
NSURL *url = [NSURL URLFromPasteboard:[NSPasteboard generalPasteboard]];
if (!url)
return CLIPBOARD_NONE;
if (path && !url.fileURL)
return CLIPBOARD_NONE;
NSString *str = path ? url.path : url.absoluteString;
item->string = ta_strdup(NULL, [str UTF8String]);
return CLIPBOARD_OK;
}
case CLIPBOARD_PATHS: {
NSArray *urls = [[NSPasteboard generalPasteboard] readObjectsForClasses:@[[NSURL class]] options:@{NSPasteboardURLReadingFileURLsOnlyKey: @(YES)}];
if (!urls || !urls.count)
return CLIPBOARD_NONE;
size_t count = urls.count;
item->string_list = talloc_array(NULL, char*, count + 1);
for (NSUInteger i = 0; i < count; i++) {
item->string_list[i] = ta_strdup(item->string_list, [[[urls objectAtIndex:i] path] UTF8String]);
}
return CLIPBOARD_OK;
}
default:
return CLIPBOARD_FAILED;
}
}
}
bool m_clipboard_poll(struct MPContext *ctx)
{
long current = [NSPasteboard generalPasteboard].changeCount;
bool ret = current != ctx->clipboard->changeCount;
ctx->clipboard->changeCount = current;
return ret;
}

77
osdep/clipboard.h Normal file
View File

@ -0,0 +1,77 @@
/*
* Clipboard access routines
*
* This file is part of mpv.
*
* mpv is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* mpv 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#include "player/core.h"
#include "video/mp_image.h"
enum m_clipboard_type {
CLIPBOARD_UNKNOWN,
CLIPBOARD_TEXT,
CLIPBOARD_URL,
CLIPBOARD_PATH,
CLIPBOARD_PATHS,
CLIPBOARD_IMAGE,
};
struct m_clipboard_item {
enum m_clipboard_type type;
union {
char *string;
char **string_list;
struct mp_image *image;
};
};
struct clipboard_state {
#if HAVE_COCOA
long changeCount;
#endif
};
enum m_clipboard_return {
CLIPBOARD_OK = 1,
CLIPBOARD_NONE = 0,
CLIPBOARD_FAILED = -1,
};
int m_clipboard_get(struct MPContext *ctx, struct m_clipboard_item *item);
int m_clipboard_set(struct MPContext *ctx, const struct m_clipboard_item *item);
bool m_clipboard_poll(struct MPContext *ctx);
static inline void m_clipboard_item_free(struct m_clipboard_item *item) {
switch (item->type) {
case CLIPBOARD_TEXT:
case CLIPBOARD_PATH:
case CLIPBOARD_URL:
talloc_free(item->string);
break;
case CLIPBOARD_PATHS:
talloc_free(item->string_list);
break;
case CLIPBOARD_IMAGE:
talloc_free(item->image);
break;
default:
break;
}
}
static inline struct clipboard_state *m_clipboard_new(struct MPContext *ctx) {
return talloc_zero(ctx, struct clipboard_state);
}

View File

@ -72,6 +72,7 @@
#include "misc/thread_pool.h"
#include "misc/thread_tools.h"
#include "osdep/clipboard.h"
#include "osdep/io.h"
#include "osdep/subprocess.h"
#include "osdep/terminal.h"
@ -131,6 +132,10 @@ static const struct m_option mdata_type = {
.type = CONF_TYPE_NODE
};
static const struct m_option clipboard_type = {
.type = CONF_TYPE_NODE
};
struct overlay {
struct mp_image *source;
int x, y;
@ -3907,6 +3912,178 @@ static int mp_property_udata(void *ctx, struct m_property *prop,
return ret;
}
static int do_get_clipboard(struct MPContext *mpctx, union m_option_value *out,
enum m_clipboard_type type)
{
struct m_clipboard_item item = {
.type = type,
};
int ret = m_clipboard_get(mpctx, &item);
if (ret < 0) {
return M_PROPERTY_ERROR;
} else if (ret == CLIPBOARD_NONE) {
if (type == CLIPBOARD_PATHS) {
out->string_list = NULL;
return M_PROPERTY_OK;
} else {
return M_PROPERTY_UNAVAILABLE;
}
}
switch (type) {
case CLIPBOARD_TEXT:
case CLIPBOARD_URL:
case CLIPBOARD_PATH:
out->string = item.string;
return M_PROPERTY_OK;
case CLIPBOARD_PATHS:
out->string_list = item.string_list;
return M_PROPERTY_OK;
default:
m_clipboard_item_free(&item);
return M_PROPERTY_NOT_IMPLEMENTED;
}
}
static int do_set_clipboard(struct MPContext *mpctx, void *arg, enum m_clipboard_type type)
{
struct m_clipboard_item item = {
.type = type,
};
union m_option_value *val = arg;
switch (type) {
case CLIPBOARD_TEXT:
case CLIPBOARD_URL:
case CLIPBOARD_PATH:
item.string = val->string;
break;
case CLIPBOARD_PATHS:
item.string_list = val->string_list;
break;
case CLIPBOARD_UNKNOWN:
default:
return M_PROPERTY_NOT_IMPLEMENTED;
}
return (m_clipboard_set(mpctx, &item) == CLIPBOARD_OK) ? M_PROPERTY_OK : M_PROPERTY_ERROR;
}
static void do_clipboard_node(struct MPContext *mpctx, mpv_node *parent,
enum m_clipboard_type type, const char *key)
{
struct mpv_node *node = node_map_add(parent, key, MPV_FORMAT_NONE);
struct m_clipboard_item item = {
.type = type,
};
int ret = m_clipboard_get(mpctx, &item);
if (ret != CLIPBOARD_OK)
return;
switch (type) {
case CLIPBOARD_TEXT:
case CLIPBOARD_URL:
case CLIPBOARD_PATH:
node->format = MPV_FORMAT_STRING;
node->u.string = item.string;
return;
case CLIPBOARD_PATHS:
node_init(node, MPV_FORMAT_NODE_ARRAY, parent);
for (size_t i = 0; item.string_list[i]; i++) {
struct mpv_node *entry = node_array_add(node, MPV_FORMAT_NONE);
entry->format = MPV_FORMAT_STRING;
entry->u.string = talloc_steal(node->u.list, item.string_list[i]);
}
m_clipboard_item_free(&item);
return;
default:
m_clipboard_item_free(&item);
return;
}
}
static int mp_property_clipboard(void *ctx, struct m_property *prop,
int action, void *arg)
{
struct MPContext *mpctx = ctx;
switch (action) {
case M_PROPERTY_GET_TYPE:
*(struct m_option *)arg = clipboard_type;
return M_PROPERTY_OK;
case M_PROPERTY_GET:
case M_PROPERTY_GET_NODE: {
mpv_node *node = arg;
m_option_free(&clipboard_type, node);
node_init(node, MPV_FORMAT_NODE_MAP, NULL);
do_clipboard_node(mpctx, node, CLIPBOARD_TEXT, "text");
do_clipboard_node(mpctx, node, CLIPBOARD_URL, "url");
do_clipboard_node(mpctx, node, CLIPBOARD_PATHS, "paths");
return M_PROPERTY_OK;
}
case M_PROPERTY_SET:
case M_PROPERTY_SET_NODE: {
mpv_node *node = arg;
switch (node->format) {
case MPV_FORMAT_STRING:
return do_set_clipboard(mpctx, &node->u.string, CLIPBOARD_TEXT);
default:
return M_PROPERTY_INVALID_FORMAT;
}
}
case M_PROPERTY_KEY_ACTION: {
struct m_property_action_arg *act = arg;
const char *key = act->key;
enum m_clipboard_type type = CLIPBOARD_UNKNOWN;
if (!strcmp(key, "text")) {
type = CLIPBOARD_TEXT;
} else if (!strcmp(key, "url")) {
type = CLIPBOARD_URL;
} else if (!strcmp(key, "path")) {
type = CLIPBOARD_PATH;
} else if (!strcmp(key, "paths")) {
type = CLIPBOARD_PATHS;
}
if (type == CLIPBOARD_UNKNOWN)
return M_PROPERTY_UNKNOWN;
switch (act->action) {
case M_PROPERTY_GET_TYPE:
switch (type) {
case CLIPBOARD_TEXT:
case CLIPBOARD_URL:
case CLIPBOARD_PATH:
*(struct m_option *)act->arg = (struct m_option){
.type = CONF_TYPE_STRING,
};
return M_PROPERTY_OK;
case CLIPBOARD_PATHS:
*(struct m_option *)act->arg = (struct m_option){
.type = CONF_TYPE_STRING_LIST,
};
return M_PROPERTY_OK;
default:
return M_PROPERTY_UNKNOWN;
}
case M_PROPERTY_GET:
return do_get_clipboard(mpctx, act->arg, type);
case M_PROPERTY_SET:
return do_set_clipboard(mpctx, act->arg, type);
default:
return M_PROPERTY_NOT_IMPLEMENTED;
}
}
default:
return M_PROPERTY_NOT_IMPLEMENTED;
}
}
// Redirect a property name to another
#define M_PROPERTY_ALIAS(name, real_property) \
{(name), mp_property_alias, .priv = (real_property)}
@ -4062,8 +4239,7 @@ static const struct m_property mp_properties_base[] = {
.priv = (void *)&(const int[]){0, SD_TEXT_TYPE_PLAIN}},
{"secondary-sub-text", mp_property_sub_text,
.priv = (void *)&(const int[]){1, SD_TEXT_TYPE_PLAIN}},
{"sub-text-ass", mp_property_sub_text,
.priv = (void *)&(const int[]){0, SD_TEXT_TYPE_ASS}},
M_PROPERTY_DEPRECATED_ALIAS("sub-text-ass", "sub-text/ass"),
{"sub-start", mp_property_sub_start,
.priv = (void *)&(const int){0}},
{"secondary-sub-start", mp_property_sub_start,
@ -4123,6 +4299,8 @@ static const struct m_property mp_properties_base[] = {
{"user-data", mp_property_udata},
{"term-size", mp_property_term_size},
{"clipboard", mp_property_clipboard},
M_PROPERTY_ALIAS("video", "vid"),
M_PROPERTY_ALIAS("audio", "aid"),
M_PROPERTY_ALIAS("sub", "sid"),
@ -6826,6 +7004,16 @@ const struct mp_cmd_def mp_cmds[] = {
},
.spawn_thread = true,
},
{ "screenshot-to-clipboard", cmd_screenshot_to_clipboard,
{
{"flags", OPT_CHOICE(v.i,
{"video", 0},
{"window", 1},
{"subtitles", 2}),
OPTDEF_INT(2)},
},
.spawn_thread = true,
},
{ "screenshot-raw", cmd_screenshot_raw,
{
{"flags", OPT_CHOICE(v.i,

View File

@ -460,6 +460,8 @@ typedef struct MPContext {
// to true.
struct demuxer *open_res_demuxer;
int open_res_error;
struct clipboard_state *clipboard;
} MPContext;
// Contains information about an asynchronous work item, how it can be aborted,

View File

@ -31,6 +31,7 @@
#include "misc/dispatch.h"
#include "misc/thread_pool.h"
#include "osdep/clipboard.h"
#include "osdep/io.h"
#include "osdep/terminal.h"
#include "osdep/threads.h"
@ -267,6 +268,7 @@ struct MPContext *mp_create(void)
.thread_pool = mp_thread_pool_create(mpctx, 0, 1, 30),
.stop_play = PT_NEXT_ENTRY,
.play_dir = 1,
.clipboard = m_clipboard_new(mpctx),
};
mp_mutex_init(&mpctx->abort_lock);

View File

@ -41,6 +41,7 @@
#include "options/m_config_frontend.h"
#include "options/m_property.h"
#include "options/options.h"
#include "osdep/clipboard.h"
#include "osdep/terminal.h"
#include "osdep/timer.h"
#include "stream/stream.h"
@ -1082,6 +1083,13 @@ err:
return -1;
}
static void handle_clipboard_changes(struct MPContext *mpctx)
{
bool changed = m_clipboard_poll(mpctx);
if (changed)
mp_notify_property(mpctx, "clipboard");
}
// Potentially needed by some Lua scripts, which assume TICK always comes.
static void handle_dummy_ticks(struct MPContext *mpctx)
{
@ -1248,6 +1256,8 @@ void run_playloop(struct MPContext *mpctx)
handle_dummy_ticks(mpctx);
handle_clipboard_changes(mpctx);
update_osd_msg(mpctx);
handle_update_subtitles(mpctx);
@ -1287,6 +1297,7 @@ void run_playloop(struct MPContext *mpctx)
void mp_idle(struct MPContext *mpctx)
{
handle_clipboard_changes(mpctx);
handle_dummy_ticks(mpctx);
mp_wait_events(mpctx);
mp_process_input(mpctx);

View File

@ -33,6 +33,7 @@
#include "misc/dispatch.h"
#include "misc/node.h"
#include "misc/thread_tools.h"
#include "osdep/clipboard.h"
#include "common/msg.h"
#include "options/path.h"
#include "video/mp_image.h"
@ -501,6 +502,29 @@ void cmd_screenshot_to_file(void *p)
talloc_free(image);
}
void cmd_screenshot_to_clipboard(void *p)
{
struct mp_cmd_ctx *cmd = p;
struct MPContext *mpctx = cmd->mpctx;
int mode = cmd->args[0].v.i;
struct image_writer_opts opts = *mpctx->opts->screenshot_image_opts;
bool high_depth = image_writer_high_depth(&opts);
struct mp_image *image = screenshot_get(mpctx, mode, high_depth);
if (!image) {
mp_cmd_msg(cmd, MSGL_ERR, "Taking screenshot failed.");
cmd->success = false;
return;
}
struct m_clipboard_item item = {
.type = CLIPBOARD_IMAGE,
.image = image,
};
cmd->success = m_clipboard_set(mpctx, &item) == CLIPBOARD_OK;
talloc_free(image);
}
void cmd_screenshot(void *p)
{
struct mp_cmd_ctx *cmd = p;

View File

@ -41,6 +41,7 @@ struct mp_image *convert_image(struct mp_image *image, int destfmt,
// Handlers for the user-facing commands.
void cmd_screenshot(void *p);
void cmd_screenshot_to_file(void *p);
void cmd_screenshot_to_clipboard(void *p);
void cmd_screenshot_raw(void *p);
#endif /* MPLAYER_SCREENSHOT_H */

View File

@ -2182,11 +2182,17 @@ static int gui_thread_control(struct vo_w32_state *w32, int request, void *arg)
} else if (changed_option == &vo_opts->cursor_passthrough) {
update_cursor_passthrough(w32);
} else if (changed_option == &vo_opts->border ||
changed_option == &vo_opts->title_bar ||
changed_option == &vo_opts->show_in_taskbar)
changed_option == &vo_opts->title_bar)
{
update_window_style(w32);
update_window_state(w32);
} else if (changed_option == &vo_opts->show_in_taskbar) {
// This hide and show is apparently required according to the documentation:
// https://learn.microsoft.com/en-us/windows/win32/shell/taskbar#managing-taskbar-buttons
ShowWindow(w32->window, SW_HIDE);
update_window_style(w32);
ShowWindow(w32->window, SW_SHOW);
update_window_state(w32);
} else if (changed_option == &vo_opts->window_minimized) {
update_minimized_state(w32);
} else if (changed_option == &vo_opts->window_maximized) {