config.py 41 KB
Newer Older
Philipp Hörist's avatar
Philipp Hörist committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
# Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org>
# Copyright (C) 2004-2005 Vincent Hanquez <tab AT snarc.org>
# Copyright (C) 2005 Stéphan Kochen <stephan AT kochen.nl>
# Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
#                         Alex Mauer <hawke AT hawkesnest.net>
#                         Nikos Kouremenos <kourem AT gmail.com>
# Copyright (C) 2005-2007 Travis Shirk <travis AT pobox.com>
# Copyright (C) 2006 Stefan Bethge <stefan AT lanpartei.de>
# Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
# Copyright (C) 2007 James Newton <redshodan AT gmail.com>
#                    Julien Pivotto <roidelapluie AT gmail.com>
# Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com>
#                         Stephan Erb <steve-e AT h3c.de>
# Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
#
# 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 <http://www.gnu.org/licenses/>.
Yann Leboulanger's avatar
Yann Leboulanger committed
29

Philipp Hörist's avatar
Philipp Hörist committed
30 31 32 33 34
from typing import Any  # pylint: disable=unused-import
from typing import Dict  # pylint: disable=unused-import
from typing import List  # pylint: disable=unused-import
from typing import Tuple  # pylint: disable=unused-import

35
import re
36
from enum import IntEnum, unique
Yann Leboulanger's avatar
Yann Leboulanger committed
37

38 39
from gi.repository import GLib

40
import gajim
41
from gajim.common.i18n import _
42

Philipp Hörist's avatar
Philipp Hörist committed
43

44
@unique
45 46 47 48 49 50 51
class Option(IntEnum):
    TYPE = 0
    VAL = 1
    DESC = 2
    # If Option.RESTART is True - we need restart to use our changed option
    # Option.DESC also should be there
    RESTART = 3
Yann Leboulanger's avatar
Yann Leboulanger committed
52

53 54 55
opt_int = ['integer', 0]
opt_str = ['string', 0]
opt_bool = ['boolean', 0]
Philipp Hörist's avatar
Philipp Hörist committed
56
opt_color = ['color', r'(#[0-9a-fA-F]{6})|rgb\(\d+,\d+,\d+\)|rgba\(\d+,\d+,\d+,[01]\.?\d*\)']
nicfit's avatar
nicfit committed
57
opt_one_window_types = ['never', 'always', 'always_with_roster', 'peracct', 'pertype']
58
opt_show_roster_on_startup = ['always', 'never', 'last_state']
59
opt_treat_incoming_messages = ['', 'chat', 'normal']
Yann Leboulanger's avatar
Yann Leboulanger committed
60

61

Yann Leboulanger's avatar
Yann Leboulanger committed
62 63
class Config:

64 65 66 67
    DEFAULT_ICONSET = 'dcraven'
    DEFAULT_MOOD_ICONSET = 'default'
    DEFAULT_ACTIVITY_ICONSET = 'default'

