%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())])))