Skip to content

Commit

Permalink
Handle HTTP(S) & FTP URLs in dom0 & GUIVMs
Browse files Browse the repository at this point in the history
A new `qubes-virtual-browser` tool is made to handle URLs within dom0
and/or GUIVMs. It could safely open URLs via DisposableVMs, copy them to
global clipboard or discard them.

resolves: QubesOS/qubes-issues#8171
  • Loading branch information
alimirjamali committed Oct 29, 2024
1 parent bab3289 commit bc0a902
Show file tree
Hide file tree
Showing 10 changed files with 662 additions and 10 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,4 @@ ENV/
*.swp

*.~undo-tree~
*.glade~
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ install-autostart:
cp desktop/qubes-global-config.desktop $(DESTDIR)/usr/share/applications/
cp desktop/qubes-new-qube.desktop $(DESTDIR)/usr/share/applications/
cp desktop/qubes-policy-editor-gui.desktop $(DESTDIR)/usr/share/applications/
cp desktop/qubes-virtual-browser.desktop $(DESTDIR)/usr/share/applications/
xdg-settings set default-web-browser qubes-virtual-browser.desktop

install-lang:
mkdir -p $(DESTDIR)/usr/share/gtksourceview-4/language-specs/
Expand Down
13 changes: 13 additions & 0 deletions desktop/qubes-virtual-browser.desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
### Note: With this installed, typing "xdg-settings set default-web-browser qubes-virtual-browser.desktop" will make it so that in gnome-terminal
### (typing "xdg-settings set default-web-browser firefox.desktop" will put it back to normal)

[Desktop Entry]
Version=1.0
Name=Qubes Virtual Browser
Exec=/usr/bin/qubes-virtual-browser %u
Icon=qubes-manager
Terminal=false
Type=Application
Categories=Network;WebBrowser;
MimeType=text/html;text/xml;application/xhtml+xml;application/vnd.mozilla.xul+xml;text/mml;x-scheme-handler/http;x-scheme-handler/https;
NoDisplay=true
203 changes: 203 additions & 0 deletions qubes_config/global_config.glade
Original file line number Diff line number Diff line change
Expand Up @@ -6759,6 +6759,209 @@ Inter-qube copy and paste actions are performed via special keyboard shortcuts,
<property name="position">11</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Qubes Virtual Browser</property>
<property name="xalign">0</property>
<style>
<class name="group_title"/>
<class name="section_title"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">12</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Qubes Virtual Browser is used to safely handle URLs in dom0 or GUIVMs. From opening online help of XFCE widgets to opening online help for this program in appropriate DisposableVM; or copying them to global clipboard. By default it asks for suitable action.</property>
<property name="use-markup">True</property>
<property name="wrap">True</property>
<property name="xalign">0</property>
<style>
<class name="explanation"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">13</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="virtual_browser_ask">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="active">True</property>
<property name="draw-indicator">True</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">&lt;b&gt;Ask&lt;/b&gt; for each URL (default)</property>
<property name="use-markup">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
</child>
<style>
<class name="enable_opts"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">14</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="spacing">10</property>
<child>
<object class="GtkRadioButton" id="virtual_browser_dispvm">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="draw-indicator">True</property>
<property name="group">virtual_browser_ask</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="spacing">10</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Open all URLs in &lt;b&gt;DisposableVM&lt;/b&gt;:</property>
<property name="use-markup">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
</child>
<style>
<class name="enable_opts"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="virtual_browser_dispvms">
<property name="visible">True</property>
<property name="can-focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">16</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="virtual_browser_clipboard">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="draw-indicator">True</property>
<property name="group">virtual_browser_ask</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Copy all URLs to &lt;b&gt;Global Clipboard&lt;/b&gt;</property>
<property name="use-markup">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
</child>
<style>
<class name="enable_opts"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">17</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="virtual_browser_discard">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="draw-indicator">True</property>
<property name="group">virtual_browser_ask</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">&lt;b&gt;Discard&lt;/b&gt; all URLs without asking</property>
<property name="use-markup">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
</child>
<style>
<class name="enable_opts"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">18</property>
</packing>
</child>
<style>
<class name="content_box"/>
</style>
Expand Down
52 changes: 52 additions & 0 deletions qubes_config/global_config/global_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@

gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GLib, GObject
from gi.repository.GdkPixbuf import Pixbuf

logger = logging.getLogger('qubes-global-config')

Expand Down Expand Up @@ -112,6 +113,57 @@ def __init__(self, qapp: qubesadmin.Qubes,
)
]

self.virtual_browser_ask: Gtk.RadioButton = \
gtk_builder.get_object('virtual_browser_ask')
self.virtual_browser_dispvm: Gtk.RadioButton = \
gtk_builder.get_object('virtual_browser_dispvm')
self.virtual_browser_clipboard: Gtk.RadioButton = \
gtk_builder.get_object('virtual_browser_clipboard')
self.virtual_browser_discard: Gtk.RadioButton = \
gtk_builder.get_object('virtual_browser_discard')

action = qapp.domains[qapp.local_name].features.get( \
"virtual-browser-action", "")
if action.startswith('disposable:') and action[11:] in qapp.domains:
self.virtual_browser_dispvm.set_active(True)
elif action == 'clipboard':
self.virtual_browser_clipboard.set_active(True)
elif action == 'discard':
self.virtual_browser_discard.set_active(True)
else:
self.virtual_browser_ask.set_active(True)

self.virtual_browser_dispvms: Gtk.ComboBox = \
gtk_builder.get_object('virtual_browser_dispvms')
self.disposables = Gtk.ListStore(object, Pixbuf, str)
self.virtual_browser_dispvms.set_model(self.disposables)
self.renderer_icon = Gtk.CellRendererPixbuf()
self.renderer_vmname = Gtk.CellRendererText()
self.virtual_browser_dispvms.pack_start(self.renderer_icon, True)
self.virtual_browser_dispvms.pack_start(self.renderer_vmname, True)
self.virtual_browser_dispvms.add_attribute( \
self.renderer_icon, "pixbuf", 1)
self.virtual_browser_dispvms.add_attribute( \
self.renderer_vmname, "text", 2)

default_dispvm = getattr(qapp, "default_dispvm", None)
for domain in qapp.domains:
if getattr(domain, "template_for_dispvms", False):
# pylint: disable=no-member
icon = Gtk.IconTheme.get_default().load_icon(
getattr(domain, "icon", "qubes-manager"), 32, 0)
row = self.disposables.append([domain, icon, domain.name])
if domain.name == default_dispvm:
self.disposables[row][2] += " (Default DispVM Template)"
if self.virtual_browser_dispvm.get_active():
if domain.name == action[11:]:
self.virtual_browser_dispvms.set_active( \
len(self.disposables) - 1)
elif domain.name == default_dispvm:
self.virtual_browser_dispvms.set_active( \
len(self.disposables) - 1)


def reset(self):
for handler in self.handlers:
handler.reset()
Expand Down
10 changes: 4 additions & 6 deletions qubes_config/widgets/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,17 +108,15 @@ def compare_rule_lists(rule_list_1: List[Rule],
return False
return True

def _open_url_in_dvm(url, default_dvm: qubesadmin.vm.QubesVM):
def _open_url(url):
subprocess.run(
['qvm-run', '-p', '--service', f'--dispvm={default_dvm}',
'qubes.OpenURL'], input=url.encode(), check=False,
['qubes-virtual-browser', url.encode()], input=None, check=False,
stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)

def open_url_in_disposable(url: str, qapp: qubesadmin.Qubes):
"""Open provided url in disposable qube based on default disposable
template"""
default_dvm = qapp.default_dispvm
open_thread = threading.Thread(group=None,
target=_open_url_in_dvm,
args=[url, default_dvm])
target=_open_url,
args=[url])
open_thread.start()
Loading

0 comments on commit bc0a902

Please sign in to comment.