Add /etc/qubes/applications override, use it for gnome-terminal
Used by qubes.StartApp so that we can override distribution-provided .desktop files. The mechanism is introduced to run gnome-terminal with --wait option, so that it's compatible with DispVMs. Fixes QubesOS/qubes-issues#2581.
This commit is contained in:
parent
943f37b481
commit
3a6e77aa43
1
Makefile
1
Makefile
@ -186,6 +186,7 @@ install-doc:
|
|||||||
|
|
||||||
install-common: install-doc
|
install-common: install-doc
|
||||||
$(MAKE) -C autostart-dropins install
|
$(MAKE) -C autostart-dropins install
|
||||||
|
$(MAKE) -C applications-dropins install
|
||||||
install -m 0644 -D misc/fstab $(DESTDIR)/etc/fstab
|
install -m 0644 -D misc/fstab $(DESTDIR)/etc/fstab
|
||||||
|
|
||||||
# force /usr/bin before /bin to have /usr/bin/python instead of /bin/python
|
# force /usr/bin before /bin to have /usr/bin/python instead of /bin/python
|
||||||
|
6
applications-dropins/Makefile
Normal file
6
applications-dropins/Makefile
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
DROPINS_DIR = /etc/qubes/applications
|
||||||
|
|
||||||
|
install:
|
||||||
|
for f in *.desktop; do install -m 0644 -D $$f $(DESTDIR)$(DROPINS_DIR)/$$f.d/30_qubes.conf; done
|
||||||
|
install -m 0644 README.txt $(DESTDIR)$(DROPINS_DIR)/
|
20
applications-dropins/README.txt
Normal file
20
applications-dropins/README.txt
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
This directory (/etc/qubes/applications) is used to override parts of files in
|
||||||
|
/usr/share/applications and other applications directories.
|
||||||
|
|
||||||
|
For each desktop file there, you can create directory named after the file plus
|
||||||
|
".d", then place files there. All such files will be read (in lexicographical
|
||||||
|
order) and lines specified there will override respective entries in the
|
||||||
|
original file.
|
||||||
|
|
||||||
|
This can be used for example to override behaviour of a specific application in
|
||||||
|
particular VM type.
|
||||||
|
|
||||||
|
For example, you can extend `/usr/share/applications/firefox.desktop` by
|
||||||
|
creating `/etc/qubes/applications/firefox.desktop.d/50_user.conf` with:
|
||||||
|
```
|
||||||
|
[Desktop Entry]
|
||||||
|
Exec=firefox --private-window http://example.com %u
|
||||||
|
```
|
||||||
|
|
||||||
|
This would mean that `Exec` key would be read as your command line, regardless
|
||||||
|
of original entry in `/usr/share/applications/firefox.desktop`.
|
2
applications-dropins/org.gnome.Terminal.desktop
Normal file
2
applications-dropins/org.gnome.Terminal.desktop
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Exec=qubes-run-gnome-terminal
|
@ -33,7 +33,7 @@ noextract=()
|
|||||||
md5sums=(SKIP)
|
md5sums=(SKIP)
|
||||||
|
|
||||||
build() {
|
build() {
|
||||||
for source in autostart-dropins qubes-rpc qrexec misc Makefile vm-init.d vm-systemd network init version doc setup.py qubesagent post-install.d; do
|
for source in autostart-dropins applications-dropins qubes-rpc qrexec misc Makefile vm-init.d vm-systemd network init version doc setup.py qubesagent post-install.d; do
|
||||||
# shellcheck disable=SC2154
|
# shellcheck disable=SC2154
|
||||||
(ln -s "$srcdir/../$source" "$srcdir/$source")
|
(ln -s "$srcdir/../$source" "$srcdir/$source")
|
||||||
done
|
done
|
||||||
|
1
debian/qubes-core-agent.install
vendored
1
debian/qubes-core-agent.install
vendored
@ -36,6 +36,7 @@ etc/qubes-rpc/qubes.WaitForSession
|
|||||||
etc/qubes-rpc/qubes.GetDate
|
etc/qubes-rpc/qubes.GetDate
|
||||||
etc/qubes-suspend-module-blacklist
|
etc/qubes-suspend-module-blacklist
|
||||||
etc/qubes/autostart/*
|
etc/qubes/autostart/*
|
||||||
|
etc/qubes/applications/*
|
||||||
etc/qubes/post-install.d/README
|
etc/qubes/post-install.d/README
|
||||||
etc/qubes/post-install.d/*.sh
|
etc/qubes/post-install.d/*.sh
|
||||||
etc/qubes/rpc-config/qubes.OpenInVM
|
etc/qubes/rpc-config/qubes.OpenInVM
|
||||||
|
@ -24,23 +24,14 @@
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
from xdg.DesktopEntry import DesktopEntry
|
from xdg.DesktopEntry import DesktopEntry
|
||||||
from qubesagent.xdg import launch
|
from qubesagent.xdg import launch, find_dropins, \
|
||||||
|
load_desktop_entry_with_dropins
|
||||||
import xdg.BaseDirectory
|
import xdg.BaseDirectory
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
||||||
QUBES_XDG_CONFIG_DROPINS = '/etc/qubes/autostart'
|
QUBES_XDG_CONFIG_DROPINS = '/etc/qubes/autostart'
|
||||||
|
|
||||||
def open_desktop_entry_and_dropins(filename):
|
|
||||||
desktop_entry = DesktopEntry(filename)
|
|
||||||
dropins_dir = os.path.join(QUBES_XDG_CONFIG_DROPINS,
|
|
||||||
os.path.basename(filename) + '.d')
|
|
||||||
if os.path.isdir(dropins_dir):
|
|
||||||
for dropin in sorted(os.listdir(dropins_dir)):
|
|
||||||
dropin_content = DesktopEntry(os.path.join(dropins_dir, dropin))
|
|
||||||
desktop_entry.content.update(dropin_content.content)
|
|
||||||
|
|
||||||
return desktop_entry
|
|
||||||
|
|
||||||
|
|
||||||
def entry_should_be_started(entry, environments):
|
def entry_should_be_started(entry, environments):
|
||||||
"""
|
"""
|
||||||
@ -80,7 +71,10 @@ def process_autostart(environments):
|
|||||||
entry_path = os.path.join(path, entry_name)
|
entry_path = os.path.join(path, entry_name)
|
||||||
# files in $HOME have higher priority than dropins
|
# files in $HOME have higher priority than dropins
|
||||||
if not path.startswith(xdg.BaseDirectory.xdg_config_home):
|
if not path.startswith(xdg.BaseDirectory.xdg_config_home):
|
||||||
entry = open_desktop_entry_and_dropins(entry_path)
|
dropins = find_dropins(
|
||||||
|
entry_path, QUBES_XDG_CONFIG_DROPINS)
|
||||||
|
entry = load_desktop_entry_with_dropins(
|
||||||
|
entry_path, dropins)
|
||||||
else:
|
else:
|
||||||
entry = DesktopEntry(entry_path)
|
entry = DesktopEntry(entry_path)
|
||||||
if entry_should_be_started(entry, environments):
|
if entry_should_be_started(entry, environments):
|
||||||
|
67
qubesagent/test_xdg.py
Normal file
67
qubesagent/test_xdg.py
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
from unittest import TestCase
|
||||||
|
import tempfile
|
||||||
|
import shutil
|
||||||
|
import os
|
||||||
|
|
||||||
|
from qubesagent.xdg import find_dropins, load_desktop_entry_with_dropins, \
|
||||||
|
ini_to_string
|
||||||
|
|
||||||
|
|
||||||
|
class TestXdg(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.tempdir = tempfile.mkdtemp()
|
||||||
|
self.addCleanup(shutil.rmtree, self.tempdir)
|
||||||
|
|
||||||
|
def test_00_load_desktop_entry(self):
|
||||||
|
filename = os.path.join(self.tempdir, 'firefox.desktop')
|
||||||
|
dropins_dir = os.path.join(self.tempdir, 'dropins')
|
||||||
|
dropin_filename = os.path.join(
|
||||||
|
self.tempdir, 'dropins', 'firefox.desktop.d', '030_qubes.conf')
|
||||||
|
|
||||||
|
with open(filename, 'w') as f:
|
||||||
|
f.write('''\
|
||||||
|
[Desktop Entry]
|
||||||
|
Name=Firefox
|
||||||
|
Exec=firefox %u
|
||||||
|
''')
|
||||||
|
|
||||||
|
os.makedirs(os.path.dirname(dropin_filename))
|
||||||
|
with open(dropin_filename, 'w') as f:
|
||||||
|
f.write('''\
|
||||||
|
[Desktop Entry]
|
||||||
|
Exec=my-firefox %u
|
||||||
|
|
||||||
|
[Other Group]
|
||||||
|
X-Key=yes
|
||||||
|
''')
|
||||||
|
|
||||||
|
dropins = find_dropins(filename, dropins_dir)
|
||||||
|
self.assertListEqual(
|
||||||
|
dropins,
|
||||||
|
[dropin_filename])
|
||||||
|
|
||||||
|
desktop_entry = load_desktop_entry_with_dropins(filename, dropins)
|
||||||
|
self.assertEqual(desktop_entry.content['Desktop Entry']['Name'],
|
||||||
|
'Firefox')
|
||||||
|
self.assertEqual(desktop_entry.content['Desktop Entry']['Exec'],
|
||||||
|
'my-firefox %u')
|
||||||
|
self.assertEqual(desktop_entry.content['Other Group']['X-Key'],
|
||||||
|
'yes')
|
||||||
|
|
||||||
|
def test_01_init_to_string(self):
|
||||||
|
filename = os.path.join(self.tempdir, 'firefox.desktop')
|
||||||
|
content = '''\
|
||||||
|
[Desktop Entry]
|
||||||
|
Name=Firefox
|
||||||
|
Exec=firefox %u
|
||||||
|
|
||||||
|
[Other Group]
|
||||||
|
X-Key=yes
|
||||||
|
'''
|
||||||
|
|
||||||
|
with open(filename, 'w') as f:
|
||||||
|
f.write(content)
|
||||||
|
|
||||||
|
desktop_entry = load_desktop_entry_with_dropins(filename, [])
|
||||||
|
output = ini_to_string(desktop_entry)
|
||||||
|
self.assertEqual(output.rstrip(), content.rstrip())
|
@ -4,17 +4,78 @@ from gi.repository import Gio # pylint: disable=import-error
|
|||||||
from gi.repository import GLib # pylint: disable=import-error
|
from gi.repository import GLib # pylint: disable=import-error
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
import io
|
||||||
|
from xdg.DesktopEntry import DesktopEntry
|
||||||
|
|
||||||
|
|
||||||
|
DROPINS_DIR = '/etc/qubes/applications'
|
||||||
|
|
||||||
|
|
||||||
|
def find_dropins(filename, dropins_dir):
|
||||||
|
result = []
|
||||||
|
app_dropins_dir = os.path.join(
|
||||||
|
dropins_dir,
|
||||||
|
os.path.basename(filename) + '.d')
|
||||||
|
if os.path.isdir(app_dropins_dir):
|
||||||
|
for dropin in sorted(os.listdir(app_dropins_dir)):
|
||||||
|
result.append(
|
||||||
|
os.path.join(app_dropins_dir, dropin))
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def load_desktop_entry_with_dropins(filename, dropins):
|
||||||
|
desktop_entry = DesktopEntry(filename)
|
||||||
|
for dropin in dropins:
|
||||||
|
dropin_entry = DesktopEntry(dropin)
|
||||||
|
for group_name, group in dropin_entry.content.items():
|
||||||
|
desktop_entry.content.setdefault(group_name, {}).update(group)
|
||||||
|
return desktop_entry
|
||||||
|
|
||||||
|
|
||||||
|
def make_launcher(filename, dropins_dir=DROPINS_DIR):
|
||||||
|
dropins = find_dropins(filename, dropins_dir)
|
||||||
|
if not dropins:
|
||||||
|
return Gio.DesktopAppInfo.new_from_filename(filename)
|
||||||
|
|
||||||
|
desktop_entry = load_desktop_entry_with_dropins(filename, dropins)
|
||||||
|
|
||||||
|
data = GLib.Bytes(ini_to_string(desktop_entry).encode('utf-8'))
|
||||||
|
keyfile = GLib.KeyFile()
|
||||||
|
keyfile.load_from_bytes(data, 0)
|
||||||
|
return Gio.DesktopAppInfo.new_from_keyfile(keyfile)
|
||||||
|
|
||||||
|
|
||||||
|
def ini_to_string(ini):
|
||||||
|
# See IniFile.write() in xdg package.
|
||||||
|
|
||||||
|
output = io.StringIO()
|
||||||
|
if ini.defaultGroup:
|
||||||
|
output.write("[%s]\n" % ini.defaultGroup)
|
||||||
|
for (key, value) in ini.content[ini.defaultGroup].items():
|
||||||
|
output.write("%s=%s\n" % (key, value))
|
||||||
|
output.write("\n")
|
||||||
|
for (name, group) in ini.content.items():
|
||||||
|
if name != ini.defaultGroup:
|
||||||
|
output.write("[%s]\n" % name)
|
||||||
|
for (key, value) in group.items():
|
||||||
|
output.write("%s=%s\n" % (key, value))
|
||||||
|
output.write("\n")
|
||||||
|
|
||||||
|
return output.getvalue()
|
||||||
|
|
||||||
|
|
||||||
def pid_callback(launcher, pid, pid_list):
|
def pid_callback(launcher, pid, pid_list):
|
||||||
pid_list.append(pid)
|
pid_list.append(pid)
|
||||||
|
|
||||||
|
|
||||||
def dbus_name_change(loop, name, old_owner, new_owner):
|
def dbus_name_change(loop, name, old_owner, new_owner):
|
||||||
if not new_owner:
|
if not new_owner:
|
||||||
loop.quit()
|
loop.quit()
|
||||||
|
|
||||||
def launch(desktop, *files, **kwargs):
|
|
||||||
|
def launch(filename, *files, **kwargs):
|
||||||
wait = kwargs.pop('wait', True)
|
wait = kwargs.pop('wait', True)
|
||||||
launcher = Gio.DesktopAppInfo.new_from_filename(desktop)
|
launcher = make_launcher(filename)
|
||||||
try:
|
try:
|
||||||
import dbus
|
import dbus
|
||||||
from dbus.mainloop.glib import DBusGMainLoop
|
from dbus.mainloop.glib import DBusGMainLoop
|
||||||
|
@ -577,10 +577,13 @@ rm -f %{name}-%{version}
|
|||||||
%config(noreplace) /etc/qubes/rpc-config/qubes.InstallUpdatesGUI
|
%config(noreplace) /etc/qubes/rpc-config/qubes.InstallUpdatesGUI
|
||||||
%config(noreplace) /etc/qubes/rpc-config/qubes.VMShell+WaitForSession
|
%config(noreplace) /etc/qubes/rpc-config/qubes.VMShell+WaitForSession
|
||||||
%config(noreplace) /etc/qubes/rpc-config/qubes.VMExecGUI
|
%config(noreplace) /etc/qubes/rpc-config/qubes.VMExecGUI
|
||||||
%dir /etc/qubes/autostart
|
|
||||||
%config(noreplace) /etc/default/grub.qubes
|
%config(noreplace) /etc/default/grub.qubes
|
||||||
|
%dir /etc/qubes/autostart
|
||||||
/etc/qubes/autostart/README.txt
|
/etc/qubes/autostart/README.txt
|
||||||
%config /etc/qubes/autostart/*.desktop.d/30_qubes.conf
|
%config /etc/qubes/autostart/*.desktop.d/30_qubes.conf
|
||||||
|
%dir /etc/qubes/applications
|
||||||
|
/etc/qubes/applications/README.txt
|
||||||
|
%config /etc/qubes/applications/*.desktop.d/30_qubes.conf
|
||||||
%dir /etc/qubes/suspend-pre.d
|
%dir /etc/qubes/suspend-pre.d
|
||||||
/etc/qubes/suspend-pre.d/README
|
/etc/qubes/suspend-pre.d/README
|
||||||
%dir /etc/qubes/suspend-post.d
|
%dir /etc/qubes/suspend-post.d
|
||||||
@ -675,6 +678,7 @@ rm -f %{name}-%{version}
|
|||||||
%{python3_sitelib}/qubesagent/vmexec.py*
|
%{python3_sitelib}/qubesagent/vmexec.py*
|
||||||
%{python3_sitelib}/qubesagent/test_vmexec.py*
|
%{python3_sitelib}/qubesagent/test_vmexec.py*
|
||||||
%{python3_sitelib}/qubesagent/xdg.py*
|
%{python3_sitelib}/qubesagent/xdg.py*
|
||||||
|
%{python3_sitelib}/qubesagent/test_xdg.py*
|
||||||
|
|
||||||
/usr/share/qubes/mime-override/globs
|
/usr/share/qubes/mime-override/globs
|
||||||
/usr/share/qubes/qubes-master-key.asc
|
/usr/share/qubes/qubes-master-key.asc
|
||||||
|
Loading…
Reference in New Issue
Block a user