Skip to content

Commit

Permalink
Merge pull request #160 from Slackadays/temp1
Browse files Browse the repository at this point in the history
Fixes, add async Linux GUI clipboard
  • Loading branch information
Slackadays authored Sep 30, 2023
2 parents 8f8d4c5 + 84c9d02 commit 42b421c
Show file tree
Hide file tree
Showing 14 changed files with 205 additions and 23 deletions.
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ cmake_minimum_required(VERSION 3.16)
#make a universal binary on macOS
set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "" FORCE)

project(Clipboard LANGUAGES CXX C VERSION 0.8.1)
set(CMAKE_CXX_STANDARD 20)
project(Clipboard LANGUAGES CXX C VERSION 0.8.2)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED True)

if (UNIX AND NOT APPLE AND NOT HAIKU AND NOT ANDROID)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ $ alias cb='snap run clipboard'
### <img src="documentation/readme-assets/InstallManually.png" alt="Install Manually" height=25px />
You'll need CMake and C++20 support, and if you want X11 or Wayland support, you'll also need libx11 or libwayland plus Wayland Protocols respectively. If you're on Linux, you'll need ALSA.

Get the latest release instead of the latest commit by adding `--branch 0.8.1` right after `git clone...`.
Get the latest release instead of the latest commit by adding `--branch 0.8.2` right after `git clone...`.

