Start work on building calibre on windows

This commit is contained in:
Kovid Goyal 2019-06-19 10:06:54 +05:30
parent 424de7f8fe
commit e804e48747
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
18 changed files with 8077 additions and 9 deletions

View File

@ -151,9 +151,10 @@ def initialize_constants():
return calibre_constants
def run(*args):
def run(*args, **extra_env):
env = os.environ.copy()
env.update(worker_env)
env.update(extra_env)
env['SW'] = PREFIX
env['LD_LIBRARY_PATH'] = LIBDIR
env['SIP_BIN'] = os.path.join(PREFIX, 'bin', 'sip')
@ -165,7 +166,8 @@ def build_c_extensions(ext_dir):
bdir = os.path.join(build_dir(), 'calibre-extension-objects')
if run(
PYTHON, 'setup.py', 'build',
'--output-dir', ext_dir, '--build-dir', bdir
'--output-dir', ext_dir, '--build-dir', bdir,
COMPILER_CWD=bdir
) != 0:
print('Building of calibre C extensions failed', file=sys.stderr)
os.chdir(CALIBRE_DIR)

4379
bypy/windows/XUnzip.cpp Normal file

File diff suppressed because it is too large Load Diff

382
bypy/windows/XUnzip.h Normal file
View File

@ -0,0 +1,382 @@
// XUnzip.h Version 1.3
//
// Authors: Mark Adler et al. (see below)
//
// Modified by: Lucian Wischik
// lu@wischik.com
//
// Version 1.0 - Turned C files into just a single CPP file
// - Made them compile cleanly as C++ files
// - Gave them simpler APIs
// - Added the ability to zip/unzip directly in memory without
// any intermediate files
//
// Modified by: Hans Dietrich
// hdietrich@gmail.com
//
///////////////////////////////////////////////////////////////////////////////
//
// Lucian Wischik's comments:
// --------------------------
// THIS FILE is almost entirely based upon code by info-zip.
// It has been modified by Lucian Wischik.
// The original code may be found at http://www.info-zip.org
// The original copyright text follows.
//
///////////////////////////////////////////////////////////////////////////////
//
// Original authors' comments:
// ---------------------------
// This is version 2002-Feb-16 of the Info-ZIP copyright and license. The
// definitive version of this document should be available at
// ftp://ftp.info-zip.org/pub/infozip/license.html indefinitely.
//
// Copyright (c) 1990-2002 Info-ZIP. All rights reserved.
//
// For the purposes of this copyright and license, "Info-ZIP" is defined as
// the following set of individuals:
//
// Mark Adler, John Bush, Karl Davis, Harald Denker, Jean-Michel Dubois,
// Jean-loup Gailly, Hunter Goatley, Ian Gorman, Chris Herborth, Dirk Haase,
// Greg Hartwig, Robert Heath, Jonathan Hudson, Paul Kienitz,
// David Kirschbaum, Johnny Lee, Onno van der Linden, Igor Mandrichenko,
// Steve P. Miller, Sergio Monesi, Keith Owens, George Petrov, Greg Roelofs,
// Kai Uwe Rommel, Steve Salisbury, Dave Smith, Christian Spieler,
// Antoine Verheijen, Paul von Behren, Rich Wales, Mike White
//
// This software is provided "as is", without warranty of any kind, express
// or implied. In no event shall Info-ZIP or its contributors be held liable
// for any direct, indirect, incidental, special or consequential damages
// arising out of the use of or inability to use this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. Redistributions of source code must retain the above copyright notice,
// definition, disclaimer, and this list of conditions.
//
// 2. Redistributions in binary form (compiled executables) must reproduce
// the above copyright notice, definition, disclaimer, and this list of
// conditions in documentation and/or other materials provided with the
// distribution. The sole exception to this condition is redistribution
// of a standard UnZipSFX binary as part of a self-extracting archive;
// that is permitted without inclusion of this license, as long as the
// normal UnZipSFX banner has not been removed from the binary or disabled.
//
// 3. Altered versions--including, but not limited to, ports to new
// operating systems, existing ports with new graphical interfaces, and
// dynamic, shared, or static library versions--must be plainly marked
// as such and must not be misrepresented as being the original source.
// Such altered versions also must not be misrepresented as being
// Info-ZIP releases--including, but not limited to, labeling of the
// altered versions with the names "Info-ZIP" (or any variation thereof,
// including, but not limited to, different capitalizations),
// "Pocket UnZip", "WiZ" or "MacZip" without the explicit permission of
// Info-ZIP. Such altered versions are further prohibited from
// misrepresentative use of the Zip-Bugs or Info-ZIP e-mail addresses or
// of the Info-ZIP URL(s).
//
// 4. Info-ZIP retains the right to use the names "Info-ZIP", "Zip", "UnZip",
// "UnZipSFX", "WiZ", "Pocket UnZip", "Pocket Zip", and "MacZip" for its
// own source and binary releases.
//
///////////////////////////////////////////////////////////////////////////////
#ifndef XUNZIP_H
#define XUNZIP_H
#ifndef XZIP_H
DECLARE_HANDLE(HZIP); // An HZIP identifies a zip file that has been opened
#endif
typedef DWORD ZRESULT;
// return codes from any of the zip functions. Listed later.
#define ZIP_HANDLE 1
#define ZIP_FILENAME 2
#define ZIP_MEMORY 3
typedef struct
{ int index; // index of this file within the zip
char name[MAX_PATH]; // filename within the zip
DWORD attr; // attributes, as in GetFileAttributes.
FILETIME atime,ctime,mtime;// access, create, modify filetimes
long comp_size; // sizes of item, compressed and uncompressed. These
long unc_size; // may be -1 if not yet known (e.g. being streamed in)
} ZIPENTRY;
typedef struct
{ int index; // index of this file within the zip
TCHAR name[MAX_PATH]; // filename within the zip
DWORD attr; // attributes, as in GetFileAttributes.
FILETIME atime,ctime,mtime;// access, create, modify filetimes
long comp_size; // sizes of item, compressed and uncompressed. These
long unc_size; // may be -1 if not yet known (e.g. being streamed in)
} ZIPENTRYW;
///////////////////////////////////////////////////////////////////////////////
//
// OpenZip()
//
// Purpose: Open an existing zip archive file
//
// Parameters: z - archive file name if flags is ZIP_FILENAME; for other
// uses see below
// len - for memory (ZIP_MEMORY) should be the buffer size;
// for other uses, should be 0
// flags - indicates usage, see below; for files, this will be
// ZIP_FILENAME
//
// Returns: HZIP - non-zero if zip archive opened ok, otherwise 0
//
HZIP OpenZip(void *z, unsigned int len, DWORD flags);
// OpenZip - opens a zip file and returns a handle with which you can
// subsequently examine its contents. You can open a zip file from:
// from a pipe: OpenZip(hpipe_read,0, ZIP_HANDLE);
// from a file (by handle): OpenZip(hfile,0, ZIP_HANDLE);
// from a file (by name): OpenZip("c:\\test.zip",0, ZIP_FILENAME);
// from a memory block: OpenZip(bufstart, buflen, ZIP_MEMORY);
// If the file is opened through a pipe, then items may only be
// accessed in increasing order, and an item may only be unzipped once,
// although GetZipItem can be called immediately before and after unzipping
// it. If it's opened i n any other way, then full random access is possible.
// Note: pipe input is not yet implemented.
///////////////////////////////////////////////////////////////////////////////
//
// GetZipItem()
//
// Purpose: Get information about an item in an open zip archive
//
// Parameters: hz - handle of open zip archive
// index - index number (0 based) of item in zip
// ze - pointer to a ZIPENTRY (if ANSI) or ZIPENTRYW struct
// (if Unicode)
//
// Returns: ZRESULT - ZR_OK if success, otherwise some other value
//
#ifdef _UNICODE
#define GetZipItem GetZipItemW
#else
#define GetZipItem GetZipItemA
#endif
ZRESULT GetZipItemA(HZIP hz, int index, ZIPENTRY *ze);
ZRESULT GetZipItemW(HZIP hz, int index, ZIPENTRYW *ze);
// GetZipItem - call this to get information about an item in the zip.
// If index is -1 and the file wasn't opened through a pipe,
// then it returns information about the whole zipfile
// (and in particular ze.index returns the number of index items).
// Note: the item might be a directory (ze.attr & FILE_ATTRIBUTE_DIRECTORY)
// See below for notes on what happens when you unzip such an item.
// Note: if you are opening the zip through a pipe, then random access
// is not possible and GetZipItem(-1) fails and you can't discover the number
// of items except by calling GetZipItem on each one of them in turn,
// starting at 0, until eventually the call fails. Also, in the event that
// you are opening through a pipe and the zip was itself created into a pipe,
// then then comp_size and sometimes unc_size as well may not be known until
// after the item has been unzipped.
///////////////////////////////////////////////////////////////////////////////
//
// FindZipItem()
//
// Purpose: Find item by name and return information about it
//
// Parameters: hz - handle of open zip archive
// name - name of file to look for inside zip archive
// ic - TRUE = case insensitive
// index - pointer to index number returned, or -1
// ze - pointer to a ZIPENTRY (if ANSI) or ZIPENTRYW struct
// (if Unicode)
//
// Returns: ZRESULT - ZR_OK if success, otherwise some other value
//
#ifdef _UNICODE
#define FindZipItem FindZipItemW
#else
#define FindZipItem FindZipItemA
#endif
ZRESULT FindZipItemA(HZIP hz, const TCHAR *name, bool ic, int *index, ZIPENTRY *ze);
ZRESULT FindZipItemW(HZIP hz, const TCHAR *name, bool ic, int *index, ZIPENTRYW *ze);
// FindZipItem - finds an item by name. ic means 'insensitive to case'.
// It returns the index of the item, and returns information about it.
// If nothing was found, then index is set to -1 and the function returns
// an error code.
///////////////////////////////////////////////////////////////////////////////
//
// UnzipItem()
//
// Purpose: Find item by index and unzip it
//
// Parameters: hz - handle of open zip archive
// index - index number of file to unzip
// dst - target file name of unzipped file
// len - for memory (ZIP_MEMORY. length of buffer;
// otherwise 0
// flags - indicates usage, see below; for files, this will be
// ZIP_FILENAME
//
// Returns: ZRESULT - ZR_OK if success, otherwise some other value
//
ZRESULT UnzipItem(HZIP hz, int index, void *dst, unsigned int len, DWORD flags);
// UnzipItem - given an index to an item, unzips it. You can unzip to:
// to a pipe: UnzipItem(hz,i, hpipe_write,0,ZIP_HANDLE);
// to a file (by handle): UnzipItem(hz,i, hfile,0,ZIP_HANDLE);
// to a file (by name): UnzipItem(hz,i, ze.name,0,ZIP_FILENAME);
// to a memory block: UnzipItem(hz,i, buf,buflen,ZIP_MEMORY);
// In the final case, if the buffer isn't large enough to hold it all,
// then the return code indicates that more is yet to come. If it was
// large enough, and you want to know precisely how big, GetZipItem.
// Note: zip files are normally stored with relative pathnames. If you
// unzip with ZIP_FILENAME a relative pathname then the item gets created
// relative to the current directory - it first ensures that all necessary
// subdirectories have been created. Also, the item may itself be a directory.
// If you unzip a directory with ZIP_FILENAME, then the directory gets created.
// If you unzip it to a handle or a memory block, then nothing gets created
// and it emits 0 bytes.
///////////////////////////////////////////////////////////////////////////////
//
// CloseZip()
//
// Purpose: Close an open zip archive
//
// Parameters: hz - handle to an open zip archive
//
// Returns: ZRESULT - ZR_OK if success, otherwise some other value
//
ZRESULT CloseZip(HZIP hz);
// CloseZip - the zip handle must be closed with this function.
unsigned int FormatZipMessage(ZRESULT code, char *buf,unsigned int len);
// FormatZipMessage - given an error code, formats it as a string.
// It returns the length of the error message. If buf/len points
// to a real buffer, then it also writes as much as possible into there.
// These are the result codes:
#define ZR_OK 0x00000000 // nb. the pseudo-code zr-recent is never returned,
#define ZR_RECENT 0x00000001 // but can be passed to FormatZipMessage.
// The following come from general system stuff (e.g. files not openable)
#define ZR_GENMASK 0x0000FF00
#define ZR_NODUPH 0x00000100 // couldn't duplicate the handle
#define ZR_NOFILE 0x00000200 // couldn't create/open the file
#define ZR_NOALLOC 0x00000300 // failed to allocate some resource
#define ZR_WRITE 0x00000400 // a general error writing to the file
#define ZR_NOTFOUND 0x00000500 // couldn't find that file in the zip
#define ZR_MORE 0x00000600 // there's still more data to be unzipped
#define ZR_CORRUPT 0x00000700 // the zipfile is corrupt or not a zipfile
#define ZR_READ 0x00000800 // a general error reading the file
// The following come from mistakes on the part of the caller
#define ZR_CALLERMASK 0x00FF0000
#define ZR_ARGS 0x00010000 // general mistake with the arguments
#define ZR_NOTMMAP 0x00020000 // tried to ZipGetMemory, but that only works on mmap zipfiles, which yours wasn't
#define ZR_MEMSIZE 0x00030000 // the memory size is too small
#define ZR_FAILED 0x00040000 // the thing was already failed when you called this function
#define ZR_ENDED 0x00050000 // the zip creation has already been closed
#define ZR_MISSIZE 0x00060000 // the indicated input file size turned out mistaken
#define ZR_PARTIALUNZ 0x00070000 // the file had already been partially unzipped
#define ZR_ZMODE 0x00080000 // tried to mix creating/opening a zip
// The following come from bugs within the zip library itself
#define ZR_BUGMASK 0xFF000000
#define ZR_NOTINITED 0x01000000 // initialisation didn't work
#define ZR_SEEK 0x02000000 // trying to seek in an unseekable file
#define ZR_NOCHANGE 0x04000000 // changed its mind on storage, but not allowed
#define ZR_FLATE 0x05000000 // an internal error in the de/inflation code
// e.g.
//
// SetCurrentDirectory("c:\\docs\\stuff");
// HZIP hz = OpenZip("c:\\stuff.zip",0,ZIP_FILENAME);
// ZIPENTRY ze; GetZipItem(hz,-1,&ze); int numitems=ze.index;
// for (int i=0; i<numitems; i++)
// { GetZipItem(hz,i,&ze);
// UnzipItem(hz,i,ze.name,0,ZIP_FILENAME);
// }
// CloseZip(hz);
//
//
// HRSRC hrsrc = FindResource(hInstance,MAKEINTRESOURCE(1),RT_RCDATA);
// HANDLE hglob = LoadResource(hInstance,hrsrc);
// void *zipbuf=LockResource(hglob);
// unsigned int ziplen=SizeofResource(hInstance,hrsrc);
// HZIP hz = OpenZip(zipbuf, ziplen, ZIP_MEMORY);
// - unzip to a membuffer -
// ZIPENTRY ze; int i; FindZipItem(hz,"file.dat",&i,&ze);
// char *ibuf = new char[ze.unc_size];
// UnzipItem(hz,i, ibuf, ze.unc_size,ZIP_MEMORY);
// delete[] buf;
// - unzip to a fixed membuff -
// ZIPENTRY ze; int i; FindZipItem(hz,"file.dat",&i,&ze);
// char ibuf[1024]; ZIPRESULT zr=ZR_MORE; unsigned long totsize=0;
// while (zr==ZR_MORE)
// { zr = UnzipItem(hz,i, ibuf,1024,ZIP_MEMORY);
// unsigned long bufsize=1024; if (zr==ZR_OK) bufsize=ze.unc_size-totsize;
// totsize+=bufsize;
// }
// - unzip to a pipe -
// HANDLE hthread=CreateWavReaderThread(&hread,&hwrite);
// FindZipItem(hz,"sound.wav",&i,&ze);
// UnzipItem(hz,i, hwrite,0,ZIP_HANDLE);
// CloseHandle(hwrite);
// WaitForSingleObject(hthread,INFINITE);
// CloseHandle(hread); CloseHandle(hthread);
// - finished -
// CloseZip(hz);
// // note: no need to free resources obtained through Find/Load/LockResource
//
//
// SetCurrentDirectory("c:\\docs\\pipedzipstuff");
// HANDLE hread,hwrite; CreatePipe(&hread,&hwrite);
// CreateZipWriterThread(hwrite);
// HZIP hz = OpenZip(hread,0,ZIP_HANDLE);
// for (int i=0; ; i++)
// { ZIPENTRY ze; ZRESULT res = GetZipItem(hz,i,&ze);
// if (res!=ZE_OK) break; // no more
// UnzipItem(hz,i, ze.name,0,ZIP_FILENAME);
// }
// CloseZip(hz);
//
// Now we indulge in a little skullduggery so that the code works whether
// the user has included just zip or both zip and unzip.
// Idea: if header files for both zip and unzip are present, then presumably
// the cpp files for zip and unzip are both present, so we will call
// one or the other of them based on a dynamic choice. If the header file
// for only one is present, then we will bind to that particular one.
HZIP OpenZipU(void *z,unsigned int len,DWORD flags);
ZRESULT CloseZipU(HZIP hz);
unsigned int FormatZipMessageU(ZRESULT code, char *buf,unsigned int len);
bool IsZipHandleU(HZIP hz);
#define OpenZip OpenZipU
#ifdef XZIP_H
#undef CloseZip
#define CloseZip(hz) (IsZipHandleU(hz)?CloseZipU(hz):CloseZipZ(hz))
#else
#define CloseZip CloseZipU
#define FormatZipMessage FormatZipMessageU
#endif
#endif //XUNZIP_H

691
bypy/windows/__main__.py Normal file
View File

@ -0,0 +1,691 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
from __future__ import absolute_import, division, print_function, unicode_literals
import errno
import glob
import os
import re
import shutil
import stat
import subprocess
import sys
import zipfile
from bypy.constants import (
PREFIX, SRC as CALIBRE_DIR, SW, is64bit, build_dir, python_major_minor_version
)
from bypy.utils import py_compile, run, walk
from .wix import create_installer
iv = globals()['init_env']
calibre_constants = iv['calibre_constants']
QT_PREFIX = os.path.join(PREFIX, 'qt')
QT_DLLS, QT_PLUGINS, PYQT_MODULES = iv['QT_DLLS'], iv['QT_PLUGINS'], iv['PYQT_MODULES']
APPNAME, VERSION = calibre_constants['appname'], calibre_constants['version']
WINVER = VERSION + '.0'
machine = 'X64' if is64bit else 'X86'
j, d, a, b = os.path.join, os.path.dirname, os.path.abspath, os.path.basename
DESCRIPTIONS = {
'calibre': 'The main calibre program',
'ebook-viewer': 'The calibre e-book viewer',
'ebook-edit': 'The calibre e-book editor',
'lrfviewer': 'Viewer for LRF files',
'ebook-convert': 'Command line interface to the conversion/news download system',
'ebook-meta': 'Command line interface for manipulating e-book metadata',
'calibredb': 'Command line interface to the calibre database',
'calibre-launcher': 'Utility functions common to all executables',
'calibre-debug': 'Command line interface for calibre debugging/development',
'calibre-customize': 'Command line interface to calibre plugin system',
'calibre-server': 'Standalone calibre content server',
'calibre-parallel': 'calibre worker process',
'calibre-smtp': 'Command line interface for sending books via email',
'calibre-eject': 'Helper program for ejecting connected reader devices',
'calibre-file-dialog': 'Helper program to show file open/save dialogs',
}
# https://msdn.microsoft.com/en-us/library/windows/desktop/dn481241(v=vs.85).aspx
SUPPORTED_OS = {
'vista': '{e2011457-1546-43c5-a5fe-008deee3d3f0}',
'w7': '{35138b9a-5d96-4fbd-8e2d-a2440225f93a}',
'w8': '{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}',
'w81': '{1f676c76-80e1-4239-95bb-83d0f6d0da78}',
'w10': '{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}',
}
EXE_MANIFEST = '''\
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<supportedOS Id="{vista}"/>
<supportedOS Id="{w7}"/>
<supportedOS Id="{w8}"/>
<supportedOS Id="{w81}"/>
<supportedOS Id="{w10}"/>
</application>
</compatibility>
</assembly>
'''.format(**SUPPORTED_OS)
def printf(*args, **kw):
print(*args, **kw)
sys.stdout.flush()
class Env(object):
def __init__(self, build_dir):
self.python_base = os.path.join(PREFIX, 'private', 'python')
self.portable_uncompressed_size = 0
self.src_root = CALIBRE_DIR
self.base = j(build_dir, 'winfrozen')
self.app_base = j(self.base, 'app')
self.rc_template = j(d(a(__file__)), 'template.rc')
self.py_ver = '.'.join(map(str, python_major_minor_version()))
self.lib_dir = j(self.app_base, 'Lib')
self.pylib = j(self.app_base, 'pylib.zip')
self.dll_dir = j(self.app_base, 'DLLs')
self.portable_base = j(d(self.base), 'Calibre Portable')
self.obj_dir = j(build_dir, 'launcher')
self.installer_dir = j(build_dir, 'wix')
self.dist = j(SW, 'dist')
def initbase(env):
os.makedirs(env.app_base)
os.mkdir(env.dll_dir)
try:
shutil.rmtree(env.dist)
except EnvironmentError as err:
if err.errno != errno.ENOENT:
raise
os.mkdir(env.dist)
def add_plugins(env, ext_dir):
printf('Adding plugins...')
tgt = env.dll_dir
for f in glob.glob(j(ext_dir, '*.pyd')):
shutil.copy2(f, tgt)
def freeze(env, ext_dir):
shutil.copy2(j(env.src_root, 'LICENSE'), env.base)
printf('Adding resources...')
tgt = j(env.app_base, 'resources')
if os.path.exists(tgt):
shutil.rmtree(tgt)
shutil.copytree(j(env.src_root, 'resources'), tgt)
printf('\tAdding misc binary deps')
def copybin(x):
shutil.copy2(x, env.dll_dir)
try:
shutil.copy2(x + '.manifest', env.dll_dir)
except EnvironmentError as err:
if err.errno != errno.ENOENT:
raise
bindir = os.path.join(PREFIX, 'bin')
for x in ('pdftohtml', 'pdfinfo', 'pdftoppm', 'jpegtran-calibre', 'cjpeg-calibre', 'optipng-calibre', 'JXRDecApp-calibre'):
copybin(os.path.join(bindir, x + '.exe'))
for f in glob.glob(os.path.join(bindir, '*.dll')):
if re.search(r'(easylzma|icutest)', f.lower()) is None:
copybin(f)
copybin(os.path.join(env.python_base, 'python%s.dll' % env.py_ver.replace('.', '')))
for x in glob.glob(os.path.join(env.python_base, 'DLLs', '*')): # python pyd modules
copybin(x)
for f in walk(os.path.join(env.python_base, 'Lib')):
if f.lower().endswith('.dll') and 'scintilla' not in f.lower():
copybin(f)
add_plugins(env, ext_dir)
printf('Adding Qt...')
for x in QT_DLLS:
copybin(os.path.join(QT_PREFIX, 'bin', x + '.dll'))
plugdir = j(QT_PREFIX, 'plugins')
tdir = j(env.app_base, 'qt_plugins')
for d in QT_PLUGINS:
imfd = os.path.join(plugdir, d)
tg = os.path.join(tdir, d)
if os.path.exists(tg):
shutil.rmtree(tg)
shutil.copytree(imfd, tg)
for f in walk(tdir):
if not f.lower().endswith('.dll'):
os.remove(f)
printf('Adding python...')
def ignore_lib(root, items):
ans = []
for x in items:
ext = os.path.splitext(x)[1].lower()
if ext in ('.dll', '.chm', '.htm', '.txt'):
ans.append(x)
return ans
shutil.copytree(r'%s\Lib' % env.python_base, env.lib_dir, ignore=ignore_lib)
install_site_py(env)
# Fix win32com
sp_dir = j(env.lib_dir, 'site-packages')
comext = j(sp_dir, 'win32comext')
shutil.copytree(j(comext, 'shell'), j(sp_dir, 'win32com', 'shell'))
shutil.rmtree(comext)
for pat in ('PyQt5\\uic\\port_v3', ):
x = glob.glob(j(env.lib_dir, 'site-packages', pat))[0]
shutil.rmtree(x)
pyqt = j(env.lib_dir, 'site-packages', 'PyQt5')
for x in {x for x in os.listdir(pyqt) if x.endswith('.pyd')}:
if x.partition('.')[0] not in PYQT_MODULES:
os.remove(j(pyqt, x))
printf('Adding calibre sources...')
for x in glob.glob(j(CALIBRE_DIR, 'src', '*')):
if os.path.isdir(x):
if os.path.exists(os.path.join(x, '__init__.py')):
shutil.copytree(x, j(sp_dir, b(x)))
else:
shutil.copy(x, j(sp_dir, b(x)))
for x in (r'calibre\manual', r'calibre\plugins', 'pythonwin'):
deld = j(sp_dir, x)
if os.path.exists(deld):
shutil.rmtree(deld)
for x in os.walk(j(sp_dir, 'calibre')):
for f in x[-1]:
if not f.endswith('.py'):
os.remove(j(x[0], f))
extract_pyd_modules(env, sp_dir)
printf('Byte-compiling all python modules...')
for x in ('test', 'lib2to3', 'distutils'):
x = j(env.lib_dir, x)
if os.path.exists(x):
shutil.rmtree(x)
py_compile(env.lib_dir.replace(os.sep, '/'))
def embed_manifests(env):
printf('Embedding remaining manifests...')
for manifest in walk(env.base):
dll, ext = os.path.splitext(manifest)
if ext != '.manifest':
continue
res = 2
if os.path.splitext(dll)[1] == '.exe':
res = 1
if os.path.exists(dll) and open(manifest, 'rb').read().strip():
run('mt.exe', '-manifest', manifest, '-outputresource:%s;%d' % (dll, res))
os.remove(manifest)
def extract_pyd_modules(env, site_packages_dir):
printf('\nExtracting .pyd modules from site-packages...')
def extract_pyd(path, root):
fullname = os.path.relpath(path, root).replace(os.sep, '/').replace('/', '.')
dest = os.path.join(env.dll_dir, fullname)
if os.path.exists(dest):
raise ValueError('Cannot extract %s into DLLs as it already exists' % fullname)
os.rename(path, dest)
bpy = dest[:-1]
if os.path.exists(bpy):
with open(bpy, 'rb') as f:
raw = f.read().strip()
if (not raw.startswith('def __bootstrap__') or not raw.endswith('__bootstrap__()')):
raise ValueError('The file %r has non bootstrap code' % bpy)
for ext in ('', 'c', 'o'):
try:
os.remove(bpy + ext)
except EnvironmentError as err:
if err.errno != errno.ENOENT:
raise
def find_pyds(base):
for dirpath, dirnames, filenames in os.walk(base):
for fname in filenames:
if fname.lower().endswith('.pyd'):
yield os.path.join(dirpath, fname)
def process_root(root, base=None):
for path in find_pyds(root):
extract_pyd(path, base or root)
def absp(x):
return os.path.normcase(os.path.abspath(os.path.join(site_packages_dir, x)))
roots = set()
for pth in glob.glob(os.path.join(site_packages_dir, '*.pth')):
for line in open(pth, 'rb').readlines():
line = line.strip()
if line and not line.startswith('#') and os.path.exists(os.path.join(site_packages_dir, line)):
roots.add(absp(line))
for x in os.listdir(site_packages_dir):
x = absp(x)
if x in roots:
process_root(x)
elif os.path.isdir(x):
process_root(x, site_packages_dir)
elif x.lower().endswith('.pyd'):
extract_pyd(x, site_packages_dir)
def embed_resources(env, module, desc=None, extra_data=None, product_description=None):
icon_base = j(env.src_root, 'icons')
icon_map = {'calibre': 'library', 'ebook-viewer': 'viewer', 'ebook-edit': 'ebook-edit',
'lrfviewer': 'viewer', 'calibre-portable': 'library'}
file_type = 'DLL' if module.endswith('.dll') else 'APP'
template = open(env.rc_template, 'rb').read()
bname = b(module)
internal_name = os.path.splitext(bname)[0]
icon = icon_map.get(internal_name, 'command-prompt')
if internal_name.startswith('calibre-portable-'):
icon = 'install'
icon = j(icon_base, icon + '.ico')
if desc is None:
defdesc = 'A dynamic link library' if file_type == 'DLL' else \
'An executable program'
desc = DESCRIPTIONS.get(internal_name, defdesc)
license = 'GNU GPL v3.0'
def e(val):
return val.replace('"', r'\"')
if product_description is None:
product_description = APPNAME + ' - E-book management'
rc = template.format(
icon=icon,
file_type=e(file_type),
file_version=e(WINVER.replace('.', ',')),
file_version_str=e(WINVER),
file_description=e(desc),
internal_name=e(internal_name),
original_filename=e(bname),
product_version=e(WINVER.replace('.', ',')),
product_version_str=e(VERSION),
product_name=e(APPNAME),
product_description=e(product_description),
legal_copyright=e(license),
legal_trademarks=e(APPNAME + ' is a registered U.S. trademark number 3,666,525')
)
if extra_data:
rc += '\nextra extra "%s"' % extra_data
tdir = env.obj_dir
rcf = j(tdir, bname + '.rc')
with open(rcf, 'wb') as f:
f.write(rc)
res = j(tdir, bname + '.res')
run('rc', '/n', '/fo' + res, rcf)
return res
def install_site_py(env):
if not os.path.exists(env.lib_dir):
os.makedirs(env.lib_dir)
shutil.copy2(j(d(__file__), 'site.py'), env.lib_dir)
def build_portable_installer(env):
zf = a(j(env.dist, 'calibre-portable-%s.zip.lz' % VERSION))
usz = env.portable_uncompressed_size or os.path.getsize(zf)
def cc(src, obj):
cflags = '/c /EHsc /MT /W4 /Ox /nologo /D_UNICODE /DUNICODE /DPSAPI_VERSION=1'.split()
cflags.append(r'/I%s\include' % PREFIX)
cflags.append('/DUNCOMPRESSED_SIZE=%d' % usz)
printf('Compiling', obj)
cmd = ['cl.exe'] + cflags + ['/Fo' + obj, src]
run(*cmd)
base = d(a(__file__))
src = j(base, 'portable-installer.cpp')
obj = j(env.obj_dir, b(src) + '.obj')
xsrc = j(base, 'XUnzip.cpp')
xobj = j(env.obj_dir, b(xsrc) + '.obj')
cc(src, obj)
cc(xsrc, xobj)
exe = j(env.dist, 'calibre-portable-installer-%s.exe' % VERSION)
printf('Linking', exe)
manifest = exe + '.manifest'
with open(manifest, 'wb') as f:
f.write(EXE_MANIFEST)
cmd = ['link.exe'] + [
'/INCREMENTAL:NO', '/MACHINE:' + machine,
'/LIBPATH:' + env.obj_dir, '/SUBSYSTEM:WINDOWS',
'/LIBPATH:' + (PREFIX + r'\lib'),
'/RELEASE', '/MANIFEST:EMBED', '/MANIFESTINPUT:' + manifest,
'/ENTRY:wWinMainCRTStartup',
'/OUT:' + exe, embed_resources(
env, exe, desc='Calibre Portable Installer', extra_data=zf, product_description='Calibre Portable Installer'),
xobj, obj, 'User32.lib', 'Shell32.lib', 'easylzma_s.lib',
'Ole32.lib', 'Shlwapi.lib', 'Kernel32.lib', 'Psapi.lib']
run(*cmd)
os.remove(zf)
def build_portable(env):
base = env.portable_base
if os.path.exists(base):
shutil.rmtree(base)
os.makedirs(base)
root = d(a(__file__))
src = j(root, 'portable.c')
obj = j(env.obj_dir, b(src) + '.obj')
cflags = '/c /EHsc /MT /W3 /Ox /nologo /D_UNICODE /DUNICODE'.split()
printf('Compiling', obj)
cmd = ['cl.exe'] + cflags + ['/Fo' + obj, '/Tc' + src]
run(*cmd)
exe = j(base, 'calibre-portable.exe')
printf('Linking', exe)
cmd = ['link.exe'] + [
'/INCREMENTAL:NO', '/MACHINE:' + machine,
'/LIBPATH:' + env.obj_dir, '/SUBSYSTEM:WINDOWS',
'/RELEASE',
'/ENTRY:wWinMainCRTStartup',
'/OUT:' + exe, embed_resources(env, exe, desc='Calibre Portable', product_description='Calibre Portable'),
obj, 'User32.lib']
run(*cmd)
printf('Creating portable installer')
shutil.copytree(env.base, j(base, 'Calibre'))
os.mkdir(j(base, 'Calibre Library'))
os.mkdir(j(base, 'Calibre Settings'))
name = '%s-portable-%s.zip' % (APPNAME, VERSION)
name = j(env.dist, name)
with zipfile.ZipFile(name, 'w', zipfile.ZIP_STORED) as zf:
add_dir_to_zip(zf, base, 'Calibre Portable')
env.portable_uncompressed_size = os.path.getsize(name)
subprocess.check_call([PREFIX + r'\bin\elzma.exe', '-9', '--lzip', name])
def sign_files(env, files):
args = ['signtool.exe', 'sign', '/a', '/fd', 'sha256', '/td', 'sha256', '/d',
'calibre - E-book management', '/du',
'https://calibre-ebook.com', '/tr']
def runcmd(cmd):
for timeserver in ('http://sha256timestamp.ws.symantec.com/sha256/timestamp', 'http://timestamp.comodoca.com/rfc3161',):
try:
subprocess.check_call(cmd + [timeserver] + list(files))
break
except subprocess.CalledProcessError:
print ('Signing failed, retrying with different timestamp server')
else:
raise SystemExit('Signing failed')
runcmd(args)
def sign_installers(env):
printf('Signing installers...')
installers = set()
for f in glob.glob(j(env.dist, '*')):
if f.rpartition('.')[-1].lower() in {'exe', 'msi'}:
installers.add(f)
else:
os.remove(f)
if not installers:
raise ValueError('No installers found')
sign_files(env, installers)
def add_dir_to_zip(zf, path, prefix=''):
'''
Add a directory recursively to the zip file with an optional prefix.
'''
if prefix:
zi = zipfile.ZipInfo(prefix + '/')
zi.external_attr = 16
zf.writestr(zi, '')
cwd = os.path.abspath(os.getcwd())
try:
os.chdir(path)
fp = (prefix + ('/' if prefix else '')).replace('//', '/')
for f in os.listdir('.'):
arcname = fp + f
if os.path.isdir(f):
add_dir_to_zip(zf, f, prefix=arcname)
else:
zf.write(f, arcname)
finally:
os.chdir(cwd)
def build_utils(env):
def build(src, name, subsys='CONSOLE', libs='setupapi.lib'.split()):
printf('Building ' + name)
obj = j(env.obj_dir, (src) + '.obj')
cflags = '/c /EHsc /MD /W3 /Ox /nologo /D_UNICODE'.split()
ftype = '/T' + ('c' if src.endswith('.c') else 'p')
cmd = ['cl.exe'] + cflags + ['/Fo' + obj, ftype + src]
run(*cmd)
exe = j(env.dll_dir, name)
mf = exe + '.manifest'
with open(mf, 'wb') as f:
f.write(EXE_MANIFEST)
cmd = ['link.exe'] + [
'/MACHINE:' + machine,
'/SUBSYSTEM:' + subsys, '/RELEASE', '/MANIFEST:EMBED', '/MANIFESTINPUT:' + mf,
'/OUT:' + exe] + [embed_resources(env, exe), obj] + libs
run(*cmd)
base = d(a(__file__))
build(j(base, 'file_dialogs.cpp'), 'calibre-file-dialog.exe', 'WINDOWS', 'Ole32.lib Shell32.lib'.split())
build(j(base, 'eject.c'), 'calibre-eject.exe')
def build_launchers(env, debug=False):
if not os.path.exists(env.obj_dir):
os.makedirs(env.obj_dir)
dflags = (['/Zi'] if debug else [])
dlflags = (['/DEBUG'] if debug else ['/INCREMENTAL:NO'])
base = d(a(__file__))
sources = [j(base, x) for x in ['util.c', ]]
objects = [j(env.obj_dir, b(x) + '.obj') for x in sources]
cflags = '/c /EHsc /W3 /Ox /nologo /D_UNICODE'.split()
cflags += ['/DPYDLL="python%s.dll"' % env.py_ver.replace('.', ''), '/I%s/include' % env.python_base]
for src, obj in zip(sources, objects):
cmd = ['cl.exe'] + cflags + dflags + ['/MD', '/Fo' + obj, '/Tc' + src]
run(*cmd)
dll = j(env.obj_dir, 'calibre-launcher.dll')
ver = '.'.join(VERSION.split('.')[:2])
cmd = ['link.exe', '/DLL', '/VERSION:' + ver, '/LTCG', '/OUT:' + dll,
'/nologo', '/MACHINE:' + machine] + dlflags + objects + \
[embed_resources(env, dll),
'/LIBPATH:%s/libs' % env.python_base,
'delayimp.lib', 'user32.lib', 'shell32.lib',
'python%s.lib' % env.py_ver.replace('.', ''),
'/delayload:python%s.dll' % env.py_ver.replace('.', '')]
printf('Linking calibre-launcher.dll')
run(*cmd)
src = j(base, 'main.c')
shutil.copy2(dll, env.dll_dir)
basenames, modules, functions = calibre_constants['basenames'], calibre_constants['modules'], calibre_constants['functions']
for typ in ('console', 'gui', ):
printf('Processing %s launchers' % typ)
subsys = 'WINDOWS' if typ == 'gui' else 'CONSOLE'
for mod, bname, func in zip(modules[typ], basenames[typ], functions[typ]):
cflags = '/c /EHsc /MT /W3 /O1 /nologo /D_UNICODE /DUNICODE /GS-'.split()
if typ == 'gui':
cflags += ['/DGUI_APP=']
cflags += ['/DMODULE="%s"' % mod, '/DBASENAME="%s"' % bname,
'/DFUNCTION="%s"' % func]
dest = j(env.obj_dir, bname + '.obj')
printf('Compiling', bname)
cmd = ['cl.exe'] + cflags + dflags + ['/Tc' + src, '/Fo' + dest]
run(*cmd)
exe = j(env.base, bname + '.exe')
lib = dll.replace('.dll', '.lib')
u32 = ['user32.lib']
printf('Linking', bname)
mf = dest + '.manifest'
with open(mf, 'wb') as f:
f.write(EXE_MANIFEST)
cmd = ['link.exe'] + [
'/MACHINE:' + machine, '/NODEFAULTLIB', '/ENTRY:start_here',
'/LIBPATH:' + env.obj_dir, '/SUBSYSTEM:' + subsys,
'/LIBPATH:%s/libs' % env.python_base, '/RELEASE',
'/MANIFEST:EMBED', '/MANIFESTINPUT:' + mf,
'user32.lib', 'kernel32.lib',
'/OUT:' + exe] + u32 + dlflags + [embed_resources(env, exe), dest, lib]
run(*cmd)
def add_to_zipfile(zf, name, base, zf_names):
abspath = j(base, name)
name = name.replace(os.sep, '/')
if name in zf_names:
raise ValueError('Already added %r to zipfile [%r]' % (name, abspath))
zinfo = zipfile.ZipInfo(filename=name, date_time=(1980, 1, 1, 0, 0, 0))
if os.path.isdir(abspath):
if not os.listdir(abspath):
return
zinfo.external_attr = 0o700 << 16
zf.writestr(zinfo, '')
for x in os.listdir(abspath):
add_to_zipfile(zf, name + os.sep + x, base, zf_names)
else:
ext = os.path.splitext(name)[1].lower()
if ext in ('.dll',):
raise ValueError('Cannot add %r to zipfile' % abspath)
zinfo.external_attr = 0o600 << 16
if ext in ('.py', '.pyc', '.pyo'):
with open(abspath, 'rb') as f:
zf.writestr(zinfo, f.read())
zf_names.add(name)
def archive_lib_dir(env):
printf('Putting all python code into a zip file for performance')
zf_names = set()
with zipfile.ZipFile(env.pylib, 'w', zipfile.ZIP_STORED) as zf:
# Add everything in Lib except site-packages to the zip file
for x in os.listdir(env.lib_dir):
if x == 'site-packages':
continue
add_to_zipfile(zf, x, env.lib_dir, zf_names)
sp = j(env.lib_dir, 'site-packages')
# Special handling for pywin32
handled = {'pywin32.pth', 'win32'}
base = j(sp, 'win32', 'lib')
for x in os.listdir(base):
if os.path.splitext(x)[1] not in ('.exe',):
add_to_zipfile(zf, x, base, zf_names)
base = os.path.dirname(base)
for x in os.listdir(base):
if not os.path.isdir(j(base, x)):
if os.path.splitext(x)[1] not in ('.exe',):
add_to_zipfile(zf, x, base, zf_names)
# We dont want the site.py (if any) from site-packages
handled.add('site.pyo')
# The rest of site-packages
for x in os.listdir(sp):
if x in handled or x.endswith('.egg-info'):
continue
absp = j(sp, x)
if os.path.isdir(absp):
if not os.listdir(absp):
continue
add_to_zipfile(zf, x, sp, zf_names)
else:
add_to_zipfile(zf, x, sp, zf_names)
shutil.rmtree(env.lib_dir)
def copy_crt(env):
printf('Copying CRT...')
plat = ('x64' if is64bit else 'x86')
for key, val in os.environ.items():
if 'COMNTOOLS' in key.upper():
redist_dir = os.path.dirname(os.path.dirname(val.rstrip(os.sep)))
redist_dir = os.path.join(redist_dir, 'VC', 'Redist', 'MSVC')
vc_path = glob.glob(os.path.join(redist_dir, '*', plat, '*.CRT'))[0]
break
else:
raise SystemExit('Could not find Visual Studio redistributable CRT')
sdk_path = os.path.join(
os.environ['UNIVERSALCRTSDKDIR'], 'Redist', os.environ['WINDOWSSDKVERSION'],
'ucrt', 'DLLs', plat)
if not os.path.exists(sdk_path):
raise SystemExit('Windows 10 Universal CRT redistributable not found at: %r' % sdk_path)
for dll in glob.glob(os.path.join(sdk_path, '*.dll')):
shutil.copy2(dll, env.dll_dir)
os.chmod(os.path.join(env.dll_dir, b(dll)), stat.S_IRWXU)
for dll in glob.glob(os.path.join(vc_path, '*.dll')):
bname = os.path.basename(dll)
if not bname.startswith('vccorlib') and not bname.startswith('concrt'):
# Those two DLLs are not required vccorlib is for the CORE CLR
# I think concrt is the concurrency runtime for C++ which I believe
# nothing in calibre currently uses
shutil.copy(dll, env.dll_dir)
os.chmod(os.path.join(env.dll_dir, bname), stat.S_IRWXU)
def sign_executables(env):
files_to_sign = []
for path in walk(env.base):
if path.lower().endswith('.exe'):
files_to_sign.append(path)
printf('Signing {} exe files'.format(len(files_to_sign)))
sign_files(env, files_to_sign)
def main():
ext_dir = globals()['ext_dir']
args = globals()['args']
run_tests = iv['run_tests']
env = Env(build_dir())
initbase(env)
build_launchers(env)
build_utils(env)
freeze(env, ext_dir)
embed_manifests(env)
copy_crt(env)
archive_lib_dir(env)
if not args.skip_tests:
run_tests(os.path.join(env.base, 'calibre-debug.exe'), env.base)
if args.sign_installers:
sign_executables(env)
create_installer(env)
if not is64bit:
build_portable(env)
build_portable_installer(env)
if args.sign_installers:
sign_installers(env)

423
bypy/windows/eject.c Normal file
View File

@ -0,0 +1,423 @@
/*
* eject.c
* Copyright (C) 2013 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#include "Windows.h"
#include <stdio.h>
#include <wchar.h>
#include <winioctl.h>
#include <setupapi.h>
#include <devguid.h>
#include <cfgmgr32.h>
#define BUFSIZE 4096
#define LOCK_TIMEOUT 10000 // 10 Seconds
#define LOCK_RETRIES 20
#define BOOL2STR(x) ((x) ? L"True" : L"False")
// Error handling {{{
static void show_error(LPCWSTR msg) {
MessageBeep(MB_ICONERROR);
MessageBoxW(NULL, msg, L"Error", MB_OK|MB_ICONERROR);
}
static void show_detailed_error(LPCWSTR preamble, LPCWSTR msg, int code) {
LPWSTR buf;
buf = (LPWSTR)LocalAlloc(LMEM_ZEROINIT, sizeof(WCHAR)*
(wcslen(msg) + wcslen(preamble) + 80));
_snwprintf_s(buf,
LocalSize(buf) / sizeof(WCHAR), _TRUNCATE,
L"%s\r\n %s (Error Code: %d)\r\n",
preamble, msg, code);
show_error(buf);
LocalFree(buf);
}
static void print_detailed_error(LPCWSTR preamble, LPCWSTR msg, int code) {
fwprintf_s(stderr, L"%s\r\n %s (Error Code: %d)\r\n", preamble, msg, code);
fflush(stderr);
}
static void show_last_error_crt(LPCWSTR preamble) {
WCHAR buf[BUFSIZE];
int err = 0;
_get_errno(&err);
_wcserror_s(buf, BUFSIZE, err);
show_detailed_error(preamble, buf, err);
}
static void show_last_error(LPCWSTR preamble) {
WCHAR *msg = NULL;
DWORD dw = GetLastError();
FormatMessageW(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPWSTR)&msg,
0, NULL );
show_detailed_error(preamble, msg, (int)dw);
}
static void print_last_error(LPCWSTR preamble) {
WCHAR *msg = NULL;
DWORD dw = GetLastError();
FormatMessageW(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPWSTR)&msg,
0, NULL );
print_detailed_error(preamble, msg, (int)dw);
}
// }}}
static void print_help() {
fwprintf_s(stderr, L"Usage: calibre-eject.exe drive-letter1 [drive-letter2 drive-letter3 ...]");
}
static LPWSTR root_path = L"X:\\", device_path = L"X:", volume_access_path = L"\\\\.\\X:";
static wchar_t dos_device_name[MAX_PATH];
static UINT drive_type = 0;
static long device_number = -1;
static DEVINST dev_inst = 0, dev_inst_parent = 0;
// Unmount and eject volumes (drives) {{{
static HANDLE open_volume(wchar_t drive_letter) {
DWORD access_flags;
switch(drive_type) {
case DRIVE_REMOVABLE:
access_flags = GENERIC_READ | GENERIC_WRITE;
break;
case DRIVE_CDROM:
access_flags = GENERIC_READ;
break;
default:
fwprintf_s(stderr, L"Cannot eject %c: Drive type is incorrect.\r\n", drive_letter);
fflush(stderr);
return INVALID_HANDLE_VALUE;
}
return CreateFileW(volume_access_path, access_flags,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0, NULL);
}
static BOOL lock_volume(HANDLE volume) {
DWORD bytes_returned;
DWORD sleep_amount = LOCK_TIMEOUT / LOCK_RETRIES;
int try_count;
// Do this in a loop until a timeout period has expired
for (try_count = 0; try_count < LOCK_RETRIES; try_count++) {
if (DeviceIoControl(volume,
FSCTL_LOCK_VOLUME,
NULL, 0,
NULL, 0,
&bytes_returned,
NULL))
return TRUE;
Sleep(sleep_amount);
}
return FALSE;
}
static BOOL dismount_volume(HANDLE volume) {
DWORD bytes_returned;
return DeviceIoControl( volume,
FSCTL_DISMOUNT_VOLUME,
NULL, 0,
NULL, 0,
&bytes_returned,
NULL);
}
static BOOL disable_prevent_removal_of_volume(HANDLE volume) {
DWORD bytes_returned;
PREVENT_MEDIA_REMOVAL PMRBuffer;
PMRBuffer.PreventMediaRemoval = FALSE;
return DeviceIoControl( volume,
IOCTL_STORAGE_MEDIA_REMOVAL,
&PMRBuffer, sizeof(PREVENT_MEDIA_REMOVAL),
NULL, 0,
&bytes_returned,
NULL);
}
static BOOL auto_eject_volume(HANDLE volume) {
DWORD bytes_returned;
return DeviceIoControl( volume,
IOCTL_STORAGE_EJECT_MEDIA,
NULL, 0,
NULL, 0,
&bytes_returned,
NULL);
}
static BOOL unmount_drive(wchar_t drive_letter, BOOL *remove_safely, BOOL *auto_eject) {
// Unmount the drive identified by drive_letter. Code adapted from:
// http://support.microsoft.com/kb/165721
HANDLE volume;
*remove_safely = FALSE; *auto_eject = FALSE;
volume = open_volume(drive_letter);
if (volume == INVALID_HANDLE_VALUE) return FALSE;
// Lock and dismount the volume.
if (lock_volume(volume) && dismount_volume(volume)) {
*remove_safely = TRUE;
// Set prevent removal to false and eject the volume.
if (disable_prevent_removal_of_volume(volume) && auto_eject_volume(volume))
*auto_eject = TRUE;
}
CloseHandle(volume);
return TRUE;
}
// }}}
// Eject USB device {{{
static void get_device_number(wchar_t drive_letter) {
HANDLE volume;
DWORD bytes_returned = 0;
STORAGE_DEVICE_NUMBER sdn;
volume = CreateFileW(volume_access_path, 0,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0, NULL);
if (volume == INVALID_HANDLE_VALUE) {
print_last_error(L"Failed to open volume while getting device number");
return;
}
if (DeviceIoControl(volume,
IOCTL_STORAGE_GET_DEVICE_NUMBER,
NULL, 0, &sdn, sizeof(sdn),
&bytes_returned, NULL))
device_number = sdn.DeviceNumber;
CloseHandle(volume);
}
static DEVINST get_dev_inst_by_device_number(long device_number, UINT drive_type, LPWSTR dos_device_name) {
GUID *guid;
HDEVINFO dev_info;
DWORD index, bytes_returned;
BOOL bRet, is_floppy;
BYTE Buf[1024];
PSP_DEVICE_INTERFACE_DETAIL_DATA pspdidd;
long res;
HANDLE drive;
STORAGE_DEVICE_NUMBER sdn;
SP_DEVICE_INTERFACE_DATA spdid;
SP_DEVINFO_DATA spdd;
DWORD size;
is_floppy = (wcsstr(dos_device_name, L"\\Floppy") != NULL); // is there a better way?
switch (drive_type) {
case DRIVE_REMOVABLE:
guid = ( (is_floppy) ? (GUID*)&GUID_DEVINTERFACE_FLOPPY : (GUID*)&GUID_DEVINTERFACE_DISK );
break;
case DRIVE_FIXED:
guid = (GUID*)&GUID_DEVINTERFACE_DISK;
break;
case DRIVE_CDROM:
guid = (GUID*)&GUID_DEVINTERFACE_CDROM;
break;
default:
fwprintf_s(stderr, L"Invalid drive type at line: %d\r\n", __LINE__);
fflush(stderr);
return 0;
}
// Get device interface info set handle
// for all devices attached to system
dev_info = SetupDiGetClassDevs(guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
if (dev_info == INVALID_HANDLE_VALUE) {
fwprintf_s(stderr, L"Failed to setup class devs at line: %d\r\n", __LINE__);
fflush(stderr);
return 0;
}
// Retrieve a context structure for a device interface
// of a device information set.
index = 0;
bRet = FALSE;
pspdidd = (PSP_DEVICE_INTERFACE_DETAIL_DATA)Buf;
spdid.cbSize = sizeof(spdid);
while ( TRUE ) {
bRet = SetupDiEnumDeviceInterfaces(dev_info, NULL,
guid, index, &spdid);
if ( !bRet ) break;
size = 0;
SetupDiGetDeviceInterfaceDetail(dev_info,
&spdid, NULL, 0, &size, NULL);
if ( size!=0 && size<=sizeof(Buf) ) {
pspdidd->cbSize = sizeof(*pspdidd); // 5 Bytes!
ZeroMemory((PVOID)&spdd, sizeof(spdd));
spdd.cbSize = sizeof(spdd);
res = SetupDiGetDeviceInterfaceDetail(dev_info, &spdid, pspdidd, size, &size, &spdd);
if ( res ) {
drive = CreateFile(pspdidd->DevicePath,0,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0, NULL);
if ( drive != INVALID_HANDLE_VALUE ) {
bytes_returned = 0;
res = DeviceIoControl(drive,
IOCTL_STORAGE_GET_DEVICE_NUMBER,
NULL, 0, &sdn, sizeof(sdn),
&bytes_returned, NULL);
if ( res ) {
if ( device_number == (long)sdn.DeviceNumber ) {
CloseHandle(drive);
SetupDiDestroyDeviceInfoList(dev_info);
return spdd.DevInst;
}
}
CloseHandle(drive);
}
}
}
index++;
}
SetupDiDestroyDeviceInfoList(dev_info);
fwprintf_s(stderr, L"Invalid device number at line: %d\r\n", __LINE__);
fflush(stderr);
return 0;
}
static void get_parent_device(wchar_t drive_letter) {
get_device_number(drive_letter);
if (device_number == -1) return;
if (QueryDosDeviceW(device_path, dos_device_name, MAX_PATH) == 0) {
print_last_error(L"Failed to query DOS device name");
return;
}
dev_inst = get_dev_inst_by_device_number(device_number,
drive_type, dos_device_name);
if (dev_inst == 0) {
fwprintf_s(stderr, L"Failed to get device by device number");
fflush(stderr);
return;
}
if (CM_Get_Parent(&dev_inst_parent, dev_inst, 0) != CR_SUCCESS) {
fwprintf_s(stderr, L"Failed to get device parent from CM");
fflush(stderr);
return;
}
}
static int eject_device() {
int tries;
CONFIGRET res;
PNP_VETO_TYPE VetoType;
WCHAR VetoNameW[MAX_PATH];
BOOL success;
for ( tries = 0; tries < 3; tries++ ) {
VetoNameW[0] = 0;
res = CM_Request_Device_EjectW(dev_inst_parent,
&VetoType, VetoNameW, MAX_PATH, 0);
success = (res==CR_SUCCESS &&
VetoType==PNP_VetoTypeUnknown);
if ( success ) {
break;
}
Sleep(500); // required to give the next tries a chance!
}
if (!success) {
fwprintf_s(stderr, L"CM_Request_Device_Eject failed after three tries\r\n");
fflush(stderr);
}
return (success) ? 0 : 1;
}
// }}}
int wmain(int argc, wchar_t *argv[ ]) {
int i = 0;
wchar_t drive_letter;
BOOL remove_safely, auto_eject;
// Validate command line arguments
if (argc < 2) { print_help(); return 1; }
for (i = 1; i < argc; i++) {
if (wcsnlen_s(argv[i], 2) != 1) { print_help(); return 1; }
}
// Unmount all mounted volumes and eject volume media
for (i = 1; i < argc; i++) {
drive_letter = *argv[i];
root_path[0] = drive_letter;
device_path[0] = drive_letter;
volume_access_path[4] = drive_letter;
drive_type = GetDriveTypeW(root_path);
if (i == 1 && device_number == -1) {
get_parent_device(drive_letter);
}
if (device_number != -1) {
unmount_drive(drive_letter, &remove_safely, &auto_eject);
fwprintf_s(stdout, L"Unmounting: %c: Remove safely: %s Media Ejected: %s\r\n",
drive_letter, BOOL2STR(remove_safely), BOOL2STR(auto_eject));
fflush(stdout);
}
}
// Eject the parent USB device
if (device_number == -1) {
fwprintf_s(stderr, L"Cannot eject, failed to get device number\r\n");
fflush(stderr);
return 1;
}
if (dev_inst_parent == 0) {
fwprintf_s(stderr, L"Cannot eject, failed to get device parent\r\n");
fflush(stderr);
return 1;
}
return eject_device();
}

9
bypy/windows/en-us.xml Normal file
View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<WixLocalization Culture="en-us" xmlns="http://schemas.microsoft.com/wix/2006/localization">
<String Id="AdvancedWelcomeEulaDlgDescriptionPerUser">Click Advanced to change installation settings.</String>
<String Id="ProgressTextFileCost">Computing space requirements, this may take up to five minutes...</String>
<String Id="ProgressTextCostInitialize">Computing space requirements, this may take up to five minutes...</String>
<String Id="ProgressTextCostFinalize">Computing space requirements, this may take up to five minutes...</String>
<String Id="WaitForCostingDlgText">Please wait while the installer finishes determining your disk space requirements, this may take up to five minutes...</String>
</WixLocalization>

View File

@ -0,0 +1,355 @@
/*
* file_dialogs.c
* Copyright (C) 2016 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#ifndef _UNICODE
#define _UNICODE
#endif
#include <Windows.h>
#include <Shobjidl.h>
#include <comdef.h>
#include <stdio.h>
#include <string.h>
#include <io.h>
#include <fcntl.h>
#define PRINTERR(x) fprintf(stderr, "%s", x); fflush(stderr);
#define SECRET_SIZE 32
void set_dpi_aware() {
// Try SetProcessDpiAwareness first
HINSTANCE sh_core = LoadLibraryW(L"Shcore.dll");
if (sh_core) {
enum ProcessDpiAwareness
{
ProcessDpiUnaware = 0,
ProcessSystemDpiAware = 1,
ProcessPerMonitorDpiAware = 2
};
typedef HRESULT (WINAPI* SetProcessDpiAwarenessFuncType)(ProcessDpiAwareness);
SetProcessDpiAwarenessFuncType SetProcessDpiAwarenessFunc = reinterpret_cast<SetProcessDpiAwarenessFuncType>(GetProcAddress(sh_core, "SetProcessDpiAwareness"));
if (SetProcessDpiAwarenessFunc) {
// We only check for E_INVALIDARG because we would get
// E_ACCESSDENIED if the DPI was already set previously
// and S_OK means the call was successful
if (SetProcessDpiAwarenessFunc(ProcessPerMonitorDpiAware) == E_INVALIDARG) {
PRINTERR("Failed to set process DPI awareness using SetProcessDpiAwareness");
} else {
FreeLibrary(sh_core);
return;
}
}
FreeLibrary(sh_core);
}
// Fall back to SetProcessDPIAware if SetProcessDpiAwareness
// is not available on this system
HINSTANCE user32 = LoadLibraryW(L"user32.dll");
if (user32) {
typedef BOOL (WINAPI* SetProcessDPIAwareFuncType)(void);
SetProcessDPIAwareFuncType SetProcessDPIAwareFunc = reinterpret_cast<SetProcessDPIAwareFuncType>(GetProcAddress(user32, "SetProcessDPIAware"));
if (SetProcessDPIAwareFunc) {
if (!SetProcessDPIAwareFunc()) {
PRINTERR("Failed to set process DPI awareness using SetProcessDPIAware");
}
}
FreeLibrary(user32);
}
}
bool write_bytes(HANDLE pipe, DWORD sz, const char* buf) {
DWORD written = 0;
if (!WriteFile(pipe, buf, sz, &written, NULL)) {
fprintf(stderr, "Failed to write to pipe. GetLastError()=%d\n", GetLastError()); fflush(stderr); return false;
}
if (written != sz) {
fprintf(stderr, "Failed to write to pipe. Incomplete write, leftover bytes: %d", sz - written); fflush(stderr); return false;
}
return true;
}
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;
}
bool from_utf8(size_t sz, const char *src, LPWSTR* ans) {
int asz = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, (int)sz, NULL, 0);
if (!asz) { PRINTERR("Failed to get size of UTF-8 string"); return false; }
*ans = (LPWSTR)calloc(asz+1, sizeof(wchar_t));
if(*ans == NULL) { PRINTERR("Out of memory!"); return false; }
asz = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, (int)sz, *ans, asz);
if (!asz) { PRINTERR("Failed to convert UTF-8 string"); return false; }
return true;
}
char* to_utf8(LPCWSTR src, int *sz) {
// Convert to a null-terminated UTF-8 encoded bytearray, allocated on the heap
char *ans = NULL;
*sz = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, NULL, 0, NULL, NULL);
if (!*sz) { PRINTERR("Failed to get size of UTF-16 string"); return NULL; }
ans = (char*)calloc((*sz) + 1, sizeof(char));
if (ans == NULL) { PRINTERR("Out of memory!"); return NULL; }
*sz = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, ans, *sz, NULL, NULL);
if (!*sz) { PRINTERR("Failed to convert UTF-16 string"); return NULL; }
return ans;
}
static char* rsbuf = NULL;
bool read_string(unsigned short sz, LPWSTR* ans) {
memset(rsbuf, 0, 65537);
if (!read_bytes(sz, rsbuf)) return false;
if (!from_utf8(sz, rsbuf, ans)) return false;
return true;
}
COMDLG_FILTERSPEC *read_file_types(UINT *num_file_types) {
char buf[10] = {0};
COMDLG_FILTERSPEC *ans = NULL;
if(!read_bytes(sizeof(unsigned short), buf)) return NULL;
*num_file_types = *((unsigned short*)buf);
if (*num_file_types < 1 || *num_file_types > 500) { PRINTERR("Invalid number of file types"); return NULL; }
ans = (COMDLG_FILTERSPEC*)calloc((*num_file_types) + 1, sizeof(COMDLG_FILTERSPEC));
if (ans == NULL) { PRINTERR("Out of memory!"); return NULL; }
for(unsigned short i = 0; i < *num_file_types; i++) {
if(!read_bytes(sizeof(unsigned short), buf)) return NULL;
if(!read_string(*((unsigned short*)buf), (LPWSTR*)&(ans[i].pszName))) return NULL;
if(!read_bytes(sizeof(unsigned short), buf)) return NULL;
if(!read_string(*((unsigned short*)buf), (LPWSTR*)&(ans[i].pszSpec))) return NULL;
}
return ans;
}
static void print_com_error(HRESULT hr, const char *msg) {
_com_error err(hr);
LPCWSTR emsg = (LPCWSTR) err.ErrorMessage();
int sz = 0;
const char *buf = to_utf8(emsg, &sz);
if (buf == NULL) { fprintf(stderr, "%s", msg); }
else { fprintf(stderr, "%s: (HRESULT=0x%x) %s\n", msg, hr, buf); }
fflush(stderr);
}
#define REPORTERR(hr, x) { print_com_error(hr, x); ret = 1; goto error; }
#define CALLCOM(x, err) hr = x; if(FAILED(hr)) REPORTERR(hr, err)
int show_dialog(HANDLE pipe, char *secret, HWND parent, bool save_dialog, LPWSTR title, LPWSTR folder, LPWSTR filename, LPWSTR save_path, bool multiselect, bool confirm_overwrite, bool only_dirs, bool no_symlinks, COMDLG_FILTERSPEC *file_types, UINT num_file_types, LPWSTR default_extension) {
int ret = 0, name_sz = 0;
IFileDialog *pfd = NULL;
IShellItemArray *items = NULL;
IShellItem *item = NULL, *folder_item = NULL, *save_path_item = NULL;
char *path = NULL;
DWORD options = 0, item_count = 0;
LPWSTR name = NULL;
HRESULT hr = S_OK;
hr = CoInitialize(NULL);
if (FAILED(hr)) { PRINTERR("Failed to initialize COM"); return 1; }
CALLCOM(CoCreateInstance((save_dialog ? CLSID_FileSaveDialog : 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(&options), "Failed to get options")
options |= FOS_PATHMUSTEXIST;
if (no_symlinks) options |= FOS_NODEREFERENCELINKS;
if (save_dialog) {
options |= FOS_NOREADONLYRETURN;
if (confirm_overwrite) options |= FOS_OVERWRITEPROMPT;
if (save_path != NULL) {
hr = SHCreateItemFromParsingName(save_path, NULL, IID_IShellItem, reinterpret_cast<void **>(&save_path_item));
// Failure to set initial save path is not critical
if (SUCCEEDED(hr)) ((IFileSaveDialog*)pfd)->SetSaveAsItem(save_path_item);
}
} else {
if (multiselect) options |= FOS_ALLOWMULTISELECT;
if (only_dirs) options |= FOS_PICKFOLDERS;
options |= FOS_FILEMUSTEXIST;
}
CALLCOM(pfd->SetOptions(options), "Failed to set options")
if (title != NULL) { CALLCOM(pfd->SetTitle(title), "Failed to set title") }
if (folder != NULL) {
hr = SHCreateItemFromParsingName(folder, NULL, IID_IShellItem, reinterpret_cast<void **>(&folder_item));
// Failure to set initial folder is not critical
if (SUCCEEDED(hr)) pfd->SetFolder(folder_item);
}
if (filename != NULL) pfd->SetFileName(filename); // Failure is not critical
if (!(options & FOS_PICKFOLDERS) && file_types != NULL && num_file_types > 0) {
CALLCOM(pfd->SetFileTypes(num_file_types, file_types), "Failed to set file types")
CALLCOM(pfd->SetFileTypeIndex(1), "Failed to set file type index")
}
if (default_extension != NULL) {
CALLCOM(pfd->SetDefaultExtension(default_extension), "Failed to set default extension")
}
hr = pfd->Show(parent);
if (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED)) goto error;
if (FAILED(hr)) REPORTERR(hr, "Failed to show dialog")
if (save_dialog) {
CALLCOM(pfd->GetResult(&item), "Failed to get save dialog result");
CALLCOM(item->GetDisplayName(SIGDN_FILESYSPATH, &name), "Failed to get display name of save dialog result");
path = to_utf8(name, &name_sz);
CoTaskMemFree(name); name = NULL;
if (path == NULL) return 1;
if (!write_bytes(pipe, SECRET_SIZE+1, secret)) return 1;
if (!write_bytes(pipe, name_sz, path)) return 1;
} else {
CALLCOM(((IFileOpenDialog*)pfd)->GetResults(&items), "Failed to get dialog results");
CALLCOM(items->GetCount(&item_count), "Failed to get count of results");
if (item_count > 0) {
if (!write_bytes(pipe, SECRET_SIZE+1, secret)) return 1;
for (DWORD i = 0; i < item_count; i++) {
CALLCOM(items->GetItemAt(i, &item), "Failed to get result item");
if (SUCCEEDED(item->GetDisplayName(SIGDN_FILESYSPATH, &name))) {
path = to_utf8(name, &name_sz);
CoTaskMemFree(name); name = NULL;
if (path == NULL) return 1;
if (!write_bytes(pipe, name_sz, path)) return 1;
}
}
}
}
error:
if(pfd) pfd->Release();
CoUninitialize();
return ret;
}
#define READ(x, y) if (!read_bytes((x), (y))) return 1;
#define CHECK_KEY(x) (key_size == sizeof(x) - 1 && memcmp(buf, x, sizeof(x) - 1) == 0)
#define READSTR(x) READ(sizeof(unsigned short), buf); if(!read_string(*((unsigned short*)buf), &x)) return 1;
#define SETBINARY(x) if(_setmode(_fileno(x), _O_BINARY) == -1) { PRINTERR("Failed to set binary mode"); return 1; }
#define READBOOL(x) READ(1, buf); x = !!buf[0];
HANDLE open_named_pipe(LPWSTR pipename) {
HANDLE ans = INVALID_HANDLE_VALUE;
while(true) {
ans = CreateFileW(pipename, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
if (ans != INVALID_HANDLE_VALUE) break;
if (GetLastError() != ERROR_PIPE_BUSY) {
fprintf(stderr, "Failed to open pipe. GetLastError()=%d\n", GetLastError()); fflush(stderr); return ans;
}
if (!WaitNamedPipeW(pipename, 20000)) {
fprintf(stderr, "Failed to open pipe. 20 second wait timed out. GetLastError()=%d\n", GetLastError()); fflush(stderr); return ans;
}
}
return ans;
}
typedef HRESULT (__stdcall *app_uid_func)(PCWSTR app_uid);
bool set_app_uid(LPWSTR app_uid) {
// Not available on vista so we have to load the function dynamically
bool ok = false;
HINSTANCE dll = LoadLibraryW(L"Shell32.dll");
if (dll != NULL) {
app_uid_func f = (app_uid_func)GetProcAddress(dll, "SetCurrentProcessExplicitAppUserModelID");
if (f != NULL) ok = f(app_uid) == S_OK;
FreeLibrary(dll); dll = NULL;
}
return ok;
}
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow) {
char buf[257] = {0}, secret[SECRET_SIZE + 1] = {0};
size_t key_size = 0;
HWND parent = NULL;
bool save_dialog = false, multiselect = false, confirm_overwrite = false, only_dirs = false, no_symlinks = false;
unsigned short len = 0;
LPWSTR title = NULL, folder = NULL, filename = NULL, save_path = NULL, echo = NULL, pipename = NULL, default_extension = NULL, app_uid = NULL;
COMDLG_FILTERSPEC *file_types = NULL;
UINT num_file_types = 0;
HANDLE pipe = INVALID_HANDLE_VALUE;
SETBINARY(stdout); SETBINARY(stdin); SETBINARY(stderr);
// The calibre executables call SetDllDirectory, we unset it here just in
// case it interferes with some idiotic shell extension or the other
SetDllDirectory(NULL);
rsbuf = (char*)calloc(65537, sizeof(char));
if(rsbuf == NULL) { PRINTERR("Out of memory!"); return 1; }
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 CHECK_KEY("HWND") {
READ(sizeof(HWND), buf);
#pragma warning( push )
#pragma warning( disable : 4312)
if (sizeof(HWND) == 8) parent = (HWND)*((__int64*)buf);
else if (sizeof(HWND) == 4) parent = (HWND)*((__int32*)buf);
else { fprintf(stderr, "Unknown pointer size: %zd", sizeof(HWND)); fflush(stderr); return 1;}
#pragma warning( pop )
}
else if CHECK_KEY("PIPENAME") { READSTR(pipename); pipe = open_named_pipe(pipename); if (pipe == INVALID_HANDLE_VALUE) return 1; }
else if CHECK_KEY("SECRET") { if(!read_bytes(SECRET_SIZE, secret)) return 1; }
else if CHECK_KEY("APP_UID") { READSTR(app_uid) }
else if CHECK_KEY("TITLE") { READSTR(title) }
else if CHECK_KEY("FOLDER") { READSTR(folder) }
else if CHECK_KEY("FILENAME") { READSTR(filename) }
else if CHECK_KEY("SAVE_PATH") { READSTR(save_path) }
else if CHECK_KEY("SAVE_AS") { READBOOL(save_dialog) }
else if CHECK_KEY("MULTISELECT") { READBOOL(multiselect) }
else if CHECK_KEY("CONFIRM_OVERWRITE") { READBOOL(confirm_overwrite) }
else if CHECK_KEY("ONLY_DIRS") { READBOOL(only_dirs) }
else if CHECK_KEY("NO_SYMLINKS") { READBOOL(no_symlinks) }
else if CHECK_KEY("FILE_TYPES") { file_types = read_file_types(&num_file_types); if (file_types == NULL) return 1; }
else if CHECK_KEY("DEFAULT_EXTENSION") { READSTR(default_extension) }
else if CHECK_KEY("ECHO") { READSTR(echo) }
else {
PRINTERR("Unknown key");
return 1;
}
}
if (pipe == INVALID_HANDLE_VALUE) { PRINTERR("No pipename received"); return 1; }
if (secret == NULL) { PRINTERR("No secret received"); return 1; }
if (echo != NULL) {
int echo_sz = 0;
char *echo_buf = to_utf8(echo, &echo_sz);
if (!write_bytes(pipe, SECRET_SIZE+1, secret)) return 1;
return write_bytes(pipe, echo_sz, echo_buf) ? 0 : 1;
}
if (app_uid != NULL) {
// dont check return status as failure is not critical
set_app_uid(app_uid);
}
set_dpi_aware();
return show_dialog(pipe, secret, parent, save_dialog, title, folder, filename, save_path, multiselect, confirm_overwrite, only_dirs, no_symlinks, file_types, num_file_types, default_extension);
}

113
bypy/windows/main.c Normal file
View File

@ -0,0 +1,113 @@
/*
* Copyright 2009 Kovid Goyal
*/
#ifndef UNICODE
#define UNICODE
#endif
#define WINDOWS_LEAN_AND_MEAN
#include<windows.h>
#include<strsafe.h>
static size_t mystrlen(const wchar_t *buf) {
size_t ans = 0;
if (FAILED(StringCbLengthW(buf, 500, &ans))) return 0;
return ans;
}
static int show_error(const wchar_t *preamble, const wchar_t *msg, const int code) {
wchar_t *buf;
buf = (wchar_t*)LocalAlloc(LMEM_ZEROINIT, sizeof(wchar_t)*
(mystrlen(msg) + mystrlen(preamble) + 80));
if (!buf) {
MessageBox(NULL, preamble, NULL, MB_OK|MB_ICONERROR);
return code;
}
MessageBeep(MB_ICONERROR);
wsprintf(buf, L"%s\r\n %s (Error Code: %d)\r\n", preamble, msg, code);
MessageBox(NULL, buf, NULL, MB_OK|MB_ICONERROR);
LocalFree(buf);
return code;
}
static int show_last_error(wchar_t *preamble) {
wchar_t *msg = NULL;
DWORD dw = GetLastError();
int ret;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPWSTR)&msg,
0,
NULL );
ret = show_error(preamble, msg, (int)dw);
if (msg != NULL) LocalFree(msg);
return ret;
}
typedef int (__cdecl *ENTRYPROC)(const char*, const char*, const char*, int);
static ENTRYPROC load_launcher_dll() {
wchar_t buf[MAX_PATH]; // Cannot use a zero initializer for the array as it generates an implicit call to memset()
wchar_t *dll_point = NULL;
int i = 0;
DWORD sz = 0;
HMODULE dll = 0;
ENTRYPROC entrypoint = NULL;
if ((sz = GetModuleFileNameW(NULL, buf, MAX_PATH)) >= MAX_PATH - 30) {
show_error(L"Installation directory path too long", L"", 1);
return NULL;
}
while (sz > 0) {
if (buf[sz] == L'\\' || buf[sz] == L'/') { dll_point = buf + sz + 1; break; }
sz--;
}
if (dll_point == NULL) {
show_error(L"Executable path has no path separators", L"", 1);
return NULL;
}
wsprintf(dll_point, L"%s\0\0", L"app\\DLLs");
if (SetDllDirectoryW(buf) == 0) {
show_last_error(L"Failed to set DLL directory");
return NULL;
}
// Have to load ucrtbase manually first, otherwise loading fails on systems where the
// Universal CRT is not installed.
if (!LoadLibraryW(L"ucrtbase.dll")) {
show_last_error(L"Unable to find ucrtbase.dll. You should install all Windows updates on your computer to get this file.");
return NULL;
}
if (!(dll = LoadLibraryW(L"calibre-launcher.dll"))) {
show_last_error(L"Failed to load: calibre-launcher.dll");
return NULL;
}
if (!(entrypoint = (ENTRYPROC) GetProcAddress(dll, "execute_python_entrypoint"))) {
show_last_error(L"Failed to get the calibre-launcher dll entry point");
return NULL;
}
return entrypoint;
}
int __stdcall start_here() {
int ret = 0;
ENTRYPROC entrypoint = load_launcher_dll();
if (entrypoint) {
#ifdef GUI_APP
// This should really be returning the value set in the WM_QUIT message, but I cannot be bothered figuring out how to get that.
entrypoint(BASENAME, MODULE, FUNCTION, 1);
#else
ret = entrypoint(BASENAME, MODULE, FUNCTION, 0);
#endif
} else ret = 1;
ExitProcess(ret);
return ret;
}

