EventGhost/EventGhost

View on GitHub
plugins/Mouse/__init__.py

Summary

Maintainability
F
3 days
Test Coverage
# -*- coding: utf-8 -*-
#
# This file is part of EventGhost.
# Copyright © 2005-2020 EventGhost Project <http://www.eventghost.net/>
#
# EventGhost is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 2 of the License, or (at your option)
# any later version.
#
# EventGhost is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along
# with EventGhost. If not, see <http://www.gnu.org/licenses/>.

import wx
from math import cos, pi, radians, sin
from Queue import Queue
from sys import maxint
from threading import Thread
from time import clock, sleep
from win32api import EnumDisplayMonitors, GetSystemMetrics, mouse_event as mouse_event2
from win32con import MOUSEEVENTF_ABSOLUTE, MOUSEEVENTF_MOVE

# Local imports
import eg
from eg import HasActiveHandler
from eg.cFunctions import SetMouseCallback
from eg.WinApi.Dynamic import GetCursorPos, mouse_event, POINT, SetCursorPos
from eg.WinApi.Utils import GetMonitorDimensions

ICON = """iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeT
AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH1QQIDRgEM71mAAAAADV0RVh0Q29tbWVudAAoYy
kgMjAwNCBKYWt1YiBTdGVpbmVyCgpDcmVhdGVkIHdpdGggVGhlIEdJTVCQ2YtvAAACHElEQVQ4y42Q
zUtUURjGf/fcyatz73Wiklwo2R/QplXQ/AURlLYJcrJNQrvQahYFI0wQ7lu0azNtYlAj2rUJRFciUf
kRgUwOM6Y5jePXfNzznhZ+NOpIvpvD+5zn/M7DY3Fo0ul0JzBQLpdvG2M8wHi++6r7Zs+Tet/Yu9Hr
W5tb/Yqjc2m7vB3zfPd7LBbzPd/tK/5Zu5ZKpZZSb1LZ0bGRG7u+F2E3PG0dfp1MJl+2tvq9xeLaJv
AxkUj01aW7UKtV3xvYam525nq6b92znieHEkqpIWwLpRSV7YBoNEoun2VhIUOTY6ODAAmkqJT68PRZ
orf+w1AoFBq63//A2LZthcNhhoeH0VrjNLVgYTHw8DGlUonC6u/IyEj6DnAAoAAq1ar1c3FxX8zlcl
QqlX97Po/XGrEa9MWREuPxOPl8nmw2Szwe538Tql9WVlZoa2tjcHDwgHZiwGqhwGqhgO/7dHZ0MDM7
e7IEG6V1zp05uy/WghrLv5YPaBul9eMBnufuRLXAwsIYQYsgRhCt0SK0n2/nuBKnxBi00YhotA7Qoh
ERRAsiBiOy559qBJjVWmMrmyAQtNboYBcmgojQdMrZ8083Anyan5/D8zxaWpqxlEKLoPVOfNd1iZyO
MDPzDeBHow7efv3yuc9xnGhX10U8z8MAGMPOYchkFlhaygG8bgSoVavVu5MT448mJ8YvA1cadJUBrg
Jrhy/+AqGrAMOnH86mAAAAAElFTkSuQmCC"""

eg.RegisterPlugin(
    name = "Mouse",
    author = (
        "Bitmonster",
        "Sem;colon",
    ),
    version = "1.1.1",
    description = (
        "Actions to control the mouse cursor and emulation of mouse events."
    ),
    kind = "core",
    guid = "{6B1751BF-F94E-4260-AB7E-64C0693FD959}",
    icon = ICON,
    url = "http://www.eventghost.net/forum/viewtopic.php?f=9&t=5481",
)

