this answer may be late but for those looking, the answer is here : https://github.com/asweigart/pyautogui/issues/321

quote - Resolve for me

If still relevant for someone on windows:

In my opinion the issue is, that the current version of pyscreeze utilizing >ImageGrab (Pillow) on windows only uses single-screen grab.


A dirty quick fix in pyscreeze could be:


  • enable all_screen grabbing:

In file: pyscreeze/__init__.py, function: def _screenshot_win32(imageFilename=None, region=None):

change im = ImageGrab.grab() to im = ImageGrab.grab(all_screens= True)


  • handle new introduced negative coordinates due to multiple monitor:

In file: pyscreeze/__init__.py, function: def locateOnScreen(image, minSearchTime=0, **kwargs): behind retVal = locate(image, screenshotIm, **kwargs) >add


if retVal and sys.platform == 'win32':
    # get the lowest x and y coordinate of the monitor setup
    monitors = win32api.EnumDisplayMonitors()
    x_min = min([mon[2][0] for mon in monitors])
    y_min = min([mon[2][1] for mon in monitors])
    # add negative offset due to multi monitor
    retVal = Box(left=retVal[0] + x_min, top=retVal[1] + y_min, width=retVal[2],height=retVal[3])

  • don't forget to add the import win32api

In file: pyscreeze/__init__.py:


if sys.platform == 'win32': # TODO - Pillow now supports ImageGrab on macOS.
    import win32api # used for multi-monitor fix
    from PIL import ImageGrab
Answer from Laurent Carmelli on Stack Overflow
Top answer
1 of 3
1

this answer may be late but for those looking, the answer is here : https://github.com/asweigart/pyautogui/issues/321

quote - Resolve for me

If still relevant for someone on windows:

In my opinion the issue is, that the current version of pyscreeze utilizing >ImageGrab (Pillow) on windows only uses single-screen grab.


A dirty quick fix in pyscreeze could be:


  • enable all_screen grabbing:

In file: pyscreeze/__init__.py, function: def _screenshot_win32(imageFilename=None, region=None):

change im = ImageGrab.grab() to im = ImageGrab.grab(all_screens= True)


  • handle new introduced negative coordinates due to multiple monitor:

In file: pyscreeze/__init__.py, function: def locateOnScreen(image, minSearchTime=0, **kwargs): behind retVal = locate(image, screenshotIm, **kwargs) >add


if retVal and sys.platform == 'win32':
    # get the lowest x and y coordinate of the monitor setup
    monitors = win32api.EnumDisplayMonitors()
    x_min = min([mon[2][0] for mon in monitors])
    y_min = min([mon[2][1] for mon in monitors])
    # add negative offset due to multi monitor
    retVal = Box(left=retVal[0] + x_min, top=retVal[1] + y_min, width=retVal[2],height=retVal[3])

  • don't forget to add the import win32api

In file: pyscreeze/__init__.py:


if sys.platform == 'win32': # TODO - Pillow now supports ImageGrab on macOS.
    import win32api # used for multi-monitor fix
    from PIL import ImageGrab
2 of 3
0

Unfortunately pyautogui currently doesn't work with multiple monitors, you can find it in their FAQ

Q: Does PyAutoGUI work on multi-monitor setups.

A: No, right now PyAutoGUI only handles the primary monitor.

As of searching specific area you can use optional region=(startXValue,startYValue,width,height) parameter as shown here.

🌐
GitHub
github.com › asweigart › pyautogui › issues › 321
Use LocateOnScreen with Multiple Monitors · Issue #321 · asweigart/pyautogui
March 29, 2019 - I have an extra monitor plugged into my laptop many times (but not all the time) and I recently noticed that the following code: import pyautogui change_intense_to_calm = pyautogui.locateOnScreen('Intense.png', confidence = 0.9) run_script_button_x, run_script_button_y = pyautogui.center(change_intense_to_calm) pyautogui.click(run_script_button_x, run_script_button_y)
Author   JohnTravolski
Discussions

Roadmap: Add multi-monitor support
PyAutoGUI does not support moving the mouse across monitors currently. This feature should be added. More on github.com
🌐 github.com
27
November 20, 2014
python - Using pyautogui with multiple monitors - Stack Overflow
I'm trying to use the pyautogui module for python to automate mouse clicks and movements. However, it doesn't seem to be able to recognise any monitor other than my main one, which means i'm not ab... More on stackoverflow.com
🌐 stackoverflow.com
python - Move mouse cursor to second monitor using pyautogui - Stack Overflow
I started using pyautogui about an hour ago. Very nice that python supports GUI automation. I'm having one problem though. I use two screens and it appears that the wrapper is unable to move my cursor to my secondary monitor. More on stackoverflow.com
🌐 stackoverflow.com
python - Screenshot on multiple monitor setup pyautogui - Stack Overflow
A: No, right now PyAutoGUI only handles the primary monitor. More on stackoverflow.com
🌐 stackoverflow.com
🌐
Reddit
reddit.com › r/learnpython › pyautogui with multiple monitors
r/learnpython on Reddit: PyAutoGUI with multiple monitors
August 22, 2018 -

Just wondering if anyone else has run into problems using pyautogui.locateCenterOnScreen with multiple monitors and if so, did you find any solution to help the function. I keep getting a None value returned when the image is clearly on the screen.

Thanks

Top answer
1 of 3
6
2 years later UPDATE: The latest version of PIL (v 8.0.1) now supports this, so this solution is no longer needed. Instead add these 3 lines to the top of your code to enable all monitor screengrabs in Windows: from PIL import ImageGrab from functools import partial ImageGrab.grab = partial(ImageGrab.grab, all_screens=True) And then use pyautogui normally. Yes, I solved this problem by using a different screenshot program. Here is my solution, assuming you are using Windows: Save the following in file named "chilimangos.py": #!/usr/bin/env python # -*- coding: utf-8 -*- # chilimangoes.py # from ctypes import windll, c_int, c_uint, c_char_p, c_buffer from struct import calcsize, pack from PIL import Image gdi32 = windll.gdi32 screen_size = (10000, 10000) #optimistic until we know better # Win32 functions CreateDC = gdi32.CreateDCA CreateCompatibleDC = gdi32.CreateCompatibleDC GetDeviceCaps = gdi32.GetDeviceCaps CreateCompatibleBitmap = gdi32.CreateCompatibleBitmap BitBlt = gdi32.BitBlt SelectObject = gdi32.SelectObject GetDIBits = gdi32.GetDIBits DeleteDC = gdi32.DeleteDC DeleteObject = gdi32.DeleteObject # Win32 constants NULL = 0 HORZRES = 8 VERTRES = 10 SRCCOPY = 13369376 HGDI_ERROR = 4294967295 ERROR_INVALID_PARAMETER = 87 #from http://www.math.uiuc.edu/~gfrancis/illimath/windows/aszgard_mini/movpy-2.0.0-py2.4.4/movpy/lib/win32/lib/win32con.py SM_XVIRTUALSCREEN = 76 SM_YVIRTUALSCREEN = 77 SM_CXVIRTUALSCREEN = 78 SM_CYVIRTUALSCREEN = 79 SM_CMONITORS = 80 def grab_screen(region=None): """ Grabs a screenshot. This is a replacement for PIL's ImageGrag.grab() method that supports multiple monitors. (SEE: https://github.com/python-pillow/Pillow/issues/1547) Returns a PIL Image, so PIL library must be installed. param region is in the format (left, top, width, height), which is the same format returned by pyscreeze Usage: im = grab_screen() # grabs a screenshot of all monitors im = grab_screen([0, 0, -1600, 1200]) # grabs a 1600 x 1200 screenshot to the left of the primary monitor im.save('screencap.jpg') """ bitmap = None try: screen = CreateDC(c_char_p('DISPLAY'),NULL,NULL,NULL) screen_copy = CreateCompatibleDC(screen) if region: left,top,width,height = region else: left = windll.user32.GetSystemMetrics(SM_XVIRTUALSCREEN) top = windll.user32.GetSystemMetrics(SM_YVIRTUALSCREEN) width = windll.user32.GetSystemMetrics(SM_CXVIRTUALSCREEN) height = windll.user32.GetSystemMetrics(SM_CYVIRTUALSCREEN) bitmap = CreateCompatibleBitmap(screen, width, height) if bitmap == NULL: print('grab_screen: Error calling CreateCompatibleBitmap. Returned NULL') return hobj = SelectObject(screen_copy, bitmap) if hobj == NULL or hobj == HGDI_ERROR: print('grab_screen: Error calling SelectObject. Returned {0}.'.format(hobj)) return if BitBlt(screen_copy, 0, 0, width, height, screen, left, top, SRCCOPY) == NULL: print('grab_screen: Error calling BitBlt. Returned NULL.') return bitmap_header = pack('LHHHH', calcsize('LHHHH'), width, height, 1, 24) bitmap_buffer = c_buffer(bitmap_header) bitmap_bits = c_buffer(' ' * (height * ((width * 3 + 3) & -4))) got_bits = GetDIBits(screen_copy, bitmap, 0, height, bitmap_bits, bitmap_buffer, 0) if got_bits == NULL or got_bits == ERROR_INVALID_PARAMETER: print('grab_screen: Error calling GetDIBits. Returned {0}.'.format(got_bits)) return image = Image.frombuffer('RGB', (width, height), bitmap_bits, 'raw', 'BGR', (width * 3 + 3) & -4, -1) if not region: #if this was a full screen grab then set the size for future use. global screen_size screen_size = image.size return image finally: if bitmap is not None: if bitmap: DeleteObject(bitmap) DeleteDC(screen_copy) DeleteDC(screen) Then, in your program add this to the pyautogui import: import pyautogui import chilimangoes pyautogui.screenshot = chilimangoes.grab_screen pyautogui.pyscreeze.screenshot = chilimangoes.grab_screen pyautogui.size = lambda: chilimangoes.screen_size
2 of 3
2
I’ve had similar issues. I wrote a gui app on a laptop and had external monitor, but using the locate centre onscreen only found the image if it was on the screen where the icon screenshot was originally taken. I believe it has to do with the screen resolution, so the locate centre on screen command will be looking for an image of a certain size, but if on a different screen then the image is rendered at a different size to what autogui is searching for.
🌐
GitHub
github.com › asweigart › pyautogui › issues › 9
Roadmap: Add multi-monitor support · Issue #9 · asweigart/pyautogui
November 20, 2014 - PyAutoGUI does not support moving the mouse across monitors currently. This feature should be added.
Author   asweigart
Find elsewhere
🌐
PyPI
pypi.org › project › PyAutoGUI
PyAutoGUI · PyPI
Currently, PyAutoGUI only works on the primary monitor.
      » pip install PyAutoGUI
    