View File

@ -0,0 +1,618 @@
#ifndef UNICODE
#define UNICODE
#endif
#ifndef _UNICODE
#define _UNICODE
#endif
#include <Windows.h>
#include <Shlobj.h>
#include <Shlwapi.h>
#include <Shellapi.h>
#include <Psapi.h>
#include <wchar.h>
#include <stdio.h>
#include <io.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <easylzma/decompress.h>
#include "XUnzip.h"
#define BUFSIZE 4096
// Error handling {{{
static void show_error(LPCWSTR msg) {
MessageBeep(MB_ICONERROR);
MessageBox(NULL, msg, L"Error", MB_OK|MB_ICONERROR);
}
static void show_detailed_error(LPCWSTR preamble, LPCWSTR msg, int code) {
LPWSTR buf;
buf = (LPWSTR)LocalAlloc(LMEM_ZEROINIT, sizeof(WCHAR)*
(wcslen(msg) + wcslen(preamble) + 80));
_snwprintf_s(buf,
LocalSize(buf) / sizeof(WCHAR), _TRUNCATE,
L"%s\r\n %s (Error Code: %d)\r\n",
preamble, msg, code);
show_error(buf);
LocalFree(buf);
}
static void show_zip_error(LPCWSTR preamble, LPCWSTR msg, ZRESULT code) {
LPWSTR buf;
char msgbuf[1024] = {0};
FormatZipMessage(code, msgbuf, 1024);
buf = (LPWSTR)LocalAlloc(LMEM_ZEROINIT, sizeof(WCHAR)*
(wcslen(preamble) + wcslen(msg) + 1100));
_snwprintf_s(buf,
LocalSize(buf) / sizeof(WCHAR), _TRUNCATE,
L"%s\r\n %s (Error: %S)\r\n",
preamble, msg, msgbuf);
show_error(buf);
LocalFree(buf);
}
static void show_last_error_crt(LPCWSTR preamble) {
WCHAR buf[BUFSIZE];
int err = 0;
_get_errno(&err);
_wcserror_s(buf, BUFSIZE, err);
show_detailed_error(preamble, buf, err);
}
static void show_last_error(LPCWSTR preamble) {
WCHAR *msg = NULL;
DWORD dw = GetLastError();
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPWSTR)&msg,
0, NULL );
show_detailed_error(preamble, msg, (int)dw);
}
// }}}
// Load, decompress and extract data {{{
static BOOL load_data(LPVOID *data, DWORD *sz) {
HRSRC rsrc;
HGLOBAL h;
rsrc = FindResourceW(NULL, L"extra", L"extra");
if (rsrc == NULL) { show_last_error(L"Failed to find portable data in exe"); return false; }
h = LoadResource(NULL, rsrc);
if (h == NULL) { show_last_error(L"Failed to load portable data from exe"); return false; }
*data = LockResource(h);
if (*data == NULL) { show_last_error(L"Failed to lock portable data in exe"); return false; }
*sz = SizeofResource(NULL, rsrc);
if (sz == 0) { show_last_error(L"Failed to get size of portable data in exe"); return false; }
return true;
}
static BOOL unzip(HZIP zipf, int nitems, IProgressDialog *pd) {
int i = 0;
ZRESULT res;
ZIPENTRYW ze;
for (i = 0; i < nitems; i++) {
res = GetZipItem(zipf, i, &ze);
if (res != ZR_OK) { show_zip_error(L"Failed to get zip item", L"", res); return false;}
res = UnzipItem(zipf, i, ze.name, 0, ZIP_FILENAME);
if (res != ZR_OK) { CloseZip(zipf); show_zip_error(L"Failed to extract zip item (is your disk full?):", ze.name, res); return false;}
pd->SetLine(2, ze.name, true, NULL);
pd->SetProgress(i, nitems);
}
CloseZip(zipf);
return true;
}
static HANDLE temp_file(LPWSTR name) {
UINT res;
HANDLE h;
res = GetTempFileNameW(L".", L"portable_data", 0, name);
if (res == 0) { show_last_error(L"Failed to create temporary file to decompress portable data"); return INVALID_HANDLE_VALUE; }
h = CreateFile(name, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (h == INVALID_HANDLE_VALUE) { show_last_error(L"Failed to open temp file to decompress portable data"); }
return h;
}
struct DataStream
{
const unsigned char *in_data;
size_t in_len;
HANDLE out;
IProgressDialog *pd;
};
static int
input_callback(void *ctx, void *buf, size_t * size)
{
size_t rd = 0;
struct DataStream * ds = (struct DataStream *) ctx;
rd = (ds->in_len < *size) ? ds->in_len : *size;
if (rd > 0) {
memcpy(buf, (void*) ds->in_data, rd);
ds->in_data += rd;
ds->in_len -= rd;
}
*size = rd;
return 0;
}
static int output_error_shown = 0;
static size_t
output_callback(void *ctx, const void *buf, size_t size)
{
struct DataStream * ds = (struct DataStream *) ctx;
DWORD written = 0;
if (size > 0) {
if (!WriteFile(ds->out, buf, size, &written, NULL)) {
show_last_error(L"Failed to write uncompressed data to temp file");
output_error_shown = 1;
return 0;
}
written = SetFilePointer(ds->out, 0, NULL, FILE_CURRENT);
ds->pd->SetProgress(written, UNCOMPRESSED_SIZE);
}
return size;
}
static BOOL decompress(LPVOID src, DWORD src_sz, HANDLE out, IProgressDialog *pd) {
elzma_decompress_handle h;
struct DataStream ds;
int rc;
h = elzma_decompress_alloc();
if (h == NULL) { show_error(L"Out of memory"); return false; }
ds.in_data = (unsigned char*)src;
ds.in_len = src_sz;
ds.out = out;
ds.pd = pd;
rc = elzma_decompress_run(h, input_callback, (void *) &ds, output_callback,
(void *) &ds, ELZMA_lzip);
if (rc != ELZMA_E_OK) {
if (!output_error_shown) show_detailed_error(L"Failed to decompress portable data", L"", rc);
elzma_decompress_free(&h);
return false;
}
elzma_decompress_free(&h);
return true;
}
static BOOL extract(LPVOID cdata, DWORD csz) {
HANDLE h;
WCHAR tempnam[MAX_PATH+1] = {0};
BOOL ret = true;
HZIP zipf;
ZIPENTRYW ze;
ZRESULT res;
int nitems;
HRESULT hr;
IProgressDialog *pd = NULL;
hr = CoCreateInstance(CLSID_ProgressDialog, NULL,
CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pd));
if (FAILED(hr)) { show_error(L"Failed to create progress dialog"); return false; }
pd->SetTitle(L"Extracting Calibre Portable");
pd->SetLine(1, L"Decompressing data...", true, NULL);
h = temp_file(tempnam);
if (h == INVALID_HANDLE_VALUE) return false;
pd->StartProgressDialog(NULL, NULL, PROGDLG_NORMAL | PROGDLG_AUTOTIME | PROGDLG_NOCANCEL, NULL);
if (!decompress(cdata, csz, h, pd)) { ret = false; goto end; }
SetFilePointer(h, 0, NULL, FILE_BEGIN);
zipf = OpenZip(h, 0, ZIP_HANDLE);
if (zipf == 0) { show_last_error(L"Failed to open zipped portable data"); ret = false; goto end; }
res = GetZipItem(zipf, -1, &ze);
if (res != ZR_OK) { show_zip_error(L"Failed to get count of items in portable data", L"", res); ret = false; goto end;}
nitems = ze.index;
pd->SetLine(1, L"Copying files...", true, NULL);
if (!unzip(zipf, nitems, pd)) { ret = false; goto end; }
end:
pd->StopProgressDialog();
pd->Release();
CloseHandle(h);
DeleteFile(tempnam);
return ret;
}
// }}}
// Find calibre portable directory and install/upgrade into it {{{
static BOOL directory_exists( LPCWSTR path )
{
if( _waccess_s( path, 0 ) == 0 )
{
struct _stat status;
_wstat( path, &status );
return (status.st_mode & S_IFDIR) != 0;
}
return FALSE;
}
static BOOL file_exists( LPCWSTR path )
{
if( _waccess_s( path, 0 ) == 0 )
{
struct _stat status;
_wstat( path, &status );
return (status.st_mode & S_IFREG) != 0;
}
return FALSE;
}
static LPWSTR get_directory_from_user() {
WCHAR name[MAX_PATH+1] = {0};
LPWSTR path = NULL;
PIDLIST_ABSOLUTE ret;
path = (LPWSTR)calloc(2*MAX_PATH, sizeof(WCHAR));
if (path == NULL) { show_error(L"Out of memory"); return NULL; }
int image = 0;
BROWSEINFO bi = { NULL, NULL, name,
L"Select the folder where you want to install or update Calibre Portable",
BIF_RETURNONLYFSDIRS | BIF_DONTGOBELOWDOMAIN | BIF_USENEWUI,
NULL, NULL, image };
ret = SHBrowseForFolder(&bi);
if (ret == NULL) {
return NULL;
}
if (!SHGetPathFromIDList(ret, path)) {
show_detailed_error(L"The selected folder is not valid: ", name, 0);
return NULL;
}
return path;
}
static bool is_dots(LPCWSTR name) {
return wcscmp(name, L".") == 0 || wcscmp(name, L"..") == 0;
}
static bool rmtree(LPCWSTR path) {
SHFILEOPSTRUCTW op;
WCHAR buf[4*MAX_PATH + 2] = {0};
if (GetFullPathName(path, 4*MAX_PATH, buf, NULL) == 0) return false;
op.hwnd = NULL;
op.wFunc = FO_DELETE;
op.pFrom = buf;
op.pTo = NULL;
op.fFlags = FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT | FOF_NOCONFIRMMKDIR;
op.fAnyOperationsAborted = false;
op.hNameMappings = NULL;
op.lpszProgressTitle = NULL;
return SHFileOperationW(&op) == 0;
}
static BOOL find_portable_dir(LPCWSTR base, LPWSTR *result, BOOL *existing) {
WCHAR buf[4*MAX_PATH] = {0};
_snwprintf_s(buf, 4*MAX_PATH, _TRUNCATE, L"%s\\calibre-portable.exe", base);
*existing = true;
if (file_exists(buf)) {
*result = _wcsdup(base);
if (*result == NULL) { show_error(L"Out of memory"); return false; }
return true;
}
WIN32_FIND_DATA fdFile;
HANDLE hFind = NULL;
_snwprintf_s(buf, 4*MAX_PATH, _TRUNCATE, L"%s\\*", base);
if((hFind = FindFirstFileEx(buf, FindExInfoStandard, &fdFile, FindExSearchLimitToDirectories, NULL, 0)) != INVALID_HANDLE_VALUE) {
do {
if(is_dots(fdFile.cFileName)) continue;
if(fdFile.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
_snwprintf_s(buf, 4*MAX_PATH, _TRUNCATE, L"%s\\%s\\calibre-portable.exe", base, fdFile.cFileName);
if (file_exists(buf)) {
*result = _wcsdup(buf);
if (*result == NULL) { show_error(L"Out of memory"); return false; }
PathRemoveFileSpec(*result);
FindClose(hFind);
return true;
}
}
} while(FindNextFile(hFind, &fdFile));
FindClose(hFind);
}
*existing = false;
_snwprintf_s(buf, 4*MAX_PATH, _TRUNCATE, L"%s\\Calibre Portable", base);
if (!CreateDirectory(buf, NULL) && GetLastError() != ERROR_ALREADY_EXISTS) {
show_last_error(L"Failed to create Calibre Portable folder");
return false;
}
*result = _wcsdup(buf);
if (*result == NULL) { show_error(L"Out of memory"); return false; }
return true;
}
static LPWSTR make_unpack_dir() {
WCHAR buf[4*MAX_PATH] = {0};
LPWSTR ans = NULL;
if (directory_exists(L"_unpack_calibre_portable"))
rmtree(L"_unpack_calibre_portable");
if (!CreateDirectory(L"_unpack_calibre_portable", NULL) && GetLastError() != ERROR_ALREADY_EXISTS) {
show_last_error(L"Failed to create temporary folder to unpack into");
return ans;
}
if (!GetFullPathName(L"_unpack_calibre_portable", 4*MAX_PATH, buf, NULL)) {
show_last_error(L"Failed to resolve path");
return NULL;
}
ans = _wcsdup(buf);
if (ans == NULL) show_error(L"Out of memory");
return ans;
}
static BOOL move_program() {
if (MoveFileEx(L"Calibre Portable\\calibre-portable.exe",
L"..\\calibre-portable.exe", MOVEFILE_REPLACE_EXISTING) == 0) {
show_last_error(L"Failed to move calibre-portable.exe, make sure calibre is not running");
return false;
}
if (directory_exists(L"..\\Calibre")) {
if (!rmtree(L"..\\Calibre")) {
show_error(L"Failed to delete the Calibre program folder. Make sure calibre is not running.");
return false;
}
}
if (MoveFileEx(L"Calibre Portable\\Calibre", L"..\\Calibre", 0) == 0) {
Sleep(4000); // Sleep and try again
if (MoveFileEx(L"Calibre Portable\\Calibre", L"..\\Calibre", 0) == 0) {
show_last_error(L"Failed to move calibre program folder. This is usually caused by an antivirus program or a file sync program like DropBox. Turn them off temporarily and try again. Underlying error: ");
return false;
}
}
if (!directory_exists(L"..\\Calibre Library")) {
MoveFileEx(L"Calibre Portable\\Calibre Library", L"..\\Calibre Library", 0);
}
if (!directory_exists(L"..\\Calibre Settings")) {
MoveFileEx(L"Calibre Portable\\Calibre Settings", L"..\\Calibre Settings", 0);
}
return true;
}
// }}}
static BOOL ensure_not_running(LPCWSTR dest) {
DWORD processes[4096], needed, num;
unsigned int i;
WCHAR name[4*MAX_PATH] = L"<unknown>";
HANDLE h;
DWORD len;
LPWSTR fname = NULL;
if ( !EnumProcesses( processes, sizeof(processes), &needed ) ) {
return true;
}
num = needed / sizeof(DWORD);
for (i = 0; i < num; i++) {
if (processes[i] == 0) continue;
h = OpenProcess( PROCESS_QUERY_INFORMATION, FALSE, processes[i] );
if (h != NULL) {
len = GetProcessImageFileNameW(h, name, 4*MAX_PATH);
CloseHandle(h);
if (len != 0) {
name[len] = 0;
fname = PathFindFileName(name);
if (wcscmp(fname, L"calibre.exe") == 0) {
show_error(L"Calibre appears to be running on your computer. Please quit it before trying to install Calibre Portable.");
return false;
}
}
}
}
return true;
}
static void launch_calibre() {
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory( &si, sizeof(si) );
si.cb = sizeof(si);
ZeroMemory( &pi, sizeof(pi) );
if (CreateProcess(_wcsdup(L"calibre-portable.exe"), NULL,
NULL, NULL, FALSE, CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_PROCESS_GROUP,
NULL, NULL, &si, &pi)
== 0) {
show_last_error(L"Failed to launch calibre portable");
}
// Close process and thread handles.
CloseHandle( pi.hProcess );
CloseHandle( pi.hThread );
}
void makedirs(LPWSTR path) {
WCHAR *p = path;
while (*p) {
if ((*p == L'\\' || *p == L'/') && p != path && *(p-1) != L':') {
*p = 0;
CreateDirectory(path, NULL);
*p = L'\\';
}
p++;
}
CreateDirectory(path, NULL);
}
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)
{
LPVOID cdata = NULL;
DWORD csz = 0;
int ret = 1, argc;
HRESULT hr;
LPWSTR tgt = NULL, dest = NULL, *argv, unpack_dir = NULL;
BOOL existing = false, launch = false, automated = false;
WCHAR buf[4*MAX_PATH] = {0}, mb_msg[4*MAX_PATH] = {0}, fdest[4*MAX_PATH] = {0};
if (!load_data(&cdata, &csz)) return ret;
hr = CoInitialize(NULL);
if (FAILED(hr)) { show_error(L"Failed to initialize COM"); return ret; }
// Get the target directory for installation
argv = CommandLineToArgvW(GetCommandLine(), &argc);
if (argv == NULL) { show_last_error(L"Failed to get command line"); return ret; }
if (argc > 1) {
tgt = argv[1];
automated = true;
if (!directory_exists(tgt)) {
if (GetFullPathName(tgt, MAX_PATH*4, fdest, NULL) == 0) {
show_last_error(L"Failed to resolve target folder");
goto end;
}
makedirs(fdest);
}
} else {
tgt = get_directory_from_user();
if (tgt == NULL) goto end;
}
if (!directory_exists(tgt)) {
show_detailed_error(L"The specified directory does not exist: ",
tgt, 1);
goto end;
}
// Ensure the path to Calibre Portable is not too long
do {
if (!find_portable_dir(tgt, &dest, &existing)) goto end;
if (GetFullPathName(dest, MAX_PATH*4, fdest, NULL) == 0) {
show_last_error(L"Failed to resolve target folder");
goto end;
}
free(dest); dest = NULL;
if (wcslen(fdest) > 58) {
_snwprintf_s(buf, 4*MAX_PATH, _TRUNCATE,
L"Path to Calibre Portable (%s) too long. Must be less than 59 characters.", fdest);
if (!existing) RemoveDirectory(fdest);
show_error(buf);
tgt = get_directory_from_user();
if (tgt == NULL) goto end;
}
} while (wcslen(fdest) > 58);
// Confirm the user wants to upgrade
if (existing && !automated) {
_snwprintf_s(mb_msg, 4*MAX_PATH, _TRUNCATE,
L"An existing install of Calibre Portable was found at %s. Do you want to upgrade it?",
fdest);
if (MessageBox(NULL, mb_msg,
L"Upgrade Calibre Portable?", MB_ICONEXCLAMATION | MB_YESNO | MB_TOPMOST) != IDYES)
goto end;
}
if (existing) {
if (!ensure_not_running(fdest)) goto end;
}
// Make a temp dir to unpack into
if (!SetCurrentDirectoryW(fdest)) { show_detailed_error(L"Failed to change to unzip directory: ", fdest, 0); goto end; }
if ( (unpack_dir = make_unpack_dir()) == NULL ) goto end;
if (!SetCurrentDirectoryW(unpack_dir)) { show_detailed_error(L"Failed to change to unpack directory: ", fdest, 0); goto end; }
// Extract files
if (!extract(cdata, csz)) goto end;
// Move files from temp dir to the install dir
if (!move_program()) goto end;
ret = 0;
if (!automated) {
_snwprintf_s(mb_msg, 4*MAX_PATH, _TRUNCATE,
L"Calibre Portable successfully installed to %s. Launch calibre?",
fdest);
launch = MessageBox(NULL, mb_msg,
L"Success", MB_ICONINFORMATION | MB_YESNO | MB_TOPMOST) == IDYES;
}
end:
if (unpack_dir != NULL) { SetCurrentDirectoryW(L".."); rmtree(unpack_dir); free(unpack_dir); }
CoUninitialize();
if (launch) launch_calibre();
return ret;
}

