diff --git a/README.md b/README.md index 43c6c95..bcbe2fe 100644 --- a/README.md +++ b/README.md @@ -25,22 +25,23 @@ The commands will be available the next time `Xcode` starts. ## Commands There are many commands; here's a few: - -|Command |Description| -|----------------------------------|-----------| -|pviews |Print the recursive view description for the key window.| -|pvc |Print the recursive view controller description for the key window.| -|visualize |Open a UIImage, CGImageRef, UIView, or CALayer in Preview.app on your Mac.| -|fv |Find a view in the hierarchy whose class name matches the provided regex.| -|fvc |Find a view controller in the hierarchy whose class name matches the provided regex.| -|show/hide |Show or hide the given view or layer. You don't even have to continue the process to see the changes!| -|mask/unmask |Overlay a view or layer with a transparent rectangle to visualize where it is.| -|border/unborder |Add a border to a view or layer to visualize where it is.| -|caflush |Flush the render server (equivalent to a "repaint" if no animations are in-flight).)| -|bmessage |Set a symbolic breakpoint on the method of a class or the method of an instance without worrying which class in the hierarchy actually implements the method.| -|wivar |Set a watchpoint on an instance variable of an object.| -|presponder |Print the responder chain starting from the given object.| -|... |... and many more!| +*(Compatibility with iOS/Mac indicated at right)* + +|Command |Description |iOS |OS X | +|-----------------|----------------|-------|-------| +|pviews |Print the recursive view description for the key window.|Yes|Yes| +|pvc |Print the recursive view controller description for the key window.|Yes|No| +|visualize |Open a UIImage, CGImageRef, UIView, or CALayer in Preview.app on your Mac.|Yes|No| +|fv |Find a view in the hierarchy whose class name matches the provided regex.|Yes|No| +|fvc |Find a view controller in the hierarchy whose class name matches the provided regex.|Yes|No| +|show/hide |Show or hide the given view or layer. You don't even have to continue the process to see the changes!|Yes|Yes| +|mask/unmask |Overlay a view or layer with a transparent rectangle to visualize where it is.|Yes|No| +|border/unborder |Add a border to a view or layer to visualize where it is.|Yes|Yes| +|caflush |Flush the render server (equivalent to a "repaint" if no animations are in-flight).)|Yes|Yes| +|bmessage |Set a symbolic breakpoint on the method of a class or the method of an instance without worrying which class in the hierarchy actually implements the method.|Yes|Yes| +|wivar |Set a watchpoint on an instance variable of an object.|Yes|Yes| +|presponder |Print the responder chain starting from the given object.|Yes|Yes| +|... |... and many more!| To see the list of **all** of the commands execute the help command in `LLDB`. diff --git a/commands/FBDisplayCommands.py b/commands/FBDisplayCommands.py index 6e7de40..d4d2abc 100644 --- a/commands/FBDisplayCommands.py +++ b/commands/FBDisplayCommands.py @@ -13,6 +13,7 @@ import fblldbviewhelpers as viewHelpers import fblldbbase as fb +import fblldbobjcruntimehelpers as runtimeHelpers def lldbcommands(): return [ @@ -34,7 +35,7 @@ def description(self): return 'Draws a border around . Color and width can be optionally provided.' def args(self): - return [ fb.FBCommandArgument(arg='viewOrLayer', type='UIView/CALayer *', help='The view/layer to border.') ] + return [ fb.FBCommandArgument(arg='viewOrLayer', type='UIView/NSView/CALayer *', help='The view/layer to border. NSViews must be layer-backed.') ] def options(self): return [ @@ -43,9 +44,15 @@ def options(self): ] def run(self, args, options): + colorClassName = 'UIColor' + isMac = runtimeHelpers.isMacintoshArch() + + if isMac: + colorClassName = 'NSColor' + layer = viewHelpers.convertToLayer(args[0]) lldb.debugger.HandleCommand('expr (void)[%s setBorderWidth:(CGFloat)%s]' % (layer, options.width)) - lldb.debugger.HandleCommand('expr (void)[%s setBorderColor:(CGColorRef)[(id)[UIColor %sColor] CGColor]]' % (layer, options.color)) + lldb.debugger.HandleCommand('expr (void)[%s setBorderColor:(CGColorRef)[(id)[%s %sColor] CGColor]]' % (layer, colorClassName, options.color)) lldb.debugger.HandleCommand('caflush') @@ -57,7 +64,7 @@ def description(self): return 'Removes border around .' def args(self): - return [ fb.FBCommandArgument(arg='viewOrLayer', type='UIView/CALayer *', help='The view/layer to unborder.') ] + return [ fb.FBCommandArgument(arg='viewOrLayer', type='UIView/NSView/CALayer *', help='The view/layer to unborder.') ] def run(self, args, options): layer = viewHelpers.convertToLayer(args[0]) @@ -73,7 +80,7 @@ def description(self): return 'Add a transparent rectangle to the window to reveal a possibly obscured or hidden view or layer\'s bounds' def args(self): - return [ fb.FBCommandArgument(arg='viewOrLayer', type='UIView/CALayer *', help='The view/layer to mask.') ] + return [ fb.FBCommandArgument(arg='viewOrLayer', type='UIView/NSView/CALayer *', help='The view/layer to mask.') ] def options(self): return [ @@ -120,7 +127,7 @@ def description(self): return 'Show a view or layer.' def args(self): - return [ fb.FBCommandArgument(arg='viewOrLayer', type='UIView/CALayer *', help='The view/layer to show.') ] + return [ fb.FBCommandArgument(arg='viewOrLayer', type='UIView/NSView/CALayer *', help='The view/layer to show.') ] def run(self, args, options): viewHelpers.setViewHidden(args[0], False) @@ -134,7 +141,7 @@ def description(self): return 'Hide a view or layer.' def args(self): - return [ fb.FBCommandArgument(arg='viewOrLayer', type='UIView/CALayer *', help='The view/layer to hide.') ] + return [ fb.FBCommandArgument(arg='viewOrLayer', type='UIView/NSView/CALayer *', help='The view/layer to hide.') ] def run(self, args, options): viewHelpers.setViewHidden(args[0], True) diff --git a/commands/FBFindCommands.py b/commands/FBFindCommands.py index 4dedbbe..9763c89 100644 --- a/commands/FBFindCommands.py +++ b/commands/FBFindCommands.py @@ -12,8 +12,8 @@ import lldb import fblldbbase as fb -import fblldbviewcontrollerhelpers as vcHelpers import fblldbobjcruntimehelpers as objc +import fblldbviewcontrollerhelpers as vcHelpers def lldbcommands(): return [ diff --git a/commands/FBFlickerCommands.py b/commands/FBFlickerCommands.py index d4605bf..0171c07 100644 --- a/commands/FBFlickerCommands.py +++ b/commands/FBFlickerCommands.py @@ -14,6 +14,7 @@ import fblldbbase as fb import fblldbviewhelpers as viewHelpers import fblldbinputhelpers as inputHelpers +import fblldbobjcruntimehelpers as runtimeHelpers def lldbcommands(): return [ @@ -30,7 +31,7 @@ def description(self): return 'Quickly show and hide a view to quickly help visualize where it is.' def args(self): - return [ fb.FBCommandArgument(arg='viewOrLayer', type='UIView*', help='The view to border.') ] + return [ fb.FBCommandArgument(arg='viewOrLayer', type='UIView/NSView*', help='The view to flicker.') ] def run(self, arguments, options): object = fb.evaluateObjectExpression(arguments[0]) @@ -107,7 +108,13 @@ def inputCallback(self, input): print '\nThere are no sibling views to this view.\n' self.setCurrentView(v) elif input == 'p': - lldb.debugger.HandleCommand('po [(id)' + oldView + ' recursiveDescription]') + recusionName = 'recursiveDescription' + isMac = runtimeHelpers.isMacintoshArch() + + if isMac: + recursionName = '_subtreeDescription' + + lldb.debugger.HandleCommand('po [(id)' + oldView + ' ' + recusionName + ']') else: print '\nI really have no idea what you meant by \'' + input + '\'... =\\\n' diff --git a/commands/FBPrintCommands.py b/commands/FBPrintCommands.py index 1213271..caf378e 100644 --- a/commands/FBPrintCommands.py +++ b/commands/FBPrintCommands.py @@ -14,6 +14,7 @@ import fblldbbase as fb import fblldbviewcontrollerhelpers as vcHelpers import fblldbviewhelpers as viewHelpers +import fblldbobjcruntimehelpers as runtimeHelpers def lldbcommands(): return [ @@ -44,10 +45,17 @@ def options(self): ] def args(self): - return [ fb.FBCommandArgument(arg='aView', type='UIView*', help='The view to print the description of.', default='(id)[[UIApplication sharedApplication] keyWindow]') ] + return [ fb.FBCommandArgument(arg='aView', type='UIView*/NSView*', help='The view to print the description of.', default='__keyWindow_dynamic__') ] def run(self, arguments, options): maxDepth = int(options.depth) + isMac = runtimeHelpers.isMacintoshArch() + + if (arguments[0] == '__keyWindow_dynamic__'): + arguments[0] = '(id)[[UIApplication sharedApplication] keyWindow]' + + if isMac: + arguments[0] = '(id)[[[[NSApplication sharedApplication] windows] objectAtIndex:0] contentView]' if options.upwards: view = arguments[0] @@ -57,7 +65,11 @@ def run(self, arguments, options): else: print 'Failed to walk view hierarchy. Make sure you pass a view, not any other kind of object or expression.' else: - description = fb.evaluateExpressionValue('(id)[' + arguments[0] + ' recursiveDescription]').GetObjectDescription() + printingMethod = 'recursiveDescription' + if (isMac): + printingMethod = '_subtreeDescription' + + description = fb.evaluateExpressionValue('(id)[' + arguments[0] + ' ' + printingMethod + ']').GetObjectDescription() if maxDepth > 0: separator = re.escape(" | ") prefixToRemove = separator * maxDepth + " " @@ -85,9 +97,16 @@ def description(self): return 'Print the recursion description of .' def args(self): - return [ fb.FBCommandArgument(arg='aViewController', type='UIViewController*', help='The view controller to print the description of.', default='(id)[(id)[[UIApplication sharedApplication] keyWindow] rootViewController]') ] + return [ fb.FBCommandArgument(arg='aViewController', type='UIViewController*', help='The view controller to print the description of.', default='__keyWindow_rootVC_dynamic__') ] def run(self, arguments, options): + isMac = runtimeHelpers.isMacintoshArch() + + if (arguments[0] == '__keyWindow_rootVC_dynamic__'): + arguments[0] = '(id)[(id)[[UIApplication sharedApplication] keyWindow] rootViewController]' + if (isMac): + arguments[0] = '(id)[[[[NSApplication sharedApplication] windows] objectAtIndex:0] contentViewController]' + print vcHelpers.viewControllerRecursiveDescription(arguments[0]) @@ -142,8 +161,8 @@ def args(self): def run(self, arguments, options): startResponder = arguments[0] - if not fb.evaluateBooleanExpression('(BOOL)[(id)' + startResponder + ' isKindOfClass:[UIResponder class]]'): - print 'Whoa, ' + startResponder + ' is not a UIResponder. =(' + if not fb.evaluateBooleanExpression('(BOOL)[(id)' + startResponder + ' isKindOfClass:[UIResponder class]]') and not fb.evaluateBooleanExpression('(BOOL)[(id)' + startResponder + ' isKindOfClass:[NSResponder class]]'): + print 'Whoa, ' + startResponder + ' is not a UI/NSResponder. =(' return _printIterative(startResponder, _responderChain) diff --git a/fblldbobjcruntimehelpers.py b/fblldbobjcruntimehelpers.py index 9897996..1b205db 100644 --- a/fblldbobjcruntimehelpers.py +++ b/fblldbobjcruntimehelpers.py @@ -9,7 +9,6 @@ import lldb import fblldbbase as fb -import re def objc_getClass(className): command = '(void*)objc_getClass("{}")'.format(className) @@ -42,6 +41,8 @@ def currentArch(): return arch def functionPreambleExpressionForSelf(): + import re + arch = currentArch() expressionForSelf = None if arch == 'i386': @@ -55,6 +56,8 @@ def functionPreambleExpressionForSelf(): return expressionForSelf def functionPreambleExpressionForObjectParameterAtIndex(parameterIndex): + import re + arch = currentArch() expresssion = None if arch == 'i386': @@ -73,3 +76,13 @@ def functionPreambleExpressionForObjectParameterAtIndex(parameterIndex): raise Exception("Current implementation can not return object at index greater than 1 for arm32") expresssion = '(id)$r' + str(parameterIndex + 2) return expresssion + +def isMacintoshArch(): + arch = currentArch() + if not arch == 'x86_64': + return False + + nsClassName ='NSApplication' + command = '(void*)objc_getClass("{}")'.format(nsClassName) + + return (fb.evaluateBooleanExpression(command + '!= nil')) diff --git a/fblldbviewcontrollerhelpers.py b/fblldbviewcontrollerhelpers.py index 28b6009..5b66d10 100644 --- a/fblldbviewcontrollerhelpers.py +++ b/fblldbviewcontrollerhelpers.py @@ -9,6 +9,7 @@ import lldb import fblldbbase as fb +import fblldbobjcruntimehelpers as runtimeHelpers def viewControllerRecursiveDescription(vc): return _recursiveViewControllerDescriptionWithPrefixAndChildPrefix(fb.evaluateObjectExpression(vc), '', '', '') @@ -28,6 +29,8 @@ def _viewControllerDescription(viewController): def _recursiveViewControllerDescriptionWithPrefixAndChildPrefix(vc, string, prefix, childPrefix): + isMac = runtimeHelpers.isMacintoshArch() + s = '%s%s%s\n' % (prefix, '' if prefix == '' else ' ', _viewControllerDescription(vc)) nextPrefix = childPrefix + ' |' @@ -39,11 +42,12 @@ def _recursiveViewControllerDescriptionWithPrefixAndChildPrefix(vc, string, pref viewController = fb.evaluateExpression('(id)[(id)[%s childViewControllers] objectAtIndex:%d]' % (vc, i)) s += _recursiveViewControllerDescriptionWithPrefixAndChildPrefix(viewController, string, nextPrefix, nextPrefix) - isModal = fb.evaluateBooleanExpression('%s != nil && ((id)[(id)[(id)%s presentedViewController] presentingViewController]) == %s' % (vc, vc, vc)) + if not isMac: + isModal = fb.evaluateBooleanExpression('%s != nil && ((id)[(id)[(id)%s presentedViewController] presentingViewController]) == %s' % (vc, vc, vc)) - if isModal: - modalVC = fb.evaluateObjectExpression('(id)[(id)%s presentedViewController]' % (vc)) - s += _recursiveViewControllerDescriptionWithPrefixAndChildPrefix(modalVC, string, childPrefix + ' *M' , nextPrefix) - s += '\n// \'*M\' means the view controller is presented modally.' + if isModal: + modalVC = fb.evaluateObjectExpression('(id)[(id)%s presentedViewController]' % (vc)) + s += _recursiveViewControllerDescriptionWithPrefixAndChildPrefix(modalVC, string, childPrefix + ' *M' , nextPrefix) + s += '\n// \'*M\' means the view controller is presented modally.' return string + s \ No newline at end of file diff --git a/fblldbviewhelpers.py b/fblldbviewhelpers.py index d229b25..be2d41f 100644 --- a/fblldbviewhelpers.py +++ b/fblldbviewhelpers.py @@ -53,10 +53,10 @@ def convertToLayer(viewOrLayer): elif fb.evaluateBooleanExpression('[(id)%s respondsToSelector:(SEL)@selector(layer)]' % viewOrLayer): return fb.evaluateExpression('(CALayer *)[%s layer]' % viewOrLayer) else: - raise Exception('Argument must be a CALayer or a UIView') + raise Exception('Argument must be a CALayer, UIView, or NSView.') def upwardsRecursiveDescription(view, maxDepth=0): - if not fb.evaluateBooleanExpression('[(id)%s isKindOfClass:(Class)[UIView class]]' % view): + if not fb.evaluateBooleanExpression('[(id)%s isKindOfClass:(Class)[UIView class]]' % view) and not fb.evaluateBooleanExpression('[(id)%s isKindOfClass:(Class)[NSView class]]' % view): return None currentView = view