Completed
Push — master ( deac0a...381858 )
by Stephan
31s
created

main()   F

Complexity

Conditions 25

Size

Total Lines 139

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 25
c 1
b 1
f 0
dl 0
loc 139
rs 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like main() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
"""Entry point and related functionality"""
2
3
__author__ = "Stephan Sokolow (deitarion/SSokolow)"
4
__license__ = "GNU GPL 2.0 or later"
5
6
7
import errno, logging, os, subprocess, sys
8
from ConfigParser import RawConfigParser
9
10
try:
11
    import pygtk
12
    pygtk.require('2.0')
13
except ImportError:
14
    pass  # Apparently Travis-CI's build environment doesn't add this
15
16
import gtk, wnck
17
18
import gtkexcepthook
19
gtkexcepthook.enable()
20
21
from . import commands, layout
22
from .util import fmt_table, XInitError
23
from .version import __version__
24
from .wm import WindowManager
25
26
#: Location for config files (determined at runtime).
27
XDG_CONFIG_DIR = os.environ.get('XDG_CONFIG_HOME',
28
                                os.path.expanduser('~/.config'))
29
30
#: Default content for the config file
31
DEFAULTS = {
32
    'general': {
33
        # Use Ctrl+Alt as the default base for key combinations
34
        'ModMask': '<Ctrl><Alt>',
35
        'UseWorkarea': True,
36
        'ColumnCount': 3
37
    },
38
    'keys': {
39
        "KP_Enter": "monitor-switch",
40
        "KP_0": "maximize",
41
        "KP_1": "bottom-left",
42
        "KP_2": "bottom",
43
        "KP_3": "bottom-right",
44
        "KP_4": "left",
45
        "KP_5": "middle",
46
        "KP_6": "right",
47
        "KP_7": "top-left",
48
        "KP_8": "top",
49
        "KP_9": "top-right",
50
        "V": "vertical-maximize",
51
        "H": "horizontal-maximize",
52
        "C": "move-to-center",
53
    }
54
}
55
56
KEYLOOKUP = {
57
    ',': 'comma',
58
    '.': 'period',
59
    '+': 'plus',
60
    '-': 'minus',
61
}  #: Used for resolving certain keysyms
62
63
64
wnck.set_client_type(wnck.CLIENT_TYPE_PAGER)  # pylint: disable=no-member
65
66
class QuickTileApp(object):
67
    """The basic Glib application itself."""
68
69
    keybinder = None
70
    dbus_name = None
71
    dbus_obj = None
72
73
    def __init__(self, winman, commands, keys=None, modmask=None):
74
        """Populate the instance variables.
75
76
        @param keys: A dict mapping X11 keysyms to L{CommandRegistry}
77
            command names.
78
        @param modmask: A modifier mask to prefix to all keybindings.
79
        @type winman: The L{WindowManager} instance to use.
80
        @type keys: C{dict}
81
        @type modmask: C{GdkModifierType}
82
        """
83
        self.winman = winman
84
        self.commands = commands
85
        self._keys = keys or {}
86
        self._modmask = modmask or gtk.gdk.ModifierType(0)
87
88
    def run(self):
89
        """Initialize keybinding and D-Bus if available, then call
90
        C{gtk.main()}.
91
92
        @returns: C{False} if none of the supported backends were available.
93
        @rtype: C{bool}
94
95
        @todo 1.0.0: Retire the C{doCommand} name. (API-breaking change)
96
        """
97
98
        # Attempt to set up the global hotkey support
99
        try:
100
            from . import keybinder
101
        except ImportError:
102
            logging.error("Could not find python-xlib. Cannot bind keys.")
103
        else:
104
            self.keybinder = keybinder.init(
105
                self._modmask, self._keys, self.commands, self.winman)
106
107
        # Attempt to set up the D-Bus API
108
        try:
109
            from . import dbus_api
110
        except ImportError:
111
            logging.warn("Could not load DBus backend. "
112
                         "Is python-dbus installed?")
113
        else:
114
            self.dbus_name, self.dbus_obj = dbus_api.init(
115
                self.commands, self.winman)
116
117
        # If either persistent backend loaded, start the GTK main loop.
118
        if self.keybinder or self.dbus_obj:
119
            try:
120
                gtk.main()  # pylint: disable=no-member
121
            except KeyboardInterrupt:
122
                pass
123
            return True
124
        else:
125
            return False
126
127
    def show_binds(self):
128
        """Print a formatted readout of defined keybindings and the modifier
129
        mask to stdout.
130
131
        @todo: Look into moving this into L{KeyBinder}
132
        """
133
134
        print "Keybindings defined for use with --daemonize:\n"
135
        print "Modifier: %s\n" % self._modmask
136
        print fmt_table(self._keys, ('Key', 'Action'))
137
138
def main():
139
    """setuptools entry point"""
140
    from optparse import OptionParser, OptionGroup
141
    parser = OptionParser(usage="%prog [options] [action] ...",
142
            version="%%prog v%s" % __version__)
143
    parser.add_option('-d', '--daemonize', action="store_true",
144
        dest="daemonize", default=False, help="Attempt to set up global "
145
        "keybindings using python-xlib and a D-Bus service using dbus-python. "
146
        "Exit if neither succeeds")
147
    parser.add_option('-b', '--bindkeys', action="store_true",
148
        dest="daemonize", default=False,
149
        help="Deprecated alias for --daemonize")
