270 lines
7.5 KiB
Python
270 lines
7.5 KiB
Python
|
'''
|
||
|
Linux file chooser
|
||
|
------------------
|
||
|
'''
|
||
|
|
||
|
from plyer.facades import FileChooser
|
||
|
from distutils.spawn import find_executable as which
|
||
|
import os
|
||
|
import subprocess as sp
|
||
|
import time
|
||
|
|
||
|
|
||
|
class SubprocessFileChooser:
|
||
|
'''A file chooser implementation that allows using
|
||
|
subprocess back-ends.
|
||
|
Normally you only need to override _gen_cmdline, executable,
|
||
|
separator and successretcode.
|
||
|
'''
|
||
|
|
||
|
executable = ""
|
||
|
'''The name of the executable of the back-end.
|
||
|
'''
|
||
|
|
||
|
separator = "|"
|
||
|
'''The separator used by the back-end. Override this for automatic
|
||
|
splitting, or override _split_output.
|
||
|
'''
|
||
|
|
||
|
successretcode = 0
|
||
|
'''The return code which is returned when the user doesn't close the
|
||
|
dialog without choosing anything, or when the app doesn't crash.
|
||
|
'''
|
||
|
|
||
|
path = None
|
||
|
multiple = False
|
||
|
filters = []
|
||
|
preview = False
|
||
|
title = None
|
||
|
icon = None
|
||
|
show_hidden = False
|
||
|
|
||
|
def __init__(self, *args, **kwargs):
|
||
|
self._handle_selection = kwargs.pop(
|
||
|
'on_selection', self._handle_selection
|
||
|
)
|
||
|
|
||
|
# Simulate Kivy's behavior
|
||
|
for i in kwargs:
|
||
|
setattr(self, i, kwargs[i])
|
||
|
|
||
|
@staticmethod
|
||
|
def _handle_selection(selection):
|
||
|
'''
|
||
|
Dummy placeholder for returning selection from chooser.
|
||
|
'''
|
||
|
return selection
|
||
|
|
||
|
_process = None
|
||
|
|
||
|
def _run_command(self, cmd):
|
||
|
self._process = sp.Popen(cmd, stdout=sp.PIPE)
|
||
|
while True:
|
||
|
ret = self._process.poll()
|
||
|
if ret is not None:
|
||
|
if ret == self.successretcode:
|
||
|
out = self._process.communicate()[0].strip().decode('utf8')
|
||
|
return self._set_and_return_selection(
|
||
|
self._split_output(out))
|
||
|
else:
|
||
|
return self._set_and_return_selection(None)
|
||
|
time.sleep(0.1)
|
||
|
|
||
|
def _set_and_return_selection(self, value):
|
||
|
self.selection = value
|
||
|
self._handle_selection(value)
|
||
|
return value
|
||
|
|
||
|
def _split_output(self, out):
|
||
|
'''This methods receives the output of the back-end and turns
|
||
|
it into a list of paths.
|
||
|
'''
|
||
|
return out.split(self.separator)
|
||
|
|
||
|
def _gen_cmdline(self):
|
||
|
'''Returns the command line of the back-end, based on the current
|
||
|
properties. You need to override this.
|
||
|
'''
|
||
|
raise NotImplementedError()
|
||
|
|
||
|
def run(self):
|
||
|
return self._run_command(self._gen_cmdline())
|
||
|
|
||
|
|
||
|
class ZenityFileChooser(SubprocessFileChooser):
|
||
|
'''A FileChooser implementation using Zenity (on GNU/Linux).
|
||
|
|
||
|
Not implemented features:
|
||
|
* show_hidden
|
||
|
* preview
|
||
|
'''
|
||
|
|
||
|
executable = "zenity"
|
||
|
separator = "|"
|
||
|
successretcode = 0
|
||
|
|
||
|
def _gen_cmdline(self):
|
||
|
cmdline = [
|
||
|
which(self.executable),
|
||
|
"--file-selection",
|
||
|
"--confirm-overwrite"
|
||
|
]
|
||
|
if self.multiple:
|
||
|
cmdline += ["--multiple"]
|
||
|
if self.mode == "save":
|
||
|
cmdline += ["--save"]
|
||
|
elif self.mode == "dir":
|
||
|
cmdline += ["--directory"]
|
||
|
if self.path:
|
||
|
cmdline += ["--filename", self.path]
|
||
|
if self.title:
|
||
|
cmdline += ["--name", self.title]
|
||
|
if self.icon:
|
||
|
cmdline += ["--window-icon", self.icon]
|
||
|
for f in self.filters:
|
||
|
if type(f) == str:
|
||
|
cmdline += ["--file-filter", f]
|
||
|
else:
|
||
|
cmdline += [
|
||
|
"--file-filter",
|
||
|
"{name} | {flt}".format(name=f[0], flt=" ".join(f[1:]))
|
||
|
]
|
||
|
return cmdline
|
||
|
|
||
|
|
||
|
class KDialogFileChooser(SubprocessFileChooser):
|
||
|
'''A FileChooser implementation using KDialog (on GNU/Linux).
|
||
|
|
||
|
Not implemented features:
|
||
|
* show_hidden
|
||
|
* preview
|
||
|
'''
|
||
|
|
||
|
executable = "kdialog"
|
||
|
separator = "\n"
|
||
|
successretcode = 0
|
||
|
|
||
|
def _gen_cmdline(self):
|
||
|
cmdline = [which(self.executable)]
|
||
|
|
||
|
filt = []
|
||
|
|
||
|
for f in self.filters:
|
||
|
if type(f) == str:
|
||
|
filt += [f]
|
||
|
else:
|
||
|
filt += list(f[1:])
|
||
|
|
||
|
if self.mode == "dir":
|
||
|
cmdline += [
|
||
|
"--getexistingdirectory",
|
||
|
(self.path if self.path else os.path.expanduser("~"))
|
||
|
]
|
||
|
elif self.mode == "save":
|
||
|
cmdline += [
|
||
|
"--getsavefilename",
|
||
|
(self.path if self.path else os.path.expanduser("~")),
|
||
|
" ".join(filt)
|
||
|
]
|
||
|
else:
|
||
|
cmdline += [
|
||
|
"--getopenfilename",
|
||
|
(self.path if self.path else os.path.expanduser("~")),
|
||
|
" ".join(filt)
|
||
|
]
|
||
|
if self.multiple:
|
||
|
cmdline += ["--multiple", "--separate-output"]
|
||
|
if self.title:
|
||
|
cmdline += ["--title", self.title]
|
||
|
if self.icon:
|
||
|
cmdline += ["--icon", self.icon]
|
||
|
return cmdline
|
||
|
|
||
|
|
||
|
class YADFileChooser(SubprocessFileChooser):
|
||
|
'''A NativeFileChooser implementation using YAD (on GNU/Linux).
|
||
|
|
||
|
Not implemented features:
|
||
|
* show_hidden
|
||
|
'''
|
||
|
|
||
|
executable = "yad"
|
||
|
separator = "|?|"
|
||
|
successretcode = 0
|
||
|
|
||
|
def _gen_cmdline(self):
|
||
|
cmdline = [
|
||
|
which(self.executable),
|
||
|
"--file-selection",
|
||
|
"--confirm-overwrite",
|
||
|
"--geometry",
|
||
|
"800x600+150+150"
|
||
|
]
|
||
|
if self.multiple:
|
||
|
cmdline += ["--multiple", "--separator", self.separator]
|
||
|
if self.mode == "save":
|
||
|
cmdline += ["--save"]
|
||
|
elif self.mode == "dir":
|
||
|
cmdline += ["--directory"]
|
||
|
if self.preview:
|
||
|
cmdline += ["--add-preview"]
|
||
|
if self.path:
|
||
|
cmdline += ["--filename", self.path]
|
||
|
if self.title:
|
||
|
cmdline += ["--name", self.title]
|
||
|
if self.icon:
|
||
|
cmdline += ["--window-icon", self.icon]
|
||
|
for f in self.filters:
|
||
|
if type(f) == str:
|
||
|
cmdline += ["--file-filter", f]
|
||
|
else:
|
||
|
cmdline += [
|
||
|
"--file-filter",
|
||
|
"{name} | {flt}".format(name=f[0], flt=" ".join(f[1:]))
|
||
|
]
|
||
|
return cmdline
|
||
|
|
||
|
|
||
|
CHOOSERS = {
|
||
|
"gnome": ZenityFileChooser,
|
||
|
"kde": KDialogFileChooser,
|
||
|
"yad": YADFileChooser
|
||
|
}
|
||
|
|
||
|
|
||
|
class LinuxFileChooser(FileChooser):
|
||
|
'''FileChooser implementation for GNu/Linux. Accepts one additional
|
||
|
keyword argument, *desktop_override*, which, if set, overrides the
|
||
|
back-end that will be used. Set it to "gnome" for Zenity, to "kde"
|
||
|
for KDialog and to "yad" for YAD (Yet Another Dialog).
|
||
|
If set to None or not set, a default one will be picked based on
|
||
|
the running desktop environment and installed back-ends.
|
||
|
'''
|
||
|
|
||
|
desktop = None
|
||
|
if (str(os.environ.get("XDG_CURRENT_DESKTOP")).lower() == "kde"
|
||
|
and which("kdialog")):
|
||
|
desktop = "kde"
|
||
|
elif (str(os.environ.get("DESKTOP_SESSION")).lower() == "trinity"
|
||
|
and which('kdialog')):
|
||
|
desktop = "kde"
|
||
|
elif which("yad"):
|
||
|
desktop = "yad"
|
||
|
elif which("zenity"):
|
||
|
desktop = "gnome"
|
||
|
|
||
|
def _file_selection_dialog(self, desktop_override=desktop, **kwargs):
|
||
|
if not desktop_override:
|
||
|
desktop_override = self.desktop
|
||
|
# This means we couldn't find any back-end
|
||
|
if not desktop_override:
|
||
|
raise OSError("No back-end available. Please install one.")
|
||
|
|
||
|
chooser = CHOOSERS[desktop_override]
|
||
|
c = chooser(**kwargs)
|
||
|
return c.run()
|
||
|
|
||
|
|
||
|
def instance():
|
||
|
return LinuxFileChooser()
|