68
    __options = ({
Philipp Hörist's avatar
Philipp Hörist committed
69
        # name: [ type, default_value, help_string, restart ]
70 71
        'verbose': [opt_bool, False, '', True],
        'autopopup': [opt_bool, False],
72
        'notify_on_signin': [opt_bool, False],
73 74 75
        'notify_on_signout': [opt_bool, False],
        'notify_on_new_message': [opt_bool, True],
        'autopopupaway': [opt_bool, False],
76 77
        'autopopup_chat_opened': [opt_bool, False, _('Show desktop notification even when a chat window is opened for this contact and does not have focus.')],
        'sounddnd': [opt_bool, False, _('Play sound even when being busy.')],
78
        'showoffline': [opt_bool, True],
79
        'show_only_chat_and_online': [opt_bool, False, _('Show only online and free for chat contacts in the contact list.')],
80 81 82
        'show_transports_group': [opt_bool, True],
        'autoaway': [opt_bool, True],
        'autoawaytime': [opt_int, 5, _('Time in minutes, after which your status changes to away.')],
83
        'autoaway_message': [opt_str, _('$S (Away: Idle more than $T min)'), _('$S will be replaced by current status message, $T by the \'autoawaytime\' value.')],
84 85
        'autoxa': [opt_bool, True],
        'autoxatime': [opt_int, 15, _('Time in minutes, after which your status changes to not available.')],
86
        'autoxa_message': [opt_str, _('$S (Not available: Idle more than $T min)'), _('$S will be replaced by current status message, $T by the \'autoxatime\' value.')],
87 88
        'ask_online_status': [opt_bool, False],
        'ask_offline_status': [opt_bool, False],
89 90
        'trayicon': [opt_str, 'always', _('When to show the notification area icon. Can be \'never\', \'on_event\', and \'always\'.'), False],
        'allow_hide_roster': [opt_bool, False, _('Allow to hide the contact list window even if the notification area icon is not shown.'), False],
91 92
        'iconset': [opt_str, DEFAULT_ICONSET, '', True],
        'use_transports_iconsets': [opt_bool, True, '', True],
93
        'collapsed_rows': [opt_str, '', _('List of rows (accounts and groups) that are collapsed (space separated).'), True],
94 95 96 97 98
        'roster_theme': [opt_str, _('default'), '', True],
        'mergeaccounts': [opt_bool, False, '', True],
        'sort_by_show_in_roster': [opt_bool, True, '', True],
        'sort_by_show_in_muc': [opt_bool, False, '', True],
        'use_speller': [opt_bool, False, ],
Philipp Hörist's avatar
Philipp Hörist committed
99
        'show_xhtml': [opt_bool, True, ],
100
        'speller_language': [opt_str, '', _('Language used for spell checking.')],
101 102
        'print_time': [opt_str, 'always', _('\'always\' - print time for every message.\n\'sometimes\' - print time every print_ichat_every_foo_minutes minute.\n\'never\' - never print time.')],
        'emoticons_theme': [opt_str, 'noto-emoticons', '', True],
103
        'ascii_emoticons': [opt_bool, True, _('When enabled, ASCII emojis will be converted to graphical emojis.'), True],
104 105
        'ascii_formatting': [opt_bool, True,
                _('Treat * / _ pairs as possible formatting characters.'), True],
106
        'show_ascii_formatting_chars': [opt_bool, True, _('If enabled, do not '
107 108
                'remove */_ . So *abc* will be bold but with * * not removed.')],
        'sounds_on': [opt_bool, True],
Daniel Brötzmann's avatar
Daniel Brötzmann committed
109
        'gc_refer_to_nick_char': [opt_str, ',', _('Character to add after nickname when using nickname completion (tab) in group chat.')],
110
        'gc_proposed_nick_char': [opt_str, '_', _('Character to propose to add after desired nickname when nickname is already used in group chat.')],
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
        'msgwin-max-state': [opt_bool, False],
        'msgwin-x-position': [opt_int, -1], # Default is to let the window manager decide
        'msgwin-y-position': [opt_int, -1], # Default is to let the window manager decide
        'msgwin-width': [opt_int, 500],
        'msgwin-height': [opt_int, 440],
        'chat-msgwin-x-position': [opt_int, -1], # Default is to let the window manager decide
        'chat-msgwin-y-position': [opt_int, -1], # Default is to let the window manager decide
        'chat-msgwin-width': [opt_int, 480],
        'chat-msgwin-height': [opt_int, 440],
        'gc-msgwin-x-position': [opt_int, -1], # Default is to let the window manager decide
        'gc-msgwin-y-position': [opt_int, -1], # Default is to let the window manager decide
        'gc-msgwin-width': [opt_int, 600],
        'gc-msgwin-height': [opt_int, 440],
        'pm-msgwin-x-position': [opt_int, -1], # Default is to let the window manager decide
        'pm-msgwin-y-position': [opt_int, -1], # Default is to let the window manager decide
        'pm-msgwin-width': [opt_int, 480],
        'pm-msgwin-height': [opt_int, 440],
        'single-msg-x-position': [opt_int, 0],
        'single-msg-y-position': [opt_int, 0],
        'single-msg-width': [opt_int, 400],
        'single-msg-height': [opt_int, 280],
132
        'save-roster-position': [opt_bool, True, _('If enabled, Gajim will save the contact list window position when hiding it, and restore it when showing the contact list window again.')],
133 134 135 136 137
        'roster_x-position': [opt_int, 0],
        'roster_y-position': [opt_int, 0],
        'roster_width': [opt_int, 200],
        'roster_height': [opt_int, 400],
        'roster_hpaned_position': [opt_int, 200],
138
        'roster_on_the_right': [opt_bool, False, _('Place the contact list on the right in single window mode'), True],
139 140 141 142 143
        'history_window_width': [opt_int, -1],
        'history_window_height': [opt_int, 450],
        'history_window_x-position': [opt_int, 0],
        'history_window_y-position': [opt_int, 0],
        'latest_disco_addresses': [opt_str, ''],
144
        'time_stamp': [opt_str, '%x | %X  ', _('This option lets you customize the timestamp that is printed in conversation. For example \'[%H:%M] \' will show \'[hour:minute] \'. See python doc on strftime for full documentation (https://docs.python.org/3/library/time.html#time.strftime).')],
145 146 147 148 149 150 151
        'before_nickname': [opt_str, '', _('Characters that are printed before the nickname in conversations.')],
        'after_nickname': [opt_str, ':', _('Characters that are printed after the nickname in conversations.')],
        'change_roster_title': [opt_bool, True, _('If enabled, Gajim will add * and [n] in contact list window title.')],
        'restore_lines': [opt_int, 10, _('Number of messages from chat history to be restored when a chat tab/window is reopened.')],
        'restore_timeout': [opt_int, -1, _('How far back in time (minutes) chat history is restored. -1 means no limit.')],
        'muc_autorejoin_timeout': [opt_int, 1, _('How many seconds to wait before trying to automatically rejoin a group chat you were disconnected from. Set to 0 to disable automatic rejoining.')],
        'send_on_ctrl_enter': [opt_bool, False, _('Send message on Ctrl+Enter and make a new line with Enter.')],
152
        'last_roster_visible': [opt_bool, True],
153
        'key_up_lines': [opt_int, 25, _('How many lines to store for Ctrl+KeyUP (previously sent messages).')],
154
        'version': [opt_str, gajim.__version__], # which version created the config
155
        'search_engine': [opt_str, 'https://duckduckgo.com/?q=%s'],
156
        'dictionary_url': [opt_str, 'WIKTIONARY', _('Either a custom URL with %%s in it (where %%s is the word/phrase) or \'WIKTIONARY\' (which means use Wikitionary).')],
157 158 159
        'always_english_wikipedia': [opt_bool, False],
        'always_english_wiktionary': [opt_bool, True],
        'remote_control': [opt_bool, False, _('If checked, Gajim can be controlled remotely using gajim-remote.'), True],
160
        'print_ichat_every_foo_minutes': [opt_int, 5, _('When not printing time for every message (\'print_time\'==sometimes), print it every x minutes.')],
161
        'confirm_paste_image': [opt_bool, True, _('Ask before pasting an image.')],
162
        'confirm_close_muc': [opt_bool, True, _('Ask before closing a group chat tab/window.')],
163
        'confirm_close_multiple_tabs': [opt_bool, True, _('Ask before closing tabbed chat window if there are chats that can lose data (chat, private chat, group chat that will not be minimized).')],
164 165
        'notify_on_file_complete': [opt_bool, True],
        'file_transfers_port': [opt_int, 28011],
166
        'ft_add_hosts_to_send': [opt_str, '', _('List of send hosts (comma separated) in addition to local interfaces for file transfers (in case of address translation/port forwarding).')],
167 168 169 170 171 172
        'use_kib_mib': [opt_bool, False, _('IEC standard says KiB = 1024 bytes, KB = 1000 bytes.')],
        'notify_on_all_muc_messages': [opt_bool, False],
        'trayicon_notification_on_events': [opt_bool, True, _('Notify of events in the notification area.')],
        'last_save_dir': [opt_str, ''],
        'last_send_dir': [opt_str, ''],
        'last_sounds_dir': [opt_str, ''],
173
        'tabs_position': [opt_str, 'left'],
174 175 176 177 178 179
        'tabs_always_visible': [opt_bool, False, _('Show tab when only one conversation?')],
        'tabs_border': [opt_bool, False, _('Show tabbed notebook border in chat windows?')],
        'tabs_close_button': [opt_bool, True, _('Show close button in tab?')],
        'notification_preview_message': [opt_bool, True, _('Preview new messages in notification popup?')],
        'notification_position_x': [opt_int, -1],
        'notification_position_y': [opt_int, -1],
180 181 182 183
        'muc_highlight_words': [opt_str, '', _('A list of words (semicolon separated) that will be highlighted in group chats.')],
        'quit_on_roster_x_button': [opt_bool, False, _('If enabled, Gajim quits when clicking the X button of your Window Manager. This setting is taken into account only if the notification area icon is used.')],
        'hide_on_roster_x_button': [opt_bool, False, _('If enabled, Gajim hides the contact list window when pressing the X button instead of minimizing into the notification area.')],
        'show_status_msgs_in_roster': [opt_bool, True, _('If enabled, Gajim will display the status message (if not empty) underneath the contact name in the contact list window.'), True],
184 185 186 187 188
        'show_avatars_in_roster': [opt_bool, True, '', True],
        'show_mood_in_roster': [opt_bool, True, '', True],
        'show_activity_in_roster': [opt_bool, True, '', True],
        'show_tunes_in_roster': [opt_bool, True, '', True],
        'show_location_in_roster': [opt_bool, True, '', True],
189 190 191 192
        'avatar_position_in_roster': [opt_str, 'right', _('Define the position of avatars in the contact list. Can be \'left\' or \'right\'.'), True],
        'print_status_in_chats': [opt_bool, False, _('If disabled, Gajim will no longer print status messages in chats when a contact changes their status (and/or their status message).')],
        'print_join_left_default': [opt_bool, False, _('Default Setting: Show a status message for every join or leave in a group chat.')],
        'print_status_muc_default': [opt_bool, False, _('Default Setting: Show a status message for all status changes (away, dnd, etc.) of users in a group chat.')],
193
        'log_contact_status_changes': [opt_bool, False],
194 195
        'roster_window_skip_taskbar': [opt_bool, False, _('Don\'t show contact list window in the system taskbar.')],
        'use_urgency_hint': [opt_bool, True, _('If enabled, Gajim makes the window flash (the default behaviour in most Window Managers) when holding pending events.')],
196 197 198
        'notification_timeout': [opt_int, 5],
        'one_message_window': [opt_str, 'always',
            #always, never, peracct, pertype should not be translated
199
                _('Controls the window where new messages are placed.\n\'always\' - All messages are sent to a single window.\n\'always_with_roster\' - Like \'always\' but the messages are in a single window along with the contact list.\n\'never\' - All messages get their own window.\n\'peracct\' - Messages for each account are sent to a specific window.\n\'pertype\' - Each message type (e.g. chats vs. group chats) is sent to a specific window.')],
200 201 202 203 204 205 206
        'show_roster_on_startup':[opt_str, 'always', _('Show contact list window on startup.\n\'always\' - Always show contact list window.\n\'never\' - Never show contact list window.\n\'last_state\' - Restore last state of the contact list window.')],
        'show_avatar_in_chat': [opt_bool, True, _('If enabled, Gajim displays the avatar in the chat window.')],
        'escape_key_closes': [opt_bool, True, _('If enabled, pressing Esc closes a tab/window.')],
        'hide_groupchat_banner': [opt_bool, False, _('Hides the banner in a group chat window.')],
        'hide_chat_banner': [opt_bool, False, _('Hides the banner in a 1:1 chat window.')],
        'hide_groupchat_occupants_list': [opt_bool, False, _('Hides the group chat participants list in a group chat window.')],
        'chat_merge_consecutive_nickname': [opt_bool, False, _('In a chat, show the nickname at the beginning of a line only when it\'s not the same person talking as in the previous message.')],
207
        'chat_merge_consecutive_nickname_indent': [opt_str, '  ', _('Indentation when using merge consecutive nickname.')],
208 209 210 211 212 213
        'ctrl_tab_go_to_next_composing': [opt_bool, True, _('Ctrl+Tab switches to the next composing tab when there are no tabs with messages pending.')],
        'confirm_metacontacts': [opt_str, '', _('Show a confirmation dialog to create metacontacts? Empty string means never show the dialog.')],
        'confirm_block': [opt_str, '', _('Show a confirmation dialog to block a contact? Empty string means never show the dialog.')],
        'enable_negative_priority': [opt_bool, False, _('If enabled, you will be able to set a negative priority to your account in the Accounts window. BE CAREFUL, when you are logged in with a negative priority, you will NOT receive any message from your server.')],
        'show_contacts_number': [opt_bool, True, _('If enabled, Gajim will show both the number of online and total contacts in account rows as well as in group rows.')],
        'scroll_roster_to_last_message': [opt_bool, True, _('If enabled, Gajim will scroll and select the contact who sent you the last message, if the chat window is not already opened.')],
214 215 216
        'change_status_window_timeout': [opt_int, 15, _('Time of inactivity needed before the change status window closes down.')],
        'max_conversation_lines': [opt_int, 500, _('Maximum number of lines that are printed in conversations. Oldest lines are cleared.')],
        'check_idle_every_foo_seconds': [opt_int, 2, _('Choose interval between 2 checks of idleness.')],
217 218
        'uri_schemes': [opt_str, 'aaa:// aaas:// acap:// cap:// cid: crid:// data: dav: dict:// dns: fax: file:/ ftp:// geo: go: gopher:// h323: http:// https:// iax: icap:// im: imap:// info: ipp:// iris: iris.beep: iris.xpc: iris.xpcs: iris.lwz: ldap:// mid: modem: msrp:// msrps:// mtqp:// mupdate:// news: nfs:// nntp:// opaquelocktoken: pop:// pres: prospero:// rtsp:// service: shttp:// sip: sips: sms: snmp:// soap.beep:// soap.beeps:// tag: tel: telnet:// tftp:// thismessage:/ tip:// tv: urn:// vemmi:// xmlrpc.beep:// xmlrpc.beeps:// z39.50r:// z39.50s:// about: apt: cvs:// daap:// ed2k:// feed: fish:// git:// iax2: irc:// ircs:// ldaps:// magnet: mms:// rsync:// ssh:// svn:// sftp:// smb:// webcal:// aesgcm://', _('Valid URI schemes. Only schemes in this list will be accepted as \'real\' URI (mailto and xmpp are handled separately).'), True],
        'shell_like_completion': [opt_bool, False, _('If enabled, completion in group chats will be like a shell auto-completion.')],
219 220 221
        'audio_input_device': [opt_str, 'autoaudiosrc ! volume name=gajim_vol'],
        'audio_output_device': [opt_str, 'autoaudiosink'],
        'video_input_device': [opt_str, 'autovideosrc'],
222 223 224
        'video_framerate': [opt_str, '', _('Optionally fix Jingle output video framerate. Example: 10/1 or 25/2.')],
        'video_size': [opt_str, '', _('Optionally resize Jingle output video. Example: 320x240.')],
        'video_see_self': [opt_bool, True, _('If enabled, you will see your webcam\'s video stream as well.')],
225 226
        'audio_input_volume': [opt_int, 50],
        'audio_output_volume': [opt_int, 50],
227
        'use_stun_server': [opt_bool, False, _('If enabled, Gajim will try to use a STUN server when using Jingle. The one in \'stun_server\' option, or the one given by the XMPP server.')],
228
        'stun_server': [opt_str, '', _('STUN server to use when using Jingle')],
229 230 231
        'show_affiliation_in_groupchat': [opt_bool, True, _('If enabled, Gajim will show affiliation of group chat participants by adding a colored square to the status icon.')],
        'global_proxy': [opt_str, '', _('Proxy used for all outgoing connections if the account does not have a specific proxy configured.')],
        'ignore_incoming_attention': [opt_bool, False, _('If enabled, Gajim will ignore incoming attention requests (\'wizz\').')],
232
        'remember_opened_chat_controls': [opt_bool, True, _('If enabled, Gajim will reopen chat windows that were opened last time Gajim was closed.')],
233 234 235
        'positive_184_ack': [opt_bool, False, _('If enabled, Gajim will display an icon to show that sent messages have been received by your contact.')],
        'use_keyring': [opt_bool, True, _('If enabled, Gajim will use the System\'s Keyring to store account passwords.')],
        'remote_commands': [opt_bool, False, _('If enabled, Gajim will execute XEP-0146 Commands.')],
Philipp Hörist's avatar
Philipp Hörist committed
236
        'dark_theme': [opt_int, 2, _('2: System, 1: Enabled, 0: Disabled')],
237
        'threshold_options': [opt_str, '1, 2, 4, 10, 0', _('Options in days which can be chosen in the sync threshold menu'), True],
238 239 240 241 242 243
        'public_room_sync_threshold': [opt_int, 1, _('Maximum history in days we request from a public group chat archive. 0: As much as possible.')],
        'private_room_sync_threshold': [opt_int, 0, _('Maximum history in days we request from a private group chat archive. 0: As much as possible.')],
        'show_subject_on_join': [opt_bool, True, _('If enabled, Gajim shows the group chat subject in the chat window when joining.')],
        'show_chatstate_in_roster': [opt_bool, True, _('If enabled, the contact row is colored according to the current chat state of the contact.')],
        'show_chatstate_in_tabs': [opt_bool, True, _('If enabled, the tab is colored according to the current chat state of the contact.')],
        'show_chatstate_in_banner': [opt_bool, True, _('Shows a text in the banner that describes the current chat state of the contact.')],
244
        'send_chatstate_default': [opt_str, 'composing_only', _('Chat state notifications that are sent to contacts. Possible values: all, composing_only, disabled')],
245
        'send_chatstate_muc_default': [opt_str, 'composing_only', _('Chat state notifications that are sent to the group chat. Possible values: \'all\', \'composing_only\', \'disabled\'')],
246
        'muclumbus_api_jid': [opt_str, 'rodrigo.de.mucobedo@dreckshal.de'],
247 248
        'muclumbus_api_http_uri': [opt_str, 'https://search.jabbercat.org/api/1.0/search'],
        'muclumbus_api_pref': [opt_str, 'http', _('API Preferences. Possible values: \'http\', \'iq\'')],
249
        'auto_copy': [opt_bool, True, _('Selecting text will copy it to the clipboard')],
250
        'command_system_execute': [opt_bool, False, _('If enabled, Gajim will execute commands (/show, /sh, /execute, /exec).')],
Philipp Hörist's avatar
Philipp Hörist committed
251
        'groupchat_roster_width': [opt_int, 210, _('Width of group chat roster in pixel')],
252
        'dev_force_bookmark_2': [opt_bool, False, _('Force Bookmark 2 usage')],
Philipp Hörist's avatar
Philipp Hörist committed
253
    }, {})  # type: Tuple[Dict[str, List[Any]], Dict[Any, Any]]
