Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 5 additions & 8 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,26 +22,23 @@ jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: List workspace files
run: ls -lR
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y libx11-dev libx11-6 dpkg xvfb xclip

- name: Checkout code
uses: actions/checkout@v4

sudo apt-get install -y libx11-dev libx11-6 dpkg xvfb xclip libgtk-3-dev libappindicator3-dev
- name: Build latest .deb
run: |
chmod +x build_deb.sh
./build_deb.sh

- name: Install hcp .deb
run: |
sudo dpkg -i hcp_*.deb
sudo apt-get install -f -y

- name: Make test script executable
run: chmod +x test_hcp.sh

- name: Run hcp integration test
run: xvfb-run --auto-servernum --server-args='-screen 0 1024x768x24' ./test_hcp.sh
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,24 @@ hcp 2

## License
See [LICENSE](LICENSE).

## Manual Page

After installing the package, you can view the manual page for usage and options:

```
man hcp
```

This provides detailed information about available commands and usage examples.

## Starting the GUI Tray App

After installing, you can enable and start the tray app (which sits in your system tray and provides clipboard history):

```
sudo systemctl enable hcp-tray@$(whoami).service
sudo systemctl start hcp-tray@$(whoami).service
```

This will launch the tray app in the background for your user session.
72 changes: 68 additions & 4 deletions build_deb.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ mkdir -p $PKGDIR/usr/bin
mkdir -p $PKGDIR/etc/systemd/system

# Ensure build dependency is installed
apt-get update && apt-get install -y libx11-dev
apt-get update && apt-get install -y libx11-dev libgtk-3-dev libappindicator3-dev

# Create control file
cat > $PKGDIR/DEBIAN/control <<EOF
Expand All @@ -24,7 +24,7 @@ Architecture: amd64
Maintainer: Prince Roshan <princekrroshan01@gmail.com>
Description: HCP - Historical Clipboard Manager
A historical clipboard manager with LRU history and systemd integration.
Depends: libx11-6
Depends: libx11-6, libgtk-3-0, libappindicator3-1
EOF

# Create postinst script directly
Expand Down Expand Up @@ -60,13 +60,73 @@ echo ""
echo "To check if your environment is set up correctly, run:"
echo " hcp --diagnostic"
echo ""
echo "To enable and start the GUI tray app (clipboard history in system tray), run:"
echo " sudo systemctl enable hcp-tray@${USERNAME}.service"
echo " sudo systemctl start hcp-tray@${USERNAME}.service"
POSTINST
chmod 755 $PKGDIR/DEBIAN/postinst

# Build the binary
# Build the main binary
make
cp hcp $PKGDIR/usr/bin/hcp

# Build the GUI tray app
cd gui && make && cd ..
cp gui/hcp-tray $PKGDIR/usr/bin/hcp-tray
Comment on lines +73 to +75
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for GUI build process.

The build process assumes the gui directory exists and has a working Makefile without verification.

-# Build the GUI tray app
-cd gui && make && cd ..
-cp gui/hcp-tray $PKGDIR/usr/bin/hcp-tray
+# Build the GUI tray app
+if [ ! -d "gui" ]; then
+    echo "Error: gui directory not found"
+    exit 1
+fi
+cd gui && make && cd ..
+if [ ! -f "gui/hcp-tray" ]; then
+    echo "Error: hcp-tray binary not built"
+    exit 1
+fi
+cp gui/hcp-tray $PKGDIR/usr/bin/hcp-tray
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Build the GUI tray app
cd gui && make && cd ..
cp gui/hcp-tray $PKGDIR/usr/bin/hcp-tray
# Build the GUI tray app
if [ ! -d "gui" ]; then
echo "Error: gui directory not found"
exit 1
fi
cd gui && make && cd ..
if [ ! -f "gui/hcp-tray" ]; then
echo "Error: hcp-tray binary not built"
exit 1
fi
cp gui/hcp-tray $PKGDIR/usr/bin/hcp-tray
🤖 Prompt for AI Agents
In build_deb.sh around lines 73 to 75, the script assumes the gui directory
exists and the make command succeeds without checking. Add error handling by
first verifying the gui directory exists before running make, and check the exit
status of the make command. If the directory is missing or make fails, output an
error message and exit the script to prevent further steps from running on a
failed build.


# Add systemd service for tray app
cat > $PKGDIR/etc/systemd/system/hcp-tray@.service <<EOF
[Unit]
Description=HCP Clipboard Tray App
After=graphical-session.target

[Service]
Type=simple
User=%i
Environment=DISPLAY=:0
Environment=XAUTHORITY=/home/%i/.Xauthority
ExecStart=/usr/bin/hcp-tray
Restart=on-failure

[Install]
WantedBy=default.target
EOF
Comment on lines +78 to +93
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Fix systemd service configuration inconsistency.

The service uses After=graphical-session.target but WantedBy=default.target. For a GUI tray app, consider using graphical-session.target for both or verify the intended behavior.

 [Unit]
 Description=HCP Clipboard Tray App
-After=graphical-session.target
+After=graphical-session.target

 [Service]
 Type=simple
 User=%i
 Environment=DISPLAY=:0
 Environment=XAUTHORITY=/home/%i/.Xauthority
 ExecStart=/usr/bin/hcp-tray
 Restart=on-failure

 [Install]
-WantedBy=default.target
+WantedBy=graphical-session.target
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
cat > $PKGDIR/etc/systemd/system/hcp-tray@.service <<EOF
[Unit]
Description=HCP Clipboard Tray App
After=graphical-session.target
[Service]
Type=simple
User=%i
Environment=DISPLAY=:0
Environment=XAUTHORITY=/home/%i/.Xauthority
ExecStart=/usr/bin/hcp-tray
Restart=on-failure
[Install]
WantedBy=default.target
EOF
cat > $PKGDIR/etc/systemd/system/hcp-tray@.service <<EOF
[Unit]
Description=HCP Clipboard Tray App
After=graphical-session.target
[Service]
Type=simple
User=%i
Environment=DISPLAY=:0
Environment=XAUTHORITY=/home/%i/.Xauthority
ExecStart=/usr/bin/hcp-tray
Restart=on-failure
[Install]
WantedBy=graphical-session.target
EOF
🤖 Prompt for AI Agents
In build_deb.sh around lines 78 to 93, the systemd service file inconsistently
uses After=graphical-session.target but WantedBy=default.target. To fix this,
update the WantedBy directive to graphical-session.target to match the After
directive, ensuring consistent and correct service startup ordering for the GUI
tray app.


# Create the man page BEFORE building the .deb
mkdir -p $PKGDIR/usr/share/man/man1
cat > $PKGDIR/usr/share/man/man1/hcp.1 <<'EOF'
.TH hcp 1 "$(date +%Y-%m-%d)" "$VERSION" "hcp manual"
.SH NAME
hcp \- Historical Clipboard Manager for X11
.SH SYNOPSIS
.B hcp
[service start | list | <index> | pop | --help | -h]
.SH DESCRIPTION
.B hcp
is a lightweight clipboard manager for X11 systems. It captures clipboard entries, maintains a history, and allows you to list, print, or remove entries. Designed for reliability and minimalism, it works directly with the X11 clipboard and is suitable for use as a background service or on-demand.
.SH COMMANDS
.TP
.B service start
Start clipboard monitoring service in the background.
.TP
.B list
List clipboard history.
.TP
.B <index>
Print clipboard entry at <index>.
.TP
.B pop
Remove most recent clipboard entry.
.TP
.B --help, -h
Show this help message.
.SH AUTHOR
Written by the hcp project contributors.
.SH SEE ALSO
clipboard(1)
EOF
gzip -f $PKGDIR/usr/share/man/man1/hcp.1
Comment on lines +97 to +128
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix variable expansion in man page creation.

The man page uses $(date +%Y-%m-%d) and $VERSION in a single-quoted heredoc, which prevents variable expansion.

-cat > $PKGDIR/usr/share/man/man1/hcp.1 <<'EOF'
-.TH hcp 1 "$(date +%Y-%m-%d)" "$VERSION" "hcp manual"
+cat > $PKGDIR/usr/share/man/man1/hcp.1 <<EOF
+.TH hcp 1 "$(date +%Y-%m-%d)" "$VERSION" "hcp manual"

Or use a different approach:

+# Create the man page with proper variable expansion
+MANPAGE_DATE=$(date +%Y-%m-%d)
+cat > $PKGDIR/usr/share/man/man1/hcp.1 <<EOF
+.TH hcp 1 "$MANPAGE_DATE" "$VERSION" "hcp manual"
🤖 Prompt for AI Agents
In build_deb.sh around lines 97 to 128, the man page is created using a
single-quoted heredoc which prevents the variables $(date +%Y-%m-%d) and
$VERSION from expanding. To fix this, change the heredoc delimiter from single
quotes to unquoted or double quotes so that shell variables are expanded
properly during the cat command execution.


# Build the .deb package
dpkg-deb --build $PKGDIR

Expand All @@ -77,4 +137,8 @@ echo "After install, enable and start the service with:"
echo " sudo systemctl daemon-reload"
echo " sudo systemctl enable hcp@${USERNAME}.service"
echo " sudo systemctl start hcp@${USERNAME}.service"
echo "\nYou can override the version by running: VERSION=your_version ./build_deb.sh"
echo "To enable and start the GUI tray app (clipboard history in system tray), run:"
echo " sudo systemctl enable hcp-tray@${USERNAME}.service"
echo " sudo systemctl start hcp-tray@${USERNAME}.service"
echo ""
echo "You can override the version by running: VERSION=your_version ./build_deb.sh"
14 changes: 14 additions & 0 deletions gui/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
CXX = g++
CXXFLAGS = -std=c++11 `pkg-config --cflags gtk+-3.0 appindicator3-0.1` -I../include
LDFLAGS = `pkg-config --libs gtk+-3.0 appindicator3-0.1` -lX11
Comment on lines +2 to +3
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add dependency checking and error handling.

The Makefile should verify that required packages are available before building.

-CXXFLAGS = -std=c++11 `pkg-config --cflags gtk+-3.0 appindicator3-0.1` -I../include
-LDFLAGS = `pkg-config --libs gtk+-3.0 appindicator3-0.1` -lX11
+# Check for required packages
+PKG_CHECK := $(shell pkg-config --exists gtk+-3.0 appindicator3-0.1 && echo "ok")
+ifneq ($(PKG_CHECK),ok)
+$(error Required packages not found. Install libgtk-3-dev and libappindicator3-dev)
+endif
+
+CXXFLAGS = -std=c++11 -Wall -Wextra `pkg-config --cflags gtk+-3.0 appindicator3-0.1` -I../include
+LDFLAGS = `pkg-config --libs gtk+-3.0 appindicator3-0.1` -lX11
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
CXXFLAGS = -std=c++11 `pkg-config --cflags gtk+-3.0 appindicator3-0.1` -I../include
LDFLAGS = `pkg-config --libs gtk+-3.0 appindicator3-0.1` -lX11
# Check for required packages
PKG_CHECK := $(shell pkg-config --exists gtk+-3.0 appindicator3-0.1 && echo "ok")
ifneq ($(PKG_CHECK),ok)
$(error Required packages not found. Install libgtk-3-dev and libappindicator3-dev)
endif
CXXFLAGS = -std=c++11 -Wall -Wextra `pkg-config --cflags gtk+-3.0 appindicator3-0.1` -I../include
LDFLAGS = `pkg-config --libs gtk+-3.0 appindicator3-0.1` -lX11
🤖 Prompt for AI Agents
In gui/Makefile around lines 2 to 3, the Makefile currently sets compiler and
linker flags without checking if the required packages are installed. Add
dependency checking by using pkg-config with the --exists flag for gtk+-3.0 and
appindicator3-0.1 before setting CXXFLAGS and LDFLAGS. If the packages are not
found, print an error message and stop the build process to prevent build
failures later.


SRC = trey_app.cpp ../src/clipboard_mgmt.cpp ../src/db.cpp ../src/logging.cpp
TARGET = hcp-tray

all: $(TARGET)

$(TARGET): $(SRC)
$(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS)

clean:
rm -f $(TARGET)
Comment on lines +1 to +14
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add missing phony targets and improve Makefile robustness.

The static analysis correctly identifies missing phony targets and other Makefile best practices.

 CXX = g++
 CXXFLAGS = -std=c++11 `pkg-config --cflags gtk+-3.0 appindicator3-0.1` -I../include
 LDFLAGS = `pkg-config --libs gtk+-3.0 appindicator3-0.1` -lX11
 
 SRC = trey_app.cpp ../src/clipboard_mgmt.cpp ../src/db.cpp ../src/logging.cpp 
 TARGET = hcp-tray
 
