mirror of https://github.com/mpv-player/mpv
Compare commits
12 Commits
9f5bfa1544
...
5e1da6ccab
Author | SHA1 | Date |
---|---|---|
rcombs | 5e1da6ccab | |
Kacper Michajłow | 69d70148c7 | |
oficsu | 3ca71b0c0e | |
oficsu | 96b34148f1 | |
nanahi | 93708a9d38 | |
Guido Cella | 2f4c550b4b | |
Kacper Michajłow | fbfc9d22c7 | |
Kacper Michajłow | 47dbc3a74e | |
Kacper Michajłow | 0b234af113 | |
rcombs | 3a851741e9 | |
rcombs | 1e37197cd5 | |
rcombs | 960faddda3 |
|
@ -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:
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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
|
20
meson.build
20
meson.build
|
@ -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'),
|
||||
|
|
|
@ -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)')
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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(®);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
|
@ -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);
|
||||
}
|
192
player/command.c
192
player/command.c
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue