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!!
