Skip to content

Commit cd60510

Browse files
author
Grégoire Sage
committed
Merge remote-tracking branch 'upstream/master'
2 parents 7c9b2ac + 91c89d8 commit cd60510

File tree

20 files changed

+682
-252
lines changed

20 files changed

+682
-252
lines changed

pebble_tool/__init__.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@
77
import sys
88
import requests.packages.urllib3 as urllib3
99

10+
from .exceptions import ToolError
11+
from .sdk import sdk_version
12+
from .util.analytics import wait_for_analytics, analytics_prompt
13+
from .util.config import config
14+
from .util.updates import wait_for_update_checks
15+
from .util.wsl import maybe_apply_wsl_hacks
16+
from .version import __version__, __version_info__
1017

1118
# Violating PEP8 for custom command ordering for `pebble -h`
1219
from .commands.sdk import manage
@@ -17,17 +24,11 @@
1724
transcription_server, data_logging)
1825
from .commands.sdk import create, emulator
1926
from .commands.sdk.project import package, analyse_size, convert, debug
20-
from .exceptions import ToolError
21-
from .sdk import sdk_version
22-
from .util.analytics import wait_for_analytics, analytics_prompt
23-
from .util.config import config
24-
from .util.updates import wait_for_update_checks
25-
from .version import __version__, __version_info__
26-
2727

2828
def run_tool(args=None):
2929
urllib3.disable_warnings() # sigh. :(
3030
logging.basicConfig()
31+
maybe_apply_wsl_hacks()
3132
analytics_prompt()
3233
parser = argparse.ArgumentParser(description="Pebble Tool", prog="pebble",
3334
epilog="For help on an individual command, call that command with --help.")
@@ -42,7 +43,7 @@ def run_tool(args=None):
4243
try:
4344
args.func(args)
4445
except ToolError as e:
45-
parser.exit(message=str(e)+"\n", status=1)
46+
parser.exit(message=unicode(e)+"\n", status=1)
4647
sys.exit(1)
4748

4849

pebble_tool/commands/base.py

Lines changed: 192 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -70,37 +70,34 @@ def _set_debugging(self, level):
7070

7171

7272
class PebbleCommand(BaseCommand):
73-
valid_connections = {'phone', 'qemu', 'cloudpebble', 'emulator', 'serial'}
73+
connection_handlers = set()
74+
75+
@classmethod
76+
def register_connection_handler(cls, impl):
77+
cls.connection_handlers.add(impl)
7478

7579
@classmethod
7680
def _shared_parser(cls):
7781
parser = argparse.ArgumentParser(add_help=False)
78-
if len(cls.valid_connections) < 2 :
79-
group = parser
80-
else :
82+
handlers = cls.valid_connection_handlers()
83+
# Having a group inside a mutually exclusive group breaks --help unless there are is
84+
# something for it to be mutually exlusive with.
85+
if len(handlers) > 1:
8186
group = parser.add_mutually_exclusive_group()
82-
if 'phone' in cls.valid_connections:
83-
group.add_argument('--phone', metavar='phone_ip',
84-
help="When using the developer connection, your phone's IP or hostname. "
85-
"Equivalent to PEBBLE_PHONE.")
86-
if 'qemu' in cls.valid_connections:
87-
group.add_argument('--qemu', nargs='?', const='localhost:12344', metavar='host',
88-
help="Use this option to connect directly to a QEMU instance. "
89-
"Equivalent to PEBBLE_QEMU.")
90-
if 'cloudpebble' in cls.valid_connections:
91-
group.add_argument('--cloudpebble', action='store_true', help="Use this option to connect to your phone via"
92-
" the CloudPebble connection. Equivalent to "
93-
"PEBBLE_CLOUDPEBBLE.")
94-
if 'emulator' in cls.valid_connections:
95-
emu_group = group.add_argument_group()
96-
emu_group.add_argument('--emulator', type=str, help="Launch an emulator. Equivalent to PEBBLE_EMULATOR.",
97-
choices=pebble_platforms)
98-
emu_group.add_argument('--sdk', type=str, help="SDK version to launch. Defaults to the active SDK"
99-
" (currently {})".format(sdk_version()))
100-
if 'serial' in cls.valid_connections:
101-
group.add_argument('--serial', type=str, help="Connected directly, given a path to a serial device.")
87+
else:
88+
group = parser
89+
for handler_impl in handlers:
90+
handler_impl.add_argument_handler(group)
10291
return super(PebbleCommand, cls)._shared_parser() + [parser]
10392