254 255

    __options_per_key = {
256 257 258
        'accounts': ({
            'name': [opt_str, '', '', True],
            'account_label': [opt_str, '', '', False],
259
            'account_color': [opt_color, 'rgb(85, 85, 85)'],
260 261 262 263 264 265 266 267
            'hostname': [opt_str, '', '', True],
            'anonymous_auth': [opt_bool, False],
            'avatar_sha': [opt_str, '', '', False],
            'client_cert': [opt_str, '', '', True],
            'client_cert_encrypted': [opt_bool, False, '', False],
            'savepass': [opt_bool, False],
            'password': [opt_str, ''],
            'resource': [opt_str, 'gajim.$rand', '', True],
268 269
            'priority': [opt_int, 0, '', True],
            'adjust_priority_with_status': [opt_bool, False, _('Priority will change automatically according to your status. Priorities are defined in \'autopriority_*\' options.')],
270 271 272 273 274 275
            'autopriority_online': [opt_int, 50],
            'autopriority_chat': [opt_int, 50],
            'autopriority_away': [opt_int, 40],
            'autopriority_xa': [opt_int, 30],
            'autopriority_dnd': [opt_int, 20],
            'autoconnect': [opt_bool, False, '', True],
Philipp Hörist's avatar
Philipp Hörist committed
276
            'autoconnect_as': [opt_str, 'online', _('Status to be used automatically when connecting. Can be \'online\', \'chat\', \'away\', \'xa\' or \'dnd\'. NOTE: This option is used only if \'restore_last_status\' is disabled.'), True],
277
            'restore_last_status': [opt_bool, False, _('If enabled, the last status will be restored.')],
Daniel Brötzmann's avatar
Daniel Brötzmann committed
278
            'autoauth': [opt_bool, False, _('If enabled, contacts requesting authorization will be accepted automatically.')],
279
            'active': [opt_bool, True, _('If disabled, this account will be disabled and will not appear in the contact list window.'), True],
280 281 282
            'proxy': [opt_str, '', '', True],
            'keyid': [opt_str, '', '', True],
            'keyname': [opt_str, '', '', True],
Philipp Hörist's avatar
Philipp Hörist committed
283 284
            'use_plain_connection': [opt_bool, False, _('Use an unencrypted connection to the server')],
            'action_when_plain_connection': [opt_str, 'warn', _('Show a warning dialog before sending password on an plaintext connection. Can be \'warn\', or \'none\'.')],
285
            'ignore_ssl_errors': [opt_str, '', _('List of SSL errors to ignore (space separated).')],
286 287 288
            'use_custom_host': [opt_bool, False, '', True],
            'custom_port': [opt_int, 5222, '', True],
            'custom_host': [opt_str, '', '', True],
289
            'custom_type': [opt_str, 'START TLS', _('ConnectionType: START TLS, DIRECT TLS or PLAIN'), True],
290
            'sync_with_global_status': [opt_bool, False, ],
291
            'no_log_for': [opt_str, '', _('List of XMPP Addresses (space separated) for which you do not want to store chat history. You can also add the name of an account to disable storing chat history for this account.')],
292
            'attached_gpg_keys': [opt_str, ''],
293
            'time_for_ping_alive_answer': [opt_int, 60, _('How many seconds to wait for the answer of a ping alive packet before trying to reconnect.')],
294 295 296
            'http_auth': [opt_str, 'ask'], # yes, no, ask
            # proxy65 for FT
            'file_transfer_proxies': [opt_str, ''],
297 298
            'use_ft_proxies': [opt_bool, False, _('If enabled, Gajim will use your IP and proxies defined in \'file_transfer_proxies\' option for file transfers.'), True],
            'test_ft_proxies_on_startup': [opt_bool, False, _('If enabled, Gajim will test file transfer proxies on startup to be sure they work. Openfire\'s proxies are known to fail this test even if they work.')],
299 300 301 302 303 304 305 306 307 308 309
            'msgwin-x-position': [opt_int, -1], # Default is to let the wm decide
            'msgwin-y-position': [opt_int, -1], # Default is to let the wm decide
            'msgwin-width': [opt_int, 480],
            'msgwin-height': [opt_int, 440],
            'is_zeroconf': [opt_bool, False],
            'last_status': [opt_str, 'online'],
            'last_status_msg': [opt_str, ''],
            'zeroconf_first_name': [opt_str, '', '', True],
            'zeroconf_last_name': [opt_str, '', '', True],
            'zeroconf_jabber_id': [opt_str, '', '', True],
            'zeroconf_email': [opt_str, '', '', True],
310
            'answer_receipts': [opt_bool, True, _('If enabled, Gajim will answer to message receipt requests.')],
311 312 313 314 315 316 317 318
            'publish_tune': [opt_bool, False],
            'publish_location': [opt_bool, False],
            'subscribe_mood': [opt_bool, True],
            'subscribe_activity': [opt_bool, True],
            'subscribe_tune': [opt_bool, True],
            'subscribe_nick': [opt_bool, True],
            'subscribe_location': [opt_bool, True],
            'ignore_unknown_contacts': [opt_bool, False],
319 320
            'send_os_info': [opt_bool, True, _('Allow Gajim to send information about the operating system you are running.')],
            'send_time_info': [opt_bool, True, _('Allow Gajim to send your local time.')],
321 322
            'send_idle_time': [opt_bool, True],
            'roster_version': [opt_str, ''],
323 324 325
            'subscription_request_msg': [opt_str, '', _('Message that is sent to contacts you want to add.')],
            'ft_send_local_ips': [opt_bool, True, _('If enabled, Gajim will send your local IP so your contact can connect to your machine for file transfers.')],
            'opened_chat_controls': [opt_str, '', _('List of XMPP Addresses (space separated) for which the chat window will be re-opened on next startup.')],
326
            'recent_groupchats': [opt_str, ''],
327 328
            'filetransfer_preference' : [opt_str, 'httpupload', _('Preferred file transfer mechanism for file drag&drop on a chat window. Can be \'httpupload\' (default) or \'jingle\'.')],
            'allow_posh': [opt_bool, True, _('Allow certificate verification with POSH.')],
329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354
        }, {}),
        'statusmsg': ({
            'message': [opt_str, ''],
            'activity': [opt_str, ''],
            'subactivity': [opt_str, ''],
            'activity_text': [opt_str, ''],
            'mood': [opt_str, ''],
            'mood_text': [opt_str, ''],
        }, {}),
        'defaultstatusmsg': ({
            'enabled': [opt_bool, False],
            'message': [opt_str, ''],
        }, {}),
        'soundevents': ({
            'enabled': [opt_bool, True],
            'path': [opt_str, ''],
        }, {}),
        'proxies': ({
            'type': [opt_str, 'http'],
            'host': [opt_str, ''],
            'port': [opt_int, 3128],
            'useauth': [opt_bool, False],
            'user': [opt_str, ''],
            'pass': [opt_str, ''],
        }, {}),
        'contacts': ({
355 356
            'speller_language': [opt_str, '', _('Language used for spell checking.')],
            'send_chatstate': [opt_str, 'composing_only', _('Chat state notifications that are sent to contacts. Possible values: \'all\', \'composing_only\', \'disabled\'')],
357 358
        }, {}),
        'encryption': ({
359
            'encryption': [opt_str, '', _('The currently active encryption for that contact.')],
360 361
        }, {}),
        'rooms': ({
362 363 364 365 366 367 368
            'speller_language': [opt_str, '', _('Language used for spell checking.')],
            'notify_on_all_messages': [opt_bool, False, _('If enabled, a notification is created for every message in this group chat.')],
            'print_status': [opt_bool, False, _('Show a status message for all status changes (away, dnd, etc.) of users in a group chat.')],
            'print_join_left': [opt_bool, False, _('Show a status message for every join or leave in a group chat.')],
            'minimize_on_autojoin': [opt_bool, True, _('If enabled, the group chat is minimized into the contact list when joining automatically.')],
            'minimize_on_close': [opt_bool, True, _('If enabled, the group chat is minimized into the contact list when closing it.')],
            'send_chatstate': [opt_str, 'composing_only', _('Chat state notifications that are sent to the group chat. Possible values: \'all\', \'composing_only\' or \'disabled\'.')],
369 370
        }, {}),
        'plugins': ({
371
            'active': [opt_bool, False, _('If enabled, plugins will be activated on startup (this is saved when exiting Gajim). This option SHOULD NOT be used to (de)activate plugins. Use the plugin window instead.')],
372
        }, {}),
Philipp Hörist's avatar
Philipp Hörist committed
373
    }  # type: Dict[str, Tuple[Dict[str, List[Any]], Dict[Any, Any]]]
