mirror of
https://github.com/apprenticeharper/DeDRM_tools
synced 2024-12-28 09:58:56 +01:00
afa4ac5716
THIS IS ON THE MASTER BRANCH. The Master branch will be Python 3.0 from now on. While Python 2.7 support will not be deliberately broken, all efforts should now focus on Python 3.0 compatibility. I can see a lot of work has been done. There's more to do. I've bumped the version number of everything I came across to the next major number for Python 3.0 compatibility indication. Thanks everyone. I hope to update here at least once a week until we have a stable 7.0 release for calibre 5.0
213 lines
6.4 KiB
Python
213 lines
6.4 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
|
|
|
# to work around tk_chooseDirectory not properly returning unicode paths on Windows
|
|
# need to use a dialog that can be hacked up to actually return full unicode paths
|
|
# originally based on AskFolder from EasyDialogs for Windows but modified to fix it
|
|
# to actually use unicode for path
|
|
|
|
# The original license for EasyDialogs is as follows
|
|
#
|
|
# Copyright (c) 2003-2005 Jimmy Retzlaff
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining a
|
|
# copy of this software and associated documentation files (the "Software"),
|
|
# to deal in the Software without restriction, including without limitation
|
|
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
# and/or sell copies of the Software, and to permit persons to whom the
|
|
# Software is furnished to do so, subject to the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be included in
|
|
# all copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
# DEALINGS IN THE SOFTWARE.
|
|
|
|
# Adjusted for Python 3, September 2020
|
|
|
|
"""
|
|
AskFolder(...) -- Ask the user to select a folder Windows specific
|
|
"""
|
|
|
|
import os
|
|
|
|
import ctypes
|
|
from ctypes import POINTER, byref, cdll, c_int, windll
|
|
from ctypes.wintypes import LPCWSTR, LPWSTR
|
|
import ctypes.wintypes as wintypes
|
|
|
|
|
|
__all__ = ['AskFolder']
|
|
|
|
# Load required Windows DLLs
|
|
ole32 = ctypes.windll.ole32
|
|
shell32 = ctypes.windll.shell32
|
|
user32 = ctypes.windll.user32
|
|
|
|
|
|
# Windows Constants
|
|
BFFM_INITIALIZED = 1
|
|
BFFM_SETOKTEXT = 1129
|
|
BFFM_SETSELECTIONA = 1126
|
|
BFFM_SETSELECTIONW = 1127
|
|
BIF_EDITBOX = 16
|
|
BS_DEFPUSHBUTTON = 1
|
|
CB_ADDSTRING = 323
|
|
CB_GETCURSEL = 327
|
|
CB_SETCURSEL = 334
|
|
CDM_SETCONTROLTEXT = 1128
|
|
EM_GETLINECOUNT = 186
|
|
EM_GETMARGINS = 212
|
|
EM_POSFROMCHAR = 214
|
|
EM_SETSEL = 177
|
|
GWL_STYLE = -16
|
|
IDC_STATIC = -1
|
|
IDCANCEL = 2
|
|
IDNO = 7
|
|
IDOK = 1
|
|
IDYES = 6
|
|
MAX_PATH = 260
|
|
OFN_ALLOWMULTISELECT = 512
|
|
OFN_ENABLEHOOK = 32
|
|
OFN_ENABLESIZING = 8388608
|
|
OFN_ENABLETEMPLATEHANDLE = 128
|
|
OFN_EXPLORER = 524288
|
|
OFN_OVERWRITEPROMPT = 2
|
|
OPENFILENAME_SIZE_VERSION_400 = 76
|
|
PBM_GETPOS = 1032
|
|
PBM_SETMARQUEE = 1034
|
|
PBM_SETPOS = 1026
|
|
PBM_SETRANGE = 1025
|
|
PBM_SETRANGE32 = 1030
|
|
PBS_MARQUEE = 8
|
|
PM_REMOVE = 1
|
|
SW_HIDE = 0
|
|
SW_SHOW = 5
|
|
SW_SHOWNORMAL = 1
|
|
SWP_NOACTIVATE = 16
|
|
SWP_NOMOVE = 2
|
|
SWP_NOSIZE = 1
|
|
SWP_NOZORDER = 4
|
|
VER_PLATFORM_WIN32_NT = 2
|
|
WM_COMMAND = 273
|
|
WM_GETTEXT = 13
|
|
WM_GETTEXTLENGTH = 14
|
|
WM_INITDIALOG = 272
|
|
WM_NOTIFY = 78
|
|
|
|
# Windows function prototypes
|
|
BrowseCallbackProc = ctypes.WINFUNCTYPE(ctypes.c_int, wintypes.HWND, ctypes.c_uint, wintypes.LPARAM, wintypes.LPARAM)
|
|
|
|
# Windows types
|
|
LPCTSTR = ctypes.c_char_p
|
|
LPTSTR = ctypes.c_char_p
|
|
LPVOID = ctypes.c_voidp
|
|
TCHAR = ctypes.c_char
|
|
|
|
class BROWSEINFO(ctypes.Structure):
|
|
_fields_ = [
|
|
("hwndOwner", wintypes.HWND),
|
|
("pidlRoot", LPVOID),
|
|
("pszDisplayName", LPTSTR),
|
|
("lpszTitle", LPCTSTR),
|
|
("ulFlags", ctypes.c_uint),
|
|
("lpfn", BrowseCallbackProc),
|
|
("lParam", wintypes.LPARAM),
|
|
("iImage", ctypes.c_int)
|
|
]
|
|
|
|
|
|
# Utilities
|
|
def CenterWindow(hwnd):
|
|
desktopRect = GetWindowRect(user32.GetDesktopWindow())
|
|
myRect = GetWindowRect(hwnd)
|
|
x = width(desktopRect) // 2 - width(myRect) // 2
|
|
y = height(desktopRect) // 2 - height(myRect) // 2
|
|
user32.SetWindowPos(hwnd, 0,
|
|
desktopRect.left + x,
|
|
desktopRect.top + y,
|
|
0, 0,
|
|
SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER
|
|
)
|
|
|
|
|
|
def GetWindowRect(hwnd):
|
|
rect = wintypes.RECT()
|
|
user32.GetWindowRect(hwnd, ctypes.byref(rect))
|
|
return rect
|
|
|
|
def width(rect):
|
|
return rect.right-rect.left
|
|
|
|
def height(rect):
|
|
return rect.bottom-rect.top
|
|
|
|
|
|
def AskFolder(
|
|
message=None,
|
|
version=None,
|
|
defaultLocation=None,
|
|
location=None,
|
|
windowTitle=None,
|
|
actionButtonLabel=None,
|
|
cancelButtonLabel=None,
|
|
multiple=None):
|
|
"""Display a dialog asking the user for select a folder.
|
|
modified to use unicode strings as much as possible
|
|
returns unicode path
|
|
"""
|
|
|
|
def BrowseCallback(hwnd, uMsg, lParam, lpData):
|
|
if uMsg == BFFM_INITIALIZED:
|
|
if actionButtonLabel:
|
|
label = actionButtonLabel.decode('utf-8', 'replace')
|
|
user32.SendMessageW(hwnd, BFFM_SETOKTEXT, 0, label)
|
|
if cancelButtonLabel:
|
|
label = cancelButtonLabel.decode('utf-8', 'replace')
|
|
cancelButton = user32.GetDlgItem(hwnd, IDCANCEL)
|
|
if cancelButton:
|
|
user32.SetWindowTextW(cancelButton, label)
|
|
if windowTitle:
|
|
title = windowTitle.decode('utf-8', 'replace')
|
|
user32.SetWindowTextW(hwnd, title)
|
|
if defaultLocation:
|
|
user32.SendMessageW(hwnd, BFFM_SETSELECTIONW, 1, defaultLocation.replace('/', '\\'))
|
|
if location:
|
|
x, y = location
|
|
desktopRect = wintypes.RECT()
|
|
user32.GetWindowRect(0, ctypes.byref(desktopRect))
|
|
user32.SetWindowPos(hwnd, 0,
|
|
desktopRect.left + x,
|
|
desktopRect.top + y, 0, 0,
|
|
SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER)
|
|
else:
|
|
CenterWindow(hwnd)
|
|
return 0
|
|
|
|
# This next line is needed to prevent gc of the callback
|
|
callback = BrowseCallbackProc(BrowseCallback)
|
|
|
|
browseInfo = BROWSEINFO()
|
|
browseInfo.pszDisplayName = ctypes.c_char_p('\0' * (MAX_PATH+1))
|
|
browseInfo.lpszTitle = message
|
|
browseInfo.lpfn = callback
|
|
|
|
pidl = shell32.SHBrowseForFolder(ctypes.byref(browseInfo))
|
|
if not pidl:
|
|
result = None
|
|
else:
|
|
path = LPCWSTR(u" " * (MAX_PATH+1))
|
|
shell32.SHGetPathFromIDListW(pidl, path)
|
|
ole32.CoTaskMemFree(pidl)
|
|
result = path.value
|
|
return result
|
|
|
|
|
|
|
|
|