93+
@classmethod
94+
def valid_connection_handlers(cls):
95+
valid_connections = getattr(cls, 'valid_connections', None)
96+
if not valid_connections:
97+
return cls.connection_handlers
98+
99+
return set([handler for handler in cls.connection_handlers if handler.name in valid_connections])
100+
104101
def __call__(self, args):
105102
super(PebbleCommand, self).__call__(args)
106103
try:
@@ -110,107 +107,199 @@ def __call__(self, args):
110107

111108
def _connect(self, args):
112109
self._set_debugging(args.v)
113-
if getattr(args, 'phone', None):
114-
return self._connect_phone(args.phone)
115-
elif getattr(args, 'qemu', None):
116-
return self._connect_qemu(args.qemu)
117-
elif getattr(args, 'emulator', None):
118-
return self._connect_emulator(args.emulator, args.sdk)
119-
elif getattr(args, 'cloudpebble', None):
120-
return self._connect_cloudpebble()
121-
elif getattr(args, 'serial', None):
122-
return self._connect_serial(args.serial)
110+
for handler_impl in self.valid_connection_handlers():
111+
if handler_impl.is_selected(args):
112+
break
123113
else:
124-
if 'phone' in self.valid_connections and 'PEBBLE_PHONE' in os.environ:
125-
return self._connect_phone(os.environ['PEBBLE_PHONE'])
126-
elif 'qemu' in self.valid_connections and 'PEBBLE_QEMU' in os.environ:
127-
return self._connect_qemu(os.environ['PEBBLE_QEMU'])
128-
elif 'cloudpebble' in self.valid_connections and os.environ.get('PEBBLE_CLOUDPEBBLE', False):
129-
return self._connect_cloudpebble()
130-
elif 'serial' in self.valid_connections and 'PEBBLE_BT_SERIAL' in os.environ:
131-
return self._connect_serial(os.environ['PEBBLE_BT_SERIAL'])
132-
elif 'emulator' in self.valid_connections:
133-
running = []
134-
emulator_platform = None
135-
emulator_sdk = None
136-
if 'PEBBLE_EMULATOR' in os.environ:
137-
emulator_platform = os.environ['PEBBLE_EMULATOR']
138-
if emulator_platform not in pebble_platforms:
139-
raise ToolError("PEBBLE_EMULATOR is set to '{}', which is not a valid platform "
140-
"(pick from {})".format(emulator_platform, ', '.join(pebble_platforms)))
141-
emulator_sdk = os.environ.get('PEBBLE_EMULATOR_VERSION', sdk_version())
142-
else:
143-
for platform, sdks in get_all_emulator_info().items():
144-
for sdk in sdks:
145-
if ManagedEmulatorTransport.is_emulator_alive(platform, sdk):
146-
running.append((platform, sdk))
147-
if len(running) == 1:
148-
emulator_platform, emulator_sdk = running[0]
149-
elif len(running) > 1:
150-
raise ToolError("Multiple emulators are running; you must specify which to use.")
151-
if emulator_platform is not None:
152-
return self._connect_emulator(emulator_platform, emulator_sdk)
153-
raise ToolError("No pebble connection specified.")
154-
155-
def _connect_phone(self, phone):
114+
# No selected transport, fallback to a running emulator if available
115+
if PebbleTransportEmulator.get_running_emulators():
116+
handler_impl = PebbleTransportEmulator
117+
else:
118+
raise ToolError("No pebble connection specified.")
119+
120+
transport = handler_impl.get_transport(args)
121+
connection = PebbleConnection(transport, **self._get_debug_args())
122+
connection.connect()
123+
connection.run_async()
124+
handler_impl.post_connect(connection)
125+
return connection
126+
127+
def _get_debug_args(self):
128+
args = {}
129+
if self._verbosity >= 3:
130+
args['log_packet_level'] = logging.DEBUG
131+
if self._verbosity >= 4:
132+
args['log_protocol_level'] = logging.DEBUG
133+
return args
134+
135+
136+
class SelfRegisteringTransportConfiguration(type):
137+
def __init__(cls, name, bases, dct):
138+
if hasattr(cls, 'name') and cls.name is not None:
139+
PebbleCommand.register_connection_handler(cls)
140+
super(SelfRegisteringTransportConfiguration, cls).__init__(name, bases, dct)
141+
142+
143+
class PebbleTransportConfiguration(with_metaclass(SelfRegisteringTransportConfiguration)):
144+
transport_class = None
145+
env_var = None
146+
name = None
147+
148+
@classmethod
149+
def _config_env_var(cls):
150+
env_var_name = cls.env_var if cls.env_var else 'PEBBLE_%s' % cls.name.upper()
151+
return os.environ.get(env_var_name)
152+
153+
@classmethod
154+
def is_selected(cls, args):
155+
return getattr(args, cls.name, None) or cls._config_env_var()
156+
157+
@classmethod
158+
def _connect_args(cls, args):
159+
arg_val = getattr(args, cls.name, None)
160+
if arg_val:
161+
return (arg_val,)
162+
163+
env_val = cls._config_env_var()
164+
if env_val:
165+
return (env_val,)
166+
167+
@classmethod
168+
def get_transport(cls, args):
169+
return cls.transport_class(*cls._connect_args(args))
170+
171+
@classmethod
172+
def add_argument_handler(cls):
173+
raise NotImplementedError
174+
175+
@classmethod
176+
def post_connect(cls, connection):
177+
pass
178+
179+
180+
class PebbleTransportSerial(PebbleTransportConfiguration):
181+
transport_class = SerialTransport
182+
env_var = 'PEBBLE_BT_SERIAL'
183+
name = 'serial'
184+
185+
@classmethod
186+
def add_argument_handler(cls, parser):
187+
parser.add_argument('--serial', type=str, help="Connected directly, given a path to a serial device.")
188+
189+
190+
class PebbleTransportPhone(PebbleTransportConfiguration):
191+
transport_class = WebsocketTransport
192+
name = 'phone'
193+
194+
@classmethod
195+
def _connect_args(cls, args):
196+
phone, = super(PebbleTransportPhone, cls)._connect_args(args)
156197
parts = phone.split(':')
157198
ip = parts[0]
158199
if len(parts) == 2:
159200
port = int(parts[1])
160201
else:
161202
port = 9000
162-
connection = PebbleConnection(WebsocketTransport("ws://{}:{}/".format(ip, port)), **self._get_debug_args())
163-
connection.connect()
164-
connection.run_async()
165-
return connection
166203

167-
def _connect_qemu(self, qemu):
168-
parts = qemu.split(':')
204+
return ("ws://{}:{}/".format(ip, port),)
205+
206+
@classmethod
207+
def add_argument_handler(cls, parser):
208+
parser.add_argument('--phone', metavar='phone_ip',
209+
help="When using the developer connection, your phone's IP or hostname. "
210+
"Equivalent to PEBBLE_PHONE.")
211+
212+
213+
class PebbleTransportQemu(PebbleTransportConfiguration):
214+
transport_class = QemuTransport
215+
name = 'qemu'
216+
217+
@classmethod
218+
def _connect_args(cls, args):
219+
phone, = super(PebbleTransportQemu, cls)._connect_args(args)
220+
parts = phone.split(':')
169221
ip = parts[0]
170-
if not ip:
171-
ip = '127.0.0.1'
172222
if len(parts) == 2:
173223
port = int(parts[1])
174224
else:
175225
port = 12344
176-
connection = PebbleConnection(QemuTransport(ip, port), **self._get_debug_args())
177-
connection.connect()
178-
connection.run_async()
179-
return connection
180226

181-
def _connect_emulator(self, platform, sdk):
182-
connection = PebbleConnection(ManagedEmulatorTransport(platform, sdk), **self._get_debug_args())
183-
connection.connect()
184-
connection.run_async()
227+
return (ip, port,)
228+
229+
@classmethod
230+
def add_argument_handler(cls, parser):
231+
parser.add_argument('--qemu', nargs='?', const='localhost:12344', metavar='host',
232+
help="Use this option to connect directly to a QEMU instance. "
233+
"Equivalent to PEBBLE_QEMU.")
234+
235+
236+
class PebbleTransportCloudPebble(PebbleTransportConfiguration):
237+
transport_class = CloudPebbleTransport
238+
name = 'cloudpebble'
239+
240+
@classmethod
241+
def _connect_args(cls, args):
242+
return ()
243+
244+
@classmethod
245+
def add_argument_handler(cls, parser):
246+
parser.add_argument('--cloudpebble', action='store_true',
247+
help="Use this option to connect to your phone via"
248+
" the CloudPebble connection. Equivalent to "
249+
"PEBBLE_CLOUDPEBBLE.")
250+
251+
252+
class PebbleTransportEmulator(PebbleTransportConfiguration):
253+
transport_class = ManagedEmulatorTransport
254+
name = 'emulator'
255+
256+
@classmethod
257+
def get_running_emulators(cls):
258+
running = []
259+
for platform, sdks in get_all_emulator_info().items():
260+
for sdk in sdks:
261+
if ManagedEmulatorTransport.is_emulator_alive(platform, sdk):
262+
running.append((platform, sdk))
263+
return running
264+
265+
@classmethod
266+
def _connect_args(cls, args):
267+
emulator_platform = getattr(args, 'emulator', None)
268+
emulator_sdk = getattr(args, 'sdk', None)
269+
if emulator_platform:
270+
return emulator_platform, emulator_sdk
271+
elif 'PEBBLE_EMULATOR' in os.environ:
272+
emulator_platform = os.environ['PEBBLE_EMULATOR']
273+
if emulator_platform not in pebble_platforms:
274+
raise ToolError("PEBBLE_EMULATOR is set to '{}', which is not a valid platform "
275+
"(pick from {})".format(emulator_platform, ', '.join(pebble_platforms)))
276+
emulator_sdk = os.environ.get('PEBBLE_EMULATOR_VERSION', sdk_version())
277+
else:
278+
running = cls.get_running_emulators()
279+
if len(running) == 1:
280+
emulator_platform, emulator_sdk = running[0]
281+
elif len(running) > 1:
282+
raise ToolError("Multiple emulators are running; you must specify which to use.")
283+
284+
return (emulator_platform, emulator_sdk)
285+
286+
@classmethod
287+
def post_connect(cls, connection):
185288
# Make sure the timezone is set usefully.
186289
if connection.firmware_version.major >= 3:
187290
ts = time.time()
188291
tz_offset = -time.altzone if time.localtime(ts).tm_isdst and time.daylight else -time.timezone
189292
tz_offset_minutes = tz_offset // 60
190293
tz_name = "UTC%+d" % (tz_offset_minutes / 60)
191294
connection.send_packet(TimeMessage(message=SetUTC(unix_time=ts, utc_offset=tz_offset_minutes, tz_name=tz_name)))
192-
return connection
193-
194-
def _connect_cloudpebble(self):
195-
connection = PebbleConnection(CloudPebbleTransport(), **self._get_debug_args())
196-
connection.connect()
197-
connection.run_async()
198-
return connection
199-
200-
def _connect_serial(self, device):
201-
connection = PebbleConnection(SerialTransport(device), **self._get_debug_args())
202-
connection.connect()
203-
connection.run_async()
204-
return connection
205-
206-
def _get_debug_args(self):
207-
args = {}
208-
if self._verbosity >= 3:
209-
args['log_packet_level'] = logging.DEBUG
210-
if self._verbosity >= 4:
211-
args['log_protocol_level'] = logging.DEBUG
212-
return args
213295