class Mouse(eg.PluginBase):
    def __init__(self):
        self.AddEvents()
        self.AddAction(LeftButton)
        self.AddAction(LeftDoubleClick)
        self.AddAction(ToggleLeftButton)
        self.AddAction(MiddleButton)
        self.AddAction(MoveAbsolute)
        self.AddAction(MoveRelative)
        self.AddAction(RightButton)
        self.AddAction(RightDoubleClick)
        self.AddAction(GoDirection)
        self.AddAction(MouseWheel)

    @eg.LogIt
    def __close__(self):
        pass

    def __start__(self):
        self.thread = MouseThread()
        self.leftMouseButtonDown = False
        self.lastMouseEvent = None
        self.mouseButtonWasBlocked = [False, False, False, False, False]
        SetMouseCallback(self.MouseCallBack)

    @eg.LogIt
    def __stop__(self):
        SetMouseCallback(None)
        self.thread.receiveQueue.put([-1])

    def MouseCallBack(self, buttonName, buttonNum, param):
        if param:
            if self.lastMouseEvent:
                self.lastMouseEvent.SetShouldEnd()
            shouldBlock = HasActiveHandler("Mouse." + buttonName)
            self.mouseButtonWasBlocked[buttonNum] = shouldBlock
            self.lastMouseEvent = self.TriggerEnduringEvent(buttonName)
            return shouldBlock
        else:
            if self.lastMouseEvent:
                self.lastMouseEvent.SetShouldEnd()
            return self.mouseButtonWasBlocked[buttonNum]
        return False


class MouseThread(Thread):
    currentAngle = 0
    newAngle = 0
    acceleration = 0
    speed = 0
    maxTicks = 5
    yRemainder = 0
    xRemainder = 0
    leftButtonDown = False
    lastTime = 0
    initSpeed = 0.06
    maxSpeed = 7.0
    useAlternateMethod = False

    def __init__(self):
        Thread.__init__(self, name="MouseThread")
        self.receiveQueue = Queue(2048)
        self.start()

    @eg.LogItWithReturn
    def run(self):
        stop = False
        point = POINT()
        while True:
            self.lastTime = clock()
            if not self.receiveQueue.empty():
                data = self.receiveQueue.get()
                if data[0] == -1:
                    break
                elif data[0] == -2:
                    stop = True
                else:
                    self.newAngle = radians(data[0])
                    self.initSpeed = data[1]
                    self.maxSpeed = data[2]
                    self.acceleration = data[3]
                    self.useAlternateMethod = data[4]

            if stop:
                self.acceleration = 0
                self.speed = 0
                stop = False
                continue

            if self.acceleration == 0:
                sleep(0.05)
                continue

            ticks = 10
            if self.speed == 0:
                self.currentAngle = self.newAngle
                self.speed = self.initSpeed
            else:
                diff = self.newAngle - self.currentAngle
                if diff > pi:
                    diff = diff - 2 * pi
                elif diff < -1 * pi:
                    diff = diff + 2 * pi
                self.currentAngle = self.currentAngle + (diff / 20)

            self.speed = self.speed + (self.speed * self.acceleration * ticks)
            if self.speed > self.maxSpeed:
                self.speed = self.maxSpeed
            elif self.speed <= 0:
                self.speed = 0

            factor = self.speed * (ticks / 10)
            xCurrent = sin(self.currentAngle) * factor + self.xRemainder
            yCurrent = -1 * cos(self.currentAngle) * factor + self.yRemainder

            x = int(xCurrent)
            y = int(yCurrent)

            self.xRemainder = xCurrent - x
            self.yRemainder = yCurrent - y
            try:
                if self.useAlternateMethod:
                    mouse_event2(MOUSEEVENTF_MOVE, x, y)
                else:
                    GetCursorPos(point)
                    SetCursorPos(point.x + x, point.y + y)
            except:
                pass
            if self.speed == 0:
                self.acceleration = 0
            waitTicks = 0.01 - (clock() - self.lastTime)
            if waitTicks < 0:
                waitTicks = 0.0
            sleep(waitTicks)


