简体   繁体   中英

Using python with windows 10 to enable mouse wraparound

I'm trying to use some combination of automatic cursor positioning with pyWinhook to implement cursor wraparound on a multiscreen desktop setup. Eventually I hope to extend the features of the code to wrap to a different system when I hit the far right or far left edge of the screen, but for now I'm leaving out the network bits by trying to get it functional on a single system.

Initially I threw together this simple script, using pyautogui, to do the wrapping:

import pyautogui

MAX_X, MAX_Y = pyautogui.size()
MAX_X -= 1
MAX_Y -= 1

def detect_wrap():
        x, y = pyautogui.position()
        if x >= MAX_X:
                pyautogui.moveTo(0,y)
        elif x <= 0:
                pyautogui.moveTo(MAX_X,y)
        elif y >= MAX_Y:
                pyautogui.moveTo(x, 0)
        elif y <= 0:
                pyautogui.moveTo(x, MAX_Y) 
        return

print(pyautogui.size())
while True:
        detect_wrap()

Which works fine, but has that annoying endless loop at the end, so I tried looking for a way to handle it more cleanly and stumbled upon pyWinhook and pythoncom and concocted a similar script to try to achieve the same thing. When I did that, though, rather than jump from far bottom to the top, far right to the left, etc. the script stuck at the edges and stayed there. Though I'm not sure why. I put some print statements in to try to shed some light and saw that the mouse did, indeed, get moved to the opposing border, but something kept making it go back. I dove into pyautogui and figured out how it was moving the cursor (using ctypes.windll.user32.SetCursorPos(xpos, ypos)) so I took pyautogui out of the equation. What I'm left with looks like this (mind you, this is a work-in-progress):

import win32api
import pyWinhook as pyHook
import pythoncom
import ctypes
import win32api, win32con
import win32.win32gui as win32gui
import os, sys, time

SCREEN_WIDTH = 0        # This will actually be set later, when we know the size of the attached screen(s)
SCREEN_HEIGHT = 0       # This will actually be set later, when we know the size of the attached screen(s)
XMAX = 0                # This will actually be set later, when we know the size of the attached screen(s)
YMAX = 0                # This will actually be set later, when we know the size of the attached screen(s)
XMIN = 1
YMIN = 1

def setWrapPos(xpos, ypos, border):
        win32gui.ReleaseCapture()
        if border == 'left':
                xpos = SCREEN_WIDTH - 1
        elif border == 'top':
                ypos = SCREEN_HEIGHT - 1
        elif border == 'right':
                xpos = XMIN
        elif border == 'bottom':
                ypos = YMIN
        else:
                print('ERROR: Illegal border passed to setWrapPos()')

        ctypes.windll.user32.SetCursorPos(xpos, ypos)
        time.sleep(0.01)
        return

def onclick(event):
        print('Detected mouseclick at (%d,%d)' % event.Position)
        return True

def trackMouse(event):
        hm.UnhookMouse()
        flags, hcursor, coords = win32gui.GetCursorInfo()
        xpos, ypos = coords
        print("Mouse at (%d, %d)" % (xpos, ypos))
        if (xpos <= XMIN):
                setWrapPos(xpos, ypos, 'left')
        elif (ypos <= YMIN):
                setWrapPos(xpos, ypos, 'top')
        elif (xpos >= XMAX):
                setWrapPos(xpos, ypos, 'right')
        elif (ypos >= YMAX):
                setWrapPos(xpos, ypos, 'bottom')
        flags, hcursor, coords = win32gui.GetCursorInfo()
        xpos, ypos = coords
        print("Mouse moved to (%d, %d)" % (xpos, ypos))
        hm.HookMouse()
        return True

def onWinCombo(event):
        if event.Key == 'X':
                print('Lcontrol-X was detected.  Exiting.')
                hm.UnhookMouse()
                hm.UnhookKeyboard()
                os._exit(0)
        else:
                hm.KeyDown = onKeyboardEvent
        return False

def noMoreCombo(event):
        hm.KeyDown = onKeyboardEvent
        hm.KeyUp = None
        return False

def onKeyboardEvent(event):
        print('Keyboard action detected, ' + str(event.Key) + ' was pressed.')
        if str(event.Key) == 'Lcontrol':
                print('Lcontrol detected.')
                hm.KeyDown = onWinCombo
                hm.KeyUp = noMoreCombo
        return True

curr_right = 0
mons = win32api.EnumDisplayMonitors()
i = 0
while i < win32api.GetSystemMetrics(win32con.SM_CMONITORS):
        minfo = win32api.GetMonitorInfo(mons[i][0])
        if minfo['Monitor'][2] > SCREEN_HEIGHT:
                SCREEN_HEIGHT = minfo['Monitor'][3]
        print("Monitor #" + str(i+1) + ": " + str(minfo['Monitor'][2] - curr_right) + "x" + str(minfo['Monitor'][3]))
        i += 1
        curr_right += minfo['Monitor'][2]
minfo = win32api.GetMonitorInfo(mons[i-1][0])
SCREEN_WIDTH = minfo['Monitor'][2]
XMAX = SCREEN_WIDTH - 1
YMAX = SCREEN_HEIGHT - 1

hm = pyHook.HookManager()
hm.SubscribeMouseAllButtonsDown(onclick)
hm.SubscribeMouseMove(trackMouse)
hm.KeyDown = onKeyboardEvent
hm.HookMouse()
hm.HookKeyboard()
pythoncom.PumpMessages()

The code works, but no matter what I've tried, I can't seem to avoid having the cursor bounce back to the borders rather than hop to the other side. I've run out of ideas, so thought maybe somebody here would have a thought about what's going on.

Thanks in advance for any insight.

Update: I created a working mouse wraparound python script called wrap.py, and put the result up on GitHub at https://github.com/XyzzyBob/wrap . I compiled it under Win10 as well, with pyinstaller, but can't upload the file as I can't transfer files from where the code is at.

The python script requires a bunch of libraries, as noted on the github page, and is highly Windows only. I may redo it for Linux and Mac at some point, if I find the need, but for now Windows is where I'm using it.

Still working on a networked version for multi-system control, but that's a bigger program. Progress on that will likely show up as a branch on the github page noted.

======================== xxxxxxxxxxxxxxxxxx ===========================

Found a way to achieve what I was looking to do using a different library. The mouse library apparently handles low-level hooks and has routines to both get and set mouse status items like position. The solution basically looks like this:

import mouse
...
def mouseEvent(event):
        if isinstance(event, mouse._mouse_event.MoveEvent):
                trackMouse(event)
        elif isinstance(event, mouse._mouse_event.ButtonEvent):
                onclick(event)
        else:
                print(event)
        return True
...
def trackMouse(event):
        xpos = event.x
        ypos = event.y
        print("Mouse at (%d, %d)" % (xpos, ypos))
        if (xpos <= XMIN):
                setWrapPos(xpos, ypos, 'left')
        elif (ypos <= YMIN):
                setWrapPos(xpos, ypos, 'top')
        elif (xpos >= XMAX):
                setWrapPos(xpos, ypos, 'right')
        elif (ypos >= YMAX):
                setWrapPos(xpos, ypos, 'bottom')
        return True
...
def onclick(event):
        print(event.event_type + ' for ' + event.button + ' button at ' + str(mouse.get_position()))
        return True

### Main ###
hm = mouse.hook(mouseEvent)
pythoncom.PumpMessages()

Now it's just a matter of adding the keyboard hooks and the rest of the bits to make it a coherent package.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM