Skip to content

Instantly share code, notes, and snippets.

@ssokolow
Last active October 26, 2025 11:24
Show Gist options
  • Select an option

  • Save ssokolow/db565fd8a82d6002baada946adb81f68 to your computer and use it in GitHub Desktop.

Select an option

Save ssokolow/db565fd8a82d6002baada946adb81f68 to your computer and use it in GitHub Desktop.

Revisions

  1. ssokolow revised this gist May 27, 2024. 1 changed file with 3 additions and 2 deletions.
    5 changes: 3 additions & 2 deletions update_flatpak_cli.py
    Original file line number Diff line number Diff line change
    @@ -86,12 +86,13 @@
    #: `STRIP_SCRIPT_EXTS`.
    STRIP_REVERSE_DNS = True

    #: If not None, strip `_wrapper` or `-wrapper` from the end of the command
    #: If not None, strip matching suffixes from the end of the command
    #: name, removing the need to special-case retrieved command names like
    #: `scummvm_wrapper`. This will be applied after `STRIP_SCRIPT_EXTS`.
    #:
    #: Will be applied after `STRIP_SCRIPT_EXTS` and `STRIP_REVERSE_DNS`
    STRIP_WRAPPER_SUFFIX = re.compile("[_-]wrapper$", re.IGNORECASE)
    STRIP_WRAPPER_SUFFIX = re.compile("[_-](wrapper|launcher)$", re.IGNORECASE)


    #: Remappings for flatpak packages that use less-than-ideal command names
    CMD_REMAPPINGS = {
  2. ssokolow revised this gist Sep 25, 2023. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions update_flatpak_cli.py
    Original file line number Diff line number Diff line change
    @@ -96,6 +96,7 @@
    #: Remappings for flatpak packages that use less-than-ideal command names
    CMD_REMAPPINGS = {
    'io.github.simple64.simple64': 'simple64', # gui-wrapper.sh
    'org.godotengine.Godot3': 'godot3', # Collides with Godot 4
    'org.jdownloader.JDownloader': 'jdownloader', # jd-wrapper
    'org.ppsspp.PPSSPP': 'ppsspp', # PPSSPPDL
    'org.purei.Play': 'play_emu', # Collides with SoX's `play`
  3. ssokolow revised this gist Sep 23, 2022. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions update_flatpak_cli.py
    Original file line number Diff line number Diff line change
    @@ -20,6 +20,7 @@
    Known shortcomings:
    * Doesn't recognize file:// URLs and `@@u` wrap them yet.
    * Doesn't try parsing `--foo=/bar/baz` to find paths that need
    `--file-forwarding`, so use `--foo /bar/baz` instead.
    * Still need to look into the best way to query the set of `.desktop` files
  4. ssokolow revised this gist Sep 23, 2022. 1 changed file with 55 additions and 33 deletions.
    88 changes: 55 additions & 33 deletions update_flatpak_cli.py
    Original file line number Diff line number Diff line change
    @@ -20,12 +20,11 @@
    Known shortcomings:
    * Doesn't try parsing `--foo=/bar/baz` to find paths that need
    `--file-forwarding`, so use `--foo /bar/baz` instead.
    * Still need to look into the best way to query the set of `.desktop` files
    installed by Things like OpenRA so I don't need to *manually* amend the
    `EXTRA_COMMANDS` list in cases involving secondary GUI apps.
    * Need a way to override things like the
    `--player-operation-mode=pseudo-gui --` that's harvested from MPV's
    `.desktop` file and breaks stdout/stderr output.
    * Uses the sledgehammer approach of just removing all non-folders from the
    target directory before generating new launchers to clear out stale entries.
    (A proper solution would keep track of which ones it created, but that'd
    @@ -56,7 +55,7 @@
    SOFTWARE.
    """

    import re
    import re, shlex
    from distutils.spawn import find_executable
    import os.path

    @@ -128,11 +127,15 @@
    unset LD_PRELOAD
    # Make arguments that are existing paths absolute
    # and wrap them in the appropriate type of --file-forwarding
    # wrapper tokens.
    # (Necessary to make forwarding work reliably)
    declare -a args
    for arg in "$@"; do
    if [ -a "$arg" ]; then
    args+=("{fwd_wrap_start}")
    args+=("$(readlink -f "$arg")")
    args+=("@@")
    else
    args+=("$arg")
    fi
    @@ -142,7 +145,9 @@
    exec {flatpak_cmd}
    """

    arg_placeholder_re = re.compile("%[uUfF]")
    # Regex to match the argument type in a .desktop file so it can be converted
    # to the appropriate --file-forwarding marker type
    arg_placeholder_re = re.compile("%([uUfF])")


    def get_installed_packages() -> Dict[str, str]:
    @@ -170,42 +175,43 @@ def get_installed_packages() -> Dict[str, str]:
    def make_flatpak_cmd(ref: str, extra_args: str = '') -> str:
    """Construct a ``flatpak run`` command for the given arguments
    This is used for secondary commands and for the fallback for the primary
    command"""
    return (f'flatpak run {extra_args} --file-forwarding "{ref}" @@u '
    f'"${{args[@]}}" @@')
    This is used to avoid a needless indirection from calling the existing
    wrappers just to add things like `--file-forwarding` handling, and is
    preferred over harvesting the `.desktop` file `Exec` lines to avoid the
    complications of handling field codes like `%c`.
    """
    return (f'flatpak run {extra_args} --file-forwarding '
    f'"{ref}" "${{args[@]}}"')


    def wants_urls(ref: str) -> bool:
    """Attempt to parse the corresponding `.desktop` file to determine whether
    a Flatpak-packaged app expects its `--file-forwarding` arguments as URLs.
    def get_flatpak_cmd(ref: str) -> str:
    """Extract or construct the best possible command to launch ``ref``
    Default to `False` on failure.
    (This tries to extract it from the ``.desktop`` file and then falls back to
    ``make_flatpak_cmd`` if it fails or the extracted command line doesn't
    include ``--file-forwarding`` to ensure that upstream can't prevent us from
    feeding command-line arguments... ScummVM as of this writing, for example.)
    Command is not actually used to construct the launcher to avoid the
    complexities introduced by field codes like `%c`.
    """
    desktop_file = None
    for candidate in FLATPAK_DESKTOP_FILE_PATHS:
    try:
    desktop_file = Gio.DesktopAppInfo.new_from_filename(
    os.path.join(candidate, ref + '.desktop'))
    break

    if desktop_file:
    commandline = desktop_file.get_commandline()
    if commandline:
    if any(x.lower() == '%u'
    for x in shlex.split(commandline)):
    return True
    except TypeError:
    pass

    # If we found a .desktop file AND it uses --file-forwarding
    if desktop_file:
    command = desktop_file.get_commandline()
    if '--file-forwarding' in command:
    return arg_placeholder_re.sub('"${args[@]}"', command)

    # ...otherwise, fall back to the generated command line that's been working
    # well for me for months.
    return make_flatpak_cmd(ref)
    return False


    def make_wrapper(flatpak_cmd: str, command: str, bin_dir: str,
    seen: List[str] = None):
    wants_urls=False, seen: List[str] = None):
    """Render ``WRAPPER_TEMPLATE`` to a command in the folder ``bin_dir`` and
    mark it executable.
    @@ -227,7 +233,8 @@ def make_wrapper(flatpak_cmd: str, command: str, bin_dir: str,
    existing = find_executable(command)
    with open(out_path, 'w') as fobj:
    fobj.write(WRAPPER_TEMPLATE.format(
    flatpak_cmd=flatpak_cmd))
    flatpak_cmd=flatpak_cmd,
    fwd_wrap_start="@@u" if wants_urls else "@@"))
    os.chmod(out_path, os.stat(out_path).st_mode | 0o755)
    if seen is not None:
    seen.append(out_path)
    @@ -288,15 +295,30 @@ def main():
    print(f"Generating wrapper for {ref}...")

    command = prepare_cmd_name(command, ref)
    make_wrapper(get_flatpak_cmd(ref), command, BIN_DIR, seen=added)
    takes_urls = wants_urls(ref)
    make_wrapper(make_flatpak_cmd(ref), command, BIN_DIR,
    wants_urls=takes_urls, seen=added)

    if ref in EXTRA_CMDS:
    for cmd in EXTRA_CMDS[ref]:
    make_wrapper(make_flatpak_cmd(ref, f"--command={cmd}"),
    cmd, BIN_DIR, added)

    # Check if BIN_DIR is in the PATH so people don't need to read this source
    if BIN_DIR not in os.environ.get('PATH', '').split(os.pathsep):
    cmd, BIN_DIR, wants_urls=takes_urls, seen=added)

    exec_path = os.environ.get('PATH', '').split(os.pathsep)

    try:
    bin_dir_idx = exec_path.index(BIN_DIR)
    masked = []
    for idx, val in enumerate(exec_path):
    if idx > bin_dir_idx and not val.startswith('/home'):
    masked.append(val)

    if masked:
    print(f"WARNING: {BIN_DIR} has a higher precendence than these"
    f" paths in your PATH. This may present a security risk by"
    f" allowing flatpaks to override trusted system commands:"
    "\n\t" + '\n\t'.join(masked))
    except ValueError:
    print(f"WARNING: Could not find {BIN_DIR} in PATH. You will need to "
    "add it before you can use the generated launchers.")

  5. ssokolow revised this gist Sep 10, 2022. 1 changed file with 59 additions and 14 deletions.
    73 changes: 59 additions & 14 deletions update_flatpak_cli.py
    Original file line number Diff line number Diff line change
    @@ -23,8 +23,9 @@
    * Still need to look into the best way to query the set of `.desktop` files
    installed by Things like OpenRA so I don't need to *manually* amend the
    `EXTRA_COMMANDS` list in cases involving secondary GUI apps.
    * Need a way to override things like the `--player-operation-mode=pseudo-gui --`
    that's harvested from MPV's `.desktop` file and breaks stdout/stderr output.
    * Need a way to override things like the
    `--player-operation-mode=pseudo-gui --` that's harvested from MPV's
    `.desktop` file and breaks stdout/stderr output.
    * Uses the sledgehammer approach of just removing all non-folders from the
    target directory before generating new launchers to clear out stale entries.
    (A proper solution would keep track of which ones it created, but that'd
    @@ -71,19 +72,33 @@
    #: Add this to the end of your $PATH
    BIN_DIR = os.path.expanduser("~/.local/bin/flatpak")

    #: Remappings for flatpak packages that use less-than-ideal command names
    #: If True, lowercase retrieved command names like `Fritzing` and `SweetHome3D`
    #: before using them as launcher names to make commands easier to type.
    FORCE_LOWERCASE_CMDS = True

    #: Strip the specified extensions from the extracted command name,
    #: removing the need to special-case retrieved command names like `86Box.sh`
    STRIP_SCRIPT_EXTS = ['.sh', '.js', '.py', '.pl', '.rb']

    #: If True and the command's name matches the reverse DNS "ref", take only the
    #: final component as the command name, removing the need to special-case
    #: command names like `com.github.tchx84.Flatseal`. Will be applied after
    #: `STRIP_SCRIPT_EXTS`.
    STRIP_REVERSE_DNS = True

    #: If not None, strip `_wrapper` or `-wrapper` from the end of the command
    #: name, removing the need to special-case retrieved command names like
    #: `scummvm_wrapper`. This will be applied after `STRIP_SCRIPT_EXTS`.
    #:
    #: TODO: Most of these could be replaced with a "force lowercase" boolean
    #: Will be applied after `STRIP_SCRIPT_EXTS` and `STRIP_REVERSE_DNS`
    STRIP_WRAPPER_SUFFIX = re.compile("[_-]wrapper$", re.IGNORECASE)

    #: Remappings for flatpak packages that use less-than-ideal command names
    CMD_REMAPPINGS = {
    'com.github.tchx84.Flatseal': 'flatseal',
    'com.sweethome3d.Sweethome3d': 'sweethome3d',
    'io.github.simple64.simple64': 'simple64',
    'net._86box._86Box': '86box',
    'net.cebix.basilisk': 'basilikii',
    'org.fritzing.Fritzing': 'fritzing',
    'org.jdownloader.JDownloader': 'jdownloader',
    'org.ppsspp.PPSSPP': 'ppsspp',
    'org.scummvm.ScummVM': 'scummvm',
    'io.github.simple64.simple64': 'simple64', # gui-wrapper.sh
    'org.jdownloader.JDownloader': 'jdownloader', # jd-wrapper
    'org.ppsspp.PPSSPP': 'ppsspp', # PPSSPPDL
    'org.purei.Play': 'play_emu', # Collides with SoX's `play`
    }

    #: Secondary commands to expose
    @@ -227,6 +242,36 @@ def make_wrapper(flatpak_cmd: str, command: str, bin_dir: str,
    print(msg + f' The Flatpak wrapper will mask access to it.')


    def prepare_cmd_name(command: str, ref: str) -> str:
    """Apply configured transformations to derive the best command name"""
    # Strip things like `86Box.sh` down to `86Box`
    cmd_base, ext = os.path.splitext(command)
    if ext.lower() in STRIP_SCRIPT_EXTS:
    command = cmd_base

    # Strip things like `com.github.tchx84.Flatseal` down to `Flatseal`
    #
    # TODO: If the last component is `Launcher` or `Wrapper`, then take the
    # next component and join them with a dash. This hasn't proved necessary
    # yet but, if `org.solarus_games.solarus.Launcher` hadn't used
    # `solarus-launcher` as its command name, it could have been.
    if STRIP_REVERSE_DNS and command.lower() == ref.lower():
    command = command.rsplit('.')[-1]

    # Force things like `SweetHome3D` to `sweethome3d` for easier typing
    if FORCE_LOWERCASE_CMDS:
    command = command.lower()

    # Remove `_wrapper` or `-wrapper` from names like `scummvm_wrapper`
    if STRIP_WRAPPER_SUFFIX:
    command = STRIP_WRAPPER_SUFFIX.sub('', command)

    # Allow manually overriding all previous transformations
    command = CMD_REMAPPINGS.get(ref, command)

    return command


    def main():
    """setuptools-compatible entry point"""
    # Ensure BIN_DIR exists and remove any stale launch scripts
    @@ -242,7 +287,7 @@ def main():
    for (ref, command) in get_installed_packages().items():
    print(f"Generating wrapper for {ref}...")

    command = CMD_REMAPPINGS.get(ref, command)
    command = prepare_cmd_name(command, ref)
    make_wrapper(get_flatpak_cmd(ref), command, BIN_DIR, seen=added)

    if ref in EXTRA_CMDS:
  6. ssokolow revised this gist Sep 10, 2022. No changes.
  7. ssokolow revised this gist Sep 10, 2022. 1 changed file with 5 additions and 2 deletions.
    7 changes: 5 additions & 2 deletions update_flatpak_cli.py
    Original file line number Diff line number Diff line change
    @@ -23,6 +23,8 @@
    * Still need to look into the best way to query the set of `.desktop` files
    installed by Things like OpenRA so I don't need to *manually* amend the
    `EXTRA_COMMANDS` list in cases involving secondary GUI apps.
    * Need a way to override things like the `--player-operation-mode=pseudo-gui --`
    that's harvested from MPV's `.desktop` file and breaks stdout/stderr output.
    * Uses the sledgehammer approach of just removing all non-folders from the
    target directory before generating new launchers to clear out stale entries.
    (A proper solution would keep track of which ones it created, but that'd
    @@ -70,6 +72,8 @@
    BIN_DIR = os.path.expanduser("~/.local/bin/flatpak")

    #: Remappings for flatpak packages that use less-than-ideal command names
    #:
    #: TODO: Most of these could be replaced with a "force lowercase" boolean
    CMD_REMAPPINGS = {
    'com.github.tchx84.Flatseal': 'flatseal',
    'com.sweethome3d.Sweethome3d': 'sweethome3d',
    @@ -92,8 +96,6 @@
    #: Paths to check for .desktop files, since ``Gio.DesktopAppInfo.new`` doesn't
    FLATPAK_DESKTOP_FILE_PATHS = (
    '/var/lib/flatpak/exports/share/applications',

    # TODO: Confirm this is correct for --user
    os.path.expanduser('~/.local/share/flatpak/exports/share/applications')
    )

    @@ -199,6 +201,7 @@ def make_wrapper(flatpak_cmd: str, command: str, bin_dir: str,
    Also warn if we're masking existing commands.
    """
    command = os.path.basename(command)
    out_path = os.path.join(bin_dir, command)
    if seen is not None and out_path in seen:
    print(f'ERROR: Wrapper name "{out_path}" was already claimed and '
  8. ssokolow revised this gist Sep 4, 2022. 1 changed file with 5 additions and 0 deletions.
    5 changes: 5 additions & 0 deletions update_flatpak_cli.py
    Original file line number Diff line number Diff line change
    @@ -20,6 +20,9 @@
    Known shortcomings:
    * Still need to look into the best way to query the set of `.desktop` files
    installed by Things like OpenRA so I don't need to *manually* amend the
    `EXTRA_COMMANDS` list in cases involving secondary GUI apps.
    * Uses the sledgehammer approach of just removing all non-folders from the
    target directory before generating new launchers to clear out stale entries.
    (A proper solution would keep track of which ones it created, but that'd
    @@ -71,6 +74,7 @@
    'com.github.tchx84.Flatseal': 'flatseal',
    'com.sweethome3d.Sweethome3d': 'sweethome3d',
    'io.github.simple64.simple64': 'simple64',
    'net._86box._86Box': '86box',
    'net.cebix.basilisk': 'basilikii',
    'org.fritzing.Fritzing': 'fritzing',
    'org.jdownloader.JDownloader': 'jdownloader',
    @@ -81,6 +85,7 @@
    #: Secondary commands to expose
    EXTRA_CMDS = {
    "com.github.AmatCoder.mednaffe": ['mednafen'],
    'net.openra.OpenRA': ['openra-cnc', 'openra-d2k'],
    "org.atheme.audacious": ['audtool'],
    }

  9. ssokolow revised this gist Sep 4, 2022. 1 changed file with 8 additions and 6 deletions.
    14 changes: 8 additions & 6 deletions update_flatpak_cli.py
    Original file line number Diff line number Diff line change
    @@ -68,12 +68,14 @@

    #: Remappings for flatpak packages that use less-than-ideal command names
    CMD_REMAPPINGS = {
    'BasiliskII': 'basilikii',
    'com.github.tchx84.Flatseal': 'flatseal',
    'Fritzing': 'fritzing',
    'scummvm_wrapper': 'scummvm',
    'PPSSPPSDL': 'ppsspp',
    'SweetHome3D': 'sweethome3d',
    'com.sweethome3d.Sweethome3d': 'sweethome3d',
    'io.github.simple64.simple64': 'simple64',
    'net.cebix.basilisk': 'basilikii',
    'org.fritzing.Fritzing': 'fritzing',
    'org.jdownloader.JDownloader': 'jdownloader',
    'org.ppsspp.PPSSPP': 'ppsspp',
    'org.scummvm.ScummVM': 'scummvm',
    }

    #: Secondary commands to expose
    @@ -232,7 +234,7 @@ def main():
    for (ref, command) in get_installed_packages().items():
    print(f"Generating wrapper for {ref}...")

    command = CMD_REMAPPINGS.get(command, command)
    command = CMD_REMAPPINGS.get(ref, command)
    make_wrapper(get_flatpak_cmd(ref), command, BIN_DIR, seen=added)

    if ref in EXTRA_CMDS:
  10. ssokolow revised this gist Sep 4, 2022. No changes.
  11. ssokolow revised this gist Sep 4, 2022. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion update_flatpak_cli.py
    Original file line number Diff line number Diff line change
    @@ -104,7 +104,7 @@
    unset LD_PRELOAD
    # Make arguments that are existing paths absolute
    # (Necessary to make "assume file:// URLs are OK" forwarding work reliably)
    # (Necessary to make forwarding work reliably)
    declare -a args
    for arg in "$@"; do
    if [ -a "$arg" ]; then
  12. ssokolow revised this gist Sep 4, 2022. 2 changed files with 250 additions and 78 deletions.
    250 changes: 250 additions & 0 deletions update_flatpak_cli.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,250 @@
    #!/usr/bin/env python3
    """Flatpak CLI Shortcut Generator
    A simple no-argument tool that generates launchers with traditional non-flatpak
    command names for your installed Flatpak applications in ~/.local/bin/flatpak.
    Does full collision detection and warns you if you forgot to add its output
    directory to your PATH. Also overrules the command-line specified in the
    ``.desktop`` file if the Flatpak maintainer didn't include support for
    command-line arguments.
    Also includes some built-in mappings to compensate for the less desirable
    launcher/wrapper names some Flatpak packages use.
    Dependencies:
    - Python 3.8+
    - PyGobject (eg. python3-gi)
    - Glib and Gio 2.0 with GIR bindings (eg. gir1.2-glib-2.0)
    - Flatpak 1.0 GIR binding (eg. gir1.2-flatpak-1.0)
    Known shortcomings:
    * Uses the sledgehammer approach of just removing all non-folders from the
    target directory before generating new launchers to clear out stale entries.
    (A proper solution would keep track of which ones it created, but that'd
    require me to go back and implement detection of all prior versions which
    don't have a specific marker.)
    * Doesn't solve the problem of flatpaks still not installing manpages
    MIT License
    Copyright (c) 2021-2022 Stephan Sokolow (deitarion/SSokolow)
    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:
    The above copyright notice and this permission notice shall be included in all
    copies or substantial portions of the Software.
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    SOFTWARE.
    """

    import re
    from distutils.spawn import find_executable
    import os.path

    from typing import Dict, List

    import gi # type: ignore
    gi.require_version('Flatpak', '1.0')
    gi.require_version('Gio', '2.0')
    gi.require_version('GLib', '2.0')

    from gi.repository import Flatpak, Gio, GLib # type: ignore

    #: Add this to the end of your $PATH
    BIN_DIR = os.path.expanduser("~/.local/bin/flatpak")

    #: Remappings for flatpak packages that use less-than-ideal command names
    CMD_REMAPPINGS = {
    'BasiliskII': 'basilikii',
    'com.github.tchx84.Flatseal': 'flatseal',
    'Fritzing': 'fritzing',
    'scummvm_wrapper': 'scummvm',
    'PPSSPPSDL': 'ppsspp',
    'SweetHome3D': 'sweethome3d',
    }

    #: Secondary commands to expose
    EXTRA_CMDS = {
    "com.github.AmatCoder.mednaffe": ['mednafen'],
    "org.atheme.audacious": ['audtool'],
    }

    #: Paths to check for .desktop files, since ``Gio.DesktopAppInfo.new`` doesn't
    FLATPAK_DESKTOP_FILE_PATHS = (
    '/var/lib/flatpak/exports/share/applications',

    # TODO: Confirm this is correct for --user
    os.path.expanduser('~/.local/share/flatpak/exports/share/applications')
    )

    #: The template for generating wrapper scripts.
    #: (Uses Python's ``.format``, so escape { and } as {{ and }}
    #:
    #: Uses bash for the wrapper script because I need to iterate an array
    #: and conditionally rewrite arguments and the Python interpreter is an
    #: order of magnitude slower to start.
    WRAPPER_TEMPLATE = """#!/bin/bash
    # AUTOGENERATED FILE! DO NOT EDIT!
    # Unset LD_PRELOAD to silence errors about gtk-nocsd being
    # missing from the Flatpak runtime
    unset LD_PRELOAD
    # Make arguments that are existing paths absolute
    # (Necessary to make "assume file:// URLs are OK" forwarding work reliably)
    declare -a args
    for arg in "$@"; do
    if [ -a "$arg" ]; then
    args+=("$(readlink -f "$arg")")
    else
    args+=("$arg")
    fi
    done
    # Use file forwarding to make paths Just Work™
    exec {flatpak_cmd}
    """

    arg_placeholder_re = re.compile("%[uUfF]")


    def get_installed_packages() -> Dict[str, str]:
    """Retrieve a dict mapping package names to command names for
    installed flatpaks"""
    results = {}

    for installation in (
    Flatpak.Installation.new_system(),
    Flatpak.Installation.new_user()):
    refs = installation.list_installed_refs_by_kind(
    Flatpak.RefKind.APP, None)
    for ref in refs:
    meta = ref.load_metadata().get_data().decode('utf8')
    keyfile = GLib.KeyFile.new()
    keyfile.load_from_data(meta, len(meta), GLib.KeyFileFlags.NONE)

    command = keyfile.get_string('Application', 'command').strip()
    if command:
    results[ref.get_name()] = command

    return results


    def make_flatpak_cmd(ref: str, extra_args: str = '') -> str:
    """Construct a ``flatpak run`` command for the given arguments
    This is used for secondary commands and for the fallback for the primary
    command"""
    return (f'flatpak run {extra_args} --file-forwarding "{ref}" @@u '
    f'"${{args[@]}}" @@')


    def get_flatpak_cmd(ref: str) -> str:
    """Extract or construct the best possible command to launch ``ref``
    (This tries to extract it from the ``.desktop`` file and then falls back to
    ``make_flatpak_cmd`` if it fails or the extracted command line doesn't
    include ``--file-forwarding`` to ensure that upstream can't prevent us from
    feeding command-line arguments... ScummVM as of this writing, for example.)
    """
    desktop_file = None
    for candidate in FLATPAK_DESKTOP_FILE_PATHS:
    try:
    desktop_file = Gio.DesktopAppInfo.new_from_filename(
    os.path.join(candidate, ref + '.desktop'))
    break
    except TypeError:
    pass

    # If we found a .desktop file AND it uses --file-forwarding
    if desktop_file:
    command = desktop_file.get_commandline()
    if '--file-forwarding' in command:
    return arg_placeholder_re.sub('"${args[@]}"', command)

    # ...otherwise, fall back to the generated command line that's been working
    # well for me for months.
    return make_flatpak_cmd(ref)


    def make_wrapper(flatpak_cmd: str, command: str, bin_dir: str,
    seen: List[str] = None):
    """Render ``WRAPPER_TEMPLATE`` to a command in the folder ``bin_dir`` and
    mark it executable.
    If provided, ``extra_args`` will be inserted into the portion of the
    ``flatpak run`` command before ``run``.
    If ``seen`` is not ``None``, use it to detect and reject naming collisions.
    Also warn if we're masking existing commands.
    """
    out_path = os.path.join(bin_dir, command)
    if seen is not None and out_path in seen:
    print(f'ERROR: Wrapper name "{out_path}" was already claimed and '
    f'could not be mapped to "{flatpak_cmd}". Please add a '
    f' CMD_REMAPPINGS entry.')
    return

    existing = find_executable(command)
    with open(out_path, 'w') as fobj:
    fobj.write(WRAPPER_TEMPLATE.format(
    flatpak_cmd=flatpak_cmd))
    os.chmod(out_path, os.stat(out_path).st_mode | 0o755)
    if seen is not None:
    seen.append(out_path)

    if existing:
    msg = (f'WARNING: Command "{command}" already exists in your PATH at '
    f'"{existing}".')
    winner = find_executable(command)
    if winner == existing:
    print(msg + f' The Flatpak wrapper will be inaccessible.')
    else:
    print(msg + f' The Flatpak wrapper will mask access to it.')


    def main():
    """setuptools-compatible entry point"""
    # Ensure BIN_DIR exists and remove any stale launch scripts
    if not os.path.exists(BIN_DIR):
    os.makedirs(BIN_DIR)
    for name in os.listdir(BIN_DIR):
    path = os.path.join(BIN_DIR, name)
    if os.path.isfile(path):
    os.remove(path)

    print(f"Getting list of installed application/non-runtime packages...")
    added = []
    for (ref, command) in get_installed_packages().items():
    print(f"Generating wrapper for {ref}...")

    command = CMD_REMAPPINGS.get(command, command)
    make_wrapper(get_flatpak_cmd(ref), command, BIN_DIR, seen=added)

    if ref in EXTRA_CMDS:
    for cmd in EXTRA_CMDS[ref]:
    make_wrapper(make_flatpak_cmd(ref, f"--command={cmd}"),
    cmd, BIN_DIR, added)

    # Check if BIN_DIR is in the PATH so people don't need to read this source
    if BIN_DIR not in os.environ.get('PATH', '').split(os.pathsep):
    print(f"WARNING: Could not find {BIN_DIR} in PATH. You will need to "
    "add it before you can use the generated launchers.")


    if __name__ == '__main__':
    main()
    78 changes: 0 additions & 78 deletions update_flatpak_cli.sh
    Original file line number Diff line number Diff line change
    @@ -1,78 +0,0 @@
    #!/bin/bash
    # Flatpak CLI Shortcut Proof of Concept
    # Copyright 2021-2022 Stephan Sokolow (deitarion/SSokolow)
    #
    # License: MIT
    #
    # Known shortcomings in this quick and dirty PoC:
    # * Assumes command name collisions will never happen
    # (A proper implementation would need to prompt the user to resolve conflicts
    # if encountered instead of just applying "last processed wins" behaviour.)
    # * In order to support non-local URL arguments to Flatpak'd browsers, use of
    # --file-forwarding assumes all installed programs will accept file:// URLs.
    # (A proper solution would parse the .desktop files to identify what kinds of
    # arguments the commands want, but I have yet to run into something I want to
    # invoke from the command-line which *doesn't* accept file:// URLs... I'm
    # guessing things like GTK's GIO and Qt's I/O classes just accept file://
    # URLs as a valid form of "local path".)
    # * Just uses `grep command= | cut -d= -f2` to "parse" the INI-style output
    # of `flatpak info -m`
    # (A proper implementation needs to make sure it's from the right section of
    # the file to ensure there's no risk of multiple lines matching.)
    # * Uses the sledgehammer approach of just removing all non-folders from the
    # target directory before generating new launchers to clear out stale entries.
    # (A proper solution would keep track of which ones it created and would also
    # look for more performant alternatives to spawning a `flatpak info -m` for
    # each application, which limits performance to about 13 applications per
    # second.)
    # * Doesn't solve the problem of flatpaks still not installing manpages

    # Add this to the end of your $PATH
    BIN_DIR=~/.local/bin/flatpak

    # Remappings for flatpak packages that use less-than-ideal command names
    declare -A CMD_REMAPPINGS
    CMD_REMAPPINGS=(
    [BasiliskII]='basilikii'
    ["com.github.tchx84.Flatseal"]='flatseal'
    [Fritzing]='fritzing'
    [scummvm_wrapper]='scummvm'
    [PPSSPPSDL]='ppsspp'
    [SweetHome3D]='sweethome3d'
    )

    declare -A EXTRA_CMDS
    EXTRA_CMDS=(
    ["com.github.AmatCoder.mednaffe"]='mednafen'
    ["org.atheme.audacious"]='audtool'
    )

    # Remove any stale launch scripts
    rm -f "$BIN_DIR"/*
    mkdir -p "$BIN_DIR"

    make_wrapper() {
    # Use bash for the wrapper script because I need to iterate an array
    # and conditionally rewrite arguments and the Python interpreter is an
    # order of magnitude slower to start.
    #
    # shellcheck disable=SC2016
    printf '#!/bin/bash\n# AUTOGENERATED FILE! DO NOT EDIT!\n\n# Unset LD_PRELOAD to silence errors about gtk-nocsd being missing from the Flatpak runtime\nunset LD_PRELOAD\n\n# Make arguments that are existing paths absolute\n# (Necessary to make "assume file:// URLs are OK" forwarding work reliably)\ndeclare -a args\nfor arg in "$@"; do\n if [ -a "$arg" ]; then\n args+=("$(readlink -f "$arg")")\n else\n args+=("$arg")\n fi\ndone\n\n# Use file forwarding to make paths Just Work™\nexec flatpak run '"$3"' --file-forwarding "%s" @@u "${args[@]}" @@' "$1" >"$2"
    chmod +x "$2"
    }

    for X in $(flatpak list --columns=ref); do
    echo "Querying $X..."
    app_command="$(flatpak info -m "$X" | grep command= | cut -d= -f2)"
    if [ -n "$app_command" ]; then
    cmd_path="$BIN_DIR"/"${CMD_REMAPPINGS["${app_command}"]:-$app_command}"
    make_wrapper "$X" "$cmd_path"

    # Strip any `/x86_64/stable` and check for a match
    if [ -n "${EXTRA_CMDS["${X%%/*}"]}" ]; then
    cmd="${EXTRA_CMDS["${X%%/*}"]}"
    cmd_path="$BIN_DIR"/
    make_wrapper "$X" "$BIN_DIR/$cmd" "--command=$cmd"
    fi
    fi
    done
  13. ssokolow revised this gist Sep 3, 2022. 1 changed file with 24 additions and 8 deletions.
    32 changes: 24 additions & 8 deletions update_flatpak_cli.sh
    Original file line number Diff line number Diff line change
    @@ -41,22 +41,38 @@ CMD_REMAPPINGS=(
    [SweetHome3D]='sweethome3d'
    )

    declare -A EXTRA_CMDS
    EXTRA_CMDS=(
    ["com.github.AmatCoder.mednaffe"]='mednafen'
    ["org.atheme.audacious"]='audtool'
    )

    # Remove any stale launch scripts
    rm -f "$BIN_DIR"/*
    mkdir -p "$BIN_DIR"

    make_wrapper() {
    # Use bash for the wrapper script because I need to iterate an array
    # and conditionally rewrite arguments and the Python interpreter is an
    # order of magnitude slower to start.
    #
    # shellcheck disable=SC2016
    printf '#!/bin/bash\n# AUTOGENERATED FILE! DO NOT EDIT!\n\n# Unset LD_PRELOAD to silence errors about gtk-nocsd being missing from the Flatpak runtime\nunset LD_PRELOAD\n\n# Make arguments that are existing paths absolute\n# (Necessary to make "assume file:// URLs are OK" forwarding work reliably)\ndeclare -a args\nfor arg in "$@"; do\n if [ -a "$arg" ]; then\n args+=("$(readlink -f "$arg")")\n else\n args+=("$arg")\n fi\ndone\n\n# Use file forwarding to make paths Just Work™\nexec flatpak run '"$3"' --file-forwarding "%s" @@u "${args[@]}" @@' "$1" >"$2"
    chmod +x "$2"
    }

    for X in $(flatpak list --columns=ref); do
    echo "Querying $X..."
    app_command="$(flatpak info -m "$X" | grep command= | cut -d= -f2)"
    if [ -n "$app_command" ]; then
    cmd_path="$BIN_DIR"/"${CMD_REMAPPINGS["${app_command}"]:-$app_command}"
    make_wrapper "$X" "$cmd_path"

    # Use bash for the wrapper script because I need to iterate an array
    # and conditionally rewrite arguments and the Python interpreter is an
    # order of magnitude slower to start.
    #
    # shellcheck disable=SC2016
    printf '#!/bin/bash\n# AUTOGENERATED FILE! DO NOT EDIT!\n\n# Unset LD_PRELOAD to silence errors about gtk-nocsd being missing from the Flatpak runtime\nunset LD_PRELOAD\n\n# Make arguments that are existing paths absolute\n# (Necessary to make "assume file:// URLs are OK" forwarding work reliably)\ndeclare -a args\nfor arg in "$@"; do\n if [ -a "$arg" ]; then\n args+=("$(readlink -f "$arg")")\n else\n args+=("$arg")\n fi\ndone\n\n# Use file forwarding to make paths Just Work™\nexec flatpak run --file-forwarding "%s" @@u "${args[@]}" @@' "$X" >"$cmd_path"
    chmod +x "$cmd_path"
    # Strip any `/x86_64/stable` and check for a match
    if [ -n "${EXTRA_CMDS["${X%%/*}"]}" ]; then
    cmd="${EXTRA_CMDS["${X%%/*}"]}"
    cmd_path="$BIN_DIR"/
    make_wrapper "$X" "$BIN_DIR/$cmd" "--command=$cmd"
    fi
    fi
    done
    done
  14. ssokolow revised this gist Sep 3, 2022. 1 changed file with 0 additions and 2 deletions.
    2 changes: 0 additions & 2 deletions update_flatpak_cli.sh
    Original file line number Diff line number Diff line change
    @@ -4,8 +4,6 @@
    #
    # License: MIT
    #
    # Features:
    #
    # Known shortcomings in this quick and dirty PoC:
    # * Assumes command name collisions will never happen
    # (A proper implementation would need to prompt the user to resolve conflicts
  15. ssokolow revised this gist Sep 3, 2022. 1 changed file with 35 additions and 15 deletions.
    50 changes: 35 additions & 15 deletions update_flatpak_cli.sh
    Original file line number Diff line number Diff line change
    @@ -1,44 +1,64 @@
    #!/bin/sh
    #!/bin/bash
    # Flatpak CLI Shortcut Proof of Concept
    # Copyright 2021 Stephan Sokolow (deitarion/SSokolow)
    # Copyright 2021-2022 Stephan Sokolow (deitarion/SSokolow)
    #
    # License: MIT
    #
    # Features:
    #
    # Known shortcomings in this quick and dirty PoC:
    # * Assumes command name collisions will never happen
    # (A proper implementation would need to prompt the user to resolve conflicts
    # if encountered instead of just applying "last processed wins" behaviour.)
    # * In order to support non-local URL arguments to Flatpak'd browsers, use of
    # --file-forwarding assumes all installed programs will accept file:// URLs.
    # (A proper solution would parse the .desktop files to identify what kinds of
    # arguments the commands want.)
    # * Assumes command name collisions will never happen
    # (A proper implementation would need to prompt the user to resolve conflicts
    # if encountered.)
    # arguments the commands want, but I have yet to run into something I want to
    # invoke from the command-line which *doesn't* accept file:// URLs... I'm
    # guessing things like GTK's GIO and Qt's I/O classes just accept file://
    # URLs as a valid form of "local path".)
    # * Just uses `grep command= | cut -d= -f2` to "parse" the INI-style output
    # of `flatpak info -m`
    # (A proper implementation needs to make sure it's from the right section of
    # the file to ensure there's no risk of multiple lines matching.)
    # * Uses the sledgehammer approach of just removing all non-folders from the
    # target directory before generating new launchers to clear out stale entries.
    # (A proper solution would keep track of which ones it created)
    # * No means of overriding Flatseal's decision to use
    # "com.github.tchx84.Flatseal" as its internal binary name rather than
    # "flatseal"
    # (A proper solution would keep track of which ones it created and would also
    # look for more performant alternatives to spawning a `flatpak info -m` for
    # each application, which limits performance to about 13 applications per
    # second.)
    # * Doesn't solve the problem of flatpaks still not installing manpages

    # Add this to the end of your $PATH
    BIN_DIR=~/.local/bin/flatpak

    # Remappings for flatpak packages that use less-than-ideal command names
    declare -A CMD_REMAPPINGS
    CMD_REMAPPINGS=(
    [BasiliskII]='basilikii'
    ["com.github.tchx84.Flatseal"]='flatseal'
    [Fritzing]='fritzing'
    [scummvm_wrapper]='scummvm'
    [PPSSPPSDL]='ppsspp'
    [SweetHome3D]='sweethome3d'
    )

    # Remove any stale launch scripts
    rm -f "$BIN_DIR"/*
    mkdir -p "$BIN_DIR"

    for X in $(flatpak list --columns=ref); do
    echo "Querying $X..."
    app_command="$(flatpak info -m "$X" | grep command= | cut -d= -f2)"
    cmd_path="$BIN_DIR"/"$app_command"

    if [ -n "$app_command" ]; then
    # Unset LD_PRELOAD to silence gtk-nocsd errors and support file
    # forwarding so you can tighten your sandbox and still open local files
    printf '#!/bin/sh\nunset LD_PRELOAD\nexec flatpak run --file-forwarding "%s" @@u "$@" @@' "$X" >"$cmd_path"
    cmd_path="$BIN_DIR"/"${CMD_REMAPPINGS["${app_command}"]:-$app_command}"

    # Use bash for the wrapper script because I need to iterate an array
    # and conditionally rewrite arguments and the Python interpreter is an
    # order of magnitude slower to start.
    #
    # shellcheck disable=SC2016
    printf '#!/bin/bash\n# AUTOGENERATED FILE! DO NOT EDIT!\n\n# Unset LD_PRELOAD to silence errors about gtk-nocsd being missing from the Flatpak runtime\nunset LD_PRELOAD\n\n# Make arguments that are existing paths absolute\n# (Necessary to make "assume file:// URLs are OK" forwarding work reliably)\ndeclare -a args\nfor arg in "$@"; do\n if [ -a "$arg" ]; then\n args+=("$(readlink -f "$arg")")\n else\n args+=("$arg")\n fi\ndone\n\n# Use file forwarding to make paths Just Work™\nexec flatpak run --file-forwarding "%s" @@u "${args[@]}" @@' "$X" >"$cmd_path"
    chmod +x "$cmd_path"
    fi
    done
  16. ssokolow revised this gist Mar 19, 2022. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion update_flatpak_cli.sh
    Original file line number Diff line number Diff line change
    @@ -37,7 +37,7 @@ for X in $(flatpak list --columns=ref); do

    if [ -n "$app_command" ]; then
    # Unset LD_PRELOAD to silence gtk-nocsd errors and support file
    # forwarding so you can sandbox browsers and still open local files
    # forwarding so you can tighten your sandbox and still open local files
    printf '#!/bin/sh\nunset LD_PRELOAD\nexec flatpak run --file-forwarding "%s" @@u "$@" @@' "$X" >"$cmd_path"
    chmod +x "$cmd_path"
    fi
  17. ssokolow revised this gist Oct 9, 2021. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions update_flatpak_cli.sh
    Original file line number Diff line number Diff line change
    @@ -7,6 +7,8 @@
    # Known shortcomings in this quick and dirty PoC:
    # * In order to support non-local URL arguments to Flatpak'd browsers, use of
    # --file-forwarding assumes all installed programs will accept file:// URLs.
    # (A proper solution would parse the .desktop files to identify what kinds of
    # arguments the commands want.)
    # * Assumes command name collisions will never happen
    # (A proper implementation would need to prompt the user to resolve conflicts
    # if encountered.)
  18. ssokolow revised this gist Oct 9, 2021. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion update_flatpak_cli.sh
    Original file line number Diff line number Diff line change
    @@ -6,7 +6,7 @@
    #
    # Known shortcomings in this quick and dirty PoC:
    # * In order to support non-local URL arguments to Flatpak'd browsers, use of
    # --file-forwarding assumes all installed programs will accept
    # --file-forwarding assumes all installed programs will accept file:// URLs.
    # * Assumes command name collisions will never happen
    # (A proper implementation would need to prompt the user to resolve conflicts
    # if encountered.)
  19. ssokolow revised this gist Oct 8, 2021. 1 changed file with 2 additions and 5 deletions.
    7 changes: 2 additions & 5 deletions update_flatpak_cli.sh
    Original file line number Diff line number Diff line change
    @@ -6,10 +6,7 @@
    #
    # Known shortcomings in this quick and dirty PoC:
    # * In order to support non-local URL arguments to Flatpak'd browsers, use of
    # --file-forwarding assumes all installed programs will be OK with local
    # paths getting translated to file:// URLs by Flatpak.
    # (Proper solution is to parse the corresponding `.desktop` files to
    # determine whether the applications expect URLs or paths.)
    # --file-forwarding assumes all installed programs will accept
    # * Assumes command name collisions will never happen
    # (A proper implementation would need to prompt the user to resolve conflicts
    # if encountered.)
    @@ -39,7 +36,7 @@ for X in $(flatpak list --columns=ref); do
    if [ -n "$app_command" ]; then
    # Unset LD_PRELOAD to silence gtk-nocsd errors and support file
    # forwarding so you can sandbox browsers and still open local files
    printf '#!/bin/sh\nunset LD_PRELOAD\nexec flatpak run --file-forwarding "%s" @@ "$@" @@' "$X" >"$cmd_path"
    printf '#!/bin/sh\nunset LD_PRELOAD\nexec flatpak run --file-forwarding "%s" @@u "$@" @@' "$X" >"$cmd_path"
    chmod +x "$cmd_path"
    fi
    done
  20. ssokolow revised this gist Oct 8, 2021. 1 changed file with 5 additions and 0 deletions.
    5 changes: 5 additions & 0 deletions update_flatpak_cli.sh
    Original file line number Diff line number Diff line change
    @@ -5,6 +5,11 @@
    # License: MIT
    #
    # Known shortcomings in this quick and dirty PoC:
    # * In order to support non-local URL arguments to Flatpak'd browsers, use of
    # --file-forwarding assumes all installed programs will be OK with local
    # paths getting translated to file:// URLs by Flatpak.
    # (Proper solution is to parse the corresponding `.desktop` files to
    # determine whether the applications expect URLs or paths.)
    # * Assumes command name collisions will never happen
    # (A proper implementation would need to prompt the user to resolve conflicts
    # if encountered.)
  21. ssokolow revised this gist Oct 8, 2021. 1 changed file with 4 additions and 1 deletion.
    5 changes: 4 additions & 1 deletion update_flatpak_cli.sh
    Original file line number Diff line number Diff line change
    @@ -18,6 +18,7 @@
    # * No means of overriding Flatseal's decision to use
    # "com.github.tchx84.Flatseal" as its internal binary name rather than
    # "flatseal"
    # * Doesn't solve the problem of flatpaks still not installing manpages

    # Add this to the end of your $PATH
    BIN_DIR=~/.local/bin/flatpak
    @@ -31,7 +32,9 @@ for X in $(flatpak list --columns=ref); do
    cmd_path="$BIN_DIR"/"$app_command"

    if [ -n "$app_command" ]; then
    printf '#!/bin/sh\nexec flatpak run "%s" "$@"' "$X" >"$cmd_path"
    # Unset LD_PRELOAD to silence gtk-nocsd errors and support file
    # forwarding so you can sandbox browsers and still open local files
    printf '#!/bin/sh\nunset LD_PRELOAD\nexec flatpak run --file-forwarding "%s" @@ "$@" @@' "$X" >"$cmd_path"
    chmod +x "$cmd_path"
    fi
    done
  22. ssokolow created this gist Apr 29, 2021.
    37 changes: 37 additions & 0 deletions update_flatpak_cli.sh
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,37 @@
    #!/bin/sh
    # Flatpak CLI Shortcut Proof of Concept
    # Copyright 2021 Stephan Sokolow (deitarion/SSokolow)
    #
    # License: MIT
    #
    # Known shortcomings in this quick and dirty PoC:
    # * Assumes command name collisions will never happen
    # (A proper implementation would need to prompt the user to resolve conflicts
    # if encountered.)
    # * Just uses `grep command= | cut -d= -f2` to "parse" the INI-style output
    # of `flatpak info -m`
    # (A proper implementation needs to make sure it's from the right section of
    # the file to ensure there's no risk of multiple lines matching.)
    # * Uses the sledgehammer approach of just removing all non-folders from the
    # target directory before generating new launchers to clear out stale entries.
    # (A proper solution would keep track of which ones it created)
    # * No means of overriding Flatseal's decision to use
    # "com.github.tchx84.Flatseal" as its internal binary name rather than
    # "flatseal"

    # Add this to the end of your $PATH
    BIN_DIR=~/.local/bin/flatpak

    # Remove any stale launch scripts
    rm -f "$BIN_DIR"/*
    mkdir -p "$BIN_DIR"

    for X in $(flatpak list --columns=ref); do
    app_command="$(flatpak info -m "$X" | grep command= | cut -d= -f2)"
    cmd_path="$BIN_DIR"/"$app_command"

    if [ -n "$app_command" ]; then
    printf '#!/bin/sh\nexec flatpak run "%s" "$@"' "$X" >"$cmd_path"
    chmod +x "$cmd_path"
    fi
    done