[vum/configmanager] make the clipboard commands configurable

pull/915/head
Timothy Stack 3 years ago
parent 4d16be7b6e
commit d239bb4594

@ -5,6 +5,11 @@ lnav v0.10.1:
* The ":write-raw-to" command now accepts a --view flag that specifies * The ":write-raw-to" command now accepts a --view flag that specifies
the source view for the data to write. For example, to write the the source view for the data to write. For example, to write the
results of a SQL query, you would pass "--view=db" to the command. results of a SQL query, you would pass "--view=db" to the command.
* The commands used to access the clipboard are now configured through
the "tuning" section of the configuration.
Interface changes:
* The xclip implementation for accessing the system clipboard now writes
to the "clipboard" selection instead of the "primary" selection.
Bug Fixes: Bug Fixes:
* The text "send-input" would show up on some terminals instead of * The text "send-input" would show up on some terminals instead of
ignoring the escape sequence. This control sequence was only ignoring the escape sequence. This control sequence was only

@ -133,6 +133,48 @@
} }
}, },
"additionalProperties": false "additionalProperties": false
},
"clipboard": {
"description": "Settings related to the clipboard",
"title": "/tuning/clipboard",
"type": "object",
"properties": {
"impls": {
"description": "Clipboard implementations",
"title": "/tuning/clipboard/impls",
"type": "object",
"patternProperties": {
"([\\w\\-]+)": {
"description": "Clipboard implementation",
"title": "/tuning/clipboard/impls/<clipboard_impl_name>",
"type": "object",
"properties": {
"test": {
"title": "/tuning/clipboard/impls/<clipboard_impl_name>/test",
"description": "The command that checks",
"type": "string",
"examples": [
"command -v pbcopy"
]
},
"general": {
"description": "Commands to work with the general clipboard",
"title": "/tuning/clipboard/impls/<clipboard_impl_name>/general",
"$ref": "#/definitions/clip-commands"
},
"find": {
"description": "Commands to work with the find clipboard",
"title": "/tuning/clipboard/impls/<clipboard_impl_name>/find",
"$ref": "#/definitions/clip-commands"
}
},
"additionalProperties": false
}
},
"additionalProperties": false
}
},
"additionalProperties": false
} }
}, },
"additionalProperties": false "additionalProperties": false
@ -512,6 +554,30 @@
}, },
"additionalProperties": false, "additionalProperties": false,
"definitions": { "definitions": {
"clip-commands": {
"title": "clip-commands",
"type": "object",
"$$target": "#/definitions/clip-commands",
"properties": {
"write": {
"title": "/write",
"description": "The command used to write to the clipboard",
"type": "string",
"examples": [
"pbcopy"
]
},
"read": {
"title": "/read",
"description": "The command used to read from the clipboard",
"type": "string",
"examples": [
"pbpaste"
]
}
},
"additionalProperties": false
},
"style": { "style": {
"title": "style", "title": "style",
"type": "object", "type": "object",

@ -444,6 +444,7 @@ add_library(diag STATIC
strong_int.hh strong_int.hh
string_attr_type.hh string_attr_type.hh
sysclip.hh sysclip.hh
sysclip.cfg.hh
term_extra.hh term_extra.hh
termios_guard.hh termios_guard.hh
text_format.hh text_format.hh

@ -257,6 +257,7 @@ noinst_HEADERS = \
string_attr_type.hh \ string_attr_type.hh \
strong_int.hh \ strong_int.hh \
sysclip.hh \ sysclip.hh \
sysclip.cfg.hh \
termios_guard.hh \ termios_guard.hh \
term_extra.hh \ term_extra.hh \
text_format.hh \ text_format.hh \

@ -930,7 +930,7 @@ static Result<string, string> com_save_to(exec_context &ec, string cmdline, vect
} }
} }
else if (split_args[0] == "/dev/clipboard") { else if (split_args[0] == "/dev/clipboard") {
toclose = outfile = open_clipboard(CT_GENERAL); toclose = outfile = sysclip::open(sysclip::type_t::GENERAL);
closer = pclose; closer = pclose;
if (!outfile) { if (!outfile) {
alerter::singleton().chime(); alerter::singleton().chime();
@ -1449,7 +1449,7 @@ static Result<string, string> com_redirect_to(exec_context &ec, string cmdline,
if (split_args[0] == "-") { if (split_args[0] == "-") {
ec.clear_output(); ec.clear_output();
} else if (split_args[0] == "/dev/clipboard") { } else if (split_args[0] == "/dev/clipboard") {
auto out = open_clipboard(CT_GENERAL); auto out = sysclip::open(sysclip::type_t::GENERAL);
if (!out) { if (!out) {
alerter::singleton().chime(); alerter::singleton().chime();
return ec.make_error("Unable to copy to clipboard. " return ec.make_error("Unable to copy to clipboard. "

@ -91,6 +91,10 @@ static auto tc = injector::bind<tailer::config>::to_instance(+[]() {
return &lnav_config.lc_tailer; return &lnav_config.lc_tailer;
}); });
static auto scc = injector::bind<sysclip::config>::to_instance(+[]() {
return &lnav_config.lc_sysclip;
});
bool check_experimental(const char *feature_name) bool check_experimental(const char *feature_name)
{ {
const char *env_value = getenv("LNAV_EXP"); const char *env_value = getenv("LNAV_EXP");
@ -973,6 +977,62 @@ static struct json_path_container remote_handlers = {
.with_children(ssh_handlers), .with_children(ssh_handlers),
}; };
static struct json_path_container sysclip_impl_cmd_handlers = json_path_container{
yajlpp::property_handler("write")
.with_synopsis("<command>")
.with_description("The command used to write to the clipboard")
.with_example("pbcopy")
.for_field(&sysclip::clip_commands::cc_write),
yajlpp::property_handler("read")
.with_synopsis("<command>")
.with_description("The command used to read from the clipboard")
.with_example("pbpaste")
.for_field(&sysclip::clip_commands::cc_read),
}
.with_definition_id("clip-commands");
static struct json_path_container sysclip_impl_handlers = {
yajlpp::property_handler("test")
.with_synopsis("<command>")
.with_description("The command that checks")
.with_example("command -v pbcopy")
.for_field(&sysclip::clipboard::c_test_command),
yajlpp::property_handler("general")
.with_description("Commands to work with the general clipboard")
.with_obj_provider<sysclip::clip_commands, sysclip::clipboard>([](const yajlpp_provider_context &ypc, sysclip::clipboard *root) {
return &root->c_general;
})
.with_children(sysclip_impl_cmd_handlers),
yajlpp::property_handler("find")
.with_description("Commands to work with the find clipboard")
.with_obj_provider<sysclip::clip_commands, sysclip::clipboard>([](const yajlpp_provider_context &ypc, sysclip::clipboard *root) {
return &root->c_find;
})
.with_children(sysclip_impl_cmd_handlers),
};
static struct json_path_container sysclip_impls_handlers = {
yajlpp::pattern_property_handler("(?<clipboard_impl_name>[\\w\\-]+)")
.with_synopsis("<name>")
.with_description("Clipboard implementation")
.with_obj_provider<sysclip::clipboard, _lnav_config>([](const yajlpp_provider_context &ypc, _lnav_config *root) {
auto &retval = root->lc_sysclip.c_clipboard_impls[ypc.ypc_extractor.get_substr("clipboard_impl_name")];
return &retval;
})
.with_path_provider<_lnav_config>([](struct _lnav_config *cfg, vector<string> &paths_out) {
for (const auto &iter : cfg->lc_sysclip.c_clipboard_impls) {
paths_out.emplace_back(iter.first);
}
})
.with_children(sysclip_impl_handlers),
};
static struct json_path_container sysclip_handlers = {
yajlpp::property_handler("impls")
.with_description("Clipboard implementations")
.with_children(sysclip_impls_handlers),
};
static struct json_path_container tuning_handlers = { static struct json_path_container tuning_handlers = {
yajlpp::property_handler("archive-manager") yajlpp::property_handler("archive-manager")
.with_description("Settings related to opening archive files") .with_description("Settings related to opening archive files")
@ -986,6 +1046,9 @@ static struct json_path_container tuning_handlers = {
yajlpp::property_handler("remote") yajlpp::property_handler("remote")
.with_description("Settings related to remote file support") .with_description("Settings related to remote file support")
.with_children(remote_handlers), .with_children(remote_handlers),
yajlpp::property_handler("clipboard")
.with_description("Settings related to the clipboard")
.with_children(sysclip_handlers),
}; };
static set<string> SUPPORTED_CONFIG_SCHEMAS = { static set<string> SUPPORTED_CONFIG_SCHEMAS = {

@ -50,6 +50,7 @@
#include "file_vtab.cfg.hh" #include "file_vtab.cfg.hh"
#include "logfile.cfg.hh" #include "logfile.cfg.hh"
#include "tailer/tailer.looper.cfg.hh" #include "tailer/tailer.looper.cfg.hh"
#include "sysclip.cfg.hh"
/** /**
* Check if an experimental feature should be enabled by * Check if an experimental feature should be enabled by
@ -99,6 +100,7 @@ struct _lnav_config {
file_vtab::config lc_file_vtab; file_vtab::config lc_file_vtab;
lnav::logfile::config lc_logfile; lnav::logfile::config lc_logfile;
tailer::config lc_tailer; tailer::config lc_tailer;
sysclip::config lc_sysclip;
}; };
extern struct _lnav_config lnav_config; extern struct _lnav_config lnav_config;

@ -603,7 +603,7 @@ static void rl_callback_int(readline_curses *rc, bool is_alt)
vis_line_t vl = is_alt ? bv.prev(tc->get_top()) : vis_line_t vl = is_alt ? bv.prev(tc->get_top()) :
bv.next(tc->get_top()); bv.next(tc->get_top());
pfile = open_clipboard(CT_FIND); pfile = sysclip::open(sysclip::type_t::FIND);
if (pfile.in() != nullptr) { if (pfile.in() != nullptr) {
fprintf(pfile, "%s", rc->get_value().c_str()); fprintf(pfile, "%s", rc->get_value().c_str());
} }

@ -22,6 +22,55 @@
"start-command": "bash -c ./{0:}", "start-command": "bash -c ./{0:}",
"transfer-command": "cat > {0:} && chmod ugo+rx ./{0:}" "transfer-command": "cat > {0:} && chmod ugo+rx ./{0:}"
} }
},
"clipboard": {
"impls": {
"MacOS": {
"test": "command -v pbcopy",
"general": {
"write": "pbcopy",
"read": "pbpaste -Prefer txt"
},
"find": {
"write": "pbcopy -pboard find",
"read": "pbpaste -pboard find -Prefer txt"
}
},
"Wayland": {
"test": "test -n \"$WAYLAND_DISPLAY\"",
"general": {
"write": "wl-copy --foreground --type text/plain",
"read": "wl-paste --no-newline"
}
},
"X11-xclip": {
"test": "test -n \"$DISPLAY\" && command -v xclip",
"general": {
"write": "xclip -i -selection clipboard",
"read": "xclip -o -selection clipboard"
}
},
"tmux": {
"test": "test -n \"$TMUX\"",
"general": {
"write": "tmux load-buffer -",
"read": "tmux save-buffer -"
}
},
"NeoVim": {
"test": "command -v win32yank.exe",
"general": {
"write": "win32yank.exe -i --crlf",
"read": "win32yank.exe -o --lf"
}
},
"Windows": {
"test": "command -v clip.exe",
"general": {
"write": "clip.exe"
}
}
}
} }
} }
} }

@ -33,97 +33,64 @@
#include <stdio.h> #include <stdio.h>
#include "base/injector.hh"
#include "base/lnav_log.hh" #include "base/lnav_log.hh"
#include "fmt/format.h"
#include "sysclip.hh" #include "sysclip.hh"
#include "sysclip.cfg.hh"
struct clip_command { namespace sysclip {
const char *cc_cmd[2];
};
static clip_command *get_commands() static nonstd::optional<clipboard> get_commands()
{ {
static clip_command NEOVIM_CMDS[] = { auto& cfg = injector::get<const config&>();
{ { "win32yank.exe -i --crlf > /dev/null 2>&1",
"win32yank.exe -o --lf < /dev/null 2>/dev/null" } },
{ { nullptr, nullptr } },
};
static clip_command OSX_CMDS[] = {
{ { "pbcopy > /dev/null 2>&1",
"pbpaste -Prefer txt 2>/dev/null", } },
{ { "pbcopy -pboard find > /dev/null 2>&1",
"pbpaste -pboard find -Prefer txt 2>/dev/null" } },
};
static clip_command TMUX_CMDS[] = {
{ { "tmux load-buffer - > /dev/null 2>&1",
"tmux save-buffer - < /dev/null 2>/dev/null" } },
{ { nullptr, nullptr } },
};
static clip_command WAYLAND_CMDS[] = {
{ { "wl-copy --foreground --type text/plain > /dev/null 2>&1",
"wl-paste --no-newline < /dev/null 2>/dev/null" } },
{ { nullptr, nullptr } },
};
static clip_command WINDOWS_CMDS[] = {
{ { "clip.exe > /dev/null 2>&1",
nullptr } },
{ { nullptr, nullptr } },
};
static clip_command XCLIP_CMDS[] = {
{ { "xclip -i > /dev/null 2>&1",
"xclip -o < /dev/null 2>/dev/null" } },
{ { nullptr, nullptr } },
};
static clip_command XSEL_CMDS[] = {
{ { "xsel --nodetach -i -b > /dev/null 2>&1",
"xclip -o -b < /dev/null 2>/dev/null" } },
{ { nullptr, nullptr } },
};
clip_command *retval = nullptr; for (const auto& pair : cfg.c_clipboard_impls) {
if (system("command -v pbcopy > /dev/null 2>&1") == 0) { const auto full_cmd = fmt::format("{} > /dev/null 2>&1",
retval = OSX_CMDS; pair.second.c_test_command);
} else if (getenv("WAYLAND_DISPLAY") != nullptr) {
retval = WAYLAND_CMDS;
} else if (getenv("DISPLAY") != nullptr && system("command -v xclip > /dev/null 2>&1") == 0) {
retval = XCLIP_CMDS;
} else if (getenv("DISPLAY") != nullptr && system("command -v xsel > /dev/null 2>&1") == 0) {
retval = XSEL_CMDS;
} else if (getenv("TMUX") != nullptr) {
retval = TMUX_CMDS;
} else if (system("command -v win32yank.exe > /dev/null 2>&1") == 0) {
/*
* NeoVim's win32yank command is bidirectional, whereas the system-supplied
* clip.exe is copy-only.
* xclip and clip.exe may coexist on Windows Subsystem for Linux
*/
retval = NEOVIM_CMDS;
} else if (system("command -v clip.exe > /dev/null 2>&1") == 0) {
retval = WINDOWS_CMDS;
} else {
log_error("unable to detect clipboard commands");
}
if (retval != nullptr) { log_debug("testing clipboard impl %s using: %s",
log_info("detected clipboard copy command: %s", retval[0].cc_cmd[0]); pair.first.c_str(), full_cmd.c_str());
log_info("detected clipboard paste command: %s", retval[0].cc_cmd[1]); if (system(full_cmd.c_str()) == 0) {
log_info("detected clipboard: %s", pair.first.c_str());
return pair.second;
}
} }
return retval; return nonstd::nullopt;
} }
/* XXX For one, this code is kinda crappy. For two, we should probably link /* XXX For one, this code is kinda crappy. For two, we should probably link
* directly with X so we don't need to have xclip installed and it'll work if * directly with X so we don't need to have xclip installed and it'll work if
* we're ssh'd into a box. * we're ssh'd into a box.
*/ */
FILE *open_clipboard(clip_type_t type, clip_op_t op) FILE *open(type_t type, op_t op)
{ {
const char *mode = op == CO_WRITE ? "w" : "r"; const char *mode = op == op_t::WRITE ? "w" : "r";
static clip_command *cc = get_commands(); static const auto clip_opt = sysclip::get_commands();
FILE *pfile = nullptr;
if (!clip_opt) {
log_error("unable to detect clipboard implementation");
return nullptr;
}
auto cmd = clip_opt.value().select(type).select(op);
if (cc != nullptr && cc[type].cc_cmd[op] != nullptr) { if (cmd.empty()) {
pfile = popen(cc[type].cc_cmd[op], mode); log_error("clipboard does not support type/op");
return nullptr;
} }
return pfile; switch (op) {
case op_t::WRITE:
cmd = fmt::format("{} > /dev/null 2>&1", cmd);
break;
case op_t::READ:
cmd = fmt::format("{} < /dev/null 2>/dev/null", cmd);
break;
}
return popen(cmd.c_str(), mode);
}
} }

@ -0,0 +1,77 @@
/**
* Copyright (c) 2021, Timothy Stack
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Timothy Stack nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @file sysclip.cfg.hh
*/
#ifndef lnav_sysclip_cfg_hh
#define lnav_sysclip_cfg_hh
#include <map>
#include <string>
#include "sysclip.hh"
namespace sysclip {
struct clip_commands {
std::string cc_write;
std::string cc_read;
std::string select(op_t op) const {
switch (op) {
case op_t::WRITE:
return this->cc_write;
case op_t::READ:
return this->cc_read;
}
}
};
struct clipboard {
std::string c_test_command;
clip_commands c_general;
clip_commands c_find;
const clip_commands& select(type_t t) const {
switch (t) {
case type_t::GENERAL:
return this->c_general;
case type_t::FIND:
return this->c_find;
}
}
};
struct config {
std::map<std::string, clipboard> c_clipboard_impls;
};
}
#endif

@ -32,18 +32,22 @@
#ifndef sysclip_hh #ifndef sysclip_hh
#define sysclip_hh #define sysclip_hh
#include <stdio.h> #include <cstdio>
enum clip_type_t { namespace sysclip {
CT_GENERAL,
CT_FIND, enum class type_t {
GENERAL,
FIND,
}; };
enum clip_op_t { enum class op_t {
CO_WRITE, WRITE,
CO_READ, READ,
}; };
FILE *open_clipboard(clip_type_t type, clip_op_t op = CO_WRITE); FILE *open(type_t type, op_t op = op_t::WRITE);
}
#endif #endif

Loading…
Cancel
Save