With python 3, I'd like to get a handle to another window (not part of my application) such that I can either:
directly capture that window as a screenshot, or
determine its position and size and capture it some other way
In case it is important, I am using Windows XP (edit: works in Windows 7 also).
I found this solution, but it is not quite what I need since it is full screen and more importantly, PIL to the best of my knowledge does not support 3.x yet.
Here's how you can do it using PIL on win32. Given a window handle (hwnd), you should only need the last 4 lines of code. The preceding simply search for a window with "firefox" in the title. Since PIL's source is available, you should be able to poke around the ImageGrab.grab(bbox) method and figure out the win32 code you need to make this happen.
from PIL import ImageGrab
import win32gui
toplist, winlist = [], []
def enum_cb(hwnd, results):
winlist.append((hwnd, win32gui.GetWindowText(hwnd)))
win32gui.EnumWindows(enum_cb, toplist)
firefox = [(hwnd, title) for hwnd, title in winlist if 'firefox' in title.lower()]
# just grab the hwnd for first window matching firefox
firefox = firefox[0]
hwnd = firefox[0]
win32gui.SetForegroundWindow(hwnd)
bbox = win32gui.GetWindowRect(hwnd)
img = ImageGrab.grab(bbox)
img.show()
Ars gave me all the pieces. I am just putting the pieces together here for anyone else who needs to get a screenshot in python 3.x. Next I need to figure out how to work with a win32 bitmap without having PIL to lean on.
Get a Screenshot (pass hwnd for a window instead of full screen):
def screenshot(hwnd = None):
import win32gui
import win32ui
import win32con
from time import sleep
if not hwnd:
hwnd=win32gui.GetDesktopWindow()
l,t,r,b=win32gui.GetWindowRect(hwnd)
h=b-t
w=r-l
hDC = win32gui.GetWindowDC(hwnd)
myDC=win32ui.CreateDCFromHandle(hDC)
newDC=myDC.CreateCompatibleDC()
myBitMap = win32ui.CreateBitmap()
myBitMap.CreateCompatibleBitmap(myDC, w, h)
newDC.SelectObject(myBitMap)
win32gui.SetForegroundWindow(hwnd)
sleep(.2) #lame way to allow screen to draw before taking shot
newDC.BitBlt((0,0),(w, h) , myDC, (0,0), win32con.SRCCOPY)
myBitMap.Paint(newDC)
myBitMap.SaveBitmapFile(newDC,'c:\\tmp.bmp')
Get a Window Handle by title (to pass to the above function):
def _get_windows_bytitle(title_text, exact = False):
def _window_callback(hwnd, all_windows):
all_windows.append((hwnd, win32gui.GetWindowText(hwnd)))
windows = []
win32gui.EnumWindows(_window_callback, windows)
if exact:
return [hwnd for hwnd, title in windows if title_text == title]
else:
return [hwnd for hwnd, title in windows if title_text in title]
This will take a new opened window and make a screenshot of it and then crop it with PIL also possible to find your specific window with pygetwindow.getAllTitles() and then fill in your window name in z3 to get screenshot of only that window.
If you definitely not want to use PIL you can maximize window with pygetwindow module and then make a screenshot with pyautogui module.
Note: not tested on Windows XP (but tested on Windows 10)
import pygetwindow
import time
import os
import pyautogui
import PIL
# get screensize
x,y = pyautogui.size()
print(f"width={x}\theight={y}")
x2,y2 = pyautogui.size()
x2,y2=int(str(x2)),int(str(y2))
print(x2//2)
print(y2//2)
# find new window title
z1 = pygetwindow.getAllTitles()
time.sleep(1)
print(len(z1))
# test with pictures folder
os.startfile("C:\\Users\\yourname\\Pictures")
time.sleep(1)
z2 = pygetwindow.getAllTitles()
print(len(z2))
time.sleep(1)
z3 = [x for x in z2 if x not in z1]
z3 = ''.join(z3)
time.sleep(3)
# also able to edit z3 to specified window-title string like: "Sublime Text (UNREGISTERED)"
my = pygetwindow.getWindowsWithTitle(z3)[0]
# quarter of screen screensize
x3 = x2 // 2
y3 = y2 // 2
my.resizeTo(x3,y3)
# top-left
my.moveTo(0, 0)
time.sleep(3)
my.activate()
time.sleep(1)
# save screenshot
p = pyautogui.screenshot()
p.save(r'C:\\Users\\yourname\\Pictures\\\\p.png')
# edit screenshot
im = PIL.Image.open('C:\\Users\\yourname\\Pictures\\p.png')
im_crop = im.crop((0, 0, x3, y3))
im_crop.save('C:\\Users\\yourname\\Pictures\\p.jpg', quality=100)
# close window
time.sleep(1)
my.close()
The solution here gets a screenshot of a single Window (so can work if the Window is in the background).
Other solutions of this page take picture of the part of the screen the window is on, and thus need to bring the Window to the front first.
Python Screenshot of inactive window PrintWindow + win32gui
Related
I have a question, its more an OS-based one.
I'm playing a video game and I want to be able to put a textual timer ontop of the game's screen as if it was a part of the game itself.
Now, I can write a program in any language that displays a TextBox with a timer on the screen, but if I run it, the game's process (lets call it game.exe) "loses" its focus and I get my TextBox focused and interactive by the OS.
Is there any option to display that text "ontop" of the game.exe that comes from an entire different process? as if there were "layers" to the screen. Also, this text shouldn't be intractable, clickable or make the game.exe process lose its focus.
Here's a very simple example I drew:
Thanks a lot!
Solved this using a window trick with python and tkinter with some windows api stuff.
The trick is to create a transparent non-clickable window and keep it always on top.
I've basically combined this answer with a bunch of simpler stuff like removing window's border and set to auto fullscreen.
from tkinter import *
import time
import win32gui
import win32api
from win32api import GetSystemMetrics
# WIDTH = 500
# HEIGHT = 500
WIDTH = GetSystemMetrics(0)
HEIGHT = GetSystemMetrics(1)
LINEWIDTH = 1
TRANSCOLOUR = 'gray'
title = 'Virtual whiteboard'
global old
old = ()
global HWND_t
HWND_t = 0
tk = Tk()
# tk.title(title)
tk.lift()
tk.wm_attributes("-topmost", True)
tk.wm_attributes("-transparentcolor", TRANSCOLOUR)
tk.attributes('-fullscreen', True)
state_left = win32api.GetKeyState(0x01) # Left button down = 0 or 1. Button up = -127 or -128
canvas = Canvas(tk, width=WIDTH, height=HEIGHT, highlightthickness=0)
canvas.pack()
canvas.config(cursor='tcross')
canvas.create_rectangle(0, 0, WIDTH, HEIGHT, fill=TRANSCOLOUR, outline=TRANSCOLOUR)
canvas.create_text(WIDTH/2,HEIGHT/2,fill="white",font="Arial 20", text="TEXT GOES HERE")
def putOnTop(event):
event.widget.unbind('<Visibility>')
event.widget.update()
event.widget.lift()
event.widget.bind('<Visibility>', putOnTop)
def drawline(data):
global old
if old !=():
canvas.create_line(old[0], old[1], data[0], data[1], width=LINEWIDTH)
old = (data[0], data[1])
def enumHandler(hwnd, lParam):
global HWND_t
if win32gui.IsWindowVisible(hwnd):
if title in win32gui.GetWindowText(hwnd):
HWND_t = hwnd
win32gui.EnumWindows(enumHandler, None)
tk.bind('<Visibility>', putOnTop)
tk.focus()
running = 1
while running == 1:
try:
tk.update()
time.sleep(0.01)
if HWND_t != 0:
windowborder = win32gui.GetWindowRect(HWND_t)
cur_pos = win32api.GetCursorPos()
state_left_new = win32api.GetKeyState(0x01)
if state_left_new != state_left:
if windowborder[0] < cur_pos[0] and windowborder[2] > cur_pos[0] and windowborder[1] < cur_pos[1] and windowborder[3] > cur_pos[1]:
drawline((cur_pos[0] - windowborder[0] - 5, cur_pos[1] - windowborder[1] - 30))
else:
old = ()
except Exception as e:
running = 0
print("error %r" % (e))
I'm using the Python cmd module to create a CLI application. Everything works great! However, I'm attempting to tailor the app to a certain type of presence: text colors, title, using alpha-numeric characters as borders, etc.
Is there a standard way to create a screen overrun of sorts: the top of the screen where I have set a border and color title remain static? And from the middle of the screen, or thereabouts, down to the bottom of the screen, any text or commands entered at the prompt will stop being visible as they reach the title/border. Basically, what I'm after is for a user to always see the title/border unless they exit the CLI app. If they type help, of course, they will see the commands below the title/border. But, as they enter commands, ideally, the command menu will disappear behind the screen title/border.
Any direction on the best way I can achieve this is appreciated.
Check curses
You should be able to decorate CLI/Terminal with colors and static borders.
I have extended example taken from HERE:
import curses
from multiprocessing import Process
p = None
def display(stdscr):
stdscr.clear()
stdscr.timeout(500)
maxy, maxx = stdscr.getmaxyx()
curses.newwin(2,maxx,3,1)
# invisible cursor
curses.curs_set(0)
if (curses.has_colors()):
# Start colors in curses
curses.start_color()
curses.use_default_colors()
curses.init_pair(1, curses.COLOR_RED, -1)
stdscr.refresh()
curses.init_pair(1, 0, -1)
curses.init_pair(2, 1, -1)
curses.init_pair(3, 2, -1)
curses.init_pair(4, 3, -1)
bottomBox = curses.newwin(8,maxx-2,maxy-8,1)
bottomBox.box()
bottomBox.addstr("BottomBox")
bottomBox.refresh()
bottomwindow = curses.newwin(6,maxx-4,maxy-7,2)
bottomwindow.addstr("This is my bottom view", curses.A_UNDERLINE)
bottomwindow.refresh()
stdscr.addstr("{:20s}".format("Hello world !"), curses.color_pair(4))
stdscr.refresh()
while True:
event = stdscr.getch()
if event == ord("q"):
break
def hang():
while True:
temp = 1 + 1
if __name__ == '__main__':
p = Process(target = hang)
curses.wrapper(display)
I run python 2.7.13 on windows 7.
I am creating a window with Gtk (from pygobject 3.18.2).
I am running windows 7 with a custom shell and I am trying to make a toolbar at the bottom of the screen.
I use a grid to divide the window in a top and a bottom part.
The bottom part is always visible.
The top part must show above the bottom part on mouse enter and hide on mouse leave without moving the bottom part.
The default positioning of a window uses the top-left corner of the window, but this will cause the bottom part to shift up to the position of the top part when the top part is hidden.
I think I understand that I have to use
set_gravity(Gdk.Gravity.SOUTH_WEST)
to change this behaviour
I do not get errors, but it seems this setting is ignored. The placement of the window is not affected at all.
What am I missing?
Anything wrong in the way I call set_gravity()?
Is set_gravity the right way to achieve this?
I read Set window gravity in PyGObject?, but this question is still not answered
Here is the code I try to get working
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk
class MyWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Test")
self.set_decorated(0)
self.screen = Gdk.Screen.get_default()
self.connect("destroy", self.destroy)
self.connect("enter-notify-event", self.mouseenter)
self.connect("leave-notify-event", self.mouseleave)
self.label1 = Gtk.Label("Label1\n line1\n line2")
self.label2 = Gtk.Label("Label2")
self.label1.set_hexpand(True)
self.label2.set_hexpand(True)
self.maingrid = Gtk.Grid()
self.add(self.maingrid)
self.maingrid.attach(self.label1, 0, 0, 1, 1)
self.maingrid.attach(self.label2, 0, 1, 1, 1)
self.set_gravity(Gdk.Gravity.SOUTH_WEST) # looks like this is ignored
print self.get_gravity()
def mouseleave(self, widget, data=None):
print "mouse leave"
self.label1.hide()
label2_height = self.label2.get_allocation().height
self.resize(self.screen.width(), label2_height)
def mouseenter(self, widget, data=None):
print "mouse enter"
label1_height = self.label1.get_allocation().height
label2_height = self.label2.get_allocation().height
self.resize(self.screen.width(), label1_height + label2_height)
self.label1.show()
# Here I expect label2 to stay where it is at the bottom of the screen and label1 to be drawn above label2.
# But label2 is pushed down to make space for label1
# (normal behaviour if Gdk.Gravity.SOUTH_WEST is not set)
def destroy(self, widget, data=None):
print "destroy signal occurred"
Gtk.main_quit()
win = MyWindow()
win.show_all()
win.label1.hide()
height = win.label2.get_allocation().height
win.resize(win.screen.width(), height)
#win.move(0, win.screen.height()) # I expect this to place the window at the bottom of the screen
# if Gdk.Gravity.SOUTH_WEST is set, but it is placed offscreen
# (normal behaviour if Gdk.Gravity.SOUTH_WEST is not set)
win.move(0, win.screen.height() - 200) # shift it up 200 pixels to see what is happening
Gtk.main()
Here is a working version where I move the window to it's proper position after resizing. Moving the window makes the window flicker and it also generates the leave-notify-event and the enter-notify-event.
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk
class MyWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Test")
self.set_decorated(0)
self.screen = Gdk.Screen.get_default()
# self.set_gravity(Gdk.Gravity.SOUTH_WEST)
self.connect("destroy", self.destroy)
self.connect("enter-notify-event", self.mouseenter)
self.connect("leave-notify-event", self.mouseleave)
self.label1 = Gtk.Label("Label1\n line1\n line2")
self.label2 = Gtk.Label("Label2")
self.label1.set_hexpand(True)
self.label2.set_hexpand(True)
self.maingrid = Gtk.Grid()
self.add(self.maingrid)
self.maingrid.attach(self.label1, 0, 0, 1, 1)
self.maingrid.attach(self.label2, 0, 1, 1, 1)
self.ismoving = 0
def mouseleave(self, widget, data=None):
print "mouse leave"
if self.ismoving:
print "window is moving"
else:
self.label1.hide()
label2_height = self.label2.get_allocation().height
self.resize(self.screen.width(), label2_height)
self.move(0, self.screen.height() - label2_height)
def mouseenter(self, widget, data=None):
print "mouse enter"
if self.ismoving: # moving the window generates a leave-notify-event and a enter-notify-event
self.ismoving = 0 # ignore these events when moving the window
else:
self.ismoving = 1
label1_height = self.label1.get_allocation().height
label2_height = self.label2.get_allocation().height
self.resize(self.screen.width(), label1_height + label2_height)
self.move(0, self.screen.height()-label1_height - label2_height)
self.label1.show()
def destroy(self, widget, data=None):
print "destroy signal occurred"
Gtk.main_quit()
win = MyWindow()
win.show_all()
win.label1.hide()
height = win.label2.get_allocation().height
win.resize(win.screen.width(), height)
win.move(0, win.screen.height() - height)
Gtk.main()
Based on AlexB's comment i assume my code is correct, but it is not working for me. I don't see any reason why it will not run under python 2. Maybe there is an issue with the window manager. I'll investigate
Did anyone succesfully use set_gravity() on windows?
Documentation indicates it may or may not work, depending on Window Manager. It doesn't for me on Xubuntu 18.04
My computer doesn't have any way of letting me know if my NumLk is on or off, so I am trying to add an icon in my systray that will changed depending on the state of my NumLk. This .py will always be running when my computer is on.
So far I was able to mix 3 codes and I am able to display the icon in the systray but it doesn't get updated when the state of NumLk change. Actually if I press NumLk twice, I still get the same icon (the on one) and I get this error:
QCoreApplication::exec: The event loop is already running
File "\systray_icon_NumLk_on_off.py", line 21, in on_key_press
main(on)
File "\systray_icon_NumLk_on_off.py", line 46, in main
sys.exit(app.exec_())
SystemExit: -1
My code may not be the best way to do it, so any alternative is welcome! Here is what I came up so far:
#####get the state of NumLk key
from win32api import GetKeyState
from win32con import VK_NUMLOCK
#how to use: print(GetKeyState(VK_NUMLOCK))
#source: http://stackoverflow.com/questions/21160100/python-3-x-getting-the-state-of-caps-lock-num-lock-scroll-lock-on-windows
#####Detect if NumLk is pressed
import pyglet
from pyglet.window import key
window = pyglet.window.Window()
#source: http://stackoverflow.com/questions/28324372/detecting-a-numlock-capslock-scrlock-keypress-keyup-in-python
on=r'on.png'
off=r'off.png'
#window.event
def on_key_press(symbol, modifiers):
if symbol == key.NUMLOCK:
if GetKeyState(VK_NUMLOCK):
#print(GetKeyState(VK_NUMLOCK))#should be 0 and 1 but
main(on)
else:
main(off)
#window.event
def on_draw():
window.clear()
### display icon in systray
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
#source: http://stackoverflow.com/questions/893984/pyqt-show-menu-in-a-system-tray-application - add answer PyQt5
class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
def __init__(self, icon, parent=None):
QtWidgets.QSystemTrayIcon.__init__(self, icon, parent)
menu = QtWidgets.QMenu(parent)
exitAction = menu.addAction("Exit")
self.setContextMenu(menu)
def main(image):
app = QtWidgets.QApplication(sys.argv)
w = QtWidgets.QWidget()
trayIcon = SystemTrayIcon(QtGui.QIcon(image), w)
trayIcon.show()
sys.exit(app.exec_())
if __name__ == '__main__':
pyglet.app.run()
The reason for QCoreApplication::exec: The event loop is already running is actually because you're trying to start app.run() twice. Qt will notice there's already an instance running and throw this exception. When instead, what you want to do is just swap the icon in the already running instance.
Your main problem here is actually the mix of libraries to solve one task if you ask me.
Rather two tasks, but using Qt5 for the graphical part is fine tho.
The way you use Pyglet is wrong from the get go.
Pyglet is intended to be a highly powerful and effective graphics library where you build a graphics engine ontop of it. For instance if you're making a game or a video-player or something.
The way you use win32api is also wrong because you're using it in a graphical window that only checks the value when a key is pressed inside that window.
Now, if you move your win32api code into a Thread (a QtThread to be precise) you can check the state no matter if you pressed your key inside your graphical window or not.
import sys
import win32api
import win32con
from PyQt5 import QtCore, QtGui, QtWidgets
from threading import Thread, enumerate
from time import sleep
class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
def __init__(self, icon, parent=None):
QtWidgets.QSystemTrayIcon.__init__(self, icon, parent)
menu = QtWidgets.QMenu(parent)
exitAction = menu.addAction("Exit")
exitAction.setShortcut('Ctrl+Q')
exitAction.setStatusTip('Exit application')
exitAction.triggered.connect(QtWidgets.qApp.quit)
self.setContextMenu(menu)
class KeyCheck(QtCore.QThread):
def __init__(self, mainWindow):
QtCore.QThread.__init__(self)
self.mainWindow = mainWindow
def run(self):
main = None
for t in enumerate():
if t.name == 'MainThread':
main = t
break
while main and main.isAlive():
x = win32api.GetAsyncKeyState(win32con.VK_NUMLOCK)
## Now, GetAsyncKeyState returns three values,
## 0 == No change since last time
## -3000 / 1 == State changed
##
## Either you use the positive and negative values to figure out which state you're at.
## Or you just swap it, but if you just swap it you need to get the startup-state correct.
if x == 1:
self.mainWindow.swap()
elif x < 0:
self.mainWindow.swap()
sleep(0.25)
class GUI():
def __init__(self):
self.app = QtWidgets.QApplication(sys.argv)
self.state = True
w = QtWidgets.QWidget()
self.modes = {
True : SystemTrayIcon(QtGui.QIcon('on.png'), w),
False : SystemTrayIcon(QtGui.QIcon('off.png'), w)
}
self.refresh()
keyChecker = KeyCheck(self)
keyChecker.start()
sys.exit(self.app.exec_())
def swap(self, state=None):
if state is not None:
self.state = state
else:
if self.state:
self.state = False
else:
self.state = True
self.refresh()
def refresh(self):
for mode in self.modes:
if self.state == mode:
self.modes[mode].show()
else:
self.modes[mode].hide()
GUI()
Note that I don't do Qt programming often (every 4 years or so).
So this code is buggy at it's best. You have to press Ctrl+C + Press "Exit" in your menu for this to stop.
I honestly don't want to put more time and effort in learning how to manage threads in Qt or how to exit the application properly, it's not my area of expertis. But this will give you a crude working example of how you can swap the icon in the lower corner instead of trying to re-instanciate the main() loop that you did.
I have an image sequence rendered out. which I want to payback in a simple QMainWindow or QDialog. This is what I have sofar. It loads the images into the qlabel, but I cant see the label being updated, its just show the last loaded image, and nothing in between.
Maybe someone knows something?
from PySide import QtCore, QtGui
import shiboken
import maya.OpenMayaUI as apiUI
import time
def getMayaWindow():
"""
Get the main Maya window as a QtGui.QMainWindow instance
#return: QtGui.QMainWindow instance of the top level Maya windows
"""
ptr = apiUI.MQtUtil.mainWindow()
if ptr is not None:
return shiboken.wrapInstance(long(ptr), QtGui.QWidget)
class Viewer(QtGui.QMainWindow):
def __init__(self, parent = getMayaWindow()):
super(Viewer, self).__init__(parent)
self.setGeometry(400, 600, 400, 300)
self.setUi()
def setUi(self):
self.label = QtGui.QLabel()
self.setCentralWidget(self.label)
def showUi(self):
self.show()
def loadImage(self, path):
self.label.clear()
image = QtGui.QImage(path)
pp = QtGui.QPixmap.fromImage(image)
self.label.setPixmap(pp.scaled(
self.label.size(),
QtCore.Qt.KeepAspectRatio,
QtCore.Qt.SmoothTransformation))
x = Viewer()
x.showUi()
for i in range(1, 11):
x.loadImage("C://anim%03d.png" % i)
time.sleep(0.5)
You change pixmaps in loop and sleep (stop) all GUI thread, that's why your GUI freeze.
http://www.tutorialspoint.com/python/time_sleep.htm
It is not correct. qLabel.repaint() it is bad solution because it still blocks GUI. Of course you can use processEvents but it is bad approach too.
You should use QTimer for this purpose, use timeout() signal, create slot and change pixmaps in this slot. In this case your GUI will not be blocked because QTimer works asynchronously and images will be successfuly changed.
Same code with loop and sleep can help you only when this code will execute in another thread (multi threading) but it is not necessary because there is special class QTimer.