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(os.path.abspath(unicode(i)) for i in self.fd.selectedFiles())
|
||||||
return tuple(self.selected_files)
|
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):
|
def choose_dir(window, name, title, default_dir='~', no_save_dir=False):
|
||||||
fd = FileDialog(title=title, filters=[], add_all_files_filter=False,
|
fd = FileDialog(title=title, filters=[], add_all_files_filter=False,
|
||||||
parent=window, name=name, mode=QFileDialog.Directory,
|
parent=window, name=name, mode=QFileDialog.Directory,
|
||||||
default_dir=default_dir, no_save_dir=no_save_dir)
|
default_dir=default_dir, no_save_dir=no_save_dir)
|
||||||
dir = fd.get_files()
|
dir = fd.get_files()
|
||||||
fd.setParent(None)
|
fd.setParent(None)
|
||||||
if dir:
|
if dir:
|
||||||
return dir[0]
|
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'):
|
def choose_osx_app(window, name, title, default_dir='/Applications'):
|
||||||
fd = FileDialog(title=title, parent=window, name=name, mode=QFileDialog.ExistingFile,
|
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:
|
if app:
|
||||||
return 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):
|
def pixmap_to_data(pixmap, format='JPEG', quality=90):
|
||||||
'''
|
'''
|
||||||
Return the QPixmap pixmap as a string saved in the specified format.
|
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)
|
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')
|
HELPER = os.path.join(base, 'calibre-file-dialogs.exe')
|
||||||
|
|
||||||
|
def is_ok():
|
||||||
|
return os.path.exists(HELPER)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from calibre.constants import filesystem_encoding
|
from calibre.constants import filesystem_encoding
|
||||||
from calibre.utils.filenames import expanduser
|
from calibre.utils.filenames import expanduser
|
||||||
|
from calibre.utils.config import dynamic
|
||||||
except ImportError:
|
except ImportError:
|
||||||
filesystem_encoding = 'utf-8'
|
filesystem_encoding = 'utf-8'
|
||||||
expanduser = os.path.expanduser
|
expanduser = os.path.expanduser
|
||||||
|
dynamic = {}
|
||||||
|
|
||||||
def get_hwnd(widget=None):
|
def get_hwnd(widget=None):
|
||||||
ewid = 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))
|
buf.append(struct.pack(b'=H%ds' % len(x), len(x), x))
|
||||||
for name, extensions in file_types:
|
for name, extensions in file_types:
|
||||||
add(name or _('Files'))
|
add(name or _('Files'))
|
||||||
|
if isinstance(extensions, basestring):
|
||||||
|
extensions = extensions.split()
|
||||||
add('; '.join('*.' + ext.lower() for ext in extensions))
|
add('; '.join('*.' + ext.lower() for ext in extensions))
|
||||||
return b''.join(buf)
|
return b''.join(buf)
|
||||||
|
|
||||||
@ -100,9 +107,10 @@ def run_file_dialog(
|
|||||||
file_types=()
|
file_types=()
|
||||||
):
|
):
|
||||||
data = []
|
data = []
|
||||||
|
parent = parent or None
|
||||||
if parent is not None:
|
if parent is not None:
|
||||||
data.append(serialize_hwnd(get_hwnd(parent)))
|
data.append(serialize_hwnd(get_hwnd(parent)))
|
||||||
if title is not None:
|
if title:
|
||||||
data.append(serialize_string('TITLE', title))
|
data.append(serialize_string('TITLE', title))
|
||||||
if no_symlinks:
|
if no_symlinks:
|
||||||
data.append(serialize_binary('NO_SYMLINKS', 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))
|
ans = tuple((os.path.abspath(x.decode('utf-8')) for x in h.stdoutdata.split(b'\0') if x))
|
||||||
return ans
|
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():
|
def test():
|
||||||
p = subprocess.Popen([HELPER], stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
|
p = subprocess.Popen([HELPER], stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||||
echo = '\U0001f431 Hello world!'
|
echo = '\U0001f431 Hello world!'
|
||||||
|
Loading…
x
Reference in New Issue
Block a user