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,7 +298,8 @@ class Build(Command):
|
||||
self.obj_dir = os.path.join(os.path.dirname(SRC), 'build', 'objects')
|
||||
if not os.path.exists(self.obj_dir):
|
||||
os.makedirs(self.obj_dir)
|
||||
self.build_style(self.j(self.SRC, 'calibre', 'plugins'))
|
||||
if not opts.only:
|
||||
self.build_style(self.j(self.SRC, 'calibre', 'plugins'))
|
||||
for ext in extensions:
|
||||
if opts.only != 'all' and opts.only != ext.name:
|
||||
continue
|
||||
|
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,78 +7,67 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import subprocess, sys, os, pprint
|
||||
import subprocess, sys, os, pprint, signal, time, glob
|
||||
pprint
|
||||
|
||||
def build(mod='wpd'):
|
||||
builder = subprocess.Popen('ssh xp_build ~/build-wpd'.split())
|
||||
syncer = subprocess.Popen('ssh getafix ~/test-wpd'.split())
|
||||
if builder.wait() != 0:
|
||||
raise Exception('Failed to build plugin')
|
||||
if syncer.wait() != 0:
|
||||
raise Exception('Failed to rsync to getafix')
|
||||
subprocess.check_call(
|
||||
('scp xp_build:build/calibre/src/calibre/plugins/%s.pyd /tmp'%mod).split())
|
||||
subprocess.check_call(
|
||||
('scp /tmp/%s.pyd getafix:calibre/src/calibre/devices/mtp/windows'%mod).split())
|
||||
p = subprocess.Popen(
|
||||
'ssh getafix calibre-debug -e calibre/src/calibre/devices/mtp/windows/remote.py'.split())
|
||||
p.wait()
|
||||
print()
|
||||
|
||||
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())
|
||||
if builder.wait() != 0:
|
||||
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:
|
||||
raise Exception('Failed to rsync to getafix')
|
||||
subprocess.check_call(
|
||||
('scp xp_build:build/calibre/src/calibre/plugins/%s.pyd /tmp'%mod).split())
|
||||
subprocess.check_call(
|
||||
('scp /tmp/%s.pyd getafix:calibre/src/calibre/devices/mtp/windows'%mod).split())
|
||||
p = subprocess.Popen(
|
||||
'ssh getafix calibre-debug -e calibre/src/calibre/devices/mtp/windows/remote.py'.split())
|
||||
p.wait()
|
||||
print()
|
||||
finally:
|
||||
for m in (master2, master):
|
||||
m.send_signal(signal.SIGHUP)
|
||||
for m in (master2, master):
|
||||
m.wait()
|
||||
|
||||
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
|
||||
from calibre.constants import plugins
|
||||
plugins._plugins['wpd'] = (wpd, '')
|
||||
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:
|
||||
for pnp_id in wpd.enumerate_devices():
|
||||
print (pnp_id)
|
||||
pprint.pprint(wpd.device_info(pnp_id))
|
||||
devices = win_scanner()
|
||||
pnp_id = dev.detect_managed_devices(devices)
|
||||
# pprint.pprint(dev.detected_devices)
|
||||
print ('Trying to connect to:', pnp_id)
|
||||
finally:
|
||||
wpd.uninit()
|
||||
|
||||
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)))
|
||||
dev.shutdown()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
# winutil()
|
||||
|
||||
|
@ -92,14 +92,10 @@ wpd_enumerate_devices(PyObject *self, PyObject *args) {
|
||||
|
||||
ENSURE_WPD(NULL);
|
||||
|
||||
if (!PyArg_ParseTuple(args, "|O", &refresh)) return NULL;
|
||||
|
||||
if (refresh != NULL && PyObject_IsTrue(refresh)) {
|
||||
Py_BEGIN_ALLOW_THREADS;
|
||||
hr = portable_device_manager->RefreshDeviceList();
|
||||
Py_END_ALLOW_THREADS;
|
||||
if (FAILED(hr)) return hresult_set_exc("Failed to refresh the list of portable devices", hr);
|
||||
}
|
||||
Py_BEGIN_ALLOW_THREADS;
|
||||
hr = portable_device_manager->RefreshDeviceList();
|
||||
Py_END_ALLOW_THREADS;
|
||||
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);
|
||||
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(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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user