+.PHONY: all clean test
+
 all: $(TARGET)
 
 $(TARGET): $(SRC)
 	$(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS)
 
+test:
+	@echo "No tests defined for GUI component"
+
 clean:
 	rm -f $(TARGET)
🧰 Tools
🪛 checkmake (0.2.2)

[warning] 1-1: Missing required phony target "all"

(minphony)


[warning] 1-1: Missing required phony target "clean"

(minphony)


[warning] 1-1: Missing required phony target "test"

(minphony)


[warning] 8-8: Target "all" should be declared PHONY.

(phonydeclared)

🤖 Prompt for AI Agents
In gui/Makefile lines 1 to 14, the Makefile lacks .PHONY declarations for
targets like 'all' and 'clean', which can cause issues if files with those names
exist. Add a .PHONY target listing 'all' and 'clean' to ensure these are always
treated as commands, not files. This improves robustness and follows Makefile
best practices.

Empty file added gui/README.md
Empty file.
68 changes: 68 additions & 0 deletions gui/trey_app.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#include <gtk/gtk.h>
#include <libappindicator3-0.1/libappindicator/app-indicator.h>
#include "../include/db.hpp"
#include "../include/clipboard_mgmt.hpp"
#include <X11/Xlib.h>
#include <vector>
#include <string>