Published   May 24, 2023
Version   0.9.54
Top answer
1 of 3
10

mss package can easily solve this problem

1. Install the package

pip install mss

2. Take screenshots

First Monitor

import os
import os.path

import mss


def on_exists(fname):
    # type: (str) -> None
    """
    Callback example when we try to overwrite an existing screenshot.
    """

    if os.path.isfile(fname):
        newfile = fname + ".old"
        print("{} -> {}".format(fname, newfile))
        os.rename(fname, newfile)


with mss.mss() as sct:
    filename = sct.shot(output="mon-{mon}.png", callback=on_exists)
    print(filename)

Second Monitor

import mss
import mss.tools


with mss.mss() as sct:
    # Get information of monitor 2
    monitor_number = 2
    mon = sct.monitors[monitor_number]

    # The screen part to capture
    monitor = {
        "top": mon["top"],
        "left": mon["left"],
        "width": mon["width"],
        "height": mon["height"],
        "mon": monitor_number,
    }
    output = "sct-mon{mon}_{top}x{left}_{width}x{height}.png".format(**monitor)

    # Grab the data
    sct_img = sct.grab(monitor)

    # Save to the picture file
    mss.tools.to_png(sct_img.rgb, sct_img.size, output=output)
    print(output)

Use with OpenCV

import mss
import cv2
import numpy as np

with mss.mss() as sct:
    
    # Get information of monitor 2
    monitor_number = 2
    mon = sct.monitors[monitor_number]

    # The screen part to capture
    monitor = {
        "top": mon["top"],
        "left": mon["left"],
        "width": mon["width"],
        "height": mon["height"],
        "mon": monitor_number,
    }
    output = "sct-mon{mon}_{top}x{left}_{width}x{height}.png".format(**monitor)

    # Grab the data
    sct_img = sct.grab(monitor)
    img = np.array(sct.grab(monitor)) # BGR Image
    
    # Display the picture
    cv2.imshow("OpenCV", img)
    cv2.waitKey(0)

3. Important Notes

  • sct.monitors will contain more than one item even if you have a single monitor because the first item will be the combined screens
>>> sct.monitors # if we have a single monitor
[{'left': 0, 'top': 0, 'width': 1366, 'height': 768}, 
{'left': 0, 'top': 0, 'width': 1366, 'height': 768}]
>>> sct.monitors # if we have two monitors
[{'left': 0, 'top': 0, 'width': 3286, 'height': 1080}, 
{'left': 1920, 'top': 0, 'width': 1366, 'height': 768}, 
{'left': 0, 'top': 0, 'width': 1920, 'height': 1080}]
2 of 3
7

found an solution here!

from PIL import ImageGrab
from functools import partial
ImageGrab.grab = partial(ImageGrab.grab, all_screens=True)

pyautogui.screenshot() will capture all screens.

