Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

player/dialog/file_dialog: add file dialog for macOS, Windows and Linux #15845

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
f4b7559
osdep/file_dialog: add file dialog for Windows
kasper93 Feb 10, 2025
1bf76e8
osdep/file_dialog: add file dialog for macOS
Akemi Feb 14, 2025
5d48de1
osdep/file_dialog-external: add implementation for kdialog and zenity
kasper93 Feb 10, 2025
c7d8eaa
osdep/file_dialog-portal: add impl of file dialog through portal
kasper93 Feb 12, 2025
fd2fbd6
osdep/file_dialog-unix: add fallback to external dialogs if portal fails
kasper93 Feb 13, 2025
96cdbb1
player/command: make the URL optional for loadfile add add loaddir
kasper93 Feb 10, 2025
d4bb18f
player/command: add loadfiles command
kasper93 Feb 11, 2025
608583a
player/command: add file-dialog command
kasper93 Feb 11, 2025
2f77e00
player/command: make the URL optional for {sub,audio,video}-add
kasper93 Feb 11, 2025
02d6636
player/command: set directory of file dialog to current file dir or cwd
kasper93 Feb 11, 2025
9943a76
player/screenshot: add file dialog to screenshot-to-file command
kasper93 Feb 13, 2025
285b729
player/command: make the URL optional for loadlist
kasper93 Feb 15, 2025
fb64451
osdep/file_dialog: add parent to mp_file_dialog_params
kasper93 Feb 14, 2025
699e713
osdep/file_dialog: add file-dialog-providers option
kasper93 Feb 14, 2025
0767d3a
player/dialog/file_dialog: move file_dialog implementation to own dir
kasper93 Feb 15, 2025
dd918d5
player/dialog/file_dialog: reorganize implementation
kasper93 Feb 15, 2025
3d72898
input.conf: add ctrl+o and ctrl+d to open file dialog
kasper93 Feb 10, 2025
d1f8e8c
select.lua: add `Open file(s)` and `Open directory` to menu
kasper93 Feb 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions DOCS/interface-changes/file-dialog.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
make the URL argument optional for `loadfile`, `loadlist`, `{sub,audio,video}-add`, `screenshot-to-file`; it will open a file selection dialog on supported platforms
add `loaddir` command to select directories using a file dialog
add `loadfiles` command to allow appending string list to playlist
kasper93 marked this conversation as resolved.
Show resolved Hide resolved
add `file-dialog` command
add `file-dialog-providers` option
106 changes: 99 additions & 7 deletions DOCS/man/input.rst
Original file line number Diff line number Diff line change
Expand Up @@ -519,13 +519,16 @@ Playlist Manipulation
restarted if for example the new playlist entry is the same as the previous
one.

``loadfile <url> [<flags> [<index> [<options>]]]``
``loadfile [<url> [<flags> [<index> [<options>]]]]``
Load the given file or URL and play it. Technically, this is just a playlist
manipulation command (which either replaces the playlist or adds an entry
to it). Actual file loading happens independently. For example, a
``loadfile`` command that replaces the current file with a new one returns
before the current file is stopped, and the new file even begins loading.

The URL argument is optional. If not provided, a file dialog will be shown
to select a file.

Second argument:

<replace> (default)
Expand Down Expand Up @@ -571,7 +574,41 @@ Playlist Manipulation
this problem, the third argument now needs to be set to -1 if the fourth
argument needs to be used.

``loadlist <url> [<flags> [<index>]]``
``loadfiles [<URLs> [<flags> [<index> [<options>]]]]``
Load the given file or URL and play it. Technically, this is just a playlist
manipulation command (which either replaces the playlist or adds an entry
to it). Actual file loading happens independently. For example, a
``loadfiles`` command that replaces the current file with a new one returns
before the current file is stopped, and the new file even begins loading.

The URLs argument is optional. If not provided, a file dialog will be shown
to select files.

Second argument:

<replace> (default)
Stop playback of the current file, and play the new file immediately.
<append>
Append files to the playlist.
<append-play>
Append files, and if nothing is currently playing, start playback.
(Always starts with the added file, even if the playlist was not empty
before running this command.)

