Sideband/sbapp/plyer/platforms/linux/filechooser.py
2024-06-02 18:31:58 +02:00

270 lines
7.5 KiB
Python

'''
Linux file chooser
------------------
'''
from sbapp.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 isinstance(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 isinstance(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",
"--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 isinstance(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()