| 1 | # -*- coding:utf-8 -*- |
|---|
| 2 | ## src/gtkgui_helpers.py |
|---|
| 3 | ## |
|---|
| 4 | ## Copyright (C) 2003-2008 Yann Leboulanger <asterix AT lagaule.org> |
|---|
| 5 | ## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com> |
|---|
| 6 | ## Copyright (C) 2005-2007 Nikos Kouremenos <kourem AT gmail.com> |
|---|
| 7 | ## Copyright (C) 2006 Travis Shirk <travis AT pobox.com> |
|---|
| 8 | ## Copyright (C) 2006-2007 Junglecow J <junglecow AT gmail.com> |
|---|
| 9 | ## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org> |
|---|
| 10 | ## Copyright (C) 2007 James Newton <redshodan AT gmail.com> |
|---|
| 11 | ## Julien Pivotto <roidelapluie AT gmail.com> |
|---|
| 12 | ## Copyright (C) 2007-2008 Stephan Erb <steve-e AT h3c.de> |
|---|
| 13 | ## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org> |
|---|
| 14 | ## |
|---|
| 15 | ## This file is part of Gajim. |
|---|
| 16 | ## |
|---|
| 17 | ## Gajim is free software; you can redistribute it and/or modify |
|---|
| 18 | ## it under the terms of the GNU General Public License as published |
|---|
| 19 | ## by the Free Software Foundation; version 3 only. |
|---|
| 20 | ## |
|---|
| 21 | ## Gajim is distributed in the hope that it will be useful, |
|---|
| 22 | ## but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 23 | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|---|
| 24 | ## GNU General Public License for more details. |
|---|
| 25 | ## |
|---|
| 26 | ## You should have received a copy of the GNU General Public License |
|---|
| 27 | ## along with Gajim. If not, see <http://www.gnu.org/licenses/>. |
|---|
| 28 | ## |
|---|
| 29 | |
|---|
| 30 | import xml.sax.saxutils |
|---|
| 31 | import gtk |
|---|
| 32 | import gtk.glade |
|---|
| 33 | import gobject |
|---|
| 34 | import pango |
|---|
| 35 | import os |
|---|
| 36 | import sys |
|---|
| 37 | |
|---|
| 38 | import vcard |
|---|
| 39 | import dialogs |
|---|
| 40 | |
|---|
| 41 | import logging |
|---|
| 42 | log = logging.getLogger('gajim.gtkgui_helpers') |
|---|
| 43 | |
|---|
| 44 | |
|---|
| 45 | HAS_PYWIN32 = True |
|---|
| 46 | if os.name == 'nt': |
|---|
| 47 | try: |
|---|
| 48 | import win32file |
|---|
| 49 | import win32con |
|---|
| 50 | import pywintypes |
|---|
| 51 | except ImportError: |
|---|
| 52 | HAS_PYWIN32 = False |
|---|
| 53 | |
|---|
| 54 | from common import i18n |
|---|
| 55 | from common import gajim |
|---|
| 56 | from common import helpers |
|---|
| 57 | |
|---|
| 58 | gtk.glade.bindtextdomain(i18n.APP, i18n.DIR) |
|---|
| 59 | gtk.glade.textdomain(i18n.APP) |
|---|
| 60 | |
|---|
| 61 | screen_w = gtk.gdk.screen_width() |
|---|
| 62 | screen_h = gtk.gdk.screen_height() |
|---|
| 63 | |
|---|
| 64 | GLADE_DIR = os.path.join('..', 'data', 'glade') |
|---|
| 65 | def get_glade(file_name, root = None): |
|---|
| 66 | file_path = os.path.join(GLADE_DIR, file_name) |
|---|
| 67 | return gtk.glade.XML(file_path, root=root, domain=i18n.APP) |
|---|
| 68 | |
|---|
| 69 | def get_completion_liststore(entry): |
|---|
| 70 | ''' create a completion model for entry widget |
|---|
| 71 | completion list consists of (Pixbuf, Text) rows''' |
|---|
| 72 | completion = gtk.EntryCompletion() |
|---|
| 73 | liststore = gtk.ListStore(gtk.gdk.Pixbuf, str) |
|---|
| 74 | |
|---|
| 75 | render_pixbuf = gtk.CellRendererPixbuf() |
|---|
| 76 | completion.pack_start(render_pixbuf, expand = False) |
|---|
| 77 | completion.add_attribute(render_pixbuf, 'pixbuf', 0) |
|---|
| 78 | |
|---|
| 79 | render_text = gtk.CellRendererText() |
|---|
| 80 | completion.pack_start(render_text, expand = True) |
|---|
| 81 | completion.add_attribute(render_text, 'text', 1) |
|---|
| 82 | completion.set_property('text_column', 1) |
|---|
| 83 | completion.set_model(liststore) |
|---|
| 84 | entry.set_completion(completion) |
|---|
| 85 | return liststore |
|---|
| 86 | |
|---|
| 87 | |
|---|
| 88 | def popup_emoticons_under_button(menu, button, parent_win): |
|---|
| 89 | ''' pops emoticons menu under button, which is in parent_win''' |
|---|
| 90 | window_x1, window_y1 = parent_win.get_origin() |
|---|
| 91 | def position_menu_under_button(menu): |
|---|
| 92 | # inline function, which will not keep refs, when used as CB |
|---|
| 93 | button_x, button_y = button.allocation.x, button.allocation.y |
|---|
| 94 | |
|---|
| 95 | # now convert them to X11-relative |
|---|
| 96 | window_x, window_y = window_x1, window_y1 |
|---|
| 97 | x = window_x + button_x |
|---|
| 98 | y = window_y + button_y |
|---|
| 99 | |
|---|
| 100 | menu_width, menu_height = menu.size_request() |
|---|
| 101 | |
|---|
| 102 | ## should we pop down or up? |
|---|
| 103 | if (y + button.allocation.height + menu_height |
|---|
| 104 | < gtk.gdk.screen_height()): |
|---|
| 105 | # now move the menu below the button |
|---|
| 106 | y += button.allocation.height |
|---|
| 107 | else: |
|---|
| 108 | # now move the menu above the button |
|---|
| 109 | y -= menu_height |
|---|
| 110 | |
|---|
| 111 | # push_in is True so all the menuitems are always inside screen |
|---|
| 112 | push_in = True |
|---|
| 113 | return (x, y, push_in) |
|---|
| 114 | |
|---|
| 115 | menu.popup(None, None, position_menu_under_button, 1, 0) |
|---|
| 116 | |
|---|
| 117 | def get_theme_font_for_option(theme, option): |
|---|
| 118 | '''return string description of the font, stored in |
|---|
| 119 | theme preferences''' |
|---|
| 120 | font_name = gajim.config.get_per('themes', theme, option) |
|---|
| 121 | font_desc = pango.FontDescription() |
|---|
| 122 | font_prop_str = gajim.config.get_per('themes', theme, option + 'attrs') |
|---|
| 123 | if font_prop_str: |
|---|
| 124 | if font_prop_str.find('B') != -1: |
|---|
| 125 | font_desc.set_weight(pango.WEIGHT_BOLD) |
|---|
| 126 | if font_prop_str.find('I') != -1: |
|---|
| 127 | font_desc.set_style(pango.STYLE_ITALIC) |
|---|
| 128 | fd = pango.FontDescription(font_name) |
|---|
| 129 | fd.merge(font_desc, True) |
|---|
| 130 | return fd.to_string() |
|---|
| 131 | |
|---|
| 132 | def get_default_font(): |
|---|
| 133 | '''Get the desktop setting for application font |
|---|
| 134 | first check for GNOME, then Xfce and last KDE |
|---|
| 135 | it returns None on failure or else a string 'Font Size' ''' |
|---|
| 136 | |
|---|
| 137 | try: |
|---|
| 138 | import gconf |
|---|
| 139 | # in try because daemon may not be there |
|---|
| 140 | client = gconf.client_get_default() |
|---|
| 141 | |
|---|
| 142 | return client.get_string('/desktop/gnome/interface/font_name' |
|---|
| 143 | ).decode('utf-8') |
|---|
| 144 | except Exception: |
|---|
| 145 | pass |
|---|
| 146 | |
|---|
| 147 | # try to get xfce default font |
|---|
| 148 | # Xfce 4.2 adopts freedesktop.org's Base Directory Specification |
|---|
| 149 | # see http://www.xfce.org/~benny/xfce/file-locations.html |
|---|
| 150 | # and http://freedesktop.org/Standards/basedir-spec |
|---|
| 151 | xdg_config_home = os.environ.get('XDG_CONFIG_HOME', '') |
|---|
| 152 | if xdg_config_home == '': |
|---|
| 153 | xdg_config_home = os.path.expanduser('~/.config') # default |
|---|
| 154 | xfce_config_file = os.path.join(xdg_config_home, 'xfce4/mcs_settings/gtk.xml') |
|---|
| 155 | |
|---|
| 156 | kde_config_file = os.path.expanduser('~/.kde/share/config/kdeglobals') |
|---|
| 157 | |
|---|
| 158 | if os.path.exists(xfce_config_file): |
|---|
| 159 | try: |
|---|
| 160 | for line in open(xfce_config_file): |
|---|
| 161 | if line.find('name="Gtk/FontName"') != -1: |
|---|
| 162 | start = line.find('value="') + 7 |
|---|
| 163 | return line[start:line.find('"', start)].decode('utf-8') |
|---|
| 164 | except Exception: |
|---|
| 165 | #we talk about file |
|---|
| 166 | print >> sys.stderr, _('Error: cannot open %s for reading') % xfce_config_file |
|---|
| 167 | |
|---|
| 168 | elif os.path.exists(kde_config_file): |
|---|
| 169 | try: |
|---|
| 170 | for line in open(kde_config_file): |
|---|
| 171 | if line.find('font=') == 0: # font=Verdana,9,other_numbers |
|---|
| 172 | start = 5 # 5 is len('font=') |
|---|
| 173 | line = line[start:] |
|---|
| 174 | values = line.split(',') |
|---|
| 175 | font_name = values[0] |
|---|
| 176 | font_size = values[1] |
|---|
| 177 | font_string = '%s %s' % (font_name, font_size) # Verdana 9 |
|---|
| 178 | return font_string.decode('utf-8') |
|---|
| 179 | except Exception: |
|---|
| 180 | #we talk about file |
|---|
| 181 | print >> sys.stderr, _('Error: cannot open %s for reading') % kde_config_file |
|---|
| 182 | |
|---|
| 183 | return None |
|---|
| 184 | |
|---|
| 185 | def autodetect_browser_mailer(): |
|---|
| 186 | # recognize the environment and set appropriate browser/mailer |
|---|
| 187 | if user_runs_gnome(): |
|---|
| 188 | gajim.config.set('openwith', 'gnome-open') |
|---|
| 189 | elif user_runs_kde(): |
|---|
| 190 | gajim.config.set('openwith', 'kfmclient exec') |
|---|
| 191 | elif user_runs_xfce(): |
|---|
| 192 | gajim.config.set('openwith', 'exo-open') |
|---|
| 193 | elif user_runs_osx(): |
|---|
| 194 | gajim.config.set('openwith', 'open') |
|---|
| 195 | else: |
|---|
| 196 | gajim.config.set('openwith', 'custom') |
|---|
| 197 | |
|---|
| 198 | def user_runs_gnome(): |
|---|
| 199 | return 'gnome-session' in get_running_processes() |
|---|
| 200 | |
|---|
| 201 | def user_runs_kde(): |
|---|
| 202 | return 'startkde' in get_running_processes() |
|---|
| 203 | |
|---|
| 204 | def user_runs_xfce(): |
|---|
| 205 | procs = get_running_processes() |
|---|
| 206 | if 'startxfce4' in procs or 'xfce4-session' in procs: |
|---|
| 207 | return True |
|---|
| 208 | return False |
|---|
| 209 | |
|---|
| 210 | def user_runs_osx(): |
|---|
| 211 | return sys.platform == 'darwin' |
|---|
| 212 | |
|---|
| 213 | def get_running_processes(): |
|---|
| 214 | '''returns running processes or None (if not /proc exists)''' |
|---|
| 215 | if os.path.isdir('/proc'): |
|---|
| 216 | # under Linux: checking if 'gnome-session' or |
|---|
| 217 | # 'startkde' programs were run before gajim, by |
|---|
| 218 | # checking /proc (if it exists) |
|---|
| 219 | # |
|---|
| 220 | # if something is unclear, read `man proc`; |
|---|
| 221 | # if /proc exists, directories that have only numbers |
|---|
| 222 | # in their names contain data about processes. |
|---|
| 223 | # /proc/[xxx]/exe is a symlink to executable started |
|---|
| 224 | # as process number [xxx]. |
|---|
| 225 | # filter out everything that we are not interested in: |
|---|
| 226 | files = os.listdir('/proc') |
|---|
| 227 | |
|---|
| 228 | # files that doesn't have only digits in names... |
|---|
| 229 | files = filter(str.isdigit, files) |
|---|
| 230 | |
|---|
| 231 | # files that aren't directories... |
|---|
| 232 | files = filter(lambda f:os.path.isdir('/proc/' + f), files) |
|---|
| 233 | |
|---|
| 234 | # processes owned by somebody not running gajim... |
|---|
| 235 | # (we check if we have access to that file) |
|---|
| 236 | files = filter(lambda f:os.access('/proc/' + f +'/exe', os.F_OK), files) |
|---|
| 237 | |
|---|
| 238 | # be sure that /proc/[number]/exe is really a symlink |
|---|
| 239 | # to avoid TBs in incorrectly configured systems |
|---|
| 240 | files = filter(lambda f:os.path.islink('/proc/' + f + '/exe'), files) |
|---|
| 241 | |
|---|
| 242 | # list of processes |
|---|
| 243 | processes = [os.path.basename(os.readlink('/proc/' + f +'/exe')) for f in files] |
|---|
| 244 | |
|---|
| 245 | return processes |
|---|
| 246 | return [] |
|---|
| 247 | |
|---|
| 248 | def move_window(window, x, y): |
|---|
| 249 | '''moves the window but also checks if out of screen''' |
|---|
| 250 | if x < 0: |
|---|
| 251 | x = 0 |
|---|
| 252 | if y < 0: |
|---|
| 253 | y = 0 |
|---|
| 254 | w, h = window.get_size() |
|---|
| 255 | if x + w > screen_w: |
|---|
| 256 | x = screen_w - w |
|---|
| 257 | if y + h > screen_h: |
|---|
| 258 | y = screen_h - h |
|---|
| 259 | window.move(x, y) |
|---|
| 260 | |
|---|
| 261 | def resize_window(window, w, h): |
|---|
| 262 | '''resizes window but also checks if huge window or negative values''' |
|---|
| 263 | if not w or not h: |
|---|
| 264 | return |
|---|
| 265 | if w > screen_w: |
|---|
| 266 | w = screen_w |
|---|
| 267 | if h > screen_h: |
|---|
| 268 | h = screen_h |
|---|
| 269 | window.resize(abs(w), abs(h)) |
|---|
| 270 | |
|---|
| 271 | class HashDigest: |
|---|
| 272 | def __init__(self, algo, digest): |
|---|
| 273 | self.algo = self.cleanID(algo) |
|---|
| 274 | self.digest = self.cleanID(digest) |
|---|
| 275 | |
|---|
| 276 | def cleanID(self, id_): |
|---|
| 277 | id_ = id_.strip().lower() |
|---|
| 278 | for strip in (' :.-_'): id_ = id_.replace(strip, '') |
|---|
| 279 | return id_ |
|---|
| 280 | |
|---|
| 281 | def __eq__(self, other): |
|---|
| 282 | sa, sd = self.algo, self.digest |
|---|
| 283 | if isinstance(other, self.__class__): |
|---|
| 284 | oa, od = other.algo, other.digest |
|---|
| 285 | elif isinstance(other, basestring): |
|---|
| 286 | sa, oa, od = None, None, self.cleanID(other) |
|---|
| 287 | elif isinstance(other, tuple) and len(other) == 2: |
|---|
| 288 | oa, od = self.cleanID(other[0]), self.cleanID(other[1]) |
|---|
| 289 | else: |
|---|
| 290 | return False |
|---|
| 291 | |
|---|
| 292 | return sa == oa and sd == od |
|---|
| 293 | |
|---|
| 294 | def __ne__(self, other): |
|---|
| 295 | return not self == other |
|---|
| 296 | |
|---|
| 297 | def __hash__(self): |
|---|
| 298 | return self.algo ^ self.digest |
|---|
| 299 | |
|---|
| 300 | def __str__(self): |
|---|
| 301 | prettydigest = '' |
|---|
| 302 | for i in xrange(0, len(self.digest), 2): |
|---|
| 303 | prettydigest += self.digest[i:i + 2] + ':' |
|---|
| 304 | return prettydigest[:-1] |
|---|
| 305 | |
|---|
| 306 | def __repr__(self): |
|---|
| 307 | return "%s(%s, %s)" % (self.__class__, repr(self.algo), repr(str(self))) |
|---|
| 308 | |
|---|
| 309 | class ServersXMLHandler(xml.sax.ContentHandler): |
|---|
| 310 | def __init__(self): |
|---|
| 311 | xml.sax.ContentHandler.__init__(self) |
|---|
| 312 | self.servers = [] |
|---|
| 313 | |
|---|
| 314 | def startElement(self, name, attributes): |
|---|
| 315 | if name == 'item': |
|---|
| 316 | # we will get the port next time so we just set it 0 here |
|---|
| 317 | sitem = [None, 0, {}] |
|---|
| 318 | sitem[2]['digest'] = {} |
|---|
| 319 | sitem[2]['hidden'] = False |
|---|
| 320 | for attribute in attributes.getNames(): |
|---|
| 321 | if attribute == 'jid': |
|---|
| 322 | jid = attributes.getValue(attribute) |
|---|
| 323 | sitem[0] = jid |
|---|
| 324 | elif attribute == 'hidden': |
|---|
| 325 | hidden = attributes.getValue(attribute) |
|---|
| 326 | if hidden.lower() in ('1', 'y', 'yes', 't', 'true', 'on'): |
|---|
| 327 | sitem[2]['hidden'] = True |
|---|
| 328 | self.servers.append(sitem) |
|---|
| 329 | elif name == 'active': |
|---|
| 330 | for attribute in attributes.getNames(): |
|---|
| 331 | if attribute == 'port': |
|---|
| 332 | port = attributes.getValue(attribute) |
|---|
| 333 | # we received the jid last time, so we now assign the port |
|---|
| 334 | # number to the last jid in the list |
|---|
| 335 | self.servers[-1][1] = port |
|---|
| 336 | elif name == 'digest': |
|---|
| 337 | algo, digest = None, None |
|---|
| 338 | for attribute in attributes.getNames(): |
|---|
| 339 | if attribute == 'algo': |
|---|
| 340 | algo = attributes.getValue(attribute) |
|---|
| 341 | elif attribute == 'value': |
|---|
| 342 | digest = attributes.getValue(attribute) |
|---|
| 343 | hd = HashDigest(algo, digest) |
|---|
| 344 | self.servers[-1][2]['digest'][hd.algo] = hd |
|---|
| 345 | |
|---|
| 346 | def endElement(self, name): |
|---|
| 347 | pass |
|---|
| 348 | |
|---|
| 349 | def parse_server_xml(path_to_file): |
|---|
| 350 | try: |
|---|
| 351 | handler = ServersXMLHandler() |
|---|
| 352 | xml.sax.parse(path_to_file, handler) |
|---|
| 353 | return handler.servers |
|---|
| 354 | # handle exception if unable to open file |
|---|
| 355 | except IOError, message: |
|---|
| 356 | print >> sys.stderr, _('Error reading file:'), message |
|---|
| 357 | # handle exception parsing file |
|---|
| 358 | except xml.sax.SAXParseException, message: |
|---|
| 359 | print >> sys.stderr, _('Error parsing file:'), message |
|---|
| 360 | |
|---|
| 361 | def set_unset_urgency_hint(window, unread_messages_no): |
|---|
| 362 | '''sets/unsets urgency hint in window argument |
|---|
| 363 | depending if we have unread messages or not''' |
|---|
| 364 | if gajim.config.get('use_urgency_hint'): |
|---|
| 365 | if unread_messages_no > 0: |
|---|
| 366 | window.props.urgency_hint = True |
|---|
| 367 | else: |
|---|
| 368 | window.props.urgency_hint = False |
|---|
| 369 | |
|---|
| 370 | def get_abspath_for_script(scriptname, want_type = False): |
|---|
| 371 | '''checks if we are svn or normal user and returns abspath to asked script |
|---|
| 372 | if want_type is True we return 'svn' or 'install' ''' |
|---|
| 373 | if os.path.isdir('.svn'): # we are svn user |
|---|
| 374 | type = 'svn' |
|---|
| 375 | cwd = os.getcwd() # it's always ending with src |
|---|
| 376 | |
|---|
| 377 | if scriptname == 'gajim-remote': |
|---|
| 378 | path_to_script = cwd + '/gajim-remote.py' |
|---|
| 379 | |
|---|
| 380 | elif scriptname == 'gajim': |
|---|
| 381 | script = '#!/bin/sh\n' # the script we may create |
|---|
| 382 | script += 'cd %s' % cwd |
|---|
| 383 | path_to_script = cwd + '/../scripts/gajim_sm_script' |
|---|
| 384 | |
|---|
| 385 | try: |
|---|
| 386 | if os.path.exists(path_to_script): |
|---|
| 387 | os.remove(path_to_script) |
|---|
| 388 | |
|---|
| 389 | f = open(path_to_script, 'w') |
|---|
| 390 | script += '\nexec python -OOt gajim.py $0 $@\n' |
|---|
| 391 | f.write(script) |
|---|
| 392 | f.close() |
|---|
| 393 | os.chmod(path_to_script, 0700) |
|---|
| 394 | except OSError: # do not traceback (could be a permission problem) |
|---|
| 395 | #we talk about a file here |
|---|
| 396 | s = _('Could not write to %s. Session Management support will not work') % path_to_script |
|---|
| 397 | print >> sys.stderr, s |
|---|
| 398 | |
|---|
| 399 | else: # normal user (not svn user) |
|---|
| 400 | type = 'install' |
|---|
| 401 | # always make it like '/usr/local/bin/gajim' |
|---|
| 402 | path_to_script = helpers.is_in_path(scriptname, True) |
|---|
| 403 | |
|---|
| 404 | |
|---|
| 405 | if want_type: |
|---|
| 406 | return path_to_script, type |
|---|
| 407 | else: |
|---|
| 408 | return path_to_script |
|---|
| 409 | |
|---|
| 410 | def get_pixbuf_from_data(file_data, want_type = False): |
|---|
| 411 | '''Gets image data and returns gtk.gdk.Pixbuf |
|---|
| 412 | if want_type is True it also returns 'jpeg', 'png' etc''' |
|---|
| 413 | pixbufloader = gtk.gdk.PixbufLoader() |
|---|
| 414 | try: |
|---|
| 415 | pixbufloader.write(file_data) |
|---|
| 416 | pixbufloader.close() |
|---|
| 417 | pixbuf = pixbufloader.get_pixbuf() |
|---|
| 418 | except gobject.GError: # 'unknown image format' |
|---|
| 419 | pixbufloader.close() |
|---|
| 420 | pixbuf = None |
|---|
| 421 | if want_type: |
|---|
| 422 | return None, None |
|---|
| 423 | else: |
|---|
| 424 | return None |
|---|
| 425 | |
|---|
| 426 | if want_type: |
|---|
| 427 | typ = pixbufloader.get_format()['name'] |
|---|
| 428 | return pixbuf, typ |
|---|
| 429 | else: |
|---|
| 430 | return pixbuf |
|---|
| 431 | |
|---|
| 432 | def get_invisible_cursor(): |
|---|
| 433 | pixmap = gtk.gdk.Pixmap(None, 1, 1, 1) |
|---|
| 434 | color = gtk.gdk.Color() |
|---|
| 435 | cursor = gtk.gdk.Cursor(pixmap, pixmap, color, color, 0, 0) |
|---|
| 436 | return cursor |
|---|
| 437 | |
|---|
| 438 | def get_current_desktop(window): |
|---|
| 439 | '''returns the current virtual desktop for given window |
|---|
| 440 | NOTE: window is GDK window''' |
|---|
| 441 | prop = window.property_get('_NET_CURRENT_DESKTOP') |
|---|
| 442 | if prop is None: # it means it's normal window (not root window) |
|---|
| 443 | # so we look for it's current virtual desktop in another property |
|---|
| 444 | prop = window.property_get('_NET_WM_DESKTOP') |
|---|
| 445 | |
|---|
| 446 | if prop is not None: |
|---|
| 447 | # f.e. prop is ('CARDINAL', 32, [0]) we want 0 or 1.. from [0] |
|---|
| 448 | current_virtual_desktop_no = prop[2][0] |
|---|
| 449 | return current_virtual_desktop_no |
|---|
| 450 | |
|---|
| 451 | def possibly_move_window_in_current_desktop(window): |
|---|
| 452 | '''moves GTK window to current virtual desktop if it is not in the |
|---|
| 453 | current virtual desktop |
|---|
| 454 | window is GTK window''' |
|---|
| 455 | if os.name == 'nt': |
|---|
| 456 | return False |
|---|
| 457 | |
|---|
| 458 | root_window = gtk.gdk.screen_get_default().get_root_window() |
|---|
| 459 | # current user's vd |
|---|
| 460 | current_virtual_desktop_no = get_current_desktop(root_window) |
|---|
| 461 | |
|---|
| 462 | # vd roster window is in |
|---|
| 463 | window_virtual_desktop = get_current_desktop(window.window) |
|---|
| 464 | |
|---|
| 465 | # if one of those is None, something went wrong and we cannot know |
|---|
| 466 | # VD info, just hide it (default action) and not show it afterwards |
|---|
| 467 | if None not in (window_virtual_desktop, current_virtual_desktop_no): |
|---|
| 468 | if current_virtual_desktop_no != window_virtual_desktop: |
|---|
| 469 | # we are in another VD that the window was |
|---|
| 470 | # so show it in current VD |
|---|
| 471 | window.present() |
|---|
| 472 | return True |
|---|
| 473 | return False |
|---|
| 474 | |
|---|
| 475 | def file_is_locked(path_to_file): |
|---|
| 476 | '''returns True if file is locked (WINDOWS ONLY)''' |
|---|
| 477 | if os.name != 'nt': # just in case |
|---|
| 478 | return |
|---|
| 479 | |
|---|
| 480 | if not HAS_PYWIN32: |
|---|
| 481 | return |
|---|
| 482 | |
|---|
| 483 | secur_att = pywintypes.SECURITY_ATTRIBUTES() |
|---|
| 484 | secur_att.Initialize() |
|---|
| 485 | |
|---|
| 486 | try: |
|---|
| 487 | # try make a handle for READING the file |
|---|
| 488 | hfile = win32file.CreateFile( |
|---|
| 489 | path_to_file, # path to file |
|---|
| 490 | win32con.GENERIC_READ, # open for reading |
|---|
| 491 | 0, # do not share with other proc |
|---|
| 492 | secur_att, |
|---|
| 493 | win32con.OPEN_EXISTING, # existing file only |
|---|
| 494 | win32con.FILE_ATTRIBUTE_NORMAL, # normal file |
|---|
| 495 | 0 # no attr. template |
|---|
| 496 | ) |
|---|
| 497 | except pywintypes.error, e: |
|---|
| 498 | return True |
|---|
| 499 | else: # in case all went ok, close file handle (go to hell WinAPI) |
|---|
| 500 | hfile.Close() |
|---|
| 501 | return False |
|---|
| 502 | |
|---|
| 503 | def _get_fade_color(treeview, selected, focused): |
|---|
| 504 | '''get a gdk color that is between foreground and background in 0.3 |
|---|
| 505 | 0.7 respectively colors of the cell for the given treeview''' |
|---|
| 506 | style = treeview.style |
|---|
| 507 | if selected: |
|---|
| 508 | if focused: # is the window focused? |
|---|
| 509 | state = gtk.STATE_SELECTED |
|---|
| 510 | else: # is it not? NOTE: many gtk themes change bg on this |
|---|
| 511 | state = gtk.STATE_ACTIVE |
|---|
| 512 | else: |
|---|
| 513 | state = gtk.STATE_NORMAL |
|---|
| 514 | bg = style.base[state] |
|---|
| 515 | fg = style.text[state] |
|---|
| 516 | |
|---|
| 517 | p = 0.3 # background |
|---|
| 518 | q = 0.7 # foreground # p + q should do 1.0 |
|---|
| 519 | return gtk.gdk.Color(int(bg.red*p + fg.red*q), |
|---|
| 520 | int(bg.green*p + fg.green*q), |
|---|
| 521 | int(bg.blue*p + fg.blue*q)) |
|---|
| 522 | |
|---|
| 523 | def get_scaled_pixbuf(pixbuf, kind): |
|---|
| 524 | '''returns scaled pixbuf, keeping ratio etc or None |
|---|
| 525 | kind is either "chat", "roster", "notification", "tooltip", "vcard"''' |
|---|
| 526 | |
|---|
| 527 | # resize to a width / height for the avatar not to have distortion |
|---|
| 528 | # (keep aspect ratio) |
|---|
| 529 | width = gajim.config.get(kind + '_avatar_width') |
|---|
| 530 | height = gajim.config.get(kind + '_avatar_height') |
|---|
| 531 | if width < 1 or height < 1: |
|---|
| 532 | return None |
|---|
| 533 | |
|---|
| 534 | # Pixbuf size |
|---|
| 535 | pix_width = pixbuf.get_width() |
|---|
| 536 | pix_height = pixbuf.get_height() |
|---|
| 537 | # don't make avatars bigger than they are |
|---|
| 538 | if pix_width < width and pix_height < height: |
|---|
| 539 | return pixbuf # we don't want to make avatar bigger |
|---|
| 540 | |
|---|
| 541 | ratio = float(pix_width) / float(pix_height) |
|---|
| 542 | if ratio > 1: |
|---|
| 543 | w = width |
|---|
| 544 | h = int(w / ratio) |
|---|
| 545 | else: |
|---|
| 546 | h = height |
|---|
| 547 | w = int(h * ratio) |
|---|
| 548 | scaled_buf = pixbuf.scale_simple(w, h, gtk.gdk.INTERP_HYPER) |
|---|
| 549 | return scaled_buf |
|---|
| 550 | |
|---|
| 551 | def get_avatar_pixbuf_from_cache(fjid, is_fake_jid = False, use_local = True): |
|---|
| 552 | '''checks if jid has cached avatar and if that avatar is valid image |
|---|
| 553 | (can be shown) |
|---|
| 554 | returns None if there is no image in vcard |
|---|
| 555 | returns 'ask' if cached vcard should not be used (user changed his vcard, |
|---|
| 556 | so we have new sha) or if we don't have the vcard''' |
|---|
| 557 | |
|---|
| 558 | jid, nick = gajim.get_room_and_nick_from_fjid(fjid) |
|---|
| 559 | if gajim.config.get('hide_avatar_of_transport') and\ |
|---|
| 560 | gajim.jid_is_transport(jid): |
|---|
| 561 | # don't show avatar for the transport itself |
|---|
| 562 | return None |
|---|
| 563 | |
|---|
| 564 | puny_jid = helpers.sanitize_filename(jid) |
|---|
| 565 | if is_fake_jid: |
|---|
| 566 | puny_nick = helpers.sanitize_filename(nick) |
|---|
| 567 | path = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick) |
|---|
| 568 | local_avatar_basepath = os.path.join(gajim.AVATAR_PATH, puny_jid, |
|---|
| 569 | puny_nick) + '_local' |
|---|
| 570 | else: |
|---|
| 571 | path = os.path.join(gajim.VCARD_PATH, puny_jid) |
|---|
| 572 | local_avatar_basepath = os.path.join(gajim.AVATAR_PATH, puny_jid) + \ |
|---|
| 573 | '_local' |
|---|
| 574 | if use_local: |
|---|
| 575 | for extension in ('.png', '.jpeg'): |
|---|
| 576 | local_avatar_path = local_avatar_basepath + extension |
|---|
| 577 | if os.path.isfile(local_avatar_path): |
|---|
| 578 | avatar_file = open(local_avatar_path, 'rb') |
|---|
| 579 | avatar_data = avatar_file.read() |
|---|
| 580 | avatar_file.close() |
|---|
| 581 | return get_pixbuf_from_data(avatar_data) |
|---|
| 582 | |
|---|
| 583 | if not os.path.isfile(path): |
|---|
| 584 | return 'ask' |
|---|
| 585 | |
|---|
| 586 | vcard_dict = gajim.connections.values()[0].get_cached_vcard(fjid, |
|---|
| 587 | is_fake_jid) |
|---|
| 588 | if not vcard_dict: # This can happen if cached vcard is too old |
|---|
| 589 | return 'ask' |
|---|
| 590 | if 'PHOTO' not in vcard_dict: |
|---|
| 591 | return None |
|---|
| 592 | pixbuf = vcard.get_avatar_pixbuf_encoded_mime(vcard_dict['PHOTO'])[0] |
|---|
| 593 | return pixbuf |
|---|
| 594 | |
|---|
| 595 | def make_gtk_month_python_month(month): |
|---|
| 596 | '''gtk start counting months from 0, so January is 0 |
|---|
| 597 | but python's time start from 1, so align to python |
|---|
| 598 | month MUST be integer''' |
|---|
| 599 | return month + 1 |
|---|
| 600 | |
|---|
| 601 | def make_python_month_gtk_month(month): |
|---|
| 602 | return month - 1 |
|---|
| 603 | |
|---|
| 604 | def make_color_string(color): |
|---|
| 605 | '''create #aabbcc color string from gtk color''' |
|---|
| 606 | col = '#' |
|---|
| 607 | for i in ('red', 'green', 'blue'): |
|---|
| 608 | h = hex(getattr(color, i) / (16*16)).split('x')[1] |
|---|
| 609 | if len(h) == 1: |
|---|
| 610 | h = '0' + h |
|---|
| 611 | col += h |
|---|
| 612 | return col |
|---|
| 613 | |
|---|
| 614 | def make_pixbuf_grayscale(pixbuf): |
|---|
| 615 | pixbuf2 = pixbuf.copy() |
|---|
| 616 | pixbuf.saturate_and_pixelate(pixbuf2, 0.0, False) |
|---|
| 617 | return pixbuf2 |
|---|
| 618 | |
|---|
| 619 | def get_path_to_generic_or_avatar(generic, jid = None, suffix = None): |
|---|
| 620 | '''Chooses between avatar image and default image. |
|---|
| 621 | Returns full path to the avatar image if it exists, |
|---|
| 622 | otherwise returns full path to the image. |
|---|
| 623 | generic must be with extension and suffix without''' |
|---|
| 624 | if jid: |
|---|
| 625 | # we want an avatar |
|---|
| 626 | puny_jid = helpers.sanitize_filename(jid) |
|---|
| 627 | path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid) + suffix |
|---|
| 628 | path_to_local_file = path_to_file + '_local' |
|---|
| 629 | for extension in ('.png', '.jpeg'): |
|---|
| 630 | path_to_local_file_full = path_to_local_file + extension |
|---|
| 631 | if os.path.exists(path_to_local_file_full): |
|---|
| 632 | return path_to_local_file_full |
|---|
| 633 | for extension in ('.png', '.jpeg'): |
|---|
| 634 | path_to_file_full = path_to_file + extension |
|---|
| 635 | if os.path.exists(path_to_file_full): |
|---|
| 636 | return path_to_file_full |
|---|
| 637 | return os.path.abspath(generic) |
|---|
| 638 | |
|---|
| 639 | def decode_filechooser_file_paths(file_paths): |
|---|
| 640 | '''decode as UTF-8 under Windows and |
|---|
| 641 | ask sys.getfilesystemencoding() in POSIX |
|---|
| 642 | file_paths MUST be LIST''' |
|---|
| 643 | file_paths_list = list() |
|---|
| 644 | |
|---|
| 645 | if os.name == 'nt': # decode as UTF-8 under Windows |
|---|
| 646 | for file_path in file_paths: |
|---|
| 647 | file_path = file_path.decode('utf8') |
|---|
| 648 | file_paths_list.append(file_path) |
|---|
| 649 | else: |
|---|
| 650 | for file_path in file_paths: |
|---|
| 651 | try: |
|---|
| 652 | file_path = file_path.decode(sys.getfilesystemencoding()) |
|---|
| 653 | except Exception: |
|---|
| 654 | try: |
|---|
| 655 | file_path = file_path.decode('utf-8') |
|---|
| 656 | except Exception: |
|---|
| 657 | pass |
|---|
| 658 | file_paths_list.append(file_path) |
|---|
| 659 | |
|---|
| 660 | return file_paths_list |
|---|
| 661 | |
|---|
| 662 | def possibly_set_gajim_as_xmpp_handler(): |
|---|
| 663 | '''registers (by default only the first time) xmmp: to Gajim.''' |
|---|
| 664 | path_to_dot_kde = os.path.expanduser('~/.kde') |
|---|
| 665 | if os.path.exists(path_to_dot_kde): |
|---|
| 666 | path_to_kde_file = os.path.join(path_to_dot_kde, |
|---|
| 667 | 'share/services/xmpp.protocol') |
|---|
| 668 | else: |
|---|
| 669 | path_to_kde_file = None |
|---|
| 670 | |
|---|
| 671 | def set_gajim_as_xmpp_handler(is_checked=None): |
|---|
| 672 | if is_checked is not None: |
|---|
| 673 | # come from confirmation dialog |
|---|
| 674 | gajim.config.set('check_if_gajim_is_default', is_checked) |
|---|
| 675 | path_to_gajim_script, typ = get_abspath_for_script('gajim-remote', True |
|---|