class GoDirection(eg.ActionBase):
    name = "Start Movement"
    description = "Starts cursor movement in the specified direction."

    class text:
        label = u"Start cursor movement in direction %.2f\u00B0"
        text1 = "Start moving cursor in direction"
        text2 = "degrees. (0-360)"
        text3 = "Initial mouse speed:"
        text4 = "Maximum mouse speed:"
        text5 = "Acceleration factor:"
        label_AM = "Use alternate method"

    def __call__(self, direction=0, initSpeed = 60, maxSpeed = 7000, accelerationFactor = 3, useAlternateMethod=False):
        def UpFunc():
            self.plugin.thread.receiveQueue.put([-2])
        self.plugin.thread.receiveQueue.put([float(direction), float(initSpeed) / 1000, float(maxSpeed) / 1000, float(accelerationFactor) / 1000, useAlternateMethod])
        eg.event.AddUpFunc(UpFunc)

    def Configure(self, direction=0, initSpeed = 60, maxSpeed = 7000, accelerationFactor = 3, useAlternateMethod=False):
        text = self.text
        panel = eg.ConfigPanel()
        direction = float(direction)
        valueCtrl = panel.SpinNumCtrl(float(direction), min=0, max=360)
        panel.AddLine(text.text1, valueCtrl, text.text2)

        initSpeedLabel = wx.StaticText(panel, -1, text.text3)
        initSpeedSpin = eg.SpinIntCtrl(panel, -1, initSpeed, 10, 2000)
        maxSpeedLabel = wx.StaticText(panel, -1, text.text4)
        maxSpeedSpin = eg.SpinIntCtrl(panel, -1, maxSpeed, 4000, 32000)
        accelerationFactorLabel = wx.StaticText(panel, -1, text.text5)
        accelerationFactorSpin = eg.SpinIntCtrl(panel, -1, accelerationFactor, 1, 200)
        eg.EqualizeWidths((initSpeedLabel, maxSpeedLabel, accelerationFactorLabel))
        panel.AddLine(initSpeedLabel, initSpeedSpin)
        panel.AddLine(maxSpeedLabel, maxSpeedSpin)
        panel.AddLine(accelerationFactorLabel, accelerationFactorSpin)

        uAMCB = panel.CheckBox(useAlternateMethod, text.label_AM)
        panel.AddLine(uAMCB)

        while panel.Affirmed():
            panel.SetResult(
                valueCtrl.GetValue(),
                initSpeedSpin.GetValue(),
                maxSpeedSpin.GetValue(),
                accelerationFactorSpin.GetValue(),
                uAMCB.GetValue(),
            )

    def GetLabel(self, direction=0, initSpeed = 60, maxSpeed = 7000, accelerationFactor = 3, useAlternateMethod=False):
        direction = float(direction)
        return self.text.label % direction


class LeftButton(eg.ActionBase):
    name = "Left Mouse Click"
    description = "Clicks the left mouse button."

    def __call__(self):
        def UpFunc():
            mouse_event(0x0004, 0, 0, 0, 0)
            self.plugin.leftMouseButtonDown = False
        mouse_event(0x0002, 0, 0, 0, 0)
        self.plugin.leftMouseButtonDown = True
        eg.event.AddUpFunc(UpFunc)


class LeftDoubleClick(eg.ActionBase):
    name = "Left Mouse Double-Click"
    description = "Double-clicks the left mouse button."

    def __call__(self):
        def UpFunc():
            mouse_event(0x0004, 0, 0, 0, 0)
        self.plugin.leftMouseButtonDown = False
        mouse_event(0x0002, 0, 0, 0, 0)
        mouse_event(0x0004, 0, 0, 0, 0)
        mouse_event(0x0002, 0, 0, 0, 0)
        eg.event.AddUpFunc(UpFunc)


class MiddleButton(eg.ActionBase):
    name = "Middle Mouse Click"
    description = "Clicks the middle mouse button."

    def __call__(self):
        def UpFunc():
            mouse_event(0x0040, 0, 0, 0, 0)
        mouse_event(0x0020, 0, 0, 0, 0)
        eg.event.AddUpFunc(UpFunc)