374 375

    statusmsg_default = {
376 377
        _('Sleeping'): ['ZZZZzzzzzZZZZZ', 'inactive', 'sleeping', '', 'sleepy', ''],
        _('Back soon'): [_('Back in some minutes.'), '', '', '', '', ''],
378 379 380 381 382
        _('Eating'): [_('I\'m eating.'), 'eating', 'other', '', '', ''],
        _('Movie'): [_('I\'m watching a movie.'), 'relaxing', 'watching_a_movie', '', '', ''],
        _('Working'): [_('I\'m working.'), 'working', 'other', '', '', ''],
        _('Phone'): [_('I\'m on the phone.'), 'talking', 'on_the_phone', '', '', ''],
        _('Out'): [_('I\'m out enjoying life.'), 'relaxing', 'going_out', '', '', ''],
383 384 385 386 387 388
        '_last_online': ['', '', '', '', '', ''],
        '_last_chat': ['', '', '', '', '', ''],
        '_last_away': ['', '', '', '', '', ''],
        '_last_xa': ['', '', '', '', '', ''],
        '_last_dnd': ['', '', '', '', '', ''],
        '_last_offline': ['', '', '', '', '', ''],
389 390 391
    }

    defaultstatusmsg_default = {
392 393
        'online': [False, _('I\'m available.')],
        'chat': [False, _('I\'m free for chat.')],
394
        'away': [False, _('Be right back.')],
395
        'xa': [False, _('I\'m not available.')],
396 397
        'dnd': [False, _('Do not disturb.')],
        'offline': [False, _('Bye!')],
398 399 400
    }

    soundevents_default = {
401 402 403 404 405 406 407
        'attention_received': [True, 'attention.wav'],
        'first_message_received': [True, 'message1.wav'],
        'next_message_received_focused': [True, 'message2.wav'],
        'next_message_received_unfocused': [True, 'message2.wav'],
        'contact_connected': [False, 'connected.wav'],
        'contact_disconnected': [False, 'disconnected.wav'],
        'message_sent': [False, 'sent.wav'],
Daniel Brötzmann's avatar
Daniel Brötzmann committed
408
        'muc_message_highlight': [True, 'gc_message1.wav', _('Sound to play when a group chat message contains one of the words in \'muc_highlight_words\' or your nickname is mentioned.')],
409
        'muc_message_received': [True, 'gc_message2.wav', _('Sound to play when any group chat message arrives.')],
410 411
    }

412 413 414 415
    proxies_default = {
        _('Tor'): ['socks5', 'localhost', 9050],
    }

416 417 418
    def foreach(self, cb, data=None):
        for opt in self.__options[1]:
            cb(data, opt, None, self.__options[1][opt])
419 420 421 422 423 424 425 426 427 428 429 430 431
        for opt in self.__options_per_key:
            cb(data, opt, None, None)
            dict_ = self.__options_per_key[opt][1]
            for opt2 in dict_.keys():
                cb(data, opt2, [opt], None)
                for opt3 in dict_[opt2]:
                    cb(data, opt3, [opt, opt2], dict_[opt2][opt3])

    def get_children(self, node=None):
        """
        Tree-like interface
        """
        if node is None:
432
            for child, option in self.__options[1].items():
433 434 435 436 437 438 439 440 441 442
                yield (child, ), option
            for grandparent in self.__options_per_key:
                yield (grandparent, ), None
        elif len(node) == 1:
            grandparent, = node
            for parent in self.__options_per_key[grandparent][1]:
                yield (grandparent, parent), None
        elif len(node) == 2:
            grandparent, parent = node
            children = self.__options_per_key[grandparent][1][parent]
443
            for child, option in children.items():
444 445 446 447 448 449 450 451 452 453 454 455 456 457
                yield (grandparent, parent, child), option
        else:
            raise ValueError('Invalid node')

    def is_valid_int(self, val):
        try:
            ival = int(val)
        except Exception:
            return None
        return ival

    def is_valid_bool(self, val):
        if val == 'True':
            return True
