mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
WPD: Device detection
This commit is contained in:
parent
48b43b555c
commit
6c94cdb03c
@ -298,6 +298,7 @@ class Build(Command):
|
|||||||
self.obj_dir = os.path.join(os.path.dirname(SRC), 'build', 'objects')
|
self.obj_dir = os.path.join(os.path.dirname(SRC), 'build', 'objects')
|
||||||
if not os.path.exists(self.obj_dir):
|
if not os.path.exists(self.obj_dir):
|
||||||
os.makedirs(self.obj_dir)
|
os.makedirs(self.obj_dir)
|
||||||
|
if not opts.only:
|
||||||
self.build_style(self.j(self.SRC, 'calibre', 'plugins'))
|
self.build_style(self.j(self.SRC, 'calibre', 'plugins'))
|
||||||
for ext in extensions:
|
for ext in extensions:
|
||||||
if opts.only != 'all' and opts.only != ext.name:
|
if opts.only != 'all' and opts.only != ext.name:
|
||||||
|
125
src/calibre/devices/mtp/windows/driver.py
Normal file
125
src/calibre/devices/mtp/windows/driver.py
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import,
|
||||||
|
print_function)
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
import time
|
||||||
|
from threading import RLock
|
||||||
|
|
||||||
|
from calibre import as_unicode, prints
|
||||||
|
from calibre.constants import plugins, __appname__, numeric_version
|
||||||
|
from calibre.devices.mtp.base import MTPDeviceBase, synchronous
|
||||||
|
|
||||||
|
class MTP_DEVICE(MTPDeviceBase):
|
||||||
|
|
||||||
|
supported_platforms = ['windows']
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
MTPDeviceBase.__init__(self, *args, **kwargs)
|
||||||
|
self.dev = None
|
||||||
|
self.lock = RLock()
|
||||||
|
self.blacklisted_devices = set()
|
||||||
|
self.ejected_devices = set()
|
||||||
|
self.currently_connected_pnp_id = None
|
||||||
|
self.detected_devices = {}
|
||||||
|
self.previous_devices_on_system = frozenset()
|
||||||
|
self.last_refresh_devices_time = time.time()
|
||||||
|
self.wpd = self.wpd_error = None
|
||||||
|
|
||||||
|
@synchronous
|
||||||
|
def startup(self):
|
||||||
|
self.wpd, self.wpd_error = plugins['wpd']
|
||||||
|
if self.wpd is not None:
|
||||||
|
try:
|
||||||
|
self.wpd.init(__appname__, *(numeric_version[:3]))
|
||||||
|
except self.wpd.NoWPD:
|
||||||
|
self.wpd_error = _(
|
||||||
|
'The Windows Portable Devices service is not available'
|
||||||
|
' on your computer. You may need to install Windows'
|
||||||
|
' Media Player 11 or newer and/or restart your computer')
|
||||||
|
except Exception as e:
|
||||||
|
self.wpd_error = as_unicode(e)
|
||||||
|
|
||||||
|
@synchronous
|
||||||
|
def shutdown(self):
|
||||||
|
if self.wpd is not None:
|
||||||
|
self.wpd.uninit()
|
||||||
|
|
||||||
|
@synchronous
|
||||||
|
def detect_managed_devices(self, devices_on_system):
|
||||||
|
if self.wpd is None: return None
|
||||||
|
|
||||||
|
devices_on_system = frozenset(devices_on_system)
|
||||||
|
if (devices_on_system != self.previous_devices_on_system or time.time()
|
||||||
|
- self.last_refresh_devices_time > 10):
|
||||||
|
self.previous_devices_on_system = devices_on_system
|
||||||
|
self.last_refresh_devices_time = time.time()
|
||||||
|
try:
|
||||||
|
pnp_ids = frozenset(self.wpd.enumerate_devices())
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
self.detected_devices = {dev:self.detected_devices.get(dev, None)
|
||||||
|
for dev in pnp_ids}
|
||||||
|
|
||||||
|
# Get device data for detected devices. If there is an error, we will
|
||||||
|
# try again for that device the next time this method is called.
|
||||||
|
for dev in tuple(self.detected_devices.iterkeys()):
|
||||||
|
data = self.detected_devices.get(dev, None)
|
||||||
|
if data is None or data is False:
|
||||||
|
try:
|
||||||
|
data = self.wpd.device_info(dev)
|
||||||
|
except Exception as e:
|
||||||
|
prints('Failed to get device info for device:', dev,
|
||||||
|
as_unicode(e))
|
||||||
|
data = {} if data is False else False
|
||||||
|
self.detected_devices[dev] = data
|
||||||
|
# Remove devices that have been disconnected from ejected
|
||||||
|
# devices
|
||||||
|
self.ejected_devices = set(self.detected_devices).intersection(self.ejected_devices)
|
||||||
|
|
||||||
|
if self.currently_connected_pnp_id is not None:
|
||||||
|
return (self.currently_connected_pnp_id if
|
||||||
|
self.currently_connected_pnp_id in self.detected_devices
|
||||||
|
else None)
|
||||||
|
|
||||||
|
for dev, data in self.detected_devices.iteritems():
|
||||||
|
if dev in self.blacklisted_devices or dev in self.ejected_devices:
|
||||||
|
# Ignore blacklisted and ejected devices
|
||||||
|
continue
|
||||||
|
if data and self.is_suitable_wpd_device(data):
|
||||||
|
return dev
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def is_suitable_wpd_device(self, devdata):
|
||||||
|
# Check that protocol is MTP
|
||||||
|
protocol = devdata.get('protocol', '').lower()
|
||||||
|
if not protocol.startswith('mtp:'): return False
|
||||||
|
|
||||||
|
# Check that the device has some read-write storage
|
||||||
|
if not devdata.get('has_storage', False): return False
|
||||||
|
has_rw_storage = False
|
||||||
|
for s in devdata.get('storage', []):
|
||||||
|
if s.get('rw', False):
|
||||||
|
has_rw_storage = True
|
||||||
|
break
|
||||||
|
if not has_rw_storage: return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
@synchronous
|
||||||
|
def post_yank_cleanup(self):
|
||||||
|
self.currently_connected_pnp_id = None
|
||||||
|
|
||||||
|
@synchronous
|
||||||
|
def eject(self):
|
||||||
|
if self.currently_connected_pnp_id is None: return
|
||||||
|
self.ejected_devices.add(self.currently_connected_pnp_id)
|
||||||
|
self.currently_connected_pnp_id = None
|
||||||
|
|
||||||
|
|
@ -7,14 +7,21 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
import subprocess, sys, os, pprint
|
import subprocess, sys, os, pprint, signal, time, glob
|
||||||
pprint
|
pprint
|
||||||
|
|
||||||
def build(mod='wpd'):
|
def build(mod='wpd'):
|
||||||
|
master = subprocess.Popen('ssh -MN getafix'.split())
|
||||||
|
master2 = subprocess.Popen('ssh -MN xp_build'.split())
|
||||||
|
try:
|
||||||
|
while not glob.glob(os.path.expanduser('~/.ssh/*kovid@xp_build*')):
|
||||||
|
time.sleep(0.05)
|
||||||
builder = subprocess.Popen('ssh xp_build ~/build-wpd'.split())
|
builder = subprocess.Popen('ssh xp_build ~/build-wpd'.split())
|
||||||
syncer = subprocess.Popen('ssh getafix ~/test-wpd'.split())
|
|
||||||
if builder.wait() != 0:
|
if builder.wait() != 0:
|
||||||
raise Exception('Failed to build plugin')
|
raise Exception('Failed to build plugin')
|
||||||
|
while not glob.glob(os.path.expanduser('~/.ssh/*kovid@getafix*')):
|
||||||
|
time.sleep(0.05)
|
||||||
|
syncer = subprocess.Popen('ssh getafix ~/test-wpd'.split())
|
||||||
if syncer.wait() != 0:
|
if syncer.wait() != 0:
|
||||||
raise Exception('Failed to rsync to getafix')
|
raise Exception('Failed to rsync to getafix')
|
||||||
subprocess.check_call(
|
subprocess.check_call(
|
||||||
@ -25,60 +32,42 @@ def build(mod='wpd'):
|
|||||||
'ssh getafix calibre-debug -e calibre/src/calibre/devices/mtp/windows/remote.py'.split())
|
'ssh getafix calibre-debug -e calibre/src/calibre/devices/mtp/windows/remote.py'.split())
|
||||||
p.wait()
|
p.wait()
|
||||||
print()
|
print()
|
||||||
|
finally:
|
||||||
|
for m in (master2, master):
|
||||||
|
m.send_signal(signal.SIGHUP)
|
||||||
|
for m in (master2, master):
|
||||||
|
m.wait()
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
fp, d = os.path.abspath(__file__), os.path.dirname
|
||||||
|
if b'CALIBRE_DEVELOP_FROM' not in os.environ:
|
||||||
|
env = os.environ.copy()
|
||||||
|
env[b'CALIBRE_DEVELOP_FROM'] = bytes(d(d(d(d(d(fp))))))
|
||||||
|
subprocess.call(['calibre-debug', '-e', fp], env=env)
|
||||||
|
return
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.dirname(fp))
|
||||||
|
if 'wpd' in sys.modules:
|
||||||
|
del sys.modules['wpd']
|
||||||
import wpd
|
import wpd
|
||||||
from calibre.constants import plugins
|
from calibre.constants import plugins
|
||||||
plugins._plugins['wpd'] = (wpd, '')
|
plugins._plugins['wpd'] = (wpd, '')
|
||||||
sys.path.pop(0)
|
sys.path.pop(0)
|
||||||
wpd.init('calibre', 1, 0, 0)
|
|
||||||
|
from calibre.devices.scanner import win_scanner
|
||||||
|
from calibre.devices.mtp.windows.driver import MTP_DEVICE
|
||||||
|
dev = MTP_DEVICE(None)
|
||||||
|
dev.startup()
|
||||||
|
print (dev.wpd, dev.wpd_error)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
for pnp_id in wpd.enumerate_devices():
|
devices = win_scanner()
|
||||||
print (pnp_id)
|
pnp_id = dev.detect_managed_devices(devices)
|
||||||
pprint.pprint(wpd.device_info(pnp_id))
|
# pprint.pprint(dev.detected_devices)
|
||||||
|
print ('Trying to connect to:', pnp_id)
|
||||||
finally:
|
finally:
|
||||||
wpd.uninit()
|
dev.shutdown()
|
||||||
|
|
||||||
def winutil():
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
||||||
del sys.modules['winutil']
|
|
||||||
import winutil
|
|
||||||
from calibre.constants import plugins
|
|
||||||
plugins._plugins['winutil'] = (winutil, '')
|
|
||||||
sys.path.pop(0)
|
|
||||||
print (winutil.serial_number_from_drive('F'))
|
|
||||||
|
|
||||||
def get_subkeys(key):
|
|
||||||
import _winreg
|
|
||||||
index = -1
|
|
||||||
while True:
|
|
||||||
index += 1
|
|
||||||
try:
|
|
||||||
yield _winreg.EnumKey(key, index)
|
|
||||||
except OSError:
|
|
||||||
break
|
|
||||||
|
|
||||||
def get_values(key):
|
|
||||||
import _winreg
|
|
||||||
index = -1
|
|
||||||
while True:
|
|
||||||
index +=1
|
|
||||||
try:
|
|
||||||
yield _winreg.EnumValue(key, index)
|
|
||||||
except OSError:
|
|
||||||
break
|
|
||||||
|
|
||||||
def test():
|
|
||||||
vid, pid = 0x1949, 0x4
|
|
||||||
import _winreg as r
|
|
||||||
usb = r.OpenKey(r.HKEY_LOCAL_MACHINE, 'SYSTEM\\CurrentControlSet\\Enum\\USB')
|
|
||||||
q = ('vid_%4.4x&pid_%4.4x'%(vid, pid))
|
|
||||||
dev = r.OpenKey(usb, q)
|
|
||||||
print (list(get_subkeys(dev)))
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
# winutil()
|
|
||||||
|
|
||||||
|
@ -92,14 +92,10 @@ wpd_enumerate_devices(PyObject *self, PyObject *args) {
|
|||||||
|
|
||||||
ENSURE_WPD(NULL);
|
ENSURE_WPD(NULL);
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "|O", &refresh)) return NULL;
|
|
||||||
|
|
||||||
if (refresh != NULL && PyObject_IsTrue(refresh)) {
|
|
||||||
Py_BEGIN_ALLOW_THREADS;
|
Py_BEGIN_ALLOW_THREADS;
|
||||||
hr = portable_device_manager->RefreshDeviceList();
|
hr = portable_device_manager->RefreshDeviceList();
|
||||||
Py_END_ALLOW_THREADS;
|
Py_END_ALLOW_THREADS;
|
||||||
if (FAILED(hr)) return hresult_set_exc("Failed to refresh the list of portable devices", hr);
|
if (FAILED(hr)) return hresult_set_exc("Failed to refresh the list of portable devices", hr);
|
||||||
}
|
|
||||||
|
|
||||||
hr = portable_device_manager->GetDevices(NULL, &num_of_devices);
|
hr = portable_device_manager->GetDevices(NULL, &num_of_devices);
|
||||||
num_of_devices += 15; // Incase new devices were connected between this call and the next
|
num_of_devices += 15; // Incase new devices were connected between this call and the next
|
||||||
@ -175,7 +171,7 @@ static PyMethodDef wpd_methods[] = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
{"enumerate_devices", wpd_enumerate_devices, METH_VARARGS,
|
{"enumerate_devices", wpd_enumerate_devices, METH_VARARGS,
|
||||||
"enumerate_devices(refresh=False)\n\n Get the list of device PnP ids for all connected devices recognized by the WPD service. The result is cached, unless refresh=True. Do not call with refresh=True too often as it is resource intensive."
|
"enumerate_devices()\n\n Get the list of device PnP ids for all connected devices recognized by the WPD service. Do not call too often as it is resource intensive."
|
||||||
},
|
},
|
||||||
|
|
||||||
{"device_info", wpd_device_info, METH_VARARGS,
|
{"device_info", wpd_device_info, METH_VARARGS,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user