I have a 3d rendering program that rotates the world around an observer based on the position the mouse is on the screen. The amount in radians that the world is rotated is defined by this line
glob.worldx=-(w.winfo_pointerxy()[0]-xy[0])/250
Where xy[0] is the x coordinate of the center of the screen
This means that the amount that the observers field of view can be rotated is limited by the distance the mouse can go. If I could get the mouse to come back to the center of the screen I could solve this problem. Any ideas?
The good news is that there is a way to do it.
The intermediate news is that it's not well documented.
The bad news is that it only works on some platforms.
The other intermediate news is that you can step outside of Tk on at least some platforms.
The way to do this in Tcl/Tk is by generating a <Motion> event with -warp 1. The documentation on this is sparse, and scattered around a few different pages (start at bind), but the details are described here. Basically, it's just this:
event generate . <Motion> -warp 1 -x 50 -y 50
So, how do you do this from Tkinter?
Well, event_generate isn't documented anywhere, and neither is the <Motion> event, or the warp parameter… but it's pretty simple to figure out if you know how Tk maps to Tkinter:
window.event_generate('<Motion>', warp=True, x=50, y=50)
And this does indeed generate an event, as you can see by binding <Motion>. Here's a simple test program:
from tkinter import *
root = Tk()
def key(event):
root.event_generate('<Motion>', warp=True, x=50, y=50)
def motion(event):
print('motion {}, {}'.format(event.x, event.y))
root.bind('<Key>', key)
root.bind('<Motion>', motion)
root.mainloop()
Run it, click the window to make sure it has focus, move the cursor around, and you'll see it print out something like this:
motion 65, 69
motion 65, 70
motion 65, 71
Then hit a key, and it'll print out this:
motion 50, 50
Which is great… except that it may not actually be able to move your cursor, in which case all this does is trick Tk into thinking the cursor moved.
From skimming various forums, it looks like:
Mac: Does not work.
You must have Tk 8.6.something or later (see issue 2926819). And you probably have 8.5.something.
But it's not hard to go right to the Cocoa API.
Windows: Usually works.
You must have Tk 8.4.something or later. I couldn't find the bug for this, but you can count on 8.4 with any official Windows binary install of Python 2.7 or 3.x+.
You also must not be running a full-screen app (which you generally aren't, with Tk).
On Vista and later, in some cases it won't work. This may have something to do with not owning the desktop session or not being a local console session, or it may have to do with needing Administrator or other privileges.
If it doesn't work, it's easy to go right to the Win32 API.
X11 (most linux, *BSD, etc.): Usually
Your window manager must not have disabled other clients from warping the pointer. Fortunately, that doesn't seem to be a common thing to do.
If you have this problem, there's no way around it.
Other platforms (iOS, Android, etc.): No idea.
For Mac, you want to generate and send an NSMouseMoved event. The easy way to do this is with pyobjc (which is built in if you're using Apple's Python; otherwise you have to install it):
app = Foundation.NSApplication.sharedApplication()
event = Foundation.NSEvent.mouseEventWithType_location_modifierFlags_timestamp_windowNumber_context_eventNumber_clickCount_pressure_(
Foundation.NSMouseMoved, (50, 50), 0, 0,
app.mainWindow().windowNumber(), None, 0, 0, 0.0)
app.sendEvent_(event)
For Windows, you want to call the SetCursorPos API, or generate and send a MOUSEEVENT. The former will not work with, e.g., DirectX games; the latter may not work with remote desktops. For this case, you probably want the former. Either way, the easiest way to do this is to install pywin32, and then it's just:
win32api.SetCursorPos((50, 50))
For anyone interested in moving the cursor to an absolute position on the screen (using #abarnert's tkinter method):
# Moves the mouse to an absolute location on the screen
def move_mouse_to(x, y):
# Create a new temporary root
temp_root = tk.Tk()
# Move it to +0+0 and remove the title bar
temp_root.overrideredirect(True)
# Make sure the window appears on the screen and handles the `overrideredirect`
temp_root.update()
# Generate the event as #abarnert did
temp_root.event_generate("<Motion>", warp=True, x=x, y=y)
# Make sure that tcl handles the event
temp_root.update()
# Destroy the root
temp_root.destroy()
This function shouldn't interfere with other tkinter windows. Please note that this function creates a new window that is refreshed twice so it might be slow and the user might notice the window created.
Related
I am working with pygamezero using an editor called MU (this has the pgzero module built in). When the code is executed the top left of the game window spawns from the centre of the screen and, depending on the dimensions provided by the user for height and width and their screen resolution, portions of the window often appears "off screen". I have found a method - using pygame - that evokes full screen, but am wondering if there is a method to set starting x/y coords of the game screen, so that it is not full-screen, but the window spawn position can be controlled.
To control the window position in Pygame Zero (pgzrun) make use of os.environ['SDL_VIDEO_WINDOW_POS'] as shown below. It must happen before the import pgzrun call, otherwise it won't work.
FYI If you're using Thonny--a teaching & learning IDE--you need to disable Pygame Zero mode from the Run menu to control where the window appears. If you don't disable the mode, Thonny will override your choice by implicitly importing (& thus executing) pgzrun.
The following code snippet will place your window at the top-left corner of the screen. You can also make the window go fullscreen in a different way but that doesn't require os.environ.
x = 0
y = 0
import os
os.environ['SDL_VIDEO_WINDOW_POS'] = f'{x},{y}'
import pgzrun
pgzrun.go()
Note: This question has also been (pretty much) answered elsewhere. Because this question and answer specifically relate to Pygame Zero I'm adding this here so anyone looking specifically for a pygame zero solution can find it.
What I want to do is, I want to have have a user click the close "X" button in an OpenCV window and have the program recognize it, and close that window.
It seems that this is not easy, and after four days of going round in circles and finding out how it can be done on a windows machine I am no closer to finding out how to do it on a Raspberry Pi using Python.
I think I need to get the handle of the OpenCV window ( how? ) and then use that to see if the window is still visible ( what call? ) and if it is not, bring proceedings to a halt ( I can do that bit ).
I have tried cvGetWindowHandle("window_name") but I've downloaded the source and GetWindowHandle doesn't seem to be available from python.
The code to capture the left button mouse click event and close a window is fairly simple:
if event == cv2.EVENT_LBUTTONDOWN:
cv2.destroyWindow("window_name")
There is a tutorial on how to use the button click event here which is where I took that code, it provides a full working example in python.
However you are probably running a unix based system on your Rpi and will therefore want to read This answer as you made need a combination of waitKey(1) in order for it to work.
I maybe have a solution but I'm not 100% sure so you'll have to check it yourself:) I assume the OpenCV uses X11 underneath (if no none of this makes sense). With X11 you can:
1) Find X11 window handle for your OpenCV window as described here
2) Use XSelectInput to hook into its event loop somewhat similar to what was done here. I assume you should useStructureNotifyMask as the mask to get the XDestroyWindowEvent event. Run the X11 event loop and as soon as you get the corresponding event you can call the OpenCV destroyWindow function.
This suggestion is based on assumptions and I can't give any guarantees it will work, but as far as I understand if OpenCV isn't built with some other specific window manager this should work. As far as I understand Raspbian was shipped with X11 up to some point and then it switched to Wayland. In case you have an image with Wayland then this probably will not work (and I'm sorry but my Linux skills do not contain a recipe on how to determine which one is used:D).
UPDATE
Actually after more reading I seem to feel that gtkshould be able to handle whatever is being used underneath (X11/Wayland). So if you install gtk development libraries you should also be able to connect to the windows deletion signal like described here. The only question then remains on how to obtain the window handle.
My personal advice - use Qt or some other GUI friendly framework to render the OpenCV images instead of doing it directly with OpenCV. OpenCV is an imaging framework but IMHO highgui is too unusable for anything serious.
all I want to do is to have a user click the close X in an openCV
window
This is how I did it, in a capture loop (RPi stretch, opencv 4.0):
while True:
# do your video capture
# ...
cv.imshow("video frame",frame)
if cv.getWindowProperty('video frame', 1) < 0:
break
getWindowProperty isn't much documented but what it does is, as its name implies, to return the property of a given window. Two of the flags of interest are WND_PROP_FULLSCREEN (or 0) and WND_PROP_AUTOSIZE (or 1). When the window is closed the function returns -1. Use this to immediately break your loop (or close your window if not in a loop).
References:
https://docs.opencv.org/3.1.0/d7/dfc/group__highgui.html#gaaf9504b8f9cf19024d9d44a14e461656
OpenCV Python: How to detect if a window is closed?
Poll with cv2.getWindowImageRect(windowName). It will return (-1, -1, -1, -1) when the user clicks the window close button.
# check if window was closed or image was resized
xPos, yPos, width, height = cv2.getWindowImageRect(windowName)
if xPos == -1: # if user closed window
pass # do whatever you want here if the user clicked CLOSE
I haven't found this documented anywhere; discovered it by accident while handling window resizing. (Tested with OpenCV 4.1.0.)
Setup:
OS: Ubuntu Precise (12.04) amd64
Python: 2.7.3
GTK: 2.24.0
WindowManager: metacity and FVWM2 both exhibit the same behavior
I have a small pygtk app that contains (among other widgets) a treeview. The window has resizable=False so as the treeview is expanded/collapsed, the window automatically resizes to fit the content. This all works just fine.
The problem is that when the window resizes, it stays centered on where it was. I want the top-left corner of the window to stay in the same spot when the window resizes.
I've found that calling parse_geometry() with a +x+y position will induce the desired behavior. But that's not always an option. In the typical case, I want the window to open where the mouse is, not in a fixed location.
I suspect I need to set gtk.gdk.HINT_USER_POS, but as best I can tell, pygtk's version of set_geometry_hints() doesn't allow me to pass GdkWindowHints like the C and Perl interfaces do.
How can I make the window's top-left corner stay still as the contents cause the window to change size?
Update:
Since parse_geometry() sets the bit for me, just before gtk.main(), I call
gobject.timeout_add(500, self.fix_window)
The callback is:
def fix_window(self):
self.window.parse_geometry("+%d+%d" % self.window.get_position())
While this works, it's ugly so I'd still appreciate a better alternative.
I am creating a full-frame (no decorations) window with code like this (in python 3.2 using tkinter):
self.root = Tk()
self.W, self.H = self.root.winfo_screenwidth(), self.root.winfo_screenheight()
self.root.overrideredirect(1) # full screen, no menu or borders
self.root.geometry("%dx%d+0+0" % (self.W, self.H))
When I try to open a file dialog or message box, they appear UNDER the full frame window. I can verify this by calling a withdraw() on the main window before I open one of the dialogs. For example,
file = tkinter.filedialog.askopenfilename(parent=self.root) # UNDER main window
On windows I don't have a problem with this, only on fedora 14 and ubuntu 12.04 have I noticed it. (I haven't tested on Mac). I'm passing a parent to the dialogs but they don't seem to be paying attention. Can someone help me understand what I'm doing wrong? Thanks.
Calling .overrideredirect(1) on a window has a different meanings on Windows and X11. On Windows, it tells the OS to disable drawing of the window border. On X11, it tells the window manager to completely ignore the window. Realistically, it should have the same effect on Windows that it does on X11, but this is not the case.
The reason why calling .overrideredirect(1) causes the window to stay on top is because X11 does not have any control over it (as the displaying of the window is not handled by the window manager). The program window and the window manager are completely independent, so implementing standard window stacking would not make sense.
With only tkinter, there is nothing you can do to prevent this behaviour, because tkinter is not really the source of the problem. There may be a way to use X11 Python bindings to show the window without a frame, but this would result in platform specific code.
You may want to rethink removing the window border. Is there a possible alternative? A fullscreen window including the window border is a fine option. Removing window borders is not a good idea at the best of times due to accessibility reasons (no way to move, minimize, maximize, etc.). Also, personally, as a Linux user, I have my window borders customized with all kinds of features (e.g. window tabbing, shade button), and use them quite frequently. Removing the window border would prevent such features from being used.
I am trying to rebuild the functionality of the desktop's "highlight to select" feature so that I can use it in my own app. When I say "highlight to select" I mean the selection box that shows up if you click and drag on your desktop (native to all main-stream OS).
I've been working for hours trying to recreate it, and simply can't find a way. I've tried PyGTK, Xlib for python, and a couple other weird hacks. All of which have their own problems that won't allow me to move forward.
I generally don't ask for straight up example code without providing some sort of starting point, but in this project I don't even know where to start. How would you do this?
Here's the requirements:
Must draw on the root window (or a transparent layer that "appears" to be the root)
Must return the coordinates of the selection (x, y, height width)
Update: Forgot some details.
I am using Ubuntu 10.10
I have dual monitors (though, I don't think that should matter)
I don't mind downloading any extra libraries that are necessary
I don't know if this is what you're looking for, but what if you created another window in your module, and have your code show it when you release drag? You could fetch the cursor's current position, and have it draw the window there.
This should help you get the mouse position on the root window.
So, your code may look a little like this (this is untested code!) I'm only showing the relevant portions of what goes inside __ init __.
def __init__(self):
...
#Some of your code here.
...
win = gtk.Window(gtk.WINDOW_TOPLEVEL)
#Note that I am creating a popup window separately.
popwin = gtk.Window(gtk.WINDOW_POPUP)
#I am setting "decorated" to False, so it will have no titlebar or window controls.
#Be sure to compensate for this by having another means of closing it.
popwin.set_decorated(False)
def ShowPopup():
#You may need to put additional arguments in above if this is to be an event.
#For sake of example, I'm leaving this open ended.
#Get the cursor position.
rootwin = widget.get_screen().get_root_window()
curx, cury, mods = rootwin.get_pointer()
#Set the popup window position.
popwin.move(curx, cury)
popwin.show()
def HidePopup():
#This is just an example for how to hide the popup when you're done with it.
popwin.hide()
...
#More of your code here.
...
#Of course, here is the code showing your program's main window automatically.
win.show()
A very simplistic approach, but it should give the appearance of what you're wanting.