mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Start work on helper process for windows file dialogs
This commit is contained in:
parent
f451b173ed
commit
d8fe21d156
79
setup/installer/windows/file_dialogs.cpp
Normal file
79
setup/installer/windows/file_dialogs.cpp
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* file_dialogs.c
|
||||||
|
* Copyright (C) 2016 Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
*
|
||||||
|
* Distributed under terms of the GPL3 license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <Shobjidl.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define PRINTERR(x) fprintf(stderr, x); fflush(stderr);
|
||||||
|
#define REPORTERR(x) { PRINTERR(x); ret = 1; goto error; }
|
||||||
|
#define CALLCOM(x, err) hr = x; if(FAILED(hr)) REPORTERR(err)
|
||||||
|
|
||||||
|
int show_dialog(HWND parent, bool save_dialog) {
|
||||||
|
int ret = 0;
|
||||||
|
IFileDialog *pfd = NULL;
|
||||||
|
IShellItem *psiResult = NULL;
|
||||||
|
DWORD dwFlags;
|
||||||
|
HRESULT hr = S_OK;
|
||||||
|
hr = CoInitialize(NULL);
|
||||||
|
if (FAILED(hr)) { PRINTERR("Failed to initialize COM"); return 1; }
|
||||||
|
|
||||||
|
CALLCOM(CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, (save_dialog ? IID_IFileSaveDialog : IID_IFileOpenDialog), reinterpret_cast<LPVOID*>(&pfd)), "Failed to create COM object for file dialog")
|
||||||
|
CALLCOM(pfd->GetOptions(&dwFlags), "Failed to get options")
|
||||||
|
dwFlags |= FOS_FORCEFILESYSTEM;
|
||||||
|
CALLCOM(pfd->SetOptions(dwFlags), "Failed to set options")
|
||||||
|
hr = pfd->Show(parent);
|
||||||
|
if (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED)) goto error;
|
||||||
|
if (FAILED(hr)) REPORTERR("Failed to show dialog")
|
||||||
|
|
||||||
|
CALLCOM(pfd->GetResult(&psiResult), "Failed to get dialog result")
|
||||||
|
|
||||||
|
error:
|
||||||
|
if(pfd) pfd->Release();
|
||||||
|
CoUninitialize();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool read_bytes(size_t sz, char* buf, bool allow_incomplete=false) {
|
||||||
|
char *ptr = buf, *limit = buf + sz;
|
||||||
|
while(limit > ptr && !feof(stdin) && !ferror(stdin)) {
|
||||||
|
ptr += fread(ptr, 1, limit - ptr, stdin);
|
||||||
|
}
|
||||||
|
if (ferror(stdin)) { PRINTERR("Failed to read from stdin!"); return false; }
|
||||||
|
if (ptr - buf != sz) { if (!allow_incomplete) PRINTERR("Truncated input!"); return false; }
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define READ(x, y) if (!read_bytes((x), (y))) return 1;
|
||||||
|
|
||||||
|
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow) {
|
||||||
|
char buf[257];
|
||||||
|
size_t key_size = 0;
|
||||||
|
HWND parent = NULL;
|
||||||
|
bool save_dialog = false;
|
||||||
|
|
||||||
|
while(!feof(stdin)) {
|
||||||
|
memset(buf, 0, sizeof(buf));
|
||||||
|
if(!read_bytes(1, buf, true)) { if (feof(stdin)) break; return 1;}
|
||||||
|
key_size = (size_t)buf[0];
|
||||||
|
READ(key_size, buf);
|
||||||
|
if (key_size == 4 && memcmp(buf, "HWND", 4) == 0) {
|
||||||
|
READ(sizeof(HWND), buf);
|
||||||
|
if (sizeof(HWND) == 8) parent = (HWND)*((__int64*)buf);
|
||||||
|
else if (sizeof(HWND) == 4) parent = (HWND)*((__int32*)buf);
|
||||||
|
else { fprintf(stderr, "Unknown pointer size: %d", sizeof(HWND)); return 1;}
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
PRINTERR("Unknown key");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return show_dialog(parent, save_dialog);
|
||||||
|
}
|
90
src/calibre/gui2/win_file_dialogs.py
Normal file
90
src/calibre/gui2/win_file_dialogs.py
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
#!/usr/bin/env python2
|
||||||
|
# vim:fileencoding=utf-8
|
||||||
|
# License: GPLv3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import,
|
||||||
|
print_function)
|
||||||
|
import sys, subprocess, struct, os
|
||||||
|
from threading import Thread
|
||||||
|
|
||||||
|
from PyQt5.Qt import QMainWindow, QApplication, QPushButton, pyqtSignal, QEventLoop, Qt
|
||||||
|
|
||||||
|
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 get_hwnd(widget=None):
|
||||||
|
ewid = None
|
||||||
|
if widget is not None:
|
||||||
|
ewid = widget.effectiveWinId()
|
||||||
|
if ewid is None:
|
||||||
|
return None
|
||||||
|
return int(ewid)
|
||||||
|
|
||||||
|
def serialize_hwnd(hwnd):
|
||||||
|
if hwnd is None:
|
||||||
|
return b''
|
||||||
|
return struct.pack(b'=' + (b'B4sQ' if is64bit else b'I'), 4, b'HWND', int(hwnd))
|
||||||
|
|
||||||
|
def serialize_string(key, val):
|
||||||
|
key = key.encode('ascii') if not isinstance(key, bytes) else key
|
||||||
|
val = type('')(val).encode('utf-8')
|
||||||
|
if len(val) > 2**16 - 1:
|
||||||
|
raise ValueError('%s is too long' % key)
|
||||||
|
return struct.pack(b'=B%dsH%ds' % (len(key), len(val)), len(key), key, len(val), val)
|
||||||
|
|
||||||
|
class Helper(Thread):
|
||||||
|
|
||||||
|
def __init__(self, process, data, callback):
|
||||||
|
Thread.__init__(self, name='FileDialogHelper')
|
||||||
|
self.process = process
|
||||||
|
self.callback = callback
|
||||||
|
self.data = data
|
||||||
|
self.daemon = True
|
||||||
|
self.rc = 0
|
||||||
|
self.stdoutdata = None
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.stdoutdata, self.stderrdata = self.process.communicate(b''.join(self.data))
|
||||||
|
self.rc = self.process.wait()
|
||||||
|
self.callback()
|
||||||
|
|
||||||
|
class Loop(QEventLoop):
|
||||||
|
|
||||||
|
dialog_closed = pyqtSignal()
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
QEventLoop.__init__(self)
|
||||||
|
self.dialog_closed.connect(self.exit, type=Qt.QueuedConnection)
|
||||||
|
|
||||||
|
def run_file_dialog(parent=None, title=None):
|
||||||
|
data = []
|
||||||
|
if parent is not None:
|
||||||
|
data.append(serialize_hwnd(get_hwnd(parent)))
|
||||||
|
if title is not None:
|
||||||
|
data.append(serialize_string('TITLE', title))
|
||||||
|
loop = Loop()
|
||||||
|
h = Helper(subprocess.Popen(
|
||||||
|
[HELPER], stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE),
|
||||||
|
data, loop.dialog_closed.emit)
|
||||||
|
h.start()
|
||||||
|
loop.exec_(QEventLoop.ExcludeUserInputEvents)
|
||||||
|
if h.rc != 0:
|
||||||
|
raise Exception('File dialog failed: ' + h.stderrdata.decode('utf-8'))
|
||||||
|
if not h.stdoutdata:
|
||||||
|
return ()
|
||||||
|
return tuple(x.decode('utf-8') for x in h.stdoutdata.split(b'\0'))
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
HELPER = sys.argv[-1]
|
||||||
|
app = QApplication([])
|
||||||
|
q = QMainWindow()
|
||||||
|
|
||||||
|
def clicked():
|
||||||
|
print(run_file_dialog(b)), sys.stdout.flush()
|
||||||
|
|
||||||
|
b = QPushButton('click me')
|
||||||
|
b.clicked.connect(clicked)
|
||||||
|
q.setCentralWidget(b)
|
||||||
|
q.show()
|
||||||
|
app.exec_()
|
Loading…
x
Reference in New Issue
Block a user