rework mime types, support pasting file lists, SIP when dropping single files

This commit is contained in:
Green Sky 2024-08-13 16:17:25 +02:00
parent 73afcfaaeb
commit a3c9be2348
No known key found for this signature in database
8 changed files with 291 additions and 51 deletions

View File

@ -6,6 +6,9 @@
#include "../image_loader_qoi.hpp" #include "../image_loader_qoi.hpp"
#include "../image_loader_sdl_image.hpp" #include "../image_loader_sdl_image.hpp"
#include <filesystem>
#include <solanaceae/file/file2_std.hpp>
#include <imgui/imgui.h> #include <imgui/imgui.h>
// fwd // fwd
@ -163,7 +166,51 @@ void SendImagePopup::sendMemory(
_on_send = std::move(on_send); _on_send = std::move(on_send);
_on_cancel = std::move(on_cancel); _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) { void SendImagePopup::render(float time_delta) {

View File

@ -68,7 +68,12 @@ struct SendImagePopup {
std::function<void(void)>&& on_cancel std::function<void(void)>&& on_cancel
); );
// from memory_raw // 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 // call this each frame
void render(float time_delta); void render(float time_delta);

View File

@ -784,12 +784,14 @@ float ChatGui4::render(float time_delta) {
//const auto* mime_type = clipboardHasImage(); //const auto* mime_type = clipboardHasImage();
//ImGui::BeginDisabled(mime_type == nullptr); //ImGui::BeginDisabled(mime_type == nullptr);
if (ImGui::Button("paste\nfile", {-FLT_MIN, 0})) { if (ImGui::Button("paste\nfile", {-FLT_MIN, 0})) {
const auto* mime_type = clipboardHasImage(); if (const auto* imt = clipboardHasImage(); imt != nullptr) { // making sure
if (mime_type != nullptr) { // making sure pasteFile(imt);
pasteFile(mime_type); } else if (const auto* fpmt = clipboardHasFileList(); fpmt != nullptr) {
pasteFile(fpmt);
} }
//} else if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { //} else if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
} else if (ImGui::BeginPopupContextItem(nullptr, ImGuiMouseButton_Right)) { } else if (ImGui::BeginPopupContextItem(nullptr, ImGuiMouseButton_Right)) {
// TODO: use list instead
const static std::vector<const char*> image_mime_types { const static std::vector<const char*> image_mime_types {
// add apng? // add apng?
"image/png", "image/png",
@ -833,7 +835,7 @@ float ChatGui4::render(float time_delta) {
return 1000.f; // TODO: higher min fps? 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); const auto path = std::filesystem::path(file_path);
if (_selected_contact && std::filesystem::is_regular_file(path)) { if (_selected_contact && std::filesystem::is_regular_file(path)) {
_rmm.sendFilePath(*_selected_contact, path.filename().generic_u8string(), path.generic_u8string()); _rmm.sendFilePath(*_selected_contact, path.filename().generic_u8string(), path.generic_u8string());
@ -1338,11 +1340,14 @@ bool ChatGui4::renderContactListContactSmall(const Contact3 c, const bool select
} }
void ChatGui4::pasteFile(const char* mime_type) { void ChatGui4::pasteFile(const char* mime_type) {
if (!_selected_contact.has_value()) {
return;
}
if (mimeIsImage(mime_type)) {
size_t data_size = 0; size_t data_size = 0;
void* data = SDL_GetClipboardData(mime_type, &data_size); void* data = SDL_GetClipboardData(mime_type, &data_size);
// if image
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( _sip.sendMemory(
@ -1377,5 +1382,121 @@ void ChatGui4::pasteFile(const char* mime_type) {
[](){} [](){}
); );
SDL_free(data); // free data 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::filesystem::path path(*it);
if (!std::filesystem::is_regular_file(path)) {
it = list.erase(it);
} else {
it++;
}
}
std::cout << "postprocessing:\n";
for (const auto it : list) {
std::cout << " >" << it << "\n";
}
sendFileList(list);
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 (???)
}
}
} }

View File

@ -73,7 +73,8 @@ class ChatGui4 {
float render(float time_delta); float render(float time_delta);
public: public:
void sendFilePath(const char* file_path); void sendFilePath(std::string_view file_path);
void sendFileList(const std::vector<std::string_view>& list);
private: private:
void renderMessageBodyText(Message3Registry& reg, const Message3 e); void renderMessageBodyText(Message3Registry& reg, const Message3 e);

View File

@ -11,6 +11,7 @@
#include <memory> #include <memory>
#include <cmath> #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) : 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_), renderer(renderer_),
@ -143,10 +144,11 @@ MainScreen::~MainScreen(void) {
bool MainScreen::handleEvent(SDL_Event& e) { bool MainScreen::handleEvent(SDL_Event& e) {
if (e.type == SDL_EVENT_DROP_FILE) { if (e.type == SDL_EVENT_DROP_FILE) {
std::cout << "DROP FILE: " << e.drop.data << "\n"; 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 _render_interval = 1.f/60.f; // TODO: magic
_time_since_event = 0.f; _time_since_event = 0.f;
return true; // TODO: forward return succ from sendFilePath() return true;
} }
if ( if (
@ -482,6 +484,17 @@ Screen* MainScreen::tick(float time_delta, bool& quit) {
mts.iterate(); // compute (after mfs) 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>( _min_tick_interval = std::min<float>(
// HACK: pow by 1.6 to increase 50 -> ~500 (~522) // HACK: pow by 1.6 to increase 50 -> ~500 (~522)
// and it does not change 1 // and it does not change 1

View File

@ -100,6 +100,8 @@ struct MainScreen final : public Screen {
uint64_t _window_hidden_ts {0}; uint64_t _window_hidden_ts {0};
float _time_since_event {0.f}; 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(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); ~MainScreen(void);

View File

@ -2,22 +2,67 @@
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include <string_view>
#include <vector> #include <vector>
#include <algorithm>
const char* clipboardHasImage(void) { static const char* clipboardHas(const std::vector<std::string_view>& filter_mime_types) {
const static std::vector<const char*> image_mime_types { for (const auto& mime_type : filter_mime_types) {
"image/png", // ASSERTS that stringview is null terminated
"image/webp", if (SDL_HasClipboardData(mime_type.data()) == SDL_TRUE) {
"image/gif", return mime_type.data();
"image/jpeg",
"image/bmp",
};
for (const char* mime_type : image_mime_types) {
if (SDL_HasClipboardData(mime_type) == SDL_TRUE) {
return mime_type;
} }
} }
return nullptr; 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();
}

View File

@ -3,4 +3,10 @@
// returns the mimetype (c-string) of the image in the clipboard // returns the mimetype (c-string) of the image in the clipboard
// or nullptr, if there is none // or nullptr, if there is none
const char* clipboardHasImage(void); const char* clipboardHasImage(void);
const char* clipboardHasFileList(void);
bool mimeIsImage(const char* mime_type);
bool mimeIsFileList(const char* mime_type);
// TODO: add is file