150
    parser.add_option('--debug', action="store_true", dest="debug",
151
        default=False, help="Display debug messages")
152
    parser.add_option('--no-workarea', action="store_true", dest="no_workarea",
153
        default=False, help="Overlap panels but work better with "
154
        "non-rectangular desktops")
155
156
    help_group = OptionGroup(parser, "Additional Help")
157
    help_group.add_option('--show-bindings', action="store_true",
158
        dest="show_binds", default=False, help="List all configured keybinds")
159
    help_group.add_option('--show-actions', action="store_true",
160
        dest="show_args", default=False, help="List valid arguments for use "
161
        "without --daemonize")
162
    parser.add_option_group(help_group)
163
164
    opts, args = parser.parse_args()
165
166
    # Hook up grep to filter out spurious libwnck error messages that we
167
    # can't filter properly because PyGTK doesn't expose g_log_set_handler()
168
    if not opts.debug:
169
        glib_log_filter = subprocess.Popen(
170
                ['grep', '-v', 'Unhandled action type _OB_WM'],
171
                stdin=subprocess.PIPE)
172
173
        # Redirect stderr through grep
174
        os.dup2(glib_log_filter.stdin.fileno(), sys.stderr.fileno())
175
176
    # Set up the output verbosity
177
    logging.basicConfig(level=logging.DEBUG if opts.debug else logging.INFO,
178
                        format='%(levelname)s: %(message)s')
179
180
    # Load the config from file if present
181
    # TODO: Refactor all this
182
    cfg_path = os.path.join(XDG_CONFIG_DIR, 'quicktile.cfg')
183
    first_run = not os.path.exists(cfg_path)
184
185
    config = RawConfigParser()
186
    config.optionxform = str  # Make keys case-sensitive
187
    # TODO: Maybe switch to two config files so I can have only the keys in the
188
    #       keymap case-sensitive?
189
    config.read(cfg_path)
190
    dirty = False
191
192
    if not config.has_section('general'):
193
        config.add_section('general')
194
        # Change this if you make backwards-incompatible changes to the
195
        # section and key naming in the config file.
196
        config.set('general', 'cfg_schema', 1)
197
        dirty = True
198
199
    for key, val in DEFAULTS['general'].items():
200
        if not config.has_option('general', key):
201
            config.set('general', key, str(val))
202
            dirty = True
203
204
    mk_raw = modkeys = config.get('general', 'ModMask')
205
    if ' ' in modkeys.strip() and '<' not in modkeys:
206
        modkeys = '<%s>' % '><'.join(modkeys.strip().split())
207
        logging.info("Updating modkeys format:\n %r --> %r", mk_raw, modkeys)
208
        config.set('general', 'ModMask', modkeys)
209
        dirty = True
210
211
    # Either load the keybindings or use and save the defaults
212
    if config.has_section('keys'):
213
        keymap = dict(config.items('keys'))
214
    else:
215
        keymap = DEFAULTS['keys']
216
        config.add_section('keys')
217
        for row in keymap.items():
218
            config.set('keys', row[0], row[1])
219
        dirty = True
220
221
    # Migrate from the deprecated syntax for punctuation keysyms
222
    for key in keymap:
223
        # Look up unrecognized shortkeys in a hardcoded dict and
224
        # replace with valid names like ',' -> 'comma'
225
        transKey = key
226
        if key in KEYLOOKUP:
227
            logging.warn("Updating config file from deprecated keybind syntax:"
228
                    "\n\t%r --> %r", key, KEYLOOKUP[key])
229
            transKey = KEYLOOKUP[key]
230
            dirty = True
231
232
    if dirty:
233
        cfg_file = file(cfg_path, 'wb')
234
        config.write(cfg_file)
235
        cfg_file.close()
236
        if first_run:
237
            logging.info("Wrote default config file to %s", cfg_path)
238
239
    ignore_workarea = ((not config.getboolean('general', 'UseWorkarea')) or
240
                       opts.no_workarea)
241
242
    # TODO: Rearchitect so this hack isn't needed
243
    commands.cycle_dimensions = commands.commands.add_many(
244
        layout.make_winsplit_positions(config.getint('general', 'ColumnCount'))
245
    )(commands.cycle_dimensions)
246
247
    try:
248
        winman = WindowManager(ignore_workarea=ignore_workarea)
249
    except XInitError as err:
250
        logging.critical(err)
251
        sys.exit(1)
252
    app = QuickTileApp(winman, commands.commands, keymap, modmask=modkeys)
253
254
    if opts.show_binds:
255
        app.show_binds()
256
    if opts.show_args:
257
        print commands.commands
258
259
    if opts.daemonize:
260
        if not app.run():
261
            logging.critical("Neither the Xlib nor the D-Bus backends were "
262
                             "available")
263
            sys.exit(errno.ENOENT)
264
            # FIXME: What's the proper exit code for "library not found"?
265
    elif not first_run:
266
        if args:
267
            winman.screen.force_update()
268
269
            for arg in args:
270
                commands.commands.call(arg, winman)
271
            while gtk.events_pending():  # pylint: disable=no-member
272
                gtk.main_iteration()  # pylint: disable=no-member
273
        elif not opts.show_args and not opts.show_binds:
274
            print commands.commands
275
            print "\nUse --help for a list of valid options."
276
            sys.exit(errno.ENOENT)
277
278
if __name__ == '__main__':
279
    main()
280
281
# vim: set sw=4 sts=4 expandtab :
282