154
bypy/windows/portable.c Normal file
View File

@ -0,0 +1,154 @@
#ifndef UNICODE
#define UNICODE
#endif
#ifndef _UNICODE
#define _UNICODE
#endif
#include <windows.h>
#include <tchar.h>
#include <wchar.h>
#include <stdio.h>
#define BUFSIZE 4096
void show_error(LPCTSTR msg) {
MessageBeep(MB_ICONERROR);
MessageBox(NULL, msg, _T("Error"), MB_OK|MB_ICONERROR);
}
void show_detailed_error(LPCTSTR preamble, LPCTSTR msg, int code) {
LPTSTR buf;
buf = (LPTSTR)LocalAlloc(LMEM_ZEROINIT, sizeof(TCHAR)*
(_tcslen(msg) + _tcslen(preamble) + 80));
_sntprintf_s(buf,
LocalSize(buf) / sizeof(TCHAR), _TRUNCATE,
_T("%s\r\n %s (Error Code: %d)\r\n"),
preamble, msg, code);
show_error(buf);
LocalFree(buf);
}
void show_last_error_crt(LPCTSTR preamble) {
TCHAR buf[BUFSIZE];
int err = 0;
_get_errno(&err);
_tcserror_s(buf, BUFSIZE, err);
show_detailed_error(preamble, buf, err);
}
void show_last_error(LPCTSTR preamble) {
TCHAR *msg = NULL;
DWORD dw = GetLastError();
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&msg,
0, NULL );
show_detailed_error(preamble, msg, (int)dw);
}
LPTSTR get_app_dir() {
LPTSTR buf, buf2, buf3;
DWORD sz;
TCHAR drive[4] = _T("\0\0\0");
errno_t err;
buf = (LPTSTR)calloc(BUFSIZE, sizeof(TCHAR));
buf2 = (LPTSTR)calloc(BUFSIZE, sizeof(TCHAR));
buf3 = (LPTSTR)calloc(BUFSIZE, sizeof(TCHAR));
sz = GetModuleFileName(NULL, buf, BUFSIZE);
if (sz == 0 || sz > BUFSIZE-1) {
show_error(_T("Failed to get path to calibre-portable.exe"));
ExitProcess(1);
}
err = _tsplitpath_s(buf, drive, 4, buf2, BUFSIZE, NULL, 0, NULL, 0);
if (err != 0) {
show_last_error_crt(_T("Failed to split path to calibre-portable.exe"));
ExitProcess(1);
}
_sntprintf_s(buf3, BUFSIZE-1, _TRUNCATE, _T("%s%s"), drive, buf2);
free(buf); free(buf2);
return buf3;
}
void launch_calibre(LPCTSTR exe, LPCTSTR config_dir) {
DWORD dwFlags=0;
STARTUPINFO si;
PROCESS_INFORMATION pi;
BOOL fSuccess;
if (! SetEnvironmentVariable(_T("CALIBRE_CONFIG_DIRECTORY"), config_dir)) {
show_last_error(_T("Failed to set environment variables"));
ExitProcess(1);
}
if (! SetEnvironmentVariable(_T("CALIBRE_PORTABLE_BUILD"), exe)) {
show_last_error(_T("Failed to set environment variables"));
ExitProcess(1);
}
dwFlags = CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_PROCESS_GROUP;
ZeroMemory( &si, sizeof(si) );
si.cb = sizeof(si);
ZeroMemory( &pi, sizeof(pi) );
fSuccess = CreateProcess(exe, NULL,
NULL, // Process handle not inheritable
NULL, // Thread handle not inheritable
FALSE, // Set handle inheritance to FALSE
dwFlags, // Creation flags http://msdn.microsoft.com/en-us/library/ms684863(v=vs.85).aspx
NULL, // Use parent's environment block
NULL, // Use parent's starting directory
&si, // Pointer to STARTUPINFO structure
&pi // Pointer to PROCESS_INFORMATION structure
);
if (fSuccess == 0) {
show_last_error(_T("Failed to launch the calibre program"));
}
// Close process and thread handles.
CloseHandle( pi.hProcess );
CloseHandle( pi.hThread );
}
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)
{
LPTSTR app_dir, config_dir, exe;
app_dir = get_app_dir();
config_dir = (LPTSTR)calloc(BUFSIZE, sizeof(TCHAR));
exe = (LPTSTR)calloc(BUFSIZE, sizeof(TCHAR));
_sntprintf_s(config_dir, BUFSIZE, _TRUNCATE, _T("%sCalibre Settings"), app_dir);
_sntprintf_s(exe, BUFSIZE, _TRUNCATE, _T("%sCalibre\\calibre.exe"), app_dir);
launch_calibre(exe, config_dir);
free(app_dir); free(config_dir); free(exe);
return 0;
}

