From 694133d8f1613a671503a19ce0f4ef4664c872b2 Mon Sep 17 00:00:00 2001 From: Theodoros Tsiridis Date: Wed, 20 Aug 2025 09:39:46 +0200 Subject: [PATCH 1/2] Add arrow key navigation commands for HID - Add ArrowUpCommand, ArrowDownCommand, ArrowLeftCommand, ArrowRightCommand - Integrate arrow key commands into CLI main interface - Add arrow key helper functions in common.hid module - Add arrow key methods to gRPC client - Fix double semicolon syntax error in FBiOSTargetProvider - Remove outdated parameter documentation in FBArchiveOperations - Make simulator headers public in Xcode project --- CompanionLib/Utility/FBiOSTargetProvider.m | 2 +- FBControlCore/Utility/FBArchiveOperations.h | 1 - FBSimulatorControl.xcodeproj/project.pbxproj | 4 +- FBSimulatorControl/FBSimulatorControl.h | 1 + idb/cli/commands/hid.py | 68 ++++++++++++++++++++ idb/cli/main.py | 8 +++ idb/common/hid.py | 16 +++++ idb/grpc/client.py | 20 ++++++ 8 files changed, 116 insertions(+), 4 deletions(-) diff --git a/CompanionLib/Utility/FBiOSTargetProvider.m b/CompanionLib/Utility/FBiOSTargetProvider.m index cd9c3c5cd..68a8e183f 100644 --- a/CompanionLib/Utility/FBiOSTargetProvider.m +++ b/CompanionLib/Utility/FBiOSTargetProvider.m @@ -39,7 +39,7 @@ @implementation FBiOSTargetProvider } id lifecycle = (id) target; if (![lifecycle conformsToProtocol:@protocol(FBSimulatorLifecycleCommands)]) { - return [FBFuture futureWithResult:target];; + return [FBFuture futureWithResult:target]; } if (FBXcodeConfiguration.isXcode12_5OrGreater) { diff --git a/FBControlCore/Utility/FBArchiveOperations.h b/FBControlCore/Utility/FBArchiveOperations.h index 0629aed5b..2c2b78209 100644 --- a/FBControlCore/Utility/FBArchiveOperations.h +++ b/FBControlCore/Utility/FBArchiveOperations.h @@ -97,7 +97,6 @@ typedef NS_ENUM(NSUInteger, FBCompressionFormat) { To confirm that the stream has been correctly written, the caller should check the exit code of the returned task upon completion. @param path the path to archive. - @param queue the queue to do work on @param logger the logger to log to. @return a Future containing a task with an NSInputStream attached to stdout. */ diff --git a/FBSimulatorControl.xcodeproj/project.pbxproj b/FBSimulatorControl.xcodeproj/project.pbxproj index cbb0ae6db..ab9623d61 100644 --- a/FBSimulatorControl.xcodeproj/project.pbxproj +++ b/FBSimulatorControl.xcodeproj/project.pbxproj @@ -62,7 +62,7 @@ AA0CA38720643CCF00347424 /* FBCrashLogCommands.h in Headers */ = {isa = PBXBuildFile; fileRef = AA0CA38620643C6800347424 /* FBCrashLogCommands.h */; settings = {ATTRIBUTES = (Public, ); }; }; AA111CCE1BBE7C5A0054AFDD /* CoreSimulatorDoubles.m in Sources */ = {isa = PBXBuildFile; fileRef = AA111CCD1BBE7C5A0054AFDD /* CoreSimulatorDoubles.m */; }; AA1174B21CEA17DB00EB699E /* FBApplicationCommands.h in Headers */ = {isa = PBXBuildFile; fileRef = AA1174B11CEA17DB00EB699E /* FBApplicationCommands.h */; settings = {ATTRIBUTES = (Public, ); }; }; - AA1174B61CEA183F00EB699E /* FBSimulatorApplicationCommands.h in Headers */ = {isa = PBXBuildFile; fileRef = AA1174B41CEA183F00EB699E /* FBSimulatorApplicationCommands.h */; }; + AA1174B61CEA183F00EB699E /* FBSimulatorApplicationCommands.h in Headers */ = {isa = PBXBuildFile; fileRef = AA1174B41CEA183F00EB699E /* FBSimulatorApplicationCommands.h */; settings = {ATTRIBUTES = (Public, ); }; }; AA1174B71CEA183F00EB699E /* FBSimulatorApplicationCommands.m in Sources */ = {isa = PBXBuildFile; fileRef = AA1174B51CEA183F00EB699E /* FBSimulatorApplicationCommands.m */; }; AA11EDF9272C44410055D6D1 /* FBFileContainerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AA11EDF8272C44410055D6D1 /* FBFileContainerTests.m */; }; AA12D5441CE1086300CCD944 /* FBTestReporterAdapter.h in Headers */ = {isa = PBXBuildFile; fileRef = AA12D5421CE1086300CCD944 /* FBTestReporterAdapter.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -128,7 +128,7 @@ AA3C18441D5DE47D00419EAA /* CoreImage.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA3C18431D5DE47D00419EAA /* CoreImage.framework */; }; AA3E44401F14AE2C00F333D2 /* FBDeviceApplicationCommands.h in Headers */ = {isa = PBXBuildFile; fileRef = AA3E443E1F14AE2C00F333D2 /* FBDeviceApplicationCommands.h */; }; AA3E44411F14AE2C00F333D2 /* FBDeviceApplicationCommands.m in Sources */ = {isa = PBXBuildFile; fileRef = AA3E443F1F14AE2C00F333D2 /* FBDeviceApplicationCommands.m */; }; - AA3EA8531F31B20D003FBDC1 /* FBSimulatorFileCommands.h in Headers */ = {isa = PBXBuildFile; fileRef = AA3EA8511F31B20D003FBDC1 /* FBSimulatorFileCommands.h */; }; + AA3EA8531F31B20D003FBDC1 /* FBSimulatorFileCommands.h in Headers */ = {isa = PBXBuildFile; fileRef = AA3EA8511F31B20D003FBDC1 /* FBSimulatorFileCommands.h */; settings = {ATTRIBUTES = (Public, ); }; }; AA3EA8541F31B20D003FBDC1 /* FBSimulatorFileCommands.m in Sources */ = {isa = PBXBuildFile; fileRef = AA3EA8521F31B20D003FBDC1 /* FBSimulatorFileCommands.m */; }; AA3EA8561F31B494003FBDC1 /* FBSimulatorApplicationDataTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AA3EA8551F31B494003FBDC1 /* FBSimulatorApplicationDataTests.m */; }; AA3FD04D1C876E4F001093CA /* FBSimulatorLaunchTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AA3FD03D1C876E4F001093CA /* FBSimulatorLaunchTests.m */; }; diff --git a/FBSimulatorControl/FBSimulatorControl.h b/FBSimulatorControl/FBSimulatorControl.h index 53cb83e78..c1c42ee8f 100644 --- a/FBSimulatorControl/FBSimulatorControl.h +++ b/FBSimulatorControl/FBSimulatorControl.h @@ -9,6 +9,7 @@ #import #import #import +#import #import #import #import diff --git a/idb/cli/commands/hid.py b/idb/cli/commands/hid.py index f3df9dc82..5c7db86c1 100644 --- a/idb/cli/commands/hid.py +++ b/idb/cli/commands/hid.py @@ -154,3 +154,71 @@ async def run_with_client(self, args: Namespace, client: Client) -> None: duration=args.duration, delta=args.delta, ) + + +class ArrowUpCommand(ClientCommand): + @property + def description(self) -> str: + return "Press arrow up key for navigation" + + @property + def name(self) -> str: + return "arrow-up" + + def add_parser_arguments(self, parser: ArgumentParser) -> None: + parser.add_argument("--duration", help="Press duration", type=float) + super().add_parser_arguments(parser) + + async def run_with_client(self, args: Namespace, client: Client) -> None: + await client.key(keycode=82, duration=args.duration) + + +class ArrowDownCommand(ClientCommand): + @property + def description(self) -> str: + return "Press arrow down key for navigation" + + @property + def name(self) -> str: + return "arrow-down" + + def add_parser_arguments(self, parser: ArgumentParser) -> None: + parser.add_argument("--duration", help="Press duration", type=float) + super().add_parser_arguments(parser) + + async def run_with_client(self, args: Namespace, client: Client) -> None: + await client.key(keycode=81, duration=args.duration) + + +class ArrowLeftCommand(ClientCommand): + @property + def description(self) -> str: + return "Press arrow left key for navigation" + + @property + def name(self) -> str: + return "arrow-left" + + def add_parser_arguments(self, parser: ArgumentParser) -> None: + parser.add_argument("--duration", help="Press duration", type=float) + super().add_parser_arguments(parser) + + async def run_with_client(self, args: Namespace, client: Client) -> None: + await client.key(keycode=80, duration=args.duration) + + +class ArrowRightCommand(ClientCommand): + @property + def description(self) -> str: + return "Press arrow right key for navigation" + + @property + def name(self) -> str: + return "arrow-right" + + def add_parser_arguments(self, parser: ArgumentParser) -> None: + parser.add_argument("--duration", help="Press duration", type=float) + super().add_parser_arguments(parser) + + async def run_with_client(self, args: Namespace, client: Client) -> None: + await client.key(keycode=79, duration=args.duration) diff --git a/idb/cli/main.py b/idb/cli/main.py index 6290a324d..1d5607e4e 100644 --- a/idb/cli/main.py +++ b/idb/cli/main.py @@ -56,6 +56,10 @@ from idb.cli.commands.focus import FocusCommand from idb.cli.commands.framework import FrameworkInstallCommand from idb.cli.commands.hid import ( + ArrowDownCommand, + ArrowLeftCommand, + ArrowRightCommand, + ArrowUpCommand, ButtonCommand, KeyCommand, KeySequenceCommand, @@ -256,6 +260,10 @@ async def gen_main(cmd_input: list[str] | None = None) -> SysExitArg: KeyCommand(), KeySequenceCommand(), SwipeCommand(), + ArrowUpCommand(), + ArrowDownCommand(), + ArrowLeftCommand(), + ArrowRightCommand(), ], ), CommandGroup( diff --git a/idb/common/hid.py b/idb/common/hid.py index 3d26cdc8b..01c18680a 100644 --- a/idb/common/hid.py +++ b/idb/common/hid.py @@ -187,6 +187,22 @@ def text_to_events(text: str) -> list[HIDEvent]: return events +def arrow_up_to_events(duration: float | None = None) -> list[HIDEvent]: + return key_press_to_events(keycode=82, duration=duration) + + +def arrow_down_to_events(duration: float | None = None) -> list[HIDEvent]: + return key_press_to_events(keycode=81, duration=duration) + + +def arrow_left_to_events(duration: float | None = None) -> list[HIDEvent]: + return key_press_to_events(keycode=80, duration=duration) + + +def arrow_right_to_events(duration: float | None = None) -> list[HIDEvent]: + return key_press_to_events(keycode=79, duration=duration) + + async def iterator_to_async_iterator( events: Iterable[HIDEvent], ) -> AsyncIterator[HIDEvent]: diff --git a/idb/grpc/client.py b/idb/grpc/client.py index 40c1841c4..dcf13a7b2 100644 --- a/idb/grpc/client.py +++ b/idb/grpc/client.py @@ -29,6 +29,10 @@ from idb.common.format import json_format_debugger_info from idb.common.gzip import drain_gzip_decompress, gunzip from idb.common.hid import ( + arrow_down_to_events, + arrow_left_to_events, + arrow_right_to_events, + arrow_up_to_events, button_press_to_events, iterator_to_async_iterator, key_press_to_events, @@ -873,6 +877,22 @@ async def key(self, keycode: int, duration: float | None = None) -> None: async def text(self, text: str) -> None: await self.send_events(text_to_events(text)) + @log_and_handle_exceptions("hid") + async def arrow_up(self, duration: float | None = None) -> None: + await self.send_events(arrow_up_to_events(duration)) + + @log_and_handle_exceptions("hid") + async def arrow_down(self, duration: float | None = None) -> None: + await self.send_events(arrow_down_to_events(duration)) + + @log_and_handle_exceptions("hid") + async def arrow_left(self, duration: float | None = None) -> None: + await self.send_events(arrow_left_to_events(duration)) + + @log_and_handle_exceptions("hid") + async def arrow_right(self, duration: float | None = None) -> None: + await self.send_events(arrow_right_to_events(duration)) + @log_and_handle_exceptions("hid") async def swipe( self, From 41e300c1ff26b87abd31b28b9463138d80f4768e Mon Sep 17 00:00:00 2001 From: Theodoros Tsiridis Date: Wed, 20 Aug 2025 09:51:33 +0200 Subject: [PATCH 2/2] Adds missing types --- idb/common/types.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/idb/common/types.py b/idb/common/types.py index 3e804e63d..f5dc82409 100644 --- a/idb/common/types.py +++ b/idb/common/types.py @@ -738,6 +738,22 @@ async def debugserver_status(self) -> list[str] | None: async def text(self, text: str) -> None: return + @abstractmethod + async def arrow_up(self, duration: float | None = None) -> None: + return + + @abstractmethod + async def arrow_down(self, duration: float | None = None) -> None: + return + + @abstractmethod + async def arrow_left(self, duration: float | None = None) -> None: + return + + @abstractmethod + async def arrow_right(self, duration: float | None = None) -> None: + return + @abstractmethod async def hid(self, event_iterator: AsyncIterable[HIDEvent]) -> None: pass