Coverage Report

Created: 2026-06-25 06:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mpv/input/dnd.c
Line
Count
Source
1
/*
2
 * This file is part of mpv.
3
 *
4
 * mpv is free software; you can redistribute it and/or
5
 * modify it under the terms of the GNU Lesser General Public
6
 * License as published by the Free Software Foundation; either
7
 * version 2.1 of the License, or (at your option) any later version.
8
 *
9
 * mpv is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU Lesser General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU Lesser General Public
15
 * License along with mpv.  If not, see <http://www.gnu.org/licenses/>.
16
 */
17
18
#include "dnd.h"
19
#include "common/msg.h"
20
#include "misc/bstr.h"
21
#include "misc/node.h"
22
#include "misc/path_utils.h"
23
#include "osdep/threads.h"
24
#include "player/client.h"
25
26
static bool might_be_subtitle_file(mpv_node *sub_exts, char *file)
27
0
{
28
0
    for (int i = 0; i < sub_exts->u.list->num; i++) {
29
0
         mpv_node sub_ext = sub_exts->u.list->values[i];
30
0
         if (sub_ext.format != MPV_FORMAT_STRING)
31
0
             continue;
32
0
         if (!bstrcasecmp0(bstr_get_ext(bstr0(file)), sub_ext.u.string))
33
0
             return true;
34
0
    }
35
0
    return false;
36
0
}
37
38
static void handle_dnd(mpv_handle *mpv, mpv_node *files, char *action)
39
0
{
40
0
    mpv_node sub_exts = {0};
41
0
    mpv_node drop_type = {0};
42
0
    if (mpv_get_property(mpv, "sub-auto-exts", MPV_FORMAT_NODE, &sub_exts) != MPV_ERROR_SUCCESS ||
43
0
        sub_exts.format != MPV_FORMAT_NODE_ARRAY)
44
0
        goto end;
45
0
    if (mpv_get_property(mpv, "drag-and-drop", MPV_FORMAT_NODE, &drop_type) != MPV_ERROR_SUCCESS ||
46
0
        drop_type.format != MPV_FORMAT_STRING)
47
0
        goto end;
48
0
    if (!strcmp(drop_type.u.string, "no"))
49
0
        goto end;
50
0
    if (strcmp(drop_type.u.string, "auto"))
51
0
        action = drop_type.u.string;
52
53
0
    struct mpv_node_list *list = files->u.list;
54
0
    for (int i = 0; i < list->num; i++) {
55
0
          mpv_node file = list->values[i];
56
0
          if (file.format != MPV_FORMAT_STRING)
57
0
              goto end;
58
0
    }
59
60
0
    bool all_sub = true;
61
0
    for (int i = 0; i < list->num; i++)
62
0
        all_sub &= might_be_subtitle_file(&sub_exts, list->values[i].u.string);
63
64
0
    if (all_sub) {
65
0
        for (int i = 0; i < list->num; i++) {
66
0
            const char *cmd[] = {
67
0
                "osd-auto",
68
0
                "sub-add",
69
0
                list->values[i].u.string,
70
0
                NULL
71
0
            };
72
0
            mpv_command(mpv, cmd);
73
0
        }
74
0
    } else if (!strcmp(action, "insert-next")) {
75
        /* To insert the entries in the correct order, we iterate over them
76
           backwards */
77
0
        for (int i = list->num - 1; i >= 0; i--) {
78
0
            const char *cmd[] = {
79
0
                "osd-auto",
80
0
                "loadfile",
81
0
                list->values[i].u.string,
82
                /* Since we're inserting in reverse, wait til the final item
83
                   is added to start playing */
84
0
                (i > 0) ? "insert-next" : "insert-next-play",
85
0
                NULL
86
0
            };
87
0
            mpv_command(mpv, cmd);
88
0
        }
89
0
    } else {
90
0
        for (int i = 0; i < list->num; i++) {
91
0
            const char *cmd[] = {
92
0
                "osd-auto",
93
0
                "loadfile",
94
0
                list->values[i].u.string,
95
                /* Either start playing the dropped files right away
96
                   or add them to the end of the current playlist */
97
0
                (i == 0 && !strcmp(action, "replace")) ? "replace" : "append-play",
98
0
                NULL
99
0
            };
100
0
            mpv_command(mpv, cmd);
101
0
        }
102
0
    }
103
104
0
end:
105
0
    mpv_free_node_contents(&sub_exts);
106
0
    mpv_free_node_contents(&drop_type);
107
0
}
108
109
static MP_THREAD_VOID mpv_event_loop_fn(void *arg)
110
107k
{
111
107k
    mp_thread_set_name("dnd");
112
107k
    mpv_handle *mpv = arg;
113
107k
    bool enabled = false;
114
107k
    mpv_observe_property(mpv, 0, "dropped-files", MPV_FORMAT_NODE);
115
107k
    mpv_observe_property(mpv, 0, "input-builtin-drag-and-drop", MPV_FORMAT_FLAG);
116
117
1.62M
    while (1) {
118
1.62M
        mpv_event *event = mpv_wait_event(mpv, -1);
119
1.62M
        if (event->event_id == MPV_EVENT_SHUTDOWN)
120
107k
            break;
121
1.51M
        if (event->event_id == MPV_EVENT_PROPERTY_CHANGE) {
122
194k
            mpv_event_property *prop = event->data;
123
194k
            if (enabled && !strcmp(prop->name, "dropped-files") && prop->format == MPV_FORMAT_NODE) {
124
0
                mpv_node *node = prop->data;
125
0
                mpv_node *action = node_map_get(node, "action");
126
0
                if (!action || action->format != MPV_FORMAT_STRING)
127
0
                    continue;
128
129
0
                mpv_node *files = node_map_get(node, "files");
130
0
                if (!files || files->format != MPV_FORMAT_NODE_ARRAY)
131
0
                    continue;
132
0
                handle_dnd(mpv, files, action->u.string);
133
0
            }
134
194k
            if (!strcmp(prop->name, "input-builtin-drag-and-drop") && prop->format == MPV_FORMAT_FLAG)
135
97.4k
                enabled = *(int *)prop->data;
136
194k
        }
137
1.51M
    }
138
139
107k
    mpv_destroy(mpv);
140
107k
    MP_THREAD_RETURN();
141
107k
}
142
143
void mp_dnd_init(mpv_handle *mpv)
144
107k
{
145
107k
    mp_thread mpv_event_loop;
146
107k
    if (!mp_thread_create(&mpv_event_loop, mpv_event_loop_fn, mpv))
147
107k
        mp_thread_detach(mpv_event_loop);
148
0
    else
149
0
        mpv_destroy(mpv);
150
107k
}