class MouseWheel(eg.ActionBase):
    name = "Turn Mouse Wheel"
    description = "Turns the mouse wheel."

    class text:
        label = u"Turn mouse wheel %d clicks"
        text1 = "Turn mouse wheel by"
        text2 = "clicks. (Negative values turn down)"

    def __call__(self, direction=0):
        mouse_event(0x0800, 0, 0, direction * 120, 0)

    def Configure(self, direction=0):
        panel = eg.ConfigPanel()
        valueCtrl = panel.SpinIntCtrl(direction, min=-100, max=100)
        panel.AddLine(self.text.text1, valueCtrl, self.text.text2)
        while panel.Affirmed():
            panel.SetResult(valueCtrl.GetValue())

    def GetLabel(self, direction=0):
        return self.text.label % direction


class MoveAbsolute(eg.ActionBase):
    name = "Move Absolute"
    description = "Moves the cursor to an absolute position."

    class text:
        display = "Move cursor to"
        label_M = "Monitor: %i,  "
        label_X = "x: %i,  "
        label_Y = "y: %i"
        label_C = "Set position to screen center"
        label_AM = "Use alternate method"
        center = "center"
        text1 = "Set horizontal position X to"
        text2 = "pixels"
        text3 = "Set vertical position Y to"
        note = (
            "Note: The coordinates X and Y are related to the monitor "
            '(not to the "virtual screen")'
        )

    def __call__(self, x = None, y = None, displayNumber = None, center = False, useAlternateMethod=False):
        point = POINT()
        GetCursorPos(point)
        X = point.x
        Y = point.y
        mons = EnumDisplayMonitors(None, None)
        mons = [item[2] for item in mons]
        for mon in range(len(mons)):  # on what monitor (= mon) is the cursor?
            m = mons[mon]
            if m[0] <= X and X <= m[2] and m[1] <= Y and Y <= m[3]:
                break
        if displayNumber is None:
            displayNumber = mon

        monitorDimensions = GetMonitorDimensions()
        try:
            displayRect = monitorDimensions[displayNumber]
        except IndexError:
            displayNumber = 0
            displayRect = monitorDimensions[displayNumber]
        if center:
            x = displayRect[2] / 2
            y = displayRect[3] / 2

        if x is None:
            x = X - mons[displayNumber][0]
        if y is None:
            y = Y - mons[displayNumber][1]

        x += displayRect[0]
        y += displayRect[1]
        if useAlternateMethod:
            x = x * 65535 / GetSystemMetrics(0)
            y = y * 65535 / GetSystemMetrics(1)
            mouse_event2(MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE, x, y)
        else:
            SetCursorPos(x, y)

    def Configure(self, x = None, y = None, displayNumber = None, center = False, useAlternateMethod=False):
        panel = eg.ConfigPanel()
        text = self.text

        uAMCB = panel.CheckBox(useAlternateMethod, text.label_AM)
        cCB = panel.CheckBox(center, text.label_C)
        xCB = panel.CheckBox(x is not None, text.text1)
        yCB = panel.CheckBox(y is not None, text.text3)
        displayCB = panel.CheckBox(displayNumber is not None, text.display)

        #xCtrl = panel.SpinIntCtrl(x or 0, min = -maxint - 1, max = maxint)
        xCtrl = panel.SpinIntCtrl(x or 0, min = 0, max = maxint)  # since 1.0.1
        xCtrl.Enable(x is not None)

        #yCtrl = panel.SpinIntCtrl(y or 0, min = -maxint - 1, max = maxint)
        yCtrl = panel.SpinIntCtrl(y or 0, min = 0, max = maxint)  # since 1.0.1
        yCtrl.Enable(y is not None)

        display = -1 if displayNumber is None else displayNumber
        displayChoice = eg.DisplayChoice(panel, display)
        displayChoice.Enable(displayNumber is not None)

        xPixels = wx.StaticText(panel, -1, text.text2)
        yPixels = wx.StaticText(panel, -1, text.text2)
        monsCtrl = eg.MonitorsCtrl(panel, background = (224, 238, 238))
        note = wx.StaticText(panel, -1, text.note)
        note.SetForegroundColour(wx.RED)
        sizer = wx.GridBagSizer(vgap = 6, hgap = 5)
        sizer.Add(cCB, (0, 0), (1, 3), flag = wx.BOTTOM, border = 8)
        sizer.Add(xCB, (1, 0), (1, 1))
        sizer.Add(xCtrl, (1, 1), (1, 1))
        sizer.Add(xPixels, (1, 2), (1, 1))
        sizer.Add(yCB, (2, 0), (1, 1))
        sizer.Add(yCtrl, (2, 1), (1, 1))
        sizer.Add(yPixels, (2, 2), (1, 1))
        sizer.Add(note, (3, 0), (1, 3))
        sizer.Add(displayCB, (4, 0), (1, 1), flag = wx.TOP, border = 14)
        sizer.Add(displayChoice, (4, 1), (1, 2), flag = wx.TOP, border = 13)
        sizer.Add(uAMCB, (5, 0), (1, 3))
        panel.sizer.Add(sizer, 1, wx.EXPAND)
        panel.sizer.Add(monsCtrl, 0, wx.TOP, 8)

        def HandleCenterCheckBox(event = None):
            val = not cCB.GetValue()
            xCB.Enable(val)
            xCtrl.Enable(val)
            xPixels.Enable(val)
            yCB.Enable(val)
            yCtrl.Enable(val)
            yPixels.Enable(val)
            if not val:
                xCB.SetValue(False)
                yCB.SetValue(False)
                xCtrl.SetValue(0)
                yCtrl.SetValue(0)
            if event:
                event.Skip()
        cCB.Bind(wx.EVT_CHECKBOX, HandleCenterCheckBox)
        HandleCenterCheckBox()

        def HandleXCheckBox(event):
            xCtrl.Enable(event.IsChecked())
            event.Skip()
        xCB.Bind(wx.EVT_CHECKBOX, HandleXCheckBox)

        def HandleYCheckBox(event):
            yCtrl.Enable(event.IsChecked())
            event.Skip()
        yCB.Bind(wx.EVT_CHECKBOX, HandleYCheckBox)

        def HandleDisplayCB(event):
            flag = event.IsChecked()
            displayChoice.Enable(flag)
            if flag:
                display = 0 if displayNumber is None else displayNumber
            else:
                display = -1
            displayChoice.SetValue(display)
            event.Skip()
        displayCB.Bind(wx.EVT_CHECKBOX, HandleDisplayCB)

        while panel.Affirmed():
            if xCtrl.IsEnabled():
                x = xCtrl.GetValue()
            else:
                x = None

            if yCtrl.IsEnabled():
                y = yCtrl.GetValue()
            else:
                y = None

            if displayChoice.IsEnabled():
                displayNumber = displayChoice.GetValue()
            else:
                displayNumber = None

            panel.SetResult(x, y, displayNumber, cCB.GetValue(), uAMCB.GetValue())

    def GetLabel(self, x, y, displayNumber, center, useAlternateMethod=False):
        if center:
            res = self.text.display + " " + self.text.center
            if displayNumber is not None:
                res += ": %s" % (self.text.label_M % (displayNumber + 1))
            return res
        else:
            return self.text.display + ":  %s%s%s" % (
                self.text.label_M % (displayNumber + 1) if displayNumber is not None else "",
                self.text.label_X % x if x is not None else "",
                self.text.label_Y % y if y is not None else "",
            )