101
bypy/windows/site.py Normal file
View File

@ -0,0 +1,101 @@
#!/usr/bin/env python2
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
__license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import sys
import os
import imp
class PydImporter(object):
__slots__ = ('items', 'description')
def __init__(self):
self.items = None
self.description = ('.pyd', 'rb', imp.C_EXTENSION)
def find_module(self, fullname, path=None):
if self.items is None:
dlls_dir = os.path.join(sys.app_dir, 'app', 'DLLs')
items = self.items = {}
for x in os.listdir(dlls_dir):
lx = x.lower()
if lx.endswith(b'.pyd'):
items[lx[:-4]] = os.path.abspath(os.path.join(dlls_dir, x))
return self if fullname.lower() in self.items else None
def load_module(self, fullname):
m = sys.modules.get(fullname)
if m is not None:
return m
try:
path = self.items[fullname.lower()]
except KeyError:
raise ImportError('The native code module %s seems to have disappeared from self.items' % fullname)
package, name = fullname.rpartition(b'.')[::2]
m = imp.load_module(fullname, None, path, self.description) # This inserts the module into sys.modules itself
m.__loader__ = self
m.__package__ = package or None
return m
def abs__file__():
"""Set all module __file__ attribute to an absolute path"""
for m in sys.modules.values():
if hasattr(m, '__loader__'):
continue # don't mess with a PEP 302-supplied __file__
try:
m.__file__ = os.path.abspath(m.__file__)
except AttributeError:
continue
def aliasmbcs():
import locale, codecs
enc = locale.getdefaultlocale()[1]
if enc.startswith('cp'): # "cp***" ?
try:
codecs.lookup(enc)
except LookupError:
import encodings
encodings._cache[enc] = encodings._unknown
encodings.aliases.aliases[enc] = 'mbcs'
def add_calibre_vars():
sys.new_app_layout = 1
sys.resources_location = os.path.join(sys.app_dir, 'app', 'resources')
sys.extensions_location = os.path.join(sys.app_dir, 'app', 'DLLs')
dv = os.environ.get('CALIBRE_DEVELOP_FROM', None)
if dv and os.path.exists(dv):
sys.path.insert(0, os.path.abspath(dv))
def run_entry_point():
bname, mod, func = sys.calibre_basename, sys.calibre_module, sys.calibre_function
sys.argv[0] = bname+'.exe'
pmod = __import__(mod, fromlist=[1], level=0)
return getattr(pmod, func)()
def main():
sys.frozen = 'windows_exe'
sys.setdefaultencoding('utf-8')
aliasmbcs()
sys.meta_path.insert(0, PydImporter())
sys.path_importer_cache.clear()
import linecache
def fake_getline(filename, lineno, module_globals=None):
return ''
linecache.orig_getline = linecache.getline
linecache.getline = fake_getline
abs__file__()
add_calibre_vars()
# Needed for pywintypes to be able to load its DLL
sys.path.append(os.path.join(sys.app_dir, 'app', 'DLLs'))
return run_entry_point()

42
bypy/windows/template.rc Normal file
View File

@ -0,0 +1,42 @@
#include <windows.h>
#define VER_FILEVERSION {file_version}
#define VER_FILEVERSION_STR "{file_version_str}"
#define VER_PRODUCTVERSION {product_version}
#define VER_PRODUCTVERSION_STR "{product_version_str}"
#define VER_DEBUG 0
1 VERSIONINFO
FILEVERSION VER_FILEVERSION
PRODUCTVERSION VER_PRODUCTVERSION
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
FILEFLAGS VER_DEBUG
FILEOS VOS_NT_WINDOWS32
FILETYPE VFT_{file_type}
FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904E4"
BEGIN
VALUE "CompanyName", "calibre-ebook.com"
VALUE "FileDescription", "{file_description}"
VALUE "FileVersion", VER_FILEVERSION_STR
VALUE "InternalName", "{internal_name}"
VALUE "LegalCopyright", "{legal_copyright}"
VALUE "LegalTrademarks", "{legal_trademarks}"
VALUE "OriginalFilename", "{original_filename}"
VALUE "ProductName", "{product_name}"
VALUE "ProductVersion", VER_PRODUCTVERSION_STR
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END
1 ICON "{icon}"

448
bypy/windows/util.c Normal file
View File

@ -0,0 +1,448 @@
/*
* Copyright 2009 Kovid Goyal
*/
#define UNICODE
#define _WIN32_WINNT 0x0502
#define WINDOWS_LEAN_AND_MEAN
#include <windows.h>
#include <Python.h>
#include <stdio.h>
#include <stdlib.h>
#include <Shellapi.h>
#include <delayimp.h>
#include <io.h>
#include <fcntl.h>
static int GUI_APP = 0;
static char python_dll[] = PYDLL;
void set_gui_app(int yes) { GUI_APP = yes; }
int calibre_show_python_error(const wchar_t *preamble, int code);
static int _show_error(const wchar_t *preamble, const wchar_t *msg, const int code) {
wchar_t *buf;
char *cbuf;
buf = (wchar_t*)LocalAlloc(LMEM_ZEROINIT, sizeof(wchar_t)*
(wcslen(msg) + wcslen(preamble) + 80));
_snwprintf_s(buf,
LocalSize(buf) / sizeof(wchar_t), _TRUNCATE,
L"%s\r\n %s (Error Code: %d)\r\n",
preamble, msg, code);
if (GUI_APP) {
MessageBeep(MB_ICONERROR);
MessageBox(NULL, buf, NULL, MB_OK|MB_ICONERROR);
}
else {
cbuf = (char*) calloc(10+(wcslen(buf)*4), sizeof(char));
if (cbuf) {
if (WideCharToMultiByte(CP_UTF8, 0, buf, -1, cbuf, (int)(10+(wcslen(buf)*4)), NULL, NULL) != 0) printf_s(cbuf);
free(cbuf);
}
}
LocalFree(buf);
return code;
}
int show_last_error_crt(wchar_t *preamble) {
wchar_t buf[1000];
int err = 0;
_get_errno(&err);
_wcserror_s(buf, 1000, err);
return _show_error(preamble, buf, err);
}
int show_last_error(wchar_t *preamble) {
wchar_t *msg = NULL;
DWORD dw = GetLastError();
int ret;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPWSTR)&msg,
0,
NULL );
ret = _show_error(preamble, msg, (int)dw);
if (msg != NULL) LocalFree(msg);
return ret;
}
char* get_app_dir() {
char *buf, *buf2, *buf3;
char drive[4] = "\0\0\0";
DWORD sz; errno_t err;
buf = (char*)calloc(MAX_PATH, sizeof(char));
buf2 = (char*)calloc(MAX_PATH, sizeof(char));
buf3 = (char*)calloc(MAX_PATH, sizeof(char));
if (!buf || !buf2 || !buf3) ExitProcess(_show_error(L"Out of memory", L"", 1));
sz = GetModuleFileNameA(NULL, buf, MAX_PATH);
if (sz >= MAX_PATH-1) ExitProcess(_show_error(L"Installation directory path too long", L"", 1));
err = _splitpath_s(buf, drive, 4, buf2, MAX_PATH, NULL, 0, NULL, 0);
if (err != 0) ExitProcess(show_last_error_crt(L"Failed to find application directory"));
_snprintf_s(buf3, MAX_PATH, _TRUNCATE, "%s%s", drive, buf2);
free(buf); free(buf2);
return buf3;
}
wchar_t* get_app_dirw() {
wchar_t *buf, *buf2, *buf3;
wchar_t drive[4] = L"\0\0\0";
DWORD sz; errno_t err;
buf = (wchar_t*)calloc(MAX_PATH, sizeof(wchar_t));
buf2 = (wchar_t*)calloc(MAX_PATH, sizeof(wchar_t));
buf3 = (wchar_t*)calloc(MAX_PATH, sizeof(wchar_t));
if (!buf || !buf2 || !buf3) ExitProcess(_show_error(L"Out of memory", L"", 1));
sz = GetModuleFileNameW(NULL, buf, MAX_PATH);
if (sz >= MAX_PATH-1) ExitProcess(_show_error(L"Installation directory path too long", L"", 1));
err = _wsplitpath_s(buf, drive, 4, buf2, MAX_PATH, NULL, 0, NULL, 0);
if (err != 0) ExitProcess(show_last_error_crt(L"Failed to find application directory"));
_snwprintf_s(buf3, MAX_PATH, _TRUNCATE, L"%s%s", drive, buf2);
free(buf); free(buf2);
return buf3;
}
void load_python_dll() {
char *app_dir, *dll_dir, *qt_plugin_dir;
size_t l;
app_dir = get_app_dir();
l = strlen(app_dir)+25;
dll_dir = (char*) calloc(l, sizeof(char));
qt_plugin_dir = (char*) calloc(l, sizeof(char));
if (!dll_dir || !qt_plugin_dir) ExitProcess(_show_error(L"Out of memory", L"", 1));
_snprintf_s(dll_dir, l, _TRUNCATE, "%s\\app\\DLLs", app_dir);
_snprintf_s(qt_plugin_dir, l, _TRUNCATE, "%s\\app\\qt_plugins", app_dir);
free(app_dir);
_putenv_s("QT_PLUGIN_PATH", qt_plugin_dir);
if (!SetDllDirectoryA(dll_dir)) ExitProcess(show_last_error(L"Failed to set DLL directory."));
if (FAILED(__HrLoadAllImportsForDll(python_dll)))
ExitProcess(_show_error(L"Failed to delay load the python dll", L"", 1));
}
static char program_name[MAX_PATH];
static char python_home[MAX_PATH];
static wchar_t out_of_memory[] = L"Out of memory";
void setup_stream(const char *name, const char *errors, UINT cp) {
PyObject *stream;
char *buf = (char *)calloc(100, sizeof(char));
if (!buf) ExitProcess(_show_error(out_of_memory, L"", 1));
if (cp == CP_UTF8) _snprintf_s(buf, 100, _TRUNCATE, "%s", "utf-8");
else if (cp == CP_UTF7) _snprintf_s(buf, 100, _TRUNCATE, "%s", "utf-7");
else _snprintf_s(buf, 100, _TRUNCATE, "cp%d", cp);
stream = PySys_GetObject((char*)name);
if (!PyFile_SetEncodingAndErrors(stream, buf, (char*)errors))
ExitProcess(calibre_show_python_error(L"Failed to set stream encoding", 1));
free(buf);
}
UINT
setup_streams() {
UINT code_page = GetConsoleOutputCP();
SetConsoleOutputCP(CP_UTF8);
_putenv_s("PYTHONIOENCODING", "UTF-8");
_setmode(_fileno(stdin), _O_BINARY);
_setmode(_fileno(stdout), _O_BINARY);
_setmode(_fileno(stderr), _O_BINARY);
if (!GUI_APP) { // Remove buffering
setvbuf(stdin, NULL, _IONBF, 2);
setvbuf(stdout, NULL, _IONBF, 2);
setvbuf(stderr, NULL, _IONBF, 2);
}
//printf("input cp: %d output cp: %d\r\n", GetConsoleCP(), GetConsoleOutputCP());
setup_stream("stdin", "strict", GetConsoleCP());
setup_stream("stdout", "strict", CP_UTF8);
setup_stream("stderr", "strict", CP_UTF8);
return code_page;
}
UINT
initialize_interpreter(const char *basename, const char *module, const char *function) {
DWORD sz; char *buf, *path; HMODULE dll;
int *flag, i, argc;
wchar_t *app_dir, **wargv;
PyObject *argv, *v;
char *dummy_argv[1] = {""};
buf = (char*)calloc(MAX_PATH, sizeof(char));
path = (char*)calloc(MAX_PATH, sizeof(char));
if (!buf || !path) ExitProcess(_show_error(L"Out of memory", L"", 1));
sz = GetModuleFileNameA(NULL, buf, MAX_PATH);
if (sz >= MAX_PATH-1) ExitProcess(_show_error(L"Installation directory path too long", L"", 1));
_snprintf_s(program_name, MAX_PATH, _TRUNCATE, "%s", buf);
free(buf);
buf = get_app_dir();
buf[strlen(buf)-1] = '\0';
_snprintf_s(python_home, MAX_PATH, _TRUNCATE, "%s", buf);
_snprintf_s(path, MAX_PATH, _TRUNCATE, "%s\\app\\pylib.zip", buf);
free(buf);
dll = GetModuleHandleA(python_dll);
if (!dll) ExitProcess(show_last_error(L"Failed to get python dll handle"));
flag = (int*)GetProcAddress(dll, "Py_OptimizeFlag");
if (!flag) ExitProcess(_show_error(L"Failed to get optimize flag", L"", 1));
*flag = 2;
flag = (int*)GetProcAddress(dll, "Py_NoSiteFlag");
if (!flag) ExitProcess(_show_error(L"Failed to get no_site flag", L"", 1));
*flag = 1;
flag = (int*)GetProcAddress(dll, "Py_DontWriteBytecodeFlag");
if (!flag) ExitProcess(_show_error(L"Failed to get no_bytecode flag", L"", 1));
*flag = 1;
flag = (int*)GetProcAddress(dll, "Py_IgnoreEnvironmentFlag");
if (!flag) ExitProcess(_show_error(L"Failed to get ignore_environment flag", L"", 1));
*flag = 1;
flag = (int*)GetProcAddress(dll, "Py_NoUserSiteDirectory");
if (!flag) ExitProcess(_show_error(L"Failed to get user_site flag", L"", 1));
*flag = 1;
flag = (int*)GetProcAddress(dll, "Py_HashRandomizationFlag");
if (!flag) ExitProcess(_show_error(L"Failed to get hash randomization flag", L"", 1));
*flag = 1;
flag = (int*)GetProcAddress(dll, "Py_VerboseFlag");
if (!flag) ExitProcess(_show_error(L"Failed to get verbose flag", L"", 1));
//*flag = 1;
flag = (int*)GetProcAddress(dll, "Py_DebugFlag");
if (!flag) ExitProcess(_show_error(L"Failed to get debug flag", L"", 1));
//*flag = 1;
Py_SetProgramName(program_name);
Py_SetPythonHome(python_home);
//printf("Path before Py_Initialize(): %s\r\n\n", Py_GetPath());
Py_Initialize();
UINT code_page = setup_streams();
PySys_SetArgv(1, dummy_argv);
//printf("Path after Py_Initialize(): %s\r\n\n", Py_GetPath());
PySys_SetPath(path);
//printf("Path set by me: %s\r\n\n", path);
PySys_SetObject("gui_app", PyBool_FromLong((long)GUI_APP));
app_dir = get_app_dirw();
PySys_SetObject("app_dir", PyUnicode_FromWideChar(app_dir, wcslen(app_dir)));
PySys_SetObject("calibre_basename", PyBytes_FromString(basename));
PySys_SetObject("calibre_module", PyBytes_FromString(module));
PySys_SetObject("calibre_function", PyBytes_FromString(function));
wargv = CommandLineToArgvW(GetCommandLineW(), &argc);
if (wargv == NULL) ExitProcess(show_last_error(L"Failed to get command line"));
argv = PyList_New(argc);
if (argv == NULL) ExitProcess(_show_error(out_of_memory, L"", 1));
for (i = 0; i < argc; i++) {
v = PyUnicode_FromWideChar(wargv[i], wcslen(wargv[i]));
if (v == NULL) ExitProcess(_show_error(out_of_memory, L"", 1));
PyList_SetItem(argv, i, v);
}
PySys_SetObject("argv", argv);
return code_page;
}
wchar_t* pyobject_to_wchar(PyObject *o) {
PyUnicodeObject *t;
size_t s;
wchar_t *ans;
if (!PyUnicode_Check(o)) {
t = (PyUnicodeObject*)PyUnicode_FromEncodedObject(o, NULL, "replace");
if (t == NULL) return NULL;
} else t = (PyUnicodeObject*)o;
s = 2*PyUnicode_GET_SIZE(t) +1;
ans = (wchar_t*)calloc(s, sizeof(wchar_t));
if (ans == NULL) return NULL;
s = PyUnicode_AsWideChar(t, ans, s-1);
ans[s] = L'\0';
return ans;
}
int pyobject_to_int(PyObject *res) {
int ret; PyObject *tmp;
tmp = PyNumber_Int(res);
if (tmp == NULL) ret = (PyObject_IsTrue(res)) ? 1 : 0;
else ret = (int)PyInt_AS_LONG(tmp);
return ret;
}
int handle_sysexit(PyObject *e) {
PyObject *code;
code = PyObject_GetAttrString(e, "code");
if (!code) return 0;
if (!PyInt_Check(code)) {
PyObject_Print(code, stderr, Py_PRINT_RAW);
fflush(stderr);
}
return pyobject_to_int(code);
}
int calibre_show_python_error(const wchar_t *preamble, int code) {
PyObject *exc, *val, *tb, *str, **system_exit;
HMODULE dll;
int ret, issysexit = 0; wchar_t *i;
if (!PyErr_Occurred()) return code;
dll = GetModuleHandleA(python_dll);
if (!dll) ExitProcess(show_last_error(L"Failed to get python dll handle"));
system_exit = (PyObject**)GetProcAddress(dll, "PyExc_SystemExit");
issysexit = PyErr_ExceptionMatches(*system_exit);
PyErr_Fetch(&exc, &val, &tb);
if (exc != NULL) {
PyErr_NormalizeException(&exc, &val, &tb);
if (issysexit) {
return (val) ? handle_sysexit(val) : 0;
}
if (val != NULL) {
str = PyObject_Unicode(val);
if (str == NULL) {
PyErr_Clear();
str = PyObject_Str(val);
}
i = pyobject_to_wchar(str);
ret = _show_error(preamble, (i==NULL)?out_of_memory:i, code);
if (i) free(i);
if (tb != NULL) {
PyErr_Restore(exc, val, tb);
PyErr_Print();
}
return ret;
}
}
return _show_error(preamble, L"", code);
}
void redirect_out_stream(FILE *stream) {
FILE *f = NULL;
errno_t err;
err = freopen_s(&f, "NUL", "wt", stream);
if (err != 0) {
ExitProcess(show_last_error_crt(L"Failed to redirect stdout/stderr to NUL. This indicates a corrupted Windows install.\r\n You should contact Microsoft for assistance and/or follow the steps described here:\r\n http://bytes.com/topic/net/answers/264804-compile-error-null-device-missing"));
}
}
static void
null_invalid_parameter_handler(
const wchar_t * expression,
const wchar_t * function,
const wchar_t * file,
unsigned int line,
uintptr_t pReserved
) {
// The python runtime expects various system calls with invalid parameters
// to return errors instead of aborting the program. So get the windows CRT
// to do that.
}
__declspec(dllexport) int __cdecl
execute_python_entrypoint(const char *basename, const char *module, const char *function, int is_gui_app) {
PyObject *site, *main, *res;
int ret = 0;
// Prevent Windows' idiotic error dialog popups when various win32 api functions fail
SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOALIGNMENTFAULTEXCEPT | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX);
if (is_gui_app) {
// Redirect stdout and stderr to NUL so that python does not fail writing to them
redirect_out_stream(stdout);
redirect_out_stream(stderr);
}
set_gui_app(is_gui_app);
// Disable the invalid parameter handler
_set_invalid_parameter_handler(null_invalid_parameter_handler);
load_python_dll();
UINT code_page = initialize_interpreter(basename, module, function);
site = PyImport_ImportModule("site");
if (site == NULL)
ret = calibre_show_python_error(L"Failed to import site module", 1);
else {
Py_XINCREF(site);
main = PyObject_GetAttrString(site, "main");
if (main == NULL || !PyCallable_Check(main))
ret = calibre_show_python_error(L"site module has no main function", 1);
else {
Py_XINCREF(main);
res = PyObject_CallObject(main, NULL);
if (res == NULL)
ret = calibre_show_python_error(L"Python function terminated unexpectedly", 1);
else {
}
}
}
PyErr_Clear();
Py_Finalize();
if (code_page != CP_UTF8) SetConsoleOutputCP(code_page);
//printf("11111 Returning: %d\r\n", ret);
return ret;
}
wchar_t* get_temp_filename(const wchar_t *prefix) {
DWORD dwRetVal;
UINT uRetVal;
wchar_t *szTempName;
wchar_t lpPathBuffer[MAX_PATH];
szTempName = (wchar_t *)LocalAlloc(LMEM_ZEROINIT, sizeof(wchar_t)*MAX_PATH);
dwRetVal = GetTempPath(MAX_PATH, lpPathBuffer);
if (dwRetVal > MAX_PATH || (dwRetVal == 0)) {
ExitProcess(show_last_error(L"Failed to get temp path."));
}
uRetVal = GetTempFileName(lpPathBuffer, // directory for tmp files
prefix, // temp file name prefix
0, // create unique name
szTempName); // buffer for name
if (uRetVal == 0) {
ExitProcess(show_last_error(L"Failed to get temp file name"));
}
return szTempName;
}

View File

