[vum/configmanager] make the clipboard commands configurable

This commit is contained in:
Timothy Stack 2021-09-15 21:29:55 -07:00
parent 4d16be7b6e
commit d239bb4594
12 changed files with 321 additions and 86 deletions

5
NEWS
View File

@ -5,6 +5,11 @@ lnav v0.10.1:
* 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
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:
* The text "send-input" would show up on some terminals instead of
ignoring the escape sequence. This control sequence was only

View File

@ -133,6 +133,48 @@
}
},
"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
@ -512,6 +554,30 @@
},
"additionalProperties": false,
"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": {
"title": "style",
"type": "object",

View File

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

View File

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

View File

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

View File

@ -91,6 +91,10 @@ static auto tc = injector::bind<tailer::config>::to_instance(+[]() {
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)
{
const char *env_value = getenv("LNAV_EXP");
@ -973,6 +977,62 @@ static struct json_path_container remote_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 = {
yajlpp::property_handler("archive-manager")
.with_description("Settings related to opening archive files")
@ -986,6 +1046,9 @@ static struct json_path_container tuning_handlers = {
yajlpp::property_handler("remote")
.with_description("Settings related to remote file support")
.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 = {

View File

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

View File

@ -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()) :
bv.next(tc->get_top());
pfile = open_clipboard(CT_FIND);
pfile = sysclip::open(sysclip::type_t::FIND);
if (pfile.in() != nullptr) {
fprintf(pfile, "%s", rc->get_value().c_str());
}

View File

@ -22,6 +22,55 @@
"start-command": "bash -c ./{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"
}
}
}
}
}
}

View File

@ -33,97 +33,64 @@
#include <stdio.h>
#include "base/injector.hh"
#include "base/lnav_log.hh"
#include "fmt/format.h"
#include "sysclip.hh"
#include "sysclip.cfg.hh"
struct clip_command {
const char *cc_cmd[2];
};
namespace sysclip {
static clip_command *get_commands()
static nonstd::optional<clipboard> get_commands()
{
static clip_command NEOVIM_CMDS[] = {
{ { "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 } },
};
auto& cfg = injector::get<const config&>();
clip_command *retval = nullptr;
if (system("command -v pbcopy > /dev/null 2>&1") == 0) {
retval = OSX_CMDS;
} 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");
for (const auto& pair : cfg.c_clipboard_impls) {
const auto full_cmd = fmt::format("{} > /dev/null 2>&1",
pair.second.c_test_command);
log_debug("testing clipboard impl %s using: %s",
pair.first.c_str(), full_cmd.c_str());
if (system(full_cmd.c_str()) == 0) {
log_info("detected clipboard: %s", pair.first.c_str());
return pair.second;
}
}
if (retval != nullptr) {
log_info("detected clipboard copy command: %s", retval[0].cc_cmd[0]);
log_info("detected clipboard paste command: %s", retval[0].cc_cmd[1]);
}
return retval;
return nonstd::nullopt;
}
/* 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
* 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";
static clip_command *cc = get_commands();
FILE *pfile = nullptr;
const char *mode = op == op_t::WRITE ? "w" : "r";
static const auto clip_opt = sysclip::get_commands();
if (cc != nullptr && cc[type].cc_cmd[op] != nullptr) {
pfile = popen(cc[type].cc_cmd[op], mode);
if (!clip_opt) {
log_error("unable to detect clipboard implementation");
return nullptr;
}
return pfile;
auto cmd = clip_opt.value().select(type).select(op);
if (cmd.empty()) {
log_error("clipboard does not support type/op");
return nullptr;
}
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);
}
}

77
src/sysclip.cfg.hh Normal file
View File

@ -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

View File

@ -32,18 +32,22 @@
#ifndef sysclip_hh
#define sysclip_hh
#include <stdio.h>
#include <cstdio>
enum clip_type_t {
CT_GENERAL,
CT_FIND,
namespace sysclip {
enum class type_t {
GENERAL,
FIND,
};
enum clip_op_t {
CO_WRITE,
CO_READ,
enum class op_t {
WRITE,
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