class MoveRelative(eg.ActionBase):
    name = "Move Relative"
    description = "Moves the cursor to a relative position."

    class text:
        label = "Change cursor position by x:%s, y:%s"
        text1 = "Change horizontal position X by"
        text2 = "pixels"
        text3 = "Change vertical position Y by"
        label_AM = "Use alternate method"

    def __call__(self, x, y, useAlternateMethod=False):
        if x is None:
            x = 0
        if y is None:
            y = 0
        if useAlternateMethod:
            mouse_event2(MOUSEEVENTF_MOVE, x, y)
        else:
            point = POINT()
            GetCursorPos(point)
            SetCursorPos(point.x + x, point.y + y)

    def Configure(self, x=0, y=0, useAlternateMethod=False):
        panel = eg.ConfigPanel()
        text = self.text

        uAMCB = panel.CheckBox(useAlternateMethod, text.label_AM)

        xCB = panel.CheckBox(x is not None, text.text1)

        def HandleXCheckBox(event):
            xCtrl.Enable(event.IsChecked())
            event.Skip()
        xCB.Bind(wx.EVT_CHECKBOX, HandleXCheckBox)

        xCtrl = panel.SpinIntCtrl(x or 0, min=-maxint - 1, max=maxint)
        xCtrl.Enable(x is not None)

        yCB = panel.CheckBox(y is not None, text.text3)

        def HandleYCheckBox(event):
            yCtrl.Enable(event.IsChecked())
            event.Skip()
        yCB.Bind(wx.EVT_CHECKBOX, HandleYCheckBox)

        yCtrl = panel.SpinIntCtrl(y or 0, min=-maxint - 1, max=maxint)
        yCtrl.Enable(y is not None)

        panel.AddLine(xCB, xCtrl, text.text2)
        panel.AddLine(yCB, yCtrl, text.text2)
        panel.AddLine(uAMCB)

        while panel.Affirmed():
            if xCtrl.IsEnabled():
                x = xCtrl.GetValue()
            else:
                x = None

            if yCtrl.IsEnabled():
                y = yCtrl.GetValue()
            else:
                y = None
            panel.SetResult(x, y, uAMCB.GetValue())

    def GetLabel(self, x, y, useAlternateMethod=False):
        return self.text.label % (str(x), str(y))