static void on_copy_clicked(GtkButton *button, gpointer user_data) {
const char *text = (const char *)user_data;
Display *display = XOpenDisplay(nullptr);
if (display) {
set_clipboard(display, std::string(text));
XCloseDisplay(display);
}
Comment on lines +11 to +15
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for clipboard operations.

The function should handle potential failures in clipboard operations and provide user feedback.

 static void on_copy_clicked(GtkButton *button, gpointer user_data) {
     const char *text = (const char *)user_data;
     Display *display = XOpenDisplay(nullptr);
     if (display) {
-        set_clipboard(display, std::string(text));
+        try {
+            set_clipboard(display, std::string(text));
+        } catch (const std::exception &e) {
+            g_warning("Failed to set clipboard: %s", e.what());
+        }
         XCloseDisplay(display);
+    } else {
+        g_warning("Failed to open X11 display");
     }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Display *display = XOpenDisplay(nullptr);
if (display) {
set_clipboard(display, std::string(text));
XCloseDisplay(display);
}
static void on_copy_clicked(GtkButton *button, gpointer user_data) {
const char *text = (const char *)user_data;
Display *display = XOpenDisplay(nullptr);
if (display) {
try {
set_clipboard(display, std::string(text));
} catch (const std::exception &e) {
g_warning("Failed to set clipboard: %s", e.what());
}
XCloseDisplay(display);
} else {
g_warning("Failed to open X11 display");
}
}
🤖 Prompt for AI Agents
In gui/trey_app.cpp around lines 11 to 15, the clipboard operations lack error
handling. Modify the code to check the success of set_clipboard and handle any
failures by providing appropriate user feedback, such as logging an error
message or notifying the user. Ensure that XOpenDisplay failure is also handled
gracefully by informing the user if the display cannot be opened.

}
Comment on lines +9 to +16
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Critical memory safety issue with dangling pointer.

The entry.c_str() pointer passed as user_data will become invalid after the entry object goes out of scope in the show_history_window function. This creates a dangling pointer that can cause crashes or undefined behavior when the copy button is clicked.

Apply this diff to fix the memory safety issue by copying the string data:

-    g_signal_connect(button, "clicked", G_CALLBACK(on_copy_clicked), (gpointer)entry.c_str());
+    char *entry_copy = g_strdup(entry.c_str());
+    g_signal_connect(button, "clicked", G_CALLBACK(on_copy_clicked), entry_copy);
+    g_object_set_data_full(G_OBJECT(button), "entry_data", entry_copy, g_free);

And update the on_copy_clicked function to handle the copied data:

 static void on_copy_clicked(GtkButton *button, gpointer user_data) {
-    const char *text = (const char *)user_data;
+    const char *text = (const char *)g_object_get_data(G_OBJECT(button), "entry_data");
     Display *display = XOpenDisplay(nullptr);
     if (display) {
         set_clipboard(display, std::string(text));
         XCloseDisplay(display);
     }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
static void on_copy_clicked(GtkButton *button, gpointer user_data) {
const char *text = (const char *)user_data;
Display *display = XOpenDisplay(nullptr);
if (display) {
set_clipboard(display, std::string(text));
XCloseDisplay(display);
}
}
static void on_copy_clicked(GtkButton *button, gpointer user_data) {
- const char *text = (const char *)user_data;
+ const char *text = (const char *)g_object_get_data(G_OBJECT(button), "entry_data");
Display *display = XOpenDisplay(nullptr);
if (display) {
set_clipboard(display, std::string(text));
XCloseDisplay(display);
}
}
🤖 Prompt for AI Agents
In gui/trey_app.cpp around lines 9 to 16, the user_data pointer passed to
on_copy_clicked is a dangling pointer from entry.c_str() that becomes invalid
after the entry object is destroyed. To fix this, allocate a copy of the string
data on the heap before passing it as user_data, ensuring it remains valid when
the callback is invoked. Then update on_copy_clicked to properly handle and free
this copied string after use to avoid memory leaks.


static void show_history_window(GtkWidget *, gpointer) {
GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(window), "Clipboard History");
gtk_window_set_default_size(GTK_WINDOW(window), 400, 400);

GtkWidget *scrolled = gtk_scrolled_window_new(NULL, NULL);
gtk_container_add(GTK_CONTAINER(window), scrolled);

GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
gtk_container_add(GTK_CONTAINER(scrolled), vbox);

std::vector<std::string> history = load_clipboard_blocks();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for database operations.

The load_clipboard_blocks() function call should handle potential database errors gracefully.

-    std::vector<std::string> history = load_clipboard_blocks();
+    std::vector<std::string> history;
+    try {
+        history = load_clipboard_blocks();
+    } catch (const std::exception &e) {
+        g_warning("Failed to load clipboard history: %s", e.what());
+        // Show error message to user
+        GtkWidget *error_label = gtk_label_new("Error loading clipboard history");
+        gtk_container_add(GTK_CONTAINER(vbox), error_label);
+        gtk_widget_show_all(window);
+        return;
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
std::vector<std::string> history = load_clipboard_blocks();
// Before: std::vector<std::string> history = load_clipboard_blocks();
std::vector<std::string> history;
try {
history = load_clipboard_blocks();
} catch (const std::exception &e) {
g_warning("Failed to load clipboard history: %s", e.what());
// Show error message to user
GtkWidget *error_label = gtk_label_new("Error loading clipboard history");
gtk_container_add(GTK_CONTAINER(vbox), error_label);
gtk_widget_show_all(window);
return;
}
🤖 Prompt for AI Agents
In gui/trey_app.cpp at line 29, the call to load_clipboard_blocks() lacks error
handling for potential database failures. Modify the code to catch exceptions or
check error return values from load_clipboard_blocks(), and handle these errors
gracefully by logging the issue or providing fallback behavior to prevent
crashes or undefined states.

for (const auto &entry : history) {
GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5);
GtkWidget *label = gtk_label_new(entry.substr(0, 60).c_str());
gtk_label_set_xalign(GTK_LABEL(label), 0.0);
GtkWidget *button = gtk_button_new_with_label("Copy");
g_signal_connect(button, "clicked", G_CALLBACK(on_copy_clicked), (gpointer)entry.c_str());
gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
}
gtk_widget_show_all(window);
}

int main(int argc, char **argv) {
gtk_init(&argc, &argv);

AppIndicator *indicator = app_indicator_new(
"clipboard-manager",
"system-run-symbolic",
APP_INDICATOR_CATEGORY_APPLICATION_STATUS
);
app_indicator_set_status(indicator, APP_INDICATOR_STATUS_ACTIVE);

GtkWidget *menu = gtk_menu_new();
GtkWidget *item_show = gtk_menu_item_new_with_label("Show Clipboard History");
g_signal_connect(item_show, "activate", G_CALLBACK(show_history_window), NULL);
gtk_menu_shell_append(GTK_MENU_SHELL(menu), item_show);

GtkWidget *item_quit = gtk_menu_item_new_with_label("Quit");
g_signal_connect(item_quit, "activate", G_CALLBACK(gtk_main_quit), NULL);
gtk_menu_shell_append(GTK_MENU_SHELL(menu), item_quit);

gtk_widget_show_all(menu);
app_indicator_set_menu(indicator, GTK_MENU(menu));

gtk_main();
return 0;
}

1 change: 1 addition & 0 deletions include/clipboard_mgmt.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
#include <string>

std::string get_clipboard(Display *display);
void set_clipboard(Display *display, const std::string &text);
#endif // CLIPBOARD_MGMT_HPP
40 changes: 40 additions & 0 deletions src/clipboard_mgmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -196,4 +196,44 @@ std::string get_clipboard(Display *display) {
}
XDestroyWindow(display, window);
return clipboard_content;
}

