mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Windows: Use a helper process to display file open/save dialogs. This should fix most crashes caused by poorly designed shell extensions
The helper process only depends on the C runtime, no Qt/Python DLLs are loaded.
This commit is contained in:
parent
9c96e5f877
commit
c1ef4b0294
@ -678,15 +678,84 @@ class FileDialog(QObject):
|
||||
return tuple(os.path.abspath(unicode(i)) for i in self.fd.selectedFiles())
|
||||
return tuple(self.selected_files)
|
||||
|
||||
has_windows_file_dialog_helper = False
|
||||
if iswindows and 'CALIBRE_NO_NATIVE_FILEDIALOGS' not in os.environ:
|
||||
from calibre.gui2.win_file_dialogs import is_ok as has_windows_file_dialog_helper
|
||||
has_windows_file_dialog_helper = has_windows_file_dialog_helper()
|
||||
if has_windows_file_dialog_helper:
|
||||
from calibre.gui2.win_file_dialogs import choose_files, choose_images, choose_dir, choose_save_file
|
||||
else:
|
||||
|
||||
def choose_dir(window, name, title, default_dir='~', no_save_dir=False):
|
||||
fd = FileDialog(title=title, filters=[], add_all_files_filter=False,
|
||||
parent=window, name=name, mode=QFileDialog.Directory,
|
||||
default_dir=default_dir, no_save_dir=no_save_dir)
|
||||
dir = fd.get_files()
|
||||
fd.setParent(None)
|
||||
if dir:
|
||||
return dir[0]
|
||||
def choose_dir(window, name, title, default_dir='~', no_save_dir=False):
|
||||
fd = FileDialog(title=title, filters=[], add_all_files_filter=False,
|
||||
parent=window, name=name, mode=QFileDialog.Directory,
|
||||
default_dir=default_dir, no_save_dir=no_save_dir)
|
||||
dir = fd.get_files()
|
||||
fd.setParent(None)
|
||||
if dir:
|
||||
return dir[0]
|
||||
|
||||
def choose_files(window, name, title,
|
||||
filters=[], all_files=True, select_only_single_file=False, default_dir=u'~'):
|
||||
'''
|
||||
Ask user to choose a bunch of files.
|
||||
:param name: Unique dialog name used to store the opened directory
|
||||
:param title: Title to show in dialogs titlebar
|
||||
:param filters: list of allowable extensions. Each element of the list
|
||||
must be a 2-tuple with first element a string describing
|
||||
the type of files to be filtered and second element a list
|
||||
of extensions.
|
||||
:param all_files: If True add All files to filters.
|
||||
:param select_only_single_file: If True only one file can be selected
|
||||
'''
|
||||
mode = QFileDialog.ExistingFile if select_only_single_file else QFileDialog.ExistingFiles
|
||||
fd = FileDialog(title=title, name=name, filters=filters, default_dir=default_dir,
|
||||
parent=window, add_all_files_filter=all_files, mode=mode,
|
||||
)
|
||||
fd.setParent(None)
|
||||
if fd.accepted:
|
||||
return fd.get_files()
|
||||
return None
|
||||
|
||||
def choose_save_file(window, name, title, filters=[], all_files=True, initial_path=None, initial_filename=None):
|
||||
'''
|
||||
Ask user to choose a file to save to. Can be a non-existent file.
|
||||
:param filters: list of allowable extensions. Each element of the list
|
||||
must be a 2-tuple with first element a string describing
|
||||
the type of files to be filtered and second element a list
|
||||
of extensions.
|
||||
:param all_files: If True add All files to filters.
|
||||
:param initial_path: The initially selected path (does not need to exist). Cannot be used with initial_filename.
|
||||
:param initial_filename: If specified, the initially selected path is this filename in the previously used directory. Cannot be used with initial_path.
|
||||
'''
|
||||
kwargs = dict(title=title, name=name, filters=filters,
|
||||
parent=window, add_all_files_filter=all_files, mode=QFileDialog.AnyFile)
|
||||
if initial_path is not None:
|
||||
kwargs['no_save_dir'] = True
|
||||
kwargs['default_dir'] = initial_path
|
||||
elif initial_filename is not None:
|
||||
kwargs['combine_file_and_saved_dir'] = True
|
||||
kwargs['default_dir'] = initial_filename
|
||||
fd = FileDialog(**kwargs)
|
||||
fd.setParent(None)
|
||||
ans = None
|
||||
if fd.accepted:
|
||||
ans = fd.get_files()
|
||||
if ans:
|
||||
ans = ans[0]
|
||||
return ans
|
||||
|
||||
def choose_images(window, name, title, select_only_single_file=True,
|
||||
formats=('png', 'gif', 'jpg', 'jpeg', 'svg')):
|
||||
mode = QFileDialog.ExistingFile if select_only_single_file else QFileDialog.ExistingFiles
|
||||
fd = FileDialog(title=title, name=name,
|
||||
filters=[(_('Images'), list(formats))],
|
||||
parent=window, add_all_files_filter=False, mode=mode,
|
||||
)
|
||||
fd.setParent(None)
|
||||
if fd.accepted:
|
||||
return fd.get_files()
|
||||
return None
|
||||
|
||||
def choose_osx_app(window, name, title, default_dir='/Applications'):
|
||||
fd = FileDialog(title=title, parent=window, name=name, mode=QFileDialog.ExistingFile,
|
||||
@ -696,68 +765,6 @@ def choose_osx_app(window, name, title, default_dir='/Applications'):
|
||||
if app:
|
||||
return app
|
||||
|
||||
def choose_files(window, name, title,
|
||||
filters=[], all_files=True, select_only_single_file=False, default_dir=u'~'):
|
||||
'''
|
||||
Ask user to choose a bunch of files.
|
||||
:param name: Unique dialog name used to store the opened directory
|
||||
:param title: Title to show in dialogs titlebar
|
||||
:param filters: list of allowable extensions. Each element of the list
|
||||
must be a 2-tuple with first element a string describing
|
||||
the type of files to be filtered and second element a list
|
||||
of extensions.
|
||||
:param all_files: If True add All files to filters.
|
||||
:param select_only_single_file: If True only one file can be selected
|
||||
'''
|
||||
mode = QFileDialog.ExistingFile if select_only_single_file else QFileDialog.ExistingFiles
|
||||
fd = FileDialog(title=title, name=name, filters=filters, default_dir=default_dir,
|
||||
parent=window, add_all_files_filter=all_files, mode=mode,
|
||||
)
|
||||
fd.setParent(None)
|
||||
if fd.accepted:
|
||||
return fd.get_files()
|
||||
return None
|
||||
|
||||
def choose_save_file(window, name, title, filters=[], all_files=True, initial_path=None, initial_filename=None):
|
||||
'''
|
||||
Ask user to choose a file to save to. Can be a non-existent file.
|
||||
:param filters: list of allowable extensions. Each element of the list
|
||||
must be a 2-tuple with first element a string describing
|
||||
the type of files to be filtered and second element a list
|
||||
of extensions.
|
||||
:param all_files: If True add All files to filters.
|
||||
:param initial_path: The initially selected path (does not need to exist). Cannot be used with initial_filename.
|
||||
:param initial_filename: If specified, the initially selected path is this filename in the previously used directory. Cannot be used with initial_path.
|
||||
'''
|
||||
kwargs = dict(title=title, name=name, filters=filters,
|
||||
parent=window, add_all_files_filter=all_files, mode=QFileDialog.AnyFile)
|
||||
if initial_path is not None:
|
||||
kwargs['no_save_dir'] = True
|
||||
kwargs['default_dir'] = initial_path
|
||||
elif initial_filename is not None:
|
||||
kwargs['combine_file_and_saved_dir'] = True
|
||||
kwargs['default_dir'] = initial_filename
|
||||
fd = FileDialog(**kwargs)
|
||||
fd.setParent(None)
|
||||
ans = None
|
||||
if fd.accepted:
|
||||
ans = fd.get_files()
|
||||
if ans:
|
||||
ans = ans[0]
|
||||
return ans
|
||||
|
||||
def choose_images(window, name, title, select_only_single_file=True,
|
||||
formats=('png', 'gif', 'jpg', 'jpeg', 'svg')):
|
||||
mode = QFileDialog.ExistingFile if select_only_single_file else QFileDialog.ExistingFiles
|
||||
fd = FileDialog(title=title, name=name,
|
||||
filters=[('Images', list(formats))],
|
||||
parent=window, add_all_files_filter=False, mode=mode,
|
||||
)
|
||||
fd.setParent(None)
|
||||
if fd.accepted:
|
||||
return fd.get_files()
|
||||
return None
|
||||
|
||||
def pixmap_to_data(pixmap, format='JPEG', quality=90):
|
||||
'''
|
||||
Return the QPixmap pixmap as a string saved in the specified format.
|
||||
|
@ -13,12 +13,17 @@ is64bit = sys.maxsize > (1 << 32)
|
||||
base = sys.extensions_location if hasattr(sys, 'new_app_layout') else os.path.dirname(sys.executable)
|
||||
HELPER = os.path.join(base, 'calibre-file-dialogs.exe')
|
||||
|
||||
def is_ok():
|
||||
return os.path.exists(HELPER)
|
||||
|
||||
try:
|
||||
from calibre.constants import filesystem_encoding
|
||||
from calibre.utils.filenames import expanduser
|
||||
from calibre.utils.config import dynamic
|
||||
except ImportError:
|
||||
filesystem_encoding = 'utf-8'
|
||||
expanduser = os.path.expanduser
|
||||
dynamic = {}
|
||||
|
||||
def get_hwnd(widget=None):
|
||||
ewid = None
|
||||
@ -52,6 +57,8 @@ def serialize_file_types(file_types):
|
||||
buf.append(struct.pack(b'=H%ds' % len(x), len(x), x))
|
||||
for name, extensions in file_types:
|
||||
add(name or _('Files'))
|
||||
if isinstance(extensions, basestring):
|
||||
extensions = extensions.split()
|
||||
add('; '.join('*.' + ext.lower() for ext in extensions))
|
||||
return b''.join(buf)
|
||||
|
||||
@ -100,9 +107,10 @@ def run_file_dialog(
|
||||
file_types=()
|
||||
):
|
||||
data = []
|
||||
parent = parent or None
|
||||
if parent is not None:
|
||||
data.append(serialize_hwnd(get_hwnd(parent)))
|
||||
if title is not None:
|
||||
if title:
|
||||
data.append(serialize_string('TITLE', title))
|
||||
if no_symlinks:
|
||||
data.append(serialize_binary('NO_SYMLINKS', no_symlinks))
|
||||
@ -151,6 +159,61 @@ def run_file_dialog(
|
||||
ans = tuple((os.path.abspath(x.decode('utf-8')) for x in h.stdoutdata.split(b'\0') if x))
|
||||
return ans
|
||||
|
||||
def get_initial_folder(name, title, default_dir='~', no_save_dir=False):
|
||||
name = name or 'dialog_' + title
|
||||
if no_save_dir:
|
||||
initial_folder = expanduser(default_dir)
|
||||
else:
|
||||
initial_folder = dynamic.get(name, expanduser(default_dir))
|
||||
if not initial_folder or not os.path.isdir(initial_folder):
|
||||
initial_folder = select_initial_dir(initial_folder)
|
||||
return name, initial_folder
|
||||
|
||||
def choose_dir(window, name, title, default_dir='~', no_save_dir=False):
|
||||
name, initial_folder = get_initial_folder(name, title, default_dir, no_save_dir)
|
||||
ans = run_file_dialog(window, title, only_dirs=True, initial_folder=initial_folder)
|
||||
if ans:
|
||||
ans = ans[0]
|
||||
if not no_save_dir:
|
||||
dynamic.set(name, ans)
|
||||
return ans
|
||||
|
||||
def choose_files(window, name, title,
|
||||
filters=(), all_files=True, select_only_single_file=False, default_dir=u'~'):
|
||||
name, initial_folder = get_initial_folder(name, title, default_dir)
|
||||
file_types = list(filters)
|
||||
if all_files:
|
||||
file_types.append((_('All files'), ['*']))
|
||||
ans = run_file_dialog(window, title, allow_multiple=not select_only_single_file, initial_folder=initial_folder, file_types=file_types)
|
||||
if ans:
|
||||
dynamic.set(name, os.path.dirname(ans[0]))
|
||||
return ans
|
||||
return None
|
||||
|
||||
def choose_images(window, name, title, select_only_single_file=True,
|
||||
formats=('png', 'gif', 'jpg', 'jpeg', 'svg')):
|
||||
file_types = [(_('Images'), list(formats))]
|
||||
return choose_files(window, name, title, select_only_single_file=select_only_single_file, filters=file_types)
|
||||
|
||||
def choose_save_file(window, name, title, filters=[], all_files=True, initial_path=None, initial_filename=None):
|
||||
no_save_dir = False
|
||||
default_dir = '~'
|
||||
filename = initial_filename
|
||||
if initial_path is not None:
|
||||
no_save_dir = True
|
||||
default_dir = select_initial_dir(initial_path)
|
||||
filename = os.path.basename(initial_path)
|
||||
file_types = list(filters)
|
||||
if all_files:
|
||||
file_types.append((_('All files'), ['*']))
|
||||
name, initial_folder = get_initial_folder(name, title, default_dir, no_save_dir)
|
||||
ans = run_file_dialog(window, title, save_as=True, initial_folder=initial_folder, filename=filename, file_types=file_types)
|
||||
if ans:
|
||||
ans = ans[0]
|
||||
if not no_save_dir:
|
||||
dynamic.set(name, ans)
|
||||
return ans
|
||||
|
||||
def test():
|
||||
p = subprocess.Popen([HELPER], stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
echo = '\U0001f431 Hello world!'
|
||||
|
Loading…
x
Reference in New Issue
Block a user