458
        if val == 'False':
459
            return False
460 461 462 463 464 465
        ival = self.is_valid_int(val)
        if ival:
            return True
        if ival is None:
            return None
        return False
466 467 468 469 470 471 472 473 474

    def is_valid_string(self, val):
        return val

    def is_valid(self, type_, val):
        if not type_:
            return None
        if type_[0] == 'boolean':
            return self.is_valid_bool(val)
475
        if type_[0] == 'integer':
476
            return self.is_valid_int(val)
477
        if type_[0] == 'string':
478
            return self.is_valid_string(val)
479 480 481
        if re.match(type_[1], val):
            return val
        return None
482 483

    def set(self, optname, value):
484
        if optname not in self.__options[1]:
485
            return
486
        value = self.is_valid(self.__options[0][optname][Option.TYPE], value)
487 488 489
        if value is None:
            return

490
        self.__options[1][optname] = value
491
        self._timeout_save()
492

493
    def get(self, optname=None):
494
        if not optname:
Yann Leboulanger's avatar
Yann Leboulanger committed
495
            return list(self.__options[1].keys())
496 497 498 499 500 501 502
        if optname not in self.__options[1]:
            return None
        return self.__options[1][optname]

    def get_default(self, optname):
        if optname not in self.__options[0]:
            return None