void set_clipboard(Display *display, const std::string &text) {
Atom clipboard = XInternAtom(display, "CLIPBOARD", False);
Atom utf8 = XInternAtom(display, "UTF8_STRING", False);
Atom targets[] = {utf8, XA_STRING};

Window window = XCreateSimpleWindow(display, DefaultRootWindow(display), 0, 0,
1, 1, 0, 0, 0);
XSetSelectionOwner(display, clipboard, window, CurrentTime);

XEvent event;
bool running = true;
while (running) {
XNextEvent(display, &event);

if (event.type == SelectionRequest) {
XSelectionRequestEvent *req = &event.xselectionrequest;
XEvent respond;
memset(&respond, 0, sizeof(respond));
respond.xselection.type = SelectionNotify;
respond.xselection.display = req->display;
respond.xselection.requestor = req->requestor;
respond.xselection.selection = req->selection;
respond.xselection.target = req->target;
respond.xselection.time = req->time;
respond.xselection.property = None;

if (req->target == utf8 || req->target == XA_STRING) {
XChangeProperty(display, req->requestor, req->property, req->target, 8,
PropModeReplace, (const unsigned char *)text.c_str(),
text.length());
respond.xselection.property = req->property;
}
XSendEvent(display, req->requestor, 0, 0, &respond);
XFlush(display);
running = false; // Only serve one request
}
}
XDestroyWindow(display, window);
}
Comment on lines +201 to 239
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Critical issue: Clipboard ownership model is flawed.

The current implementation has several serious problems:

  1. Single-use limitation: The function exits after serving one SelectionRequest (line 235), meaning clipboard data will only be available for one paste operation. Subsequent paste attempts will fail.

  2. Infinite blocking: The event loop has no timeout mechanism - if no SelectionRequest arrives, the function will block indefinitely.

  3. Missing error handling: No verification that XSetSelectionOwner succeeded or that window creation didn't fail.

For proper clipboard management, consider one of these approaches:

Option 1: Persistent ownership (recommended for GUI tray app)

-void set_clipboard(Display *display, const std::string &text) {
+void set_clipboard(Display *display, const std::string &text, bool persistent = true) {
   // ... existing setup code ...
   
   XEvent event;
-  bool running = true;
+  bool running = persistent;
   while (running) {
     XNextEvent(display, &event);
     
     if (event.type == SelectionRequest) {
       // ... existing handling code ...
-      running = false; // Only serve one request
+      if (!persistent) running = false; // Only serve one request for CLI usage
     }
   }
   XDestroyWindow(display, window);
 }

Option 2: Add timeout for non-persistent usage

+  fd_set readfds;
+  struct timeval timeout;
+  int x11_fd = ConnectionNumber(display);
+  
   while (running) {
+    FD_ZERO(&readfds);
+    FD_SET(x11_fd, &readfds);
+    timeout.tv_sec = 30; // 30 second timeout
+    timeout.tv_usec = 0;
+    
+    if (select(x11_fd + 1, &readfds, NULL, NULL, &timeout) <= 0) {
+      break; // Timeout or error
+    }
+    
+    if (XPending(display)) {
       XNextEvent(display, &event);
       // ... rest of event handling
+    }
   }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/clipboard_mgmt.cpp around lines 201 to 239, the clipboard ownership is
handled incorrectly by serving only one SelectionRequest and then exiting,
causing clipboard data to be unavailable for subsequent paste operations. To fix
this, modify the event loop to persistently handle multiple SelectionRequest
events instead of stopping after one, ensuring continuous clipboard ownership.
Additionally, add error handling to verify that XCreateSimpleWindow and
XSetSelectionOwner succeed, and implement a timeout mechanism in the event loop
to prevent indefinite blocking if no requests arrive. This will make clipboard
management robust and reliable.