I need to write text directly to the screen without a window. The text needs to appear above all other windows and full-screen applications and should not be clickable or interactable in any way.
Example: The text doesn't need to have a transparent background as seen in the example. I can use either Python 2 or 3 on Windows 7.
I tried making a standalone label using Tkinter:
Edit: Improved with the help of Christian Rapp
import Tkinter
label = Tkinter.Label(text='Text on the screen', font=('Times','30'), fg='black', bg='white')
label.master.overrideredirect(True)
label.master.geometry("+250+250")
label.master.lift()
label.master.wm_attributes("-topmost", True)
label.master.wm_attributes("-disabled", True)
label.master.wm_attributes("-transparentcolor", "white")
label.pack()
label.mainloop()
What works:
What doesn't:
It turns out there are two entirely different problems here. To show text over windows, you'll need to create an undecorated topmost window and chroma key the background. However, this won't work when there's a full-screen application running (such as a game). The only reliable way to show text over a full-screen application is to use a Direct3D hook.
I haven't written up a Direct3D hook example, but I'll give two different solutions to the first problem.
In this example, I do the majority of the work with Tkinter and use win32api to prevent the text from blocking mouse clicks. If win32api isn't available to you, then you can just remove that part of the code.
import Tkinter, win32api, win32con, pywintypes
label = Tkinter.Label(text='Text on the screen', font=('Times New Roman','80'), fg='black', bg='white')
label.master.overrideredirect(True)
label.master.geometry("+250+250")
label.master.lift()
label.master.wm_attributes("-topmost", True)
label.master.wm_attributes("-disabled", True)
label.master.wm_attributes("-transparentcolor", "white")
hWindow = pywintypes.HANDLE(int(label.master.frame(), 16))
# http://msdn.microsoft.com/en-us/library/windows/desktop/ff700543(v=vs.85).aspx
# The WS_EX_TRANSPARENT flag makes events (like mouse clicks) fall through the window.
exStyle = win32con.WS_EX_COMPOSITED | win32con.WS_EX_LAYERED | win32con.WS_EX_NOACTIVATE | win32con.WS_EX_TOPMOST | win32con.WS_EX_TRANSPARENT
win32api.SetWindowLong(hWindow, win32con.GWL_EXSTYLE, exStyle)
label.pack()
label.mainloop()
This example does everything through pywin32. This makes it more complicated and less portable, but considerably more powerful. I've included links to the relevant parts of the Windows API throughout the code.
import win32api, win32con, win32gui, win32ui
def main():
hInstance = win32api.GetModuleHandle()
className = 'MyWindowClassName'
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms633576(v=vs.85).aspx
# win32gui does not support WNDCLASSEX.
wndClass = win32gui.WNDCLASS()
# http://msdn.microsoft.com/en-us/library/windows/desktop/ff729176(v=vs.85).aspx
wndClass.style = win32con.CS_HREDRAW | win32con.CS_VREDRAW
wndClass.lpfnWndProc = wndProc
wndClass.hInstance = hInstance
wndClass.hCursor = win32gui.LoadCursor(None, win32con.IDC_ARROW)
wndClass.hbrBackground = win32gui.GetStockObject(win32con.WHITE_BRUSH)
wndClass.lpszClassName = className
# win32gui does not support RegisterClassEx
wndClassAtom = win32gui.RegisterClass(wndClass)
# http://msdn.microsoft.com/en-us/library/windows/desktop/ff700543(v=vs.85).aspx
# Consider using: WS_EX_COMPOSITED, WS_EX_LAYERED, WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW, WS_EX_TOPMOST, WS_EX_TRANSPARENT
# The WS_EX_TRANSPARENT flag makes events (like mouse clicks) fall through the window.
exStyle = win32con.WS_EX_COMPOSITED | win32con.WS_EX_LAYERED | win32con.WS_EX_NOACTIVATE | win32con.WS_EX_TOPMOST | win32con.WS_EX_TRANSPARENT
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms632600(v=vs.85).aspx
# Consider using: WS_DISABLED, WS_POPUP, WS_VISIBLE
style = win32con.WS_DISABLED | win32con.WS_POPUP | win32con.WS_VISIBLE
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms632680(v=vs.85).aspx
hWindow = win32gui.CreateWindowEx(
exStyle,
wndClassAtom,
None, # WindowName
style,
0, # x
0, # y
win32api.GetSystemMetrics(win32con.SM_CXSCREEN), # width
win32api.GetSystemMetrics(win32con.SM_CYSCREEN), # height
None, # hWndParent
None, # hMenu
hInstance,
None # lpParam
)
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms633540(v=vs.85).aspx
win32gui.SetLayeredWindowAttributes(hWindow, 0x00ffffff, 255, win32con.LWA_COLORKEY | win32con.LWA_ALPHA)
# http://msdn.microsoft.com/en-us/library/windows/desktop/dd145167(v=vs.85).aspx
#win32gui.UpdateWindow(hWindow)
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms633545(v=vs.85).aspx
win32gui.SetWindowPos(hWindow, win32con.HWND_TOPMOST, 0, 0, 0, 0,
win32con.SWP_NOACTIVATE | win32con.SWP_NOMOVE | win32con.SWP_NOSIZE | win32con.SWP_SHOWWINDOW)
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms633548(v=vs.85).aspx
#win32gui.ShowWindow(hWindow, win32con.SW_SHOW)
win32gui.PumpMessages()
def wndProc(hWnd, message, wParam, lParam):
if message == win32con.WM_PAINT:
hdc, paintStruct = win32gui.BeginPaint(hWnd)
dpiScale = win32ui.GetDeviceCaps(hdc, win32con.LOGPIXELSX) / 60.0
fontSize = 80
# http://msdn.microsoft.com/en-us/library/windows/desktop/dd145037(v=vs.85).aspx
lf = win32gui.LOGFONT()
lf.lfFaceName = "Times New Roman"
lf.lfHeight = int(round(dpiScale * fontSize))
#lf.lfWeight = 150
# Use nonantialiased to remove the white edges around the text.
# lf.lfQuality = win32con.NONANTIALIASED_QUALITY
hf = win32gui.CreateFontIndirect(lf)
win32gui.SelectObject(hdc, hf)
rect = win32gui.GetClientRect(hWnd)
# http://msdn.microsoft.com/en-us/library/windows/desktop/dd162498(v=vs.85).aspx
win32gui.DrawText(
hdc,
'Text on the screen',
-1,
rect,
win32con.DT_CENTER | win32con.DT_NOCLIP | win32con.DT_SINGLELINE | win32con.DT_VCENTER
)
win32gui.EndPaint(hWnd, paintStruct)
return 0
elif message == win32con.WM_DESTROY:
print 'Closing the window.'
win32gui.PostQuitMessage(0)
return 0
else:
return win32gui.DefWindowProc(hWnd, message, wParam, lParam)
if __name__ == '__main__':
main()