296+
@classmethod
297+
def add_argument_handler(cls, parser):
298+
emu_group = parser.add_argument_group()
299+
emu_group.add_argument('--emulator', type=str, help="Launch an emulator. Equivalent to PEBBLE_EMULATOR.",
300+
choices=pebble_platforms)
301+
emu_group.add_argument('--sdk', type=str, help="SDK version to launch. Defaults to the active SDK"
302+
" (currently {})".format(sdk_version()))
214303

215304
def register_children(parser):
216305
subparsers = parser.add_subparsers(title="command")

pebble_tool/commands/emucontrol.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ def add_parser(cls, parser):
280280

281281

282282
class EmuSetTimelinePeekCommand(PebbleCommand):
283-
command = 'emu-set-timeline-peek'
283+
command = 'emu-set-timeline-quick-view'
284284
valid_connections = {'qemu', 'emulator'}
285285

286286
def __call__(self, args):
@@ -291,4 +291,4 @@ def __call__(self, args):
291291
@classmethod
292292
def add_parser(cls, parser):
293293
parser = super(EmuSetTimelinePeekCommand, cls).add_parser(parser)
294-
parser.add_argument('state', choices=['on', 'off'], help="Set whether a peek is visible.")
294+
parser.add_argument('state', choices=['on', 'off'], help="Set whether a timeline quick view is visible.")

0 commit comments

Comments
 (0)