Skip to content

Commit 721cb37

Browse files
committed
clip: use xclip on linux
reason: launching 2 instances of oshot will make the last instance freeze until the original exits on X11 removing the socket on next commit
1 parent 2cfb03e commit 721cb37

4 files changed

Lines changed: 86 additions & 62 deletions

File tree

src/clipboard.cpp

Lines changed: 27 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -5,52 +5,61 @@
55
#include <cstring>
66

77
#include "clip/clip.h"
8+
#include "screen_capture.hpp"
89
#include "socket.hpp"
910

1011
#ifdef __linux__
1112
# include <sys/wait.h>
1213
# include <unistd.h>
1314
#endif
1415

15-
// Starts wlcopy in the background, forgetting it.
16-
// Sets wlcopy_pid, and returns an stdin pipe on success.
17-
Result<int> start_wlcopy(const std::string_view mime_type = "text/plain;charset=utf-8")
16+
// Starts wlcopy/xclip in the background, forgetting it.
17+
// Sets wlcopy_pid/xclip_pid, and returns an stdin pipe on success.
18+
Result<int> start_linux_copy(SessionType session, const std::string_view mime_type = "text/plain;charset=utf-8")
1819
{
1920
#ifdef __linux__
20-
static int wlcopy_pid = -1;
21+
static pid_t clip_pid = -1;
2122

22-
// stop if already launched
23-
if (wlcopy_pid > 0)
23+
// stop if already launched wlcopy
24+
if (clip_pid > 0)
2425
{
25-
kill(wlcopy_pid, SIGINT);
26+
kill(clip_pid, SIGINT);
2627

2728
// we need to do this on Linux, because the process will be defunct otherwise.
28-
waitpid(wlcopy_pid, NULL, 0);
29+
waitpid(clip_pid, NULL, 0);
2930

30-
wlcopy_pid = -1;
31+
clip_pid = -1;
3132
}
3233

3334
int copy_pipe[2];
3435
if (pipe(copy_pipe) == -1)
3536
return Err("Failed to open stdin pipe: " + std::string(strerror(errno)));
3637

37-
wlcopy_pid = fork();
38+
clip_pid = fork();
3839

39-
if (wlcopy_pid == 0)
40+
if (clip_pid == 0)
4041
{
4142
close(copy_pipe[1]);
4243
dup2(copy_pipe[0], STDIN_FILENO);
4344
close(copy_pipe[0]);
4445

45-
const char* args[] = { "wl-copy", "--foreground", "--type", mime_type.data(), nullptr };
46-
execvp("wl-copy", const_cast<char* const*>(args));
46+
if (session == SessionType::Wayland)
47+
{
48+
const char* args[] = { "wl-copy", "--foreground", "--type", mime_type.data(), nullptr };
49+
execvp("wl-copy", const_cast<char* const*>(args));
50+
}
51+
else
52+
{
53+
const char* args[] = { "xclip", "-selection", "clipboard", "-t", mime_type.data(), "-i", nullptr };
54+
execvp("xclip", const_cast<char* const*>(args));
55+
}
4756

4857
exit(-1);
4958
}
5059

5160
close(copy_pipe[0]);
5261

53-
if (wlcopy_pid < 0)
62+
if (clip_pid < 0)
5463
{
5564
close(copy_pipe[1]);
5665
return Err("Failed to fork: " + std::string(strerror(errno)));
@@ -64,9 +73,9 @@ Result<int> start_wlcopy(const std::string_view mime_type = "text/plain;charset=
6473

6574
Result<> Clipboard::CopyText(const std::string& text)
6675
{
67-
if (m_session == SessionType::Wayland)
76+
if (m_session == SessionType::Wayland || m_session == SessionType::X11)
6877
{
69-
const Result<int>& res = start_wlcopy();
78+
const Result<int>& res = start_linux_copy(m_session);
7079
if (!res.ok())
7180
return res.error();
7281

@@ -82,15 +91,6 @@ Result<> Clipboard::CopyText(const std::string& text)
8291
return Ok();
8392
}
8493

85-
// Linux only, external client with systray already running
86-
if (!g_is_systray && !OSHOT_TOOL_ON_MAIN_THREAD && g_sender->IsConnected())
87-
{
88-
const Result<>& res = g_sender->Send(text);
89-
if (res.ok())
90-
return Ok();
91-
return Err("Failed to send text to copy: " + res.error_v());
92-
}
93-
9494
if (clip::set_text(text))
9595
return Ok();
9696
return Err("Failed to copy text into clipboard");
@@ -101,9 +101,9 @@ Result<> Clipboard::CopyImage(const capture_result_t& cap)
101101
if (cap.w <= 0 || cap.h <= 0)
102102
return Err("Image size is empty");
103103

104-
if (m_session == SessionType::Wayland)
104+
if (m_session == SessionType::Wayland || m_session == SessionType::X11)
105105
{
106-
const Result<int>& res = start_wlcopy("image/png");
106+
const Result<int>& res = start_linux_copy(m_session, "image/png");
107107
if (!res.ok())
108108
return res.error();
109109

@@ -121,23 +121,6 @@ Result<> Clipboard::CopyImage(const capture_result_t& cap)
121121
return Ok();
122122
}
123123

124-
// Linux only, external client with systray already running
125-
if (!g_is_systray && !OSHOT_TOOL_ON_MAIN_THREAD && g_sender->IsConnected())
126-
{
127-
const size_t size = static_cast<size_t>(cap.w) * cap.h * 4;
128-
std::vector<uint8_t> payload;
129-
payload.resize(8 + size);
130-
131-
std::memcpy(payload.data() + 0, &cap.w, 4);
132-
std::memcpy(payload.data() + 4, &cap.h, 4);
133-
std::memcpy(payload.data() + 8, cap.view().data(), size);
134-
135-
const Result<>& res = g_sender->Send(SendMsg::Image, payload.data(), payload.size());
136-
if (res.ok())
137-
return Ok();
138-
return Err("Failed to send image to copy: " + res.error_v());
139-
}
140-
141124
clip::image_spec spec;
142125
spec.width = cap.w;
143126
spec.height = cap.h;

src/main.cpp

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
#include <thread>
1515
#include <utility>
1616

17+
#include "fmt/format.h"
18+
1719
#ifndef _WIN32
1820
# include <netdb.h>
1921
# include <netinet/in.h>
@@ -197,7 +199,7 @@ static bool do_capture = false;
197199
static capture_result_t pending_image;
198200
static bool do_copy_image = false;
199201

200-
void exit_handler(int _)
202+
void exit_handler(int)
201203
{
202204
quit.store(true);
203205
cv.notify_all();
@@ -207,6 +209,12 @@ void exit_handler(int _)
207209
#endif
208210
extern_glfwTerminate();
209211
trayMaker.Exit();
212+
std::error_code ec;
213+
fs::remove(fs::temp_directory_path(ec) / fmt::format("oshot_{}.log", getpid()));
214+
}
215+
void exit_handler_nc()
216+
{
217+
exit_handler(0);
210218
}
211219

212220
int run_main_tool(const std::string& imgui_ini_path);
@@ -333,7 +341,7 @@ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
333341
#else
334342
int main(int argc, char* argv[])
335343
{
336-
const fs::path& log_path = fs::temp_directory_path(ec) / "oshot.log";
344+
const fs::path& log_path = fs::temp_directory_path(ec) / fmt::format("oshot_{}.log", getpid());
337345
fs::create_directories(log_path.parent_path(), ec);
338346
auto file = std::make_shared<spdlog::sinks::basic_file_sink_mt>(log_path.string(), true);
339347
#endif
@@ -356,6 +364,7 @@ int main(int argc, char* argv[])
356364
XInitThreads();
357365
#endif
358366

367+
atexit(exit_handler_nc);
359368
signal(SIGINT, exit_handler);
360369
signal(SIGTERM, exit_handler);
361370
signal(SIGABRT, exit_handler);
@@ -376,6 +385,13 @@ int main(int argc, char* argv[])
376385

377386
// [2026-03-10 17:24:07.593] [DEBUG] XRandR capturing: monitor 1920x1080+0+0 (cursor at 1130,682)
378387
logger.set_pattern("[%Y-%m-%d %T.%e] [%l] %^%v%$");
388+
logger.flush_on(spdlog::level::trace);
389+
390+
logger.info("=== oshot starting ===");
391+
logger.info("Log file path: {}", log_path.string());
392+
logger.flush();
393+
394+
spdlog::flush_every(std::chrono::seconds(1));
379395

380396
// Check if demo build.
381397
// removing it once the hackaton has ended
@@ -439,15 +455,28 @@ int main(int argc, char* argv[])
439455
continue;
440456
}
441457

458+
// Set receive timeout
459+
struct timeval tv;
460+
tv.tv_sec = 2;
461+
tv.tv_usec = 0;
462+
setsockopt(client, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
463+
442464
auto recv_all = [](int fd, void* dst, size_t n) {
443-
uint8_t* p = static_cast<uint8_t*>(dst);
444-
while (n)
465+
uint8_t* p = static_cast<uint8_t*>(dst);
466+
size_t remaining = n;
467+
while (remaining > 0)
445468
{
446-
const ssize_t r = ::recv(fd, p, n, 0);
469+
const ssize_t r = ::recv(fd, p, remaining, 0);
447470
if (r <= 0)
471+
{
472+
if (r == 0)
473+
spdlog::debug("Connection closed by peer");
474+
else
475+
spdlog::debug("recv error: {}", strerror(errno));
448476
return false;
477+
}
449478
p += static_cast<size_t>(r);
450-
n -= static_cast<size_t>(r);
479+
remaining -= static_cast<size_t>(r);
451480
}
452481
return true;
453482
};
@@ -457,11 +486,15 @@ int main(int argc, char* argv[])
457486

458487
bool ok = recv_all(client, &type, 1) && recv_all(client, &len, sizeof(len));
459488

460-
std::vector<uint8_t> payload;
461-
payload.resize(len);
489+
if (ok)
490+
spdlog::debug("IPC received type={}, len={}", type, len);
462491

463-
if (ok && len > 0)
492+
std::vector<uint8_t> payload;
493+
if (ok && len > 0 && len < 100 * 1024 * 1024) // Sanity check: max 100MB
494+
{
495+
payload.resize(len);
464496
ok = recv_all(client, payload.data(), payload.size());
497+
}
465498

466499
close(client);
467500

@@ -476,18 +509,26 @@ int main(int argc, char* argv[])
476509
else if (type == 'I')
477510
{
478511
if (payload.size() < 8)
512+
{
513+
spdlog::debug("IPC image payload too small: {}", payload.size());
479514
continue;
515+
}
480516

481517
int w = 0, h = 0;
482518
std::memcpy(&w, payload.data() + 0, 4);
483519
std::memcpy(&h, payload.data() + 4, 4);
484520

521+
spdlog::debug("IPC received image: {}x{}", w, h);
522+
485523
if (w <= 0 || h <= 0)
486524
continue;
487525

488526
const size_t expected = static_cast<size_t>(w) * h * 4;
489527
if (payload.size() != expected + 8)
528+
{
529+
spdlog::debug("IPC image size mismatch: got {}, expected {}", payload.size(), expected + 8);
490530
continue;
531+
}
491532

492533
// Strip the 8-byte header
493534
payload.erase(payload.begin(), payload.begin() + 8);

src/screenshot_tool.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1609,12 +1609,12 @@ static void draw_preference_edit_config(const std::function<void()>& refresh_mod
16091609
if (ImGui::TreeNode("Format help"))
16101610
{
16111611
auto row = [](const char* spec, const char* desc) {
1612-
ImGui::TableNextRow();
1613-
ImGui::TableSetColumnIndex(0);
1614-
ImGui::TextDisabled("%s", spec);
1615-
ImGui::TableSetColumnIndex(1);
1616-
ImGui::Text("%s", desc);
1617-
};
1612+
ImGui::TableNextRow();
1613+
ImGui::TableSetColumnIndex(0);
1614+
ImGui::TextDisabled("%s", spec);
1615+
ImGui::TableSetColumnIndex(1);
1616+
ImGui::Text("%s", desc);
1617+
};
16181618

16191619
ImGui::Spacing();
16201620
ImGui::TextDisabled("Specifiers:");

src/util.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -391,15 +391,15 @@ bool parse_hex_rgba(const std::string_view hex, rgba_t& out)
391391
// from the substring
392392
const std::string s(hex.substr(1));
393393

394-
char* end = nullptr;
395-
unsigned long parsed = std::strtoul(s.c_str(), &end, 16);
394+
char* end = nullptr;
395+
unsigned long parsed = std::strtoul(s.c_str(), &end, 16);
396396

397397
// Ensure the entire string was consumed and no overflow occurred.
398398
if (end != s.c_str() + s.size() || parsed > 0xFFFFFFFFul)
399399
return false;
400400

401401
const auto value = static_cast<uint32_t>(parsed);
402-
rgba_t v(hex.size() == 7 ? (value << 8) | 0xFF : value);
402+
rgba_t v(hex.size() == 7 ? (value << 8) | 0xFF : value);
403403

404404
out = v;
405405
return true;

0 commit comments

Comments
 (0)