class RightButton(eg.ActionBase):
    name = "Right Mouse Click"
    description = "Clicks the right mouse button."

    def __call__(self):
        def UpFunc():
            mouse_event(0x0010, 0, 0, 0, 0)
        mouse_event(0x0008, 0, 0, 0, 0)
        eg.event.AddUpFunc(UpFunc)


class RightDoubleClick(eg.ActionBase):
    name = "Right Mouse Double-Click"
    description = "Double-clicks the right mouse button."

    def __call__(self):
        def UpFunc():
            mouse_event(0x0010, 0, 0, 0, 0)
        mouse_event(0x0008, 0, 0, 0, 0)
        mouse_event(0x0010, 0, 0, 0, 0)
        mouse_event(0x0008, 0, 0, 0, 0)
        eg.event.AddUpFunc(UpFunc)


class ToggleLeftButton(eg.ActionBase):
    class text:
        name = "Left Mouse Toggle"
        description = "Changes the status of the left mouse button."
        radioBoxLabel = "Option"
        radioBoxOptions = [
            "Toggle left mouse button",
            "Set left mouse button \"Up\"",
            "Set left mouse button \"Down\""
        ]

    def __call__(self, data=0):
        if self.plugin.leftMouseButtonDown and data == 0 or data == 1:
            mouse_event(0x0004, 0, 0, 0, 0)
            self.plugin.leftMouseButtonDown = False
        else:
            mouse_event(0x0002, 0, 0, 0, 0)
            self.plugin.leftMouseButtonDown = True

    def GetLabel(self, data=0):
        return self.plugin.label + ': ' + self.text.radioBoxOptions[data]

    def Configure(self, data=0):
        panel = eg.ConfigPanel()
        radioBox = wx.RadioBox(
            panel,
            label=self.text.radioBoxLabel,
            choices=self.text.radioBoxOptions,
            style=wx.RA_SPECIFY_ROWS
        )
        radioBox.SetSelection(data)
        panel.sizer.Add(radioBox, 0, wx.EXPAND)
        while panel.Affirmed():
            panel.SetResult(radioBox.GetSelection())