source: trunk/screenshot.py @ 277

Revision 277, 8.4 KB checked in by marc, 3 years ago (diff)

Enhanced console logging functions to support multiple arguments and better concatenation of them. Changed names for quicker testing. Don't import the reactor twice, better handling of a single reactor, a bug fix. Build shot file path for screenshots on request, this fixes a bug where the screenshot file name locally wasn't changed while the app was running. Better clean up of stale screenshot files. Added handling of SIGINT (ctrl-c) which shuts down the server and cleans up stale screenshot files.

  • Property svn:keywords set to Id Rev
Line 
1#! /usr/bin/env python
2# -*- coding: utf-8 -*-
3#
4# Itaka is free software; you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation; either version 3 of the License, or
7# any later version.
8#
9# Itaka is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with Itaka; if not, write to the Free Software
16# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17#
18# Copyright 2003-2009 Marc E.
19# http://itaka.jardinpresente.com.ar
20#
21# $Id$
22
23""" Itaka screenshot engine """
24
25import gc
26import os
27import gtk
28import pygtk
29pygtk.require("2.0")
30import error
31import traceback
32
33class Screenshot:
34    """
35    Takes screenshots of windows or screens
36    """
37
38    def __init__(self, gui_instance, scaling_method=gtk.gdk.INTERP_BILINEAR):
39        """
40        Constructor
41
42        @type scaling_method: gtk.gdk.INTERP_TYPE
43        @param scaling_method: A type of interpolation for screenshot scaling. U{http://pygtk.org/pygtk2reference/class-gdkpixbuf.html#method-gdkpixbuf--scale-simple}
44
45        @type gui_instance: instance
46        @param gui_instance: An instance of our L{Gui} class
47        """
48
49        self.gui = gui_instance
50        self.itaka_globals = self.gui.itaka_globals
51        self.configuration = self.gui.configuration
52        self.console = self.gui.console
53        self.scaling_method = scaling_method
54
55        #: Whether our current window method failed or not
56        self.current_window_failed = False
57
58        self.root_screen = gtk.gdk.screen_get_default()
59        self.root_window = gtk.gdk.get_default_root_window()
60
61        self.screen_width = gtk.gdk.screen_width()
62        self.screen_height = gtk.gdk.screen_height()
63
64    def find_current_active_window(self):
65        """
66        Find the current active window through the _NET_ACTIVE_WINDOW hint
67
68        @rtype: tuple
69        @return: (int) window width, (int) window heigth, (int) window position x, (int) window position y
70        """
71
72        if self.root_screen.supports_net_wm_hint("_NET_ACTIVE_WINDOW") and \
73            self.root_screen.supports_net_wm_hint("_NET_WM_WINDOW_TYPE"):
74            self.active_window = self.root_screen.get_active_window()
75
76            # Calculate the size of the window including window manager decorations
77            self.relativex, self.relativey, self.winw, self.winh, self.d = self.active_window.get_geometry()
78            self.window_width = self.winw + (self.relativex*2)
79            self.window_height = self.winh + (self.relativey+self.relativex)
80
81            # Calculate the position of where the window manager decorations start
82            # get_position() will return the position of the window relative to the WM
83            self.window_positionx, self.window_positiony = self.active_window.get_root_origin()
84        else:
85            self.current_window_failed = True
86            raise error.ItakaScreenshotWmHintsError, _('Window Manager does not support _NET_WM hints')
87   
88        # We do not want to grab the desktop window
89        if self.active_window.property_get("_NET_WM_WINDOW_TYPE")[-1][0] == '_NET_WM_WINDOW_TYPE_DESKTOP':
90            self.current_window_failed = True
91            raise error.ItakaScreenshotActiveDesktopError, _('Active window is desktop')
92
93        return (self.window_width, self.window_height, self.window_positionx, self.window_positiony)
94
95    def take_screenshot(self):
96        """
97        Take a screenshot of the whole screen or a window
98
99        @rtype: str
100        @return: Path to the screenshot (L{self.shot_file})
101        """
102
103        # Get up to date configuration values and build shot file path everytime there is a request
104        self.configuration = self.gui.configuration
105
106        #: Final absolute path to the screenshot file
107        self.shot_file = os.path.join(self.configuration['screenshot']['path'], 'itakashot.%s' % (self.configuration['screenshot']['format']))
108       
109        if self.configuration['screenshot']['currentwindow'] and not self.itaka_globals.system == 'nt':
110            try:
111                self.current_window = self.find_current_active_window()
112            except error.ItakaScreenshotWmHintsError:
113                self.gui.log.failure(('Screenshot', 'take_screenshot'), (_('Can not grab the current window'), _('Can not grab the current window because your window manager does not support NET_WM_* hints')), 'WARNING')
114            except error.ItakaScreenshotActiveDesktopError:
115                self.gui.log.failure(('Screenshot', 'take_screenshot'), (_('Not grabing the desktop as the current window'), _('Your focus was on the destop when a client requested a screenshot, Itaka instead took a screenshot of the whole screen')), 'WARNING')
116
117            if not self.current_window_failed:
118                # Make the window size also the screen size for scaling purposes
119                self.active_windowwidth = self.current_window[0]
120                self.active_windowheight = self.current_window[1]
121
122                self.screenshot = gtk.gdk.Pixbuf.get_from_drawable(
123                        gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, self.active_windowwidth, self.active_windowheight),
124                        self.root_window,
125                        gtk.gdk.colormap_get_system(),
126                        self.current_window[2], self.current_window[3], 0, 0, self.active_windowwidth, self.active_windowheight)
127
128        if self.current_window_failed or not self.configuration['screenshot']['currentwindow'] or self.itaka_globals.system == 'nt':
129            self.screenshot = gtk.gdk.Pixbuf.get_from_drawable(
130                    gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, self.screen_width, self.screen_height),
131                    self.root_window,
132                    gtk.gdk.colormap_get_system(),
133                    0, 0, 0, 0, self.screen_width, self.screen_height)
134
135        # GTK manages errors this way
136        if not hasattr(self, 'screenshot') or self.screenshot is None:
137            # Reset the failure flag
138            self.current_window_failed = False
139            self.gui.log.failure(('Screenshot', 'take_screenshot'), (_('Could not grab screenshot'), _('GTK+ could not grab a screenshot of the screen')), 'ERROR')
140            raise error.ItakaScreenshotError, _('Could not grab screenshot, GTK+ error')
141
142        if self.configuration['screenshot']['scale']:
143            # Make it just work, dont bother warning about very rare cases
144            if self.configuration['screenshot']['scalepercent'] == 0:
145                self.configuration['screenshot']['scalepercent'] = 1
146
147            if self.configuration['screenshot']['currentwindow'] and not self.current_window_failed and not self.itaka_globals.system == 'nt':
148                self.scale_width = self.active_windowwidth * int(self.configuration['screenshot']['scalepercent']) / 100
149                self.scale_height = self.active_windowheight * int(self.configuration['screenshot']['scalepercent']) / 100
150            else:
151                self.scale_width = self.screen_width * int(self.configuration['screenshot']['scalepercent']) / 100
152                self.scale_height = self.screen_height * int(self.configuration['screenshot']['scalepercent']) / 100
153            self.screenshot = self.screenshot.scale_simple(self.scale_width, self.scale_height, self.scaling_method)
154
155        # Save the screnshot, checking before if to set JPEG quality
156        try:
157            if self.configuration['screenshot']['format'] == 'jpeg':
158                self.screenshot.save(self.shot_file, self.configuration['screenshot']['format'].lower(), {"quality":str(self.configuration['screenshot']['quality'])})
159            else:
160                self.screenshot.save(self.shot_file, self.configuration['screenshot']['format'].lower())
161        except:
162            self.gui.log.failure(('Screenshot','take_screenshot'), (_('Could not save screenshot'), _('Could not save screenshot %s') % (traceback.format_exc())), 'ERROR')
163            raise error.ItakaScreenshotSaveError, _('Could not save screenshot')
164
165        # Important workaround to avoid a memory leak
166        # http://www.async.com.br/faq/pygtk/index.py?req=show&file=faq08.004.htp
167        del self.screenshot
168        gc.collect()
169
170        # Reset the failure flag
171        self.current_window_failed = False
172
173        return self.shot_file
174
Note: See TracBrowser for help on using the repository browser.