%PDF- %PDF-
| Direktori : /lib/calibre/calibre/gui2/ |
| Current File : //lib/calibre/calibre/gui2/linux_file_dialogs.py |
#!/usr/bin/env python3
# License: GPLv3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
import functools
import os
import subprocess
import sys
import time
from threading import Thread
from qt.core import QEventLoop
from calibre import force_unicode
from calibre.constants import DEBUG, filesystem_encoding, preferred_encoding
from calibre.utils.config import dynamic
from polyglot.builtins import reraise, string_or_bytes
def dialog_name(name, title):
return name or 'dialog_' + title
def get_winid(widget=None):
if widget is not None:
return widget.effectiveWinId()
def detect_desktop_environment():
de = os.getenv('XDG_CURRENT_DESKTOP')
if de:
return de.upper().split(':', 1)[0]
if os.getenv('KDE_FULL_SESSION') == 'true':
return 'KDE'
if os.getenv('GNOME_DESKTOP_SESSION_ID'):
return 'GNOME'
ds = os.getenv('DESKTOP_SESSION')
if ds and ds.upper() in {'GNOME', 'XFCE'}:
return ds.upper()
def is_executable_present(name):
PATH = os.getenv('PATH') or ''
for path in PATH.split(os.pathsep):
if os.access(os.path.join(path, name), os.X_OK):
return True
return False
def process_path(x):
if isinstance(x, bytes):
x = x.decode(filesystem_encoding)
return os.path.abspath(os.path.expanduser(x))
def ensure_dir(path, default='~'):
while path and path != '/' and not os.path.isdir(path):
path = os.path.dirname(path)
if path == '/':
path = os.path.expanduser(default)
return path or os.path.expanduser(default)
def get_initial_dir(name, title, default_dir, no_save_dir):
if no_save_dir:
return ensure_dir(process_path(default_dir))
key = dialog_name(name, title)
saved = dynamic.get(key)
if not isinstance(saved, string_or_bytes):
saved = None
if saved and os.path.isdir(saved):
return ensure_dir(process_path(saved))
return ensure_dir(process_path(default_dir))
def save_initial_dir(name, title, ans, no_save_dir, is_file=False):
if ans and not no_save_dir:
if is_file:
ans = os.path.dirname(os.path.abspath(ans))
key = dialog_name(name, title)
dynamic.set(key, ans)
def encode_arg(title):
if isinstance(title, str):
try:
title = title.encode(preferred_encoding)
except UnicodeEncodeError:
title = title.encode('utf-8')
return title
def image_extensions():
from calibre.gui2.dnd import image_extensions
return image_extensions()
def decode_output(raw):
raw = raw or b''
try:
return raw.decode(preferred_encoding)
except UnicodeDecodeError:
return force_unicode(raw, 'utf-8')
def run(cmd):
from calibre.gui2 import sanitize_env_vars
if DEBUG:
try:
print(cmd)
except Exception:
pass
with sanitize_env_vars():
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = p.communicate()
ret = p.wait()
return ret, decode_output(stdout), decode_output(stderr)
# KDE {{{
def kdialog_supports_desktopfile():
ans = getattr(kdialog_supports_desktopfile, 'ans', None)
if ans is None:
from calibre.gui2 import sanitize_env_vars
try:
with sanitize_env_vars():
raw = subprocess.check_output(['kdialog', '--help'])
except (subprocess.CalledProcessError, FileNotFoundError, OSError):
raw = b'--desktopfile'
ans = kdialog_supports_desktopfile.ans = b'--desktopfile' in raw
return ans
def kde_cmd(window, title, *rest):
ans = ['kdialog', '--title', title]
if kdialog_supports_desktopfile():
ans += ['--desktopfile', 'calibre-gui']
winid = get_winid(window)
if winid is not None:
ans += ['--attach', str(int(winid))]
return ans + list(rest)
def run_kde(cmd):
ret, stdout, stderr = run(cmd)
if ret == 1:
return # canceled
if ret != 0:
raise ValueError(f'KDE file dialog aborted with return code: {ret} and stderr: {stderr}')
ans = stdout.splitlines()
return ans
def kdialog_choose_dir(window, name, title, default_dir='~', no_save_dir=False):
initial_dir = get_initial_dir(name, title, default_dir, no_save_dir)
ans = run_kde(kde_cmd(window, title, '--getexistingdirectory', initial_dir))
ans = None if ans is None else ans[0]
save_initial_dir(name, title, ans, no_save_dir)
return ans
def kdialog_filters(filters, all_files=True):
ans = []
for name, exts in filters:
if not exts or (len(exts) == 1 and exts[0] == '*'):
ans.append(name + ' (*)')
else:
ans.append('{} ({})'.format(name, ' '.join('*.' + x for x in exts)))
if all_files:
ans.append(_('All files') + ' (*)')
return '\n'.join(ans)
def kdialog_choose_files(
window,
name,
title,
filters=[],
all_files=True,
select_only_single_file=False,
default_dir='~'):
initial_dir = get_initial_dir(name, title, default_dir, False)
args = []
if not select_only_single_file:
args += '--multiple --separate-output'.split()
args += ['--getopenfilename', initial_dir, kdialog_filters(filters, all_files)]
ans = run_kde(kde_cmd(window, title, *args))
save_initial_dir(name, title, ans[0] if ans else None, False, is_file=True)
return ans
def kdialog_choose_save_file(window, name, title, filters=[], all_files=True, initial_path=None, initial_filename=None):
if initial_path is not None:
initial_dir = initial_path
else:
initial_dir = get_initial_dir(name, title, '~', False)
if initial_filename:
initial_dir = os.path.join(initial_dir, initial_filename)
args = ['--getsavefilename', initial_dir, kdialog_filters(filters, all_files)]
ans = run_kde(kde_cmd(window, title, *args))
ans = None if ans is None else ans[0]
if initial_path is None:
save_initial_dir(name, title, ans, False, is_file=True)
return ans
def kdialog_choose_images(window, name, title, select_only_single_file=True, formats=None):
return kdialog_choose_files(
window, name, title, select_only_single_file=select_only_single_file, all_files=False,
filters=[(_('Images'), list(formats or image_extensions()))])
# }}}
# GTK {{{
def zenity_cmd(window, title, *rest):
ans = ['zenity', '--modal', '--file-selection', '--title=' + title, '--separator=\n']
winid = get_winid(window)
if winid is not None:
ans += ['--attach=%d' % int(winid)]
return ans + list(rest)
def run_zenity(cmd):
ret, stdout, stderr = run(cmd)
if ret == 1:
return # canceled
if ret != 0:
raise ValueError(f'GTK file dialog aborted with return code: {ret} and stderr: {stderr}')
ans = stdout.splitlines()
return ans
def zenity_choose_dir(window, name, title, default_dir='~', no_save_dir=False):
initial_dir = get_initial_dir(name, title, default_dir, no_save_dir)
ans = run_zenity(zenity_cmd(window, title, '--directory', '--filename', initial_dir))
ans = None if ans is None else ans[0]
save_initial_dir(name, title, ans, no_save_dir)
return ans
def zenity_filters(filters, all_files=True):
ans = []
for name, exts in filters:
if not exts or (len(exts) == 1 and exts[0] == '*'):
ans.append('--file-filter={} | {}'.format(name, '*'))
else:
ans.append('--file-filter={} | {}'.format(name, ' '.join('*.' + x for x in exts)))
if all_files:
ans.append('--file-filter={} | {}'.format(_('All files'), '*'))
return ans
def zenity_choose_files(
window,
name,
title,
filters=[],
all_files=True,
select_only_single_file=False,
default_dir='~'):
initial_dir = get_initial_dir(name, title, default_dir, False)
args = ['--filename=' + os.path.join(initial_dir, '.fgdfg.gdfhjdhf*&^839')]
args += zenity_filters(filters, all_files)
if not select_only_single_file:
args.append('--multiple')
ans = run_zenity(zenity_cmd(window, title, *args))
save_initial_dir(name, title, ans[0] if ans else None, False, is_file=True)
return ans
def zenity_choose_save_file(window, name, title, filters=[], all_files=True, initial_path=None, initial_filename=None):
if initial_path is not None:
initial_dir = initial_path
else:
initial_dir = get_initial_dir(name, title, '~', False)
initial_dir = os.path.join(initial_dir, initial_filename or _('File name'))
args = ['--filename=' + initial_dir, '--confirm-overwrite', '--save']
args += zenity_filters(filters, all_files)
ans = run_zenity(zenity_cmd(window, title, *args))
ans = None if ans is None else ans[0]
if initial_path is None:
save_initial_dir(name, title, ans, False, is_file=True)
return ans
def zenity_choose_images(window, name, title, select_only_single_file=True, formats=None):
return zenity_choose_files(
window, name, title, select_only_single_file=select_only_single_file, all_files=False,
filters=[(_('Images'), list(formats or image_extensions()))])
# }}}
def linux_native_dialog(name):
prefix = check_for_linux_native_dialogs()
func = globals()[f'{prefix}_choose_{name}']
@functools.wraps(func)
def looped(window, *args, **kwargs):
if hasattr(linux_native_dialog, 'native_failed'):
import importlib
m = importlib.import_module('calibre.gui2.qt_file_dialogs')
qfunc = getattr(m, 'choose_' + name)
return qfunc(window, *args, **kwargs)
try:
if window is None:
return func(window, *args, **kwargs)
ret = [None, None]
loop = QEventLoop(window)
def r():
try:
ret[0] = func(window, *args, **kwargs)
except:
ret[1] = sys.exc_info()
while not loop.isRunning():
time.sleep(0.001) # yield so that loop starts
loop.quit()
t = Thread(name='FileDialogHelper', target=r)
t.daemon = True
t.start()
loop.exec(QEventLoop.ProcessEventsFlag.ExcludeUserInputEvents)
if ret[1] is not None:
reraise(*ret[1])
return ret[0]
except Exception:
linux_native_dialog.native_failed = True
import traceback
traceback.print_exc()
return looped(window, *args, **kwargs)
return looped
def check_for_linux_native_dialogs():
ans = getattr(check_for_linux_native_dialogs, 'ans', None)
if ans is None:
de = detect_desktop_environment()
order = ('zenity', 'kdialog')
if de in {'GNOME', 'UNITY', 'MATE', 'XFCE'}:
order = ('zenity',)
elif de in {'KDE', 'LXDE'}:
order = ('kdialog',)
for exe in order:
if is_executable_present(exe):
ans = exe
break
else:
ans = False
check_for_linux_native_dialogs.ans = ans
return ans
if __name__ == '__main__':
# print(repr(kdialog_choose_dir(None, 'testkddcd', 'Testing choose dir...')))
print(repr(kdialog_choose_files(None, 'testkddcf', 'Testing choose files...', select_only_single_file=False, filters=[
('moo', 'epub png'.split()), ('boo', 'docx'.split())], all_files=True)))
# print(repr(kdialog_choose_images(None, 'testkddci', 'Testing choose images...')))
# print(repr(kdialog_choose_save_file(None, 'testkddcs', 'Testing choose save file...', initial_filename='moo.x')))
# print(repr(zenity_choose_dir(None, 'testzcd', 'Testing choose dir...')))
# print(repr(zenity_choose_files(
# None, 'testzcf', 'Testing choose files...', select_only_single_file=False,
# filters=[('moo', 'epub png'.split()), ('boo', 'docx'.split())], all_files=True)))
# print(repr(kdialog_choose_images(None, 'testzi', 'Testing choose images...')))
# print(repr(zenity_choose_save_file(None, 'testzcs', 'Testing choose save file...', filters=[('x', 'epub'.split())])))