Change the system installation prefix by adding `-DCMAKE_INSTALL_PREFIX=/custom/prefix` to `cmake ..`, or the library install location by adding `-DCMAKE_INSTALL_LIBDIR=/custom/dir`.
```bash
Expand Down
3 changes: 2 additions & 1 deletion app.getclipboard.Clipboard.metainfo.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
<screenshots>
<screenshot type="default">
<caption>An example of using Clipboard</caption>
<image type="source" width="1906" height="1010">https://raw.githubusercontent.com/Slackadays/Clipboard/0.8.1/documentation/readme-assets/CBDemo.png</image>
<image type="source" width="1906" height="1010">https://raw.githubusercontent.com/Slackadays/Clipboard/0.8.2/documentation/readme-assets/CBDemo.png</image>
</screenshot>
</screenshots>

Expand All @@ -44,6 +44,7 @@
<url type="contribute">https://github.com/Slackadays/Clipboard/blob/main/.github/CONTRIBUTING.md</url>

<releases>
<release version="0.8.2" date="2023-09-30" />
<release version="0.8.1" date="2023-07-08" />
<release version="0.8.0" date="2023-06-03" />
<release version="0.7.1" date="2023-05-10" />
Expand Down
33 changes: 33 additions & 0 deletions documentation/completions/cb.zsh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#compdef cb
local -a actions
actions=(
"cut:cut something"
"copy:copy something"
"paste:paste something"
"clear:clear clipboard"
"show:show clipboard content"
"edit:edit clipboard content"
"add:add something to clipboard"
"remove:remove something from clipboard"
"note:add note to clipboard"
"swap:swap clipboard content"
"status:show status"
"info:show clipboard info"
"load:load clipboard into other clipboard"
"import:import clipboard from file"
"export:export clipboard to file"
"history:show clipboard history"
"ignore:ignore content"
"search:search clipboard content"
"help:show help for CB"
)
# only put up to one action in front of the cb command
if [ "${#words[@]}" -eq 2 ]; then
_describe 'command' actions
return
fi
# now complete files
if [ "${words[2]}" = "cut" ] || [ "${words[2]}" = "ct" ] || [ "${words[2]}" = "copy" ] || [ "${words[2]}" = "cp" ] || [ "${words[2]}" = "add" ] || [ "${words[2]}" = "ad" ]; then
_files
return
fi
2 changes: 1 addition & 1 deletion snapcraft.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: clipboard
version: "0.8.1"
version: "0.8.2"
summary: The ultimate clipboard manager for the terminal
description: |
The Clipboard Project is one of the most advanced clipboard managers ever.
Expand Down
4 changes: 4 additions & 0 deletions src/cb/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -127,4 +127,8 @@ if(X11WL OR APPLE)
if(BASH)
install(FILES ${CMAKE_SOURCE_DIR}/documentation/completions/cb.bash DESTINATION share/bash-completion/completions RENAME cb)
endif()
find_program(ZSH zsh)
if(ZSH)
install(FILES ${CMAKE_SOURCE_DIR}/documentation/completions/cb.zsh DESTINATION share/zsh/site-functions RENAME _cb)
endif()
endif()
6 changes: 3 additions & 3 deletions src/cb/src/actions/history.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ void historyJSON() {
printf("{\n");
printf(" \"dataType\": \"%s\",\n", type.value().data());
printf(" \"dataSize\": %zd,\n", content.length());
printf(" \"path\": \"%s\"\n", path.data.raw.string().data());
printf(" \"path\": \"%s\"\n", JSONescape(path.data.raw.string()).data());
printf(" }");
} else {
printf("\"%s\"", JSONescape(content).data());
Expand All @@ -294,8 +294,8 @@ void historyJSON() {
std::vector<fs::path> itemsInPath(fs::directory_iterator(path.data), fs::directory_iterator());
for (const auto& entry : itemsInPath) {
printf(" {\n");
printf(" \"filename\": \"%s\",\n", entry.filename().string().data());
printf(" \"path\": \"%s\",\n", entry.string().data());
printf(" \"filename\": \"%s\",\n", JSONescape(entry.filename().string()).data());
printf(" \"path\": \"%s\",\n", JSONescape(entry.string()).data());
printf(" \"isDirectory\": %s\n", fs::is_directory(entry) ? "true" : "false");
printf(" }%s\n", entry == itemsInPath.back() ? "" : ",");
}
Expand Down
20 changes: 10 additions & 10 deletions src/cb/src/actions/info.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,16 +51,16 @@ void info() {

#if defined(__linux__) || defined(__APPLE__) || defined(__unix__) || defined(__FreeBSD__)
time_t latest = 0;
for (const auto& entry : fs::recursive_directory_iterator(path)) {
for (const auto& entry : fs::recursive_directory_iterator(path.data)) {
struct stat info;
stat(entry.path().string().data(), &info);
if (info.st_ctime > latest) latest = info.st_ctime;
}
time = std::ctime(&latest);
std::erase(time, '\n');
fprintf(stderr, formatColors("[info]%sโ”ƒ Last changed [help]%s[blank]\n").data(), generatedEndbar().data(), time.data());
fprintf(stderr, formatColors("[info]%sโ”ƒ Content last changed [help]%s[blank]\n").data(), generatedEndbar().data(), time.data());
#elif defined(_WIN32) || defined(_WIN64)
fprintf(stderr, formatColors("[info]โ”ƒ Last changed [help]%s[blank]\n").data(), std::format("{}", fs::last_write_time(path)).data());
fprintf(stderr, formatColors("[info]โ”ƒ Content last changed [help]%s[blank]\n").data(), std::format("{}", fs::last_write_time(path)).data());
#endif

fprintf(stderr, formatColors("[info]%sโ”ƒ Stored in [help]%s[blank]\n").data(), generatedEndbar().data(), path.string().data());
Expand Down Expand Up @@ -146,19 +146,19 @@ void infoJSON() {

#if defined(__linux__) || defined(__APPLE__) || defined(__unix__)
time_t latest = 0;
for (const auto& entry : fs::recursive_directory_iterator(path)) {
for (const auto& entry : fs::recursive_directory_iterator(path.data)) {
struct stat info;
stat(entry.path().string().data(), &info);
if (info.st_ctime > latest) latest = info.st_ctime;
}
time = std::ctime(&latest);
std::erase(time, '\n');
printf(" \"lastChanged\": \"%s\",\n", time.data());
printf(" \"contentLastChanged\": \"%s\",\n", time.data());
#elif defined(_WIN32) || defined(_WIN64)
printf(" \"lastChanged\": \"%s\",\n", std::format("{}", fs::last_write_time(path)).data());
printf(" \"contentLastChanged\": \"%s\",\n", std::format("{}", fs::last_write_time(path)).data());
#endif

printf(" \"path\": \"%s\",\n", path.string().data());
printf(" \"path\": \"%s\",\n", JSONescape(path.string()).data());

#if defined(__linux__) || defined(__APPLE__) || defined(__unix__) || defined(__FreeBSD__)
struct passwd* pw = getpwuid(getuid());
Expand Down Expand Up @@ -198,15 +198,15 @@ void infoJSON() {
if (path.isLocked()) printf(" \"lockedBy\": \"%s\",\n", fileContents(path.metadata.lock).value().data());

if (fs::exists(path.metadata.notes))
printf(" \"note\": \"%s\"\n", std::regex_replace(fileContents(path.metadata.notes).value(), std::regex("\""), "\\\"").data());
printf(" \"note\": \"%s\",\n", JSONescape(fileContents(path.metadata.notes).value()).data());
else
printf(" \"note\": \"\"\n");
printf(" \"note\": null,\n");

if (path.holdsIgnoreRegexes()) {
printf(" \"ignoreRegexes\": [");
auto regexes = fileLines(path.metadata.ignore);
for (const auto& regex : regexes)
printf("\"%s\"%s", std::regex_replace(regex, std::regex("\""), "\\\"").data(), regex != regexes.back() ? ", " : "");
printf("\"%s\"%s", JSONescape(regex).data(), regex != regexes.back() ? ", " : "");
printf("]\n");
} else {
printf(" \"ignoreRegexes\": []\n");
Expand Down
1 change: 1 addition & 0 deletions src/cb/src/actions/status.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ void status() {
else
std::erase(content, '\n');
fprintf(stderr, formatColors("[help]%s[blank]\n").data(), content.substr(0, widthRemaining).data());
clipboard.releaseLock();
continue;
}

Expand Down
6 changes: 5 additions & 1 deletion src/cb/src/clipboard.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,10 @@ std::string formatBytes(const auto& bytes) {
return formatNumbers(bytes / (1024.0 * 1024.0 * 1024.0)) + "GB";
}

void verifyClipboardName();
void setupGUIClipboardDaemon();
void syncWithRemoteClipboard(bool force = false);
void syncWithGUIClipboard(bool force = false);
void fixMissingItems();
unsigned int suitableThreadAmount();
bool envVarIsTrue(const std::string_view& name);
Expand Down Expand Up @@ -379,7 +383,7 @@ void showClipboardContents();
void setupAction(int& argc, char* argv[]);
void checkForNoItems();
void startIndicator();
void setupIndicator();
void indicatorThread();
void deduplicateItems();
unsigned long long totalItemSize();
void checkItemSize();
Expand Down
91 changes: 91 additions & 0 deletions src/cb/src/externalclipboards.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
#include "platforms/windows.hpp"
#endif

#if defined(__linux__) || defined(__APPLE__) || defined(__unix__)
#include <unistd.h>
#endif

bool isARemoteSession() {
if (getenv("SSH_CLIENT") || getenv("SSH_TTY") || getenv("SSH_CONNECTION")) return true;
return false;
Expand Down Expand Up @@ -191,6 +195,37 @@ ClipboardContent thisClipboard() {
return {};
}

void syncWithRemoteClipboard(bool force) {
using enum ClipboardContentType;
if ((!isAClearingAction() && clipboard_name == constants.default_clipboard_name && clipboard_entry == constants.default_clipboard_entry && action != Action::Status)
|| force) { // exclude Status because it does this manually
ClipboardContent content;
if (envVarIsTrue("CLIPBOARD_NOREMOTE")) return;
content = getRemoteClipboard();
if (content.type() == Text) {
convertFromGUIClipboard(content.text());
copying.mime = !content.mime().empty() ? content.mime() : inferMIMEType(content.text()).value_or("text/plain");
}
}
}

void syncWithGUIClipboard(bool force) {
using enum ClipboardContentType;
if ((!isAClearingAction() && clipboard_name == constants.default_clipboard_name && clipboard_entry == constants.default_clipboard_entry && action != Action::Status)
|| force) { // exclude Status because it does this manually
ClipboardContent content;
if (envVarIsTrue("CLIPBOARD_NOGUI")) return;
content = getGUIClipboard(preferred_mime);
if (content.type() == Text) {
convertFromGUIClipboard(content.text());
copying.mime = !content.mime().empty() ? content.mime() : inferMIMEType(content.text()).value_or("text/plain");
} else if (content.type() == Paths) {
convertFromGUIClipboard(content.paths());
copying.mime = "text/uri-list";
}
}
}

void syncWithExternalClipboards(bool force) {
using enum ClipboardContentType;
if ((!isAClearingAction() && clipboard_name == constants.default_clipboard_name && clipboard_entry == constants.default_clipboard_entry && action != Action::Status)
Expand Down Expand Up @@ -258,3 +293,59 @@ void updateExternalClipboards(bool force) {
if (!envVarIsTrue("CLIPBOARD_NOREMOTE")) writeToRemoteClipboard(thisContent);
}
}

void setupGUIClipboardDaemon() {
if (envVarIsTrue("CLIPBOARD_NOGUI")) return;

#if defined(__linux__) || defined(__APPLE__) || defined(__unix__)
auto pid = fork();
if (pid > 0) return;
if (pid < 0) {
perror("fork");
exit(EXIT_FAILURE);
}
if (setsid() < 0) {
perror("setsid");
exit(EXIT_FAILURE);
}
if (chdir("/") < 0) {
perror("chdir");
exit(EXIT_FAILURE);
}
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);

#if defined(__linux__)
// check if there is already a cb daemon by checking /proc for a process which has an exe symlink entry that points to a binary called "cb" and which does not have stdin or stdout file descriptors

try {
for (const auto& entry : fs::directory_iterator("/proc")) {
if (!entry.is_directory()) continue;
auto exe = entry.path() / "exe";
if (!fs::exists(exe)) continue;
auto exeTarget = fs::read_symlink(exe);
if (exeTarget.filename() != "cb") continue;
auto fd = entry.path() / "fd";
if (fs::exists(fd / "0") || fs::exists(fd / "1") || fs::exists(fd / "2")) continue;
// found a cb daemon
exit(EXIT_SUCCESS);
}
} catch (...) {}

// std::cerr << "Starting cb daemon" << std::endl;
#endif
#elif defined(_WIN32) | defined(_WIN64)

#endif
path = Clipboard(std::string(constants.default_clipboard_name));

while (fs::exists(path)) {
path.getLock();
syncWithGUIClipboard(true);
path.releaseLock();
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
}

exit(EXIT_SUCCESS);
}
8 changes: 6 additions & 2 deletions src/cb/src/indicator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ bool stopIndicator(bool change_condition_variable) {
return true;
}

void setupIndicator() {
void indicatorThread() {
if (!is_tty.err || output_silent || progress_silent) return;

bool hasFocus = true;
Expand Down Expand Up @@ -76,6 +76,10 @@ void setupIndicator() {
};

auto display_progress = [&](const auto& formattedNum, const std::string_view& actionText = doing_action[action]) {
if (std::chrono::steady_clock::now() - start < std::chrono::milliseconds(500)) {
cv.wait_for(lock, std::chrono::milliseconds(17), [&] { return progress_state != IndicatorState::Active; });
return;
}
std::string progressBar;
if (step < 40) {
progressBar += repeatString("โ–ˆ", step);
Expand Down Expand Up @@ -168,5 +172,5 @@ void setupIndicator() {
void startIndicator() { // If cancelled, leave cancelled
IndicatorState expect = IndicatorState::Done;
progress_state.compare_exchange_strong(expect, IndicatorState::Active);
indicator = std::thread(setupIndicator);
indicator = std::thread(indicatorThread);
}
9 changes: 8 additions & 1 deletion src/cb/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ int main(int argc, char* argv[]) {

startIndicator();

verifyClipboardName();

setFilepaths();

action = getAction();
Expand All @@ -42,9 +44,14 @@ int main(int argc, char* argv[]) {

verifyAction();

#if defined(__linux__)
setupGUIClipboardDaemon();
syncWithRemoteClipboard();
if (action != Action::Info) path.getLock();
#else
if (action != Action::Info) path.getLock();

syncWithExternalClipboards();
#endif

fixMissingItems();

Expand Down
Loading

0 comments on commit 42b421c

Please sign in to comment.