@ -0,0 +1,206 @@
<?xml version='1.0' encoding='utf-8'?>
<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi' xmlns:util="http://schemas.microsoft.com/wix/UtilExtension"
>
<Product Name='{app}{x64}' Id='*' UpgradeCode='{upgrade_code}' Language='1033' Codepage='1252' Version='{version}' Manufacturer='Kovid Goyal'>
<Package Id='*' Keywords='Installer' Description="{app} Installer"
Comments='{app} is a registered trademark of Kovid Goyal' Manufacturer='Kovid Goyal'
InstallerVersion='300' Languages='1033' Compressed='yes'
SummaryCodepage='1252' />
<!-- Disable creation of system restore points on calibre installs. Speeds
up the install. We dont need system restore since we dont install any
system DLLs/components anyway (apart from start menu entries) -->
<Property Id="MSIFASTINSTALL" Value="3" />
<Media Id="1" Cabinet="{app}.cab" CompressionLevel="{compression}" EmbedCab="yes" />
<!-- The following line ensures that DLLs are replaced even if
their version is the same as before or they dont have versions.
Microsoft's brain dead installer will otherwise use file dates to
determine whether to install a file or not. Simply not robust. And
since we dont install any system files whatsoever, we can never replace
a system file with an older version. This way the calibre install
should always result in a consistent set of files being present in the
installation folder, though of course, with Microsoft there are no
guarantees of anything. -->
<Property Id='REINSTALLMODE' Value='amus'/>
<Upgrade Id="{upgrade_code}">
<UpgradeVersion Maximum="{version}"
IncludeMaximum="yes"
OnlyDetect="no"
Language="1033"
MigrateFeatures="yes"
Property="OLDPRODUCTFOUND"/>
<UpgradeVersion Minimum="{version}"
IncludeMinimum="no"
OnlyDetect="yes"
Language="1033"
Property="NEWPRODUCTFOUND"/>
</Upgrade>
<CustomAction Id="PreventDowngrading" Error="Newer version of {app} already installed. If you want to downgrade you must uninstall {app} first."/>
<Property Id="APPLICATIONFOLDER">
<RegistrySearch Id='calibreInstDir' Type='raw'
Root='HKLM' Key="Software\{app}{x64}\Installer" Name="InstallPath" />
</Property>
<Directory Id='TARGETDIR' Name='SourceDir'>
<Directory Id='{ProgramFilesFolder}' Name='PFiles'>
<!-- The name must be calibre on 32 bit to ensure
that the component guids dont change compared to previous msis.
However, on 64 bit it must be Calibre2 otherwise by default it
will install to C:\Program Files\calibre -->
<Directory Id='APPLICATIONFOLDER' Name="{appfolder}" />
</Directory>
<Directory Id="ProgramMenuFolder">
<Directory Id="ApplicationProgramsFolder" Name="{app}{x64} - E-book Management"/>
</Directory>
<Directory Id="DesktopFolder" Name="Desktop"/>
</Directory>
<Icon Id="main_icon" SourceFile="{main_icon}"/>
<!-- <Icon Id="viewer_icon" SourceFile="{viewer_icon}"/> -->
<!-- <Icon Id="editor_icon" SourceFile="{editor_icon}"/> -->
<DirectoryRef Id="APPLICATIONFOLDER">
{app_components}
<Component Id="AddToPath" Guid="*">
<Environment Id='UpdatePath' Name='PATH' Action='set' System='yes' Part='last' Value='[APPLICATIONFOLDER]' />
<RegistryValue Root="HKCU" Key="Software\Microsoft\{app}{x64}" Name="system_path_updated" Type="integer" Value="1" KeyPath="yes"/>
</Component>
<Component Id="RememberInstallDir" Guid="*">
<RegistryValue Root="HKLM" Key="Software\{app}{x64}\Installer" Name="InstallPath" Type="string" Value="[APPLICATIONFOLDER]" KeyPath="yes"/>
</Component>
</DirectoryRef>
<DirectoryRef Id="ApplicationProgramsFolder">
<Component Id="StartMenuShortcuts" Guid="*">
<Shortcut Id="s1" Name="{app}{x64} - E-book management"
Description="Manage your e-book collection and download news"
Target="[#{exe_map[calibre]}]"
WorkingDirectory="APPLICATIONROOTDIRECTORY" />
<Shortcut Id="s2" Name="E-book viewer{x64}"
Description="Viewer for all the major e-book formats"
Target="[#{exe_map[ebook-viewer]}]"
WorkingDirectory="APPLICATIONROOTDIRECTORY" />
<Shortcut Id="s4" Name="Edit E-book{x64}"
Description="Edit e-books"
Target="[#{exe_map[ebook-edit]}]"
WorkingDirectory="APPLICATIONROOTDIRECTORY" />
<Shortcut Id="s3" Name="LRF viewer{x64}"
Description="Viewer for LRF format e-books"
Target="[#{exe_map[lrfviewer]}]"
WorkingDirectory="APPLICATIONROOTDIRECTORY" />
<util:InternetShortcut Id="OnlineDocumentationShortcut"
Name="User Manual" Type="url"
Target="https://manual.calibre-ebook.com"/>
<util:InternetShortcut Id="GetInvolvedS"
Name="Get Involved" Type="url"
Target="https://calibre-ebook.com/get-involved"/>
<RemoveFolder Id="ApplicationProgramsFolder" On="uninstall"/>
<RegistryValue Root="HKCU" Key="Software\Microsoft\{app}{x64}" Name="start_menu_shortcuts_installed" Type="integer" Value="1" KeyPath="yes"/>
</Component>
</DirectoryRef>
<DirectoryRef Id="DesktopFolder">
<Component Id="DesktopShortcut" Guid="*">
<Shortcut Id="ds1" Name="{app}{x64} - E-book management"
Description="Manage your e-book collection and download news"
Target="[#{exe_map[calibre]}]"
WorkingDirectory="APPLICATIONROOTDIRECTORY" />
<RegistryValue Root="HKCU" Key="Software\Microsoft\{app}{x64}" Name="desktop_shortcut_installed" Type="integer" Value="1" KeyPath="yes"/>
</Component>
</DirectoryRef>
<Feature Id="Complete" Title="{app}" Display="expand" Level="1"
ConfigurableDirectory="APPLICATIONFOLDER">
<Feature Id="MainApplication" Title="Program Files" Level="1"
Description="All the files needed to run {app}" Absent="disallow">
<ComponentRef Id="RememberInstallDir"/>
</Feature>
<Feature Id="FSMS" Title="Start menu shortcuts" Level="1"
Description="Program shortcuts installed in the Start Menu">
<ComponentRef Id="StartMenuShortcuts"/>
</Feature>
<Feature Id="DS" Title="Shortcut on desktop" Level="1"
Description="Shortcut to {app} on your desktop">
<ComponentRef Id="DesktopShortcut"/>
</Feature>
<Feature Id="FAddToPath" Title="Add install directory to path" Level="1"
Description="Add installation directory to PATH. Makes using command line tools easier">
<ComponentRef Id="AddToPath"/>
</Feature>
</Feature>
<!-- Add icon to entry in Add/Remove programs -->
<Property Id="ARPPRODUCTICON" Value="main_icon" />
<Property Id="ARPURLINFOABOUT" Value="https://calibre-ebook.com" />
<Property Id='ARPHELPLINK' Value="https://calibre-ebook.com/help" />
<Property Id='ARPURLUPDATEINFO' Value="https://calibre-ebook.com/download_windows" />
<SetProperty Id="ARPINSTALLLOCATION" Value="[APPLICATIONFOLDER]" After="CostFinalize" />
<Condition
Message="This application is only supported on {minverhuman}, or higher.">
<![CDATA[Installed OR (VersionNT >= {minver})]]>
</Condition>
<!-- On 64 bit installers there is a bug in WiX that causes the
WixSetDefaultPerMachineFolder action to incorrectly set
APPLICATIONFOLDER to the x86 value, so we override it. See
http://stackoverflow.com/questions/5479790/wix-how-to-override-c-program-files-x86-on-x64-machine-in-wixui-advanced-s
-->
<CustomAction
Id="OverwriteWixSetDefaultPerMachineFolder"
Property="WixPerMachineFolder"
Value="[APPLICATIONFOLDER]"
Execute="immediate"
/>
<InstallExecuteSequence>
<Custom Action="PreventDowngrading" After="FindRelatedProducts">NEWPRODUCTFOUND</Custom>
{fix_wix}
<RemoveExistingProducts After="InstallFinalize" />
</InstallExecuteSequence>
<InstallUISequence>
<Custom Action="PreventDowngrading" After="FindRelatedProducts">NEWPRODUCTFOUND</Custom>
{fix_wix}
</InstallUISequence>
<UI>
<UIRef Id="WixUI_Advanced" />
<UIRef Id="WixUI_ErrorProgressText" />
<Publish Dialog="ExitDialog"
Control="Finish"
Event="DoAction"
Value="LaunchApplication">WIXUI_EXITDIALOGOPTIONALCHECKBOX = 1 and NOT Installed</Publish>
</UI>
<!--
Set default folder name and allow only per machine installs.
For a per-machine installation, the default installation location
will be [ProgramFilesFolder][ApplicationFolderName] and the user
will be able to change it in the setup UI. This is no longer necessary
(i.e. per user installs should work) but left this way as I
dont want to deal with the complications
-->
<Property Id="ApplicationFolderName" Value="Calibre2" />
<Property Id="WixAppFolder" Value="WixPerMachineFolder" />
<Property Id="ALLUSERS" Value="1" />
<WixVariable Id="WixUISupportPerUser" Value="0" />
<!-- Add option to launch calibre after install -->
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT" Value="Launch {app}" />
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOX" Value="1"/>
<Property Id="WixShellExecTarget" Value="[#{exe_map[calibre]}]" />
<CustomAction Id="LaunchApplication" BinaryKey="WixCA" DllEntry="WixShellExec" Impersonate="yes"/>
</Product>
</Wix>

137
bypy/windows/wix.py Normal file
View File

@ -0,0 +1,137 @@
#!/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)
from itertools import count
import os
import shutil
from pkgs.constants import is64bit
from pkgs.utils import run
from .. import calibre_constants
WIXP = r'C:\Program Files (x86)\WiX Toolset v3.10'
if is64bit:
UPGRADE_CODE = '5DD881FF-756B-4097-9D82-8C0F11D521EA'
else:
UPGRADE_CODE = 'BEB2A80D-E902-4DAD-ADF9-8BD2DA42CFE1'
MINVERHUMAN = 'Windows Vista SP2'
CANDLE = WIXP + r'\bin\candle.exe'
LIGHT = WIXP + r'\bin\light.exe'
j, d, a, b = os.path.join, os.path.dirname, os.path.abspath, os.path.basename
def create_installer(env):
if os.path.exists(env.installer_dir):
shutil.rmtree(env.installer_dir)
os.makedirs(env.installer_dir)
template = open(j(d(__file__), 'wix-template.xml'), 'rb').read()
components, smap = get_components_from_files(env)
wxs = template.format(
app=calibre_constants['appname'],
appfolder='Calibre2' if is64bit else 'Calibre',
version=calibre_constants['version'],
upgrade_code=UPGRADE_CODE,
ProgramFilesFolder='ProgramFiles64Folder' if is64bit else 'ProgramFilesFolder',
x64=' 64bit' if is64bit else '',
minverhuman=MINVERHUMAN,
minver='600',
fix_wix='<Custom Action="OverwriteWixSetDefaultPerMachineFolder" After="WixSetDefaultPerMachineFolder" />' if is64bit else '',
compression='high',
app_components=components,
exe_map=smap,
main_icon=j(env.src_root, 'icons', 'library.ico'),
viewer_icon=j(env.src_root, 'icons', 'viewer.ico'),
editor_icon=j(env.src_root, 'icons', 'ebook-edit.ico'),
web_icon=j(env.src_root, 'icons', 'web.ico'),
)
template = open(j(d(__file__), 'en-us.xml'), 'rb').read()
enus = template.format(app=calibre_constants['appname'])
enusf = j(env.installer_dir, 'en-us.wxl')
wxsf = j(env.installer_dir, calibre_constants['appname'] + '.wxs')
with open(wxsf, 'wb') as f:
f.write(wxs)
with open(enusf, 'wb') as f:
f.write(enus)
wixobj = j(env.installer_dir, calibre_constants['appname'] + '.wixobj')
arch = 'x64' if is64bit else 'x86'
cmd = [CANDLE, '-nologo', '-arch', arch, '-ext', 'WiXUtilExtension', '-o', wixobj, wxsf]
run(*cmd)
installer = j(env.dist, '%s%s-%s.msi' % (
calibre_constants['appname'], ('-64bit' if is64bit else ''), calibre_constants['version']))
license = j(env.src_root, 'LICENSE.rtf')
banner = j(env.src_root, 'icons', 'wix-banner.bmp')
dialog = j(env.src_root, 'icons', 'wix-dialog.bmp')
cmd = [LIGHT, '-nologo', '-ext', 'WixUIExtension',
'-cultures:en-us', '-loc', enusf, wixobj,
'-ext', 'WixUtilExtension',
'-o', installer,
'-dWixUILicenseRtf=' + license,
'-dWixUIBannerBmp=' + banner,
'-dWixUIDialogBmp=' + dialog]
cmd.extend([
'-sice:ICE60', # No language in dlls warning
'-sice:ICE61', # Allow upgrading with same version number
'-sice:ICE40', # Re-install mode overriden
'-sice:ICE69', # Shortcut components are part of a different feature than the files they point to
])
cmd.append('-sval') # Disable all checks since they fail when running under ssh
run(*cmd)
def get_components_from_files(env):
file_idc = count()
file_id_map = {}
def process_dir(path):
components = []
for x in os.listdir(path):
f = os.path.join(path, x)
file_id_map[f] = fid = next(file_idc)
if os.path.isdir(f):
components.append(
'<Directory Id="file_%s" FileSource="%s" Name="%s">' %
(file_id_map[f], f, x))
c = process_dir(f)
components.extend(c)
components.append('</Directory>')
else:
checksum = 'Checksum="yes"' if x.endswith('.exe') else ''
c = [
('<Component Id="component_%s" Feature="MainApplication" '
'Guid="*">') % (fid,),
('<File Id="file_%s" Source="%s" Name="%s" ReadOnly="yes" '
'KeyPath="yes" %s/>') %
(fid, f, x, checksum),
'</Component>'
]
if x.endswith('.exe') and not x.startswith('pdf'):
# Add the executable to app paths so that users can
# launch it from the run dialog even if it is not on
# the path. See http://msdn.microsoft.com/en-us/library/windows/desktop/ee872121(v=vs.85).aspx
c[-1:-1] = [
('<RegistryValue Root="HKLM" '
r'Key="SOFTWARE\Microsoft\Windows\CurrentVersion\App '
r'Paths\%s" Value="[#file_%d]" Type="string" />' % (x, fid)),
('<RegistryValue Root="HKLM" '
r'Key="SOFTWARE\Microsoft\Windows\CurrentVersion\App '
r'Paths\{0}" Name="Path" Value="[APPLICATIONFOLDER]" '
'Type="string" />'.format(x)),
]
components.append('\n'.join(c))
return components
components = process_dir(a(env.base))
smap = {}
for x in calibre_constants['basenames']['gui']:
smap[x] = 'file_%d' % file_id_map[a(j(env.base, x + '.exe'))]
return '\t\t\t\t' + '\n\t\t\t\t'.join(components), smap

View File

@ -206,7 +206,8 @@ def init_env():
for p in win_inc:
cflags.append('-I'+p)
for p in win_lib:
ldflags.append('/LIBPATH:'+p)
if p:
ldflags.append('/LIBPATH:'+p)
cflags.append('-I%s'%sysconfig.get_python_inc())
ldflags.append('/LIBPATH:'+os.path.join(sysconfig.PREFIX, 'libs'))
linker = msvc.linker
@ -297,7 +298,7 @@ class Build(Command):
def lib_dirs_to_ldflags(self, dirs):
pref = '/LIBPATH:' if iswindows else '-L'
return [pref+x for x in dirs]
return [pref+x for x in dirs if x]
def libraries_to_ldflags(self, dirs):
pref = '' if iswindows else '-l'
@ -338,7 +339,11 @@ class Build(Command):
self.info('Linking', ext.name)
cmd = [linker]
if iswindows:
cmd += self.env.ldflags + ext.ldflags + elib + xlib + \
pre_ld_flags = []
if ext.name in ('icu', 'matcher'):
# windows has its own ICU libs that dont work
pre_ld_flags = elib
cmd += pre_ld_flags + self.env.ldflags + ext.ldflags + elib + xlib + \
['/EXPORT:' + init_symbol_name(ext.name)] + objects + ext.extra_objs + ['/OUT:'+dest]
else:
cmd += objects + ext.extra_objs + ['-o', dest] + self.env.ldflags + ext.ldflags + elib + xlib

View File

@ -20,8 +20,8 @@ if iswindows:
NMAKE = msvc.find_exe('nmake.exe')
RC = msvc.find_exe('rc.exe')
MT = msvc.find_exe('mt.exe')
win_inc = os.environ['include'].split(';')
win_lib = os.environ['lib'].split(';')
win_inc = [x for x in os.environ['include'].split(';') if x]
win_lib = [x for x in os.environ['lib'].split(';') if x]
QMAKE = 'qmake'
for x in ('qmake-qt5', 'qt5-qmake', 'qmake'):

View File

@ -5,7 +5,7 @@ from __future__ import absolute_import, division, print_function, unicode_litera
__license__ = 'GPL v3'
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
import subprocess
import subprocess, os
from multiprocessing.dummy import Pool
from functools import partial
from contextlib import closing
@ -49,8 +49,11 @@ cpu_count = min(16, max(1, cpu_count))
def run_worker(job, decorate=True):
cmd, human_text = job
human_text = human_text or ' '.join(cmd)
cwd = None
if cmd[0].lower().endswith('cl.exe'):
cwd = os.environ.get('COMPILER_CWD')
try:
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=cwd)
except Exception as err:
return False, human_text, unicode_type(err)
stdout, stderr = p.communicate()