What is best signal for writing configuration file in pyGTK? - python

I want to save window position and size when user is closing main window of my application, but i cant get correct window position
myTopLevelWindow.connect('unrealize', self.__onUnrealize)
def __onUnrealize(self, widget):
myTopLevelWindow.get_size() #OK
myTopLevelWindow.get_position() <-- always (0, 0) :P
in what signal handler get_position() will work correctly? I've tried 'destroy' and it's also not good :/

Have you tried "delete-event"? I just tested it in C and gtk gives me the correct position/size in the handler.

Related

How do I take a screenshot of a specfic window in Qt (Python, Linux), even if the windows are overlapped?

I'm trying to take a screenshot of the current active window in PyQt5. I know the generic method to take an screenshot of any window is QScreen::grabWindow(winID), for which winID is an implementation-specific ID depending on the window system. Since I'm running X and KDE, I plan to eventual use CTypes to call Xlib, but for now, I simply execute "xdotool getactivewindow" to obtain the windowID in a shell.
For a minimum exmaple, I created a QMainWindow with a QTimer. When the timer is fired, I identify the active window ID by executing "xdotool getactivewindow", get its return value, call grabWindow() to capture the active window, and display the screetshot in a QLabel. On startup, I also set my window a fixed 500x500 size for observation, and activate Qt.WindowStaysOnTopHint flag, so that my window is still visible when it's not in focus. To put them together, the implementation is the following code.
from PyQt5 import QtCore, QtGui, QtWidgets
import subprocess
class ScreenCapture(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
self.setFixedHeight(500)
self.setFixedWidth(500)
self.label = QtWidgets.QLabel(self)
self.timer = QtCore.QTimer(self)
self.timer.setInterval(500)
self.timer.timeout.connect(self.timer_handler)
self.timer.start()
self.screen = QtWidgets.QApplication.primaryScreen()
#QtCore.pyqtSlot()
def timer_handler(self):
window = int(subprocess.check_output(["xdotool", "getactivewindow"]).decode("ascii"))
self.screenshot = self.screen.grabWindow(window)
self.label.setPixmap(self.screenshot)
self.label.setFixedSize(self.screenshot.size())
if __name__ == '__main__':
app = QtWidgets.QApplication([])
window = ScreenCapture()
window.show()
app.exec()
To test the implementation, I started the script and clicked another window. It appears to work without problems if there is no overlap between my application window and the active window. See the following screenshot, when Firefox (right) is selected, my application is able to capture the active window of Firefox and display it in the QLabel.
However, the screenshot doesn't work as expected if there is an overlap between the application window and the active window. The window of the application itself will be captured, and creates a positive feedback.
If there is an overlap between the application window and the active window. The window of the application itself will be captured, and creates a positive feedback.
I've already disabled the 3D composite in KDE's settings, but the problem remains. The examples above are taken with all composite effects disabled.
Question
Why isn't this implementation working correctly when the application window and the active window are overlapped? I suspect it's an issue caused by some forms of unwanted interaction between graphics systems (Qt toolkit, window manager, X, etc), but I'm not sure.
Is it even possible solve this problem? (Note: I know I can hide() before the screenshot and show() it again, but it doesn't really solve this problem, which is taking a screenshot even if an overlap exists.)
As pointed out by #eyllanesc, it appears that it is not possible to do it in Qt, at least not with QScreen::grabWindow, because grabWindow() doesn't actually grab the window itself, but merely the area occupied by the window. The documentation contains the following warning.
The grabWindow() function grabs pixels from the screen, not from the window, i.e. if there is another window partially or entirely over the one you grab, you get pixels from the overlying window, too. The mouse cursor is generally not grabbed.
The conclusion is that it's impossible do to it in pure Qt. It's only possible to implement such a functionality by writing a low-level X program. Since the question asks for a solution "in Qt", any answer that potentially involves deeper, low-level X solutions are out-of-scope. This question can be marked as resolved.
The lesson to learn here: Always check the documentation before using a function or method.
Update: I managed to solve the problem by reading the window directly from X via Xlib. Somewhat ironically, my solution uses GTK to grab the window and sends its result to Qt... Anyway, you can write the same program with Xlib directly if you don't want to use GTK, but I used GTK since the Xlib-related functions in GDK is pretty convenient to demonstrate the basic concept.
To get a screenshot, we first convert our window ID to an GdkWindow suitable for use within GDK, and we call Gdk.pixbuf_get_from_window() to grab the window and store it in a gdk_pixbuf. Finally, we call save_to_bufferv() to convert the raw pixbuf to a suitable image format and store it in a buffer. At this point, the image in the buffer is suitable to use in any program, including Qt.
The documentation contains the following warning:
If the window is off the screen, then there is no image data in the obscured/offscreen regions to be placed in the pixbuf. The contents of portions of the pixbuf corresponding to the offscreen region are undefined.
If the window you’re obtaining data from is partially obscured by other windows, then the contents of the pixbuf areas corresponding to the obscured regions are undefined.
If the window is not mapped (typically because it’s iconified/minimized or not on the current workspace), then NULL will be returned.
If memory can’t be allocated for the return value, NULL will be returned instead.
It also has some remarks about compositing,
gdk_display_supports_composite has been deprecated since version 3.16 and should not be used in newly-written code.
Compositing is an outdated technology that only ever worked on X11.
So basically, it's only possible to grab a partially obscured window under X11 (not possible in Wayland!), with a compositing window manager. I tested it without compositing, and found the window is blacked-out when compositing is disabled. But when composition is enabled, it seems to work without problem. It may or may not work for your application. But I think if you are using compositing under X11, it probably will work.
from PyQt5 import QtCore, QtGui, QtWidgets
import subprocess
class ScreenCapture(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
self.setFixedHeight(500)
self.setFixedWidth(500)
self.label = QtWidgets.QLabel(self)
self.screen = QtWidgets.QApplication.primaryScreen()
self.timer = QtCore.QTimer(self)
self.timer.setInterval(500)
self.timer.timeout.connect(self.timer_handler)
self.timer.start()
#staticmethod
def grab_screenshot():
from gi.repository import Gdk, GdkX11
window_id = int(subprocess.check_output(["xdotool", "getactivewindow"]).decode("ascii"))
display = GdkX11.X11Display.get_default()
window = GdkX11.X11Window.foreign_new_for_display(display, window_id)
x, y, width, height = window.get_geometry()
pb = Gdk.pixbuf_get_from_window(window, 0, 0, width, height)
if pb:
buf = pb.save_to_bufferv("bmp", (), ())
return buf[1]
else:
return
#QtCore.pyqtSlot()
def timer_handler(self):
screenshot = self.grab_screenshot()
self.pixmap = QtGui.QPixmap()
if not self.pixmap:
return
self.pixmap.loadFromData(screenshot)
self.label.setPixmap(self.pixmap)
self.label.setFixedSize(self.pixmap.size())
if __name__ == '__main__':
app = QtWidgets.QApplication([])
window = ScreenCapture()
window.show()
app.exec()
Now it captures an active window perfectly, even if there are overlapping windows on top of it.

Python 2.7: Close a dialog without exiting the entire application

I'm running a script coded in python from a scripts menu in a desktop application. It's basically a giant macro that I wrote and added a GUI to. I'm pretty sure the GUI is a really old one that my desktop app uses called dialogKit from MIT.
GitHub still has it here.
The problem is the word "stop" at the very end of the dialog code.
I keep getting a "stop is undefined" message, which I understand, but I've tried everything to close the dialog and if I use exit(), sys.exit(), I don't get an error, but it also closes my entire desktop app.
I need to close the dialog and keep the software open.
The limited dialog documentation for what I'm using can be found here.
(you might have to click on the Dialog section. Their site uses frames.)
class MyDialog:
def __init__(self):
self.d = Dialog(self)
self.d.size = Point(300, 340)
self.d.Center()
self.d.title = "Halftone" #<----- Title of the dialogue
self.d.AddControl(STATICCONTROL, Rect(aIDENT, aIDENT, aIDENT, aIDENT), "frame", STYLE_FRAME)
# more controls and methods..
def on_ok(self, code):
return 1
def on_cancel(self, code):
print "blah"
def Run(self):
return self.d.Run()
d = MyDialog()
if d.Run()!= 1:
stop
I just need a way to change stop to something that 1) will prevent the script from running, and 2) close the dialog without quitting the entire application. This is the functionality of a typical "cancel" button, which is what I want.
Another option is the method called on_cancel(), which I also tried and could get the event itself to work, but still the entire application quits with any kind of exit().
The docs show a method called End(), which claims to terminate the dialog object, but I've tried and failed to get that to work either.
Okay, I'm posting an answer because I think I have a handle on your problem.
Try replacing stop with:
d.d.End()
If that works, you might want to try putting:
self.d.End()
inside of the on_cancel function in your class. That should close the dialogue without closing your program.

How to open a popup window with a spinner in python + Gtk

I have a python Gtk application, with the GUI designed in Glade. It has a "scan" feature which will scan the network for a few seconds and then report its results to the user. During the scanning I want a popup window to appear stealing the focus from the parent until scanning is done.
I use a threading.Lock to synchronize the GUI and the scan thread, which makes the popup to last exactly the right time I want (see scanLock.acquire() ). It seems straightforward to me to implement something like a show() and hide() call before and after the scanLock.acquire(). I did use waitPopupShow and waitPopupHide instead of just calling the window.show() and window.hide() because I also may want to set the Label in the popup or start/stop the GtkSpinner. Here is some code from the GUI class:
def scan(self):
sT = scannerThread(self,self.STagList)
self.dataShare.threadsList.append(sT)
sT.start() # start scanning
self.waitPopupShow('Scanning... Please Wait')
self.scanLock.acquire() # blocks here until scan is finished
self.waitPopupHide()
def waitPopupShow(self, msg): # shows a GtkSpinner until the semaphore is cleared
self.waitDialogLabel.set_text(msg)
self.waitDialogBox.show_all()
self.waitDialog.show()
self.waitDialogSpinner.start()
def waitPopupHide(self):
# how to get the handle to the spinner and stop it?
self.waitDialogSpinner.stop()
self.waitDialog.hide()
def getAll(self):
# GUI
self.builder = Gtk.Builder()
self.builder.add_from_file(path to main GUI)
# ... getting stuff from a first glade file
# getting stuff from the waitDialog glade file
self.builder.add_from_file(path to waitDialog GUI)
self.waitDialog = self.builder.get_object("waitDialog") # GtkWindow
self.waitDialogBox = self.builder.get_object("waitDialogBox") # GtkBox
self.waitDialogLabel = self.builder.get_object("waitDialogLabel") # GtkLabel
self.waitDialogSpinner = self.builder.get_object("waitDialogSpinner") # GtkSpinner
self.waitDialog.hide()
I'm trying hardly since a couple of days to show a dialog with a label and a Gtk.Spinner. The best I obtain at the moment is to have the window showing up with no content. Please note that the self.waitDialog.hide() right after getting it with self.builder.get_object is needed because I set the property of the waitDialog Gtkwindow to Visibile. If I stop with the debugger before .hide() the waitDialog shows up perfectly. Afterwards its broken.
This is the waitDialog GUI file: http://pastebin.com/5enDQg3g
So my best guess is that I'm dooing something wrong, and I could find nothing on creating a new Gtk window over the main one, only basic examples and dialogs. A pointer to the documentation saying a bit about this would be a good starting point...

Can you display a Splash Screen post launch?

I'm trying to use QSplashScreen as a 'simple' notification window. I build the actual notification in a dialog and am then trying to use QPixmap.grabWidget() on that and pass the pixmap to the SplashScreen but its not showing. So I'm wondering if the problem is due to me trying to use it post-start.
Simplified version of what I'm doing.
class NotifyHandler:
def showNotify(self, event):
widget = NotifyWidget.createInstance(event) # Build the QDialog
#Do I need to do widget.show()?
pixmap = QtGui.QPixmap.grabWidget(widget) # Convert to pixmap
splash = QtGui.QSplashScreen(pixmap, QtCore.Qt.WindowStayOnTopHint)
(x,y) = getDisplayLocation(splash) # Get coords to put it in bottom-right corner
splash.setGeometry(splash.width(), splash.height(), x, y)
splash.show()
QtCore.QTimer.singleShot(3000, splash.close)
I try this however nothing shows up. If I try it as a Dialog though:
widget.show()
widget.raise_()
widget.activateWindow()
It works fine. So I figure either I can't do this post-launch or there is something going on when I try to feed it a pixmap of a widget. Any ideas?
It seems my dialog issue and splash screen were having the same sort of issue.
I forgot that if you don't assign the dialog (or Splash screen in this case) to a scope outside the method then the GC will just eat the object after the method returns.
So by simply adding something like:
self.splash = splash
self.splash.show()
QtCore.QTimer.singleShot(3000, self.splash.close)
The splash screen wouldn't be garbage collected and I get my splash screen.

wxPython: Exit Fullscreen

To display a wxPython window in full screen mode you use:
ShowFullScreen(True)
How do you get out of full screen though? I've tried the obvious way:
ShowFullScreen(True)
sleep(5)
ShowFullScreen(False)
This doesn't work though. When I run the script, nothing appears. After 5 seconds a window roughly 200x250 appears in the top-left corner of the screen, without anything inside of it. It doesn't appear to have any borders either.
If I change this to
showFullScreen(True)
then I get stuck with a full screen window that I have to use Alt + F2 -> xkill to get out of.
It looks like you need to Show() the window first. (According to the documentation, you shouldn't have to. Maybe this is a bug.) I tested on Mac OS X and Windows - they both exhibit issues if you don't call Show() first.
Also note that you shouldn't sleep in the main GUI thread. You'll hang the UI. Using CallLater is one potential solution, as shown in my example.
Working example:
import wx
def main():
app = wx.PySimpleApp()
frame = wx.Frame(None, -1, 'Full Screen Test')
frame.Show()
frame.ShowFullScreen(True)
wx.CallLater(5000, frame.ShowFullScreen, False)
app.MainLoop()
if __name__ == '__main__':
main()
The documentation for ShowFullScreen reads:
ShowFullScreen(show, style=wx.FULLSCREEN_ALL)
Depending on the value of show parameter the window is either shown full screen or restored to its normal state.
Parameters:
show (bool)
style (long): is a bit list containing some or all of the following values, which indicate what elements of the window to hide in full-screen mode:
wx.FULLSCREEN_NOMENUBAR
wx.FULLSCREEN_NOTOOLBAR
wx.FULLSCREEN_NOSTATUSBAR
wx.FULLSCREEN_NOBORDER
wx.FULLSCREEN_NOCAPTION
wx.FULLSCREEN_ALL (all of the above)
So put your Full Screen toggle event/s in a Menu and start full screen mode with:
self.window.ShowFullScreen(True, style=(wx.FULLSCREEN_NOTOOLBAR | wx.FULLSCREEN_NOSTATUSBAR |wx.FULLSCREEN_NOBORDER |wx.FULLSCREEN_NOCAPTION))
Note that I omitted wx.FULLSCREEN_NOMENUBAR, in this way you will still be able to access the menu to turn full screen mode off again.

Categories

Resources