The third argument is a list of options and values which should be set
while the file is playing. It is of the form ``opt1=value1,opt2=value2,..``.
When using the client API, this can be a ``MPV_FORMAT_NODE_MAP`` (or a Lua
table), however the values themselves must be strings currently. These
options are set during playback, and restored to the previous value at end
of playback (see `Per-File Options`_).

This command returns id of first added file in the playlist.

``loaddir [<url> [<flags> [<index> [<options>]]]]``
This is the same as ``loadfile``, but it also allows opening directories.
When a URL is not provided, a file dialog will be shown to select a directory.

``loadlist [<url> [<flags> [<index>]]]``
Load the given playlist file or URL (like ``--playlist``).

Second argument:
Expand Down Expand Up @@ -634,9 +671,12 @@ Playlist Manipulation
Track Manipulation
~~~~~~~~~~~~~~~~~~

``sub-add <url> [<flags> [<title> [<lang>]]]``
``sub-add [<url> [<flags> [<title> [<lang>]]]]``
Load the given subtitle file or stream. By default, it is selected as
current subtitle after loading.
current subtitle after loading.

The ``url`` argument is optional and if empty, file dialog will be shown to
select subtitle file.

The ``flags`` argument is one of the following values:

Expand Down Expand Up @@ -683,7 +723,7 @@ Track Manipulation
secondary
Steps through the secondary subtitles.

``audio-add <url> [<flags> [<title> [<lang>]]]``
``audio-add [<url> [<flags> [<title> [<lang>]]]]``
Load the given audio file. See ``sub-add`` command.

``audio-remove [<id>]``
Expand All @@ -692,7 +732,7 @@ Track Manipulation
``audio-reload [<id>]``
Reload the given audio tracks. See ``sub-reload`` command.

``video-add <url> [<flags> [<title> [<lang> [<albumart>]]]]``
``video-add [<url> [<flags> [<title> [<lang> [<albumart>]]]]]``
Load the given video file. See ``sub-add`` command for common options.

``albumart`` (``MPV_FORMAT_FLAG``)
Expand Down Expand Up @@ -1404,11 +1444,14 @@ Screenshot Commands
On success, returns a ``mpv_node`` with a ``filename`` field set to the
saved screenshot location.

``screenshot-to-file <filename> [<flags>]``
``screenshot-to-file [<filename> [<flags>]]``
Take a screenshot and save it to a given file. The format of the file will
be guessed by the extension (and ``--screenshot-format`` is ignored - the
behavior when the extension is missing or unknown is arbitrary).
kasper93 marked this conversation as resolved.
Show resolved Hide resolved

The filename argument is optional. If not provided, a file dialog will be
shown to select a location and name for the file.

The second argument is like the first argument to ``screenshot`` and
supports ``subtitles``, ``video``, ``window``.

Expand Down Expand Up @@ -1640,6 +1683,55 @@ Miscellaneous Commands
``context-menu``
Show context menu on the video window. See `Context Menu`_ section for details.

``file-dialog``
Shows a file dialog that can be used to select file(s) or a directory to
open or save. It is accessible when using commands that require a file path
as an argument, such as ``loadfile``, when used with an empty URL argument.
Using ``file-dialog`` makes the use standalone.

The command has the following arguments:

``type``
The type of dialog to show. Can be one of the following:

``open``
Open file dialog.
``save``
Save file dialog.
``directory``
Open directory dialog.

``allow-multiple``
Allows multiple files to be selected. Only valid for the ``open`` type.

``title``
The title of the dialog.

``initial-selection``
The initial selection in the dialog. Useful for save dialogs to suggest
a filename.

``initial-dir``
The initial directory in which to open the dialog.

``filter``
File type filters. This is a list of key-value pairs, where the key is
the filter name and the value is a list of file extensions. Extensions
are separated by spaces.
Example: ``Video Files=mkv mp4,Audio Files=mp3 aac``.

``set-property``
The property to set with the selected file.

Return Value:

- If a ``set-property`` is set, the first selected file is used to
set the given property via ``mp_property_do``. Command returns stats of
that operation.
- If ``set-property`` is not set, the selected files are returned as an
array (``MPV_FORMAT_NODE_ARRAY``), where each file path is stored as a
string (``MPV_FORMAT_STRING``).

Undocumented commands: ``ao-reload`` (experimental/internal).

List of events
Expand Down
25 changes: 25 additions & 0 deletions DOCS/man/options.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7914,3 +7914,28 @@ Miscellaneous
On Wayland, this option only has effect on the ``wayland`` backend, and
not for the ``vo`` backend. See ``current-clipboard-backend`` property for
more details.

``--file-dialog-providers=<providers1,providers2,...[,]>``
Specify a priority list of the file dialog porviders to be used.
You can also pass ``help`` to get a complete list of compiled in porviders.
Removing a provider from the list will disable it, clearing the list will
disable file dialog support entirely.

The following file dialog providers are implemented:

``win32``
Native Windows dialog.

``mac``
Native macOS dialog.

``portal``
Desktop FileChooser portal through D-Bus.

``kdialog``
KDialog run as subprocess.

``zenity``
Zenity run as subprocess.

This is an object settings list option. See `List Options`_ for details.
2 changes: 2 additions & 0 deletions etc/input.conf
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,8 @@
#L cycle-values loop-file "inf" "no" # toggle infinite looping
#ctrl+c quit 4
#Ctrl+v loadfile ${clipboard/text} append-play; show-text '+ ${clipboard/text}' # append the copied path
#ctrl+o loadfiles # open file(s) selection dialog
#ctrl+d loaddir # open directory selection dialog
#DEL script-binding osc/visibility # cycle OSC visibility between never, auto (mouse-move) and always
#ctrl+h cycle-values hwdec "no" "auto-safe" # toggle hardware decoding
#F8 show-text ${playlist} # show the playlist
Expand Down
18 changes: 17 additions & 1 deletion meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,8 @@ if features['cocoa']
'osdep/path-mac.m',
'osdep/utils-mac.c',
'osdep/mac/app_bridge.m',
'player/clipboard/clipboard-mac.m')
'player/clipboard/clipboard-mac.m',
'player/dialog/file_dialog-mac.m')
main_fn_source = files('osdep/main-fn-mac.c')
endif

Expand Down Expand Up @@ -533,6 +534,7 @@ if features['win32-desktop']
endif
sources += files('input/ipc-win.c',
'player/clipboard/clipboard-win.c',
'player/dialog/file_dialog-win.c',
'osdep/language-win.c',
'osdep/terminal-win.c',
'video/out/w32_common.c',
Expand All @@ -548,6 +550,20 @@ if not posix and not features['win32-desktop']
'osdep/terminal-dummy.c')
endif

if not win32 and not darwin
gio = dependency('gio-2.0', required: get_option('gio'))
features += {'gio': gio.found()}
if features['gio']
sources += files('player/dialog/file_dialog-portal.c')
dependencies += gio
endif
sources += files('player/dialog/file_dialog-external.c')
else
features += {'gio': false}
endif

sources += files('player/dialog/file_dialog.c')

# media controls
if win32
subdir('osdep/win32')
Expand Down
2 changes: 2 additions & 0 deletions meson.options
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ option('swift-flags', type: 'string', description: 'Optional Swift compiler flag
option('win32-smtc', type: 'feature', value: 'auto', description: 'Enable Media Control support')
option('win32-subsystem', type : 'string', value: 'windows', description : 'Windows subsystem to use for mpv.exe')

option('gio', type: 'feature', value: 'auto', description: 'Enable Gio for Desktop Portal support')

# manpages
option('html-build', type: 'feature', value: 'disabled', description: 'HTML manual generation')
option('manpage-build', type: 'feature', value: 'auto', description: 'manpage generation')
Expand Down
3 changes: 3 additions & 0 deletions options/options.c
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ extern const struct m_sub_options ao_conf;

extern const struct m_sub_options dvd_conf;
extern const struct m_sub_options clipboard_conf;
extern const struct m_sub_options file_dialog_conf;

extern const struct m_sub_options opengl_conf;
extern const struct m_sub_options vulkan_conf;
Expand Down Expand Up @@ -884,6 +885,8 @@ static const m_option_t mp_opts[] = {
{"screenshot-directory", OPT_ALIAS("screenshot-dir")},
{"screenshot-sw", OPT_BOOL(screenshot_sw)},

{"file-dialog", OPT_SUBSTRUCT(file_dialog_opts, file_dialog_conf)},

{"", OPT_SUBSTRUCT(resample_opts, resample_conf)},

{"", OPT_SUBSTRUCT(input_opts, input_config)},
Expand Down
2 changes: 2 additions & 0 deletions options/options.h
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,8 @@ typedef struct MPOpts {
int w32_priority;
bool media_controls;

struct file_dialog_opts *file_dialog_opts;

struct bluray_opts *stream_bluray_opts;
struct cdda_opts *stream_cdda_opts;
struct dvb_opts *stream_dvb_opts;
Expand Down
1 change: 1 addition & 0 deletions osdep/mac/app_bridge_objc.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "options/m_config.h"
#include "player/core.h"
#include "player/clipboard/clipboard.h"
#include "player/dialog/file_dialog.h"
#include "common/global.h"
#include "input/input.h"
#include "input/event.h"
Expand Down
62 changes: 61 additions & 1 deletion osdep/mac/dialog.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,13 @@
import Cocoa
import UniformTypeIdentifiers

class Dialog {
class Dialog: NSObject {
var option: OptionHelper?

@objc override init() {
super.init()
}

init(_ option: OptionHelper? = nil) {
self.option = option
}
Expand Down Expand Up @@ -56,6 +60,62 @@ class Dialog {
return nil
}

func save(title: String? = nil, name: String? = nil, path: URL? = nil) -> [String]? {
let panel = NSSavePanel()
panel.title = title ?? panel.title
panel.nameFieldStringValue = name ?? panel.nameFieldStringValue
panel.directoryURL = path

if panel.runModal() == .OK {
if let url = panel.url {
return [url.path]
}
}

return nil
}

@objc func openDialog(_ tallocCtx: UnsafeMutableRawPointer,
params: UnsafePointer<mp_file_dialog_params>) -> UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>? {
let conf = params.pointee
let isSave = Bool(conf.flags.rawValue & MP_FILE_DIALOG_SAVE.rawValue)
let directories = Bool(conf.flags.rawValue & MP_FILE_DIALOG_DIRECTORY.rawValue)
let multiple = Bool(conf.flags.rawValue & MP_FILE_DIALOG_MULTIPLE.rawValue)
let title = conf.title != nil ? String(cString: conf.title) : nil
let path = conf.initial_dir != nil ? URL(fileURLWithPath: String(cString: conf.initial_dir)): nil
let name = conf.initial_selection != nil ? String(cString: conf.initial_selection) : nil
var types: [UTType] = []

if var filters = conf.filters {
while filters.pointee.name != nil, let exts = filters.pointee.extensions {
types += TypeHelper.toStringArray(exts).compactMap { UTType(filenameExtension: $0) }
filters += 1
}
}

var paths: [String]?
DispatchQueue.main.sync {
paths = isSave ? save(title: title, name: name, path: path) :
open(title: title, path: path, files: !directories, directories: directories, multiple: multiple, types: types)
}

guard let files = paths else { return nil }

var retCount: Int32 = 0
var ret: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?
for file in files {
let item = ta_xstrdup(tallocCtx, file)
app_bridge_tarray_append(tallocCtx, &ret, &retCount, item)
ta_steal_(ret, item)
}

if retCount > 0 {
app_bridge_tarray_append(tallocCtx, &ret, &retCount, nil)
}

return ret
}

func openUrl() -> String? {
let alert = NSAlert()
alert.messageText = "Open URL"
Expand Down
4 changes: 4 additions & 0 deletions osdep/mac/swift_extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ extension Bool {
init(_ int32: Int32) {
self.init(int32 != 0)
}

init(_ uint32: UInt32) {
self.init(uint32 != 0)
}
}

extension Int32 {
Expand Down
Loading
Loading