503
        return self.__options[0][optname][Option.VAL]
504 505 506

    def get_type(self, optname):
        if optname not in self.__options[0]:
507
            return None
508
        return self.__options[0][optname][Option.TYPE][0]
509 510

    def get_desc(self, optname):
511
        if optname not in self.__options[0]:
512
            return None
513 514
        if len(self.__options[0][optname]) > Option.DESC:
            return self.__options[0][optname][Option.DESC]
515 516

    def get_restart(self, optname):
517
        if optname not in self.__options[0]:
518
            return None
519 520
        if len(self.__options[0][optname]) > Option.RESTART:
            return self.__options[0][optname][Option.RESTART]
521 522 523 524 525 526 527 528 529

    def add_per(self, typename, name): # per_group_of_option
        if typename not in self.__options_per_key:
            return

        opt = self.__options_per_key[typename]
        if name in opt[1]:
            # we already have added group name before
            return 'you already have added %s before' % name
530 531
        opt[1][name] = {}
        for o in opt[0]:
532
            opt[1][name][o] = opt[0][o][Option.VAL]
533
        self._timeout_save()
534

535
    def del_per(self, typename, name, subname=None): # per_group_of_option
536 537 538 539 540 541 542 543 544
        if typename not in self.__options_per_key:
            return

        opt = self.__options_per_key[typename]
        if subname is None:
            del opt[1][name]
        # if subname is specified, delete the item in the group.
        elif subname in opt[1][name]:
            del opt[1][name][subname]
