Skip to content

Commit

Permalink
Add options global clipboard clearing
Browse files Browse the repository at this point in the history
Menu item to Global Clipboard widget to allow users to manually clear it
Also `global-clipboard-timeout` feature could be set per VM and/or GUIVM

resolves: QubesOS/qubes-issues#6641
  • Loading branch information
alimirjamali committed Oct 26, 2024
1 parent bab3289 commit 74c435f
Showing 1 changed file with 57 additions and 1 deletion.
58 changes: 57 additions & 1 deletion qui/clipboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@

import gi
gi.require_version('Gtk', '3.0') # isort:skip
from gi.repository import Gtk, Gio, Gdk # isort:skip
from gi.repository import Gtk, Gio, Gdk, GLib # isort:skip

import gbulb
import pyinotify
Expand Down Expand Up @@ -93,6 +93,29 @@ def _copy(self, vmname: str = None):

self.gtk_app.update_clipboard_contents(vmname, size, message=body)

if self.gtk_app.clear_event_source_id is not None:
# If there is any clipboard clearing event in event loop, cancel it
GLib.source_remove(self.gtk_app.clear_event_source_id)
self.gtk_app.clear_event_source_id = None

try:
clear_timeout = self.gtk_app.qapp.domains[vmname].features[ \
"global-clipboard-timeout"]
except qubesadmin.exc.QubesException:
clear_timeout = None
if clear_timeout is None:
try:
clear_timeout = self.gtk_app.qapp.domains[ \
self.gtk_app.qapp.local_name].features[ \
"global-clipboard-timeout"]
except qubesadmin.exc.QubesException:
clear_timeout = None
if clear_timeout is not None and clear_timeout.isnumeric() and \
int(clear_timeout) > 0:
self.gtk_app.clear_event_source_id = GLib.timeout_add_seconds(
int(clear_timeout), self.gtk_app.clear_clipboard_event, \
os.path.getmtime(DATA))

def _paste(self):
''' Sends Paste notification via Gio.Notification.
'''
Expand Down Expand Up @@ -175,6 +198,8 @@ def __init__(self, wm, qapp, dispatcher, **properties):
self.wm = wm
self.temporary_watch = None

self.clear_event_source_id: int = None

if not os.path.exists(FROM):
# pylint: disable=no-member
self.temporary_watch = \
Expand Down Expand Up @@ -260,6 +285,10 @@ def setup_ui(self, *_args, **_kwargs):
dom0_item.connect('activate', self.copy_dom0_clipboard)
self.menu.append(dom0_item)

clear_item = Gtk.MenuItem(_("Clear global clipboard"))
clear_item.connect('activate', self.clear_clipboard)
self.menu.append(clear_item)

def copy_dom0_clipboard(self, *_args, **_kwargs):
clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
text = clipboard.wait_for_text()
Expand Down Expand Up @@ -302,6 +331,33 @@ def _convert_to_readable(key: str):
return key.upper()
return key

def clear_clipboard(self, *_args, **_kwargs):
try:
with appviewer_lock():
if os.path.exists(DATA):
os.unlink(DATA)
if os.path.exists(FROM):
os.unlink(FROM)
if os.path.exists(XEVENT):
os.unlink(XEVENT)
self.update_clipboard_contents(vm=None, size=0, message= \
_("Global clipboard cleared"))
except Exception: # pylint: disable=broad-except
self.send_notify(_("Error while clearing global clipboard!"))

def clear_clipboard_event(self, modification_time: float) -> bool:
""" this event is called via GLib if source VM and/or GUIVM has
`global-clipboard-timeout=<SECONDS>` feature set and it times out """
if self.clear_event_source_id:
# first we remove the event from event loop
GLib.source_remove(self.clear_event_source_id)
self.clear_event_source_id = None
if os.path.getmtime(DATA) != modification_time:
# some other clipboard copy action has happened in the meantime
return False
self.clear_clipboard()
return False

def main():
loop = asyncio.get_event_loop()
wm = pyinotify.WatchManager()
Expand Down

0 comments on commit 74c435f

Please sign in to comment.