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

Fix support for desktop files with DBusActivatable=true #1031

Closed
marmarek opened this Issue Jun 15, 2015 · 11 comments

Comments

Projects
None yet
2 participants
@marmarek
Member

marmarek commented Jun 15, 2015

According to Desktop Entry Specification:

If the value is true then implementations should ignore the Exec key and send a D-Bus message to launch the application. See D-Bus Activation for more information on how this works. Applications should still include Exec= lines in their desktop files for compatibility with implementations that do not understand the DBusActivatable key.

Apparently our current solution (usage of glib to handle desktop files) does not handle those files correctly
Details here: https://groups.google.com/d/msgid/qubes-users/557CD10B.8020908%40gmail.com

@marmarek marmarek added this to the Release 3.0 milestone Aug 4, 2015

@marmarek

This comment has been minimized.

Show comment
Hide comment
@marmarek

marmarek Aug 4, 2015

Member

Setting priority to major, since this affects basic applications like Nautilus.

Member

marmarek commented Aug 4, 2015

Setting priority to major, since this affects basic applications like Nautilus.

@nrgaway

This comment has been minimized.

Show comment
Hide comment
@nrgaway

nrgaway Aug 4, 2015

I wrote a fix (referenced in that thread) that may work.

nrgaway commented Aug 4, 2015

I wrote a fix (referenced in that thread) that may work.

@nrgaway

This comment has been minimized.

Show comment
Hide comment
@nrgaway

nrgaway Aug 4, 2015

I stopped working on it since it seemed you had corrected the issue. Want me to continue to work on it?

user@untrusted:~/src$ cat qubes-desktop-run
#! /usr/bin/env python
# -*- coding: utf-8 -*-
# vim: set ft=python ts=4 sw=4 sts=4 et :

import sys
from gi.repository import Gio, GLib

def main(myname, desktop, *files):
    info = Gio.DesktopAppInfo()
    dbus_activate = False

    try:
        launcher = info.new_from_filename(desktop)
    except TypeError:
        launcher = None

    if launcher:
        dbus_activate = launcher.get_boolean('DBusActivatable')

    if not dbus_activate:
        launcher.launch(files, None)

    else:
        #import subprocess
        #if dbus_activate:
        #    print launcher.get_id().replace('.desktop', '')
        #    process = subprocess.Popen(['gapplication', 'launch', launcher.get_id().replace('.desktop', '')], close_fds=True)
        #return

        # (Gio.DBusConnection) – A Gio.DBusConnection
        session = Gio.bus_get_sync(Gio.BusType.SESSION, None)

        # (Gio.DBusProxyFlags) – Flags used when constructing the proxy.
        flags = Gio.DBusProxyFlags.NONE  

        # (Gio.DBusInterfaceInfo|None) – A Gio.DBusInterfaceInfo specifying the minimal interface that proxy conforms to or None.
        info = None

        # (str or None) – A bus name (well-known or unique) or None if connection is not a message bus connection.
        app_id = launcher.get_id()
        if app_id.endswith('.desktop'):
            app_id = app_id[:-8]

        # (str) – An object path.
        object_path = '/' + app_id.replace('.', '/').replace('-', '_')

        # (str) – A D-Bus interface name.
        interface_name = 'org.freedesktop.Application'

        # cancellable (Gio.Cancellable or None) – A Gio.Cancellable or None.
        cancellable = None

        proxy = Gio.DBusProxy.new_sync(session, 
                                       flags,
                                       info,
                                       app_id,
                                       object_path,
                                       interface_name,
                                       cancellable
                                       )

        builder = GLib.VariantBuilder.new(GLib.VariantType.new('r'))

        if files:
            builder.open(GLib.VariantType.new('as'))
            for filename in files:
                print 'Adding ', filename
                builder.add_value(GLib.Variant('s', filename))
            builder.close()

        builder.open(GLib.VariantType.new('a{sv}'))
        #startup_id = GLib.getenv("DESKTOP_STARTUP_ID") or app_id
        #if startup_id:
        #    #builder.add_value(GLib.Variant('a{sv}', {'startup-id': GLib.Variant('s', startup_id)}))
        builder.close()

        parameters = builder.end()

        print 'Calling call_sync...'
        if files:
            print 'Open...'
            result = proxy.call_sync ("Open", parameters, Gio.DBusCallFlags.NONE, -1, cancellable)
        else:
            print 'Activate...'
            result = proxy.call_sync ("Activate", parameters, Gio.DBusCallFlags.NONE, -1, cancellable)

        print 'Result: ', result
        print 'Done.'

