Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clear global clipboard after specified seconds #158

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
55 changes: 55 additions & 0 deletions gui-daemon/xside.c
Original file line number Diff line number Diff line change
Expand Up @@ -880,6 +880,7 @@ static void save_clipboard_metadata(struct clipboard_metadata *metadata) {
fprintf(file, "\"buffer_size\":%d,\n", metadata->buffer_size);
fprintf(file, "\"protocol_version_xside\":%d,\n", metadata->protocol_version_xside);
fprintf(file, "\"protocol_version_vmside\":%d\n", metadata->protocol_version_vmside);
fprintf(file, "\"timeout\":%d\n", metadata->timeout);
// other key,value pairs could be added if needed in future
fprintf(file, "}\n");
fclose(file);
Expand Down Expand Up @@ -959,6 +960,8 @@ static bool load_clipboard_metadata(struct clipboard_metadata *metadata, bool lo
metadata->protocol_version_vmside = dummy;
} else if (strcmp(key, "protocol_version_xside") == 0) {
metadata->protocol_version_xside = dummy;
} else if (strcmp(key, "timeout") == 0) {
metadata->timeout = dummy;
}
}
fclose(file);
Expand Down Expand Up @@ -1219,6 +1222,13 @@ static void handle_clipboard_data(Ghandles * g, unsigned int untrusted_len)
metadata.protocol_version_vmside = g->protocol_version;
metadata.protocol_version_xside = PROTOCOL_VERSION(
PROTOCOL_VERSION_MAJOR, PROTOCOL_VERSION_MINOR);
metadata.timeout = g->clipboard_timeout;

// if a clipboard timeout child is pending, terminate it and clear its PID.
if (g->clipboard_timeout_pid) {
kill(g->clipboard_timeout_pid, SIGTERM);
g->clipboard_timeout_pid = 0;
}

if (g->log_level > 0)
fprintf(stderr, "handle_clipboard_data, len=0x%x\n",
Expand Down Expand Up @@ -1301,6 +1311,42 @@ static void handle_clipboard_data(Ghandles * g, unsigned int untrusted_len)
save_clipboard_file_xevent_timestamp(g->clipboard_xevent_time);
metadata.successful = true;
save_clipboard_metadata(&metadata);

if (g->clipboard_timeout > 0) {
struct stat st0, st1;
if (lstat(QUBES_CLIPBOARD_FILENAME, &st0) == -1) {
perror("Can not get clipboard data file status");
exit(1);
}
switch (g->clipboard_timeout_pid = fork()) {
Copy link
Member

Choose a reason for hiding this comment

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

but if you clear the pid variable on SIGCHLD, depending on the execution order, it may override the new PID instead of the old one, so be careful.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think I am losing this and I need to study a lot. The way I did it at the beginning was to assure that the clearing child process(es) assure that the clipboard content belongs to their original intended clearing event. Otherwise simply exit. The disadvantage would have been possibility for multiple useless waiting child processes.

Trying to keep track of a clearing child process and terminating it (and nothing else) is something I have difficulty with doing.

Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of using a subprocess, I recommend using a timer integrated with the event loop. This is much cleaner and avoids all of the problems you are having with subprocesses. Using fork() for concurrency is something Qubes OS does a lot, but it’s generally considered bad practice nowadays. Newer programs usually an event loop.

The two ways to deal with timeouts are to either use a timeout for poll() (updated each call) or a Linux timerfd.

case -1:
perror("fork");
exit(1);
case 0:
if (sleep(g->clipboard_timeout) != 0) {
perror("Clipboard clearing timeout was interrupted");
_exit(1);
}
inter_appviewer_lock(g, 1);
if (lstat(QUBES_CLIPBOARD_FILENAME, &st1) == -1) {
perror("Can not get clipboard data file status");
_exit(1);
}
if (st0.st_mtime == st1.st_mtime) {
old_umask = umask(0007);
metadata.copy_action = false;
metadata.timeout = g->clipboard_timeout;
clear_clipboard(&metadata);
umask(old_umask);
if (g->log_level > 1)
fprintf(stderr, "Clipboard cleared after %d seconds\n",
g->clipboard_timeout);
}
inter_appviewer_lock(g, 0);
_exit(1);
}
}

error:
umask(old_umask);
inter_appviewer_lock(g, 0);
Expand Down Expand Up @@ -4494,6 +4540,8 @@ static void load_default_config_values(Ghandles * g)
g->paste_seq_mask = ControlMask | ShiftMask;
g->paste_seq_key = XK_v;
g->clipboard_buffer_size = DEFAULT_CLIPBOARD_BUFFER_SIZE;
g->clipboard_timeout = 0;
g->clipboard_timeout_pid = 0;
g->allow_fullscreen = 0;
g->override_redirect_protection = 1;
g->startup_timeout = 45;
Expand Down Expand Up @@ -4588,6 +4636,11 @@ static void parse_vm_config(Ghandles * g, config_setting_t * group)
}
}

if ((setting =
config_setting_get_member(group, "clipboard_timeout"))) {
g->clipboard_timeout = config_setting_get_int(setting);
}

if ((setting =
config_setting_get_member(group, "allow_utf8_titles"))) {
g->allow_utf8_titles = config_setting_get_bool(setting);
Expand Down Expand Up @@ -4976,6 +5029,8 @@ int main(int argc, char **argv)
ebuf_release_xevents(&ghandles);
}
} while (busy);
if (ghandles.clipboard_timeout_pid > 0)
kill(ghandles.clipboard_timeout_pid, SIGTERM);
if (ghandles.ebuf_max_delay > 0) {
wait_for_vchan_or_argfd_once(ghandles.vchan, xfd, ghandles.ebuf_next_timeout);
} else {
Expand Down
3 changes: 3 additions & 0 deletions gui-daemon/xside.h
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,8 @@ struct _global_handles {
KeySym paste_seq_key; /* key for secure-paste key sequence */
unsigned int clipboard_buffer_size; /* maximum clipboard size limit */
int qrexec_clipboard; /* 0: use GUI protocol to fetch/put clipboard, 1: use qrexec */
unsigned int clipboard_timeout; /* x: clear global clipboard after x seconds, 0: do not clear */
pid_t clipboard_timeout_pid; /* pid of the existing clipboard timeout child (if any) */
int use_kdialog; /* use kdialog for prompts (default on KDE) or zenity (default on non-KDE) */
int prefix_titles; /* prefix windows titles with VM name (for WM without support for _QUBES_VMNAME property) */
enum trayicon_mode trayicon_mode; /* trayicon coloring mode */
Expand Down Expand Up @@ -348,6 +350,7 @@ struct clipboard_metadata {
unsigned int buffer_size; /* maximum allowed clipboard size for this vm */
uint32_t protocol_version_vmside; /* inter-vm clipboard GUI protocol version - vmside */
uint32_t protocol_version_xside; /* inter-vm clipboard GUI protocol version - xside */
unsigned int timeout; /* 0: Clipboard is/will be NOT automatically cleared. x: is/will be cleared after x seconds */
};

#define QUBES_NO_SHM_SEGMENT ((xcb_shm_seg_t)-1)
Expand Down