🌐
Lightrun
lightrun.com › answers › asweigart-pyautogui-use-locateonscreen-with-multiple-monitors
Use LocateOnScreen with Multiple Monitors
I have an extra monitor plugged into my laptop many times (but not all the time) and I recently noticed that the following code: import pyautogui change_intense_to_calm = pyautogui.locateOnScreen('Intense.png', confidence = 0.9) run_script_button_x, run_script_button_y = pyautogui.center(change_intense_to_calm) pyautogui.click(run_script_button_x, run_script_button_y)
🌐
YouTube
youtube.com › hey delphi
Windows : Screenshot on multiple monitor setup pyautogui - YouTube
Windows : Screenshot on multiple monitor setup pyautoguiTo Access My Live Chat Page, On Google, Search for "hows tech developer connect"I promised to reveal ...
Published   May 17, 2023
Views   320
🌐
Python Forum
python-forum.io › thread-12115.html
Looking through multiple screens for a button
I have used pyautogui to look for a button and then click on it. But I have noticed that it doesn't work when there are multiple screens to a machine. Currently when there is one screen, pyautogui looks at a screenshot I have of the button and finds ...
🌐
Medium
medium.com › @shubhamj2079 › exploring-the-power-of-pyautogui-automating-the-digital-world-72835d6cf480
Power of PyAutoGUI : Automating the Digital World | by Shubham Joshi | Medium
October 19, 2023 - 7. Apart from these functionalities PyAutoGUI offers a range of features for GUI automation; 8. Support for multiple monitors :- Users can seamlessly control the mouse and keyboard across multiple monitors using PyAutoGUI.
🌐
Reddit
reddit.com › r/learnpython › hacktober opportunity: add multimonitor support to pyautogui for windows
r/learnpython on Reddit: Hacktober opportunity: Add multimonitor support to pyautogui for windows
October 23, 2020 -

I came across a problem today that could be solved by a beginner / intermediate python programmer and a little time. Anyone want to take on a challenge?

The pyautogui.screenshot() function on Windows does not support multiple monitors. This has been a problem for many years, but recently the PIL ImageGrab module (which pyautogui depends on) added the all_screens option.

# primary monitor only 
import pyautogui
im = pyautogui.screenshot() 

# all monitors
from PIL import ImageGrab
im = ImageGrab.grab(all_screens=True)

This new feature just needs to be added to pyautogui / pyscreeze. Easy(ish). You would learn a lot, get listed as a contributor to a popular open source project (great for resumes), and it would count toward a hacktoberfest tshirt!

Here's the issue you would close: https://github.com/asweigart/pyautogui/issues/9

Here's the offending line at the core of what needs to be updated: https://github.com/asweigart/pyscreeze/blob/master/pyscreeze/init.py#L427

Top answer
1 of 3
18

I'm the creator of PyAutoGUI. The problem you have isn't with the screen resolution, but the screen scaling. Your program will work fine on monitors at different resolutions. But at high resolutions, the text and buttons of your programs become too small and so Windows and macOS fix this with "screen scaling", which can increase the size of UI elements like text and buttons by 150% or 200%. If you have multiple monitors, each monitor can have it's own screen scaling setting; for example one monitor at 100% and another at 150%.

If you take a screenshot while a monitor is at, for example, 100% and then try to use it on a monitor that is scaled at 200%, the image won't match because the screenshot is literally half the width and length of what it would have been on the 200% monitor.

So far, there is no work around for this. Resizing the screenshot might not work because there could be subtle differences and the screenshot mechanism currently needs a pixel-perfect match. You just need to retake the screenshots on the monitor with the different screen scaling setting.

2 of 3
3

I think you'll need to take a screenshot of the image on the different resolution, and at the start of your program have it detect whether it's on the 1600x900 screen or the 1340x640 screen. Then make all the 'locateOnScreen' pieces take a variable, and depending on the screen size, replace those variables with the path to the correct image.

import pyautogui

def function():
    pyautogui.locateOnScreen(x)
    ...
    pyautogui.locateOnScreen(y)
    ...

screen = pyautogui.size()
if screen = (1600, 900):
    x = 'image1_1600_900.png'
    y = 'image2_1600_900.png'
else:
    x = 'image1_1340_640.png'
    y = 'image2_1340_640.png'

function()
🌐
PyAutoGUI
pyautogui.readthedocs.io › en › latest › roadmap.html
Roadmap — PyAutoGUI documentation - Read the Docs
Make it easier to work on systems with multiple monitors. ... Ability to set global hotkey on all platforms so that there can be an easy “kill switch” for GUI automation programs. Optional nonblocking pyautogui calls.
🌐
GitHub
github.com › asweigart › pyautogui › issues › 126
Screenshot does not work with multiple monitors · Issue #126 · asweigart/pyautogui
March 8, 2017 - (This is possibly a duplicate.) This is an enhancement to make screenshot() work on multi-monitor systems.
Author   asweigart