if __name__ == "__main__":
    main(*sys.argv)

nrgaway commented Aug 4, 2015

I stopped working on it since it seemed you had corrected the issue. Want me to continue to work on it?

user@untrusted:~/src$ cat qubes-desktop-run
#! /usr/bin/env python
# -*- coding: utf-8 -*-
# vim: set ft=python ts=4 sw=4 sts=4 et :

import sys
from gi.repository import Gio, GLib

def main(myname, desktop, *files):
    info = Gio.DesktopAppInfo()
    dbus_activate = False

    try:
        launcher = info.new_from_filename(desktop)
    except TypeError:
        launcher = None

    if launcher:
        dbus_activate = launcher.get_boolean('DBusActivatable')

    if not dbus_activate:
        launcher.launch(files, None)

    else:
        #import subprocess
        #if dbus_activate:
        #    print launcher.get_id().replace('.desktop', '')
        #    process = subprocess.Popen(['gapplication', 'launch', launcher.get_id().replace('.desktop', '')], close_fds=True)
        #return

        # (Gio.DBusConnection) – A Gio.DBusConnection
        session = Gio.bus_get_sync(Gio.BusType.SESSION, None)

        # (Gio.DBusProxyFlags) – Flags used when constructing the proxy.
        flags = Gio.DBusProxyFlags.NONE  

        # (Gio.DBusInterfaceInfo|None) – A Gio.DBusInterfaceInfo specifying the minimal interface that proxy conforms to or None.
        info = None

        # (str or None) – A bus name (well-known or unique) or None if connection is not a message bus connection.
        app_id = launcher.get_id()
        if app_id.endswith('.desktop'):
            app_id = app_id[:-8]

        # (str) – An object path.
        object_path = '/' + app_id.replace('.', '/').replace('-', '_')

        # (str) – A D-Bus interface name.
        interface_name = 'org.freedesktop.Application'

        # cancellable (Gio.Cancellable or None) – A Gio.Cancellable or None.
        cancellable = None

        proxy = Gio.DBusProxy.new_sync(session, 
                                       flags,
                                       info,
                                       app_id,
                                       object_path,
                                       interface_name,
                                       cancellable
                                       )

        builder = GLib.VariantBuilder.new(GLib.VariantType.new('r'))

        if files:
            builder.open(GLib.VariantType.new('as'))
            for filename in files:
                print 'Adding ', filename
                builder.add_value(GLib.Variant('s', filename))
            builder.close()

        builder.open(GLib.VariantType.new('a{sv}'))
        #startup_id = GLib.getenv("DESKTOP_STARTUP_ID") or app_id
        #if startup_id:
        #    #builder.add_value(GLib.Variant('a{sv}', {'startup-id': GLib.Variant('s', startup_id)}))
        builder.close()

        parameters = builder.end()

        print 'Calling call_sync...'
        if files:
            print 'Open...'
            result = proxy.call_sync ("Open", parameters, Gio.DBusCallFlags.NONE, -1, cancellable)
        else:
            print 'Activate...'
            result = proxy.call_sync ("Activate", parameters, Gio.DBusCallFlags.NONE, -1, cancellable)

        print 'Result: ', result
        print 'Done.'

if __name__ == "__main__":
    main(*sys.argv)
@marmarek

This comment has been minimized.

Show comment
Hide comment
@marmarek

marmarek Aug 4, 2015

Member

Since apparently the issue also affects Fedora (where
DBUS_SESSION_BUS_ADDRESS is properly set), I think its good idea to
resurrect your patch.

Best Regards,
Marek Marczykowski-Górecki
Invisible Things Lab
A: Because it messes up the order in which people normally read text.
Q: Why is top-posting such a bad thing?

Member

marmarek commented Aug 4, 2015

Since apparently the issue also affects Fedora (where
DBUS_SESSION_BUS_ADDRESS is properly set), I think its good idea to
resurrect your patch.

Best Regards,
Marek Marczykowski-Górecki
Invisible Things Lab
A: Because it messes up the order in which people normally read text.
Q: Why is top-posting such a bad thing?

@nrgaway

This comment has been minimized.

Show comment
Hide comment
@nrgaway

nrgaway Aug 4, 2015

On 4 August 2015 at 12:45, Marek Marczykowski-Górecki <
notifications@github.com> wrote:

Since apparently the issue also affects Fedora (where
DBUS_SESSION_BUS_ADDRESS is properly set), I think its good idea to
resurrect your patch.

I'll work on it tonight; From what I recall it works by using DBUS to
activate when DBUS is available in .desktop file, otherwise I am not sure
if I implemented the exec functionally yet, but the hard stuff was working
(passing files works as well)

nrgaway commented Aug 4, 2015

On 4 August 2015 at 12:45, Marek Marczykowski-Górecki <
notifications@github.com> wrote:

Since apparently the issue also affects Fedora (where
DBUS_SESSION_BUS_ADDRESS is properly set), I think its good idea to
resurrect your patch.

I'll work on it tonight; From what I recall it works by using DBUS to
activate when DBUS is available in .desktop file, otherwise I am not sure
if I implemented the exec functionally yet, but the hard stuff was working
(passing files works as well)

@marmarek

This comment has been minimized.

Show comment
Hide comment
@marmarek

marmarek Aug 4, 2015

Member

Normal Exec= handling works well in the current solution. Of course it
would be better to not reimplement all of that and use some standard
way.
From what I see, the current implementation works well if the target
process is already running. If not, it is properly started, but somehow
method call got missed (so the first call fails, but the second
succeed). Maybe it would be enough to just trigger application start
(obtain dbus object handle?), then use the current one-liner
implementation (launcher.launch(files, None)).

Best Regards,
Marek Marczykowski-Górecki
Invisible Things Lab
A: Because it messes up the order in which people normally read text.
Q: Why is top-posting such a bad thing?

Member

marmarek commented Aug 4, 2015

Normal Exec= handling works well in the current solution. Of course it
would be better to not reimplement all of that and use some standard
way.
From what I see, the current implementation works well if the target
process is already running. If not, it is properly started, but somehow
method call got missed (so the first call fails, but the second
succeed). Maybe it would be enough to just trigger application start
(obtain dbus object handle?), then use the current one-liner
implementation (launcher.launch(files, None)).

Best Regards,
Marek Marczykowski-Górecki
Invisible Things Lab
A: Because it messes up the order in which people normally read text.
Q: Why is top-posting such a bad thing?

@nrgaway

This comment has been minimized.

Show comment
Hide comment
@nrgaway

nrgaway Aug 4, 2015

On 4 August 2015 at 13:26, Marek Marczykowski-Górecki <
notifications@github.com> wrote:

Normal Exec= handling works well in the current solution. Of course it
would be better to not reimplement all of that and use some standard
way.
From what I see, the current implementation works well if the target
process is already running. If not, it is properly started, but somehow
method call got missed (so the first call fails, but the second
succeed). Maybe it would be enough to just trigger application start
(obtain dbus object handle?), then use the current one-liner
implementation (launcher.launch(files, None)).

The script I pasted here works well for DBUS. I had tested with Debian and
Nautilus and it started Nautilus every time. It will activate when needed
and follows the standards.

One reason to start an application via DBUS is for security purposes since
apps started via DBUS are handled differently.

I'm actually pretty sure the script is actually mostly complete and will
handle DBUS and non DBUS. I just need to look for any other copies that
may be more recent.

if not dbus_activate:
launcher.launch(files, None)

nrgaway commented Aug 4, 2015

On 4 August 2015 at 13:26, Marek Marczykowski-Górecki <
notifications@github.com> wrote:

Normal Exec= handling works well in the current solution. Of course it
would be better to not reimplement all of that and use some standard
way.
From what I see, the current implementation works well if the target
process is already running. If not, it is properly started, but somehow
method call got missed (so the first call fails, but the second
succeed). Maybe it would be enough to just trigger application start
(obtain dbus object handle?), then use the current one-liner
implementation (launcher.launch(files, None)).

The script I pasted here works well for DBUS. I had tested with Debian and
Nautilus and it started Nautilus every time. It will activate when needed
and follows the standards.

One reason to start an application via DBUS is for security purposes since
apps started via DBUS are handled differently.

I'm actually pretty sure the script is actually mostly complete and will
handle DBUS and non DBUS. I just need to look for any other copies that
may be more recent.

if not dbus_activate:
launcher.launch(files, None)

@marmarek

This comment has been minimized.

Show comment
Hide comment
@marmarek

marmarek Aug 4, 2015

Member

Interesting - using launcher.launch(files, None) manually from python console works at first shot!
More interestingly just adding time.sleep(1) at the end of qubes-desktop-run also makes it working. So probably launcher.launch is an async call, which require script to be running until got finished (most likely to keep dbus connection active). So need to find a way to make this call synchronous, or wait for its completion.

Much more preferred than reimplementing part of launcher.launch method.

Member

marmarek commented Aug 4, 2015

Interesting - using launcher.launch(files, None) manually from python console works at first shot!
More interestingly just adding time.sleep(1) at the end of qubes-desktop-run also makes it working. So probably launcher.launch is an async call, which require script to be running until got finished (most likely to keep dbus connection active). So need to find a way to make this call synchronous, or wait for its completion.

Much more preferred than reimplementing part of launcher.launch method.

@nrgaway

This comment has been minimized.

Show comment
Hide comment
@nrgaway

nrgaway Aug 4, 2015

nrgaway commented Aug 4, 2015

@marmarek

This comment has been minimized.

Show comment
Hide comment
@marmarek

marmarek Aug 4, 2015

Member

Yes, this would be much better! I think better to use subprocess, because the current script already handles file parsing, so you can just check launcher.get_boolean('DBusActivatable').

Member

marmarek commented Aug 4, 2015

Yes, this would be much better! I think better to use subprocess, because the current script already handles file parsing, so you can just check launcher.get_boolean('DBusActivatable').

@nrgaway

This comment has been minimized.

Show comment
Hide comment
@nrgaway

nrgaway Aug 4, 2015

On 4 August 2015 at 14:39, Marek Marczykowski-Górecki <
notifications@github.com> wrote:

Yes, this would be much better! I think better to use subprocess, because
the current script already handles file parsing, so you can just check
launcher.get_boolean('DBusActivatable').

Sure thing, will take care of it tonight!

nrgaway commented Aug 4, 2015

On 4 August 2015 at 14:39, Marek Marczykowski-Górecki <
notifications@github.com> wrote:

Yes, this would be much better! I think better to use subprocess, because
the current script already handles file parsing, so you can just check
launcher.get_boolean('DBusActivatable').

Sure thing, will take care of it tonight!

marmarek added a commit to marmarek/old-qubes-core-agent-linux that referenced this issue Aug 27, 2015

qubes-desktop-run: start the Dbus service (if needed)
Much tidier way to solve the issue, provided by @unman.

QubesOS/qubes-issues#1031

marmarek added a commit to marmarek/old-qubes-core-agent-linux that referenced this issue Sep 29, 2015

qubes-desktop-run: start the Dbus service (if needed)
Much tidier way to solve the issue, provided by @unman.

QubesOS/qubes-issues#1031

(cherry picked from commit 93e0904)

@marmarek marmarek referenced this issue Feb 8, 2016

Closed

D-Bus Error #1138

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment