add msvcr90.dll to windows installer. Fixes #6754
[gajim.git] / src / gajim.py
blobe87349f9d32ef654627f7da2a1346760eb231e88
1 # -*- coding:utf-8 -*-
2 ## src/gajim.py
3 ##
4 ## Copyright (C) 2003-2010 Yann Leboulanger <asterix AT lagaule.org>
5 ## Copyright (C) 2004-2005 Vincent Hanquez <tab AT snarc.org>
6 ## Copyright (C) 2005 Alex Podaras <bigpod AT gmail.com>
7 ## Norman Rasmussen <norman AT rasmussen.co.za>
8 ## Stéphan Kochen <stephan AT kochen.nl>
9 ## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
10 ## Alex Mauer <hawke AT hawkesnest.net>
11 ## Copyright (C) 2005-2007 Travis Shirk <travis AT pobox.com>
12 ## Nikos Kouremenos <kourem AT gmail.com>
13 ## Copyright (C) 2006 Junglecow J <junglecow AT gmail.com>
14 ## Stefan Bethge <stefan AT lanpartei.de>
15 ## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
16 ## Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net>
17 ## James Newton <redshodan AT gmail.com>
18 ## Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com>
19 ## Julien Pivotto <roidelapluie AT gmail.com>
20 ## Stephan Erb <steve-e AT h3c.de>
21 ## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
23 ## This file is part of Gajim.
25 ## Gajim is free software; you can redistribute it and/or modify
26 ## it under the terms of the GNU General Public License as published
27 ## by the Free Software Foundation; version 3 only.
29 ## Gajim is distributed in the hope that it will be useful,
30 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
31 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
32 ## GNU General Public License for more details.
34 ## You should have received a copy of the GNU General Public License
35 ## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
38 from common import demandimport
39 demandimport.enable()
40 demandimport.ignore += ['gobject._gobject', 'libasyncns', 'i18n',
41 'logging.NullHandler', 'dbus.glib', 'dbus.service',
42 'command_system.implementation.standard', 'OpenSSL.SSL', 'OpenSSL.crypto',
43 'common.sleepy', 'DLFCN', 'dl', 'xml.sax', 'xml.sax.handler', 'ic']
45 import os
46 import sys
48 if os.name == 'nt':
49 import locale
50 import gettext
51 APP = 'gajim'
52 DIR = '../po'
53 lang, enc = locale.getdefaultlocale()
54 os.environ['LANG'] = lang
55 gettext.bindtextdomain(APP, DIR)
56 gettext.textdomain(APP)
57 gettext.install(APP, DIR, unicode=True)
59 locale.setlocale(locale.LC_ALL, '')
60 import ctypes
61 import ctypes.util
62 libintl_path = ctypes.util.find_library('intl')
63 if libintl_path == None:
64 local_intl = os.path.join('gtk', 'bin', 'intl.dll')
65 if os.path.exists(local_intl):
66 libintl_path = local_intl
67 if libintl_path == None:
68 raise ImportError('intl.dll library not found')
69 libintl = ctypes.cdll.LoadLibrary(libintl_path)
70 libintl.bindtextdomain(APP, DIR)
71 libintl.bind_textdomain_codeset(APP, 'UTF-8')
73 import warnings
75 if os.name == 'nt':
76 log_path = os.path.join(os.environ['APPDATA'], 'Gajim')
77 if not os.path.exists(log_path):
78 os.mkdir(log_path, 0700)
79 log_file = os.path.join(log_path, 'gajim.log')
80 fout = open(log_file, 'a')
81 sys.stdout = fout
82 sys.stderr = fout
84 warnings.filterwarnings(action='ignore')
86 if os.path.isdir('gtk'):
87 # Used to create windows installer with GTK included
88 paths = os.environ['PATH']
89 list_ = paths.split(';')
90 new_list = []
91 for p in list_:
92 if p.find('gtk') < 0 and p.find('GTK') < 0:
93 new_list.append(p)
94 new_list.insert(0, 'gtk/lib')
95 new_list.insert(0, 'gtk/bin')
96 os.environ['PATH'] = ';'.join(new_list)
97 os.environ['GTK_BASEPATH'] = 'gtk'
99 if os.name == 'nt':
100 # needed for docutils
101 sys.path.append('.')
103 from common import logging_helpers
104 logging_helpers.init('TERM' in os.environ)
106 import logging
107 # gajim.gui or gajim.gtk more appropriate ?
108 log = logging.getLogger('gajim.gajim')
110 import getopt
111 from common import i18n
113 def parseOpts():
114 profile_ = ''
115 config_path_ = None
117 try:
118 shortargs = 'hqvl:p:c:'
119 # add gtk/gnome session option as gtk_get_option_group is not wrapped
120 longargs = 'help quiet verbose loglevel= profile= config_path='
121 longargs += ' class= name= screen= gtk-module= sync g-fatal-warnings'
122 longargs += ' sm-client-id= sm-client-state-file= sm-disable'
123 opts = getopt.getopt(sys.argv[1:], shortargs, longargs.split())[0]
124 except getopt.error, msg1:
125 print msg1
126 print 'for help use --help'
127 sys.exit(2)
128 for o, a in opts:
129 if o in ('-h', '--help'):
130 print 'gajim [--help] [--quiet] [--verbose] ' + \
131 '[--loglevel subsystem=level[,subsystem=level[...]]] ' + \
132 '[--profile name] [--config-path]'
133 sys.exit()
134 elif o in ('-q', '--quiet'):
135 logging_helpers.set_quiet()
136 elif o in ('-v', '--verbose'):
137 logging_helpers.set_verbose()
138 elif o in ('-p', '--profile'): # gajim --profile name
139 profile_ = a
140 elif o in ('-l', '--loglevel'):
141 logging_helpers.set_loglevels(a)
142 elif o in ('-c', '--config-path'):
143 config_path_ = a
144 return profile_, config_path_
146 profile, config_path = parseOpts()
147 del parseOpts
149 import locale
150 profile = unicode(profile, locale.getpreferredencoding())
152 import common.configpaths
153 common.configpaths.gajimpaths.init(config_path)
154 del config_path
155 common.configpaths.gajimpaths.init_profile(profile)
156 del profile
158 if os.name == 'nt':
159 class MyStderr(object):
160 _file = None
161 _error = None
162 def write(self, text):
163 fname = os.path.join(common.configpaths.gajimpaths.cache_root,
164 os.path.split(sys.executable)[1]+'.log')
165 if self._file is None and self._error is None:
166 try:
167 self._file = open(fname, 'a')
168 except Exception, details:
169 self._error = details
170 if self._file is not None:
171 self._file.write(text)
172 self._file.flush()
173 def flush(self):
174 if self._file is not None:
175 self._file.flush()
177 sys.stderr = MyStderr()
179 # PyGTK2.10+ only throws a warning
180 warnings.filterwarnings('error', module='gtk')
181 try:
182 import gobject
183 import gtk
184 except Warning, msg2:
185 if str(msg2) == 'could not open display':
186 print >> sys.stderr, _('Gajim needs X server to run. Quiting...')
187 else:
188 print >> sys.stderr, _('importing PyGTK failed: %s') % str(msg2)
189 sys.exit()
190 warnings.resetwarnings()
192 gobject.set_prgname('gajim')
194 if os.name == 'nt':
195 warnings.filterwarnings(action='ignore')
197 pritext = ''
199 from common import exceptions
200 try:
201 from common import gajim
202 except exceptions.DatabaseMalformed:
203 pritext = _('Database Error')
204 sectext = _('The database file (%s) cannot be read. Try to repair it (see '
205 'http://trac.gajim.org/wiki/DatabaseBackup) or remove it (all history '
206 'will be lost).') % common.logger.LOG_DB_PATH
207 else:
208 from common import dbus_support
209 if dbus_support.supported:
210 from music_track_listener import MusicTrackListener
212 from ctypes import CDLL
213 from ctypes.util import find_library
214 import platform
216 sysname = platform.system()
217 if sysname in ('Linux', 'FreeBSD', 'OpenBSD', 'NetBSD'):
218 libc = CDLL(find_library('c'))
220 # The constant defined in <linux/prctl.h> which is used to set the name
221 # of the process.
222 PR_SET_NAME = 15
224 if sysname == 'Linux':
225 libc.prctl(PR_SET_NAME, 'gajim')
226 elif sysname in ('FreeBSD', 'OpenBSD', 'NetBSD'):
227 libc.setproctitle('gajim')
229 if gtk.pygtk_version < (2, 16, 0):
230 pritext = _('Gajim needs PyGTK 2.16 or above')
231 sectext = _('Gajim needs PyGTK 2.16 or above to run. Quiting...')
232 elif gtk.gtk_version < (2, 16, 0):
233 pritext = _('Gajim needs GTK 2.16 or above')
234 sectext = _('Gajim needs GTK 2.16 or above to run. Quiting...')
236 from common import check_paths
238 if os.name == 'nt':
239 try:
240 import winsound # windows-only built-in module for playing wav
241 import win32api # do NOT remove. we req this module
242 except Exception:
243 pritext = _('Gajim needs pywin32 to run')
244 sectext = _('Please make sure that Pywin32 is installed on your '
245 'system. You can get it at %s') % \
246 'http://sourceforge.net/project/showfiles.php?group_id=78018'
248 if pritext:
249 dlg = gtk.MessageDialog(None,
250 gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL,
251 gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, message_format = pritext)
253 dlg.format_secondary_text(sectext)
254 dlg.run()
255 dlg.destroy()
256 sys.exit()
258 del pritext
260 import gtkexcepthook
262 import signal
263 import gtkgui_helpers
265 gajimpaths = common.configpaths.gajimpaths
267 pid_filename = gajimpaths['PID_FILE']
268 config_filename = gajimpaths['CONFIG_FILE']
270 import traceback
271 import errno
272 import dialogs
274 def pid_alive():
275 try:
276 pf = open(pid_filename)
277 except IOError:
278 # probably file not found
279 return False
281 try:
282 pid = int(pf.read().strip())
283 pf.close()
284 except Exception:
285 traceback.print_exc()
286 # PID file exists, but something happened trying to read PID
287 # Could be 0.10 style empty PID file, so assume Gajim is running
288 return True
290 if os.name == 'nt':
291 try:
292 from ctypes import (windll, c_ulong, c_int, Structure, c_char)
293 from ctypes import (POINTER, pointer, sizeof)
294 except Exception:
295 return True
297 class PROCESSENTRY32(Structure):
298 _fields_ = [
299 ('dwSize', c_ulong, ),
300 ('cntUsage', c_ulong, ),
301 ('th32ProcessID', c_ulong, ),
302 ('th32DefaultHeapID', c_ulong, ),
303 ('th32ModuleID', c_ulong, ),
304 ('cntThreads', c_ulong, ),
305 ('th32ParentProcessID', c_ulong, ),
306 ('pcPriClassBase', c_ulong, ),
307 ('dwFlags', c_ulong, ),
308 ('szExeFile', c_char*512, ),
311 kernel = windll.kernel32
312 kernel.CreateToolhelp32Snapshot.argtypes = c_ulong, c_ulong,
313 kernel.CreateToolhelp32Snapshot.restype = c_int
314 kernel.Process32First.argtypes = c_int, POINTER(PROCESSENTRY32),
315 kernel.Process32First.restype = c_int
316 kernel.Process32Next.argtypes = c_int, POINTER(PROCESSENTRY32),
317 kernel.Process32Next.restype = c_int
319 def get_p(pid_):
320 TH32CS_SNAPPROCESS = 2
321 CreateToolhelp32Snapshot = kernel.CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)
322 assert CreateToolhelp32Snapshot > 0, 'CreateToolhelp32Snapshot failed'
323 pe32 = PROCESSENTRY32()
324 pe32.dwSize = sizeof( PROCESSENTRY32 )
325 f3 = kernel.Process32First(CreateToolhelp32Snapshot, pointer(pe32))
326 while f3:
327 if pe32.th32ProcessID == pid_:
328 return pe32.szExeFile
329 f3 = kernel.Process32Next(CreateToolhelp32Snapshot, pointer(pe32))
331 if get_p(pid) in ('python.exe', 'gajim.exe'):
332 return True
333 return False
334 try:
335 if not os.path.exists('/proc'):
336 return True # no /proc, assume Gajim is running
338 try:
339 f1 = open('/proc/%d/cmdline'% pid)
340 except IOError, e1:
341 if e1.errno == errno.ENOENT:
342 return False # file/pid does not exist
343 raise
345 n = f1.read().lower()
346 f1.close()
347 if n.find('gajim') < 0:
348 return False
349 return True # Running Gajim found at pid
350 except Exception:
351 traceback.print_exc()
353 # If we are here, pidfile exists, but some unexpected error occured.
354 # Assume Gajim is running.
355 return True
357 if pid_alive():
358 pix = gtkgui_helpers.get_icon_pixmap('gajim', 48)
359 gtk.window_set_default_icon(pix) # set the icon to all newly opened wind
360 pritext = _('Gajim is already running')
361 sectext = _('Another instance of Gajim seems to be running\nRun anyway?')
362 dialog = dialogs.YesNoDialog(pritext, sectext)
363 dialog.popup()
364 if dialog.run() != gtk.RESPONSE_YES:
365 sys.exit(3)
366 dialog.destroy()
367 # run anyway, delete pid and useless global vars
368 if os.path.exists(pid_filename):
369 os.remove(pid_filename)
370 del pix
371 del pritext
372 del sectext
373 dialog.destroy()
375 # Create .gajim dir
376 pid_dir = os.path.dirname(pid_filename)
377 if not os.path.exists(pid_dir):
378 check_paths.create_path(pid_dir)
379 # Create pid file
380 try:
381 f2 = open(pid_filename, 'w')
382 f2.write(str(os.getpid()))
383 f2.close()
384 except IOError, e2:
385 dlg = dialogs.ErrorDialog(_('Disk Write Error'), str(e2))
386 dlg.run()
387 dlg.destroy()
388 sys.exit()
389 del pid_dir
391 def on_exit():
392 # delete pid file on normal exit
393 if os.path.exists(pid_filename):
394 os.remove(pid_filename)
395 # Shutdown GUI and save config
396 if hasattr(gajim.interface, 'roster') and gajim.interface.roster:
397 gajim.interface.roster.prepare_quit()
399 import atexit
400 atexit.register(on_exit)
402 from gui_interface import Interface
404 if __name__ == '__main__':
405 def sigint_cb(num, stack):
406 sys.exit(5)
407 # ^C exits the application normally to delete pid file
408 signal.signal(signal.SIGINT, sigint_cb)
410 log.info("Encodings: d:%s, fs:%s, p:%s", sys.getdefaultencoding(), \
411 sys.getfilesystemencoding(), locale.getpreferredencoding())
413 if os.name != 'nt':
414 # Session Management support
415 try:
416 import gnome.ui
417 raise ImportError
418 except ImportError:
419 pass
420 else:
421 def die_cb(dummy):
422 gajim.interface.roster.quit_gtkgui_interface()
423 gnome.program_init('gajim', gajim.version)
424 cli = gnome.ui.master_client()
425 cli.connect('die', die_cb)
427 path_to_gajim_script = gtkgui_helpers.get_abspath_for_script(
428 'gajim')
430 if path_to_gajim_script:
431 argv = [path_to_gajim_script]
432 try:
433 cli.set_restart_command(argv)
434 except TypeError:
435 # Fedora systems have a broken gnome-python wrapper for this
436 # function.
437 cli.set_restart_command(len(argv), argv)
439 check_paths.check_and_possibly_create_paths()
441 interface = Interface()
442 interface.run()
444 try:
445 if os.name != 'nt':
446 # This makes Gajim unusable under windows, and threads are used only
447 # for GPG, so not under windows
448 gtk.gdk.threads_init()
449 gtk.main()
450 except KeyboardInterrupt:
451 print >> sys.stderr, 'KeyboardInterrupt'