media:cpp : ui 1/2
This commit is contained in:
parent
8ead230cc2
commit
cb3e596736
@ -154,6 +154,7 @@ if(WIN32)
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/win/register_explorer.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/win/resize_ui.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/win/resize_progress_ui.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/win/ui_singleton.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/win/media-img-win.manifest"
|
||||
)
|
||||
endif()
|
||||
|
||||
BIN
packages/media/cpp/dist/media-img.exe
vendored
BIN
packages/media/cpp/dist/media-img.exe
vendored
Binary file not shown.
@ -6,6 +6,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <set>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
@ -168,6 +169,123 @@ std::vector<std::string> expand_input_paths(const std::string &input_spec, std::
|
||||
return out;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
void ext_to_lower_ascii(std::string &e) {
|
||||
for (char &c : e) {
|
||||
if (c >= 'A' && c <= 'Z')
|
||||
c = static_cast<char>(c - 'A' + 'a');
|
||||
}
|
||||
}
|
||||
|
||||
bool is_supported_image_extension(const std::string &ext_with_dot) {
|
||||
static const char *k[] = {".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".tiff", ".tif",
|
||||
".jpe", ".jfif", ".avif", ".arw", ".heic", ".jf"};
|
||||
std::string e = ext_with_dot;
|
||||
ext_to_lower_ascii(e);
|
||||
for (const char *ref : k) {
|
||||
if (e == ref)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void collect_images_recursive(const fs::path &dir, std::set<std::string> &out) {
|
||||
std::error_code ec;
|
||||
fs::recursive_directory_iterator it(dir, fs::directory_options::skip_permission_denied, ec);
|
||||
if (ec)
|
||||
return;
|
||||
const fs::recursive_directory_iterator end;
|
||||
for (; it != end; it.increment(ec)) {
|
||||
if (ec)
|
||||
break;
|
||||
const fs::directory_entry &ent = *it;
|
||||
std::error_code fe;
|
||||
if (!ent.is_regular_file(fe) || fe)
|
||||
continue;
|
||||
const std::string ext = ent.path().extension().string();
|
||||
if (!is_supported_image_extension(ext))
|
||||
continue;
|
||||
fs::path canon = fs::weakly_canonical(ent.path(), fe);
|
||||
if (fe)
|
||||
canon = fs::absolute(ent.path());
|
||||
out.insert(canon.generic_string());
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> split_semicolon_paths(const std::string &input_spec) {
|
||||
std::vector<std::string> parts;
|
||||
std::string cur;
|
||||
for (char c : input_spec) {
|
||||
if (c == ';') {
|
||||
trim_in_place(cur);
|
||||
if (!cur.empty())
|
||||
parts.push_back(std::move(cur));
|
||||
cur.clear();
|
||||
} else {
|
||||
cur.push_back(c);
|
||||
}
|
||||
}
|
||||
trim_in_place(cur);
|
||||
if (!cur.empty())
|
||||
parts.push_back(std::move(cur));
|
||||
return parts;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::string expand_resize_ui_inputs(const std::string &input_spec, std::string &err_out) {
|
||||
err_out.clear();
|
||||
if (input_spec.empty()) {
|
||||
err_out = "input is empty";
|
||||
return {};
|
||||
}
|
||||
if (is_http_url(input_spec))
|
||||
return input_spec;
|
||||
if (has_glob_tokens(input_spec) || input_spec.find("**") != std::string::npos)
|
||||
return input_spec;
|
||||
|
||||
std::vector<std::string> segments;
|
||||
if (input_spec.find(';') == std::string::npos)
|
||||
segments.push_back(input_spec);
|
||||
else
|
||||
segments = split_semicolon_paths(input_spec);
|
||||
|
||||
std::set<std::string> collected;
|
||||
for (const auto &seg : segments) {
|
||||
std::string s = seg;
|
||||
trim_in_place(s);
|
||||
if (s.empty())
|
||||
continue;
|
||||
fs::path p(s);
|
||||
std::error_code ec;
|
||||
if (fs::is_directory(p, ec)) {
|
||||
collect_images_recursive(p, collected);
|
||||
} else if (fs::is_regular_file(p, ec)) {
|
||||
fs::path canon = fs::weakly_canonical(fs::absolute(p), ec);
|
||||
if (ec)
|
||||
canon = fs::absolute(p);
|
||||
collected.insert(canon.generic_string());
|
||||
} else {
|
||||
err_out = "not found: " + s;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
if (collected.empty()) {
|
||||
err_out = "resize: no image files in selection";
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string joined;
|
||||
for (const auto &path : collected) {
|
||||
if (!joined.empty())
|
||||
joined.push_back(';');
|
||||
joined += path;
|
||||
}
|
||||
return joined;
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::string, fs::path>> pair_resize_paths(const std::string &input_spec,
|
||||
const std::string &output_spec,
|
||||
std::string &err_out) {
|
||||
|
||||
@ -22,6 +22,13 @@ bool has_dst_template(const std::string &output_spec);
|
||||
*/
|
||||
std::vector<std::string> expand_input_paths(const std::string &input_spec, std::string &err_out);
|
||||
|
||||
/**
|
||||
* For `resize --ui`: expand semicolon-separated paths; recurse into directories and collect
|
||||
* supported image files (same extensions as Explorer registration). Leaves glob/URL specs unchanged.
|
||||
* @return New semicolon-separated list, or empty with err_out set.
|
||||
*/
|
||||
std::string expand_resize_ui_inputs(const std::string &semicolon_or_single_path, std::string &err_out);
|
||||
|
||||
/**
|
||||
* Map inputs to output paths: optional per-file dst templates, one output file,
|
||||
* or one file per input under a directory (trailing separator or existing directory).
|
||||
|
||||
@ -34,6 +34,7 @@ std::string join_src_semicolons(const std::vector<std::string> &v) {
|
||||
#include "win/register_explorer.hpp"
|
||||
#include "win/resize_progress_ui.hpp"
|
||||
#include "win/resize_ui.hpp"
|
||||
#include "win/ui_singleton.hpp"
|
||||
#endif
|
||||
|
||||
#ifndef MEDIA_IMG_VERSION
|
||||
@ -194,7 +195,23 @@ int main(int argc, char **argv) {
|
||||
|
||||
CLI11_PARSE(app, argc, argv);
|
||||
|
||||
#if defined(_WIN32)
|
||||
bool resize_ui_mode = false;
|
||||
#endif
|
||||
|
||||
if (resize_cmd->parsed()) {
|
||||
#if defined(_WIN32)
|
||||
struct UiSingletonOwner {
|
||||
bool mutex = false;
|
||||
bool bridge = false;
|
||||
~UiSingletonOwner() {
|
||||
if (bridge)
|
||||
media::win::destroy_ui_singleton_bridge();
|
||||
if (mutex)
|
||||
media::win::release_ui_singleton_mutex();
|
||||
}
|
||||
} ui_singleton;
|
||||
#endif
|
||||
media::ResizeOptions opt;
|
||||
std::string in_path_e;
|
||||
std::string out_path_e;
|
||||
@ -205,6 +222,25 @@ int main(int argc, char **argv) {
|
||||
std::cerr << "resize: use either positional input or --src, not both\n";
|
||||
return 1;
|
||||
}
|
||||
{
|
||||
const std::string forward_seed = src_list.empty() ? in_path : join_src_semicolons(src_list);
|
||||
if (!media::win::try_acquire_ui_singleton_mutex()) {
|
||||
if (!forward_seed.empty()) {
|
||||
if (!media::win::forward_resize_ui_paths_to_primary(forward_seed))
|
||||
std::cerr << "media-img: could not reach the running resize UI (try again in a moment)\n";
|
||||
} else {
|
||||
std::cerr << "media-img: resize UI is already running\n";
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
ui_singleton.mutex = true;
|
||||
if (!media::win::create_ui_singleton_bridge()) {
|
||||
std::cerr << "media-img: could not create UI bridge window\n";
|
||||
return 1;
|
||||
}
|
||||
ui_singleton.bridge = true;
|
||||
}
|
||||
resize_ui_mode = true;
|
||||
media::ResizeOptions initial{};
|
||||
initial.max_width = max_w;
|
||||
initial.max_height = max_h;
|
||||
@ -280,6 +316,18 @@ int main(int argc, char **argv) {
|
||||
opt.url_max_redirects = url_max_redirects;
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
if (resize_ui_mode) {
|
||||
std::string xerr;
|
||||
const std::string expanded_in = media::expand_resize_ui_inputs(in_path_e, xerr);
|
||||
if (expanded_in.empty()) {
|
||||
std::cerr << xerr << "\n";
|
||||
return 1;
|
||||
}
|
||||
in_path_e = expanded_in;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (out_path_e.empty()) {
|
||||
std::string derr;
|
||||
out_path_e = media::default_output_path_for_resize(in_path_e, opt.format, derr, opt.output_stem_suffix);
|
||||
@ -300,8 +348,10 @@ int main(int argc, char **argv) {
|
||||
std::cerr << preview_err << "\n";
|
||||
return 1;
|
||||
}
|
||||
if (jobs_preview.size() > 1)
|
||||
batch_ok = media::win::run_resize_batch_with_progress_ui(in_path_e, out_path_e, opt, err, &batch);
|
||||
const bool use_progress = resize_ui_mode || jobs_preview.size() > 1;
|
||||
if (use_progress)
|
||||
batch_ok = media::win::run_resize_batch_with_progress_ui(in_path_e, out_path_e, opt, err, &batch,
|
||||
resize_ui_mode);
|
||||
else
|
||||
batch_ok = media::resize_batch(in_path_e, out_path_e, opt, err, &batch);
|
||||
}
|
||||
|
||||
@ -6,6 +6,9 @@
|
||||
#include <windows.h>
|
||||
#include <commctrl.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
@ -85,7 +88,8 @@ static bool register_prog_class() {
|
||||
} // namespace
|
||||
|
||||
bool run_resize_batch_with_progress_ui(const std::string &input_spec, const std::string &output_spec,
|
||||
const ResizeOptions &opt, std::string &err_out, ResizeBatchResult *out_stats) {
|
||||
const ResizeOptions &opt, std::string &err_out, ResizeBatchResult *out_stats,
|
||||
bool always_show_progress) {
|
||||
std::string pair_err;
|
||||
auto jobs = media::pair_resize_paths(input_spec, output_spec, pair_err);
|
||||
if (!pair_err.empty()) {
|
||||
@ -96,7 +100,7 @@ bool run_resize_batch_with_progress_ui(const std::string &input_spec, const std:
|
||||
err_out = "no resize jobs";
|
||||
return false;
|
||||
}
|
||||
if (jobs.size() == 1)
|
||||
if (!always_show_progress && jobs.size() == 1)
|
||||
return media::resize_batch(input_spec, output_spec, opt, err_out, out_stats);
|
||||
|
||||
INITCOMMONCONTROLSEX icc{};
|
||||
@ -134,12 +138,49 @@ bool run_resize_batch_with_progress_ui(const std::string &input_spec, const std:
|
||||
bool ok = false;
|
||||
std::string worker_err;
|
||||
|
||||
// Job queue + mutex: single consumer worker today; same mutex can guard pushes from other
|
||||
// producers (e.g. WM_COPYDATA) with a std::condition_variable if streaming jobs are added later.
|
||||
namespace fs = std::filesystem;
|
||||
std::queue<std::pair<std::string, fs::path>> job_q;
|
||||
std::mutex job_q_mutex;
|
||||
for (const auto &j : jobs) {
|
||||
std::lock_guard<std::mutex> lk(job_q_mutex);
|
||||
job_q.push(j);
|
||||
}
|
||||
|
||||
std::thread worker([&]() {
|
||||
ok = media::resize_batch(
|
||||
input_spec, output_spec, opt, worker_err, out_stats,
|
||||
[&](std::size_t i, std::size_t total) {
|
||||
PostMessageW(hwnd, WM_PI_PROG, static_cast<WPARAM>(i + 1), static_cast<LPARAM>(total));
|
||||
});
|
||||
if (out_stats) {
|
||||
out_stats->count = 0;
|
||||
out_stats->outputs.clear();
|
||||
}
|
||||
const std::size_t total = jobs.size();
|
||||
std::size_t idx = 0;
|
||||
while (true) {
|
||||
std::pair<std::string, fs::path> job;
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(job_q_mutex);
|
||||
if (job_q.empty())
|
||||
break;
|
||||
job = job_q.front();
|
||||
job_q.pop();
|
||||
}
|
||||
PostMessageW(hwnd, WM_PI_PROG, static_cast<WPARAM>(idx + 1), static_cast<LPARAM>(total));
|
||||
std::error_code ec;
|
||||
fs::create_directories(job.second.parent_path(), ec);
|
||||
std::string one_err;
|
||||
if (!media::resize_file(job.first, job.second.string(), opt, one_err)) {
|
||||
ok = false;
|
||||
worker_err = job.first + ": " + one_err;
|
||||
PostMessageW(hwnd, WM_PI_DONE, 0, 0);
|
||||
return;
|
||||
}
|
||||
if (out_stats) {
|
||||
++out_stats->count;
|
||||
out_stats->outputs.push_back(job.second.string());
|
||||
}
|
||||
++idx;
|
||||
}
|
||||
ok = true;
|
||||
PostMessageW(hwnd, WM_PI_DONE, 0, 0);
|
||||
});
|
||||
|
||||
|
||||
@ -6,8 +6,12 @@
|
||||
|
||||
namespace media::win {
|
||||
|
||||
/** Windows: run `resize_batch` with a native progress bar when multiple files; single job uses `resize_batch` only. */
|
||||
/**
|
||||
* Windows: run resize jobs with a native progress bar.
|
||||
* @param always_show_progress If true (e.g. `resize --ui`), show the window even for a single file.
|
||||
*/
|
||||
bool run_resize_batch_with_progress_ui(const std::string &input_spec, const std::string &output_spec,
|
||||
const ResizeOptions &opt, std::string &err_out, ResizeBatchResult *out_stats);
|
||||
const ResizeOptions &opt, std::string &err_out, ResizeBatchResult *out_stats,
|
||||
bool always_show_progress = false);
|
||||
|
||||
} // namespace media::win
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
#include "resize_ui.hpp"
|
||||
#include "win/resize_ui.hpp"
|
||||
|
||||
#include "win/ui_singleton.hpp"
|
||||
|
||||
#include <windows.h>
|
||||
#include <winerror.h>
|
||||
@ -319,6 +321,7 @@ static LRESULT CALLBACK UiWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
|
||||
|
||||
set_utf8_edit(st->h_in, *st->in_path);
|
||||
set_utf8_edit(st->h_out, *st->out_path);
|
||||
media::win::set_ui_merge_input_edit(st->h_in);
|
||||
apply_message_font(hwnd);
|
||||
{
|
||||
RECT cr{};
|
||||
@ -431,6 +434,7 @@ static LRESULT CALLBACK UiWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
|
||||
DestroyWindow(hwnd);
|
||||
return 0;
|
||||
case WM_DESTROY:
|
||||
media::win::clear_ui_merge_input_edit();
|
||||
PostQuitMessage(0);
|
||||
return 0;
|
||||
default:
|
||||
|
||||
156
packages/media/cpp/src/win/ui_singleton.cpp
Normal file
156
packages/media/cpp/src/win/ui_singleton.cpp
Normal file
@ -0,0 +1,156 @@
|
||||
#include "win/ui_singleton.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#pragma comment(lib, "User32.lib")
|
||||
|
||||
namespace media::win {
|
||||
namespace {
|
||||
|
||||
HWND g_merge_input_edit{};
|
||||
HWND g_bridge_hwnd{};
|
||||
HANDLE g_mutex{};
|
||||
|
||||
static const wchar_t kBridgeClass[] = L"MediaImgUiBridge";
|
||||
static const wchar_t kBridgeTitle[] = L"media-img";
|
||||
|
||||
std::wstring utf8_to_wide(const std::string &s) {
|
||||
if (s.empty())
|
||||
return L"";
|
||||
int n = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), static_cast<int>(s.size()), nullptr, 0);
|
||||
if (n <= 0)
|
||||
return L"";
|
||||
std::wstring w(static_cast<size_t>(n), L'\0');
|
||||
MultiByteToWideChar(CP_UTF8, 0, s.c_str(), static_cast<int>(s.size()), w.data(), n);
|
||||
return w;
|
||||
}
|
||||
|
||||
LRESULT CALLBACK bridge_wnd_proc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
|
||||
(void)wp;
|
||||
if (msg == WM_COPYDATA) {
|
||||
auto *cds = reinterpret_cast<COPYDATASTRUCT *>(lp);
|
||||
if (!cds || cds->dwData != 1 || !cds->lpData || cds->cbData == 0)
|
||||
return FALSE;
|
||||
const char *bytes = static_cast<const char *>(cds->lpData);
|
||||
size_t len = cds->cbData;
|
||||
while (len > 0 && bytes[len - 1] == '\0')
|
||||
--len;
|
||||
if (len == 0)
|
||||
return TRUE;
|
||||
std::string payload(bytes, bytes + len);
|
||||
|
||||
if (g_merge_input_edit && IsWindow(g_merge_input_edit)) {
|
||||
const int n = GetWindowTextLengthW(g_merge_input_edit);
|
||||
std::wstring cur;
|
||||
if (n > 0) {
|
||||
cur.assign(static_cast<size_t>(n) + 1, L'\0');
|
||||
GetWindowTextW(g_merge_input_edit, cur.data(), n + 1);
|
||||
cur.resize(static_cast<size_t>(n));
|
||||
}
|
||||
std::wstring add = utf8_to_wide(payload);
|
||||
if (!cur.empty() && !add.empty())
|
||||
cur += L';';
|
||||
cur += add;
|
||||
SetWindowTextW(g_merge_input_edit, cur.c_str());
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
return DefWindowProcW(hwnd, msg, wp, lp);
|
||||
}
|
||||
|
||||
bool register_bridge_class_once() {
|
||||
static bool tried = false;
|
||||
if (tried)
|
||||
return true;
|
||||
tried = true;
|
||||
WNDCLASSEXW wc{};
|
||||
wc.cbSize = sizeof(wc);
|
||||
wc.lpfnWndProc = bridge_wnd_proc;
|
||||
wc.hInstance = GetModuleHandleW(nullptr);
|
||||
wc.lpszClassName = kBridgeClass;
|
||||
if (!RegisterClassExW(&wc)) {
|
||||
if (GetLastError() != ERROR_CLASS_ALREADY_EXISTS)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool try_acquire_ui_singleton_mutex() {
|
||||
g_mutex = CreateMutexW(nullptr, TRUE, L"Local\\MediaImgResizeUi_v1");
|
||||
if (!g_mutex)
|
||||
return false;
|
||||
if (GetLastError() == ERROR_ALREADY_EXISTS) {
|
||||
CloseHandle(g_mutex);
|
||||
g_mutex = nullptr;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void release_ui_singleton_mutex() {
|
||||
if (!g_mutex)
|
||||
return;
|
||||
ReleaseMutex(g_mutex);
|
||||
CloseHandle(g_mutex);
|
||||
g_mutex = nullptr;
|
||||
}
|
||||
|
||||
bool create_ui_singleton_bridge() {
|
||||
if (g_bridge_hwnd)
|
||||
return true;
|
||||
if (!register_bridge_class_once())
|
||||
return false;
|
||||
#ifndef HWND_MESSAGE
|
||||
#define HWND_MESSAGE ((HWND)(-3))
|
||||
#endif
|
||||
g_bridge_hwnd =
|
||||
CreateWindowExW(0, kBridgeClass, kBridgeTitle, 0, 0, 0, 0, 0, HWND_MESSAGE, nullptr, GetModuleHandleW(nullptr),
|
||||
nullptr);
|
||||
return g_bridge_hwnd != nullptr;
|
||||
}
|
||||
|
||||
void destroy_ui_singleton_bridge() {
|
||||
if (g_bridge_hwnd) {
|
||||
DestroyWindow(g_bridge_hwnd);
|
||||
g_bridge_hwnd = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void set_ui_merge_input_edit(HWND h) {
|
||||
g_merge_input_edit = h;
|
||||
}
|
||||
|
||||
void clear_ui_merge_input_edit() {
|
||||
g_merge_input_edit = nullptr;
|
||||
}
|
||||
|
||||
bool forward_resize_ui_paths_to_primary(const std::string &utf8_paths) {
|
||||
HWND bridge = nullptr;
|
||||
for (int i = 0; i < 80 && !bridge; ++i) {
|
||||
bridge = FindWindowW(kBridgeClass, kBridgeTitle);
|
||||
if (!bridge)
|
||||
Sleep(25);
|
||||
}
|
||||
if (!bridge)
|
||||
return false;
|
||||
|
||||
std::vector<char> buf;
|
||||
buf.reserve(utf8_paths.size() + 1);
|
||||
buf.assign(utf8_paths.begin(), utf8_paths.end());
|
||||
buf.push_back('\0');
|
||||
|
||||
COPYDATASTRUCT cds{};
|
||||
cds.dwData = 1;
|
||||
cds.cbData = static_cast<DWORD>(buf.size());
|
||||
cds.lpData = buf.data();
|
||||
|
||||
DWORD_PTR result = 0;
|
||||
const LRESULT r =
|
||||
SendMessageTimeoutW(bridge, WM_COPYDATA, 0, reinterpret_cast<LPARAM>(&cds), SMTO_ABORTIFHUNG, 15000, &result);
|
||||
return r != 0;
|
||||
}
|
||||
|
||||
} // namespace media::win
|
||||
32
packages/media/cpp/src/win/ui_singleton.hpp
Normal file
32
packages/media/cpp/src/win/ui_singleton.hpp
Normal file
@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
|
||||
namespace media::win {
|
||||
|
||||
/** Create named mutex; returns true if this process is the primary UI instance. */
|
||||
bool try_acquire_ui_singleton_mutex();
|
||||
|
||||
/** Release mutex from try_acquire_ui_singleton_mutex (primary only). */
|
||||
void release_ui_singleton_mutex();
|
||||
|
||||
/** Message-only window that receives WM_COPYDATA from secondary instances. */
|
||||
bool create_ui_singleton_bridge();
|
||||
void destroy_ui_singleton_bridge();
|
||||
|
||||
/** Target edit control for merging forwarded paths (resize dialog input row). */
|
||||
void set_ui_merge_input_edit(HWND h);
|
||||
void clear_ui_merge_input_edit();
|
||||
|
||||
/**
|
||||
* Find the bridge window and send UTF-8 paths (semicolon-separated) to the primary instance.
|
||||
* Retries briefly to avoid races right after mutex creation.
|
||||
*/
|
||||
bool forward_resize_ui_paths_to_primary(const std::string &utf8_paths);
|
||||
|
||||
} // namespace media::win
|
||||
Loading…
Reference in New Issue
Block a user