Best Python code snippet using pyatom_python
AXClasses.py
Source:AXClasses.py
...307 modFlags = self._pressModifiers(modifiers)308 # Post the queued keypresses:309 self._postQueuedEvents()310 return modFlags311 def _releaseModifiers(self, modifiers, globally=False):312 """Release given modifiers (provided in list form).313 Parameters: modifiers list314 Returns: None315 """316 # Release them in reverse order from pressing them:317 modifiers.reverse()318 modFlags = self._pressModifiers(modifiers, pressed=False,319 globally=globally)320 return modFlags321 def _releaseModifierKeys(self, modifiers):322 """Release given modifier keys (provided in list form).323 Parameters: modifiers list324 Returns: Unsigned int representing flags to set325 """326 modFlags = self._releaseModifiers(modifiers)327 # Post the queued keypresses:328 self._postQueuedEvents()329 return modFlags330 @staticmethod331 def _isSingleCharacter(keychr):332 """Check whether given keyboard character is a single character.333 Parameters: key character which will be checked.334 Returns: True when given key character is a single character.335 """336 if not keychr:337 return False338 # Regular character case.339 if len(keychr) == 1:340 return True341 # Tagged character case.342 return keychr.count('<') == 1 and keychr.count('>') == 1 and \343 keychr[0] == '<' and keychr[-1] == '>'344 def _sendKeyWithModifiers(self, keychr, modifiers, globally=False):345 """Send one character with the given modifiers pressed.346 Parameters: key character, list of modifiers, global or app specific347 Returns: None or raise ValueError exception348 """349 if not self._isSingleCharacter(keychr):350 raise ValueError('Please provide only one character to send')351 if not hasattr(self, 'keyboard'):352 self.keyboard = AXKeyboard.loadKeyboard()353 modFlags = self._pressModifiers(modifiers, globally=globally)354 # Press the non-modifier key355 self._sendKey(keychr, modFlags, globally=globally)356 # Release the modifiers357 self._releaseModifiers(modifiers, globally=globally)358 # Post the queued keypresses:359 self._postQueuedEvents()360 def _queueMouseButton(self, coord, mouseButton, modFlags, clickCount=1,361 dest_coord=None):362 """Private method to handle generic mouse button clicking.363 Parameters: coord (x, y) to click, mouseButton (e.g.,364 kCGMouseButtonLeft), modFlags set (int)365 Optional: clickCount (default 1; set to 2 for double-click; 3 for366 triple-click on host)367 Returns: None368 """369 # For now allow only left and right mouse buttons:370 mouseButtons = {371 Quartz.kCGMouseButtonLeft: 'LeftMouse',372 Quartz.kCGMouseButtonRight: 'RightMouse',373 }374 if mouseButton not in mouseButtons:375 raise ValueError('Mouse button given not recognized')376 eventButtonDown = getattr(Quartz,377 'kCGEvent%sDown' % mouseButtons[mouseButton])378 eventButtonUp = getattr(Quartz,379 'kCGEvent%sUp' % mouseButtons[mouseButton])380 eventButtonDragged = getattr(Quartz,381 'kCGEvent%sDragged' % mouseButtons[382 mouseButton])383 # Press the button384 buttonDown = Quartz.CGEventCreateMouseEvent(None,385 eventButtonDown,386 coord,387 mouseButton)388 # Set modflags (default None) on button down:389 Quartz.CGEventSetFlags(buttonDown, modFlags)390 # Set the click count on button down:391 Quartz.CGEventSetIntegerValueField(buttonDown,392 Quartz.kCGMouseEventClickState,393 int(clickCount))394 if dest_coord:395 # Drag and release the button396 buttonDragged = Quartz.CGEventCreateMouseEvent(None,397 eventButtonDragged,398 dest_coord,399 mouseButton)400 # Set modflags on the button dragged:401 Quartz.CGEventSetFlags(buttonDragged, modFlags)402 buttonUp = Quartz.CGEventCreateMouseEvent(None,403 eventButtonUp,404 dest_coord,405 mouseButton)406 else:407 # Release the button408 buttonUp = Quartz.CGEventCreateMouseEvent(None,409 eventButtonUp,410 coord,411 mouseButton)412 # Set modflags on the button up:413 Quartz.CGEventSetFlags(buttonUp, modFlags)414 # Set the click count on button up:415 Quartz.CGEventSetIntegerValueField(buttonUp,416 Quartz.kCGMouseEventClickState,417 int(clickCount))418 # Queue the events419 self._queueEvent(Quartz.CGEventPost,420 (Quartz.kCGSessionEventTap, buttonDown))421 if dest_coord:422 self._queueEvent(Quartz.CGEventPost,423 (Quartz.kCGHIDEventTap, buttonDragged))424 self._queueEvent(Quartz.CGEventPost,425 (Quartz.kCGSessionEventTap, buttonUp))426 def _leftMouseDragged(self, stopCoord, strCoord, speed):427 """Private method to handle generic mouse left button dragging and428 dropping.429 Parameters: stopCoord(x,y) drop point430 Optional: strCoord (x, y) drag point, default (0,0) get current431 mouse position432 speed (int) 1 to unlimit, simulate mouse moving433 action from some special requirement434 Returns: None435 """436 # Get current position as start point if strCoord not given437 if strCoord == (0, 0):438 loc = AppKit.NSEvent.mouseLocation()439 strCoord = (loc.x, Quartz.CGDisplayPixelsHigh(0) - loc.y)440 # Press left button down441 pressLeftButton = Quartz.CGEventCreateMouseEvent(442 None,443 Quartz.kCGEventLeftMouseDown,444 strCoord,445 Quartz.kCGMouseButtonLeft446 )447 # Queue the events448 Quartz.CGEventPost(Quartz.CoreGraphics.kCGHIDEventTap, pressLeftButton)449 # Wait for reponse of system, a fuzzy icon appears450 time.sleep(5)451 # Simulate mouse moving speed, k is slope452 speed = round(1 / float(speed), 2)453 xmoved = stopCoord[0] - strCoord[0]454 ymoved = stopCoord[1] - strCoord[1]455 if ymoved == 0:456 raise ValueError('Not support horizontal moving')457 else:458 k = abs(ymoved / xmoved)459 if xmoved != 0:460 for xpos in range(int(abs(xmoved))):461 if xmoved > 0 and ymoved > 0:462 currcoord = (strCoord[0] + xpos, strCoord[1] + xpos * k)463 elif xmoved > 0 and ymoved < 0:464 currcoord = (strCoord[0] + xpos, strCoord[1] - xpos * k)465 elif xmoved < 0 and ymoved < 0:466 currcoord = (strCoord[0] - xpos, strCoord[1] - xpos * k)467 elif xmoved < 0 and ymoved > 0:468 currcoord = (strCoord[0] - xpos, strCoord[1] + xpos * k)469 # Drag with left button470 dragLeftButton = Quartz.CGEventCreateMouseEvent(471 None,472 Quartz.kCGEventLeftMouseDragged,473 currcoord,474 Quartz.kCGMouseButtonLeft475 )476 Quartz.CGEventPost(Quartz.CoreGraphics.kCGHIDEventTap,477 dragLeftButton)478 # Wait for reponse of system479 time.sleep(speed)480 else:481 raise ValueError('Not support vertical moving')482 upLeftButton = Quartz.CGEventCreateMouseEvent(483 None,484 Quartz.kCGEventLeftMouseUp,485 stopCoord,486 Quartz.kCGMouseButtonLeft487 )488 # Wait for reponse of system, a plus icon appears489 time.sleep(5)490 # Up left button up491 Quartz.CGEventPost(Quartz.CoreGraphics.kCGHIDEventTap, upLeftButton)492 def _waitFor(self, timeout, notification, **kwargs):493 """Wait for a particular UI event to occur; this can be built494 upon in NativeUIElement for specific convenience methods.495 """496 callback = self._matchOther497 retelem = None498 callbackArgs = None499 callbackKwargs = None500 # Allow customization of the callback, though by default use the basic501 # _match() method502 if 'callback' in kwargs:503 callback = kwargs['callback']504 del kwargs['callback']505 # Deal with these only if callback is provided:506 if 'args' in kwargs:507 if not isinstance(kwargs['args'], tuple):508 errStr = 'Notification callback args not given as a tuple'509 raise TypeError(errStr)510 # If args are given, notification will pass back the returned511 # element in the first positional arg512 callbackArgs = kwargs['args']513 del kwargs['args']514 if 'kwargs' in kwargs:515 if not isinstance(kwargs['kwargs'], dict):516 errStr = 'Notification callback kwargs not given as a dict'517 raise TypeError(errStr)518 callbackKwargs = kwargs['kwargs']519 del kwargs['kwargs']520 # If kwargs are not given as a dictionary but individually listed521 # need to update the callbackKwargs dict with the remaining items in522 # kwargs523 if kwargs:524 if callbackKwargs:525 callbackKwargs.update(kwargs)526 else:527 callbackKwargs = kwargs528 else:529 callbackArgs = (retelem, )530 # Pass the kwargs to the default callback531 callbackKwargs = kwargs532 return self._setNotification(timeout, notification, callback,533 callbackArgs,534 callbackKwargs)535 def waitForFocusToMatchCriteria(self, timeout=10, **kwargs):536 """Convenience method to wait for focused element to change537 (to element matching kwargs criteria).538 Returns: Element or None539 """540 def _matchFocused(retelem, **kwargs):541 return retelem if retelem._match(**kwargs) else None542 retelem = None543 return self._waitFor(timeout, 'AXFocusedUIElementChanged',544 callback=_matchFocused,545 args=(retelem, ),546 **kwargs)547 def _getActions(self):548 """Retrieve a list of actions supported by the object."""549 actions = _a11y.AXUIElement._getActions(self)550 # strip leading AX from actions - help distinguish them from attributes551 return [action[2:] for action in actions]552 def _performAction(self, action):553 """Perform the specified action."""554 _a11y.AXUIElement._performAction(self, 'AX%s' % action)555 def _generateChildren(self):556 """Generator which yields all AXChildren of the object."""557 try:558 children = self.AXChildren559 except _a11y.Error:560 return561 if children:562 for child in children:563 yield child564 def _generateChildrenR(self, target=None):565 """Generator which recursively yields all AXChildren of the object."""566 if target is None:567 target = self568 try:569 children = target.AXChildren570 except _a11y.Error:571 return572 if children:573 for child in children:574 yield child575 for c in self._generateChildrenR(child):576 yield c577 def _match(self, **kwargs):578 """Method which indicates if the object matches specified criteria.579 Match accepts criteria as kwargs and looks them up on attributes.580 Actual matching is performed with fnmatch, so shell-like wildcards581 work within match strings. Examples:582 obj._match(AXTitle='Terminal*')583 obj._match(AXRole='TextField', AXRoleDescription='search text field')584 """585 for k in kwargs.keys():586 try:587 val = getattr(self, k)588 except _a11y.Error:589 return False590 # Not all values may be strings (e.g. size, position)591 if isinstance(val, str):592 if not fnmatch.fnmatch(val, kwargs[k]):593 return False594 else:595 if val != kwargs[k]:596 return False597 return True598 def _matchOther(self, obj, **kwargs):599 """Perform _match but on another object, not self."""600 if obj is not None:601 # Need to check that the returned UI element wasn't destroyed first:602 if self._findFirstR(**kwargs):603 return obj._match(**kwargs)604 return False605 def _generateFind(self, **kwargs):606 """Generator which yields matches on AXChildren."""607 for needle in self._generateChildren():608 if needle._match(**kwargs):609 yield needle610 def _generateFindR(self, **kwargs):611 """Generator which yields matches on AXChildren and their children."""612 for needle in self._generateChildrenR():613 if needle._match(**kwargs):614 yield needle615 def _findAll(self, **kwargs):616 """Return a list of all children that match the specified criteria."""617 result = []618 for item in self._generateFind(**kwargs):619 result.append(item)620 return result621 def _findAllR(self, **kwargs):622 """Return a list of all children (recursively) that match the specified623 criteria.624 """625 result = []626 for item in self._generateFindR(**kwargs):627 result.append(item)628 return result629 def _findFirst(self, **kwargs):630 """Return the first object that matches the criteria."""631 for item in self._generateFind(**kwargs):632 return item633 def _findFirstR(self, **kwargs):634 """Search recursively for the first object that matches the criteria."""635 for item in self._generateFindR(**kwargs):636 return item637 def _getApplication(self):638 """Get the base application UIElement.639 If the UIElement is a child of the application, it will try640 to get the AXParent until it reaches the top application level641 element.642 """643 app = self644 while True:645 try:646 app = app.AXParent647 except _a11y.ErrorUnsupported:648 break649 return app650 def _menuItem(self, menuitem, *args):651 """Return the specified menu item.652 Example - refer to items by name:653 app._menuItem(app.AXMenuBar, 'File', 'New').Press()654 app._menuItem(app.AXMenuBar, 'Edit', 'Insert', 'Line Break').Press()655 Refer to items by index:656 app._menuitem(app.AXMenuBar, 1, 0).Press()657 Refer to items by mix-n-match:658 app._menuitem(app.AXMenuBar, 1, 'About TextEdit').Press()659 """660 self._activate()661 for item in args:662 # If the item has an AXMenu as a child, navigate into it.663 # This seems like a silly abstraction added by apple's a11y api.664 if menuitem.AXChildren[0].AXRole == 'AXMenu':665 menuitem = menuitem.AXChildren[0]666 # Find AXMenuBarItems and AXMenuItems using a handy wildcard667 role = 'AXMenu*Item'668 try:669 menuitem = menuitem.AXChildren[int(item)]670 except ValueError:671 menuitem = menuitem.findFirst(AXRole='AXMenu*Item',672 AXTitle=item)673 return menuitem674 def _activate(self):675 """Activate the application (bringing menus and windows forward)."""676 ra = AppKit.NSRunningApplication677 app = ra.runningApplicationWithProcessIdentifier_(678 self._getPid())679 # NSApplicationActivateAllWindows | NSApplicationActivateIgnoringOtherApps680 # == 3 - PyObjC in 10.6 does not expose these constants though so I have681 # to use the int instead of the symbolic names682 app.activateWithOptions_(3)683 def _getBundleId(self):684 """Return the bundle ID of the application."""685 ra = AppKit.NSRunningApplication686 app = ra.runningApplicationWithProcessIdentifier_(687 self._getPid())688 return app.bundleIdentifier()689 def _getLocalizedName(self):690 """Return the localized name of the application."""691 return self._getApplication().AXTitle692 def __getattr__(self, name):693 """Handle attribute requests in several ways:694 1. If it starts with AX, it is probably an a11y attribute. Pass695 it to the handler in _a11y which will determine that for sure.696 2. See if the attribute is an action which can be invoked on the697 UIElement. If so, return a function that will invoke the attribute.698 """699 if name.startswith('AX'):700 try:701 attr = self._getAttribute(name)702 return attr703 except AttributeError:704 pass705 # Populate the list of callable actions:706 actions = []707 try:708 actions = self._getActions()709 except Exception:710 pass711 if name.startswith('AX') and (name[2:] in actions):712 errStr = 'Actions on an object should be called without AX ' \713 'prepended'714 raise AttributeError(errStr)715 if name in actions:716 def performSpecifiedAction():717 # activate the app before performing the specified action718 self._activate()719 return self._performAction(name)720 return performSpecifiedAction721 else:722 raise AttributeError('Object %s has no attribute %s' % (self, name))723 def __setattr__(self, name, value):724 """Set attributes on the object."""725 if name.startswith('AX'):726 return self._setAttribute(name, value)727 else:728 _a11y.AXUIElement.__setattr__(self, name, value)729 def __repr__(self):730 """Build a descriptive string for UIElements."""731 title = repr('')732 role = '<No role!>'733 c = repr(self.__class__).partition('<class \'')[-1].rpartition('\'>')[0]734 try:735 title = repr(self.AXTitle)736 except Exception:737 try:738 title = repr(self.AXValue)739 except Exception:740 try:741 title = repr(self.AXRoleDescription)742 except Exception:743 pass744 try:745 role = self.AXRole746 except Exception:747 pass748 if len(title) > 20:749 title = title[:20] + '...\''750 return '<%s %s %s>' % (c, role, title)751class NativeUIElement(BaseAXUIElement):752 """NativeUIElement class - expose the accessibility API in the simplest,753 most natural way possible.754 """755 def getAttributes(self):756 """Get a list of the attributes available on the element."""757 return self._getAttributes()758 def getActions(self):759 """Return a list of the actions available on the element."""760 return self._getActions()761 def setString(self, attribute, string):762 """Set the specified attribute to the specified string."""763 return self._setString(attribute, string)764 def findFirst(self, **kwargs):765 """Return the first object that matches the criteria."""766 return self._findFirst(**kwargs)767 def findFirstR(self, **kwargs):768 """Search recursively for the first object that matches the769 criteria.770 """771 return self._findFirstR(**kwargs)772 def findAll(self, **kwargs):773 """Return a list of all children that match the specified criteria."""774 return self._findAll(**kwargs)775 def findAllR(self, **kwargs):776 """Return a list of all children (recursively) that match777 the specified criteria.778 """779 return self._findAllR(**kwargs)780 def getElementAtPosition(self, coord):781 """Return the AXUIElement at the given coordinates.782 If self is behind other windows, this function will return self.783 """784 return self._getElementAtPosition(float(coord[0]), float(coord[1]))785 def activate(self):786 """Activate the application (bringing menus and windows forward)"""787 return self._activate()788 def getApplication(self):789 """Get the base application UIElement.790 If the UIElement is a child of the application, it will try791 to get the AXParent until it reaches the top application level792 element.793 """794 return self._getApplication()795 def menuItem(self, *args):796 """Return the specified menu item.797 Example - refer to items by name:798 app.menuItem('File', 'New').Press()799 app.menuItem('Edit', 'Insert', 'Line Break').Press()800 Refer to items by index:801 app.menuitem(1, 0).Press()802 Refer to items by mix-n-match:803 app.menuitem(1, 'About TextEdit').Press()804 """805 menuitem = self._getApplication().AXMenuBar806 return self._menuItem(menuitem, *args)807 def popUpItem(self, *args):808 """Return the specified item in a pop up menu."""809 self.Press()810 time.sleep(.5)811 return self._menuItem(self, *args)812 def getBundleId(self):813 """Return the bundle ID of the application."""814 return self._getBundleId()815 def getLocalizedName(self):816 """Return the localized name of the application."""817 return self._getLocalizedName()818 def sendKey(self, keychr):819 """Send one character with no modifiers."""820 return self._sendKey(keychr)821 def sendGlobalKey(self, keychr):822 """Send one character without modifiers to the system.823 It will not send an event directly to the application, system will824 dispatch it to the window which has keyboard focus.825 Parameters: keychr - Single keyboard character which will be sent.826 """827 return self._sendKey(keychr, globally=True)828 def sendKeys(self, keystr):829 """Send a series of characters with no modifiers."""830 return self._sendKeys(keystr)831 def pressModifiers(self, modifiers):832 """Hold modifier keys (e.g. [Option])."""833 return self._holdModifierKeys(modifiers)834 def releaseModifiers(self, modifiers):835 """Release modifier keys (e.g. [Option])."""836 return self._releaseModifierKeys(modifiers)837 def sendKeyWithModifiers(self, keychr, modifiers):838 """Send one character with modifiers pressed839 Parameters: key character, modifiers (list) (e.g. [SHIFT] or840 [COMMAND, SHIFT] (assuming you've first used841 from pyatom.AXKeyCodeConstants import *))842 """843 return self._sendKeyWithModifiers(keychr, modifiers, False)844 def sendGlobalKeyWithModifiers(self, keychr, modifiers):845 """Global send one character with modifiers pressed.846 See sendKeyWithModifiers847 """848 return self._sendKeyWithModifiers(keychr, modifiers, True)849 def dragMouseButtonLeft(self, coord, dest_coord, interval=0.5):850 """Drag the left mouse button without modifiers pressed.851 Parameters: coordinates to click on screen (tuple (x, y))852 dest coordinates to drag to (tuple (x, y))853 interval to send event of btn down, drag and up854 Returns: None855 """856 modFlags = 0857 self._queueMouseButton(coord, Quartz.kCGMouseButtonLeft, modFlags,858 dest_coord=dest_coord)859 self._postQueuedEvents(interval=interval)860 def doubleClickDragMouseButtonLeft(self, coord, dest_coord, interval=0.5):861 """Double-click and drag the left mouse button without modifiers862 pressed.863 Parameters: coordinates to double-click on screen (tuple (x, y))864 dest coordinates to drag to (tuple (x, y))865 interval to send event of btn down, drag and up866 Returns: None867 """868 modFlags = 0869 self._queueMouseButton(coord, Quartz.kCGMouseButtonLeft, modFlags,870 dest_coord=dest_coord)871 self._queueMouseButton(coord, Quartz.kCGMouseButtonLeft, modFlags,872 dest_coord=dest_coord,873 clickCount=2)874 self._postQueuedEvents(interval=interval)875 def clickMouseButtonLeft(self, coord, interval=None):876 """Click the left mouse button without modifiers pressed.877 Parameters: coordinates to click on screen (tuple (x, y))878 Returns: None879 """880 modFlags = 0881 self._queueMouseButton(coord, Quartz.kCGMouseButtonLeft, modFlags)882 if interval:883 self._postQueuedEvents(interval=interval)884 else:885 self._postQueuedEvents()886 def clickMouseButtonRight(self, coord):887 """Click the right mouse button without modifiers pressed.888 Parameters: coordinates to click on scren (tuple (x, y))889 Returns: None890 """891 modFlags = 0892 self._queueMouseButton(coord, Quartz.kCGMouseButtonRight, modFlags)893 self._postQueuedEvents()894 def clickMouseButtonLeftWithMods(self, coord, modifiers, interval=None):895 """Click the left mouse button with modifiers pressed.896 Parameters: coordinates to click; modifiers (list) (e.g. [SHIFT] or897 [COMMAND, SHIFT] (assuming you've first used898 from pyatom.AXKeyCodeConstants import *))899 Returns: None900 """901 modFlags = self._pressModifiers(modifiers)902 self._queueMouseButton(coord, Quartz.kCGMouseButtonLeft, modFlags)903 self._releaseModifiers(modifiers)904 if interval:905 self._postQueuedEvents(interval=interval)906 else:907 self._postQueuedEvents()908 def clickMouseButtonRightWithMods(self, coord, modifiers):909 """Click the right mouse button with modifiers pressed.910 Parameters: coordinates to click; modifiers (list)911 Returns: None912 """913 modFlags = self._pressModifiers(modifiers)914 self._queueMouseButton(coord, Quartz.kCGMouseButtonRight, modFlags)915 self._releaseModifiers(modifiers)916 self._postQueuedEvents()917 def leftMouseDragged(self, stopCoord, strCoord=(0, 0), speed=1):918 """Click the left mouse button and drag object.919 Parameters: stopCoord, the position of dragging stopped920 strCoord, the position of dragging started921 (0,0) will get current position922 speed is mouse moving speed, 0 to unlimited923 Returns: None924 """925 self._leftMouseDragged(stopCoord, strCoord, speed)926 def doubleClickMouse(self, coord):927 """Double-click primary mouse button.928 Parameters: coordinates to click (assume primary is left button)929 Returns: None930 """931 modFlags = 0932 self._queueMouseButton(coord, Quartz.kCGMouseButtonLeft, modFlags)933 # This is a kludge:934 # If directed towards a Fusion VM the clickCount gets ignored and this935 # will be seen as a single click, so in sequence this will be a double-936 # click937 # Otherwise to a host app only this second one will count as a double-938 # click939 self._queueMouseButton(coord, Quartz.kCGMouseButtonLeft, modFlags,940 clickCount=2)941 self._postQueuedEvents()942 def doubleMouseButtonLeftWithMods(self, coord, modifiers):943 """Click the left mouse button with modifiers pressed.944 Parameters: coordinates to click; modifiers (list)945 Returns: None946 """947 modFlags = self._pressModifiers(modifiers)948 self._queueMouseButton(coord, Quartz.kCGMouseButtonLeft, modFlags)949 self._queueMouseButton(coord, Quartz.kCGMouseButtonLeft, modFlags,950 clickCount=2)951 self._releaseModifiers(modifiers)952 self._postQueuedEvents()953 def tripleClickMouse(self, coord):954 """Triple-click primary mouse button.955 Parameters: coordinates to click (assume primary is left button)956 Returns: None957 """958 # Note above re: double-clicks applies to triple-clicks959 modFlags = 0960 for i in range(2):961 self._queueMouseButton(coord, Quartz.kCGMouseButtonLeft, modFlags)962 self._queueMouseButton(coord, Quartz.kCGMouseButtonLeft, modFlags,963 clickCount=3)964 self._postQueuedEvents()965 def waitFor(self, timeout, notification, **kwargs):...
oskb.py
Source:oskb.py
...160 continue161 self._kbdname = n162 self._kbd = k163 # print("setKeybaord picked ", n)164 self._releaseModifiers()165 if self._sendmapchanges and k.get("keymap"):166 self._sendmapchanges(k.get("keymap"))167 if newgeometry:168 self.hide()169 if kbdname != "_minimized":170 self._previouskeyboard = n171 self._previousgeometry = self.geometry()172 self._kbdstack.setCurrentIndex(k.get("_stackindex", 0))173 if self._kbd["views"].get(self._viewname):174 self.setView(self._viewname, newgeometry)175 else:176 self.setView("default", newgeometry)177 return True178 return False179 def setView(self, viewname, newgeometry=None):180 # print ("setView", viewname)181 if self._kbd["views"].get(viewname):182 self._view = self._kbd["views"][viewname]183 self._viewname = viewname184 self._kbd["_QWidget"].layout().setCurrentIndex(self._view["_stackindex"])185 if newgeometry:186 self.setGeometry(newgeometry)187 self.show()188 else:189 self.updateKeyboard()190 return True191 return False192 def getRawKbds(self):193 return self._kbds194 #195 # initKeyboards sets up a QStackedLayout holding QWidgets for each keyboard, which in turn have a196 # QStackedlayout that holds a QWidget for each view within that keyboard. That has a QGridLayout with197 # QHboxLayouts in it that hold the individual key QPushButton widgets. It also sets the captions and198 # button actions for each key and figures out how many standard key widths and row vis there are in199 # all the views, which is used by updateKeyboard() to dynamically figure out how big the fonts, margins200 # and rounded corners need to be.201 #202 def initKeyboards(self):203 # Helper to return placeholder "empty row" widget204 def _makeEmptyRow(row):205 er = QPushButton(self)206 er.pressed.connect(partial(self._buttonhandler, er, PRESSED))207 er.released.connect(partial(self._buttonhandler, er, RELEASED))208 er.setMinimumSize(1, 1)209 er.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)210 er.setProperty("class", "emptyrow")211 row["_QWidget"] = er212 row["type"] = "emptyrow"213 er.data = row214 return er215 # Helper to return a QLayout to go in place of the QPushButton that contains it plus216 # any extra labels stacked on top217 def _makeCaptionLayout(k):218 extracaptions = k.data.get("extracaptions", None)219 if not extracaptions:220 return False221 # ecl = extra captions layout222 ecl = QStackedLayout()223 ecl.setStackingMode(QStackedLayout.StackAll)224 ecl.addWidget(k)225 for cssclass, txt in extracaptions.items():226 ql = QLabel(txt)227 ql.setProperty("class", cssclass)228 ql.setAttribute(Qt.WA_TransparentForMouseEvents)229 ecl.addWidget(ql)230 return ecl231 def _maxRowsInView(view):232 maxrows = 0233 for column in view.get("columns", []):234 maxrows = max(len(column.get("rows")), maxrows)235 return maxrows236 # This stores the width and height in standard key widths for each view.237 def _storeWidthsAndHeights(view):238 total_height = 0239 # Heights are only stored in first column240 column = view["columns"][0]241 for ri, row in enumerate(column.get("rows", [])):242 total_height += row.get("height", 1)243 total_width = 0244 for ci, column in enumerate(view.get("columns", [])):245 largest_width = 0246 for ri, row in reversed(list(enumerate(column.get("rows", [])))):247 if len(row.get("keys", [])):248 totalweight = 0249 for keydata in row.get("keys", []):250 w = keydata.get("width", 1)251 totalweight += w252 # Not counting frst row if there are widths already (reversed order)253 if totalweight > largest_width and (ri != 0 or totalweight == 0):254 largest_width = totalweight255 column["_widthInUnits"] = largest_width256 total_width += largest_width257 view["_widthInUnits"] = max(total_width, 1)258 view["_heightInUnits"] = max(total_height, 1)259 # Start of initKeyboards() itself260 if self._kbdstack.itemAt(0):261 self._clearLayout(self._kbdstack)262 ki = 0263 for kbdname, kbd in self._kbds.items():264 viewstack = QStackedLayout()265 vi = 0266 for viewname, view in kbd.get("views", {}).items():267 _storeWidthsAndHeights(view)268 grid = QGridLayout()269 grid.setSpacing(0)270 grid.setContentsMargins(0, 0, 0, 0)271 for ci, column in enumerate(view.get("columns", [])):272 for ri in range(_maxRowsInView(view)):273 if ri < len(column["rows"]):274 row = column["rows"][ri]275 else:276 row = {"keys": []}277 column["rows"].append(row)278 keys = row.get("keys", [])279 kl = QHBoxLayout()280 kl.setContentsMargins(0, 0, 0, 0)281 kl.setSpacing(0)282 for keydata in keys:283 stretch = keydata.get("width", 1) * 10284 type = keydata.get("type", "key")285 k = QPushButton(self)286 k.setMinimumSize(1, 1)287 keydata["_QWidget"] = k288 keydata["_selected"] = False289 k.data = keydata290 k.pressed.connect(partial(self._buttonhandler, k, PRESSED))291 k.released.connect(partial(self._buttonhandler, k, RELEASED))292 k.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)293 k.setMinimumSize(1, 1)294 if type == "key":295 k.setText(keydata.get("caption", ""))296 # Multiple captions? Create a QStackedWidget overlays them all297 ecl = _makeCaptionLayout(k)298 if ecl:299 kl.addLayout(ecl, stretch)300 else:301 kl.addWidget(k, stretch)302 else:303 kl.addWidget(k, stretch)304 if not len(keys):305 er = _makeEmptyRow(row)306 kl.addWidget(er)307 else:308 row["_QWidget"] = None309 grid.addLayout(kl, ri, ci * 2)310 if ci == 0:311 grid.setRowStretch(ri, row.get("height", 1) * 10)312 grid.setColumnStretch(ci * 2, column.get("_widthInUnits", 1) * 10)313 if ci > 0:314 spacercolumn = QHBoxLayout()315 spacercolumn.addWidget(QWidget(None))316 grid.setColumnStretch((ci * 2) - 1, COLUMN_MARGIN * 10)317 grid.addLayout(spacercolumn, 0, (ci * 2) - 1)318 # Create with self as parent, then reparent to prevent startup flicker319 view["_QWidget"] = QWidget(self)320 view["_QWidget"].setLayout(grid)321 viewstack.addWidget(view["_QWidget"])322 view["_stackindex"] = vi323 vi += 1324 kbd["_QWidget"] = QWidget(self)325 kbd["_stackindex"] = ki326 ki += 1327 kbd["_QWidget"].setLayout(viewstack)328 self._kbdstack.addWidget(kbd["_QWidget"])329 self.setKeyboard(self._kbdname)330 # Qt keeps coming up with minimum sizes that are way too wide331 # Some sane number will have to go in at some point, I guess332 self.setMaximumSize(16777215, 16777215)333 self.setMinimumSize(1, 1)334 def updateKeyboard(self):335 # Helper function to dynamically recalculate some sizes in stylesheets336 def fixStyle(stylesheet, fontsize, margin, radius):337 if stylesheet == "":338 return ""339 # Replace the main calculated values340 stylesheet = stylesheet.replace("_OSKB_FONTSIZE_", str(fontsize))341 stylesheet = stylesheet.replace("_OSKB_MARGIN_", str(margin))342 stylesheet = stylesheet.replace("_OSKB_RADIUS_", str(radius))343 # And then all the percentages based thereon (Qt5 doesn't do percentages in fontsizes)344 r = re.compile(r"font-size\s*:\s*(\d+)\%")345 i = r.finditer(stylesheet)346 for m in i:347 stylesheet = stylesheet.replace(348 m.group(0), "font-size: " + str(int((fontsize / 100) * int(m.group(1)))) + "px",349 )350 return stylesheet351 if not self._view:352 return False353 # Calculate the font and margin sizes354 kw = self.width() / self._view["_widthInUnits"]355 kh = self.height() / self._view["_heightInUnits"]356 fontsize = min(max(int(min(kw / 1.5, kh / 2)), 5), 50)357 margin = int(fontsize / 15)358 radius = margin * 3359 # Dynamically change the default and keyboard stylesheets360 all_sheets = self._stylesheet + "\n\n" + self._kbd.get("style", "")361 super().setStyleSheet(fixStyle(all_sheets, fontsize, margin, radius))362 # Then adjust the stylesheets and class properties of all keys363 for ci, column in enumerate(self._view.get("columns", [])):364 for ri, row in enumerate(column.get("rows", [])):365 rowwidget = row.get("_QWidget")366 if rowwidget:367 if row.get("_selected", False):368 rowwidget.setProperty("class", "emptyrow selected")369 else:370 rowwidget.setProperty("class", "emptyrow")371 # It needs .setStyleSheet(""), not .repaint() to show the changes372 rowwidget.setStyleSheet("")373 else:374 for keydata in row.get("keys", []):375 k = keydata.get("_QWidget")376 type = keydata.get("type", "key")377 classes = [type]378 classes.append(keydata.get("class", ""))379 if keydata.get("single") and keydata["single"].get("modifier"):380 modname = keydata["single"]["modifier"].get("name", "")381 moddata = self._modifiers.get(modname, {})382 modstate = moddata.get("state")383 if modstate == 1:384 classes.append("held")385 elif modstate == 2:386 classes.append("locked")387 else:388 classes.append("modifier")389 if keydata.get("_selected", False):390 classes.append("selected")391 classes.append("view_" + self._viewname)392 classes.append("row" + str(ri + 1))393 classes.append("col" + str(ci + 1))394 k.setProperty("class", " ".join(classes).strip())395 keystyle = keydata.get("style", "")396 k.setStyleSheet(fixStyle(keystyle, fontsize, margin, radius))397 #398 # The part here is the low-level button handling. It takes care of calling _doAction() with PRESSED and399 # RELEASED with pointers to either the "single", "double" or "long" sub-dictionaries for that button,400 # handling all the nitty-gritty. Bit involved.., Maybe only touch when wide awake and concentrated.401 #402 def _oskbButtonHandler(self, button, direction):403 sng = button.data.get("single")404 dbl = button.data.get("double")405 lng = button.data.get("long")406 if direction == PRESSED:407 if self._doublebutton and self._doublebutton != button:408 # Another key was pressed within the doubleclick timeout, so we must409 # first process the previous key that was held back410 self._doAction(self._doublebutton.data.get("single"), PRESSED)411 self._doAction(self._doublebutton.data.get("single"), RELEASED)412 self._doublebutton = None413 self._doubletimer.stop()414 self._stopsinglepress = False415 if lng or dbl:416 if lng:417 self._longtimer = QTimer()418 self._longtimer.setSingleShot(True)419 self._longtimer.timeout.connect(partial(self._longPress, lng))420 self._longtimer.start(LONGPRESS_TIMEOUT)421 if dbl:422 self._stopsinglepress = True423 if self._doubletimer.isActive():424 self._doubletimer.stop()425 self._doAction(dbl, PRESSED)426 self._doAction(dbl, RELEASED)427 self._doublebutton = None428 else:429 self._doublebutton = button430 self._doubletimer.start(DOUBLECLICK_TIMEOUT)431 else:432 self._doAction(sng, PRESSED)433 else:434 if not self._stopsinglepress:435 if self._longtimer.isActive():436 self._longtimer.stop()437 self._doAction(sng, PRESSED)438 self._doAction(sng, RELEASED)439 else:440 self._doAction(sng, RELEASED)441 self._stopsinglepress = False442 self._longtimer.stop()443 def _longPress(self, lng):444 self._stopsinglepress = True445 self._doAction(lng, PRESSED)446 self._doAction(lng, RELEASED)447 def _doubleTimeout(self):448 if not self._stopsinglepress:449 actiondict = self._doublebutton.data.get("single")450 self._doAction(actiondict, PRESSED)451 self._doAction(actiondict, RELEASED)452 self._doublebutton = None453 #454 # Higher level button handling: parses the actions from the action dictionary455 #456 def _doAction(self, actiondict, direction):457 if not actiondict:458 return459 for cmd, argdict in actiondict.items():460 if not argdict:461 continue462 if cmd == "send":463 keycode = argdict.get("keycode", "")464 keycodeplus = keycode465 keyname = argdict.get("name", "")466 printable = argdict.get("printable", True)467 for modname, mod in self._modifiers.items():468 if mod.get("state") > 0:469 keyname = modname + " " + keyname470 modkeycode = mod.get("keycode")471 keycodeplus = modkeycode + "+" + keycode472 if not mod.get("printable"):473 printable = False474 if direction == PRESSED and self._flashmodifiers:475 self._injectKeys(modkeycode, PRESSED)476 self._injectKeys(keycode, direction)477 if direction == RELEASED:478 self._releaseModifiers()479 if self._viewuntil and re.fullmatch(self._viewuntil, keyname):480 self.setView(self._thenview)481 self.viewuntil, self._thenview = None, None482 if cmd == "view" and direction == RELEASED:483 viewname = argdict.get("name", "default")484 self._viewuntil = argdict.get("until")485 self._thenview = argdict.get("thenview")486 self.setView(viewname)487 addclass = "oneview" if self._viewuntil else "view"488 self.setProperty("class", self._view.get("class", "") + addclass)489 self.updateKeyboard()490 if cmd == "modifier" and direction == RELEASED:491 keycode = argdict.get("keycode", "")492 modifier = argdict.get("name", "")493 printable = argdict.get("printable", True)494 modaction = argdict.get("action", "toggle")495 m = self._modifiers.get(modifier)496 if modaction == "toggle":497 if not m or m["state"] == 0:498 self._modifiers[modifier] = {499 "state": 1,500 "keycode": keycode,501 "printable": printable,502 }503 if not self._flashmodifiers:504 self._injectKeys(keycode, PRESSED)505 else:506 self._modifiers[modifier] = {507 "state": 0,508 "keycode": keycode,509 "printable": printable,510 }511 if not self._flashmodifiers:512 self._injectKeys(keycode, RELEASED)513 if modaction == "lock":514 if not m:515 self._modifiers[modifier] = {}516 s = self._modifiers[modifier].get("state", 0)517 self._modifiers[modifier] = {518 "state": 0 if s == 2 else 2,519 "keycode": keycode,520 "printable": printable,521 }522 if not self._flashmodifiers:523 self._injectKeys(keycode, PRESSED if s == 0 else RELEASED)524 self.updateKeyboard()525 if cmd == "keyboard" and direction == RELEASED:526 kbdname = argdict.get("name", "")527 self.setKeyboard(kbdname)528 # This is where the strings with keycodes to be pressed or released get turned into actual keypress529 # events. There's two levels here: "42+2;57" (in the US layout) means we're first pressing and then530 # releasing shift 2 (an exclamation point) and then a space.531 def _injectKeys(self, keystr, direction):532 keylist = keystr.split(";")533 # If PRESSED, press and release all the ;-separated keycodes, releasing all but the last534 if direction == PRESSED:535 for keycodes in keylist:536 keycodelist = keycodes.split("+")537 for keycode in keycodelist:538 self._sendKey(int(keycode), PRESSED)539 if keycodes != keylist[-1]:540 self._sendKey(int(keycode), RELEASED)541 # If RELEASED, only need to release the last (set of) keys542 if direction == RELEASED:543 keycodelist = keylist[-1].split("+")544 for keycode in reversed(keycodelist):545 self._sendKey(int(keycode), RELEASED)546 def _sendKey(self, keycode, keyevent):547 if self._sendkeys:548 self._sendkeys(keycode, keyevent)549 def _releaseModifiers(self):550 if self._view:551 donestuff = False552 for modinfo in self._modifiers.values():553 if modinfo["state"] == 1:554 donestuff = True555 if not self._flashmodifiers:556 self._injectKeys(modinfo["keycode"], RELEASED)557 modinfo["state"] = 0558 if self._flashmodifiers:559 self._injectKeys(modinfo["keycode"], RELEASED)560 if donestuff:561 self.updateKeyboard()562 # Helper563 def _clearLayout(self, layout):...
Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.
You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.
Get 100 minutes of automation test minutes FREE!!