├── .github └── workflows │ └── main.yml ├── .gitignore ├── .travis.yml.bak ├── LICENSE.txt ├── MANIFEST.in ├── README.md ├── Vagrantfile ├── clean.py ├── doc ├── gen │ ├── python3_-m_pyscreenshot.check.speedtest.txt │ ├── python3_-m_pyscreenshot.check.speedtest_--childprocess_0.txt │ ├── python3_-m_pyscreenshot.check.versions.txt │ ├── python3_-m_pyscreenshot.examples.virtdisp.txt │ └── xmessage.png ├── generate-doc.py └── hierarchy.dot ├── format-code.sh ├── lint.sh ├── pyproject.toml ├── pyscreenshot ├── __init__.py ├── about.py ├── check │ ├── __init__.py │ ├── showall.py │ ├── speedtest.py │ └── versions.py ├── childproc.py ├── cli │ ├── __init__.py │ ├── grab.py │ └── print_backend_version.py ├── err.py ├── examples │ ├── __init__.py │ ├── grabbox.py │ ├── grabfullscreen.py │ └── virtdisp.py ├── imcodec.py ├── loader.py ├── plugins │ ├── __init__.py │ ├── backend.py │ ├── freedesktop_dbus.py │ ├── gdk3pixbuf.py │ ├── gnome_dbus.py │ ├── gnome_screenshot.py │ ├── grim.py │ ├── imagemagick.py │ ├── ksnip.py │ ├── kwin_dbus.py │ ├── mac_quartz.py │ ├── mac_screencapture.py │ ├── maim.py │ ├── msswrap.py │ ├── pilwrap.py │ ├── pyside2_grabwindow.py │ ├── pyside_grabwindow.py │ ├── qt4grabwindow.py │ ├── qt5grabwindow.py │ ├── scrot.py │ ├── wxscreen.py │ └── xwd.py ├── py.typed ├── tempexport.py └── util.py ├── pytest.ini ├── requirements-doc.txt ├── requirements-test.txt ├── requirements.txt ├── setup.py ├── tests ├── bt.py ├── config.py ├── double_disp.py ├── fillscreen.py ├── fillscreen_pygame.py ├── fillscreen_tk.py ├── genimg.py ├── image_debug.py ├── notest.py ├── size.py ├── test_check.py ├── test_default.py ├── test_examples.py ├── test_freedesktop_dbus.py ├── test_gnome_dbus.py ├── test_grim.py ├── test_imagemagick.py ├── test_ksnip.py ├── test_kwin_dbus.py ├── test_mac_quartz.py ├── test_mac_screencapture.py ├── test_maim.py ├── test_mss.py ├── test_pil.py ├── test_pygdk3.py ├── test_pyqt4.py ├── test_pyqt5.py ├── test_pyside.py ├── test_pyside2.py ├── test_scrot.py ├── test_wx.py ├── test_x_gnome_screenshot.py └── vagrant │ ├── Vagrantfile.debian10.gnome.wayland.rb │ ├── Vagrantfile.debian10.gnome.x11.rb │ ├── Vagrantfile.debian10.kde.wayland.rb │ ├── Vagrantfile.debian10.kde.x11.rb │ ├── Vagrantfile.debian11.gnome.wayland.rb │ ├── Vagrantfile.debian11.gnome.x11.rb │ ├── Vagrantfile.debian11.kde.wayland.rb │ ├── Vagrantfile.debian11.kde.x11.rb │ ├── Vagrantfile.kubuntu.20.04.rb │ ├── Vagrantfile.kubuntu.22.04.rb │ ├── Vagrantfile.kubuntu.22.10.rb │ ├── Vagrantfile.lubuntu.20.04.rb │ ├── Vagrantfile.lubuntu.22.04.rb │ ├── Vagrantfile.lubuntu.22.10.rb │ ├── Vagrantfile.ubuntu.20.04.rb │ ├── Vagrantfile.ubuntu.22.04.rb │ ├── Vagrantfile.ubuntu.22.04.sway.rb │ ├── Vagrantfile.ubuntu.22.10.rb │ ├── Vagrantfile.ubuntu.server.22.04.rb │ ├── Vagrantfile.xubuntu.20.04.rb │ ├── Vagrantfile.xubuntu.22.04.rb │ ├── Vagrantfile.xubuntu.22.10.rb │ ├── debian10.gnome.wayland.sh │ ├── debian10.gnome.x11.sh │ ├── debian10.kde.wayland.sh │ ├── debian10.kde.x11.sh │ ├── debian10_kde.sh │ ├── gdm3.sh │ ├── kubuntu.20.04.sh │ ├── kubuntu.22.04.sh │ ├── kubuntu.22.10.sh │ ├── lightdm.sh │ ├── lubuntu.20.04.sh │ ├── lubuntu.22.04.sh │ ├── lubuntu.22.10.sh │ ├── osx.sh │ ├── osx1015.1.sh │ ├── osx1015.2.sh │ ├── osx1015.sh │ ├── ubudep.sh │ ├── ubuntu.20.04.sh │ ├── ubuntu.22.04.sh │ ├── ubuntu.22.04.sway.sh │ ├── ubuntu.22.10.sh │ ├── ubuntu.server.sh │ ├── vagrant_boxes.py │ ├── win.sh │ ├── xubuntu.20.04.sh │ ├── xubuntu.22.04.sh │ └── xubuntu.22.10.sh └── tox.ini /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # For more information see: 2 | # https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python 3 | 4 | name: Python package 5 | 6 | on: 7 | schedule: 8 | # * is a special character in YAML so you have to quote this string 9 | - cron: '30 5 1 */3 *' 10 | push: 11 | pull_request: 12 | jobs: 13 | build: 14 | runs-on: ${{ matrix.os }} 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | os: 19 | - "ubuntu-20.04" 20 | - "ubuntu-22.04" 21 | - "macos-11" 22 | - "macos-12" 23 | - "windows-2019" 24 | - "windows-2022" 25 | python-version: 26 | - "3.9" 27 | - "3.10" 28 | include: 29 | - python-version: "3.8" 30 | os: ubuntu-22.04 31 | - python-version: "3.11" 32 | os: ubuntu-22.04 33 | 34 | steps: 35 | - uses: actions/checkout@v3 36 | - name: Set up Python ${{ matrix.python-version }} 37 | uses: actions/setup-python@v4 38 | with: 39 | python-version: ${{ matrix.python-version }} 40 | - name: Install ubuntu dependencies 41 | if: startsWith(matrix.os, 'ubuntu') 42 | run: | 43 | sudo apt-get update 44 | sudo apt-get install -y xvfb scrot maim imagemagick x11-utils python3-gi gir1.2-gtk-3.0 python3-wxgtk4.0 python3-pyqt5 45 | sudo apt-get install -y libsdl2-dev libsdl2-ttf-dev libsdl2-image-dev libsdl2-mixer-dev libportmidi-dev libportmidi-dev 46 | - name: Install ubuntu-20.04 dependencies 47 | if: matrix.os == 'ubuntu-20.04' 48 | run: | 49 | sudo apt-get install -y python3-pyside2.qtwidgets 50 | #wget http://ppa.launchpad.net/jan-simon/pqiv/ubuntu/pool/main/p/pqiv/pqiv_2.10.3-0~4~ubuntu20.04.1_amd64.deb 51 | #sudo apt install ./*.deb 52 | # - name: Install MacOS dependencies 53 | # if: startsWith(matrix.os, 'macos') 54 | # run: | 55 | # #brew install --cask xquartz 56 | # #brew install wxmac pyqt pyside 57 | # #brew install imagemagick pqiv 58 | - name: Install Win dependencies 59 | if: startsWith(matrix.os, 'win') 60 | run: | 61 | # choco install fsviewer -y 62 | choco install imagemagick -y 63 | choco install wxpython -y 64 | choco install gtk-runtime -y 65 | choco install pyqt4 -y 66 | choco install pyqt5 -y 67 | - name: pip install 68 | run: | 69 | pip install wheel 70 | python -m pip install . 71 | pip install -r requirements-test.txt 72 | - name: print versions 73 | run: | 74 | python -m pyscreenshot.check.versions 75 | - name: speedtest 76 | run: | 77 | python -m pyscreenshot.check.speedtest 78 | python -m pyscreenshot.check.speedtest --childprocess 0 79 | - name: speedtest --virtual-display 80 | if: startsWith(matrix.os, 'ubuntu') 81 | run: | 82 | python -m pyscreenshot.check.speedtest --virtual-display 83 | python -m pyscreenshot.check.speedtest --virtual-display --childprocess 0 84 | - name: Test with pytest 85 | if: ${{ !startsWith(matrix.os, 'ubuntu') }} 86 | run: | 87 | cd tests 88 | pytest -v . 89 | - name: Test with pytest on xvfb 90 | if: startsWith(matrix.os, 'ubuntu') 91 | run: | 92 | cd tests 93 | xvfb-run -s "-br -screen 0 900x800x24" pytest -v . 94 | - name: Archive production artifacts 95 | if: ${{ always() }} 96 | uses: actions/upload-artifact@v3 97 | with: 98 | name: testout-${{ matrix.os }}-${{ matrix.python-version }} 99 | path: | 100 | tests/testout 101 | # - name: Lint 102 | # if: matrix.os == 'ubuntu-20.04' 103 | # run: | 104 | # ./lint.sh 105 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[c|o] 2 | *.egg 3 | *.egg-info 4 | /build 5 | /dist 6 | /nbproject/ 7 | pip-log.txt 8 | /.project 9 | /.pydevproject 10 | /include 11 | /lib 12 | /lib64 13 | /bin 14 | /virtualenv 15 | 16 | *.bak 17 | */build/* 18 | */_build/* 19 | */_build/latex/* 20 | 21 | *.class 22 | #*.png 23 | .version 24 | nosetests.xml 25 | 26 | /distribute_setup.py 27 | 28 | .* 29 | !.git* 30 | !.travis* 31 | !.coveragerc 32 | 33 | 34 | sloccount.sc 35 | *.prefs 36 | MANIFEST 37 | 38 | docs/api/* 39 | 40 | *.log 41 | 42 | Vagrantfile.osx*.rb 43 | Vagrantfile.win*.rb 44 | 45 | testout -------------------------------------------------------------------------------- /.travis.yml.bak: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | matrix: 4 | include: 5 | # - name: 3.6_trusty 6 | # python: 3.6 7 | # dist: trusty 8 | 9 | - name: "3.5_xenial_with_system" 10 | python: "3.5_with_system_site_packages" 11 | dist: xenial 12 | 13 | - name: 3.7_xenial 14 | python: 3.7 15 | dist: xenial 16 | 17 | - name: 3.8_xenial 18 | python: 3.8 19 | dist: xenial 20 | 21 | - name: "3.6_bionic_with_system" 22 | python: "3.6_with_system_site_packages" 23 | dist: bionic 24 | env: PIPINST="pyside2" 25 | 26 | - name: 3.7_bionic 27 | python: 3.7 28 | dist: bionic 29 | 30 | - name: 3.8_bionic 31 | python: 3.8 32 | dist: bionic 33 | 34 | - name: "3.8_focal_with_system" 35 | python: "3.8_with_system_site_packages" 36 | dist: focal 37 | 38 | - name: 3.9_focal 39 | python: 3.9 40 | dist: focal 41 | 42 | - name: "Python 3.7 on macOS" 43 | os: osx 44 | osx_image: xcode11.2 # Python 3.7.4 running on macOS 10.14.4 45 | language: shell # 'language: python' is an error on Travis CI macOS 46 | env: PATH=/Users/travis/Library/Python/3.7/bin:$PATH PIPUSER=--user PIPINST="wxpython pyobjc-framework-Quartz pyobjc-framework-LaunchServices" 47 | 48 | # - name: "Python 3.8 on Windows" 49 | # os: windows # Windows 10.0.17134 N/A Build 17134 50 | # language: shell # 'language: python' is an error on Travis CI Windows 51 | # before_install: 52 | # - choco install python --version 3.8 53 | # - python -m pip install --upgrade pip 54 | # env: PATH=/c/Python38:/c/Python38/Scripts:$PATH 55 | 56 | # windows has no GUI 57 | 58 | addons: 59 | apt: 60 | sources: 61 | # - sourceline: "ppa:jan-simon/pqiv" 62 | - sourceline: "ppa:nilarimogard/webupd8" 63 | packages: 64 | - xvfb 65 | - scrot 66 | - maim 67 | - imagemagick 68 | # - pqiv 69 | - x11-utils 70 | homebrew: 71 | # update: true # https://travis-ci.community/t/macos-build-fails-because-of-homebrew-bundle-unknown-command/7296/10 72 | packages: 73 | - wxmac 74 | - pyqt 75 | - pyside 76 | - pqiv 77 | 78 | install: 79 | - if [ ${TRAVIS_OS_NAME} == "linux" ] && [ ${TRAVIS_DIST} != "focal" ]; then sudo add-apt-repository -y ppa:jan-simon/pqiv; fi # error in focal 80 | - if [ ${TRAVIS_OS_NAME} == "linux" ]; then sudo apt-get -q update; fi 81 | - if [ ${TRAVIS_OS_NAME} == "linux" ]; then sudo apt-get install --no-install-recommends -y --allow-unauthenticated pqiv $(./tests/packages.sh ${TRAVIS_DIST} ); fi 82 | - PYTHON=python3 83 | - if [ ${TRAVIS_OS_NAME} == "windows" ]; then PYTHON=python; fi 84 | - if [ ${TRAVIS_OS_NAME} == "osx" ]; then PYTHON=/usr/local/opt/python/libexec/bin/python; fi 85 | - $PYTHON -m pip install $PIPUSER --upgrade -r requirements-test.txt 86 | - $PYTHON -m pip install $PIPUSER --upgrade . ${PIPINST} 87 | 88 | script: 89 | - cd tests 90 | - $PYTHON -m pyscreenshot.check.versions 91 | - if [ ${TRAVIS_OS_NAME} == "osx" ]; then $PYTHON -m pyscreenshot.check.speedtest 2> /dev/null; fi 92 | - if [ ${TRAVIS_OS_NAME} == "linux" ]; then $PYTHON -m pyscreenshot.check.speedtest --virtual-display 2> /dev/null; fi 93 | - if [ ${TRAVIS_OS_NAME} == "osx" ]; then $PYTHON -m pytest -v . ; fi 94 | - if [ ${TRAVIS_OS_NAME} == "linux" ]; then xvfb-run -s "-br -screen 0 900x800x24" $PYTHON -m pytest -v . ; fi 95 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011, ponty 2 | All rights reserved. 3 | 4 | 5 | Redistribution and use in source and binary forms, with or without modification, 6 | are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, 9 | this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 17 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE* 2 | recursive-include tests *.py -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | TL;DR: Use [Pillow][15]. If Pillow doesn't work or it's slow then try pyscreenshot. 2 | 3 | The ``pyscreenshot`` module is obsolete in most cases. 4 | It was created because [PIL][5] ImageGrab module worked on Windows only, 5 | but now Linux and macOS are also [supported][15] by Pillow. 6 | There are some features in ``pyscreenshot`` which can be useful in special cases: 7 | flexible backends with the same interface, Wayland support, sometimes better performance, optional subprocessing. 8 | 9 | The module can be used to copy the contents of the screen to a [Pillow][6] image memory 10 | using various back-ends. Replacement for the [ImageGrab][15] Module. 11 | 12 | For handling image memory (e.g. saving to file, converting,..) please read [Pillow][6] documentation. 13 | 14 | Links: 15 | 16 | * home: https://github.com/ponty/pyscreenshot 17 | * PYPI: https://pypi.python.org/pypi/pyscreenshot 18 | 19 | ![workflow](https://github.com/ponty/pyscreenshot/actions/workflows/main.yml/badge.svg) 20 | 21 | Goal: 22 | Pyscreenshot tries to allow to take screenshots without installing 3rd party libraries. 23 | It is cross-platform. 24 | It is only a pure Python wrapper, a thin layer over existing back-ends. 25 | Its strategy should work on most Linux distributions: 26 | a lot of back-ends are wrapped, if at least one exists then it works, 27 | if not then one back-end should be installed. 28 | 29 | Features: 30 | 31 | * Cross-platform wrapper 32 | * Capturing the whole desktop or an area 33 | * saving to [Pillow][6] image memory 34 | * some back-ends are based on this discussion: http://stackoverflow.com/questions/69645/take-a-screenshot-via-a-python-script-linux 35 | * pure Python library 36 | * supported Python versions: 3.9, 3.10, 3.11 37 | * It has wrappers for various back-ends: 38 | * [Pillow][6] (X11, Linux gnome-screenshot, Win, macOS screencapture) 39 | * [Python MSS][14] (X11, Win, macOS) 40 | * [xdg-desktop-portal][20] (D-Bus: org.freedesktop.portal.Screenshot) (X11, Wayland) 41 | * GNOME (D-Bus: org.gnome.Shell.Screenshot) (Gnome X11/Wayland) 42 | * [scrot][1] (X11) 43 | * [maim][2] (X11) 44 | * [ImageMagick][3] (X11) 45 | * [PyQt5][8] (X11, Win, macOS) 46 | * [PySide2][10] (X11, Win, macOS) 47 | * [wxPython][12] (X11, Win, macOS) 48 | * [gnome-screenshot][13] (X11, Gnome Wayland) 49 | * [Grim][19] (Sway Wayland) 50 | * Quartz (macOS) 51 | * screencapture (macOS) 52 | * Old removed backends: 53 | * PyGTK 54 | * QtPy, 55 | * [PySide][9] 56 | * [PyQt4][7] 57 | * KDE Plasma (D-Bus: org.kde.kwin.Screenshot) 58 | * Performance is not the main target for this library, but you can benchmark the possible settings and choose the fastest one. 59 | * Interactivity is not supported. 60 | * Mouse pointer is not visible. 61 | 62 | Known problems: 63 | 64 | * KDE Wayland has on screen notification 65 | * gnome-screenshot has Flash effect (https://bugzilla.gnome.org/show_bug.cgi?id=672759) 66 | * xdg-desktop-portal can have confirmation dialog box 67 | 68 | Comparison: 69 | 70 | | system | pyscreenshot 3.1 | pillow 9.4.0 | mss 7.0.1 | 71 | | ----------------------------- | :--------------: | :----------: | :-------: | 72 | | Ubuntu 22.04 | ✅ | ❌ | ❌ | 73 | | Kubuntu 22.04 | ✅ | ✅ | ✅ | 74 | | Xubuntu 22.04 | ✅ | ✅ | ✅ | 75 | | Lubuntu 22.04 | ✅ | ✅ | ✅ | 76 | | Ubuntu Server 22.04+sway+grim | ✅ | ❌ | ❌ | 77 | 78 | 79 | Installation: 80 | 81 | ```console 82 | $ python3 -m pip install Pillow pyscreenshot 83 | ``` 84 | 85 | Examples 86 | ======== 87 | 88 | ```py 89 | # pyscreenshot/examples/grabfullscreen.py 90 | 91 | "Grab the whole screen" 92 | import pyscreenshot as ImageGrab 93 | 94 | # grab fullscreen 95 | im = ImageGrab.grab() 96 | 97 | # save image file 98 | im.save("fullscreen.png") 99 | 100 | ``` 101 | 102 | ```py 103 | # pyscreenshot/examples/grabbox.py 104 | 105 | "Grab the part of the screen" 106 | import pyscreenshot as ImageGrab 107 | 108 | # part of the screen 109 | im = ImageGrab.grab(bbox=(10, 10, 510, 510)) # X1,Y1,X2,Y2 110 | 111 | # save image file 112 | im.save("box.png") 113 | 114 | ``` 115 | 116 | ```py 117 | # pyscreenshot/examples/virtdisp.py 118 | 119 | "Create screenshot of xmessage with Xvfb" 120 | from time import sleep 121 | 122 | from easyprocess import EasyProcess 123 | from pyvirtualdisplay import Display 124 | 125 | import pyscreenshot as ImageGrab 126 | 127 | with Display(size=(100, 60)) as disp: # start Xvfb display 128 | # display is available 129 | with EasyProcess(["xmessage", "hello"]): # start xmessage 130 | sleep(1) # wait for displaying window 131 | img = ImageGrab.grab() 132 | img.save("xmessage.png") 133 | 134 | ``` 135 | 136 | Image: 137 | 138 | ![](/doc/gen/xmessage.png) 139 | 140 | Performance 141 | =========== 142 | 143 | The performance can be checked with `pyscreenshot.check.speedtest` module. 144 | Backends are started in a subprocess with default (safest) settings 145 | which is necessary to isolate them from the main process and from each other. 146 | Disabling this option makes performance much better, but it may cause problems in some cases. 147 | 148 | Test on Ubuntu 22.04 X11 149 | 150 | Versions: 151 | 152 | 153 | 154 | ```console 155 | $ python3 -m pyscreenshot.check.versions 156 | python 3.10.6 157 | pyscreenshot 3.1 158 | pil 9.0.1 159 | mss 7.0.1 160 | scrot 1.7 161 | grim ?.? 162 | maim 5.6.3 163 | imagemagick 6.9.11 164 | pyqt5 5.15.6 165 | pyside2 5.15.2 166 | wx 4.0.7 167 | pygdk3 3.42.1 168 | mac_screencapture 169 | mac_quartz 170 | freedesktop_dbus ?.? 171 | gnome_dbus ?.? 172 | gnome-screenshot 41.0 173 | ``` 174 | 175 | Backends are started in a subprocess (safest): 176 | 177 | 178 | 179 | ```console 180 | $ python3 -m pyscreenshot.check.speedtest 181 | 182 | n=10 183 | ------------------------------------------------------ 184 | default 1 sec ( 100 ms per call) 185 | pil 1.7 sec ( 167 ms per call) 186 | mss 2 sec ( 197 ms per call) 187 | scrot 1 sec ( 100 ms per call) 188 | grim 189 | maim 1.4 sec ( 135 ms per call) 190 | imagemagick 2.2 sec ( 221 ms per call) 191 | pyqt5 4 sec ( 403 ms per call) 192 | pyside2 3.9 sec ( 394 ms per call) 193 | wx 2.9 sec ( 293 ms per call) 194 | pygdk3 2.2 sec ( 218 ms per call) 195 | mac_screencapture 196 | mac_quartz 197 | gnome_dbus 198 | gnome-screenshot 4 sec ( 401 ms per call) 199 | ``` 200 | 201 | Backends are started without subprocess (fastest): 202 | 203 | 204 | 205 | ```console 206 | $ python3 -m pyscreenshot.check.speedtest --childprocess 0 207 | 208 | n=10 209 | ------------------------------------------------------ 210 | default 0.13 sec ( 12 ms per call) 211 | pil 0.12 sec ( 11 ms per call) 212 | mss 0.2 sec ( 19 ms per call) 213 | scrot 1 sec ( 99 ms per call) 214 | grim 215 | maim 1.3 sec ( 134 ms per call) 216 | imagemagick 2.2 sec ( 218 ms per call) 217 | pyqt5 1 sec ( 104 ms per call) 218 | pyside2 1 sec ( 101 ms per call) 219 | wx 0.34 sec ( 33 ms per call) 220 | pygdk3 0.23 sec ( 23 ms per call) 221 | mac_screencapture 222 | mac_quartz 223 | gnome_dbus 224 | gnome-screenshot 4.4 sec ( 437 ms per call) 225 | ``` 226 | 227 | You can force a backend: 228 | 229 | ```python 230 | import pyscreenshot as ImageGrab 231 | im = ImageGrab.grab(backend="scrot") 232 | ``` 233 | 234 | You can force if subprocess is applied, setting it to False together with `mss` or `pil` gives the best performance in most cases: 235 | 236 | ```python 237 | # best performance 238 | import pyscreenshot as ImageGrab 239 | im = ImageGrab.grab(backend="mss", childprocess=False) 240 | ``` 241 | 242 | Wayland 243 | ======= 244 | 245 | Wayland is supported with these setups: 246 | 247 | 1. using D-Bus (org.freedesktop.portal.Screenshot) on any desktop with xdg-desktop-portal. 248 | 2. using D-Bus (org.gnome.Shell.Screenshot) on GNOME. 249 | 3. using [Grim][19] on any Wayland compositor with wlr-screencopy-unstable-v1 support. (GNOME:no, KDE:no, Sway:yes) 250 | 251 | If both Wayland and X are available then Wayland is preferred 252 | because Xwayland can not be used for screenshot. Rules for decision: 253 | 254 | 1. use X if DISPLAY variable exists and XDG_SESSION_TYPE variable != "wayland" 255 | 2. use Wayland if 1. is not successful 256 | 257 | Dependencies 258 | ============ 259 | 260 | Only pure python modules are used: 261 | 262 | 1. [EasyProcess][17] for calling programs 263 | 2. [entrypoint2][18] for generating command line interface 264 | 3. [MSS][14] backend is added because it is very fast and pure and multiplatform 265 | 4. [jeepney][16] for D-Bus calls 266 | 267 | Test 268 | ==== 269 | 270 | Some Linux distributions can be tested with VirtualBox and Vagrant: 271 | 272 | ```console 273 | $ ./tests/vagrant/vagrant_boxes.py 274 | ``` 275 | 276 | Hierarchy 277 | ========= 278 | 279 | ![Alt text](https://g.gravizo.com/source/svg?https%3A%2F%2Fraw.githubusercontent.com/ponty/pyscreenshot/master/doc/hierarchy.dot) 280 | 281 | [1]: https://en.wikipedia.org/wiki/Scrot 282 | [2]: https://github.com/naelstrof/maim 283 | [3]: https://www.imagemagick.org/ 284 | [5]: https://en.wikipedia.org/wiki/Python_Imaging_Library 285 | [6]: https://pypi.org/project/Pillow/ 286 | [7]: https://pypi.org/project/PyQt4/ 287 | [8]: https://pypi.org/project/PyQt5/ 288 | [9]: https://pypi.org/project/PySide/ 289 | [10]: https://pypi.org/project/PySide2/ 290 | [12]: https://www.wxpython.org/ 291 | [13]: https://git.gnome.org/browse/gnome-screenshot/ 292 | [14]: https://github.com/BoboTiG/python-mss 293 | [15]: https://pillow.readthedocs.org/en/latest/reference/ImageGrab.html 294 | [16]: https://pypi.org/project/jeepney/ 295 | [17]: https://github.com/ponty/EasyProcess 296 | [18]: https://github.com/ponty/entrypoint2 297 | [19]: https://github.com/emersion/grim 298 | [20]: https://github.com/flatpak/xdg-desktop-portal 299 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | Vagrant.configure(2) do |config| 2 | config.vm.box = "ubuntu/jammy64" 3 | 4 | config.vm.boot_timeout = 600 5 | 6 | config.vm.provider "virtualbox" do |vb| 7 | # vb.gui = true 8 | vb.memory = "2048" 9 | vb.name = "pyscreenshot_ubuntu.server.22.04_main" 10 | end 11 | 12 | config.vm.provision "shell", path: "tests/vagrant/ubuntu.server.sh", privileged: true 13 | 14 | config.ssh.extra_args = ["-t", "cd /vagrant; bash --login"] 15 | end 16 | -------------------------------------------------------------------------------- /clean.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import pathlib 3 | import shutil 4 | 5 | [shutil.rmtree(p) for p in pathlib.Path(".").glob(".tox")] 6 | [shutil.rmtree(p) for p in pathlib.Path(".").glob("dist")] 7 | [shutil.rmtree(p) for p in pathlib.Path(".").glob("*.egg-info")] 8 | [shutil.rmtree(p) for p in pathlib.Path(".").glob("build")] 9 | [p.unlink() for p in pathlib.Path(".").rglob("*.py[co]")] 10 | [p.rmdir() for p in pathlib.Path(".").rglob("__pycache__")] 11 | [p.unlink() for p in pathlib.Path(".").rglob("*.log")] 12 | [shutil.rmtree(p) for p in pathlib.Path(".").glob("testout")] 13 | [shutil.rmtree(p) for p in pathlib.Path("tests").glob("testout")] 14 | -------------------------------------------------------------------------------- /doc/gen/python3_-m_pyscreenshot.check.speedtest.txt: -------------------------------------------------------------------------------- 1 | $ python3 -m pyscreenshot.check.speedtest 2 | 3 | n=10 4 | ------------------------------------------------------ 5 | default 1 sec ( 100 ms per call) 6 | pil 1.7 sec ( 167 ms per call) 7 | mss 2 sec ( 197 ms per call) 8 | scrot 1 sec ( 100 ms per call) 9 | grim 10 | maim 1.4 sec ( 135 ms per call) 11 | imagemagick 2.2 sec ( 221 ms per call) 12 | pyqt5 4 sec ( 403 ms per call) 13 | pyside2 3.9 sec ( 394 ms per call) 14 | wx 2.9 sec ( 293 ms per call) 15 | pygdk3 2.2 sec ( 218 ms per call) 16 | mac_screencapture 17 | mac_quartz 18 | gnome_dbus 19 | gnome-screenshot 4 sec ( 401 ms per call) -------------------------------------------------------------------------------- /doc/gen/python3_-m_pyscreenshot.check.speedtest_--childprocess_0.txt: -------------------------------------------------------------------------------- 1 | $ python3 -m pyscreenshot.check.speedtest --childprocess 0 2 | 3 | n=10 4 | ------------------------------------------------------ 5 | default 0.13 sec ( 12 ms per call) 6 | pil 0.12 sec ( 11 ms per call) 7 | mss 0.2 sec ( 19 ms per call) 8 | scrot 1 sec ( 99 ms per call) 9 | grim 10 | maim 1.3 sec ( 134 ms per call) 11 | imagemagick 2.2 sec ( 218 ms per call) 12 | pyqt5 1 sec ( 104 ms per call) 13 | pyside2 1 sec ( 101 ms per call) 14 | wx 0.34 sec ( 33 ms per call) 15 | pygdk3 0.23 sec ( 23 ms per call) 16 | mac_screencapture 17 | mac_quartz 18 | gnome_dbus 19 | gnome-screenshot 4.4 sec ( 437 ms per call) -------------------------------------------------------------------------------- /doc/gen/python3_-m_pyscreenshot.check.versions.txt: -------------------------------------------------------------------------------- 1 | $ python3 -m pyscreenshot.check.versions 2 | python 3.10.6 3 | pyscreenshot 3.1 4 | pil 9.0.1 5 | mss 7.0.1 6 | scrot 1.7 7 | grim ?.? 8 | maim 5.6.3 9 | imagemagick 6.9.11 10 | pyqt5 5.15.6 11 | pyside2 5.15.2 12 | wx 4.0.7 13 | pygdk3 3.42.1 14 | mac_screencapture 15 | mac_quartz 16 | freedesktop_dbus ?.? 17 | gnome_dbus ?.? 18 | gnome-screenshot 41.0 -------------------------------------------------------------------------------- /doc/gen/python3_-m_pyscreenshot.examples.virtdisp.txt: -------------------------------------------------------------------------------- 1 | $ python3 -m pyscreenshot.examples.virtdisp 2 | -------------------------------------------------------------------------------- /doc/gen/xmessage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ponty/pyscreenshot/c9f6051fe339b4b4c59ef75aa17140211e51606f/doc/gen/xmessage.png -------------------------------------------------------------------------------- /doc/generate-doc.py: -------------------------------------------------------------------------------- 1 | import glob 2 | import logging 3 | import os 4 | 5 | from easyprocess import EasyProcess 6 | from entrypoint2 import entrypoint 7 | 8 | # (cmd,grab,background) 9 | commands = [ 10 | "python3 -m pyscreenshot.check.versions", 11 | "python3 -m pyscreenshot.examples.virtdisp", 12 | "python3 -m pyscreenshot.check.speedtest", 13 | "python3 -m pyscreenshot.check.speedtest --childprocess 0", 14 | ] 15 | 16 | 17 | def empty_dir(dir): 18 | files = glob.glob(os.path.join(dir, "*")) 19 | for f in files: 20 | os.remove(f) 21 | 22 | 23 | @entrypoint 24 | def main(): 25 | gendir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "gen") 26 | logging.info("gendir: %s", gendir) 27 | os.makedirs(gendir, exist_ok=True) 28 | empty_dir(gendir) 29 | pls = [] 30 | try: 31 | os.chdir("gen") 32 | for cmd in commands: 33 | logging.info("cmd: %s", cmd) 34 | fname_base = cmd.replace(" ", "_") 35 | fname = fname_base + ".txt" 36 | logging.info("cmd: %s", cmd) 37 | print("file name: %s" % fname) 38 | with open(fname, "w") as f: 39 | f.write("$ " + cmd + "\n") 40 | p = EasyProcess(cmd).call() 41 | f.write(p.stdout) 42 | f.write(p.stderr) 43 | pls += [p] 44 | finally: 45 | os.chdir("..") 46 | for p in pls: 47 | p.stop() 48 | embedme = EasyProcess(["embedme", "../README.md"]) 49 | embedme.call() 50 | print(embedme.stdout) 51 | assert embedme.return_code == 0 52 | assert "but file does not exist" not in embedme.stdout 53 | -------------------------------------------------------------------------------- /doc/hierarchy.dot: -------------------------------------------------------------------------------- 1 | digraph G { 2 | rankdir=LR; 3 | node [fontsize=8]; 4 | fontsize=8; 5 | 6 | subgraph cluster_0 { 7 | label = "pyscreenshot"; 8 | style=filled; 9 | fillcolor=lightgrey; 10 | subgraph cluster_1 { 11 | label = "API"; 12 | style=filled; 13 | fillcolor=white; 14 | 15 | pyscreenshot; 16 | } 17 | subgraph cluster_2 { 18 | style=filled; 19 | fillcolor=white; 20 | label = "backends"; 21 | 22 | // pyscreenshot -> Qt4GrabWindowWrapper; 23 | pyscreenshot -> Qt5GrabWindowWrapper; 24 | // pyscreenshot -> PySideGrabWindowWrapper; 25 | pyscreenshot -> PySide2GrabWindowWrapper; 26 | pyscreenshot -> PilWrapper; 27 | pyscreenshot -> ImagemagickWrapper; 28 | pyscreenshot -> WxScreenWrapper; 29 | pyscreenshot -> ScrotWrapper; 30 | pyscreenshot -> MaimWrapper; 31 | pyscreenshot -> MacQuartzWrapper 32 | pyscreenshot -> ScreencaptureWrapper 33 | pyscreenshot -> Gdk3PixbufWrapper 34 | pyscreenshot -> GnomeScreenshotWrapper 35 | pyscreenshot -> FreedesktopDBusWrapper 36 | pyscreenshot -> GnomeDBusWrapper 37 | // pyscreenshot -> KwinDBusWrapper 38 | pyscreenshot -> MssWrapper 39 | pyscreenshot -> GrimWrapper; 40 | } 41 | } 42 | subgraph cluster_3 { 43 | Pillow; 44 | wxPython; 45 | // PyQt4; 46 | PyQt5; 47 | // PySide; 48 | PySide2; 49 | screencapture; 50 | Quartz; 51 | Scrot; 52 | Maim; 53 | Imagemagick; 54 | "gnome-screenshot"; 55 | Grim; 56 | } 57 | subgraph cluster_4 { 58 | label = "GUI library"; 59 | // Qt4; 60 | Qt5; 61 | wxWidgets; 62 | "GTK+"; 63 | } 64 | subgraph cluster_5 { 65 | label = "D-Bus"; 66 | "D-Bus (GNOME)"; 67 | // "D-Bus (KDE)"; 68 | "D-Bus (xdg-desktop-portal)"; 69 | 70 | "jeepney (GNOME)"; 71 | // "jeepney (KDE)"; 72 | "jeepney (xdg-desktop-portal)"; 73 | } 74 | subgraph cluster_9 { 75 | label = "Display"; 76 | Windows; 77 | macOS; 78 | X11; 79 | Wayland; 80 | } 81 | // Qt4GrabWindowWrapper -> PyQt4 -> Qt4; 82 | // PyQt4 -> Qt5; 83 | Qt5GrabWindowWrapper -> PyQt5 -> Qt5; 84 | // PySideGrabWindowWrapper -> PySide -> Qt4; 85 | PySide2GrabWindowWrapper -> PySide2 -> Qt5; 86 | 87 | // Qt4 -> macOS; 88 | // Qt4 -> Windows; 89 | // Qt4 -> X11; 90 | Qt5 -> macOS; 91 | Qt5 -> Windows; 92 | Qt5 -> X11; 93 | 94 | PilWrapper -> Pillow -> Windows; 95 | Pillow -> macOS; 96 | Pillow -> X11; 97 | Pillow -> "gnome-screenshot"; 98 | 99 | ImagemagickWrapper -> Imagemagick -> X11; 100 | ScrotWrapper -> Scrot -> X11; 101 | // KsnipWrapper -> ksnip; 102 | MaimWrapper -> Maim -> X11; 103 | 104 | Gnome -> Wayland; 105 | Gnome -> X11; 106 | // KDE -> Wayland; 107 | // KDE -> X11; 108 | PortalDesktop -> Wayland; 109 | PortalDesktop -> X11; 110 | 111 | "D-Bus (GNOME)" -> Gnome; 112 | // "D-Bus (KDE)" -> KDE; 113 | "D-Bus (xdg-desktop-portal)" -> PortalDesktop; 114 | 115 | "jeepney (GNOME)"-> "D-Bus (GNOME)"; 116 | // "jeepney (KDE)"-> "D-Bus (KDE)"; 117 | "jeepney (xdg-desktop-portal)"-> "D-Bus (xdg-desktop-portal)"; 118 | 119 | GnomeDBusWrapper -> "jeepney (GNOME)"; 120 | // KwinDBusWrapper -> "jeepney (KDE)"; 121 | FreedesktopDBusWrapper -> "jeepney (xdg-desktop-portal)"; 122 | 123 | GnomeScreenshotWrapper -> "gnome-screenshot"; 124 | "gnome-screenshot" -> X11; 125 | "gnome-screenshot" -> "D-Bus (GNOME)"; 126 | 127 | //"GTK+" -> macOS; 128 | "GTK+" -> Windows; 129 | "GTK+" -> X11; 130 | 131 | Gdk3PixbufWrapper -> PyGObject -> GdkPixbuf; 132 | GdkPixbuf -> macOS; 133 | GdkPixbuf -> Windows; 134 | GdkPixbuf -> X11; 135 | 136 | WxScreenWrapper -> wxPython -> wxWidgets; 137 | wxWidgets -> "GTK+"; 138 | wxWidgets -> macOS; 139 | wxWidgets -> Windows; 140 | wxWidgets -> X11; 141 | 142 | MacQuartzWrapper -> Quartz -> macOS; 143 | ScreencaptureWrapper -> screencapture -> macOS; 144 | 145 | MssWrapper -> X11; 146 | MssWrapper -> macOS; 147 | MssWrapper -> Windows; 148 | 149 | GrimWrapper -> Grim -> Wayland; 150 | 151 | application -> pyscreenshot; 152 | 153 | } 154 | -------------------------------------------------------------------------------- /format-code.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | autoflake -i -r --remove-all-unused-imports . 4 | autoflake -i -r --remove-unused-variables . 5 | isort . 6 | black . 7 | -------------------------------------------------------------------------------- /lint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | # TODO: max-complexity=10 4 | python3 -m flake8 . --max-complexity=14 --max-line-length=127 --extend-ignore=E203 5 | python3 -m mypy "pyscreenshot" 6 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools"] 3 | build-backend = "setuptools.build_meta" 4 | -------------------------------------------------------------------------------- /pyscreenshot/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import List, Optional, Tuple 3 | 4 | from PIL import Image 5 | 6 | from pyscreenshot.about import __version__ 7 | from pyscreenshot.childproc import childprocess_backend_version 8 | from pyscreenshot.loader import FailedBackendError, backend_dict, backend_grab 9 | 10 | ADDITIONAL_IMPORTS = [FailedBackendError] 11 | 12 | log = logging.getLogger(__name__) 13 | log.debug("version=%s", __version__) 14 | 15 | 16 | def grab( 17 | bbox: Optional[Tuple[int, int, int, int]] = None, 18 | childprocess: bool = True, 19 | backend: Optional[str] = None, 20 | ) -> "Image": 21 | """Copy the contents of the screen to PIL image memory. 22 | 23 | :param bbox: optional bounding box (x1,y1,x2,y2) 24 | :param childprocess: run back-end in new process using popen. (bool) 25 | This isolates back-ends from each other and from main process. 26 | Leave it as it is (True) to have a safe setting. 27 | Set it False to improve performance, but then conflicts are possible. 28 | :param backend: back-end can be forced if set (examples:scrot, wx,..), 29 | otherwise back-end is automatic 30 | """ 31 | if bbox: 32 | x1, y1, x2, y2 = bbox 33 | if x2 <= x1: 34 | raise ValueError("bbox x2<=x1") 35 | if y2 <= y1: 36 | raise ValueError("bbox y2<=y1") 37 | return backend_grab(backend, bbox, childprocess) 38 | 39 | 40 | def backends() -> List[str]: 41 | """Back-end names as a list. 42 | 43 | :return: back-ends as string list 44 | """ 45 | return list(backend_dict.keys()) 46 | 47 | 48 | def backend_version(backend: str) -> str: 49 | """Back-end version. 50 | 51 | :param backend: back-end (examples:scrot, wx,..) 52 | :return: version as string 53 | """ 54 | return childprocess_backend_version(backend) 55 | -------------------------------------------------------------------------------- /pyscreenshot/about.py: -------------------------------------------------------------------------------- 1 | __version__ = "3.1" 2 | -------------------------------------------------------------------------------- /pyscreenshot/check/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ponty/pyscreenshot/c9f6051fe339b4b4c59ef75aa17140211e51606f/pyscreenshot/check/__init__.py -------------------------------------------------------------------------------- /pyscreenshot/check/showall.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from entrypoint2 import entrypoint 4 | 5 | import pyscreenshot 6 | from pyscreenshot import backends 7 | 8 | 9 | @entrypoint 10 | def show(): 11 | im = [] 12 | blist = [] 13 | 14 | for x in backends(): 15 | try: 16 | print("--> grabbing by " + x) 17 | im.append(pyscreenshot.grab(bbox=(500, 400, 800, 600), backend=x)) 18 | blist.append(x) 19 | except Exception as e: 20 | print(e) 21 | print(im) 22 | print(blist) 23 | for x in im: 24 | x.show() 25 | time.sleep(0.5) 26 | -------------------------------------------------------------------------------- /pyscreenshot/check/speedtest.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | 4 | from entrypoint2 import entrypoint 5 | 6 | import pyscreenshot 7 | from pyscreenshot.plugins.freedesktop_dbus import FreedesktopDBusWrapper 8 | from pyscreenshot.plugins.gnome_dbus import GnomeDBusWrapper 9 | from pyscreenshot.plugins.gnome_screenshot import GnomeScreenshotWrapper 10 | from pyscreenshot.plugins.kwin_dbus import KwinDBusWrapper 11 | from pyscreenshot.util import run_mod_as_subproc 12 | 13 | 14 | def run(force_backend, n, childprocess, bbox=None): 15 | sys.stdout.write("%-20s\t" % force_backend) 16 | sys.stdout.flush() # before any crash 17 | # if force_backend == "freedesktop_dbus": 18 | # return 19 | if force_backend == "default": 20 | force_backend = None 21 | try: 22 | start = time.time() 23 | for _ in range(n): 24 | pyscreenshot.grab( 25 | backend=force_backend, childprocess=childprocess, bbox=bbox 26 | ) 27 | end = time.time() 28 | dt = end - start 29 | s = "%-4.2g sec\t" % dt 30 | s += "(%5d ms per call)" % (1000.0 * dt / n) 31 | sys.stdout.write(s) 32 | finally: 33 | print("") 34 | 35 | 36 | novirt = [ 37 | GnomeDBusWrapper.name, 38 | KwinDBusWrapper.name, 39 | GnomeScreenshotWrapper.name, 40 | FreedesktopDBusWrapper.name, 41 | ] 42 | 43 | 44 | def run_all(n, childprocess_param, virtual_only=True, bbox=None): 45 | debug = True 46 | print("") 47 | print("n=%s" % n) 48 | print("------------------------------------------------------") 49 | 50 | if bbox: 51 | x1, y1, x2, y2 = map(str, bbox) 52 | bbox = ":".join(map(str, (x1, y1, x2, y2))) 53 | bboxpar = ["--bbox", bbox] 54 | else: 55 | bboxpar = [] 56 | if debug: 57 | debugpar = ["--debug"] 58 | else: 59 | debugpar = [] 60 | for x in ["default"] + pyscreenshot.backends(): 61 | if x == "freedesktop_dbus": 62 | continue 63 | backendpar = ["--backend", x] 64 | # skip non X backends 65 | if virtual_only and x in novirt: 66 | continue 67 | p = run_mod_as_subproc( 68 | "pyscreenshot.check.speedtest", 69 | ["--childprocess", childprocess_param, "--number", str(n)] 70 | + bboxpar 71 | + debugpar 72 | + backendpar, 73 | ) 74 | print(p.stdout) 75 | 76 | 77 | @entrypoint 78 | def speedtest(virtual_display=False, backend="", childprocess="", bbox="", number=10): 79 | """Performance test of all back-ends. 80 | 81 | :param virtual_display: run with Xvfb 82 | :param bbox: bounding box coordinates x1:y1:x2:y2 83 | :param backend: back-end can be forced if set (example:default, scrot, wx,..), 84 | otherwise all back-ends are tested 85 | :param childprocess: pyscreenshot parameter childprocess (0/1) 86 | :param number: number of screenshots for each backend (default:10) 87 | """ 88 | childprocess_param = childprocess 89 | if childprocess == "": 90 | childprocess = True # default 91 | elif childprocess == "0": 92 | childprocess = False 93 | elif childprocess == "1": 94 | childprocess = True 95 | else: 96 | raise ValueError("invalid childprocess value") 97 | 98 | if bbox: 99 | x1, y1, x2, y2 = map(int, bbox.split(":")) 100 | bbox = x1, y1, x2, y2 101 | else: 102 | bbox = None 103 | 104 | def f(virtual_only): 105 | if backend: 106 | try: 107 | run(backend, number, childprocess, bbox=bbox) 108 | except pyscreenshot.FailedBackendError: 109 | pass 110 | else: 111 | run_all(number, childprocess_param, virtual_only=virtual_only, bbox=bbox) 112 | 113 | if virtual_display: 114 | from pyvirtualdisplay import Display 115 | 116 | with Display(visible=0): 117 | f(virtual_only=True) 118 | else: 119 | f(virtual_only=False) 120 | -------------------------------------------------------------------------------- /pyscreenshot/check/versions.py: -------------------------------------------------------------------------------- 1 | import platform 2 | 3 | import pyscreenshot 4 | from pyscreenshot import backend_version 5 | 6 | 7 | def print_name_version(name, version): 8 | s = "{:<20} {}".format(name, version) 9 | print(s) 10 | 11 | 12 | def print_versions(): 13 | print_name_version("python", platform.python_version()) 14 | print_name_version("pyscreenshot", pyscreenshot.__version__) 15 | 16 | for name in pyscreenshot.backends(): 17 | v = backend_version(name) 18 | if not v: 19 | v = "" 20 | print_name_version(name, v) 21 | 22 | 23 | if __name__ == "__main__": 24 | print_versions() 25 | -------------------------------------------------------------------------------- /pyscreenshot/childproc.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | from tempfile import TemporaryDirectory 4 | 5 | from pyscreenshot.err import FailedBackendError 6 | from pyscreenshot.imcodec import codec 7 | from pyscreenshot.util import run_mod_as_subproc 8 | 9 | log = logging.getLogger(__name__) 10 | 11 | 12 | def childprocess_backend_version(backend): 13 | p = run_mod_as_subproc("pyscreenshot.cli.print_backend_version", [backend]) 14 | if p.return_code != 0: 15 | log.warning(p) 16 | raise FailedBackendError(p) 17 | 18 | return p.stdout 19 | 20 | 21 | def childprocess_grab(backend, bbox): 22 | with TemporaryDirectory(prefix="pyscreenshot") as tmpdirname: 23 | filename = os.path.join(tmpdirname, "screenshot.png") 24 | cmd = ["--filename", filename] 25 | if bbox: 26 | x1, y1, x2, y2 = map(str, bbox) 27 | bbox = ":".join(map(str, (x1, y1, x2, y2))) 28 | cmd += ["--bbox", bbox] 29 | if backend: 30 | cmd += ["--backend", backend] 31 | if log.isEnabledFor(logging.DEBUG): 32 | cmd += ["--debug"] 33 | 34 | p = run_mod_as_subproc("pyscreenshot.cli.grab", cmd) 35 | if p.return_code != 0: 36 | # log.debug(p) 37 | raise FailedBackendError(p) 38 | 39 | data = open(filename, "rb").read() 40 | data = codec[1](data) 41 | return data 42 | -------------------------------------------------------------------------------- /pyscreenshot/cli/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ponty/pyscreenshot/c9f6051fe339b4b4c59ef75aa17140211e51606f/pyscreenshot/cli/__init__.py -------------------------------------------------------------------------------- /pyscreenshot/cli/grab.py: -------------------------------------------------------------------------------- 1 | from entrypoint2 import entrypoint 2 | 3 | import pyscreenshot 4 | from pyscreenshot.imcodec import codec 5 | 6 | 7 | @entrypoint 8 | def main(filename="", bbox="", backend="", show=False): 9 | """Copy the contents of the screen to file. 10 | 11 | :param filename: output file 12 | :param show: show image 13 | :param bbox: bounding box coordinates x1:y1:x2:y2 14 | :param backend: back-end can be forced if set (example:scrot, wx,..), 15 | otherwise back-end is automatic 16 | """ 17 | backend = backend if backend else None 18 | if bbox: 19 | x1, y1, x2, y2 = map(int, bbox.split(":")) 20 | bbox = x1, y1, x2, y2 21 | else: 22 | bbox = None 23 | 24 | im = pyscreenshot.grab(bbox=bbox, childprocess=False, backend=backend) 25 | if filename: 26 | b = codec[0](im) 27 | with open(filename, "wb") as f: 28 | f.write(b) 29 | if show: 30 | im.show() 31 | -------------------------------------------------------------------------------- /pyscreenshot/cli/print_backend_version.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from entrypoint2 import entrypoint 4 | 5 | from pyscreenshot.loader import backend_version2 6 | 7 | log = logging.getLogger(__name__) 8 | 9 | 10 | @entrypoint 11 | def main(backend): 12 | """Print pyscreenshot back-end version. 13 | 14 | :param backend: back-end (example:scrot, wx,..) 15 | """ 16 | backend = backend if backend else None 17 | 18 | try: 19 | v = backend_version2(backend) 20 | except Exception as e: 21 | log.warning(e) 22 | v = "" 23 | print(v) 24 | -------------------------------------------------------------------------------- /pyscreenshot/err.py: -------------------------------------------------------------------------------- 1 | class FailedBackendError(Exception): 2 | pass 3 | -------------------------------------------------------------------------------- /pyscreenshot/examples/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ponty/pyscreenshot/c9f6051fe339b4b4c59ef75aa17140211e51606f/pyscreenshot/examples/__init__.py -------------------------------------------------------------------------------- /pyscreenshot/examples/grabbox.py: -------------------------------------------------------------------------------- 1 | "Grab the part of the screen" 2 | import pyscreenshot as ImageGrab 3 | 4 | # part of the screen 5 | im = ImageGrab.grab(bbox=(10, 10, 510, 510)) # X1,Y1,X2,Y2 6 | 7 | # save image file 8 | im.save("box.png") 9 | -------------------------------------------------------------------------------- /pyscreenshot/examples/grabfullscreen.py: -------------------------------------------------------------------------------- 1 | "Grab the whole screen" 2 | import pyscreenshot as ImageGrab 3 | 4 | # grab fullscreen 5 | im = ImageGrab.grab() 6 | 7 | # save image file 8 | im.save("fullscreen.png") 9 | -------------------------------------------------------------------------------- /pyscreenshot/examples/virtdisp.py: -------------------------------------------------------------------------------- 1 | "Create screenshot of xmessage with Xvfb" 2 | from time import sleep 3 | 4 | from easyprocess import EasyProcess 5 | from pyvirtualdisplay import Display 6 | 7 | import pyscreenshot as ImageGrab 8 | 9 | with Display(size=(100, 60)) as disp: # start Xvfb display 10 | # display is available 11 | with EasyProcess(["xmessage", "hello"]): # start xmessage 12 | sleep(1) # wait for displaying window 13 | img = ImageGrab.grab() 14 | img.save("xmessage.png") 15 | -------------------------------------------------------------------------------- /pyscreenshot/imcodec.py: -------------------------------------------------------------------------------- 1 | import io 2 | 3 | from PIL import Image 4 | 5 | # def _coder(im): 6 | # if im: 7 | # data = { 8 | # 'pixels': im.tobytes(), 9 | # 'size': im.size, 10 | # 'mode': im.mode, 11 | # } 12 | # return data 13 | # 14 | # 15 | # def _decoder(data): 16 | # if data: 17 | # im = Image.frombytes(data['mode'], data['size'], data['pixels']) 18 | # return im 19 | 20 | 21 | def _coder(im): 22 | if im: 23 | b = io.BytesIO() 24 | im.save(b, format="png") 25 | data = b.getvalue() 26 | return data 27 | 28 | 29 | def _decoder(data): 30 | if data: 31 | b = io.BytesIO(data) 32 | im = Image.open(b) 33 | return im 34 | 35 | 36 | codec = (_coder, _decoder) 37 | -------------------------------------------------------------------------------- /pyscreenshot/loader.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import traceback 3 | 4 | from pyscreenshot.childproc import childprocess_grab 5 | from pyscreenshot.err import FailedBackendError 6 | from pyscreenshot.plugins.freedesktop_dbus import FreedesktopDBusWrapper 7 | from pyscreenshot.plugins.gdk3pixbuf import Gdk3PixbufWrapper 8 | from pyscreenshot.plugins.gnome_dbus import GnomeDBusWrapper 9 | from pyscreenshot.plugins.gnome_screenshot import GnomeScreenshotWrapper 10 | from pyscreenshot.plugins.grim import GrimWrapper 11 | from pyscreenshot.plugins.imagemagick import ImagemagickWrapper 12 | 13 | # from pyscreenshot.plugins.kwin_dbus import KwinDBusWrapper 14 | from pyscreenshot.plugins.mac_quartz import MacQuartzWrapper 15 | from pyscreenshot.plugins.mac_screencapture import ScreencaptureWrapper 16 | from pyscreenshot.plugins.maim import MaimWrapper 17 | from pyscreenshot.plugins.msswrap import MssWrapper 18 | from pyscreenshot.plugins.pilwrap import PilWrapper 19 | from pyscreenshot.plugins.pyside2_grabwindow import PySide2GrabWindow 20 | 21 | # from pyscreenshot.plugins.pyside_grabwindow import PySideGrabWindow 22 | # from pyscreenshot.plugins.qt4grabwindow import Qt4GrabWindow 23 | from pyscreenshot.plugins.qt5grabwindow import Qt5GrabWindow 24 | from pyscreenshot.plugins.scrot import ScrotWrapper 25 | from pyscreenshot.plugins.wxscreen import WxScreen 26 | 27 | # from pyscreenshot.plugins.ksnip import KsnipWrapper 28 | from pyscreenshot.util import ( 29 | platform_is_linux, 30 | platform_is_osx, 31 | platform_is_win, 32 | use_x_display, 33 | ) 34 | 35 | log = logging.getLogger(__name__) 36 | 37 | 38 | backend_dict = { 39 | PilWrapper.name: PilWrapper, 40 | MssWrapper.name: MssWrapper, 41 | ScrotWrapper.name: ScrotWrapper, 42 | GrimWrapper.name: GrimWrapper, 43 | MaimWrapper.name: MaimWrapper, 44 | ImagemagickWrapper.name: ImagemagickWrapper, 45 | Qt5GrabWindow.name: Qt5GrabWindow, 46 | # Qt4GrabWindow.name: Qt4GrabWindow, 47 | PySide2GrabWindow.name: PySide2GrabWindow, 48 | # PySideGrabWindow.name: PySideGrabWindow, 49 | WxScreen.name: WxScreen, 50 | Gdk3PixbufWrapper.name: Gdk3PixbufWrapper, 51 | ScreencaptureWrapper.name: ScreencaptureWrapper, 52 | MacQuartzWrapper.name: MacQuartzWrapper, 53 | FreedesktopDBusWrapper.name: FreedesktopDBusWrapper, 54 | GnomeDBusWrapper.name: GnomeDBusWrapper, 55 | GnomeScreenshotWrapper.name: GnomeScreenshotWrapper, 56 | # KwinDBusWrapper.name: KwinDBusWrapper, 57 | # XwdWrapper.name: XwdWrapper, 58 | # KsnipWrapper.name: KsnipWrapper, 59 | } 60 | 61 | 62 | def qt(): 63 | yield Qt5GrabWindow 64 | # yield Qt4GrabWindow 65 | yield PySide2GrabWindow 66 | # yield PySideGrabWindow 67 | 68 | 69 | def backends(childprocess): 70 | # the order is based on performance 71 | if platform_is_linux(): 72 | if use_x_display(): 73 | if childprocess: 74 | yield ScrotWrapper 75 | yield PilWrapper 76 | yield MssWrapper 77 | else: 78 | yield PilWrapper 79 | yield MssWrapper 80 | yield ScrotWrapper 81 | yield MaimWrapper 82 | yield ImagemagickWrapper 83 | yield Gdk3PixbufWrapper 84 | yield WxScreen 85 | for x in qt(): 86 | yield x 87 | 88 | yield GnomeDBusWrapper 89 | 90 | # on screen notification 91 | # "The process is not authorized to take a screenshot" 92 | # yield KwinDBusWrapper 93 | 94 | # flash effect 95 | yield GnomeScreenshotWrapper 96 | 97 | yield GrimWrapper 98 | # yield KsnipWrapper 99 | 100 | # confirmation dialog 101 | yield FreedesktopDBusWrapper 102 | 103 | elif platform_is_osx(): 104 | yield PilWrapper 105 | yield MssWrapper 106 | 107 | # alternatives for older pillow versions 108 | yield ScreencaptureWrapper 109 | yield MacQuartzWrapper 110 | 111 | # qt has some color difference 112 | 113 | # does not work: Gdk3, wx, Imagemagick 114 | 115 | elif platform_is_win(): 116 | yield PilWrapper 117 | yield MssWrapper 118 | else: 119 | yield PilWrapper 120 | yield MssWrapper 121 | for x in backend_dict.values(): 122 | yield x 123 | 124 | 125 | def select_childprocess(childprocess, backend_class): 126 | if backend_class.is_subprocess: 127 | # backend is always a subprocess -> nothing to do 128 | return False 129 | 130 | return childprocess 131 | 132 | 133 | def auto(bbox, childprocess): 134 | im = None 135 | for backend_class in backends(childprocess): 136 | backend_name = backend_class.name 137 | try: 138 | if select_childprocess(childprocess, backend_class): 139 | log.debug('running "%s" in child process', backend_name) 140 | im = childprocess_grab(backend_name, bbox) 141 | else: 142 | obj = backend_class() 143 | 144 | im = obj.grab(bbox) 145 | break 146 | except Exception: 147 | msg = traceback.format_exc() 148 | log.debug(msg) 149 | if not im: 150 | msg = "All backends failed!" 151 | raise FailedBackendError(msg) 152 | 153 | return im 154 | 155 | 156 | def force(backend_name, bbox, childprocess): 157 | backend_class = backend_dict[backend_name] 158 | if select_childprocess(childprocess, backend_class): 159 | log.debug('running "%s" in child process', backend_name) 160 | return childprocess_grab(backend_name, bbox) 161 | else: 162 | obj = backend_class() 163 | im = obj.grab(bbox) 164 | return im 165 | 166 | 167 | def backend_grab(backend, bbox, childprocess): 168 | if backend: 169 | return force(backend, bbox, childprocess) 170 | else: 171 | return auto(bbox, childprocess) 172 | 173 | 174 | def backend_version2(backend_name): 175 | backend_class = backend_dict[backend_name] 176 | obj = backend_class() 177 | v = obj.backend_version() 178 | return v 179 | -------------------------------------------------------------------------------- /pyscreenshot/plugins/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ponty/pyscreenshot/c9f6051fe339b4b4c59ef75aa17140211e51606f/pyscreenshot/plugins/__init__.py -------------------------------------------------------------------------------- /pyscreenshot/plugins/backend.py: -------------------------------------------------------------------------------- 1 | UNKNOWN_VERSION = "?.?" 2 | 3 | 4 | class CBackend(object): 5 | is_subprocess = False 6 | -------------------------------------------------------------------------------- /pyscreenshot/plugins/freedesktop_dbus.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | 4 | from PIL import Image 5 | 6 | from pyscreenshot.plugins.backend import UNKNOWN_VERSION, CBackend 7 | 8 | log = logging.getLogger(__name__) 9 | 10 | 11 | class FreedesktopDBusError(Exception): 12 | pass 13 | 14 | 15 | class FreedesktopDBusWrapper(CBackend): 16 | name = "freedesktop_dbus" 17 | is_subprocess = True 18 | 19 | def grab(self, bbox=None): 20 | has_jeepney = False 21 | try: 22 | from jeepney import DBusAddress, new_method_call 23 | from jeepney.bus_messages import MatchRule, message_bus 24 | from jeepney.io.blocking import Proxy, open_dbus_connection 25 | 26 | has_jeepney = True 27 | except ImportError: 28 | pass 29 | 30 | if not has_jeepney: 31 | raise FreedesktopDBusError("jeepney library is missing") 32 | 33 | portal = DBusAddress( 34 | object_path="/org/freedesktop/portal/desktop", 35 | bus_name="org.freedesktop.portal.Desktop", 36 | ) 37 | screenshot = portal.with_interface("org.freedesktop.portal.Screenshot") 38 | 39 | conn = open_dbus_connection() 40 | 41 | token = "pyscreenshot" 42 | sender_name = conn.unique_name[1:].replace(".", "_") 43 | handle = f"/org/freedesktop/portal/desktop/request/{sender_name}/{token}" 44 | 45 | response_rule = MatchRule( 46 | type="signal", interface="org.freedesktop.portal.Request", path=handle 47 | ) 48 | Proxy(message_bus, conn).AddMatch(response_rule) 49 | 50 | with conn.filter(response_rule) as responses: 51 | req = new_method_call( 52 | screenshot, 53 | "Screenshot", 54 | "sa{sv}", 55 | ("", {"handle_token": ("s", token), "interactive": ("b", False)}), 56 | ) 57 | conn.send_and_get_reply(req) 58 | response_msg = conn.recv_until_filtered(responses) 59 | 60 | response, results = response_msg.body 61 | 62 | im = False 63 | if response == 0: 64 | filename = results["uri"][1].split("file://", 1)[-1] 65 | if os.path.isfile(filename): 66 | im = Image.open(filename) 67 | os.remove(filename) 68 | 69 | conn.close() 70 | 71 | if bbox and im: 72 | im = im.crop(bbox) 73 | return im 74 | 75 | def backend_version(self): 76 | return UNKNOWN_VERSION 77 | -------------------------------------------------------------------------------- /pyscreenshot/plugins/gdk3pixbuf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Gdk3-based screenshotting. 3 | 4 | Adapted from https://stackoverflow.com/a/37768950/81636, but uses 5 | buffers directly instead of saving intermediate files (which is slow). 6 | """ 7 | 8 | from PIL import Image 9 | 10 | from pyscreenshot.plugins.backend import CBackend 11 | from pyscreenshot.util import platform_is_osx 12 | 13 | 14 | class Gdk3BackendError(Exception): 15 | pass 16 | 17 | 18 | class Gdk3PixbufWrapper(CBackend): 19 | name = "pygdk3" 20 | 21 | def grab(self, bbox=None): 22 | """Grabs an image directly to a buffer. 23 | 24 | :param bbox: Optional tuple or list containing (x1, y1, x2, y2) coordinates 25 | of sub-region to capture. 26 | :return: PIL RGB image 27 | :raises: ValueError, if image data does not have 3 channels (RGB), each with 8 28 | bits. 29 | :rtype: Image 30 | """ 31 | if platform_is_osx(): 32 | raise Gdk3BackendError("osx not supported") 33 | import gi # type: ignore 34 | 35 | gi.require_version("Gdk", "3.0") 36 | # gi.require_version('GdkPixbuf', '2.0') 37 | from gi.repository import Gdk, GdkPixbuf # type: ignore 38 | 39 | # read_pixel_bytes: New in version 2.32. 40 | if GdkPixbuf.PIXBUF_MAJOR == 2: 41 | if GdkPixbuf.PIXBUF_MINOR < 32: 42 | raise ValueError( 43 | "GdkPixbuf min supported version: 2.32 current:" 44 | + GdkPixbuf.PIXBUF_VERSION 45 | ) 46 | 47 | w = Gdk.get_default_root_window() 48 | if bbox is not None: 49 | g = [bbox[0], bbox[1], bbox[2] - bbox[0], bbox[3] - bbox[1]] 50 | else: 51 | g = w.get_geometry() 52 | pb = Gdk.pixbuf_get_from_window(w, *g) 53 | if not pb: 54 | raise Gdk3BackendError("empty buffer") 55 | 56 | if pb.get_bits_per_sample() != 8: 57 | raise Gdk3BackendError("Expected 8 bits per pixel.") 58 | elif pb.get_n_channels() != 3: 59 | raise Gdk3BackendError("Expected RGB image.") 60 | 61 | # Read the entire buffer into a python bytes object. 62 | # read_pixel_bytes: New in version 2.32. 63 | pixel_bytes = pb.read_pixel_bytes().get_data() # type: bytes 64 | width, height = g[2], g[3] 65 | 66 | # Probably for SSE alignment reasons, the pixbuf has extra data in each line. 67 | # The args after "raw" help handle this; see 68 | # http://effbot.org/imagingbook/decoder.htm#the-raw-decoder 69 | return Image.frombytes( 70 | "RGB", (width, height), pixel_bytes, "raw", "RGB", pb.get_rowstride(), 1 71 | ) 72 | 73 | def backend_version(self): 74 | import gi 75 | 76 | return ".".join(map(str, gi.version_info)) 77 | -------------------------------------------------------------------------------- /pyscreenshot/plugins/gnome_dbus.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from pyscreenshot.plugins.backend import UNKNOWN_VERSION, CBackend 4 | from pyscreenshot.tempexport import read_func_img 5 | 6 | log = logging.getLogger(__name__) 7 | 8 | 9 | class GnomeDBusError(Exception): 10 | pass 11 | 12 | 13 | class GnomeDBusWrapper(CBackend): 14 | name = "gnome_dbus" 15 | is_subprocess = True 16 | 17 | def grab(self, bbox=None): 18 | im = read_func_img(self._grab_to_file, bbox) 19 | return im 20 | 21 | def _grab_to_file(self, filename, bbox=None): 22 | has_jeepney = False 23 | try: 24 | # from jeepney import new_method_call 25 | from jeepney.io.blocking import open_dbus_connection # type: ignore 26 | from jeepney.wrappers import MessageGenerator # type: ignore 27 | from jeepney.wrappers import new_method_call 28 | 29 | has_jeepney = True 30 | except ImportError: 31 | pass 32 | 33 | if not has_jeepney: 34 | raise GnomeDBusError("jeepney library is missing") 35 | 36 | class Screenshot(MessageGenerator): 37 | interface = "org.gnome.Shell.Screenshot" 38 | 39 | def __init__( 40 | self, 41 | object_path="/org/gnome/Shell/Screenshot", 42 | bus_name="org.gnome.Shell.Screenshot", 43 | ): 44 | super().__init__(object_path=object_path, bus_name=bus_name) 45 | 46 | def Screenshot(self, include_cursor, flash, filename): 47 | return new_method_call( 48 | self, "Screenshot", "bbs", (include_cursor, flash, filename) 49 | ) 50 | 51 | def ScreenshotArea(self, x, y, width, height, flash, filename): 52 | return new_method_call( 53 | self, 54 | "ScreenshotArea", 55 | "iiiibs", 56 | (x, y, width, height, flash, filename), 57 | ) 58 | 59 | # https://jeepney.readthedocs.io/en/latest/integrate.html 60 | connection = open_dbus_connection(bus="SESSION") 61 | dbscr = Screenshot() 62 | if bbox: 63 | msg = dbscr.ScreenshotArea( 64 | bbox[0], 65 | bbox[1], 66 | bbox[2] - bbox[0], 67 | bbox[3] - bbox[1], 68 | False, 69 | filename, 70 | ) 71 | else: 72 | msg = dbscr.Screenshot(False, False, filename) 73 | reply = connection.send_and_get_reply(msg) 74 | result = reply.body[0] 75 | if not result: 76 | raise GnomeDBusError("dbus error: %s %s" % (msg, result)) 77 | 78 | def backend_version(self): 79 | return UNKNOWN_VERSION 80 | -------------------------------------------------------------------------------- /pyscreenshot/plugins/gnome_screenshot.py: -------------------------------------------------------------------------------- 1 | from easyprocess import EasyProcess 2 | 3 | from pyscreenshot.plugins.backend import CBackend 4 | from pyscreenshot.tempexport import read_prog_img 5 | from pyscreenshot.util import extract_version 6 | 7 | PROGRAM = "gnome-screenshot" 8 | 9 | # https://gitlab.gnome.org/GNOME/gnome-screenshot/blob/master/src/screenshot-utils.c 10 | # DBus is used for screenshot. 11 | # If it doesn't succeed or $GNOME_SCREENSHOT_FORCE_FALLBACK is set then X DISPLAY is used. 12 | # Flash effect! https://bugzilla.gnome.org/show_bug.cgi?id=672759 13 | 14 | 15 | class GnomeScreenshotWrapper(CBackend): 16 | """Plugin for ``pyscreenshot`` that uses ``gnome-screenshot`` 17 | https://git.gnome.org/browse/gnome-screenshot/ 18 | 19 | This plugin can take screenshot when system is running Wayland. 20 | Info: https://bugs.freedesktop.org/show_bug.cgi?id=98672#c1 21 | """ 22 | 23 | name = "gnome-screenshot" 24 | is_subprocess = True 25 | 26 | def grab(self, bbox=None): 27 | im = read_prog_img([PROGRAM, "-f"]) 28 | if bbox: 29 | im = im.crop(bbox) 30 | return im 31 | 32 | def backend_version(self): 33 | p = EasyProcess([PROGRAM, "--version"]) 34 | p.enable_stdout_log = False 35 | p.enable_stderr_log = False 36 | p.call() 37 | return extract_version(p.stdout.replace("-", " ")) 38 | -------------------------------------------------------------------------------- /pyscreenshot/plugins/grim.py: -------------------------------------------------------------------------------- 1 | """ 2 | Backend for grim (https://github.com/emersion/grim), a Wayland screen tool for 3 | environments other than Gnome and KDE, such as Sway. 4 | """ 5 | import logging 6 | 7 | from easyprocess import EasyProcess 8 | 9 | from pyscreenshot.plugins.backend import UNKNOWN_VERSION, CBackend 10 | from pyscreenshot.tempexport import read_prog_img 11 | 12 | log = logging.getLogger(__name__) 13 | 14 | 15 | PROGRAM = "grim" 16 | 17 | 18 | class GrimWrapper(CBackend): 19 | name = "grim" 20 | is_subprocess = True 21 | 22 | def _bbox_to_grim_region(self, bbox): 23 | """ 24 | Translate pyscreenshot's bbox tuple convention of (x1, y1, x2, y2) to 25 | grim's bbox convention, which is a string of the following format: 26 | , x 27 | """ 28 | x1, y1, x2, y2 = bbox 29 | width = x2 - x1 30 | height = y2 - y1 31 | return "{},{} {}x{}".format(x1, y1, width, height) 32 | 33 | def grab(self, bbox=None): 34 | if bbox: 35 | # using grim's built-in cropping feature 36 | region = self._bbox_to_grim_region(bbox) 37 | return read_prog_img([PROGRAM, "-g", region]) 38 | return read_prog_img([PROGRAM]) 39 | 40 | def backend_version(self): 41 | # grim doesn't have a version flag for some reason 42 | p = EasyProcess([PROGRAM, "-help"]) 43 | p.enable_stdout_log = False 44 | p.enable_stderr_log = False 45 | p.call() 46 | if p.return_code == 0: 47 | return UNKNOWN_VERSION 48 | -------------------------------------------------------------------------------- /pyscreenshot/plugins/imagemagick.py: -------------------------------------------------------------------------------- 1 | from easyprocess import EasyProcess 2 | 3 | from pyscreenshot.plugins.backend import CBackend 4 | from pyscreenshot.tempexport import read_prog_img 5 | from pyscreenshot.util import extract_version, platform_is_osx 6 | 7 | PROGRAM = "import" 8 | # http://www.imagemagick.org/ 9 | 10 | 11 | class ImagemagickBackendError(Exception): 12 | pass 13 | 14 | 15 | class ImagemagickWrapper(CBackend): 16 | name = "imagemagick" 17 | is_subprocess = True 18 | 19 | def grab(self, bbox=None): 20 | if platform_is_osx(): 21 | raise ImagemagickBackendError("osx not supported") 22 | 23 | command = [PROGRAM, "-silent", "-window", "root"] 24 | if bbox: 25 | pbox = "{}x{}+{}+{}".format( 26 | bbox[2] - bbox[0], bbox[3] - bbox[1], bbox[0], bbox[1] 27 | ) 28 | command += ["-crop", pbox] 29 | im = read_prog_img(command) 30 | return im 31 | 32 | def backend_version(self): 33 | stdout = EasyProcess([PROGRAM, "-version"]).call().stdout 34 | s = stdout.splitlines()[0] 35 | return extract_version(s.replace("-", " ")) 36 | -------------------------------------------------------------------------------- /pyscreenshot/plugins/ksnip.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | 4 | from easyprocess import EasyProcess 5 | from PIL import Image 6 | 7 | from pyscreenshot.plugins.backend import CBackend 8 | from pyscreenshot.tempexport import RunProgError 9 | 10 | log = logging.getLogger(__name__) 11 | 12 | 13 | PROGRAM = "ksnip" 14 | 15 | 16 | class KsnipWrapper(CBackend): 17 | name = "ksnip" 18 | is_subprocess = True 19 | 20 | def grab(self, bbox=None): 21 | cmd = [PROGRAM, "--fullscreen", "--save"] 22 | p = EasyProcess(cmd) 23 | p.call() 24 | if p.return_code != 0: 25 | raise RunProgError(p.stderr) 26 | lastline = p.stdout.splitlines()[-1] 27 | if "Image Saved" not in lastline: 28 | raise RunProgError(p.stderr) 29 | filename = lastline.split()[-1] 30 | im = Image.open(filename) 31 | os.remove(filename) 32 | 33 | # TODO: bbox param 34 | if bbox: 35 | im = im.crop(bbox) 36 | return im 37 | 38 | def backend_version(self): 39 | for line in EasyProcess([PROGRAM, "--version"]).call().stderr.splitlines(): 40 | if "version" in line.lower(): 41 | return line.split()[-1] 42 | -------------------------------------------------------------------------------- /pyscreenshot/plugins/kwin_dbus.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | 4 | from PIL import Image 5 | 6 | from pyscreenshot.plugins.backend import UNKNOWN_VERSION, CBackend 7 | 8 | log = logging.getLogger(__name__) 9 | 10 | 11 | class KdeDBusError(Exception): 12 | pass 13 | 14 | 15 | # https://gitlab.gnome.org/GNOME/gimp/-/issues/6626 16 | # The org.kde.kwin.Screenshot interface is deprecated in KDE Plasma 5.22. 17 | 18 | # "The process is not authorized to take a screenshot" 19 | 20 | 21 | class KwinDBusWrapper(CBackend): 22 | name = "kwin_dbus" 23 | is_subprocess = True 24 | 25 | def grab(self, bbox=None): 26 | has_jeepney = False 27 | try: 28 | # from jeepney import new_method_call 29 | from jeepney.io.blocking import open_dbus_connection # type: ignore 30 | from jeepney.wrappers import MessageGenerator # type: ignore 31 | from jeepney.wrappers import new_method_call 32 | 33 | has_jeepney = True 34 | except ImportError: 35 | pass 36 | 37 | if not has_jeepney: 38 | raise KdeDBusError("jeepney library is missing") 39 | 40 | class Screenshot(MessageGenerator): 41 | interface = "org.kde.kwin.Screenshot" 42 | 43 | def __init__(self, object_path="/Screenshot", bus_name="org.kde.KWin"): 44 | super().__init__(object_path=object_path, bus_name=bus_name) 45 | 46 | def screenshotFullscreen(self, captureCursor): 47 | return new_method_call( 48 | self, "screenshotFullscreen", "b", (captureCursor,) 49 | ) 50 | 51 | def screenshotArea(self, x, y, width, height, captureCursor): 52 | return new_method_call( 53 | self, 54 | "screenshotArea", 55 | "iiiib", 56 | (x, y, width, height, captureCursor), 57 | ) 58 | 59 | # https://jeepney.readthedocs.io/en/latest/integrate.html 60 | connection = open_dbus_connection(bus="SESSION") 61 | dbscr = Screenshot() 62 | # bbox not working: 63 | # if bbox: msg = dbscr.screenshotArea(bbox[0], bbox[1], bbox[2] - bbox[0], bbox[3] - bbox[1], False) 64 | msg = dbscr.screenshotFullscreen(False) 65 | reply = connection.send_and_get_reply(msg) 66 | filename = reply.body[0] 67 | if not filename: 68 | raise KdeDBusError() 69 | 70 | im = Image.open(filename) 71 | os.remove(filename) 72 | if bbox: 73 | im = im.crop(bbox) 74 | return im 75 | 76 | def backend_version(self): 77 | return UNKNOWN_VERSION 78 | -------------------------------------------------------------------------------- /pyscreenshot/plugins/mac_quartz.py: -------------------------------------------------------------------------------- 1 | # Javier Escalada Gomez 2 | # 3 | # from: 4 | # https://stackoverflow.com/questions/4524723/take-screenshot-in-python-on-mac-os-x 5 | 6 | from pyscreenshot.plugins.backend import CBackend 7 | from pyscreenshot.tempexport import read_func_img 8 | 9 | 10 | class MacQuartzWrapper(CBackend): 11 | name = "mac_quartz" 12 | 13 | def grab(self, bbox=None): 14 | im = read_func_img(self._grab_to_file, bbox) 15 | return im 16 | 17 | def _grab_to_file(self, filename, bbox=None, dpi=72): 18 | # Should query dpi from somewhere, e.g for retina displays? 19 | import LaunchServices # type: ignore 20 | import Quartz # type: ignore 21 | import Quartz.CoreGraphics as CG # type: ignore 22 | from Cocoa import NSURL # type: ignore 23 | 24 | if bbox: 25 | width = bbox[2] - bbox[0] 26 | height = bbox[3] - bbox[1] 27 | region = CG.CGRectMake(bbox[0], bbox[1], width, height) 28 | else: 29 | region = CG.CGRectInfinite 30 | 31 | # Create screenshot as CGImage 32 | image = CG.CGWindowListCreateImage( 33 | region, 34 | CG.kCGWindowListOptionOnScreenOnly, 35 | CG.kCGNullWindowID, 36 | CG.kCGWindowImageDefault, 37 | ) 38 | 39 | file_type = LaunchServices.kUTTypePNG 40 | 41 | url = NSURL.fileURLWithPath_(filename) 42 | 43 | dest = Quartz.CGImageDestinationCreateWithURL( 44 | url, 45 | file_type, 46 | # 1 image in file 47 | 1, 48 | None, 49 | ) 50 | 51 | properties = { 52 | Quartz.kCGImagePropertyDPIWidth: dpi, 53 | Quartz.kCGImagePropertyDPIHeight: dpi, 54 | } 55 | 56 | # Add the image to the destination, characterizing the image with 57 | # the properties dictionary. 58 | Quartz.CGImageDestinationAddImage(dest, image, properties) 59 | 60 | # When all the images (only 1 in this example) are added to the destination, 61 | # finalize the CGImageDestination object. 62 | Quartz.CGImageDestinationFinalize(dest) 63 | 64 | def backend_version(self): 65 | import objc # type: ignore 66 | 67 | return objc.__version__ 68 | -------------------------------------------------------------------------------- /pyscreenshot/plugins/mac_screencapture.py: -------------------------------------------------------------------------------- 1 | from easyprocess import EasyProcess 2 | 3 | from pyscreenshot.plugins.backend import UNKNOWN_VERSION, CBackend 4 | from pyscreenshot.tempexport import read_prog_img 5 | from pyscreenshot.util import platform_is_osx 6 | 7 | PROGRAM = "screencapture" 8 | # https://ss64.com/osx/screencapture.html 9 | # By default screenshots are saved as .png files, 10 | 11 | 12 | class ScreencaptureError(Exception): 13 | pass 14 | 15 | 16 | class ScreencaptureWrapper(CBackend): 17 | name = "mac_screencapture" 18 | is_subprocess = True 19 | 20 | def grab(self, bbox=None): 21 | if not platform_is_osx(): 22 | raise ScreencaptureError("This backend runs only on Darwin") 23 | 24 | command = [PROGRAM, "-x"] 25 | if bbox: 26 | width = bbox[2] - bbox[0] 27 | height = bbox[3] - bbox[1] 28 | command += ["-R{},{},{},{}".format(bbox[0], bbox[1], width, height)] 29 | im = read_prog_img(command) 30 | return im 31 | 32 | def backend_version(self): 33 | p = EasyProcess([PROGRAM, "-help"]) 34 | p.enable_stdout_log = False 35 | p.enable_stderr_log = False 36 | p.call() 37 | if p.return_code == 0: 38 | return UNKNOWN_VERSION 39 | -------------------------------------------------------------------------------- /pyscreenshot/plugins/maim.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from easyprocess import EasyProcess 4 | 5 | from pyscreenshot.plugins.backend import CBackend 6 | from pyscreenshot.tempexport import read_prog_img 7 | from pyscreenshot.util import extract_version 8 | 9 | log = logging.getLogger(__name__) 10 | 11 | 12 | PROGRAM = "maim" 13 | 14 | 15 | class MaimWrapper(CBackend): 16 | name = "maim" 17 | is_subprocess = True 18 | 19 | def grab(self, bbox=None): 20 | cmd = [PROGRAM, "--hidecursor"] 21 | if bbox: 22 | width = bbox[2] - bbox[0] 23 | height = bbox[3] - bbox[1] 24 | # https://github.com/naelstrof/maim/issues/119 25 | cmd += ["-g", "{}x{}+{}+{}".format(width, height, bbox[0], bbox[1])] 26 | im = read_prog_img(cmd) 27 | return im 28 | 29 | def backend_version(self): 30 | return extract_version(EasyProcess([PROGRAM, "--version"]).call().stdout) 31 | -------------------------------------------------------------------------------- /pyscreenshot/plugins/msswrap.py: -------------------------------------------------------------------------------- 1 | # import atexit 2 | 3 | from PIL import Image 4 | 5 | from pyscreenshot.plugins.backend import CBackend 6 | from pyscreenshot.util import py_minor 7 | 8 | # https://python-mss.readthedocs.io/examples.html 9 | 10 | 11 | class MssError(Exception): 12 | pass 13 | 14 | 15 | sct = None 16 | 17 | # not working without xrandr extension 18 | # only bits_per_pixel == 32 is supported 19 | 20 | 21 | class MssWrapper(CBackend): 22 | name = "mss" 23 | 24 | def grab(self, bbox=None): 25 | if py_minor() < 5: 26 | raise MssError() 27 | import mss # type: ignore 28 | 29 | # atexit.register(sct.close()) 30 | 31 | global sct 32 | if not sct: 33 | sct = mss.mss() 34 | 35 | # with self.mss.mss() as sct: 36 | if bbox: 37 | monitor = bbox 38 | else: 39 | monitor = sct.monitors[0] 40 | sct_img = sct.grab(monitor) 41 | 42 | im = Image.frombytes("RGB", sct_img.size, sct_img.bgra, "raw", "BGRX") 43 | # The same, but less efficient: 44 | # im = Image.frombytes('RGB', sct_img.size, sct_img.rgb) 45 | return im 46 | 47 | def backend_version(self): 48 | import mss 49 | 50 | return mss.__version__ 51 | -------------------------------------------------------------------------------- /pyscreenshot/plugins/pilwrap.py: -------------------------------------------------------------------------------- 1 | from PIL import __version__ 2 | 3 | from pyscreenshot.plugins.backend import CBackend 4 | from pyscreenshot.util import use_x_display 5 | 6 | 7 | class PilWrapper(CBackend): 8 | name = "pil" 9 | 10 | def grab(self, bbox=None): 11 | from PIL import ImageGrab 12 | 13 | # https://pillow.readthedocs.io/en/stable/reference/ImageGrab.html 14 | # On Linux, if xdisplay is None then gnome-screenshot will be used if it is installed. 15 | # To capture the default X11 display instead, pass xdisplay="" 16 | xdisplay = None 17 | if use_x_display(): 18 | xdisplay = "" 19 | return ImageGrab.grab(bbox, xdisplay=xdisplay) 20 | 21 | def backend_version(self): 22 | return __version__ 23 | -------------------------------------------------------------------------------- /pyscreenshot/plugins/pyside2_grabwindow.py: -------------------------------------------------------------------------------- 1 | import io 2 | import logging 3 | 4 | from PIL import Image 5 | 6 | from pyscreenshot.plugins.backend import CBackend 7 | 8 | log = logging.getLogger(__name__) 9 | 10 | 11 | class PySide2BugError(Exception): 12 | pass 13 | 14 | 15 | app = None 16 | 17 | 18 | class PySide2GrabWindow(CBackend): 19 | name = "pyside2" 20 | 21 | def grab_to_buffer(self, buff, file_type="png"): 22 | from PySide2 import QtCore, QtGui, QtWidgets # type: ignore 23 | 24 | QApplication = QtWidgets.QApplication 25 | QBuffer = QtCore.QBuffer 26 | QIODevice = QtCore.QIODevice 27 | QScreen = QtGui.QScreen 28 | # QPixmap = self.PySide2.QtGui.QPixmap 29 | 30 | global app 31 | if not app: 32 | app = QApplication([]) 33 | qbuffer = QBuffer() 34 | qbuffer.open(QIODevice.ReadWrite) 35 | QScreen.grabWindow( 36 | QApplication.primaryScreen(), QApplication.desktop().winId() 37 | ).save(qbuffer, file_type) 38 | # https://stackoverflow.com/questions/52291585/pyside2-typeerror-bytes-object-cannot-be-interpreted-as-an-integer 39 | buff.write(qbuffer.data().data()) 40 | qbuffer.close() 41 | 42 | def grab(self, bbox=None): 43 | strio = io.BytesIO() 44 | self.grab_to_buffer(strio) 45 | strio.seek(0) 46 | im = Image.open(strio) 47 | if bbox: 48 | im = im.crop(bbox) 49 | return im 50 | 51 | def backend_version(self): 52 | import PySide2 53 | 54 | return PySide2.__version__ 55 | -------------------------------------------------------------------------------- /pyscreenshot/plugins/pyside_grabwindow.py: -------------------------------------------------------------------------------- 1 | import io 2 | import logging 3 | 4 | from PIL import Image 5 | 6 | from pyscreenshot.plugins.backend import CBackend 7 | 8 | log = logging.getLogger(__name__) 9 | 10 | # based on qt4 backend 11 | 12 | app = None 13 | 14 | 15 | class PySideGrabWindow(CBackend): 16 | name = "pyside" 17 | 18 | def grab_to_buffer(self, buff, file_type="png"): 19 | from PySide import QtCore, QtGui # type: ignore 20 | 21 | QApplication = QtGui.QApplication 22 | QBuffer = QtCore.QBuffer 23 | QIODevice = QtCore.QIODevice 24 | QPixmap = QtGui.QPixmap 25 | 26 | global app 27 | if not app: 28 | app = QApplication([]) 29 | qbuffer = QBuffer() 30 | qbuffer.open(QIODevice.ReadWrite) 31 | QPixmap.grabWindow(QApplication.desktop().winId()).save(qbuffer, file_type) 32 | # https://stackoverflow.com/questions/52291585/pyside2-typeerror-bytes-object-cannot-be-interpreted-as-an-integer 33 | buff.write(qbuffer.data().data()) 34 | qbuffer.close() 35 | 36 | def grab(self, bbox=None): 37 | strio = io.BytesIO() 38 | self.grab_to_buffer(strio) 39 | strio.seek(0) 40 | im = Image.open(strio) 41 | if bbox: 42 | im = im.crop(bbox) 43 | return im 44 | 45 | def backend_version(self): 46 | import PySide 47 | 48 | return PySide.__version__ 49 | -------------------------------------------------------------------------------- /pyscreenshot/plugins/qt4grabwindow.py: -------------------------------------------------------------------------------- 1 | import io 2 | import logging 3 | 4 | from PIL import Image 5 | 6 | from pyscreenshot.plugins.backend import CBackend 7 | 8 | log = logging.getLogger(__name__) 9 | 10 | 11 | # based on: 12 | # http://stackoverflow.com/questions/69645/take-a-screenshot-via-a-python-script-linux 13 | 14 | app = None 15 | 16 | 17 | class Qt4GrabWindow(CBackend): 18 | name = "pyqt" 19 | 20 | def grab_to_buffer(self, buff, file_type="png"): 21 | from PyQt4 import Qt, QtGui # type: ignore 22 | 23 | QApplication = QtGui.QApplication 24 | QBuffer = Qt.QBuffer 25 | QIODevice = Qt.QIODevice 26 | QPixmap = QtGui.QPixmap 27 | 28 | global app 29 | if not app: 30 | app = QApplication([]) 31 | qbuffer = QBuffer() 32 | qbuffer.open(QIODevice.ReadWrite) 33 | QPixmap.grabWindow(QApplication.desktop().winId()).save(qbuffer, file_type) 34 | buff.write(qbuffer.data()) 35 | qbuffer.close() 36 | 37 | def grab(self, bbox=None): 38 | strio = io.BytesIO() 39 | self.grab_to_buffer(strio) 40 | strio.seek(0) 41 | im = Image.open(strio) 42 | if bbox: 43 | im = im.crop(bbox) 44 | return im 45 | 46 | def backend_version(self): 47 | from PyQt4 import Qt 48 | 49 | return Qt.PYQT_VERSION_STR 50 | -------------------------------------------------------------------------------- /pyscreenshot/plugins/qt5grabwindow.py: -------------------------------------------------------------------------------- 1 | import io 2 | import logging 3 | 4 | from PIL import Image 5 | 6 | from pyscreenshot.plugins.backend import CBackend 7 | 8 | log = logging.getLogger(__name__) 9 | 10 | # based on qt4 backend 11 | 12 | app = None 13 | 14 | 15 | class Qt5GrabWindow(CBackend): 16 | name = "pyqt5" 17 | 18 | # qt backends have conflict with each other in the same process. 19 | 20 | def grab_to_buffer(self, buff, file_type="png"): 21 | from PyQt5 import Qt, QtGui, QtWidgets # type: ignore 22 | 23 | QApplication = QtWidgets.QApplication 24 | QBuffer = Qt.QBuffer 25 | QIODevice = Qt.QIODevice 26 | QScreen = QtGui.QScreen 27 | 28 | global app 29 | if not app: 30 | app = QApplication([]) 31 | qbuffer = QBuffer() 32 | qbuffer.open(QIODevice.ReadWrite) 33 | QScreen.grabWindow( 34 | QApplication.primaryScreen(), QApplication.desktop().winId() 35 | ).save(qbuffer, file_type) 36 | buff.write(qbuffer.data()) 37 | qbuffer.close() 38 | 39 | def grab(self, bbox=None): 40 | strio = io.BytesIO() 41 | self.grab_to_buffer(strio) 42 | strio.seek(0) 43 | im = Image.open(strio) 44 | if bbox: 45 | im = im.crop(bbox) 46 | return im 47 | 48 | def backend_version(self): 49 | from PyQt5 import Qt 50 | 51 | return Qt.PYQT_VERSION_STR 52 | -------------------------------------------------------------------------------- /pyscreenshot/plugins/scrot.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from easyprocess import EasyProcess 4 | 5 | from pyscreenshot.plugins.backend import CBackend 6 | from pyscreenshot.tempexport import read_prog_img 7 | from pyscreenshot.util import extract_version 8 | 9 | log = logging.getLogger(__name__) 10 | 11 | 12 | PROGRAM = "scrot" 13 | 14 | 15 | class ScrotWrapper(CBackend): 16 | name = "scrot" 17 | is_subprocess = True 18 | 19 | def grab(self, bbox=None): 20 | im = read_prog_img([PROGRAM, "--silent"]) 21 | if bbox: 22 | im = im.crop(bbox) 23 | return im 24 | 25 | def backend_version(self): 26 | return extract_version(EasyProcess([PROGRAM, "-version"]).call().stdout) 27 | -------------------------------------------------------------------------------- /pyscreenshot/plugins/wxscreen.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from PIL import Image 4 | 5 | from pyscreenshot.plugins.backend import CBackend 6 | from pyscreenshot.util import platform_is_osx 7 | 8 | log = logging.getLogger(__name__) 9 | 10 | # based on: 11 | # http://stackoverflow.com/questions/69645/take-a-screenshot-via-a-python-script-linux 12 | 13 | 14 | class WxBackendError(Exception): 15 | pass 16 | 17 | 18 | app = None 19 | 20 | 21 | class WxScreen(CBackend): 22 | name = "wx" 23 | # conflict with pygdk3 24 | # wx is never installed by default 25 | # pygdk3 is default on Gnome 26 | 27 | def grab(self, bbox=None): 28 | if platform_is_osx(): 29 | raise WxBackendError("osx not supported") 30 | import wx # type: ignore 31 | 32 | global app 33 | if not app: 34 | app = wx.App() 35 | screen = wx.ScreenDC() 36 | size = screen.GetSize() 37 | if wx.__version__ >= "4": 38 | bmp = wx.Bitmap(size[0], size[1]) 39 | else: 40 | bmp = wx.EmptyBitmap(size[0], size[1]) 41 | mem = wx.MemoryDC(bmp) 42 | mem.Blit(0, 0, size[0], size[1], screen, 0, 0) 43 | del mem 44 | if hasattr(bmp, "ConvertToImage"): 45 | myWxImage = bmp.ConvertToImage() 46 | else: 47 | myWxImage = wx.ImageFromBitmap(bmp) 48 | im = Image.new("RGB", (myWxImage.GetWidth(), myWxImage.GetHeight())) 49 | if hasattr(Image, "frombytes"): 50 | # for Pillow 51 | im.frombytes(bytes(myWxImage.GetData())) 52 | else: 53 | # for PIL 54 | im.fromstring(myWxImage.GetData()) 55 | if bbox: 56 | im = im.crop(bbox) 57 | return im 58 | 59 | def backend_version(self): 60 | import wx 61 | 62 | return wx.__version__ 63 | -------------------------------------------------------------------------------- /pyscreenshot/plugins/xwd.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from easyprocess import EasyProcess 4 | 5 | from pyscreenshot.plugins.backend import CBackend 6 | from pyscreenshot.tempexport import RunProgError, read_func_img 7 | from pyscreenshot.util import extract_version 8 | 9 | log = logging.getLogger(__name__) 10 | 11 | 12 | # wikipedia: https://en.wikipedia.org/wiki/Xwd 13 | # xwd | xwdtopnm | pnmtopng > Screenshot.png 14 | # xwdtopnm is buggy: https://bugs.launchpad.net/ubuntu/+source/netpbm-free/+bug/1379480 15 | # solution : imagemagick convert 16 | # xwd -root -display :0 | convert xwd:- file.png 17 | # TODO: xwd sometimes grabs the wrong window so this backend will be not added now 18 | PROGRAM = "xwd" 19 | 20 | 21 | def read_xwd_img(): 22 | def run_prog(fpng, bbox=None): 23 | fxwd = fpng + ".xwd" 24 | pxwd = EasyProcess([PROGRAM, "-root", "-out", fxwd]) 25 | pxwd.call() 26 | if pxwd.return_code != 0: 27 | raise RunProgError(pxwd.stderr) 28 | 29 | pconvert = EasyProcess(["convert", "xwd:" + fxwd, fpng]) 30 | pconvert.call() 31 | if pconvert.return_code != 0: 32 | raise RunProgError(pconvert.stderr) 33 | 34 | im = read_func_img(run_prog) 35 | return im 36 | 37 | 38 | class XwdWrapper(CBackend): 39 | name = "xwd" 40 | is_subprocess = True 41 | 42 | def grab(self, bbox=None): 43 | im = read_xwd_img() 44 | if bbox: 45 | im = im.crop(bbox) 46 | return im 47 | 48 | def backend_version(self): 49 | return extract_version(EasyProcess([PROGRAM, "-version"]).call().stdout) 50 | -------------------------------------------------------------------------------- /pyscreenshot/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ponty/pyscreenshot/c9f6051fe339b4b4c59ef75aa17140211e51606f/pyscreenshot/py.typed -------------------------------------------------------------------------------- /pyscreenshot/tempexport.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | from tempfile import TemporaryDirectory 3 | 4 | from easyprocess import EasyProcess 5 | from PIL import Image 6 | 7 | 8 | class RunProgError(Exception): 9 | pass 10 | 11 | 12 | def read_func_img(func, bbox=None): 13 | with TemporaryDirectory(prefix="pyscreenshot") as tmpdirname: 14 | filename = os.path.join(tmpdirname, "screenshot.png") 15 | func(filename, bbox) 16 | im = Image.open(filename) 17 | return im 18 | 19 | 20 | def read_prog_img(cmd): 21 | def run_prog(filename, bbox=None): 22 | p = EasyProcess(cmd + [filename]) 23 | p.call() 24 | if p.return_code != 0: 25 | raise RunProgError(p.stderr) 26 | 27 | im = read_func_img(run_prog) 28 | return im 29 | -------------------------------------------------------------------------------- /pyscreenshot/util.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | from easyprocess import EasyProcess 5 | 6 | 7 | def py_minor(): 8 | return sys.version_info[1] 9 | 10 | 11 | def platform_is_osx(): 12 | return sys.platform == "darwin" 13 | 14 | 15 | def platform_is_win(): 16 | return sys.platform == "win32" 17 | 18 | 19 | def platform_is_linux(): 20 | return sys.platform.startswith("linux") 21 | 22 | 23 | def use_x_display(): 24 | if platform_is_win(): 25 | return False 26 | if platform_is_osx(): 27 | return False 28 | DISPLAY = os.environ.get("DISPLAY") 29 | XDG_SESSION_TYPE = os.environ.get("XDG_SESSION_TYPE") 30 | # Xwayland can not be used for screenshot 31 | return DISPLAY and XDG_SESSION_TYPE != "wayland" 32 | 33 | 34 | def extract_version(txt): 35 | """This function tries to extract the version from the help text of any 36 | program.""" 37 | words = txt.replace(",", " ").split() 38 | version = None 39 | for x in reversed(words): 40 | if len(x) > 2: 41 | if x[0].lower() == "v": 42 | x = x[1:] 43 | if "." in x and x[0].isdigit(): 44 | version = x 45 | break 46 | return version 47 | 48 | 49 | def run_mod_as_subproc(name, params=[]): 50 | python = sys.executable 51 | cmd = [python, "-m", name] + params 52 | p = EasyProcess(cmd).call() 53 | return p 54 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | log_level=DEBUG 3 | log_format=%(asctime)s.%(msecs)03d %(name)s %(levelname)s %(message)s 4 | log_date_format=%Y-%m-%d %H:%M:%S 5 | #log_cli=true -------------------------------------------------------------------------------- /requirements-doc.txt: -------------------------------------------------------------------------------- 1 | autoflake 2 | isort 3 | black 4 | 5 | # pytest 6 | pyvirtualdisplay 7 | pillow 8 | # python-xlib 9 | # pyobjc-framework-Quartz ; platform_system == 'Darwin' 10 | # pyobjc-framework-LaunchServices; platform_system == 'Darwin' 11 | # pywin32 ; platform_system == 'Windows' 12 | # pygame>=2.1 13 | -------------------------------------------------------------------------------- /requirements-test.txt: -------------------------------------------------------------------------------- 1 | pytest 2 | pyvirtualdisplay 3 | pillow 4 | python-xlib 5 | pyobjc-framework-Quartz ; platform_system == 'Darwin' 6 | pyobjc-framework-LaunchServices; platform_system == 'Darwin' 7 | pywin32 ; platform_system == 'Windows' 8 | mypy 9 | flake8 10 | 11 | # TODO: pygame 2.2 error on MacOS 12 | pygame==2.1.2 ; platform_system == 'Darwin' 13 | pygame>=2.2 ; platform_system != 'Darwin' 14 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | EasyProcess 2 | entrypoint2>=0.2.4 3 | mss ; python_version > '3.4' 4 | jeepney ; python_version > '3.4' and platform_system == 'Linux' 5 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | 3 | from setuptools import setup 4 | 5 | NAME = "pyscreenshot" 6 | 7 | # get __version__ 8 | __version__ = None 9 | exec(open(os.path.join(NAME, "about.py")).read()) 10 | VERSION = __version__ 11 | 12 | 13 | URL = "https://github.com/ponty/pyscreenshot" 14 | DESCRIPTION = "python screenshot" 15 | LONG_DESCRIPTION = """TL;DR: Use Pillow. If Pillow doesn't work or it's slow then try pyscreenshot. 16 | 17 | The pyscreenshot module is obsolete in most cases. 18 | It was created because PIL ImageGrab module worked on Windows only, 19 | but now Linux and macOS are also supported. 20 | There are some features in pyscreenshot which can be useful in special cases: 21 | flexible backends, Wayland support, sometimes better performance, optional subprocessing. 22 | 23 | The module can be used to copy the contents of the screen to a Pillow image memory using various back-ends. 24 | Replacement for the ImageGrab Module. 25 | 26 | Documentation: https://github.com/ponty/pyscreenshot/tree/""" 27 | LONG_DESCRIPTION += VERSION 28 | 29 | PACKAGES = [ 30 | NAME, 31 | NAME + ".plugins", 32 | NAME + ".check", 33 | NAME + ".cli", 34 | NAME + ".examples", 35 | ] 36 | 37 | classifiers = [ 38 | # Get more strings from 39 | # http://www.python.org/pypi?%3Aaction=list_classifiers 40 | "License :: OSI Approved :: BSD License", 41 | "Natural Language :: English", 42 | "Operating System :: OS Independent", 43 | "Programming Language :: Python", 44 | "Programming Language :: Python :: 3", 45 | "Programming Language :: Python :: 3 :: Only", 46 | "Programming Language :: Python :: 3.9", 47 | "Programming Language :: Python :: 3.10", 48 | "Programming Language :: Python :: 3.11", 49 | ] 50 | 51 | install_requires = [ 52 | "EasyProcess", 53 | "entrypoint2", 54 | "mss ; python_version > '3.4'", 55 | "jeepney ; python_version > '3.4' and platform_system == 'Linux'", 56 | ] 57 | 58 | setup( 59 | name=NAME, 60 | version=VERSION, 61 | description=DESCRIPTION, 62 | long_description=LONG_DESCRIPTION, 63 | long_description_content_type="text/x-rst", 64 | python_requires=">=3.4", 65 | classifiers=classifiers, 66 | keywords="screenshot", 67 | author="ponty", 68 | # author_email='', 69 | url=URL, 70 | license="BSD", 71 | packages=PACKAGES, 72 | install_requires=install_requires, 73 | package_data={ 74 | NAME: ["py.typed"], 75 | }, 76 | ) 77 | -------------------------------------------------------------------------------- /tests/bt.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import sys 4 | from time import sleep 5 | 6 | import fillscreen 7 | from config import bbox_ls 8 | from double_disp import check_double_disp 9 | from easyprocess import EasyProcess 10 | from entrypoint2 import entrypoint 11 | from image_debug import ImageDebug 12 | from PIL import Image, ImageChops 13 | 14 | import pyscreenshot 15 | from pyscreenshot.util import platform_is_linux 16 | 17 | # backend tester (bt) 18 | 19 | imdbg = ImageDebug() 20 | 21 | 22 | def check_ref(backend, bbox, childprocess, refimgpath): 23 | im = pyscreenshot.grab(bbox=bbox, backend=backend, childprocess=childprocess) 24 | 25 | img_ref = Image.open(refimgpath) 26 | logging.debug("ref full getextrema: %s", img_ref.getextrema()) 27 | # if not bbox: 28 | # bbox = img_ref.getbbox() 29 | # # rm tkinter border 30 | # bw = 1 31 | # bbox = (bbox[0] + bw, bbox[1] + bw, bbox[2] - bw, bbox[3] - bw) 32 | # img_ref = img_ref.crop(bbox) 33 | # im = im.crop(bbox) 34 | # elif bbox: 35 | if bbox: 36 | img_ref = img_ref.crop(bbox) 37 | 38 | img_ref = img_ref.convert("RGB") 39 | logging.debug("ref getextrema: %s", img_ref.getextrema()) 40 | im = im.convert("RGB") 41 | logging.debug("shot getextrema: %s", im.getextrema()) 42 | 43 | assert "RGB" == img_ref.mode 44 | assert "RGB" == im.mode 45 | 46 | imdbg.img_debug(img_ref, "ref" + str(bbox)) 47 | imdbg.img_debug(im, str(backend) + str(bbox)) 48 | 49 | img_diff = ImageChops.difference(img_ref, im) 50 | ex = img_diff.getextrema() 51 | logging.debug("diff getextrema: %s", ex) 52 | diff_bbox = img_diff.getbbox() 53 | if diff_bbox: 54 | imdbg.img_debug(img_diff, "img_diff" + str(diff_bbox)) 55 | # if ( 56 | # platform_is_osx() 57 | # and backend 58 | # and backend in ["pyqt", "pyqt5", "pyside", "pyside2"] 59 | # ): 60 | # color_diff_max = max([b for (_, b) in ex]) 61 | # assert color_diff_max < 70 62 | # else: 63 | if diff_bbox is not None: 64 | print( 65 | "different image data %s bbox=%s extrema:%s diff_bbox=%s" 66 | % (backend, bbox, ex, diff_bbox) 67 | ) 68 | assert diff_bbox is None 69 | 70 | 71 | def backend_ref(backend, childprocess=True, refimgpath="", delay=0): 72 | for bbox in bbox_ls: 73 | print("bbox: {}".format(bbox)) 74 | print("backend: %s" % backend) 75 | check_ref(backend, bbox, childprocess, refimgpath) 76 | if delay: 77 | sleep(delay) 78 | 79 | 80 | def backend_to_check(backend, delay=0): 81 | refimgpath = fillscreen.init() 82 | backend_ref(backend, childprocess=True, refimgpath=refimgpath, delay=delay) 83 | 84 | # childprocess=False is tested in a subprocess for isolation 85 | cmd = [ 86 | sys.executable, 87 | __file__.rsplit(".", 1)[0] + ".py", 88 | backend if backend else "", 89 | refimgpath, 90 | str(delay), 91 | "--debug", 92 | ] 93 | p = EasyProcess(cmd).call() 94 | assert p.return_code == 0 95 | 96 | if platform_is_linux() and prog_check(["Xvfb", "-help"]): 97 | check_double_disp(backend) 98 | 99 | 100 | @entrypoint 101 | def main(backend, refimgpath: str, delay: float): 102 | if not backend: 103 | backend = None 104 | # delay = int(delay) 105 | backend_ref(backend, childprocess=False, refimgpath=refimgpath, delay=delay) 106 | 107 | 108 | def kde(): 109 | XDG_CURRENT_DESKTOP = os.environ.get("XDG_CURRENT_DESKTOP") 110 | if XDG_CURRENT_DESKTOP: 111 | return "kde" in XDG_CURRENT_DESKTOP.lower() 112 | 113 | 114 | def gnome(): 115 | XDG_CURRENT_DESKTOP = os.environ.get("XDG_CURRENT_DESKTOP") 116 | if XDG_CURRENT_DESKTOP: 117 | return "gnome" in XDG_CURRENT_DESKTOP.lower() 118 | 119 | 120 | def gnome_version(): 121 | p = EasyProcess(["gnome-shell", "--version"]).call() 122 | # GNOME Shell 41.3 123 | if p.return_code != 0: 124 | return 125 | # 41.3 126 | s = p.stdout.split()[-1] 127 | return [int(x) for x in s.split(".")] 128 | 129 | 130 | def kde_version(): 131 | p = EasyProcess(["plasmashell", "--version"]).call() 132 | # plasmashell 5.14.5 133 | if p.return_code != 0: 134 | return 135 | # 5.14.5 136 | s = p.stdout.split()[-1] 137 | return [int(x) for x in s.split(".")] 138 | 139 | 140 | def check_import(module): 141 | found = False 142 | # try: 143 | # __import__(module) 144 | 145 | # ok = True 146 | # except ImportError: 147 | # pass 148 | 149 | import importlib 150 | 151 | try: 152 | spam_spec = importlib.util.find_spec(module) 153 | found = spam_spec is not None 154 | return found 155 | except ModuleNotFoundError: 156 | return False 157 | 158 | 159 | def prog_check(cmd): 160 | try: 161 | if EasyProcess(cmd).call().return_code == 0: 162 | return True 163 | except Exception: 164 | return False 165 | -------------------------------------------------------------------------------- /tests/config.py: -------------------------------------------------------------------------------- 1 | bbox_ls = [ 2 | None, 3 | (100, 200, 300, 400), 4 | (110, 110, 120, 120), 5 | (100, 100, 200, 200), 6 | (101, 102, 103, 104), 7 | (110, 120, 130, 140), 8 | (100, 100, 101, 110), 9 | (100, 7, 101, 500), 10 | (100, 100, 101, 110), 11 | ] 12 | -------------------------------------------------------------------------------- /tests/double_disp.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | from easyprocess import EasyProcess 5 | from entrypoint2 import entrypoint 6 | from pyvirtualdisplay import Display 7 | 8 | 9 | def check_double_disp(backend): 10 | python = sys.executable 11 | if not backend: 12 | backend = "" 13 | 14 | # the main process may crash, 15 | # so it must be tested in subprocess 16 | fname = __file__.rsplit(".", 1)[0] + ".py" 17 | cmd = [python, fname, backend, "--debug"] 18 | p = EasyProcess(cmd).call() 19 | assert p.return_code == 0 20 | 21 | 22 | @entrypoint 23 | def main(backend): 24 | if not backend: 25 | backend = None 26 | 27 | sys.path.append(os.getcwd()) 28 | from pyscreenshot import grab 29 | 30 | with Display(visible=False): 31 | img = grab(backend=backend) 32 | assert img 33 | 34 | with Display(visible=False): 35 | img = grab(backend=backend) 36 | assert img 37 | -------------------------------------------------------------------------------- /tests/fillscreen.py: -------------------------------------------------------------------------------- 1 | import atexit 2 | import logging 3 | import sys 4 | import tempfile 5 | from os.path import dirname, join 6 | from shutil import rmtree 7 | from time import sleep 8 | 9 | from easyprocess import EasyProcess 10 | from PIL import Image, ImageChops 11 | 12 | import pyscreenshot 13 | from pyscreenshot.util import platform_is_osx, platform_is_win 14 | 15 | # from image_debug import ImageDebug 16 | 17 | 18 | log = logging.getLogger(__name__) 19 | 20 | refimgpath = None 21 | 22 | 23 | class FillscreenError(Exception): 24 | pass 25 | 26 | 27 | def init(): 28 | global refimgpath 29 | if not refimgpath: 30 | d = tempfile.mkdtemp(prefix="fillscreen") 31 | atexit.register(lambda: rmtree(d)) 32 | refimgpath = join(d, "ref.bmp") 33 | 34 | # img_ref = generate_image() 35 | # img_ref.save(refimgpath) 36 | 37 | # fillscreen_tk = join(dirname(__file__), "fillscreen_tk.py") 38 | fillscreen_pygame = join(dirname(__file__), "fillscreen_pygame.py") 39 | python = sys.executable 40 | cmd = [python, fillscreen_pygame, "--image", refimgpath] 41 | cmd += ["--debug"] 42 | # if platform_is_win(): 43 | # cmd = [ 44 | # "C:\\Program Files (x86)\\FastStone Image Viewer\\FSViewer.exe", 45 | # refimgpath, 46 | # ] 47 | # else: 48 | # cmd = [ 49 | # "pqiv", 50 | # "--fullscreen", 51 | # "--hide-info-box", 52 | # "--disable-scaling", 53 | # refimgpath, 54 | # ] 55 | # if platform_is_osx(): 56 | # cmd = [ 57 | # "pqiv", 58 | # "--fullscreen", 59 | # "--hide-info-box", 60 | # "--disable-scaling", 61 | # refimgpath, 62 | # ] 63 | # else: 64 | # cmd = [python, fillscreen_pygame, "--image", refimgpath] 65 | proc = EasyProcess(cmd).start() 66 | atexit.register(proc.stop) 67 | log.debug(refimgpath) 68 | 69 | # sleep(30) 70 | # imdbg = ImageDebug() 71 | t = 0 72 | while True: 73 | sleep(2) 74 | t += 2 75 | if t > 90: 76 | raise FillscreenError("fillscreen timeout: %s" % t) 77 | 78 | if not proc.is_alive(): 79 | raise FillscreenError("fillscreen stopped: %s" % proc) 80 | 81 | try: 82 | img_ref = Image.open(refimgpath) 83 | except FileNotFoundError: 84 | continue 85 | im = pyscreenshot.grab() 86 | # imdbg.img_debug(im, "raw") 87 | im = im.convert("RGB") 88 | img_diff = ImageChops.difference(img_ref, im) 89 | ex = img_diff.getextrema() 90 | log.debug("diff getextrema: %s", ex) 91 | color_diff_max = max([b for (_, b) in ex]) 92 | diff_bbox = img_diff.getbbox() 93 | if diff_bbox is not None: 94 | log.debug( 95 | "different image data. color_diff_max:%s extrema:%s diff_bbox=%s" 96 | % (color_diff_max, ex, diff_bbox) 97 | ) 98 | # imdbg.img_debug(img_ref, "ref") 99 | # imdbg.img_debug(im, "got") 100 | # imdbg.img_debug(img_diff, "img_diff" + str(diff_bbox)) 101 | 102 | if color_diff_max < 10: 103 | break 104 | 105 | # if the OS has color correction 106 | # then the screenshot has slightly different color than the original image 107 | if platform_is_win() or platform_is_osx(): 108 | refimgpath = refimgpath + ".pil.png" 109 | im = pyscreenshot.grab(backend="pil") 110 | im.save(refimgpath) 111 | log.debug("%s saved", refimgpath) 112 | 113 | return refimgpath 114 | -------------------------------------------------------------------------------- /tests/fillscreen_pygame.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import tempfile 4 | from os.path import join 5 | 6 | import pygame 7 | from entrypoint2 import entrypoint 8 | from genimg import generate_image 9 | from pygame.locals import K_ESCAPE, KEYDOWN 10 | 11 | log = logging.getLogger(__name__) 12 | 13 | 14 | def fillscreen_pygame(fimage): 15 | # DBus org.kde.kwin.Screenshot disappears on Kubuntu 20.04 after starting pygame 16 | # fix: System Settings > Compositor > uncheck Allow apps to turn off compositing. 17 | # or https://www.pygame.org/docs/ref/pygame.html 18 | # SDL_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR 19 | # Set to "0" to re-enable the compositor. 20 | os.environ["SDL_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR"] = "0" 21 | pygame.init() 22 | pygame.mouse.set_visible(0) 23 | log.info("pygame modes:%s", pygame.display.list_modes()) 24 | # log.info("pygame info:%s", pygame.display.Info()) 25 | log.info("env $DISPLAY= %s", os.environ.get("DISPLAY")) 26 | 27 | infoObject = pygame.display.Info() 28 | im = generate_image(infoObject.current_w, infoObject.current_h) 29 | im.save(fimage) 30 | 31 | windowSurface = pygame.display.set_mode((0, 0), pygame.FULLSCREEN) 32 | img = pygame.image.load(fimage) 33 | 34 | mainLoop = True 35 | 36 | while mainLoop: 37 | for event in pygame.event.get(): 38 | if event.type == pygame.QUIT: 39 | mainLoop = False 40 | elif event.type == KEYDOWN: 41 | if event.key == K_ESCAPE: 42 | mainLoop = False 43 | 44 | windowSurface.blit(img, (0, 0)) 45 | # pygame.display.flip() 46 | pygame.display.update() 47 | 48 | pygame.quit() 49 | 50 | 51 | @entrypoint 52 | def main(image=""): 53 | if not image: 54 | d = tempfile.mkdtemp(prefix="fillscreen") 55 | # atexit.register(lambda: rmtree(d)) 56 | image = join(d, "ref.bmp") 57 | # im = generate_image() 58 | # im.save(image) 59 | fillscreen_pygame(image) 60 | -------------------------------------------------------------------------------- /tests/fillscreen_tk.py: -------------------------------------------------------------------------------- 1 | """ 2 | import logging 3 | import tempfile 4 | import tkinter 5 | from os.path import join 6 | 7 | from entrypoint2 import entrypoint 8 | from PIL import Image, ImageTk 9 | 10 | from genimg import generate_image 11 | from pyscreenshot.util import platform_is_osx 12 | 13 | log = logging.getLogger(__name__) 14 | 15 | 16 | # https://stackoverflow.com/questions/47316266/can-i-display-image-in-full-screen-mode-with-pil/47317411 17 | def fillscreen_tk(fimage): 18 | pilImage = Image.open(fimage) 19 | root = tkinter.Tk() 20 | w, h = root.winfo_screenwidth(), root.winfo_screenheight() 21 | 22 | root.config(cursor="none") 23 | root.geometry("%dx%d+0+0" % (w, h)) 24 | root.focus_set() 25 | root.bind("", lambda e: (e.widget.withdraw(), e.widget.quit())) 26 | root.bind("q", lambda e: (e.widget.withdraw(), e.widget.quit())) 27 | 28 | # print(w,h) 29 | canvas = tkinter.Canvas(root, width=w, height=h) 30 | canvas.pack() 31 | canvas.configure(background="red") 32 | assert pilImage.size == (w, h) 33 | image = ImageTk.PhotoImage(pilImage) 34 | imagesprite = canvas.create_image(w / 2, h / 2, image=image) 35 | 36 | # make fullscreen 37 | if platform_is_osx(): 38 | root.tk.call( 39 | "::tk::unsupported::MacWindowStyle", "style", root._w, "plain", "none" 40 | ) 41 | root.attributes("-fullscreen", True) 42 | # root.overrideredirect(True) 43 | root.attributes("-topmost", True) 44 | 45 | root.mainloop() 46 | 47 | 48 | @entrypoint 49 | def main(image=""): 50 | if not image: 51 | d = tempfile.mkdtemp(prefix="fillscreen") 52 | image = join(d, "ref.png") 53 | im = generate_image() 54 | im.save(image) 55 | fillscreen_tk(image) 56 | """ 57 | -------------------------------------------------------------------------------- /tests/genimg.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from entrypoint2 import entrypoint 4 | from PIL import Image 5 | 6 | # from size import display_size 7 | 8 | log = logging.getLogger(__name__) 9 | 10 | 11 | def generate_image(w, h): 12 | # w, h = display_size() 13 | log.debug("display size: %s x %s", w, h) 14 | if w <= 0 or h <= 0: 15 | raise ValueError("invalid display size %s x %s" % (w, h)) 16 | img = Image.new("RGB", (w, h), "black") # Create a new black image 17 | pixels = img.load() # Create the pixel map 18 | i = 0 19 | B = 42 20 | for x in range(img.size[0]): # For every pixel: 21 | for y in range(img.size[1]): 22 | r = int(x * 255 / w) 23 | g = int(y * 255 / h) 24 | 25 | b = int(((x % B) * 255 / B + (y % B) * 255 / B) / 2) 26 | pixels[x, y] = (r, g, b) # Set the colour accordingly 27 | i += 1 28 | return img 29 | 30 | 31 | @entrypoint 32 | def main(): 33 | im = generate_image() 34 | im.show() 35 | -------------------------------------------------------------------------------- /tests/image_debug.py: -------------------------------------------------------------------------------- 1 | import atexit 2 | import logging 3 | import os 4 | import shutil 5 | import tempfile 6 | from logging import DEBUG 7 | 8 | log = logging.getLogger(__name__) 9 | 10 | CLEANUP = False 11 | 12 | # for image debug 13 | USE_TESTOUT_DIR = 1 14 | 15 | 16 | class ImageDebug(object): 17 | def __init__(self): 18 | self.index = 0 19 | d = None 20 | if USE_TESTOUT_DIR: 21 | d = os.path.join(os.getcwd(), "testout") 22 | os.makedirs(d, exist_ok=True) 23 | self.dir = tempfile.mkdtemp(dir=d, prefix="pyscreenshot_img_") 24 | if CLEANUP: 25 | atexit.register(shutil.rmtree, self.dir) 26 | 27 | def img_debug(self, im, text): 28 | if not log.isEnabledFor(DEBUG): 29 | return 30 | fname = os.path.join(self.dir, str(self.index).zfill(3) + "_" + text + ".png") 31 | im.save(fname) 32 | log.debug("image (%s) was saved:" % im + fname) 33 | self.index += 1 34 | -------------------------------------------------------------------------------- /tests/notest.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ponty/pyscreenshot/c9f6051fe339b4b4c59ef75aa17140211e51606f/tests/notest.py -------------------------------------------------------------------------------- /tests/size.py: -------------------------------------------------------------------------------- 1 | """ 2 | import os 3 | 4 | # import tkinter 5 | import pygame 6 | from easyprocess import EasyProcess 7 | 8 | from pyscreenshot.util import platform_is_linux, platform_is_osx, platform_is_win 9 | 10 | 11 | def display_size_x(): 12 | # http://www.cyberciti.biz/faq/how-do-i-find-out-screen-resolution-of-my-linux-desktop/ 13 | # xdpyinfo | grep 'dimensions:' 14 | screen_width, screen_height = 0, 0 15 | if not os.environ.get("DISPLAY"): 16 | raise ValueError("missing DISPLAY variable") 17 | xdpyinfo = EasyProcess("xdpyinfo") 18 | xdpyinfo.enable_stdout_log = False 19 | if xdpyinfo.call().return_code != 0: 20 | raise ValueError("xdpyinfo error: %s" % xdpyinfo) 21 | for x in xdpyinfo.stdout.splitlines(): 22 | if "dimensions:" in x: 23 | screen_width, screen_height = map(int, x.strip().split()[1].split("x")) 24 | 25 | return screen_width, screen_height 26 | 27 | 28 | def display_size_osx(): 29 | from Quartz import CGDisplayBounds 30 | from Quartz import CGMainDisplayID 31 | 32 | mainMonitor = CGDisplayBounds(CGMainDisplayID()) 33 | return int(mainMonitor.size.width), int(mainMonitor.size.height) 34 | 35 | 36 | def display_size_win(): 37 | from win32api import GetSystemMetrics 38 | 39 | return int(GetSystemMetrics(0)), int(GetSystemMetrics(1)) 40 | 41 | 42 | # def display_size_tk(): 43 | # root = tkinter.Tk() 44 | # w, h = root.winfo_screenwidth(), root.winfo_screenheight() 45 | # return (w, h) 46 | 47 | 48 | def display_size_pygame(): 49 | infoObject = pygame.display.Info() 50 | return infoObject.current_w, infoObject.current_h 51 | 52 | 53 | def display_size(): 54 | if platform_is_osx(): 55 | w, h = display_size_osx() 56 | 57 | if platform_is_win(): 58 | w, h = display_size_win() 59 | 60 | if platform_is_linux(): 61 | w, h = display_size_x() 62 | 63 | # pgw, pgh = display_size_pygame() 64 | # assert pgw == w 65 | # assert pgh == h 66 | return w, h 67 | """ 68 | -------------------------------------------------------------------------------- /tests/test_check.py: -------------------------------------------------------------------------------- 1 | # this test should come after backend tests 2 | import os 3 | 4 | from pyscreenshot.util import run_mod_as_subproc 5 | 6 | 7 | def test_speedtest(): 8 | assert run_mod_as_subproc("pyscreenshot.check.speedtest").return_code == 0 9 | assert ( 10 | run_mod_as_subproc( 11 | "pyscreenshot.check.speedtest", ["--childprocess", "0"] 12 | ).return_code 13 | == 0 14 | ) 15 | assert ( 16 | run_mod_as_subproc( 17 | "pyscreenshot.check.speedtest", ["--childprocess", "1"] 18 | ).return_code 19 | == 0 20 | ) 21 | 22 | 23 | def test_print_versions(): 24 | assert run_mod_as_subproc("pyscreenshot.check.versions").return_code == 0 25 | 26 | 27 | def test_print_versions_no_path(): 28 | path = os.environ["PATH"] 29 | os.environ["PATH"] = "xxx" 30 | try: 31 | assert run_mod_as_subproc("pyscreenshot.check.versions").return_code == 0 32 | finally: 33 | os.environ["PATH"] = path 34 | -------------------------------------------------------------------------------- /tests/test_default.py: -------------------------------------------------------------------------------- 1 | from bt import backend_to_check 2 | 3 | 4 | def test_default(): 5 | # delay for freedesktop_dbus 6 | backend_to_check(None, delay=0.2) 7 | -------------------------------------------------------------------------------- /tests/test_examples.py: -------------------------------------------------------------------------------- 1 | import os 2 | from tempfile import TemporaryDirectory 3 | 4 | from bt import prog_check 5 | 6 | from pyscreenshot.util import run_mod_as_subproc 7 | 8 | 9 | def check_example(name): 10 | owd = os.getcwd() 11 | with TemporaryDirectory(prefix="pyscreenshot") as tmpdirname: 12 | try: 13 | os.chdir(tmpdirname) 14 | assert run_mod_as_subproc("pyscreenshot.examples." + name).return_code == 0 15 | finally: 16 | os.chdir(owd) 17 | 18 | 19 | def test_grabbox(): 20 | check_example("grabbox") 21 | 22 | 23 | def test_grabfullscreen(): 24 | check_example("grabfullscreen") 25 | 26 | 27 | if prog_check(["Xvfb", "-version"]): 28 | 29 | def test_virtdisp(): 30 | check_example("virtdisp") 31 | -------------------------------------------------------------------------------- /tests/test_freedesktop_dbus.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from bt import backend_to_check 4 | 5 | PYSCREENSHOT_TEST_FREEDESKTOP_DBUS = os.environ.get( 6 | "PYSCREENSHOT_TEST_FREEDESKTOP_DBUS" 7 | ) 8 | 9 | if PYSCREENSHOT_TEST_FREEDESKTOP_DBUS: 10 | 11 | def test_freedesktop_dbus(): 12 | # the previous confirmation dialog can be seen on the next screenshot, 13 | # so some delay is needed 14 | # 0.1 is not enough 15 | backend_to_check("freedesktop_dbus", delay=0.2) 16 | -------------------------------------------------------------------------------- /tests/test_gnome_dbus.py: -------------------------------------------------------------------------------- 1 | from bt import backend_to_check, gnome, gnome_version, kde 2 | 3 | if gnome(): 4 | # GNOME Shell 41.0 disallowed unrestricted access to the screenshot API 5 | if gnome_version()[0] < 41: 6 | 7 | def test_gnome_dbus(): 8 | assert not kde() 9 | backend_to_check("gnome_dbus") 10 | -------------------------------------------------------------------------------- /tests/test_grim.py: -------------------------------------------------------------------------------- 1 | from bt import backend_to_check, prog_check 2 | 3 | from pyscreenshot.util import use_x_display 4 | 5 | if not use_x_display(): 6 | if prog_check(["grim", "-h"]): 7 | 8 | def test_grim(): 9 | backend_to_check("grim") 10 | -------------------------------------------------------------------------------- /tests/test_imagemagick.py: -------------------------------------------------------------------------------- 1 | from bt import backend_to_check, prog_check 2 | 3 | from pyscreenshot.util import use_x_display 4 | 5 | if use_x_display(): 6 | if prog_check(["import", "-version"]): 7 | 8 | def test_imagemagick(): 9 | backend_to_check("imagemagick") 10 | -------------------------------------------------------------------------------- /tests/test_ksnip.py: -------------------------------------------------------------------------------- 1 | from bt import backend_to_check, prog_check 2 | 3 | if prog_check(["ksnip", "--version"]): 4 | 5 | def test_ksnip(): 6 | backend_to_check("ksnip") 7 | -------------------------------------------------------------------------------- /tests/test_kwin_dbus.py: -------------------------------------------------------------------------------- 1 | # from bt import backend_to_check, gnome, kde 2 | 3 | # "The process is not authorized to take a screenshot" 4 | 5 | # if kde(): 6 | 7 | # def test_kwin_dbus(): 8 | # assert not gnome() 9 | # backend_to_check("kwin_dbus") 10 | -------------------------------------------------------------------------------- /tests/test_mac_quartz.py: -------------------------------------------------------------------------------- 1 | from bt import backend_to_check 2 | 3 | from pyscreenshot.util import platform_is_osx 4 | 5 | if platform_is_osx(): 6 | 7 | def test_mac_quartz(): 8 | backend_to_check("mac_quartz") 9 | -------------------------------------------------------------------------------- /tests/test_mac_screencapture.py: -------------------------------------------------------------------------------- 1 | from bt import backend_to_check 2 | 3 | from pyscreenshot.util import platform_is_osx 4 | 5 | if platform_is_osx(): 6 | 7 | def test_mac_screencapture(): 8 | backend_to_check("mac_screencapture") 9 | -------------------------------------------------------------------------------- /tests/test_maim.py: -------------------------------------------------------------------------------- 1 | from bt import backend_to_check, prog_check 2 | 3 | from pyscreenshot.util import use_x_display 4 | 5 | if use_x_display(): 6 | if prog_check(["maim", "--version"]): 7 | 8 | def test_maim(): 9 | backend_to_check("maim") 10 | -------------------------------------------------------------------------------- /tests/test_mss.py: -------------------------------------------------------------------------------- 1 | from bt import backend_to_check, check_import 2 | 3 | from pyscreenshot import FailedBackendError 4 | from pyscreenshot.util import ( 5 | platform_is_linux, 6 | platform_is_osx, 7 | platform_is_win, 8 | use_x_display, 9 | ) 10 | 11 | try: 12 | from Xlib import display 13 | except ImportError: 14 | display = None 15 | 16 | 17 | def missing_RANDR(): 18 | # https://github.com/python-xlib/python-xlib/blob/master/examples/xrandr.py#L44 19 | if platform_is_osx(): 20 | return False 21 | if platform_is_win(): 22 | return False 23 | if not display: 24 | return False 25 | disp = display.Display() 26 | return not disp.has_extension("RANDR") 27 | 28 | 29 | ok = False 30 | if check_import("mss"): 31 | if platform_is_osx() and not use_x_display(): 32 | ok = True 33 | if platform_is_linux() and use_x_display(): 34 | ok = True 35 | if platform_is_win(): 36 | ok = True 37 | 38 | if ok: 39 | 40 | def test_mss(): 41 | if missing_RANDR(): 42 | try: 43 | backend_to_check("mss") 44 | except FailedBackendError: 45 | pass 46 | else: 47 | backend_to_check("mss") 48 | -------------------------------------------------------------------------------- /tests/test_pil.py: -------------------------------------------------------------------------------- 1 | from bt import backend_to_check 2 | 3 | from pyscreenshot.util import ( 4 | platform_is_linux, 5 | platform_is_osx, 6 | platform_is_win, 7 | use_x_display, 8 | ) 9 | 10 | ok = False 11 | if platform_is_osx() and not use_x_display(): 12 | ok = True 13 | if platform_is_linux() and use_x_display(): 14 | ok = True 15 | if platform_is_win(): 16 | ok = True 17 | 18 | if ok: 19 | 20 | def test_pil(): 21 | backend_to_check("pil") 22 | -------------------------------------------------------------------------------- /tests/test_pygdk3.py: -------------------------------------------------------------------------------- 1 | from bt import backend_to_check, check_import 2 | 3 | from pyscreenshot.util import use_x_display 4 | 5 | ok = False 6 | if check_import("gi"): 7 | if use_x_display(): 8 | import gi 9 | 10 | # Arch: AttributeError: module 'gi' has no attribute 'require_version' 11 | try: 12 | gi.require_version("Gdk", "3.0") 13 | ok = True 14 | except AttributeError: 15 | pass 16 | except ValueError: 17 | pass 18 | if ok: 19 | 20 | def test_pygdk3(): 21 | backend_to_check("pygdk3") 22 | -------------------------------------------------------------------------------- /tests/test_pyqt4.py: -------------------------------------------------------------------------------- 1 | from bt import backend_to_check, check_import 2 | 3 | from pyscreenshot.util import platform_is_osx 4 | 5 | # qt color problem on osx 6 | if not platform_is_osx(): 7 | if check_import("PyQt4"): 8 | 9 | def test_pyqt4(): 10 | backend_to_check("pyqt") 11 | -------------------------------------------------------------------------------- /tests/test_pyqt5.py: -------------------------------------------------------------------------------- 1 | from bt import backend_to_check, check_import 2 | 3 | from pyscreenshot.util import platform_is_osx 4 | 5 | # qt color problem on osx 6 | if not platform_is_osx(): 7 | if check_import("PyQt5.Qt"): 8 | if check_import("PyQt5.QtGui"): 9 | if check_import("PyQt5.QtWidgets"): 10 | 11 | def test_pyqt5(): 12 | backend_to_check("pyqt5") 13 | -------------------------------------------------------------------------------- /tests/test_pyside.py: -------------------------------------------------------------------------------- 1 | from bt import backend_to_check, check_import 2 | 3 | from pyscreenshot.util import platform_is_osx 4 | 5 | # qt color problem on osx 6 | if not platform_is_osx(): 7 | if check_import("PySide"): 8 | 9 | def test_pyside(): 10 | backend_to_check("pyside") 11 | -------------------------------------------------------------------------------- /tests/test_pyside2.py: -------------------------------------------------------------------------------- 1 | from bt import backend_to_check, check_import 2 | 3 | from pyscreenshot.util import platform_is_osx 4 | 5 | # qt color problem on osx 6 | if not platform_is_osx(): 7 | if check_import("PySide2"): 8 | 9 | def test_pyside2(): 10 | backend_to_check("pyside2") 11 | -------------------------------------------------------------------------------- /tests/test_scrot.py: -------------------------------------------------------------------------------- 1 | from bt import backend_to_check, prog_check 2 | 3 | from pyscreenshot.util import use_x_display 4 | 5 | if use_x_display(): 6 | if prog_check(["scrot", "-version"]): 7 | 8 | def test_scrot(): 9 | backend_to_check("scrot") 10 | -------------------------------------------------------------------------------- /tests/test_wx.py: -------------------------------------------------------------------------------- 1 | from bt import backend_to_check, check_import 2 | 3 | from pyscreenshot.util import platform_is_osx 4 | 5 | if not platform_is_osx() and check_import("wx"): 6 | 7 | def test_wx(): 8 | backend_to_check("wx") 9 | -------------------------------------------------------------------------------- /tests/test_x_gnome_screenshot.py: -------------------------------------------------------------------------------- 1 | from bt import backend_to_check, gnome, kde, prog_check 2 | 3 | # 'gnome-screenshot' makes strange effects on screen. 4 | # It can disturb later tests. 5 | # Make this test the last by starting the name with 'x' 6 | if gnome(): 7 | if prog_check(["gnome-screenshot", "--version"]): 8 | 9 | def test_gnome_screenshot(): 10 | assert not kde() 11 | # the flash effect can be seen on the next screenshot, 12 | # so some delay is needed 13 | backend_to_check("gnome-screenshot", delay=1) 14 | -------------------------------------------------------------------------------- /tests/vagrant/Vagrantfile.debian10.gnome.wayland.rb: -------------------------------------------------------------------------------- 1 | Vagrant.configure("2") do |config| 2 | config.vm.box = "debian/buster64" 3 | config.vm.boot_timeout = 600 4 | 5 | config.vm.provider "virtualbox" do |vb| 6 | # vb.gui = true 7 | vb.memory = "2048" 8 | vb.name = "pyscreenshot.debian10.gnome.wayland" 9 | end 10 | 11 | config.vm.provision "shell", path: "tests/vagrant/debian10.gnome.wayland.sh", privileged: true 12 | 13 | config.ssh.extra_args = ["-t", "cd /vagrant; bash --login"] 14 | end 15 | 16 | # export VAGRANT_VAGRANTFILE=Vagrantfile.debian10.gnome.wayland.rb;export VAGRANT_DOTFILE_PATH=.vagrant_${VAGRANT_VAGRANTFILE} 17 | # vagrant up && vagrant ssh 18 | -------------------------------------------------------------------------------- /tests/vagrant/Vagrantfile.debian10.gnome.x11.rb: -------------------------------------------------------------------------------- 1 | Vagrant.configure("2") do |config| 2 | config.vm.box = "debian/buster64" 3 | config.vm.boot_timeout = 600 4 | 5 | config.vm.provider "virtualbox" do |vb| 6 | # vb.gui = true 7 | vb.memory = "2048" 8 | vb.name = "pyscreenshot.debian10.gnome.x11" 9 | end 10 | 11 | config.vm.provision "shell", path: "tests/vagrant/debian10.gnome.x11.sh", privileged: true 12 | config.vm.provision "shell", inline: "echo 'WaylandEnable=false' >> /etc/gdm3/daemon.conf", privileged: true 13 | 14 | config.ssh.extra_args = ["-t", "cd /vagrant; bash --login"] 15 | end 16 | 17 | # export VAGRANT_VAGRANTFILE=Vagrantfile.debian10.gnome.x11.rb;export VAGRANT_DOTFILE_PATH=.vagrant_${VAGRANT_VAGRANTFILE} 18 | # vagrant up && vagrant ssh 19 | -------------------------------------------------------------------------------- /tests/vagrant/Vagrantfile.debian10.kde.wayland.rb: -------------------------------------------------------------------------------- 1 | Vagrant.configure("2") do |config| 2 | config.vm.box = "debian/buster64" 3 | config.vm.boot_timeout = 600 4 | 5 | config.vm.provider "virtualbox" do |vb| 6 | # vb.gui = true 7 | vb.memory = "2048" 8 | vb.name = "pyscreenshot.debian10.kde.wayland" 9 | end 10 | 11 | config.vm.provision "shell", path: "tests/vagrant/debian10.kde.wayland.sh", privileged: true 12 | 13 | config.ssh.extra_args = ["-t", "cd /vagrant; bash --login"] 14 | end 15 | 16 | # export VAGRANT_VAGRANTFILE=Vagrantfile.debian10.kde.wayland.rb;export VAGRANT_DOTFILE_PATH=.vagrant_${VAGRANT_VAGRANTFILE} 17 | # vagrant up && vagrant ssh 18 | -------------------------------------------------------------------------------- /tests/vagrant/Vagrantfile.debian10.kde.x11.rb: -------------------------------------------------------------------------------- 1 | Vagrant.configure("2") do |config| 2 | config.vm.box = "debian/buster64" 3 | config.vm.boot_timeout = 600 4 | 5 | config.vm.provider "virtualbox" do |vb| 6 | # vb.gui = true 7 | vb.memory = "2048" 8 | vb.name = "pyscreenshot.debian10.kde.x11" 9 | end 10 | 11 | config.vm.provision "shell", path: "tests/vagrant/debian10.kde.x11.sh", privileged: true 12 | 13 | config.ssh.extra_args = ["-t", "cd /vagrant; bash --login"] 14 | end 15 | 16 | # export VAGRANT_VAGRANTFILE=Vagrantfile.debian10.kde.x11.rb;export VAGRANT_DOTFILE_PATH=.vagrant_${VAGRANT_VAGRANTFILE} 17 | # vagrant up && vagrant ssh 18 | -------------------------------------------------------------------------------- /tests/vagrant/Vagrantfile.debian11.gnome.wayland.rb: -------------------------------------------------------------------------------- 1 | Vagrant.configure("2") do |config| 2 | config.vm.box = "debian/bullseye64" 3 | config.vm.boot_timeout = 600 4 | 5 | config.vm.provider "virtualbox" do |vb| 6 | # vb.gui = true 7 | vb.memory = "2048" 8 | vb.name = "pyscreenshot.debian11.gnome.wayland" 9 | end 10 | 11 | config.vm.provision "shell", path: "tests/vagrant/debian10.gnome.wayland.sh", privileged: true 12 | 13 | config.ssh.extra_args = ["-t", "cd /vagrant; bash --login"] 14 | end 15 | 16 | # export VAGRANT_VAGRANTFILE=Vagrantfile.debian11.gnome.wayland.rb;export VAGRANT_DOTFILE_PATH=.vagrant_${VAGRANT_VAGRANTFILE} 17 | # vagrant up && vagrant ssh 18 | -------------------------------------------------------------------------------- /tests/vagrant/Vagrantfile.debian11.gnome.x11.rb: -------------------------------------------------------------------------------- 1 | Vagrant.configure("2") do |config| 2 | config.vm.box = "debian/bullseye64" 3 | config.vm.boot_timeout = 600 4 | 5 | config.vm.provider "virtualbox" do |vb| 6 | # vb.gui = true 7 | vb.memory = "2048" 8 | vb.name = "pyscreenshot.debian11.gnome.x11" 9 | end 10 | 11 | config.vm.provision "shell", path: "tests/vagrant/debian10.gnome.x11.sh", privileged: true 12 | # config.vm.provision "shell", path: "tests/vagrant/debian10_gnome.sh", privileged: true 13 | config.vm.provision "shell", inline: "echo 'WaylandEnable=false' >> /etc/gdm3/daemon.conf", privileged: true 14 | 15 | config.ssh.extra_args = ["-t", "cd /vagrant; bash --login"] 16 | end 17 | 18 | # export VAGRANT_VAGRANTFILE=Vagrantfile.debian11.gnome.x11.rb;export VAGRANT_DOTFILE_PATH=.vagrant_${VAGRANT_VAGRANTFILE} 19 | # vagrant up && vagrant ssh 20 | -------------------------------------------------------------------------------- /tests/vagrant/Vagrantfile.debian11.kde.wayland.rb: -------------------------------------------------------------------------------- 1 | Vagrant.configure("2") do |config| 2 | config.vm.box = "debian/bullseye64" 3 | config.vm.boot_timeout = 600 4 | 5 | config.vm.provider "virtualbox" do |vb| 6 | # vb.gui = true 7 | vb.memory = "2048" 8 | vb.name = "pyscreenshot.debian11.kde.wayland" 9 | end 10 | 11 | config.vm.provision "shell", path: "tests/vagrant/debian10.kde.wayland.sh", privileged: true 12 | 13 | config.ssh.extra_args = ["-t", "cd /vagrant; bash --login"] 14 | end 15 | 16 | # export VAGRANT_VAGRANTFILE=Vagrantfile.debian11.kde.wayland.rb;export VAGRANT_DOTFILE_PATH=.vagrant_${VAGRANT_VAGRANTFILE} 17 | # vagrant up && vagrant ssh 18 | -------------------------------------------------------------------------------- /tests/vagrant/Vagrantfile.debian11.kde.x11.rb: -------------------------------------------------------------------------------- 1 | Vagrant.configure("2") do |config| 2 | config.vm.box = "debian/bullseye64" 3 | config.vm.boot_timeout = 600 4 | 5 | config.vm.provider "virtualbox" do |vb| 6 | # vb.gui = true 7 | vb.memory = "2048" 8 | vb.name = "pyscreenshot.debian11.kde.x11" 9 | end 10 | 11 | config.vm.provision "shell", path: "tests/vagrant/debian10.kde.x11.sh", privileged: true 12 | 13 | config.ssh.extra_args = ["-t", "cd /vagrant; bash --login"] 14 | end 15 | 16 | # export VAGRANT_VAGRANTFILE=Vagrantfile.debian11.kde.x11.rb;export VAGRANT_DOTFILE_PATH=.vagrant_${VAGRANT_VAGRANTFILE} 17 | # vagrant up && vagrant ssh 18 | -------------------------------------------------------------------------------- /tests/vagrant/Vagrantfile.kubuntu.20.04.rb: -------------------------------------------------------------------------------- 1 | Vagrant.configure(2) do |config| 2 | config.vm.box = "ubuntu/focal64" 3 | 4 | config.vm.boot_timeout = 600 5 | 6 | config.vm.provider "virtualbox" do |vb| 7 | # vb.gui = true 8 | vb.memory = "2048" 9 | vb.name = "pyscreenshot_kubuntu.20.04" 10 | end 11 | 12 | config.vm.provision "shell", path: "tests/vagrant/kubuntu.20.04.sh", privileged: true 13 | 14 | config.ssh.extra_args = ["-t", "cd /vagrant; bash --login"] 15 | end 16 | 17 | # export VAGRANT_VAGRANTFILE=Vagrantfile.kubuntu.20.04.rb;export VAGRANT_DOTFILE_PATH=.vagrant_${VAGRANT_VAGRANTFILE} 18 | # vagrant up && vagrant ssh 19 | -------------------------------------------------------------------------------- /tests/vagrant/Vagrantfile.kubuntu.22.04.rb: -------------------------------------------------------------------------------- 1 | Vagrant.configure(2) do |config| 2 | config.vm.box = "ubuntu/jammy64" 3 | 4 | config.vm.boot_timeout = 600 5 | 6 | config.vm.provider "virtualbox" do |vb| 7 | # vb.gui = true 8 | vb.memory = "2048" 9 | vb.name = "pyscreenshot_kubuntu.22.04" 10 | end 11 | 12 | config.vm.provision "shell", path: "tests/vagrant/kubuntu.22.04.sh", privileged: true 13 | 14 | config.ssh.extra_args = ["-t", "cd /vagrant; bash --login"] 15 | end 16 | 17 | # export VAGRANT_VAGRANTFILE=Vagrantfile.kubuntu.20.04.rb;export VAGRANT_DOTFILE_PATH=.vagrant_${VAGRANT_VAGRANTFILE} 18 | # vagrant up && vagrant ssh 19 | -------------------------------------------------------------------------------- /tests/vagrant/Vagrantfile.kubuntu.22.10.rb: -------------------------------------------------------------------------------- 1 | Vagrant.configure(2) do |config| 2 | config.vm.box = "ubuntu/kinetic64" 3 | 4 | config.vm.boot_timeout = 600 5 | config.vm.provider "virtualbox" do |vb| 6 | vb.memory = "2048" 7 | vb.name = "pyscreenshot_kubuntu.22.10" 8 | end 9 | 10 | config.vm.provision "shell", path: "tests/vagrant/kubuntu.22.10.sh", privileged: true 11 | 12 | config.ssh.extra_args = ["-t", "cd /vagrant; bash --login"] 13 | end 14 | 15 | -------------------------------------------------------------------------------- /tests/vagrant/Vagrantfile.lubuntu.20.04.rb: -------------------------------------------------------------------------------- 1 | Vagrant.configure(2) do |config| 2 | config.vm.box = "ubuntu/focal64" 3 | 4 | config.vm.boot_timeout = 600 5 | config.vm.provider "virtualbox" do |vb| 6 | # vb.gui = true 7 | vb.memory = "2048" 8 | vb.name = "pyscreenshot_lubuntu.20.04" 9 | end 10 | 11 | config.vm.provision "shell", path: "tests/vagrant/lubuntu.20.04.sh", privileged: true 12 | 13 | config.ssh.extra_args = ["-t", "cd /vagrant; bash --login"] 14 | end 15 | 16 | # export VAGRANT_VAGRANTFILE=Vagrantfile.lubuntu.20.04.rb;export VAGRANT_DOTFILE_PATH=.vagrant_${VAGRANT_VAGRANTFILE} 17 | # vagrant up && vagrant ssh 18 | -------------------------------------------------------------------------------- /tests/vagrant/Vagrantfile.lubuntu.22.04.rb: -------------------------------------------------------------------------------- 1 | Vagrant.configure(2) do |config| 2 | config.vm.box = "ubuntu/jammy64" 3 | 4 | config.vm.boot_timeout = 600 5 | config.vm.provider "virtualbox" do |vb| 6 | # vb.gui = true 7 | vb.memory = "2048" 8 | vb.name = "pyscreenshot_lubuntu.22.04" 9 | end 10 | 11 | config.vm.provision "shell", path: "tests/vagrant/lubuntu.22.04.sh", privileged: true 12 | 13 | config.ssh.extra_args = ["-t", "cd /vagrant; bash --login"] 14 | end 15 | 16 | # export VAGRANT_VAGRANTFILE=Vagrantfile.lubuntu.20.04.rb;export VAGRANT_DOTFILE_PATH=.vagrant_${VAGRANT_VAGRANTFILE} 17 | # vagrant up && vagrant ssh 18 | -------------------------------------------------------------------------------- /tests/vagrant/Vagrantfile.lubuntu.22.10.rb: -------------------------------------------------------------------------------- 1 | Vagrant.configure(2) do |config| 2 | config.vm.box = "ubuntu/kinetic64" 3 | 4 | config.vm.boot_timeout = 600 5 | config.vm.provider "virtualbox" do |vb| 6 | vb.memory = "2048" 7 | vb.name = "pyscreenshot_lubuntu.22.10" 8 | end 9 | 10 | config.vm.provision "shell", path: "tests/vagrant/lubuntu.22.10.sh", privileged: true 11 | 12 | config.ssh.extra_args = ["-t", "cd /vagrant; bash --login"] 13 | end 14 | 15 | -------------------------------------------------------------------------------- /tests/vagrant/Vagrantfile.ubuntu.20.04.rb: -------------------------------------------------------------------------------- 1 | Vagrant.configure(2) do |config| 2 | config.vm.box = "ubuntu/focal64" 3 | 4 | config.vm.boot_timeout = 600 5 | config.vm.provider "virtualbox" do |vb| 6 | #vb.gui = true 7 | vb.memory = "2048" 8 | vb.name = "pyscreenshot_ubuntu.20.04" 9 | end 10 | 11 | config.vm.provision "shell", path: "tests/vagrant/ubuntu.20.04.sh", privileged: true 12 | 13 | config.ssh.extra_args = ["-t", "cd /vagrant; bash --login"] 14 | end 15 | 16 | # export VAGRANT_VAGRANTFILE=Vagrantfile.ubuntu.20.04.rb;export VAGRANT_DOTFILE_PATH=.vagrant_${VAGRANT_VAGRANTFILE} 17 | # vagrant up && vagrant ssh 18 | -------------------------------------------------------------------------------- /tests/vagrant/Vagrantfile.ubuntu.22.04.rb: -------------------------------------------------------------------------------- 1 | Vagrant.configure(2) do |config| 2 | config.vm.box = "ubuntu/jammy64" 3 | 4 | config.vm.boot_timeout = 600 5 | config.vm.provider "virtualbox" do |vb| 6 | #vb.gui = true 7 | vb.memory = "2048" 8 | vb.name = "pyscreenshot_ubuntu.22.04" 9 | vb.customize ["modifyvm", :id, "--graphicscontroller", "vmsvga"] 10 | end 11 | 12 | config.vm.provision "shell", path: "tests/vagrant/ubuntu.22.04.sh", privileged: true 13 | 14 | config.ssh.extra_args = ["-t", "cd /vagrant; bash --login"] 15 | end 16 | 17 | # export VAGRANT_VAGRANTFILE=Vagrantfile.ubuntu.22.04.rb;export VAGRANT_DOTFILE_PATH=.vagrant_${VAGRANT_VAGRANTFILE} 18 | # vagrant up && vagrant ssh 19 | -------------------------------------------------------------------------------- /tests/vagrant/Vagrantfile.ubuntu.22.04.sway.rb: -------------------------------------------------------------------------------- 1 | Vagrant.configure(2) do |config| 2 | config.vm.box = "ubuntu/jammy64" 3 | 4 | config.vm.boot_timeout = 600 5 | config.vm.provider "virtualbox" do |vb| 6 | # vb.gui = true 7 | vb.memory = "2048" 8 | vb.name = "pyscreenshot.ubuntu.22.04.sway" 9 | vb.customize ["modifyvm", :id, "--graphicscontroller", "vmsvga"] 10 | vb.customize ["modifyvm", :id, "--accelerate3d", "on"] 11 | end 12 | 13 | config.vm.provision "shell", path: "tests/vagrant/ubuntu.22.04.sway.sh", privileged: true 14 | 15 | config.ssh.extra_args = ["-t", "cd /vagrant; bash --login"] 16 | end 17 | 18 | # export VAGRANT_VAGRANTFILE=Vagrantfile.ubuntu.22.04.sway.rb;export VAGRANT_DOTFILE_PATH=.vagrant_${VAGRANT_VAGRANTFILE} 19 | # vagrant up && vagrant ssh 20 | -------------------------------------------------------------------------------- /tests/vagrant/Vagrantfile.ubuntu.22.10.rb: -------------------------------------------------------------------------------- 1 | Vagrant.configure(2) do |config| 2 | config.vm.box = "ubuntu/kinetic64" 3 | 4 | config.vm.boot_timeout = 600 5 | config.vm.provider "virtualbox" do |vb| 6 | vb.memory = "2048" 7 | vb.name = "pyscreenshot_ubuntu.22.10" 8 | vb.customize ["modifyvm", :id, "--graphicscontroller", "vmsvga"] 9 | end 10 | 11 | config.vm.provision "shell", path: "tests/vagrant/ubuntu.22.10.sh", privileged: true 12 | 13 | config.ssh.extra_args = ["-t", "cd /vagrant; bash --login"] 14 | end 15 | 16 | -------------------------------------------------------------------------------- /tests/vagrant/Vagrantfile.ubuntu.server.22.04.rb: -------------------------------------------------------------------------------- 1 | Vagrant.configure(2) do |config| 2 | config.vm.box = "ubuntu/jammy64" 3 | 4 | config.vm.boot_timeout = 600 5 | 6 | config.vm.provider "virtualbox" do |vb| 7 | # vb.gui = true 8 | vb.memory = "2048" 9 | vb.name = "pyscreenshot_ubuntu.server.22.04" 10 | end 11 | 12 | config.vm.provision "shell", path: "tests/vagrant/ubuntu.server.sh", privileged: true 13 | 14 | config.ssh.extra_args = ["-t", "cd /vagrant; bash --login"] 15 | end 16 | -------------------------------------------------------------------------------- /tests/vagrant/Vagrantfile.xubuntu.20.04.rb: -------------------------------------------------------------------------------- 1 | Vagrant.configure(2) do |config| 2 | config.vm.box = "ubuntu/focal64" 3 | 4 | config.vm.boot_timeout = 600 5 | config.vm.provider "virtualbox" do |vb| 6 | #vb.gui = true 7 | vb.memory = "2048" 8 | vb.name = "pyscreenshot_xubuntu.20.04" 9 | end 10 | 11 | config.vm.provision "shell", path: "tests/vagrant/xubuntu.20.04.sh", privileged: true 12 | config.ssh.extra_args = ["-t", "cd /vagrant; bash --login"] 13 | end 14 | 15 | # export VAGRANT_VAGRANTFILE=Vagrantfile.xubuntu.20.04.rb;export VAGRANT_DOTFILE_PATH=.vagrant_${VAGRANT_VAGRANTFILE} 16 | # vagrant up && vagrant ssh 17 | -------------------------------------------------------------------------------- /tests/vagrant/Vagrantfile.xubuntu.22.04.rb: -------------------------------------------------------------------------------- 1 | Vagrant.configure(2) do |config| 2 | config.vm.box = "ubuntu/jammy64" 3 | 4 | config.vm.boot_timeout = 600 5 | config.vm.provider "virtualbox" do |vb| 6 | #vb.gui = true 7 | vb.memory = "2048" 8 | vb.name = "pyscreenshot_xubuntu.22.04" 9 | end 10 | 11 | config.vm.provision "shell", path: "tests/vagrant/xubuntu.22.04.sh", privileged: true 12 | config.ssh.extra_args = ["-t", "cd /vagrant; bash --login"] 13 | end 14 | 15 | # export VAGRANT_VAGRANTFILE=Vagrantfile.xubuntu.20.04.rb;export VAGRANT_DOTFILE_PATH=.vagrant_${VAGRANT_VAGRANTFILE} 16 | # vagrant up && vagrant ssh 17 | -------------------------------------------------------------------------------- /tests/vagrant/Vagrantfile.xubuntu.22.10.rb: -------------------------------------------------------------------------------- 1 | Vagrant.configure(2) do |config| 2 | config.vm.box = "ubuntu/kinetic64" 3 | 4 | config.vm.boot_timeout = 600 5 | config.vm.provider "virtualbox" do |vb| 6 | vb.memory = "2048" 7 | vb.name = "pyscreenshot_xubuntu.22.10" 8 | end 9 | 10 | config.vm.provision "shell", path: "tests/vagrant/xubuntu.22.10.sh", privileged: true 11 | config.ssh.extra_args = ["-t", "cd /vagrant; bash --login"] 12 | end 13 | 14 | -------------------------------------------------------------------------------- /tests/vagrant/debian10.gnome.wayland.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | export DEBIAN_FRONTEND=noninteractive 3 | sudo apt-get update 4 | 5 | # to avoid system message about upgrades 6 | # sudo apt-get upgrade -y 7 | 8 | # gnome 9 | sudo apt-get install -y gnome-core gnome-screenshot 10 | 11 | # test dependencies 12 | # sudo add-apt-repository --yes ppa:jan-simon/pqiv 13 | # sudo apt-get update 14 | sudo apt-get install -y x11-utils xvfb python3-pip 15 | sudo python3 -m pip install tox 16 | 17 | # tools 18 | sudo apt-get install -y mc htop 19 | 20 | # autologin 21 | echo ' 22 | [daemon] 23 | AutomaticLoginEnable = true 24 | AutomaticLogin = vagrant 25 | ' > /etc/gdm3/daemon.conf 26 | 27 | # Disable Lock Screen and Screen Saver Locking 28 | sudo -H -u vagrant bash -c 'dbus-launch gsettings set org.gnome.desktop.session idle-delay 0' 29 | 30 | # disable notifications 31 | sudo -H -u vagrant bash -c 'dbus-launch gsettings set org.gnome.desktop.notifications show-banners false' 32 | 33 | # autostart terminal 34 | sudo -H -u vagrant bash -c 'mkdir -p /home/vagrant/.config/autostart/ && cp /usr/share/applications/org.gnome.Terminal.desktop /home/vagrant/.config/autostart/' 35 | 36 | 37 | -------------------------------------------------------------------------------- /tests/vagrant/debian10.gnome.x11.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cd /vagrant/tests/vagrant 3 | ./debian10.gnome.wayland.sh 4 | 5 | echo ' 6 | WaylandEnable=false 7 | ' >> /etc/gdm3/daemon.conf -------------------------------------------------------------------------------- /tests/vagrant/debian10.kde.wayland.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | mkdir /etc/sddm.conf.d 4 | echo '[Autologin] 5 | User=vagrant 6 | #Session=plasma.desktop 7 | Session=plasmawayland.desktop' >/etc/sddm.conf.d/autologin.conf 8 | 9 | cd /vagrant/tests/vagrant 10 | ./debian10_kde.sh 11 | sudo apt-get install -y plasma-workspace-wayland 12 | 13 | sudo apt-get install -y ksnip 14 | -------------------------------------------------------------------------------- /tests/vagrant/debian10.kde.x11.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | mkdir /etc/sddm.conf.d 3 | echo '[Autologin] 4 | User=vagrant 5 | Session=plasma.desktop 6 | #Session=plasmawayland.desktop' > /etc/sddm.conf.d/autologin.conf 7 | 8 | cd /vagrant/tests/vagrant 9 | ./debian10_kde.sh 10 | 11 | -------------------------------------------------------------------------------- /tests/vagrant/debian10_kde.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | export DEBIAN_FRONTEND=noninteractive 3 | 4 | sudo apt-get update 5 | # sudo apt-get upgrade -y 6 | 7 | # kde 8 | sudo apt-get install -y kde-plasma-desktop 9 | 10 | # test dependencies 11 | # sudo add-apt-repository --yes ppa:jan-simon/pqiv 12 | # sudo apt-get update 13 | sudo apt-get install -y x11-utils xvfb python3-pip 14 | sudo python3 -m pip install tox 15 | 16 | # tools 17 | sudo apt-get install -y mc htop 18 | 19 | # TODO: pyqt5 crash: 20 | # $ python3 -c 'from PyQt5 import QtWidgets;app = QtWidgets.QApplication([])' 21 | # malloc_consolidate(): invalid chunk size 22 | # KCrash: crashing... crashRecursionCounter = 2 23 | # KCrash: Application Name = python3.7 path = /usr/bin pid = 1840 24 | # KCrash: Arguments: 25 | # Alarm clock 26 | # -> remove pyqt5 27 | sudo apt-get -y remove python3-pyqt5 28 | 29 | # disable screen lock 30 | sudo -H -u vagrant bash -c 'mkdir -p /home/vagrant/.config' 31 | echo '[$Version] 32 | update_info=kscreenlocker.upd:0.1-autolock 33 | 34 | [Daemon] 35 | Autolock=false 36 | Timeout=60' > /home/vagrant/.config/kscreenlockerrc 37 | 38 | # autostart konsole 39 | sudo -H -u vagrant bash -c 'mkdir -p /home/vagrant/.config/autostart/ && cp /usr/share/applications/org.kde.konsole.desktop /home/vagrant/.config/autostart/' 40 | -------------------------------------------------------------------------------- /tests/vagrant/gdm3.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | sudo apt-get install -y gdm3 4 | 5 | # autologin 6 | echo ' 7 | [daemon] 8 | AutomaticLoginEnable = true 9 | AutomaticLogin = vagrant 10 | ' > /etc/gdm3/custom.conf 11 | 12 | sudo systemctl enable gdm3 13 | # sudo systemctl start gdm3 14 | 15 | 16 | -------------------------------------------------------------------------------- /tests/vagrant/kubuntu.20.04.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | /vagrant/tests/vagrant/ubudep.sh 3 | 4 | export DEBIAN_FRONTEND=noninteractive 5 | APT="apt-get -o Dpkg::Options::=--force-confold -o Dpkg::Options::=--force-confdef -y --allow-downgrades --allow-remove-essential --allow-change-held-packages" 6 | 7 | $APT install kubuntu-desktop^ 8 | $APT remove sddm 9 | $APT install lightdm 10 | 11 | # autologin 12 | echo ' 13 | [Seat:*] 14 | autologin-user=vagrant 15 | autologin-session=plasma 16 | ' >>/etc/lightdm/lightdm.conf.d/12-autologin.conf 17 | 18 | # disable screen lock 19 | sudo -H -u vagrant bash -c 'mkdir -p /home/vagrant/.config' 20 | echo '[$Version] 21 | update_info=kscreenlocker.upd:0.1-autolock 22 | 23 | [Daemon] 24 | Autolock=false 25 | Timeout=60' >/home/vagrant/.config/kscreenlockerrc 26 | 27 | # autostart konsole 28 | sudo -H -u vagrant bash -c 'mkdir -p /home/vagrant/.config/autostart/ && cp /usr/share/applications/org.kde.konsole.desktop /home/vagrant/.config/autostart/' 29 | 30 | # TODO: pyqt5 crash: 31 | apt-get -y remove python3-pyqt5 32 | -------------------------------------------------------------------------------- /tests/vagrant/kubuntu.22.04.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | /vagrant/tests/vagrant/ubudep.sh 3 | 4 | export DEBIAN_FRONTEND=noninteractive 5 | APT="apt-get -o Dpkg::Options::=--force-confold -o Dpkg::Options::=--force-confdef -y --allow-downgrades --allow-remove-essential --allow-change-held-packages" 6 | 7 | $APT install kubuntu-desktop^ 8 | $APT remove sddm 9 | $APT install lightdm 10 | 11 | # autologin 12 | echo ' 13 | [Seat:*] 14 | autologin-user=vagrant 15 | autologin-session=plasma 16 | ' >>/etc/lightdm/lightdm.conf.d/12-autologin.conf 17 | 18 | # disable screen lock 19 | sudo -H -u vagrant bash -c 'mkdir -p /home/vagrant/.config' 20 | echo '[$Version] 21 | update_info=kscreenlocker.upd:0.1-autolock 22 | 23 | [Daemon] 24 | Autolock=false 25 | Timeout=60' >/home/vagrant/.config/kscreenlockerrc 26 | 27 | # autostart konsole 28 | sudo -H -u vagrant bash -c 'mkdir -p /home/vagrant/.config/autostart/ && cp /usr/share/applications/org.kde.konsole.desktop /home/vagrant/.config/autostart/' 29 | 30 | # TODO: pyqt5 crash: 31 | apt-get -y remove python3-pyqt5 32 | -------------------------------------------------------------------------------- /tests/vagrant/kubuntu.22.10.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | /vagrant/tests/vagrant/ubudep.sh 3 | 4 | export DEBIAN_FRONTEND=noninteractive 5 | APT="apt-get -o Dpkg::Options::=--force-confold -o Dpkg::Options::=--force-confdef -y --allow-downgrades --allow-remove-essential --allow-change-held-packages" 6 | 7 | $APT install kubuntu-desktop^ 8 | $APT remove sddm 9 | $APT install lightdm 10 | 11 | # autologin 12 | echo ' 13 | [Seat:*] 14 | autologin-user=vagrant 15 | autologin-session=plasma 16 | ' >>/etc/lightdm/lightdm.conf.d/12-autologin.conf 17 | 18 | # disable screen lock 19 | sudo -H -u vagrant bash -c 'mkdir -p /home/vagrant/.config' 20 | echo '[$Version] 21 | update_info=kscreenlocker.upd:0.1-autolock 22 | 23 | [Daemon] 24 | Autolock=false 25 | Timeout=60' >/home/vagrant/.config/kscreenlockerrc 26 | 27 | # autostart konsole 28 | sudo -H -u vagrant bash -c 'mkdir -p /home/vagrant/.config/autostart/ && cp /usr/share/applications/org.kde.konsole.desktop /home/vagrant/.config/autostart/' 29 | 30 | # TODO: pyqt5 crash: 31 | apt-get -y remove python3-pyqt5 32 | -------------------------------------------------------------------------------- /tests/vagrant/lightdm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # autologin 4 | echo ' 5 | [SeatDefaults] 6 | autologin-user=vagrant 7 | ' >> /etc/lightdm/lightdm.conf.d/12-autologin.conf 8 | 9 | sudo systemctl enable lightdm 10 | # sudo systemctl start lightdm 11 | 12 | -------------------------------------------------------------------------------- /tests/vagrant/lubuntu.20.04.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | /vagrant/tests/vagrant/ubudep.sh 3 | 4 | export DEBIAN_FRONTEND=noninteractive 5 | APT="apt-get -o Dpkg::Options::=--force-confold -o Dpkg::Options::=--force-confdef -y --allow-downgrades --allow-remove-essential --allow-change-held-packages" 6 | 7 | $APT install lubuntu-desktop 8 | $APT remove sddm gdm3 9 | $APT install lightdm 10 | 11 | # autologin 12 | echo ' 13 | [Seat:*] 14 | autologin-user=vagrant 15 | autologin-session=Lubuntu 16 | ' >>/etc/lightdm/lightdm.conf.d/12-autologin.conf 17 | 18 | # autostart qterminal 19 | sudo -H -u vagrant bash -c 'mkdir -p /home/vagrant/.config/autostart/ && cp /usr/share/applications/qterminal.desktop /home/vagrant/.config/autostart/' 20 | -------------------------------------------------------------------------------- /tests/vagrant/lubuntu.22.04.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | /vagrant/tests/vagrant/ubudep.sh 3 | 4 | export DEBIAN_FRONTEND=noninteractive 5 | APT="apt-get -o Dpkg::Options::=--force-confold -o Dpkg::Options::=--force-confdef -y --allow-downgrades --allow-remove-essential --allow-change-held-packages" 6 | 7 | $APT install lubuntu-desktop 8 | $APT remove sddm gdm3 9 | $APT install lightdm 10 | 11 | # autologin 12 | echo ' 13 | [Seat:*] 14 | autologin-user=vagrant 15 | autologin-session=Lubuntu 16 | ' >>/etc/lightdm/lightdm.conf.d/12-autologin.conf 17 | 18 | # autostart qterminal 19 | sudo -H -u vagrant bash -c 'mkdir -p /home/vagrant/.config/autostart/ && cp /usr/share/applications/qterminal.desktop /home/vagrant/.config/autostart/' 20 | -------------------------------------------------------------------------------- /tests/vagrant/lubuntu.22.10.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | /vagrant/tests/vagrant/ubudep.sh 3 | 4 | export DEBIAN_FRONTEND=noninteractive 5 | APT="apt-get -o Dpkg::Options::=--force-confold -o Dpkg::Options::=--force-confdef -y --allow-downgrades --allow-remove-essential --allow-change-held-packages" 6 | 7 | $APT install lubuntu-desktop 8 | $APT remove sddm gdm3 9 | $APT install lightdm 10 | 11 | # autologin 12 | echo ' 13 | [Seat:*] 14 | autologin-user=vagrant 15 | autologin-session=Lubuntu 16 | ' >>/etc/lightdm/lightdm.conf.d/12-autologin.conf 17 | 18 | # autostart qterminal 19 | sudo -H -u vagrant bash -c 'mkdir -p /home/vagrant/.config/autostart/ && cp /usr/share/applications/qterminal.desktop /home/vagrant/.config/autostart/' 20 | -------------------------------------------------------------------------------- /tests/vagrant/osx.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # config.vm.synced_folder ".", "/vagrant", type: "rsync", 5 | # rsync__exclude: [".git/",".tox/"], 6 | # owner: "vagrant", group: "wheel" 7 | 8 | # $script = " 9 | # cd /vagrant/vagrant 10 | # sudo -u vagrant -H ./osx.sh 11 | # " 12 | 13 | #autologin 14 | brew tap xfreebird/utils 15 | brew install kcpassword 16 | enable_autologin "vagrant" "vagrant" 17 | 18 | # disable screensaver 19 | defaults -currentHost write com.apple.screensaver idleTime 0 20 | 21 | # Turn Off System/Display/HDD Sleep 22 | sudo systemsetup -setcomputersleep Never 23 | sudo systemsetup -setdisplaysleep Never 24 | sudo systemsetup -setharddisksleep Never 25 | 26 | # Warning: A newer Command Line Tools release is available. 27 | # Update them from Software Update in System Preferences or run: 28 | # softwareupdate --all --install --force 29 | 30 | # Error: 31 | # homebrew-core is a shallow clone. 32 | # To `brew update`, first run: 33 | git -C /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core fetch --unshallow 34 | 35 | brew install openssl@1.1 36 | brew install python3 37 | brew install wxwidgets 38 | 39 | brew install pidof 40 | # brew install mc htop 41 | brew install imagemagick 42 | # brew install pyqt pyqt@5 43 | # brew install pyside pyside@2 44 | brew install gdk-pixbuf gtk+3 45 | # brew install python-tk 46 | # brew install pqiv 47 | 48 | #brew cask install xquartz 49 | python3 -m pip install --user pillow wxpython pyobjc-framework-Quartz pyobjc-framework-LaunchServices pytest tox 50 | 51 | sudo chown -R vagrant /vagrant 52 | -------------------------------------------------------------------------------- /tests/vagrant/osx1015.1.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # disable screensaver 4 | defaults -currentHost write com.apple.screensaver idleTime 0 5 | 6 | # Turn Off System/Display/HDD Sleep 7 | sudo systemsetup -setcomputersleep Never 8 | sudo systemsetup -setdisplaysleep Never 9 | sudo systemsetup -setharddisksleep Never 10 | 11 | sudo sh -c "echo \"vagrant\\tUsers/vagrant/work\" >>/etc/synthetic.conf" 12 | mkdir -p /Users/vagrant/work 13 | 14 | sudo /usr/sbin/softwareupdate -l 15 | sudo /usr/sbin/softwareupdate -ia 16 | sudo /usr/sbin/softwareupdate -l 17 | 18 | -------------------------------------------------------------------------------- /tests/vagrant/osx1015.2.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export CI=1 4 | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" 5 | 6 | #autologin 7 | brew tap xfreebird/utils 8 | brew install kcpassword 9 | enable_autologin "vagrant" "vagrant" 10 | 11 | 12 | brew install wxwidgets 13 | 14 | brew install pidof 15 | brew install mc htop 16 | brew install imagemagick 17 | # brew install pyqt pyqt@5 18 | # brew install pyside pyside@2 19 | brew install gdk-pixbuf gtk+3 20 | # brew install python-tk 21 | # brew install pqiv 22 | 23 | #brew cask install xquartz 24 | python3 -m pip install --user pillow wxpython pyobjc-framework-Quartz pyobjc-framework-LaunchServices pytest tox 25 | 26 | -------------------------------------------------------------------------------- /tests/vagrant/osx1015.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | brew install wxwidgets 3 | 4 | brew install pidof 5 | # brew install mc htop 6 | brew install imagemagick 7 | # brew install pyqt pyqt@5 8 | # brew install pyside pyside@2 9 | brew install gdk-pixbuf gtk+3 10 | # brew install python-tk 11 | # brew install pqiv 12 | 13 | #brew cask install xquartz 14 | python3 -m pip install --user pillow wxpython pyobjc-framework-Quartz pyobjc-framework-LaunchServices pytest tox 15 | 16 | -------------------------------------------------------------------------------- /tests/vagrant/ubudep.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | export DEBIAN_FRONTEND=noninteractive 3 | APT="apt-get -o Dpkg::Options::=--force-confold -o Dpkg::Options::=--force-confdef -y --allow-downgrades --allow-remove-essential --allow-change-held-packages" 4 | 5 | $APT update 6 | $APT dist-upgrade 7 | 8 | update-locale LANG=en_US.UTF-8 LANGUAGE=en.UTF-8 9 | # echo 'export export LC_ALL=C' >> /home/vagrant/.profile 10 | 11 | # tools 12 | $APT install -y mc htop python3-pip 13 | sudo python3 -m pip install tox -U 14 | 15 | #sudo apt-get install -y x11-utils xvfb 16 | 17 | sudo python3 -m pip install pillow -U -------------------------------------------------------------------------------- /tests/vagrant/ubuntu.20.04.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | /vagrant/tests/vagrant/ubudep.sh 3 | 4 | export DEBIAN_FRONTEND=noninteractive 5 | APT="apt-get -o Dpkg::Options::=--force-confold -o Dpkg::Options::=--force-confdef -y --allow-downgrades --allow-remove-essential --allow-change-held-packages" 6 | 7 | $APT install ubuntu-desktop^ 8 | 9 | /vagrant/tests/vagrant/gdm3.sh 10 | 11 | # autostart gnome terminal 12 | sudo -H -u vagrant bash -c 'mkdir -p /home/vagrant/.config/autostart/ && cp /usr/share/applications/org.gnome.Terminal.desktop /home/vagrant/.config/autostart/' 13 | -------------------------------------------------------------------------------- /tests/vagrant/ubuntu.22.04.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | /vagrant/tests/vagrant/ubudep.sh 3 | 4 | export DEBIAN_FRONTEND=noninteractive 5 | APT="apt-get -o Dpkg::Options::=--force-confold -o Dpkg::Options::=--force-confdef -y --allow-downgrades --allow-remove-essential --allow-change-held-packages" 6 | 7 | $APT install ubuntu-desktop-minimal 8 | 9 | # autologin 10 | echo ' 11 | [daemon] 12 | AutomaticLoginEnable = true 13 | AutomaticLogin = vagrant 14 | ' >/etc/gdm3/custom.conf 15 | 16 | # autostart gnome terminal 17 | sudo -H -u vagrant bash -c 'mkdir -p /home/vagrant/.config/autostart/ && cp /usr/share/applications/org.gnome.Terminal.desktop /home/vagrant/.config/autostart/' 18 | -------------------------------------------------------------------------------- /tests/vagrant/ubuntu.22.04.sway.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | /vagrant/tests/vagrant/ubudep.sh 3 | 4 | export DEBIAN_FRONTEND=noninteractive 5 | APT="apt-get -o Dpkg::Options::=--force-confold -o Dpkg::Options::=--force-confdef -y --allow-downgrades --allow-remove-essential --allow-change-held-packages" 6 | $APT install grim sway foot 7 | 8 | # for pygame 9 | $APT install libsdl2-dev libfreetype-dev python3-pip 10 | # wheel does not work 11 | sudo pip3 install https://files.pythonhosted.org/packages/06/bf/008bcc00ebe22fcbea016f80566216c0b8e2cfea2bfdbf72664f676069b5/pygame-2.2.0.tar.gz 12 | 13 | # autologin 14 | mkdir -p /etc/systemd/system/getty@tty1.service.d 15 | echo ' 16 | [Service] 17 | ExecStart= 18 | ExecStart=-/sbin/agetty --noissue --autologin vagrant %I $TERM 19 | Type=idle 20 | ' > /etc/systemd/system/getty@tty1.service.d/override.conf 21 | 22 | # sway autostart 23 | echo ' 24 | if [[ -z $DISPLAY ]] && [[ $(tty) = /dev/tty1 ]]; then 25 | exec sway 26 | fi 27 | ' > /home/vagrant/.bash_profile 28 | 29 | # autostart terminal 30 | echo ' 31 | exec foot 32 | ' >>/etc/sway/config 33 | -------------------------------------------------------------------------------- /tests/vagrant/ubuntu.22.10.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | /vagrant/tests/vagrant/ubudep.sh 3 | 4 | export DEBIAN_FRONTEND=noninteractive 5 | APT="apt-get -o Dpkg::Options::=--force-confold -o Dpkg::Options::=--force-confdef -y --allow-downgrades --allow-remove-essential --allow-change-held-packages" 6 | 7 | $APT install ubuntu-desktop-minimal 8 | 9 | # autologin 10 | echo ' 11 | [daemon] 12 | AutomaticLoginEnable = true 13 | AutomaticLogin = vagrant 14 | ' >/etc/gdm3/custom.conf 15 | 16 | # autostart gnome terminal 17 | sudo -H -u vagrant bash -c 'mkdir -p /home/vagrant/.config/autostart/ && cp /usr/share/applications/org.gnome.Terminal.desktop /home/vagrant/.config/autostart/' 18 | -------------------------------------------------------------------------------- /tests/vagrant/ubuntu.server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Ubuntu 14.04.6 LTS (Trusty Tahr) 4 | # Ubuntu 16.04.6 LTS (Xenial Xerus) 5 | # Ubuntu 18.04.3 LTS (Bionic Beaver) 6 | # Ubuntu 20.04 LTS (Focal Fossa) 7 | 8 | # no python3-wx before bionic 9 | # no pyqt5 before xenial 10 | # pygdk3 plugin is not compatible with trusty 11 | # no python3-pyside before xenial 12 | # no python3-pyside2 before disco (19.04) 13 | 14 | 15 | export DEBIAN_FRONTEND=noninteractive 16 | APT="apt-get -o Dpkg::Options::=--force-confold -o Dpkg::Options::=--force-confdef -y --allow-downgrades --allow-remove-essential --allow-change-held-packages" 17 | 18 | sudo update-locale LANG=en_US.UTF-8 LANGUAGE=en.UTF-8 19 | # echo 'export export LC_ALL=C' >> /home/vagrant/.profile 20 | 21 | # install python versions 22 | sudo add-apt-repository --yes ppa:deadsnakes/ppa 23 | # sudo add-apt-repository --yes ppa:jan-simon/pqiv 24 | sudo apt-get update 25 | 26 | sudo apt-get install -y python3.9-dev 27 | sudo apt-get install -y python3.9-distutils 28 | 29 | sudo apt-get install -y python3.10-dev 30 | sudo apt-get install -y python3.10-distutils 31 | 32 | sudo apt-get install -y python3.11-dev 33 | sudo apt-get install -y python3.11-distutils 34 | 35 | # tools 36 | sudo apt-get install -y mc xvfb 37 | sudo apt-get install -y python3-pip 38 | sudo pip3 install -U pip 39 | 40 | # for pillow source install 41 | # sudo apt-get install -y libjpeg-dev zlib1g-dev 42 | 43 | # project dependencies 44 | sudo apt-get install -y grim 45 | sudo apt-get install -y maim 46 | sudo apt-get install -y scrot 47 | sudo apt-get install -y imagemagick 48 | sudo apt-get install -y gnome-screenshot 49 | 50 | sudo apt-get install -y libcanberra-gtk-module 51 | 52 | sudo apt-get install -y python3-gi 53 | sudo apt-get install -y gir1.2-gtk-3.0 54 | sudo apt-get install -y libcanberra-gtk3-module 55 | 56 | sudo apt-get install -y python3-wxgtk4.0 57 | 58 | # sudo apt-get install -y python3-pyqt4 59 | sudo apt-get install -y python3-pyqt5 60 | # sudo apt-get install -y python3-pyside 61 | 62 | #sudo apt-get install -y python3-pyside2 # no python3-pyside2 before disco (19.04) 63 | sudo pip3 install pyside2 --no-cache-dir 64 | 65 | # test dependencies 66 | sudo apt-get install -y x11-utils 67 | sudo pip3 install -U tox 68 | 69 | # doc dependencies 70 | sudo apt-get install -y graphviz 71 | -------------------------------------------------------------------------------- /tests/vagrant/vagrant_boxes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | import os.path 4 | import threading 5 | import time 6 | from pathlib import Path 7 | from tempfile import TemporaryDirectory 8 | from time import sleep 9 | 10 | import fabric 11 | import imagehash 12 | import vagrant 13 | from easyprocess import EasyProcess 14 | from entrypoint2 import entrypoint 15 | from PIL import Image 16 | 17 | KBD_RIGHT = "e0 4d e0 cd".split() 18 | KBD_SPACE = "39 b9".split() 19 | 20 | 21 | def vbox_send_kbd(box, kbd): 22 | cmd = ["VBoxManage", "controlvm", box, "keyboardputscancode"] + kbd 23 | EasyProcess(cmd).call() 24 | 25 | 26 | def close_confirm_wnd(box): 27 | vbox_send_kbd(box, KBD_RIGHT + KBD_RIGHT + KBD_SPACE) 28 | 29 | 30 | def vbox_screenshot(box, bbox=None): 31 | with TemporaryDirectory(prefix="vbox_screenshot") as tmpdirname: 32 | filename = os.path.join(tmpdirname, "screenshot.png") 33 | 34 | cmd = ["VBoxManage", "controlvm", box, "screenshotpng", filename] 35 | EasyProcess(cmd).call() 36 | 37 | im = Image.open(filename) 38 | if bbox: 39 | im = im.crop(bbox) # (left, upper, right, lower) 40 | return im 41 | 42 | 43 | # from fabric.api import env, execute, task, run, sudo, settings 44 | 45 | 46 | # pip3 install fabric vncdotool python-vagrant entrypoint2 47 | 48 | DIR = Path(__file__).parent 49 | 50 | 51 | class Options: 52 | halt = True 53 | recreate = True 54 | destroy = False 55 | fast = False 56 | create = False 57 | 58 | 59 | def wrapcmd(cmd, guiproc): 60 | if guiproc: 61 | # copy env vars from graphical session 62 | cmd2 = f"pid=`pidof {guiproc}`" 63 | cmd2 += ";pid=$( cut -d ' ' -f 1 <<< $pid )" 64 | cmd2 += ";sudo cat /proc/$pid/environ | tr '\\0' '\\n' > /tmp/environ" 65 | cmd2 += ";export $(cat /tmp/environ)" 66 | cmd = cmd2 + f"; {cmd}" 67 | print(f"command: {cmd}") 68 | return cmd 69 | 70 | 71 | def run_box(name, options, vagrantfile, cmds, guiproc): 72 | env = os.environ 73 | env["VAGRANT_VAGRANTFILE"] = str(DIR / vagrantfile) 74 | env["VAGRANT_DOTFILE_PATH"] = str(DIR / (".vagrant_" + vagrantfile)) 75 | 76 | v = vagrant.Vagrant(env=env, quiet_stdout=False, quiet_stderr=False) 77 | status = v.status() 78 | state = status[0].state 79 | print(status) 80 | 81 | if options.destroy: 82 | v.destroy() 83 | return 84 | 85 | if options.halt: 86 | v.halt() # avoid screensaver 87 | 88 | if state == "not_created": 89 | # install programs in box 90 | v.up() 91 | 92 | # restart box 93 | v.halt() 94 | 95 | if not options.fast: 96 | # go over first boot messages, tasks 97 | v.up() 98 | sleep(3 * 60) 99 | v.halt() 100 | 101 | if options.create: 102 | return 103 | 104 | event = threading.Event() 105 | 106 | auto_confirm_thread = None 107 | if name == "ubuntu.22.04": 108 | 109 | box = "pyscreenshot_" + name 110 | 111 | def auto_confirm(): 112 | confirm_wnd_hash = "0000207e3e3e0000" 113 | bbox = (610, 120, 670, 150) 114 | while True: 115 | if event.is_set(): 116 | break 117 | img = vbox_screenshot(box, bbox) 118 | h = imagehash.average_hash(img) 119 | # print(h) 120 | if str(h) == confirm_wnd_hash: 121 | # print("close_confirm_wnd") 122 | close_confirm_wnd(box) 123 | time.sleep(1) 124 | 125 | auto_confirm_thread = threading.Thread(target=auto_confirm) 126 | 127 | try: 128 | v.up() 129 | 130 | with fabric.Connection( 131 | v.user_hostname_port(), 132 | connect_kwargs={ 133 | "key_filename": v.keyfile(), 134 | }, 135 | ) as conn: 136 | with conn.cd("c:/vagrant" if options.win else "/vagrant"): 137 | if not options.win: 138 | if options.osx: 139 | freecmd = "top -l 1 -s 0 | grep PhysMem" 140 | else: # linux 141 | freecmd = "free -h" 142 | cmds = ( 143 | [ 144 | freecmd, 145 | "env | sort", 146 | "gnome-shell --version || true", 147 | "plasmashell --version || true", 148 | ] 149 | + cmds 150 | + [freecmd] 151 | ) 152 | 153 | if guiproc: 154 | pid = None 155 | while not pid: 156 | print(f"waiting for {guiproc}") 157 | cmd = f"bash --login -c 'pidof {guiproc} || true'" 158 | print(cmd) 159 | pid = conn.run(cmd).stdout 160 | sleep(1) 161 | print(f"{guiproc} pid={pid}") 162 | sleep(5) 163 | 164 | if auto_confirm_thread: 165 | auto_confirm_thread.start() 166 | 167 | print(f"collected commands: {cmds}") 168 | for cmd in cmds: 169 | if options.recreate: 170 | if "tox" in cmd: 171 | # cmd += " -r" 172 | cmd = cmd.replace("tox", "tox -r") 173 | conn.run(wrapcmd(cmd, guiproc), echo=True, pty=True) 174 | finally: 175 | event.set() 176 | if auto_confirm_thread: 177 | auto_confirm_thread.join() 178 | if options.halt: 179 | v.halt() 180 | 181 | 182 | config = { 183 | "ubuntu.server.22.04": ( 184 | ["tox"], 185 | "", 186 | ), 187 | "lubuntu.20.04": ( 188 | ["tox -e py3-desktop"], 189 | "qterminal", 190 | ), 191 | "lubuntu.22.04": ( 192 | ["tox -e py3-desktop"], 193 | "qterminal", 194 | ), 195 | "lubuntu.22.10": ( 196 | ["tox -e py3-desktop"], 197 | "qterminal", 198 | ), 199 | "xubuntu.20.04": ( 200 | ["tox -e py3-desktop"], 201 | "xfdesktop", 202 | ), 203 | "xubuntu.22.04": ( 204 | ["tox -e py3-desktop"], 205 | "xfdesktop", 206 | ), 207 | "xubuntu.22.10": ( 208 | ["tox -e py3-desktop"], 209 | "xfdesktop", 210 | ), 211 | "kubuntu.20.04": ( 212 | ["tox -e py3-desktop"], 213 | "konsole", 214 | ), 215 | "kubuntu.22.04": ( 216 | ["tox -e py3-desktop-freedesktop"], 217 | "konsole", 218 | ), 219 | "kubuntu.22.10": ( 220 | ["tox -e py3-desktop-freedesktop"], 221 | "konsole", 222 | ), 223 | "ubuntu.20.04": ( 224 | ["tox -e py3-desktop"], 225 | "gnome-terminal-server", 226 | ), 227 | "ubuntu.22.04": ( 228 | ["tox -e py3-desktop-freedesktop"], 229 | "gnome-terminal-server", 230 | ), 231 | "ubuntu.22.10": ( 232 | ["tox -e py3-desktop-freedesktop"], 233 | "gnome-terminal-server", 234 | ), 235 | "ubuntu.22.04.sway": ( 236 | ["tox -e py3-desktop"], 237 | "foot", 238 | ), 239 | # "debian11.gnome.wayland": ( 240 | # ["tox -e py3-desktop"], 241 | # "gnome-terminal-server", 242 | # ), 243 | # "debian11.gnome.x11": ( 244 | # ["tox -e py3-desktop"], 245 | # "gnome-terminal-server", 246 | # ), 247 | # "debian11.kde.wayland": ( 248 | # ["tox -e py3-desktop"], 249 | # "konsole", 250 | # ), 251 | # "debian11.kde.x11": ( 252 | # ["tox -e py3-desktop"], 253 | # "konsole", 254 | # ), 255 | # "debian10.gnome.wayland": ( 256 | # # ["bash -c 'tox -e py3-desktop'"], 257 | # ["tox -e py3-desktop"], 258 | # "gnome-terminal-server", 259 | # ), 260 | # "debian10.gnome.x11": ( 261 | # # ["bash -c 'tox -e py3-desktop'"], 262 | # ["tox -e py3-desktop"], 263 | # "gnome-terminal-server", 264 | # ), 265 | # "debian10.kde.wayland": ( 266 | # ["tox -e py3-desktop"], 267 | # "konsole", 268 | # ), 269 | # "debian10.kde.x11": ( 270 | # ["tox -e py3-desktop"], 271 | # "konsole", 272 | # ), 273 | # "osx.10.14": ( 274 | # ["bash --login -c 'python3 -m tox -e py3-osx'"], 275 | # "Dock", 276 | # ), 277 | # "osx.10.15": ( 278 | # ["bash --login -c 'python3 -m tox -e py3-osx'"], 279 | # "Dock", 280 | # ), 281 | # "win": ("Vagrantfile.win.rb", ["tox -e py3-win"], "",), 282 | } 283 | 284 | 285 | @entrypoint 286 | def main(boxes="all", fast=False, destroy=False, create=False): 287 | options = Options() 288 | options.halt = not fast 289 | options.recreate = not fast 290 | options.destroy = destroy 291 | options.fast = fast 292 | options.create = create 293 | 294 | if boxes == "all": 295 | boxes = list(config.keys()) 296 | else: 297 | boxes = boxes.split(",") 298 | 299 | for k, v in config.items(): 300 | name = k 301 | vagrantfile = "Vagrantfile." + name + ".rb" 302 | cmds, guiproc = v[0], v[1] 303 | if name in boxes: 304 | options.win = k.startswith("win") 305 | options.osx = k.startswith("osx") 306 | print("----->") 307 | print("----->") 308 | print("-----> %s %s %s" % (name, vagrantfile, cmds)) 309 | print("----->") 310 | print("----->") 311 | try: 312 | run_box(name, options, vagrantfile, cmds, guiproc) 313 | finally: 314 | print("<-----") 315 | print("<-----") 316 | print("<----- %s %s %s" % (name, vagrantfile, cmds)) 317 | print("<-----") 318 | print("<-----") 319 | -------------------------------------------------------------------------------- /tests/vagrant/win.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # https://chocolatey.org/blog/remove-support-for-old-tls-versions 4 | PowerShell -Command "[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12" 5 | # https://chocolatey.org/courses/installation/installing?method=install-from-powershell-v3 6 | PowerShell -Command "Set-ExecutionPolicy Bypass -Scope Process -Force; iwr https://chocolatey.org/install.ps1 -UseBasicParsing | iex" 7 | 8 | choco install python -y 9 | # choco install fsviewer -y 10 | choco install imagemagick -y 11 | choco install wxpython -y 12 | choco install gtk-runtime -y 13 | choco install pyqt4 -y 14 | choco install pyqt5 -y 15 | 16 | python -m pip install -U pip 17 | python -m pip install tox 18 | 19 | # cd /cygdrive/c/vagrant 20 | # tox -e py38-win -------------------------------------------------------------------------------- /tests/vagrant/xubuntu.20.04.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | /vagrant/tests/vagrant/ubudep.sh 3 | 4 | export DEBIAN_FRONTEND=noninteractive 5 | APT="apt-get -o Dpkg::Options::=--force-confold -o Dpkg::Options::=--force-confdef -y --allow-downgrades --allow-remove-essential --allow-change-held-packages" 6 | 7 | $APT install xubuntu-desktop^ 8 | 9 | /vagrant/tests/vagrant/lightdm.sh 10 | -------------------------------------------------------------------------------- /tests/vagrant/xubuntu.22.04.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | /vagrant/tests/vagrant/ubudep.sh 3 | 4 | export DEBIAN_FRONTEND=noninteractive 5 | APT="apt-get -o Dpkg::Options::=--force-confold -o Dpkg::Options::=--force-confdef -y --allow-downgrades --allow-remove-essential --allow-change-held-packages" 6 | 7 | $APT install xubuntu-desktop^ 8 | 9 | /vagrant/tests/vagrant/lightdm.sh 10 | -------------------------------------------------------------------------------- /tests/vagrant/xubuntu.22.10.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | /vagrant/tests/vagrant/ubudep.sh 3 | 4 | export DEBIAN_FRONTEND=noninteractive 5 | APT="apt-get -o Dpkg::Options::=--force-confold -o Dpkg::Options::=--force-confdef -y --allow-downgrades --allow-remove-essential --allow-change-held-packages" 6 | 7 | $APT install xubuntu-desktop^ 8 | 9 | /vagrant/tests/vagrant/lightdm.sh 10 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | 2 | [tox] 3 | envlist = 4 | py311 5 | py310 6 | py39 7 | #py3-doc 8 | #py3-lint 9 | 10 | # Workaround for Vagrant 11 | #toxworkdir={toxinidir}/.tox # default 12 | toxworkdir={env:HOME}/.tox/pyscreenshot 13 | 14 | [testenv] 15 | whitelist_externals=xvfb-run 16 | allowlist_externals=xvfb-run 17 | changedir=tests 18 | deps = -rrequirements-test.txt 19 | 20 | # TODO: environment with missing backends 21 | # TODO: check color depth Xvfb 8,15,16,24,30 22 | passenv = 23 | DBUS_SESSION_BUS_ADDRESS 24 | DISPLAY 25 | XDG_SESSION_TYPE 26 | XAUTHORITY 27 | XDG_CURRENT_DESKTOP 28 | XDG_RUNTIME_DIR 29 | WAYLAND_DISPLAY 30 | 31 | commands= 32 | {envpython} -m pyscreenshot.check.versions 33 | {envpython} -m pyscreenshot.check.speedtest --virtual-display 34 | {envpython} -m pyscreenshot.check.speedtest --virtual-display --childprocess 0 35 | xvfb-run -n 101 -s "+extension RANDR -br -screen 0 900x800x24" {envpython} -m pytest -v . 36 | xvfb-run -n 102 -s "-extension RANDR -br -screen 0 900x800x24" {envpython} -m pytest -v . 37 | 38 | [testenv:py3-desktop] 39 | sitepackages=true 40 | commands= 41 | {envpython} -m pyscreenshot.check.versions 42 | {envpython} -m pyscreenshot.check.speedtest 43 | {envpython} -m pyscreenshot.check.speedtest --childprocess 0 44 | {envpython} -m pytest -v . 45 | 46 | [testenv:py3-desktop-freedesktop] 47 | sitepackages=true 48 | setenv = PYSCREENSHOT_TEST_FREEDESKTOP_DBUS = 1 49 | commands= 50 | {envpython} -m pyscreenshot.check.versions 51 | {envpython} -m pyscreenshot.check.speedtest 52 | {envpython} -m pyscreenshot.check.speedtest --childprocess 0 53 | {envpython} -m pytest -v . 54 | 55 | [testenv:py3-osx] 56 | changedir=tests 57 | deps = -rrequirements-test.txt 58 | 59 | commands= 60 | {envpython} -m pyscreenshot.check.versions 61 | {envpython} -m pyscreenshot.check.speedtest 62 | {envpython} -m pyscreenshot.check.speedtest --childprocess 0 63 | {envpython} -m pytest -v . 64 | 65 | [testenv:py3-win] 66 | changedir=tests 67 | deps= 68 | pytest 69 | pillow 70 | pypiwin32 71 | 72 | commands= 73 | {envpython} -m pyscreenshot.check.versions 74 | {envpython} -m pyscreenshot.check.speedtest 75 | {envpython} -m pyscreenshot.check.speedtest --childprocess 0 76 | {envpython} -m pytest -v . 77 | 78 | 79 | [testenv:py3-doc] 80 | sitepackages=true 81 | allowlist_externals=bash 82 | changedir=doc 83 | deps = 84 | -rrequirements-doc.txt 85 | 86 | commands= 87 | bash -c "cd ..;./format-code.sh" 88 | {envpython} generate-doc.py --debug 89 | 90 | [testenv:py3-lint] 91 | allowlist_externals=bash 92 | changedir=. 93 | deps = -rrequirements-test.txt 94 | 95 | commands= 96 | bash -c "./lint.sh" 97 | --------------------------------------------------------------------------------