#! /usr/bin/env python3

from os import path, walk, execlp, environ
from sys import exit
from argparse import ArgumentParser
from tempfile import TemporaryFile
from contextlib import suppress
from collections import defaultdict, OrderedDict

import shlex
import pickle
import subprocess

from xdg import BaseDirectory
from xdg.DesktopEntry import DesktopEntry

from gi.repository import Gio


dmenu_cache = path.join(BaseDirectory.xdg_cache_home, 'dmenu_desktop')


def cacher(it):
    with open(dmenu_cache, 'wb') as fp:
        for e in it:
            pickle.dump(e, fp)
            yield e


def get_runnable_entries():
    directories = list(BaseDirectory.load_data_paths('applications'))
    try:
        cachemt = path.getmtime(dmenu_cache)
    except FileNotFoundError:
        pass
    else:
        if all(path.getmtime(directory) <= cachemt
               for directory
               in directories):
            with suppress(EOFError):
                with open(dmenu_cache, 'rb') as fp:
                    while True:
                        yield pickle.load(fp)
            return
    yield from cacher(sorted((entry
                              for entry
                              in (DesktopEntry(path.join(dirpath, filename))
                                  for directory in directories
                                  for dirpath, _, filenames in walk(directory)
                                  for filename in filenames
                                  if filename.endswith('.desktop'))
                              if entry.getType() == 'Application' and
                                 entry.hasKey('Exec') and
                                 'true' not in {entry.getNoDisplay(),
                                                entry.getHidden()}),
                             key=lambda entry: entry.getName().casefold()))


parser = ArgumentParser(description='dmenu_run alternative only over desktop entries')
parser.add_argument('dmenuargs', nargs='*')
parser.add_argument('terminal', default='st', nargs='?')
parser.add_argument('--no-args', action='store_true', default=False,
                    help="Don't support passing arguments after the desktop entry name")
args = parser.parse_args()

# TODO: Handle duplicates
entries = OrderedDict((entry.getName(), entry)
                      for entry
                      in get_runnable_entries())

with TemporaryFile() as fp:
    fp.write(b'\n'.join(map(str.encode, entries)))
    fp.flush()
    fp.seek(0)
    choice = subprocess.check_output(['dmenu'] + args.dmenuargs,
                                     universal_newlines=True,
                                     stdin=fp)

if args.no_args:
    entry = entries[choice.strip()]
    launcher = Gio.DesktopAppInfo.new_from_filename(entry.filename)
    #launcher = Gio.DesktopAppInfo(**entry.content['Desktop Entry'])
    if launcher.launch_uris():
        exit()
    else:
        exit(1)

split = shlex.split(choice)
# Hack to be able to pass arguments to the desktop entries; longest prefix
# match on the desktop entry name.
# TODO: Pop up dmenu again to prompt?
for prefix_len in range(len(split), 0, -1):
    try:
        entry = entries[' '.join(split[:prefix_len])]
    except KeyError:
        pass
    else:
        launcher = Gio.DesktopAppInfo.new_from_filename(entry.filename)
        #launcher = Gio.DesktopAppInfo(**entry.content['Desktop Entry'])
        if launcher.launch_uris(split[prefix_len:]):
            exit()
        else:
            exit(1)

#! /usr/bin/env python3

from os import path, walk, execlp, environ
from sys import exit
from argparse import ArgumentParser
from tempfile import TemporaryFile
from contextlib import suppress
from collections import defaultdict, OrderedDict

import shlex
import pickle
import subprocess

from xdg import BaseDirectory
from xdg.DesktopEntry import DesktopEntry

from gi.repository import Gio


dmenu_cache = path.join(BaseDirectory.xdg_cache_home, 'dmenu_desktop')


def cacher(it):
    with open(dmenu_cache, 'wb') as fp:
        for e in it:
            pickle.dump(e, fp)
            yield e


def get_runnable_entries():
    directories = list(BaseDirectory.load_data_paths('applications'))
    try:
        cachemt = path.getmtime(dmenu_cache)
    except FileNotFoundError:
        pass
    else:
        if all(path.getmtime(directory) <= cachemt
               for directory
               in directories):
            with suppress(EOFError):
                with open(dmenu_cache, 'rb') as fp:
                    while True:
                        yield pickle.load(fp)
            return
    yield from cacher(sorted((entry
                              for entry
                              in (DesktopEntry(path.join(dirpath, filename))
                                  for directory in directories
                                  for dirpath, _, filenames in walk(directory)
                                  for filename in filenames
                                  if filename.endswith('.desktop'))
                              if entry.getType() == 'Application' and
                                 entry.hasKey('Exec') and
                                 'true' not in {entry.getNoDisplay(),
                                                entry.getHidden()}),
                             key=lambda entry: entry.getName().casefold()))


parser = ArgumentParser(description='dmenu_run alternative only over desktop entries')
parser.add_argument('dmenuargs', nargs='*')
parser.add_argument('terminal', default='st', nargs='?')
parser.add_argument('--no-args', action='store_true', default=False,
                    help="Don't support passing arguments after the desktop entry name")
args = parser.parse_args()

# TODO: Handle duplicates
entries = OrderedDict((entry.getName(), entry)
                      for entry
                      in get_runnable_entries())

with TemporaryFile() as fp:
    fp.write(b'\n'.join(map(str.encode, entries)))
    fp.flush()
    fp.seek(0)
    choice = subprocess.check_output(['dmenu'] + args.dmenuargs,
                                     universal_newlines=True,
                                     stdin=fp)

if args.no_args:
    entry = entries[choice.strip()]
    launcher = Gio.DesktopAppInfo.new_from_filename(entry.filename)
    #launcher = Gio.DesktopAppInfo(**entry.content['Desktop Entry'])
    if launcher.launch_uris():
        exit()
    else:
        exit(1)

split = shlex.split(choice)
# Hack to be able to pass arguments to the desktop entries; longest prefix
# match on the desktop entry name.
# TODO: Pop up dmenu again to prompt?
for prefix_len in range(len(split), 0, -1):
    try:
        entry = entries[' '.join(split[:prefix_len])]
    except KeyError:
        pass
    else:
        launcher = Gio.DesktopAppInfo.new_from_filename(entry.filename)
        #launcher = Gio.DesktopAppInfo(**entry.content['Desktop Entry'])
        if launcher.launch_uris(split[prefix_len:]):
            exit()
        else:
            exit(1)

#! /usr/bin/env python3 # TODO: Handle duplicates #launcher = Gio.DesktopAppInfo(**entry.content['Desktop Entry']) # Hack to be able to pass arguments to the desktop entries; longest prefix # match on the desktop entry name. # TODO: Pop up dmenu again to prompt? #launcher = Gio.DesktopAppInfo(**entry.content['Desktop Entry'])