Skip to content

Instantly share code, notes, and snippets.

@JoshuaJakowlew
Created October 7, 2024 12:15
Show Gist options
  • Save JoshuaJakowlew/319f6da495c604b70a3460ca36e698d1 to your computer and use it in GitHub Desktop.
Save JoshuaJakowlew/319f6da495c604b70a3460ca36e698d1 to your computer and use it in GitHub Desktop.
namespace {
std::string clipboard_file_format()
{
enum class DE {
Gnome,
Mate,
Fly,
Unknown
};
static auto formats = std::unordered_map<DE, std::string>{
{DE::Gnome, "x-special/gnome-copied-files"},
{DE::Mate, "x-special/mate-copied-files"},
{DE::Fly, "x-special/gnome-copied-files"},
{DE::Unknown, "text/uri-list"}
};
static std::string env_de = []() -> std::string {
auto env = std::getenv("XDG_CURRENT_DESKTOP");
if (!env) {
spdlog::error("cannot get value of XDG_CURRENT_DESKTOP envvar, clipboard file format is unknown");
return "";
}
return env;
}();
const DE current_de = []{
if (env_de.find("GNOME") != std::string::npos) return DE::Gnome;
if (env_de.find("MATE" ) != std::string::npos) return DE::Mate;
if (env_de.find("fly" ) != std::string::npos) return DE::Fly;
return DE::Unknown;
}();
spdlog::info(
"clipboard: current DE index is {} (from '{}'), file format is {}",
static_cast<int>(current_de),
env_de,
formats[current_de]
);
return formats[current_de];
}
}
X11Clipboard::X11Clipboard()
{
std::lock_guard lock{m_lock};
m_file_format = clip::register_format(clipboard_file_format());
clip::set_x11_wait_timeout(1000 * 60 * 10);
}
bool X11Clipboard::pushFile(fs::path const & file)
{
std::lock_guard lock{m_lock};
const auto uri = fileUri(file);
m_clip.clear();
const auto res = m_clip.set_data(m_file_format, uri.data(), uri.size());
spdlog::info("push {} to clipboard: {}", uri, res);
return res;
}
bool X11Clipboard::pushFiles(path_list const & file)
{
std::lock_guard lock{m_lock};
const auto uris = fileUris(file);
m_clip.clear();
const auto res = m_clip.set_data(m_file_format, uris.data(), uris.size());
spdlog::info("push {} to clipboard: {}", uris, res);
return res;
}
auto X11Clipboard::extractFiles() -> path_list
{
std::lock_guard lock{m_lock};
if (!m_clip.is_convertible(m_file_format)) {
spdlog::info("no such format in clipboard");
return {};
}
const auto size = m_clip.get_data_length(m_file_format);
if (size == 0) {
spdlog::info("no files in clipboard");
return {};
}
std::string buffer(size, '\0');
if (!m_clip.get_data(m_file_format, buffer.data(), size)) {
spdlog::error("failed to get data from clipboard");
return {};
}
return parseFileUri(buffer);
}
std::string X11Clipboard::fileUri(fs::path const & file)
{
return fmt::format("copy\nfile://{}\0", file.string());
}
std::string X11Clipboard::fileUris(path_list const & files)
{
auto uris = files | views::transform([](fs::path const & p){ return fmt::format("file://{}", p.string()); });
return fmt::format("copy\n{}\0", fmt::join(uris, "\n"));
}
auto X11Clipboard::parseFileUri(std::string const & uri) -> path_list
{
const std::string prefix = "file://";
std::istringstream stream(uri);
path_list files;
std::string line;
std::getline(stream, line, '\n'); // skip the first entry ("copy")
while (std::getline(stream, line, '\n')) {
if (line.find(prefix) == 0) { // Remove the "file://" prefix
line = line.substr(prefix.length());
}
spdlog::info("parsed file at {}", line);
files.emplace_back(std::move(line));
}
return files;
}
class IClipboard
{
public:
using path_list = std::vector<fs::path>;
virtual ~IClipboard() = default;
virtual bool pushFile(fs::path const & file) = 0;
virtual bool pushFiles(path_list const & file) = 0;
virtual path_list extractFiles() = 0;
};
class X11Clipboard : public IClipboard
{
public:
X11Clipboard();
bool pushFile(fs::path const & file) override;
bool pushFiles(path_list const & file) override;
path_list extractFiles() override;
private:
clip::format m_file_format;
clip::lock m_clip;
std::mutex m_lock;
static std::string fileUri(fs::path const & file);
static std::string fileUris(path_list const & files);
static path_list parseFileUri(std::string const & uri);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment