diff --git a/src/application.py b/src/application.py new file mode 100644 index 000000000..721a75bc4 --- /dev/null +++ b/src/application.py @@ -0,0 +1,237 @@ +# -*- coding:utf-8 -*- +## src/application.py +## +## Copyright (C) 2016 Emmanuel Gil Peyrot +## +## This file is part of Gajim. +## +## Gajim is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 3 only. +## +## Gajim is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with Gajim. If not, see . +## + +import sys +import os +import logging +import signal +import locale +from gi.repository import GLib, Gio, Gtk +from common import i18n +from common import logging_helpers +from common import crypto +try: + PYOPENSSL_PRNG_PRESENT = True + import OpenSSL.rand +except ImportError: + print('PyOpenSSL not available, impossible to generate entropy', file=sys.stderr) + PYOPENSSL_PRNG_PRESENT = False + +logging_helpers.init(sys.stderr.isatty()) +log = logging.getLogger('gajim.gajim') + + +class GajimApplication(Gtk.Application): + '''Main class handling activation and command line.''' + + def __init__(self): + Gtk.Application.__init__(self, application_id='org.gajim.Gajim', + flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE) + self.add_main_option('version', ord('V'), GLib.OptionFlags.NONE, + GLib.OptionArg.NONE, + _('Show the application\'s version')) + self.add_main_option('quiet', ord('q'), GLib.OptionFlags.NONE, + GLib.OptionArg.NONE, + _('Show only critical errors')) + self.add_main_option('separate', ord('s'), GLib.OptionFlags.NONE, + GLib.OptionArg.NONE, + _('Separate profile files completely (even ' + 'history db and plugins)')) + self.add_main_option('verbose', ord('v'), GLib.OptionFlags.NONE, + GLib.OptionArg.NONE, + _('Print xml stanzas and other debug ' + 'information')) + self.add_main_option('windev', ord('w'), GLib.OptionFlags.NONE, + GLib.OptionArg.NONE, + _('Print stdout/stderr to the console ' + 'on Windows')) + self.add_main_option('profile', ord('p'), GLib.OptionFlags.NONE, + GLib.OptionArg.STRING, + _('Use defined profile in configuration ' + 'directory'), 'NAME') + self.add_main_option('config-path', ord('c'), GLib.OptionFlags.NONE, + GLib.OptionArg.STRING, + _('Set configuration directory'), 'PATH') + self.add_main_option('loglevel', ord('l'), GLib.OptionFlags.NONE, + GLib.OptionArg.STRING, + _('Configure logging system'), 'LEVEL') + + self.profile = '' + self.config_path = None + self.profile_separation = False + self.interface = None + self.rng_seed = None + + GLib.set_prgname('gajim') + GLib.set_application_name('Gajim') + + def do_startup(self): + Gtk.Application.do_startup(self) + + def do_activate(self): + # If a second instance starts do_activate() is called + # We bringt the Roster window to the front, GTK exits afterwards. + if self.interface: + self.interface.roster.window.present() + return + + Gtk.Application.do_activate(self) + + # Create and initialize Application Paths & Databases + import common.configpaths + common.configpaths.gajimpaths.init( + self.config_path, self.profile, self.profile_separation) + from common import gajim + from common import check_paths + from common import exceptions + from common import logger + from common import caps_cache + try: + gajim.logger = logger.Logger() + caps_cache.initialize(gajim.logger) + check_paths.check_and_possibly_create_paths() + except exceptions.DatabaseMalformed: + dlg = Gtk.MessageDialog( + None, + Gtk.DialogFlags.DESTROY_WITH_PARENT | Gtk.DialogFlags.MODAL, + Gtk.MessageType.ERROR, + Gtk.ButtonsType.OK, + _('Database Error')) + dlg.format_secondary_text( + _('The database file (%s) cannot be read. Try to repair it ' + '(see http://trac.gajim.org/wiki/DatabaseBackup) or remove it ' + '(all history will be lost).') % gajim.gajimpaths['LOG_DB']) + dlg.run() + dlg.destroy() + sys.exit() + + if os.name == 'nt': + import gettext + # needed for docutils + sys.path.append('.') + APP = 'gajim' + DIR = '../po' + lang, enc = locale.getdefaultlocale() + os.environ['LANG'] = lang + gettext.bindtextdomain(APP, DIR) + gettext.textdomain(APP) + gettext.install(APP, DIR) + + # This is for Windows translation which is currently not + # working on GTK 3.18.9 + # locale.setlocale(locale.LC_ALL, '') + # import ctypes + # import ctypes.util + # libintl_path = ctypes.util.find_library('intl') + # if libintl_path == None: + # local_intl = os.path.join('gtk', 'bin', 'intl.dll') + # if os.path.exists(local_intl): + # libintl_path = local_intl + # if libintl_path == None: + # raise ImportError('intl.dll library not found') + # libintl = ctypes.cdll.LoadLibrary(libintl_path) + # libintl.bindtextdomain(APP, DIR) + # libintl.bind_textdomain_codeset(APP, 'UTF-8') + # plugins_locale_dir = os.path.join(common.configpaths.gajimpaths[ + # 'PLUGINS_USER'], 'locale').encode(locale.getpreferredencoding()) + # libintl.bindtextdomain('gajim_plugins', plugins_locale_dir) + # libintl.bind_textdomain_codeset('gajim_plugins', 'UTF-8') + + if Gtk.Widget.get_default_direction() == Gtk.TextDirection.RTL: + i18n.direction_mark = '\u200F' + + from ctypes import CDLL + from ctypes.util import find_library + import platform + + sysname = platform.system() + if sysname in ('Linux', 'FreeBSD', 'OpenBSD', 'NetBSD'): + libc = CDLL(find_library('c')) + + # The constant defined in which is used to set the name + # of the process. + PR_SET_NAME = 15 + + if sysname == 'Linux': + libc.prctl(PR_SET_NAME, 'gajim') + elif sysname in ('FreeBSD', 'OpenBSD', 'NetBSD'): + libc.setproctitle('gajim') + + # Seed the OpenSSL pseudo random number generator from file and initialize + if PYOPENSSL_PRNG_PRESENT: + self.rng_seed = gajim.gajimpaths['RNG_SEED'] + # Seed from file + try: + OpenSSL.rand.load_file(self.rng_seed) + except TypeError: + OpenSSL.rand.load_file(self.rng_seed.encode('utf-8')) + crypto.add_entropy_sources_OpenSSL() + try: + OpenSSL.rand.write_file(self.rng_seed) + except TypeError: + OpenSSL.rand.write_file(self.rng_seed.encode('utf-8')) + + def sigint_cb(num, stack): + print('SIGINT/SIGTERM received') + self.quit() + # ^C exits the application normally + signal.signal(signal.SIGINT, sigint_cb) + signal.signal(signal.SIGTERM, sigint_cb) + + log.info("Encodings: d:%s, fs:%s, p:%s", sys.getdefaultencoding(), + sys.getfilesystemencoding(), locale.getpreferredencoding()) + + from gui_interface import Interface + self.interface = Interface() + self.interface.run(self) + + def do_shutdown(self, *args): + Gtk.Application.do_shutdown(self) + # Save the entropy from OpenSSL PRNG + if PYOPENSSL_PRNG_PRESENT and self.rng_seed: + try: + OpenSSL.rand.write_file(self.rng_seed) + except TypeError: + OpenSSL.rand.write_file(self.rng_seed.encode('utf-8')) + # Shutdown GUI and save config + if hasattr(self.interface, 'roster') and self.interface.roster: + self.interface.roster.prepare_quit() + + def do_command_line(self, command_line: Gio.ApplicationCommandLine) -> int: + Gtk.Application.do_command_line(self, command_line) + options = command_line.get_options_dict() + if options.contains('quiet'): + logging_helpers.set_quiet() + if options.contains('separate'): + self.profile_separation = True + if options.contains('verbose'): + logging_helpers.set_verbose() + if options.contains('profile'): + variant = options.lookup_value('profile') + self.profile = variant.get_string() + if options.contains('loglevel'): + variant = options.lookup_value('loglevel') + string = variant.get_string() + logging_helpers.set_loglevels(string) + if options.contains('config-path'): + variant = options.lookup_value('config-path') + self.config_path = variant.get_string() + self.activate() + return 0 diff --git a/src/common/configpaths.py b/src/common/configpaths.py index fa775d5cd..cf4792c24 100644 --- a/src/common/configpaths.py +++ b/src/common/configpaths.py @@ -180,7 +180,6 @@ class ConfigPaths: def init_profile(self, profile): conffile = windowsify('config') - pidfile = windowsify('gajim') secretsfile = windowsify('secrets') pluginsconfdir = windowsify('pluginsconfig') certsdir = windowsify(u'certs') @@ -188,16 +187,13 @@ class ConfigPaths: if len(profile) > 0: conffile += '.' + profile - pidfile += '.' + profile secretsfile += '.' + profile pluginsconfdir += '.' + profile certsdir += u'.' + profile localcertsdir += u'.' + profile - pidfile += '.pid' self.add('SECRETS_FILE', TYPE_DATA, secretsfile) self.add('MY_PEER_CERTS', TYPE_DATA, certsdir) - self.add('PID_FILE', TYPE_CACHE, pidfile) self.add('CONFIG_FILE', TYPE_CONFIG, conffile) self.add('PLUGINS_CONFIG_DIR', TYPE_CONFIG, pluginsconfdir) self.add('MY_CERT', TYPE_CONFIG, localcertsdir) diff --git a/src/common/helpers.py b/src/common/helpers.py index b4923080f..3c1cc96c2 100644 --- a/src/common/helpers.py +++ b/src/common/helpers.py @@ -52,10 +52,16 @@ from string import Template from common.i18n import Q_ from common.i18n import ngettext +if os.name == 'nt': + try: + HAS_WINSOUND = True + import winsound # windows-only built-in module for playing wav + except ImportError: + HAS_WINSOUND = False + print('Gajim is not able to playback sound because' + 'pywin32 is missing', file=sys.stderr) + try: - import winsound # windows-only built-in module for playing wav - import win32api - import win32con import wave # posix-only fallback wav playback import ossaudiodev as oss except Exception: @@ -462,52 +468,6 @@ def get_output_of_command(command): return output -def get_windows_reg_env(varname, default=''): - r""" - Ask for paths commonly used but not exposed as ENVs in english Windows 2003 - those are: - 'AppData' = %USERPROFILE%\Application Data (also an ENV) - 'Desktop' = %USERPROFILE%\Desktop - 'Favorites' = %USERPROFILE%\Favorites - 'NetHood' = %USERPROFILE%\ NetHood - 'Personal' = D:\My Documents (PATH TO MY DOCUMENTS) - 'PrintHood' = %USERPROFILE%\PrintHood - 'Programs' = %USERPROFILE%\Start Menu\Programs - 'Recent' = %USERPROFILE%\Recent - 'SendTo' = %USERPROFILE%\SendTo - 'Start Menu' = %USERPROFILE%\Start Menu - 'Startup' = %USERPROFILE%\Start Menu\Programs\Startup - 'Templates' = %USERPROFILE%\Templates - 'My Pictures' = D:\My Documents\My Pictures - 'Local Settings' = %USERPROFILE%\Local Settings - 'Local AppData' = %USERPROFILE%\Local Settings\Application Data - 'Cache' = %USERPROFILE%\Local Settings\Temporary Internet Files - 'Cookies' = %USERPROFILE%\Cookies - 'History' = %USERPROFILE%\Local Settings\History - """ - if os.name != 'nt': - return '' - - val = default - try: - rkey = win32api.RegOpenKey(win32con.HKEY_CURRENT_USER, -r'Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders') - try: - val = str(win32api.RegQueryValueEx(rkey, varname)[0]) - val = win32api.ExpandEnvironmentStrings(val) # expand using environ - except Exception: - pass - finally: - win32api.RegCloseKey(rkey) - return val - -def get_documents_path(): - if os.name == 'nt': - path = get_windows_reg_env('Personal') - else: - path = os.path.expanduser('~') - return path - def sanitize_filename(filename): """ Make sure the filename we will write does contain only acceptable and latin @@ -787,12 +747,12 @@ def play_sound_file(path_to_soundfile): path_to_soundfile = check_soundfile_path(path_to_soundfile) if path_to_soundfile is None: return - elif os.name == 'nt': + elif os.name == 'nt' and HAS_WINSOUND: try: winsound.PlaySound(path_to_soundfile, winsound.SND_FILENAME|winsound.SND_ASYNC) except Exception: - pass + log.exception('Sound Playback Error') elif os.name == 'posix': if gajim.config.get('soundplayer') == '': def _oss_play(): diff --git a/src/common/logger.py b/src/common/logger.py index f4f035dd2..204734ea3 100644 --- a/src/common/logger.py +++ b/src/common/logger.py @@ -43,10 +43,9 @@ from common import ged import sqlite3 as sqlite -from common import configpaths -LOG_DB_PATH = configpaths.gajimpaths['LOG_DB'] +LOG_DB_PATH = gajim.gajimpaths['LOG_DB'] LOG_DB_FOLDER, LOG_DB_FILE = os.path.split(LOG_DB_PATH) -CACHE_DB_PATH = configpaths.gajimpaths['CACHE_DB'] +CACHE_DB_PATH = gajim.gajimpaths['CACHE_DB'] import logging log = logging.getLogger('gajim.c.logger') diff --git a/src/dialogs.py b/src/dialogs.py index fcb3202d7..c358cc12a 100644 --- a/src/dialogs.py +++ b/src/dialogs.py @@ -1495,7 +1495,7 @@ class FileChooserDialog(Gtk.FileChooserDialog): if current_folder and os.path.isdir(current_folder): self.set_current_folder(current_folder) else: - self.set_current_folder(helpers.get_documents_path()) + self.set_current_folder(os.path.expanduser('~')) self.response_ok, self.response_cancel = \ on_response_ok, on_response_cancel # in gtk+-2.10 clicked signal on some of the buttons in a dialog @@ -5047,7 +5047,7 @@ class ArchiveChooserDialog(FileChooserDialog): callback(path_to_file) self.destroy() - path = helpers.get_documents_path() + path = os.path.expanduser('~') FileChooserDialog.__init__(self, title_text=_('Choose Archive'), diff --git a/src/gajim.py b/src/gajim.py index ac6e52f47..a627cb98a 100644 --- a/src/gajim.py +++ b/src/gajim.py @@ -35,24 +35,64 @@ ## along with Gajim. If not, see . ## -import os import sys -import warnings -import OpenSSL +import os -if os.name == 'nt': +if '--version' in sys.argv or '-V' in sys.argv: + from common.defs import version + print(version) + sys.exit(0) + +WINDEV = False +if '--windev' in sys.argv or '-w' in sys.argv: + WINDEV = True + +if os.name == 'nt' and not WINDEV: + import warnings log_path = os.path.join(os.environ['APPDATA'], 'Gajim') if not os.path.exists(log_path): os.mkdir(log_path, 0o700) log_file = os.path.join(log_path, 'gajim.log') - fout = open(log_file, 'a') - sys.stdout = fout - sys.stderr = fout + class MyStd(object): + _file = None + _error = None + + def write(self, text): + if self._file is None and self._error is None: + try: + self._file = open(log_file, 'a') + except Exception as details: + self._error = details + if self._file is not None: + self._file.write(text) + self._file.flush() + + def flush(self): + if self._file is not None: + self._file.flush() + + def isatty(self): + return False + + outerr = MyStd() + sys.stdout = outerr + sys.stderr = outerr warnings.filterwarnings(action='ignore') + +# Test here for all required versions so we dont have to +# test multiple times in every module. nbxmpp also needs GLib. +import gi +gi.require_version('GLib', '2.0') +gi.require_version('Gio', '2.0') +gi.require_version('Gtk', '3.0') +gi.require_version('Gdk', '3.0') +gi.require_version('GObject', '2.0') +gi.require_version('Pango', '1.0') + MIN_NBXMPP_VER = "0.5.3" -from gi.repository import GLib + try: import nbxmpp except ImportError: @@ -64,438 +104,7 @@ if V(nbxmpp.__version__) < V(MIN_NBXMPP_VER): print('Gajim needs python-nbxmpp >= %s to run. Quiting...' % MIN_NBXMPP_VER) sys.exit(1) -if os.name == 'nt': - import locale - import gettext - APP = 'gajim' - DIR = '../po' - lang, enc = locale.getdefaultlocale() - os.environ['LANG'] = lang - gettext.bindtextdomain(APP, DIR) - gettext.textdomain(APP) - gettext.install(APP, DIR) +from application import GajimApplication -# locale.setlocale(locale.LC_ALL, '') -# import ctypes -# import ctypes.util -# libintl_path = ctypes.util.find_library('intl') -# if libintl_path == None: -# local_intl = os.path.join('gtk', 'bin', 'intl.dll') -# if os.path.exists(local_intl): -# libintl_path = local_intl -# if libintl_path == None: -# raise ImportError('intl.dll library not found') -# libintl = ctypes.cdll.LoadLibrary(libintl_path) -# libintl.bindtextdomain(APP, DIR) -# libintl.bind_textdomain_codeset(APP, 'UTF-8') - -if os.name == 'nt': - # needed for docutils - sys.path.append('.') - -from common import logging_helpers -logging_helpers.init(sys.stderr.isatty()) - -import logging -# gajim.gui or gajim.gtk more appropriate ? -log = logging.getLogger('gajim.gajim') - -import gi -gi.require_version('Gtk', '3.0') -gi.require_version('Gdk', '3.0') -gi.require_version('GObject', '2.0') -gi.require_version('Pango', '1.0') - -import getopt -from common import i18n - -def parseOpts(): - profile_ = '' - config_path_ = None - profile_separation_ = False - - try: - shortargs = 'hqsvl:p:c:' - # add gtk/gnome session option as gtk_get_option_group is not wrapped - longargs = 'help quiet separate verbose loglevel= profile= config-path=' - longargs += ' class= name= screen= gtk-module= sync g-fatal-warnings' - longargs += ' sm-client-id= sm-client-state-file= sm-disable' - opts = getopt.getopt(sys.argv[1:], shortargs, longargs.split())[0] - except getopt.error as msg1: - print(str(msg1)) - print('for help use --help') - sys.exit(2) - for o, a in opts: - if o in ('-h', '--help'): - out = _('Usage:') + \ - '\n gajim [options] filename\n\n' + \ - _('Options:') + \ - '\n -h, --help ' + \ - _('Show this help message and exit') + \ - '\n -q, --quiet ' + \ - _('Show only critical errors') + \ - '\n -s, --separate ' + \ - _('Separate profile files completely (even history db and plugins)') + \ - '\n -v, --verbose ' + \ - _('Print xml stanzas and other debug information') + \ - '\n -p, --profile ' + \ - _('Use defined profile in configuration directory') + \ - '\n -c, --config-path ' + \ - _('Set configuration directory') + \ - '\n -l, --loglevel ' + \ - _('Configure logging system') + '\n' - print(out) - sys.exit() - elif o in ('-q', '--quiet'): - logging_helpers.set_quiet() - elif o in ('-s', '--separate'): - profile_separation_ = True - elif o in ('-v', '--verbose'): - logging_helpers.set_verbose() - elif o in ('-p', '--profile'): # gajim --profile name - profile_ = a - elif o in ('-l', '--loglevel'): - logging_helpers.set_loglevels(a) - elif o in ('-c', '--config-path'): - config_path_ = a - return profile_, config_path_, profile_separation_ - -import locale -profile, config_path, profile_separation = parseOpts() -del parseOpts - -import common.configpaths -common.configpaths.gajimpaths.init(config_path, profile, profile_separation) -del config_path -del profile - -if os.name == 'nt': - plugins_locale_dir = os.path.join(common.configpaths.gajimpaths[ - 'PLUGINS_USER'], 'locale').encode(locale.getpreferredencoding()) -# libintl.bindtextdomain('gajim_plugins', plugins_locale_dir) -# libintl.bind_textdomain_codeset('gajim_plugins', 'UTF-8') - - class MyStderr(object): - _file = None - _error = None - def write(self, text): - fname = os.path.join(common.configpaths.gajimpaths.cache_root, - os.path.split(sys.executable)[1]+'.log') - if self._file is None and self._error is None: - try: - self._file = open(fname, 'a') - except Exception as details: - self._error = details - if self._file is not None: - self._file.write(text) - self._file.flush() - def flush(self): - if self._file is not None: - self._file.flush() - - sys.stderr = MyStderr() - -# PyGTK2.10+ only throws a warning -warnings.filterwarnings('error', module='Gtk') -try: - from gi.repository import GObject - GObject.set_prgname('gajim') - from gi.repository import Gtk - from gi.repository import Gdk - from gi.repository import GLib -except Warning as msg2: - if str(msg2) == 'could not open display': - print(_('Gajim needs X server to run. Quiting...'), file=sys.stderr) - else: - print(_('importing PyGTK failed: %s') % str(msg2), file=sys.stderr) - sys.exit() -warnings.resetwarnings() - - -if os.name == 'nt': - warnings.filterwarnings(action='ignore') - -if Gtk.Widget.get_default_direction() == Gtk.TextDirection.RTL: - i18n.direction_mark = '\u200F' -pritext = '' - -from common import exceptions -try: - from common import gajim -except exceptions.DatabaseMalformed: - pritext = _('Database Error') - sectext = _('The database file (%s) cannot be read. Try to repair it (see ' - 'http://trac.gajim.org/wiki/DatabaseBackup) or remove it (all history ' - 'will be lost).') % common.logger.LOG_DB_PATH -else: - from common import logger - gajim.logger = logger.Logger() - from common import caps_cache - caps_cache.initialize(gajim.logger) - from common import dbus_support - if dbus_support.supported: - from music_track_listener import MusicTrackListener - - from ctypes import CDLL - from ctypes.util import find_library - import platform - - sysname = platform.system() - if sysname in ('Linux', 'FreeBSD', 'OpenBSD', 'NetBSD'): - libc = CDLL(find_library('c')) - - # The constant defined in which is used to set the name - # of the process. - PR_SET_NAME = 15 - - if sysname == 'Linux': - libc.prctl(PR_SET_NAME, 'gajim') - elif sysname in ('FreeBSD', 'OpenBSD', 'NetBSD'): - libc.setproctitle('gajim') - -# if Gtk.pygtk_version < (2, 22, 0): -# pritext = _('Gajim needs PyGTK 2.22 or above') -# sectext = _('Gajim needs PyGTK 2.22 or above to run. Quiting...') -# elif Gtk.gtk_version < (2, 22, 0): -# if (Gtk.get_major_version(), Gtk.get_minor_version(), -# Gtk.get_micro_version()) < (2, 22, 0): -# pritext = _('Gajim needs GTK 2.22 or above') -# sectext = _('Gajim needs GTK 2.22 or above to run. Quiting...') - - from common import check_paths - - if os.name == 'nt': - try: - import winsound # windows-only built-in module for playing wav - import win32api # do NOT remove. we req this module - except Exception: - pritext = _('Gajim needs pywin32 to run') - sectext = _('Please make sure that Pywin32 is installed on your ' - 'system. You can get it at %s') % \ - 'http://sourceforge.net/project/showfiles.php?group_id=78018' - -if pritext: - dlg = Gtk.MessageDialog(None, - Gtk.DialogFlags.DESTROY_WITH_PARENT | Gtk.DialogFlags.MODAL, - Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, message_format = pritext) - - dlg.format_secondary_text(sectext) - dlg.run() - dlg.destroy() - sys.exit() - -del pritext - -#import gtkexcepthook - -import signal -import gtkgui_helpers - -gajimpaths = common.configpaths.gajimpaths - -pid_filename = gajimpaths['PID_FILE'] -config_filename = gajimpaths['CONFIG_FILE'] - -# Seed the OpenSSL pseudo random number generator from file and initialize -RNG_SEED = gajimpaths['RNG_SEED'] -PYOPENSSL_PRNG_PRESENT = False -try: - import OpenSSL.rand - from common import crypto - PYOPENSSL_PRNG_PRESENT = True - # Seed from file - try: - OpenSSL.rand.load_file(RNG_SEED) - except TypeError: - OpenSSL.rand.load_file(RNG_SEED.encode('utf-8')) - crypto.add_entropy_sources_OpenSSL() - try: - OpenSSL.rand.write_file(RNG_SEED) - except TypeError: - OpenSSL.rand.write_file(RNG_SEED.encode('utf-8')) -except ImportError: - log.info("PyOpenSSL PRNG not available") - -import traceback -import errno -import dialogs - -def pid_alive(): - try: - pf = open(pid_filename) - except IOError: - # probably file not found - return False - - try: - pid = int(pf.read().strip()) - pf.close() - except Exception: - traceback.print_exc() - # PID file exists, but something happened trying to read PID - # Could be 0.10 style empty PID file, so assume Gajim is running - return True - - if os.name == 'nt': - try: - from ctypes import (windll, c_ulong, c_int, Structure, c_char) - from ctypes import (POINTER, pointer, sizeof) - except Exception: - return True - - class PROCESSENTRY32(Structure): - _fields_ = [ - ('dwSize', c_ulong, ), - ('cntUsage', c_ulong, ), - ('th32ProcessID', c_ulong, ), - ('th32DefaultHeapID', c_ulong, ), - ('th32ModuleID', c_ulong, ), - ('cntThreads', c_ulong, ), - ('th32ParentProcessID', c_ulong, ), - ('pcPriClassBase', c_ulong, ), - ('dwFlags', c_ulong, ), - ('szExeFile', c_char*512, ), - ] - - kernel = windll.kernel32 - kernel.CreateToolhelp32Snapshot.argtypes = c_ulong, c_ulong, - kernel.CreateToolhelp32Snapshot.restype = c_int - kernel.Process32First.argtypes = c_int, POINTER(PROCESSENTRY32), - kernel.Process32First.restype = c_int - kernel.Process32Next.argtypes = c_int, POINTER(PROCESSENTRY32), - kernel.Process32Next.restype = c_int - - def get_p(pid_): - TH32CS_SNAPPROCESS = 2 - CreateToolhelp32Snapshot = kernel.CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) - assert CreateToolhelp32Snapshot > 0, 'CreateToolhelp32Snapshot failed' - pe32 = PROCESSENTRY32() - pe32.dwSize = sizeof( PROCESSENTRY32 ) - f3 = kernel.Process32First(CreateToolhelp32Snapshot, pointer(pe32)) - while f3: - if pe32.th32ProcessID == pid_: - return pe32.szExeFile - f3 = kernel.Process32Next(CreateToolhelp32Snapshot, pointer(pe32)) - - if get_p(pid) in ('python.exe', 'gajim.exe'): - return True - return False - try: - if not os.path.exists('/proc'): - return True # no /proc, assume Gajim is running - - try: - f1 = open('/proc/%d/cmdline'% pid) - except IOError as e1: - if e1.errno == errno.ENOENT: - return False # file/pid does not exist - raise - - n = f1.read().lower() - f1.close() - if n.find('gajim') < 0: - return False - return True # Running Gajim found at pid - except Exception: - traceback.print_exc() - - # If we are here, pidfile exists, but some unexpected error occured. - # Assume Gajim is running. - return True - -def show_remote_gajim_roster(): - try: - import dbus - - OBJ_PATH = '/org/gajim/dbus/RemoteObject' - INTERFACE = 'org.gajim.dbus.RemoteInterface' - SERVICE = 'org.gajim.dbus' - - # Attempt to call show_roster - dbus.Interface(dbus.SessionBus().get_object(SERVICE, OBJ_PATH), INTERFACE).__getattr__("show_roster")() - - return True - except Exception: - return False - -if pid_alive(): - if (show_remote_gajim_roster()): - print("Gajim is already running, bringing the roster to front...") - sys.exit(0) - pixs = [] - for size in (16, 32, 48, 64, 128): - pix = gtkgui_helpers.get_icon_pixmap('gajim', size) - if pix: - pixs.append(pix) - if pixs: - # set the icon to all windows - Gtk.Window.set_default_icon_list(pixs) - pritext = _('Gajim is already running') - sectext = _('Another instance of Gajim seems to be running\nRun anyway?') - dialog = dialogs.YesNoDialog(pritext, sectext) - dialog.popup() - if dialog.run() != Gtk.ResponseType.YES: - sys.exit(3) - dialog.destroy() - # run anyway, delete pid and useless global vars - if os.path.exists(pid_filename): - os.remove(pid_filename) - del pix - del pritext - del sectext - dialog.destroy() - -# Create .gajim dir -pid_dir = os.path.dirname(pid_filename) -if not os.path.exists(pid_dir): - check_paths.create_path(pid_dir) -# Create pid file -try: - f2 = open(pid_filename, 'w') - f2.write(str(os.getpid())) - f2.close() -except IOError as e2: - dlg = dialogs.ErrorDialog(_('Disk Write Error'), str(e2)) - dlg.run() - dlg.destroy() - sys.exit() -del pid_dir - -def on_exit(): - # Save the entropy from OpenSSL PRNG - if PYOPENSSL_PRNG_PRESENT: - try: - OpenSSL.rand.write_file(RNG_SEED) - except TypeError: - OpenSSL.rand.write_file(RNG_SEED.encode('utf-8')) - # delete pid file on normal exit - if os.path.exists(pid_filename): - os.remove(pid_filename) - # Shutdown GUI and save config - if hasattr(gajim.interface, 'roster') and gajim.interface.roster: - gajim.interface.roster.prepare_quit() - -import atexit -atexit.register(on_exit) - -from gui_interface import Interface - -if __name__.endswith('__main__'): - def sigint_cb(num, stack): - sys.exit(5) - # ^C exits the application normally to delete pid file - signal.signal(signal.SIGINT, sigint_cb) - signal.signal(signal.SIGTERM, sigint_cb) - - log.info("Encodings: d:%s, fs:%s, p:%s", sys.getdefaultencoding(), \ - sys.getfilesystemencoding(), locale.getpreferredencoding()) - - check_paths.check_and_possibly_create_paths() - - interface = Interface() - interface.run() - - try: - Gtk.main() - except KeyboardInterrupt: - print('KeyboardInterrupt', file=sys.stderr) +app = GajimApplication() +app.run(sys.argv) diff --git a/src/gui_interface.py b/src/gui_interface.py index 7967fefb5..a1f1068d5 100644 --- a/src/gui_interface.py +++ b/src/gui_interface.py @@ -2719,11 +2719,11 @@ class Interface: view.updateNamespace({'gajim': gajim}) gajim.ipython_window = window - def run(self): + def run(self, app): if gajim.config.get('trayicon') != 'never': self.show_systray() - self.roster = roster_window.RosterWindow() + self.roster = roster_window.RosterWindow(app) if self.msg_win_mgr.mode == \ MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER: self.msg_win_mgr.create_window(None, None, None) diff --git a/src/roster_window.py b/src/roster_window.py index f54b95d99..4ca0a644e 100644 --- a/src/roster_window.py +++ b/src/roster_window.py @@ -94,7 +94,7 @@ empty_pixbuf = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, True, 8, 1, 1) empty_pixbuf.fill(0xffffff00) -class RosterWindow: +class RosterWindow(Gtk.ApplicationWindow): """ Class for main window of the GTK+ interface """ @@ -2479,7 +2479,7 @@ class RosterWindow: When we quit the gtk interface - exit gtk """ self.prepare_quit() - Gtk.main_quit() + self.application.quit() def on_quit_request(self, widget=None): """ @@ -6178,7 +6178,8 @@ class RosterWindow: ### ################################################################################ - def __init__(self): + def __init__(self, app): + self.application = app self.filtering = False self.starting = False self.starting_filtering = False @@ -6195,6 +6196,7 @@ class RosterWindow: self.xml = gtkgui_helpers.get_gtk_builder('roster_window.ui') self.window = self.xml.get_object('roster_window') self.hpaned = self.xml.get_object('roster_hpaned') + self.window.set_application(app) gajim.interface.msg_win_mgr = MessageWindowMgr(self.window, self.hpaned) gajim.interface.msg_win_mgr.connect('window-delete', self.on_message_window_delete)