545
        self._timeout_save()
546

547 548 549 550 551 552 553 554 555 556 557 558 559 560
    def del_all_per(self, typename, subname):
        # Deletes all settings per typename
        # Example: Delete `account_label` for all accounts
        if typename not in self.__options_per_key:
            raise ValueError('typename %s does not exist' % typename)

        opt = self.__options_per_key[typename]
        for name in opt[1]:
            try:
                del opt[1][name][subname]
            except KeyError:
                pass
        self._timeout_save()

561 562 563 564 565 566 567 568 569 570 571
    def set_per(self, optname, key, subname, value): # per_group_of_option
        if optname not in self.__options_per_key:
            return
        if not key:
            return
        dict_ = self.__options_per_key[optname][1]
        if key not in dict_:
            self.add_per(optname, key)
        obj = dict_[key]
        if subname not in obj:
            return
572
        typ = self.__options_per_key[optname][0][subname][Option.TYPE]
573
        value = self.is_valid(typ, value)
574 575
        if value is None:
            return
576
        obj[subname] = value
577
        self._timeout_save()
578

Philipp Hörist's avatar
Philipp Hörist committed
579
    def get_per(self, optname, key=None, subname=None, default=None): # per_group_of_option
580 581 582 583
        if optname not in self.__options_per_key:
            return None
        dict_ = self.__options_per_key[optname][1]
        if not key:
Yann Leboulanger's avatar
Yann Leboulanger committed
584
            return list(dict_.keys())
585
        if key not in dict_:
Philipp Hörist's avatar
Philipp Hörist committed
586 587
            if default is not None:
                return default
588 589
            if subname in self.__options_per_key[optname][0]:
                return self.__options_per_key[optname][0][subname][1]
590 591 592 593 594 595
            return None
        obj = dict_[key]
        if not subname:
            return obj
        if subname not in obj:
            return None
596
        return obj[subname]
597

598
    def get_default_per(self, optname, subname):
599 600
        if optname not in self.__options_per_key:
            return None
601 602 603
        dict_ = self.__options_per_key[optname][0]
        if subname not in dict_:
            return None
604
        return dict_[subname][Option.VAL]
605 606 607 608 609 610 611

    def get_type_per(self, optname, subname):
        if optname not in self.__options_per_key:
            return None
        dict_ = self.__options_per_key[optname][0]
        if subname not in dict_:
            return None
612
        return dict_[subname][Option.TYPE][0]
613

614
    def get_desc_per(self, optname, subname=None):
615 616 617
        if optname not in self.__options_per_key:
            return None
        dict_ = self.__options_per_key[optname][0]
618
        if subname not in dict_:
619
            return None
620 621 622
        obj = dict_[subname]
        if len(obj) > Option.DESC:
            return obj[Option.DESC]
623 624
        return None

625
    def get_restart_per(self, optname, key=None, subname=None):
626 627
        if optname not in self.__options_per_key:
            return False
628
        dict_ = self.__options_per_key[optname][0]
629 630 631 632 633 634 635 636 637
        if not key:
            return False
        if key not in dict_:
            return False
        obj = dict_[key]
        if not subname:
            return False
        if subname not in obj:
            return False
638 639
        if len(obj[subname]) > Option.RESTART:
            return obj[subname][Option.RESTART]
640 641 642 643 644 645 646 647 648 649 650 651 652 653 654
        return False

    def should_log(self, account, jid):
        """
        Should conversations between a local account and a remote jid be logged?
        """
        no_log_for = self.get_per('accounts', account, 'no_log_for')

        if not no_log_for:
            no_log_for = ''

        no_log_for = no_log_for.split()

        return (account not in no_log_for) and (jid not in no_log_for)

Philipp Hörist's avatar
Philipp Hörist committed
655 656 657 658 659
    def notify_for_muc(self, room):
        all_ = self.get('notify_on_all_muc_messages')
        room = self.get_per('rooms', room, 'notify_on_all_messages')
        return all_ or room

660 661 662 663 664
    def get_options(self, optname, return_type=str):
        options = self.get(optname).split(',')
        options = [return_type(option.strip()) for option in options]
        return options

665 666
    def _init_options(self):
        for opt in self.__options[0]:
667
            self.__options[1][opt] = self.__options[0][opt][Option.VAL]
668

669 670 671
        if gajim.IS_PORTABLE:
            self.__options[1]['use_keyring'] = False

672
    def _really_save(self):
673 674 675
        from gajim.common import app
        if app.interface:
            app.interface.save_config()
676
        self.save_timeout_id = None
677 678 679 680 681
        return False

    def _timeout_save(self):
        if self.save_timeout_id:
            return
Yann Leboulanger's avatar
Yann Leboulanger committed
682
        self.save_timeout_id = GLib.timeout_add(1000, self._really_save)
683

684 685
    def __init__(self):
        #init default values
686
        self._init_options()
687
        self.save_timeout_id = None
688 689 690 691 692 693 694 695 696 697 698
        for event in self.soundevents_default:
            default = self.soundevents_default[event]
            self.add_per('soundevents', event)
            self.set_per('soundevents', event, 'enabled', default[0])
            self.set_per('soundevents', event, 'path', default[1])

        for status in self.defaultstatusmsg_default:
            default = self.defaultstatusmsg_default[status]
            self.add_per('defaultstatusmsg', status)
            self.set_per('defaultstatusmsg', status, 'enabled', default[0])
            self.set_per('defaultstatusmsg', status, 'message', default[1])