rework mime types, support pasting file lists, SIP when dropping single files
Some checks failed
ContinuousDelivery / linux-ubuntu (push) Failing after 5m18s
ContinuousDelivery / android (map[ndk_abi:arm64-v8a vcpkg_toolkit:arm64-android]) (push) Failing after 6m48s
ContinuousDelivery / android (map[ndk_abi:x86_64 vcpkg_toolkit:x64-android]) (push) Failing after 6m37s
ContinuousIntegration / linux (push) Successful in 4m29s
ContinuousIntegration / android (map[ndk_abi:arm64-v8a vcpkg_toolkit:arm64-android]) (push) Failing after 6m16s
ContinuousIntegration / android (map[ndk_abi:x86_64 vcpkg_toolkit:x64-android]) (push) Failing after 6m6s
ContinuousDelivery / windows (push) Has been cancelled
ContinuousDelivery / windows-asan (push) Has been cancelled
ContinuousIntegration / macos (push) Has been cancelled
ContinuousIntegration / windows (push) Has been cancelled
ContinuousDelivery / release (push) Has been cancelled
Some checks failed
ContinuousDelivery / linux-ubuntu (push) Failing after 5m18s
ContinuousDelivery / android (map[ndk_abi:arm64-v8a vcpkg_toolkit:arm64-android]) (push) Failing after 6m48s
ContinuousDelivery / android (map[ndk_abi:x86_64 vcpkg_toolkit:x64-android]) (push) Failing after 6m37s
ContinuousIntegration / linux (push) Successful in 4m29s
ContinuousIntegration / android (map[ndk_abi:arm64-v8a vcpkg_toolkit:arm64-android]) (push) Failing after 6m16s
ContinuousIntegration / android (map[ndk_abi:x86_64 vcpkg_toolkit:x64-android]) (push) Failing after 6m6s
ContinuousDelivery / windows (push) Has been cancelled
ContinuousDelivery / windows-asan (push) Has been cancelled
ContinuousIntegration / macos (push) Has been cancelled
ContinuousIntegration / windows (push) Has been cancelled
ContinuousDelivery / release (push) Has been cancelled
This commit is contained in:
parent
73afcfaaeb
commit
a3c9be2348
@ -6,6 +6,9 @@
|
||||
#include "../image_loader_qoi.hpp"
|
||||
#include "../image_loader_sdl_image.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <solanaceae/file/file2_std.hpp>
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
// fwd
|
||||
@ -163,7 +166,51 @@ void SendImagePopup::sendMemory(
|
||||
|
||||
_on_send = std::move(on_send);
|
||||
_on_cancel = std::move(on_cancel);
|
||||
}
|
||||
|
||||
bool SendImagePopup::sendFilePath( // file2 instead?
|
||||
std::string_view file_path,
|
||||
std::function<void(const std::vector<uint8_t>&, std::string_view)>&& on_send,
|
||||
std::function<void(void)>&& on_cancel
|
||||
) {
|
||||
original_raw = false;
|
||||
if (file_path.empty() || !std::filesystem::exists(file_path)) {
|
||||
return false; // error
|
||||
}
|
||||
|
||||
std::filesystem::path path_o{file_path};
|
||||
|
||||
const auto file_size = std::filesystem::file_size(path_o);
|
||||
if (file_size <= 0 || file_size > 100*1024*1024) { // limit to 100mib for now
|
||||
return false; // error
|
||||
}
|
||||
|
||||
File2RFile file2{file_path};
|
||||
if (!file2.isGood() || !file2.can_read) {
|
||||
std::cerr << "filed to read file '" << file_path << "'\n";
|
||||
}
|
||||
|
||||
{ // copy paste data to memory
|
||||
// inefficent
|
||||
const auto data = file2.read(file_size, 0);
|
||||
original_data = {data.ptr, data.ptr+data.size};
|
||||
}
|
||||
|
||||
if (path_o.has_extension()) {
|
||||
original_file_ext = path_o.extension().u8string();
|
||||
}
|
||||
|
||||
if (!load()) {
|
||||
std::cerr << "SIP: failed to load image from file '" << file_path << "'\n";
|
||||
reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
_open_popup = true;
|
||||
|
||||
_on_send = std::move(on_send);
|
||||
_on_cancel = std::move(on_cancel);
|
||||
return true;
|
||||
}
|
||||
|
||||
void SendImagePopup::render(float time_delta) {
|
||||
|
@ -68,7 +68,12 @@ struct SendImagePopup {
|
||||
std::function<void(void)>&& on_cancel
|
||||
);
|
||||
// from memory_raw
|
||||
// from file_path
|
||||
|
||||
bool sendFilePath( // file2 instead?
|
||||
std::string_view file_path,
|
||||
std::function<void(const std::vector<uint8_t>&, std::string_view)>&& on_send,
|
||||
std::function<void(void)>&& on_cancel
|
||||
);
|
||||
|
||||
// call this each frame
|
||||
void render(float time_delta);
|
||||
|
@ -784,12 +784,14 @@ float ChatGui4::render(float time_delta) {
|
||||
//const auto* mime_type = clipboardHasImage();
|
||||
//ImGui::BeginDisabled(mime_type == nullptr);
|
||||
if (ImGui::Button("paste\nfile", {-FLT_MIN, 0})) {
|
||||
const auto* mime_type = clipboardHasImage();
|
||||
if (mime_type != nullptr) { // making sure
|
||||
pasteFile(mime_type);
|
||||
if (const auto* imt = clipboardHasImage(); imt != nullptr) { // making sure
|
||||
pasteFile(imt);
|
||||
} else if (const auto* fpmt = clipboardHasFileList(); fpmt != nullptr) {
|
||||
pasteFile(fpmt);
|
||||
}
|
||||
//} else if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
|
||||
} else if (ImGui::BeginPopupContextItem(nullptr, ImGuiMouseButton_Right)) {
|
||||
// TODO: use list instead
|
||||
const static std::vector<const char*> image_mime_types {
|
||||
// add apng?
|
||||
"image/png",
|
||||
@ -833,7 +835,7 @@ float ChatGui4::render(float time_delta) {
|
||||
return 1000.f; // TODO: higher min fps?
|
||||
}
|
||||
|
||||
void ChatGui4::sendFilePath(const char* file_path) {
|
||||
void ChatGui4::sendFilePath(std::string_view file_path) {
|
||||
const auto path = std::filesystem::path(file_path);
|
||||
if (_selected_contact && std::filesystem::is_regular_file(path)) {
|
||||
_rmm.sendFilePath(*_selected_contact, path.filename().generic_u8string(), path.generic_u8string());
|
||||
@ -1338,44 +1340,163 @@ bool ChatGui4::renderContactListContactSmall(const Contact3 c, const bool select
|
||||
}
|
||||
|
||||
void ChatGui4::pasteFile(const char* mime_type) {
|
||||
size_t data_size = 0;
|
||||
void* data = SDL_GetClipboardData(mime_type, &data_size);
|
||||
if (!_selected_contact.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if image
|
||||
if (mimeIsImage(mime_type)) {
|
||||
size_t data_size = 0;
|
||||
void* data = SDL_GetClipboardData(mime_type, &data_size);
|
||||
|
||||
std::cout << "CG: pasted image of size " << data_size << " mime " << mime_type << "\n";
|
||||
std::cout << "CG: pasted image of size " << data_size << " mime " << mime_type << "\n";
|
||||
|
||||
_sip.sendMemory(
|
||||
static_cast<const uint8_t*>(data), data_size,
|
||||
[this](const auto& img_data, const auto file_ext) {
|
||||
// create file name
|
||||
// TODO: move this into sip
|
||||
std::ostringstream tmp_file_name {"tomato_Image_", std::ios_base::ate};
|
||||
{
|
||||
const auto now = std::chrono::system_clock::now();
|
||||
const auto ctime = std::chrono::system_clock::to_time_t(now);
|
||||
tmp_file_name
|
||||
<< std::put_time(std::localtime(&ctime), "%F_%H-%M-%S")
|
||||
<< "."
|
||||
<< std::setfill('0') << std::setw(3)
|
||||
<< std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch() - std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch())).count()
|
||||
<< file_ext
|
||||
;
|
||||
_sip.sendMemory(
|
||||
static_cast<const uint8_t*>(data), data_size,
|
||||
[this](const auto& img_data, const auto file_ext) {
|
||||
// create file name
|
||||
// TODO: move this into sip
|
||||
std::ostringstream tmp_file_name {"tomato_Image_", std::ios_base::ate};
|
||||
{
|
||||
const auto now = std::chrono::system_clock::now();
|
||||
const auto ctime = std::chrono::system_clock::to_time_t(now);
|
||||
tmp_file_name
|
||||
<< std::put_time(std::localtime(&ctime), "%F_%H-%M-%S")
|
||||
<< "."
|
||||
<< std::setfill('0') << std::setw(3)
|
||||
<< std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch() - std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch())).count()
|
||||
<< file_ext
|
||||
;
|
||||
}
|
||||
|
||||
std::cout << "tmp image path " << tmp_file_name.str() << "\n";
|
||||
|
||||
const std::filesystem::path tmp_send_file_path = "tmp_send_files";
|
||||
std::filesystem::create_directories(tmp_send_file_path);
|
||||
const auto tmp_file_path = tmp_send_file_path / tmp_file_name.str();
|
||||
|
||||
std::ofstream(tmp_file_path, std::ios_base::out | std::ios_base::binary)
|
||||
.write(reinterpret_cast<const char*>(img_data.data()), img_data.size());
|
||||
|
||||
_rmm.sendFilePath(*_selected_contact, tmp_file_name.str(), tmp_file_path.generic_u8string());
|
||||
},
|
||||
[](){}
|
||||
);
|
||||
SDL_free(data); // free data
|
||||
} else if (mimeIsFileList(mime_type)) {
|
||||
size_t data_size = 0;
|
||||
void* data = SDL_GetClipboardData(mime_type, &data_size);
|
||||
|
||||
std::cout << "CG: pasted file list of size " << data_size << " mime " << mime_type << "\n";
|
||||
|
||||
std::vector<std::string_view> list;
|
||||
if (mime_type == std::string_view{"text/uri-list"}) {
|
||||
// lines starting with # are comments
|
||||
// every line is a link
|
||||
// line sep is CRLF
|
||||
std::string_view list_body{reinterpret_cast<const char*>(data), data_size};
|
||||
size_t start {0};
|
||||
bool comment {false};
|
||||
for (size_t i = 0; i < data_size; i++) {
|
||||
if (list_body[i] == '\r' || list_body[i] == '\n') {
|
||||
if (!comment && i - start > 0) {
|
||||
list.push_back(list_body.substr(start, i - start));
|
||||
}
|
||||
start = i+1;
|
||||
comment = false;
|
||||
} else if (i == start && list_body[i] == '#') {
|
||||
comment = true;
|
||||
}
|
||||
}
|
||||
if (!comment && start+1 < data_size) {
|
||||
list.push_back(list_body.substr(start));
|
||||
}
|
||||
} else if (mime_type == std::string_view{"text/x-moz-url"}) {
|
||||
assert(false && "implement me");
|
||||
// every link line is followed by link-title line (for the prev link)
|
||||
// line sep unclear ("text/*" should always be CRLF, but its not explicitly specified)
|
||||
// (does not matter, we can account for both)
|
||||
}
|
||||
|
||||
// TODO: remove debug log
|
||||
std::cout << "preprocessing:\n";
|
||||
for (const auto it : list) {
|
||||
std::cout << " >" << it << "\n";
|
||||
}
|
||||
|
||||
// now we need to potentially convert file uris to file paths
|
||||
|
||||
for (auto it = list.begin(); it != list.end();) {
|
||||
constexpr auto size_of_file_uri_prefix = std::string_view{"file://"}.size();
|
||||
if (it->size() > size_of_file_uri_prefix && it->substr(0, size_of_file_uri_prefix) == std::string_view{"file://"}) {
|
||||
it->remove_prefix(size_of_file_uri_prefix);
|
||||
}
|
||||
|
||||
std::cout << "tmp image path " << tmp_file_name.str() << "\n";
|
||||
std::filesystem::path path(*it);
|
||||
if (!std::filesystem::is_regular_file(path)) {
|
||||
it = list.erase(it);
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
|
||||
const std::filesystem::path tmp_send_file_path = "tmp_send_files";
|
||||
std::filesystem::create_directories(tmp_send_file_path);
|
||||
const auto tmp_file_path = tmp_send_file_path / tmp_file_name.str();
|
||||
std::cout << "postprocessing:\n";
|
||||
for (const auto it : list) {
|
||||
std::cout << " >" << it << "\n";
|
||||
}
|
||||
|
||||
std::ofstream(tmp_file_path, std::ios_base::out | std::ios_base::binary)
|
||||
.write(reinterpret_cast<const char*>(img_data.data()), img_data.size());
|
||||
sendFileList(list);
|
||||
|
||||
_rmm.sendFilePath(*_selected_contact, tmp_file_name.str(), tmp_file_path.generic_u8string());
|
||||
},
|
||||
[](){}
|
||||
);
|
||||
SDL_free(data); // free data
|
||||
SDL_free(data); // free data
|
||||
}
|
||||
}
|
||||
|
||||
void ChatGui4::sendFileList(const std::vector<std::string_view>& list) {
|
||||
// TODO: file collection sip
|
||||
if (list.size() > 1) {
|
||||
for (const auto it : list) {
|
||||
sendFilePath(it);
|
||||
}
|
||||
} else if (list.size() == 1) {
|
||||
const auto path = std::filesystem::path(list.front());
|
||||
if (std::filesystem::is_regular_file(path)) {
|
||||
if (!_sip.sendFilePath(
|
||||
list.front(),
|
||||
[this](const auto& img_data, const auto file_ext) {
|
||||
// create file name
|
||||
// TODO: only create file if changed or from memory
|
||||
// TODO: move this into sip
|
||||
std::ostringstream tmp_file_name {"tomato_Image_", std::ios_base::ate};
|
||||
{
|
||||
const auto now = std::chrono::system_clock::now();
|
||||
const auto ctime = std::chrono::system_clock::to_time_t(now);
|
||||
tmp_file_name
|
||||
<< std::put_time(std::localtime(&ctime), "%F_%H-%M-%S")
|
||||
<< "."
|
||||
<< std::setfill('0') << std::setw(3)
|
||||
<< std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch() - std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch())).count()
|
||||
<< file_ext
|
||||
;
|
||||
}
|
||||
|
||||
std::cout << "tmp image path " << tmp_file_name.str() << "\n";
|
||||
|
||||
const std::filesystem::path tmp_send_file_path = "tmp_send_files";
|
||||
std::filesystem::create_directories(tmp_send_file_path);
|
||||
const auto tmp_file_path = tmp_send_file_path / tmp_file_name.str();
|
||||
|
||||
std::ofstream(tmp_file_path, std::ios_base::out | std::ios_base::binary)
|
||||
.write(reinterpret_cast<const char*>(img_data.data()), img_data.size());
|
||||
|
||||
_rmm.sendFilePath(*_selected_contact, tmp_file_name.str(), tmp_file_path.generic_u8string());
|
||||
},
|
||||
[](){}
|
||||
)) {
|
||||
// if sip fails to open the file
|
||||
sendFilePath(list.front());
|
||||
}
|
||||
} else {
|
||||
// if not file (???)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -73,7 +73,8 @@ class ChatGui4 {
|
||||
float render(float time_delta);
|
||||
|
||||
public:
|
||||
void sendFilePath(const char* file_path);
|
||||
void sendFilePath(std::string_view file_path);
|
||||
void sendFileList(const std::vector<std::string_view>& list);
|
||||
|
||||
private:
|
||||
void renderMessageBodyText(Message3Registry& reg, const Message3 e);
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
#include <memory>
|
||||
#include <cmath>
|
||||
#include <string_view>
|
||||
|
||||
MainScreen::MainScreen(SimpleConfigModel&& conf_, SDL_Renderer* renderer_, Theme& theme_, std::string save_path, std::string save_password, std::string new_username, std::vector<std::string> plugins) :
|
||||
renderer(renderer_),
|
||||
@ -143,10 +144,11 @@ MainScreen::~MainScreen(void) {
|
||||
bool MainScreen::handleEvent(SDL_Event& e) {
|
||||
if (e.type == SDL_EVENT_DROP_FILE) {
|
||||
std::cout << "DROP FILE: " << e.drop.data << "\n";
|
||||
cg.sendFilePath(e.drop.data);
|
||||
_dopped_files.emplace_back(e.drop.data);
|
||||
//cg.sendFilePath(e.drop.data);
|
||||
_render_interval = 1.f/60.f; // TODO: magic
|
||||
_time_since_event = 0.f;
|
||||
return true; // TODO: forward return succ from sendFilePath()
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
@ -482,6 +484,17 @@ Screen* MainScreen::tick(float time_delta, bool& quit) {
|
||||
|
||||
mts.iterate(); // compute (after mfs)
|
||||
|
||||
if (!_dopped_files.empty()) {
|
||||
std::vector<std::string_view> tmp_view;
|
||||
for (const std::string& it : _dopped_files) {
|
||||
tmp_view.push_back(std::string_view{it});
|
||||
}
|
||||
|
||||
cg.sendFileList(tmp_view);
|
||||
|
||||
_dopped_files.clear();
|
||||
}
|
||||
|
||||
_min_tick_interval = std::min<float>(
|
||||
// HACK: pow by 1.6 to increase 50 -> ~500 (~522)
|
||||
// and it does not change 1
|
||||
|
@ -100,6 +100,8 @@ struct MainScreen final : public Screen {
|
||||
uint64_t _window_hidden_ts {0};
|
||||
float _time_since_event {0.f};
|
||||
|
||||
std::vector<std::string> _dopped_files;
|
||||
|
||||
MainScreen(SimpleConfigModel&& conf_, SDL_Renderer* renderer_, Theme& theme_, std::string save_path, std::string save_password, std::string new_username, std::vector<std::string> plugins);
|
||||
~MainScreen(void);
|
||||
|
||||
|
@ -2,22 +2,67 @@
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
const char* clipboardHasImage(void) {
|
||||
const static std::vector<const char*> image_mime_types {
|
||||
"image/png",
|
||||
"image/webp",
|
||||
"image/gif",
|
||||
"image/jpeg",
|
||||
"image/bmp",
|
||||
};
|
||||
|
||||
for (const char* mime_type : image_mime_types) {
|
||||
if (SDL_HasClipboardData(mime_type) == SDL_TRUE) {
|
||||
return mime_type;
|
||||
static const char* clipboardHas(const std::vector<std::string_view>& filter_mime_types) {
|
||||
for (const auto& mime_type : filter_mime_types) {
|
||||
// ASSERTS that stringview is null terminated
|
||||
if (SDL_HasClipboardData(mime_type.data()) == SDL_TRUE) {
|
||||
return mime_type.data();
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const static std::vector<std::string_view> image_mime_types {
|
||||
"image/svg+xml",
|
||||
"image/jxl",
|
||||
"image/avif",
|
||||
"image/apng",
|
||||
"image/webp",
|
||||
"image/png",
|
||||
"image/gif",
|
||||
"image/jpeg",
|
||||
"image/qoi",
|
||||
"image/bmp",
|
||||
// tiff?
|
||||
};
|
||||
|
||||
const static std::vector<std::string_view> file_list_mime_types {
|
||||
"text/uri-list",
|
||||
"text/x-moz-url",
|
||||
};
|
||||
|
||||
const char* clipboardHasImage(void) {
|
||||
return clipboardHas(image_mime_types);
|
||||
}
|
||||
|
||||
const char* clipboardHasFileList(void) {
|
||||
return clipboardHas(file_list_mime_types);
|
||||
}
|
||||
|
||||
bool mimeIsImage(const char* mime_type) {
|
||||
if (mime_type == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string_view mt_sv {mime_type};
|
||||
auto it = std::find(image_mime_types.cbegin(), image_mime_types.cend(), mime_type);
|
||||
|
||||
return it != image_mime_types.cend();
|
||||
}
|
||||
|
||||
bool mimeIsFileList(const char* mime_type) {
|
||||
if (mime_type == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string_view mt_sv {mime_type};
|
||||
auto it = std::find(file_list_mime_types.cbegin(), file_list_mime_types.cend(), mime_type);
|
||||
|
||||
return it != image_mime_types.cend();
|
||||
}
|
||||
|
||||
|
@ -3,4 +3,10 @@
|
||||
// returns the mimetype (c-string) of the image in the clipboard
|
||||
// or nullptr, if there is none
|
||||
const char* clipboardHasImage(void);
|
||||
const char* clipboardHasFileList(void);
|
||||
|
||||
bool mimeIsImage(const char* mime_type);
|
||||
bool mimeIsFileList(const char* mime_type);
|
||||
|
||||
// TODO: add is file
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user