mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Start work on building calibre on windows
This commit is contained in:
parent
424de7f8fe
commit
e804e48747
@ -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
4379
bypy/windows/XUnzip.cpp
Normal file
File diff suppressed because it is too large
Load Diff
382
bypy/windows/XUnzip.h
Normal file
382
bypy/windows/XUnzip.h
Normal 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
691
bypy/windows/__main__.py
Normal 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
423
bypy/windows/eject.c
Normal 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
9
bypy/windows/en-us.xml
Normal 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>
|
||||
|
355
bypy/windows/file_dialogs.cpp
Normal file
355
bypy/windows/file_dialogs.cpp
Normal 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
113
bypy/windows/main.c
Normal 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;
|
||||
}
|
618
bypy/windows/portable-installer.cpp
Normal file
618
bypy/windows/portable-installer.cpp
Normal 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
154
bypy/windows/portable.c
Normal 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
101
bypy/windows/site.py
Normal 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
42
bypy/windows/template.rc
Normal 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
448
bypy/windows/util.c
Normal 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;
|
||||
}
|
206
bypy/windows/wix-template.xml
Normal file
206
bypy/windows/wix-template.xml
Normal 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
137
bypy/windows/wix.py
Normal 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
|
@ -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
|
||||
|
@ -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'):
|
||||
|
@ -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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user