ADDED CMakeLists.txt Index: CMakeLists.txt ================================================================== --- CMakeLists.txt +++ CMakeLists.txt @@ -0,0 +1,48 @@ +# +# top-level minx build spec +# + +# +# Copyright (C) 2012 The Minx Project Developers +# +# See wiki/copyright.wiki for the full list of authors who have +# contributed to this project. +# + +# +# This file is part of Minx. +# +# Minx 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, either version 3 of the License, or (at your +# option) any later version. +# +# Minx 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 Minx. If not, see . +# + +# Min cmake version +cmake_minimum_required(VERSION 2.8) + +# Project name +project(minx) + +# Build sub-projects +add_subdirectory(minxlib) +add_subdirectory(dox) + +############################################## +# Editor config: # +############################################## +# Local Variables: # +# indent-tabs-mode: nil # +# sh-basic-offset: 4 # +# End: # +############################################## +# vim: set expandtab shiftwidth=4 tabstop=4: # +############################################## ADDED __init__.py Index: __init__.py ================================================================== --- __init__.py +++ __init__.py @@ -0,0 +1,46 @@ +# +# __init__.py -- initialization for minx module +# + +# +# Copyright (C) 2012 The Minx Project Developers +# +# See wiki/copyright.wiki for the full list of authors who have +# contributed to this project. +# + +# +# This file is part of Minx. +# +# Minx 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, either version 3 of the License, or (at your +# option) any later version. +# +# Minx 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 Minx. If not, see . +# + +#----------------------------- EXPORTS --------------------------------- + +__all__ = ["core"] + +# Pull in minx.core so that users don't have to do it explicitly +# themselves... +import core + +#----------------------------------------------------------------------- + +# Editor config: +# +# Local Variables: +# indent-tabs-mode: nil +# py-indent-offset: 4 +# End: +# +# vim: expandtab shiftwidth=4 tabstop=4 ADDED core/__init__.py Index: core/__init__.py ================================================================== --- core/__init__.py +++ core/__init__.py @@ -0,0 +1,53 @@ +# +# __init__.py -- initialization for minx.core module +# + +# +# Copyright (C) 2012 The Minx Project Developers +# +# See wiki/copyright.wiki in the top-level directory of the Minx source +# distribution for the full list of authors who have contributed to this +# project. +# + +# +# This file is part of Minx. +# +# Minx 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, either version 3 of the License, or (at your +# option) any later version. +# +# Minx 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 Minx. If not, see . +# + +#----------------------------- IMPORTS --------------------------------- + +# Pull in various classes from minx.core so that end-users see them as +# minx.core.wm instead of minx.core.wm.wm, minx.core.config instead of +# minx.core.config.config, and so on. +from wm import wm +from config import config + +#----------------------------- EXPORTS --------------------------------- + +__all__ = ["wm", "config", "hooks"] + +#----------------------------------------------------------------------- + +############################################## +# Editor config: # +############################################## +# Local Variables: # +# indent-tabs-mode: nil # +# py-indent-offset: 4 # +# End: # +############################################## +# vim: set expandtab shiftwidth=4 tabstop=4: # +############################################## ADDED core/config.py Index: core/config.py ================================================================== --- core/config.py +++ core/config.py @@ -0,0 +1,258 @@ +# +# config.py -- various end-user settings +# + +# +# Copyright (C) 2012 The Minx Project Developers +# +# See wiki/copyright.wiki in the top-level directory of the Minx source +# distribution for the full list of authors who have contributed to this +# project. +# + +# +# This file is part of Minx. +# +# Minx 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, either version 3 of the License, or (at your +# option) any later version. +# +# Minx 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 Minx. If not, see . +# + +#------------------------ MODULE DOC STRING ---------------------------- + +"""@package minx.core.config +Settings object. + +This file defines a config class that contains attributes for various +settings through which end-users can effect simple customizations. + +""" + +#------------------------- CLASS DEFINITION ---------------------------- + +class config: + """Attributes for simple customizations. + + This class has the following attributes to allow simple + customizations. In the list below, the parenthesized term specifies + the type of the attribute and the term in square brackets is the + attribute's default value. + +

Focus-related Customizations

+ + @li active_border_color (int) [0xFF0000]: + This attribute specifies the color of the border of the + window with the input focus. It should be a three-byte RGB + packed into a single integer. The easiest way to specify + this is as a 24-bit hex number, where byte 0 is blue, byte 1 + is green, and byte 2 is red. By default, the active window's + border color is red. + @li inactive_border_color (int) [0xFFFFFF]: + This attribute specifies the color of unfocused windows. By + default, this color is white. + @li active_border_size (int) [1]: + This attribute specifies the size (in pixels) of the border + of the window with the input focus. + @li inactive_border_size (int) [1]: + This attribute specifies the border size (in pixels) of + inactive windows. + +

Logging Configuration

+ + @li logger (dict) [see below]: + This field is passed to + logging.config.dictConfig(). + The default configuration sets up a + logging.NullHandler + to suppress Minx's log messages. + + To enable logging, use either log_to_console() to send log + messages to STDERR or log_to_file() to send them to a file. + You can also use log_level() to configure the level of + verbosity. + + The above three functions should be enough to configure + logging for the most common cases. However, you can further + customize logging by retrieving the keys of the logger dict + and reassigning them to the desired values. The following + paragraphs provide some details. + + logger['formatters']['minx_formatter'] will get you + a reference to the formatter for Minx's log messages. This + formatter is itself a dict with the keys 'format' + and 'datefmt'. Consult the + Python logging documentation + for further details about formatters. + + logger['handlers']['minx_handler'] yields Minx's + logging handler, which is also a dict with keys + 'class' and 'formatter'. As mentioned + above, the handler's 'class' is + 'logging.NullHandler'. The value of the + 'formatter' key is 'minx_formatter'. You + should reset 'class' to another logging handler + class (e.g., 'logging.FileHandler'). You should + also set other keys appropriately. Consult the Python + logging module's documentation for more info. + + Instead of using the predefined 'minx_formatter' + and 'minx_handler' keys as described above, you can + also reset the config object's logger + attribute to anything you want. + +

Miscellaneous Settings

+ + @li terminal (string) [xterm]: + This field specifies the command to use for launching a + terminal window. Your PATH variable will be searched to find + it; so you don't have to specify a full path. Moreover, you + can supply command-line arguments for the terminal program + in this setting and they will be passed as-is. Minx's + default configuration defines a + config.logger attribute's complicated + dict-of-dict-of-dict structure. Using it, you can easily + configure Minx's log messages to be printed to STDERR. + + """ + handler = self.logger['handlers']['minx_handler'] + handler['class'] = 'logging.StreamHandler' + + # Convenience function for configuring logging to a file + def log_to_file(self, name, mode = 'w'): + """Send log messages to a file. + + @param name Log file's name. + @param mode Log file open mode; default = 'w'. + + One of the most common configurations for logging is to send log + messages to a file. This function configures the + config.logger attribute so that log messages will be + written to the named file. By default, the log will be + overwritten. However, if you use the value 'a' for the + mode parameter, log messages will be appended to the + file. + + As described earlier, you can configure Minx's logging by + directly manipulating the config.logger dict. However, + since that process can be somewhat convoluted and because + sending log messages to a file is a fairly common thing to do, + it's easier to use this function. + + """ + handler = self.logger['handlers']['minx_handler'] + handler['class' ] = 'logging.FileHandler' + handler['filename'] = name + handler['mode' ] = mode + + # Convenience function for configuring logging level + def log_level(self, level): + """Set the level of logging verbosity. + + @param level The log level from the Python logging module. + + The default log level is logging.WARNING. Thus, only + WARNING, ERROR, and CRITICAL messages + will be printed. However, with this function, you can change the + log level to either produce more or fewer messages. + + Consult the Python logging documentation for more details about + logging levels. + + """ + logger = self.logger['loggers' ]['minx'] + handler = self.logger['handlers']['minx_handler'] + logger ['level'] = level + handler['level'] = level + +#----------------------------------------------------------------------- + +############################################## +# Editor config: # +############################################## +# Local Variables: # +# indent-tabs-mode: nil # +# py-indent-offset: 4 # +# python-indent: 4 # +# End: # +############################################## +# vim: set expandtab shiftwidth=4 tabstop=4: # +############################################## ADDED core/focus_list.py Index: core/focus_list.py ================================================================== --- core/focus_list.py +++ core/focus_list.py @@ -0,0 +1,204 @@ +# +# focus_list.py -- circular, doubly linked list of top-level windows +# + +# +# Copyright (C) 2012 The Minx Project Developers +# +# See wiki/copyright.wiki in the top-level directory of the Minx source +# distribution for the full list of authors who have contributed to this +# project. +# + +# +# This file is part of Minx. +# +# Minx 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, either version 3 of the License, or (at your +# option) any later version. +# +# Minx 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 Minx. If not, see . +# + +#------------------------ MODULE DOC STRING ---------------------------- + +"""@package wm.core.focus_list + +This file defines the minx.core.focus_list.focus_list class, which can +be referred to simply as minx.core.focus_list. + +""" + +#------------------------- CLASS DEFINITION ---------------------------- + +class focus_list: + """A doubly-linked circular list for keeping track of windows that + can be focused. + + This class implements a doubly-linked circular list that is meant to + be used by @ref minx.core.wm.wm "the window manager" for keeping + track of the list of @ref minxlib::window "top-level windows" that + can be focused. The head of this list will always be taken to be the + currently focused window. + + """ + + # Internal class for storing an item and references to its two + # neighbours. + class _node: + def __init__(self, item): + self._item = item + self._left = None + self._right = None + + # Constructor + def __init__(self): + """Create an empty list. + + """ + self._head = None + + # Adding elements to the focus list + def add(self, item): + """Insert an item at the beginning of the list. + + @param item The item to be added. + + This method inserts item (which can be of any type) at the + beginning of the list, taking care to maintain circularity. + + @note We add new items to the beginning of the list because this + is usually how we want input focus to behave: when a new window + is created, it should acquire the input focus. + + """ + node = self._node(item) # create a new node to store item + if self._head == None: # list is empty + node._left = node + node._right = node + else: # list has at least one element + tail = self._head._left + tail._right = node + node._left = tail + node._right = self._head + self._head._left = node + self._head = node + + # Removing items from from the focus list + def remove(self, item): + """Remove specified item from list. + + @param item List item to be removed. + + This method searches the list for item and, if it is present, + removes it, taking care to maintain circularity. If item is the + first element in the list, the second element will become the + list's new head after the removal. + + @note To be able to find item, we need its type to support the + equality operator. Also, if item occurs more than once, the + first instance will be removed. + + """ + node = self._find(item) + if node == None: # list does not contain specified item + return # therefore, nothing to remove + else: + self._remove_node(node) + + # Helper function for node removal + def _remove_node(self, node): + if node == self._head: # removing list's first element + if (self._head._left == self._head and + self._head._right == self._head): # list has only one element + self._head._left = None + self._head._right = None + self._head = None + else: # removing first element when list has more than one item + tail = self._head._left + tail._right = self._head._right + self._head._right._left = tail + self._head._left = None + self._head._right = None + self._head = tail._right + else: # removing an item after the lists' first element + node._left._right = node._right + node._right._left = node._left + node._left = None + node._right = None + + # Helper function for finding items in list + def _find(self, item): + if self._head == None: # list is empty + return None # therefore, it cannot contain item + + if self._head._item == item: # item is list's first element + return self._head # so let's return that + + # Look for item starting at second element of list + node = self._head._right + while node != self._head: + if node._item == item: # found it! + return node + node = node._right # no luck yet, examine next element + + # If we get to this point, then the list does not contain item + return None + + # Return list's first element + def head(self): + """Return list's first element or None if list is empty. + + """ + if self._head == None: + return None + return self._head._item + + def forward(self): + """Move head to next element. + + This method is meant to allow moving the input focus from the + current @ref minxlib::window "top-level window" to the next one + in the @ref minx.core.wm.wm "window manager's" list of windows + that can be focused. + + If the focus list is empty, this function does nothing. + + """ + if self._head != None: + self._head = self._head._right + + def backward(self): + """Move head to previous element. + + This method is meant to allow moving the input focus from the + current @ref minxlib::window "top-level window" to the previous + one in the @ref minx.core.wm.wm "window manager's" list of + windows that can be focused. + + If the focus list is empty, this function does nothing. + + """ + if self._head != None: + self._head = self._head._left + +#----------------------------------------------------------------------- + +############################################## +# Editor config: # +############################################## +# Local Variables: # +# indent-tabs-mode: nil # +# py-indent-offset: 4 # +# python-indent: 4 # +# End: # +############################################## +# vim: set expandtab shiftwidth=4 tabstop=4: # +############################################## ADDED core/hooks.py Index: core/hooks.py ================================================================== --- core/hooks.py +++ core/hooks.py @@ -0,0 +1,683 @@ +## +# @file hooks.py +# @brief Minx's hooking infrastructure. +# @defgroup grp_minx_core_hooks Hooks Infrastructure +# + +# +# Copyright (C) 2012 The Minx Project Developers +# +# See wiki/copyright.wiki in the top-level directory of the Minx source +# distribution for the full list of authors who have contributed to this +# project. +# + +# +# This file is part of Minx. +# +# Minx 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, either version 3 of the License, or (at your +# option) any later version. +# +# Minx 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 Minx. If not, see . +# + +#------------------------ MODULE DOC STRING ---------------------------- + +"""@ingroup grp_minx_core_hooks +Minx's infrastructure for defining and using hooks. + +This module defines the minx.core.hooks.hooks and related classes that +implement the infrastructure for defining and triggering hook functions, +which are callbacks that Minx invokes in response to different events. +Minx uses hooks both internally (to handle events sent by the X server) +and externally (to allow end-users to customize its responses to and +also to be notified of different window manager events). + +""" + +#----------------------------- IMPORTS --------------------------------- + +# Standard library +from heapq import heappush +import logging + +#-------------------------- MODULE LOGGER ------------------------------ + +logger = logging.getLogger(__name__) + +#------------------------- CLASS DEFINITION ---------------------------- + +class hooks: + """@ingroup grp_minx_core_hooks + Mapping arbitrary key types to callable objects. + + This class maps keys (strings, ints, whatever) to prioritized lists + of callables (i.e., functions or function objects). After a mapping + has been setup, clients can trigger the functions for a key. + + This class provides a general infrastructure for Minx's support for + customization via hooks. Although we can use any (reasonable) type + as the keys, in Minx, keys are strings that name the different hooks + that Minx supports (e.g., 'manage_hook', 'x_create_notify', and so + on). If you name your hooks using a non-string type, those hooks + will be added to the map but simply be ignored. + + """ + + MIN_PRIORITY = 0 + MAX_PRIORITY = 101 + + # Constructor + def __init__(self): + """Create an empty hook map. + + @return An empty hook map. + + When a new hooks object is created, it will have no hook + functions in it. Clients (i.e., other Minx classes and + functions or end-user configuration code) will have to then add + hook functions to the hook map. + + @note In general, there should be no need in end-user code to + create a hooks object. Rather, end-users should use the hooks + attribute of the @ref minx.core.wm.wm "main window manager object". + Refer to the Hooks HOWTO + for more on typical usage patterns for this class. + + """ + self._hooks = {} + + # Add a new function for some key + def add(self, k, f, p = None): + """Add a new function for some key. + + @param k The name of the hook. + @param f The function to be executed for k. + @param p The function priority (must be in range [1, 100]). + + This method adds f to the (prioritized) list of functions + corresponding to k. If f is already in the list for k, it will + not be duplicated. Thus, you can safely call this function + multiple times with the same hook without worrying about + creating duplicate entries in the hook lists or needing to + first check to avoid duplication. This is useful, for example, + when creating window objects that add hooks for various X + events; the same function will be passed for each window + instance and we don't want duplicate entries of the same + function in our hook map (because each hook should be called + once and only once per triggering). + + Priorities should be integers in the range [1, 100]. Higher + numbers will insert f into the beginning of the list. If the + priority p is not supplied, it will be assigned a default + priority. Passing a non-integral value for the parameter p will + also result in the priority for f being set to the default + priority. + + To add a hook at the highest priority, pass MAX_PRIORITY as the + value of p. Similarly, to add a hook at the lowest priority, pass + MIN_PRIORITY as the value of p. Alternatively, to add hooks at + the highest or lowest priorities, you can pass values greater + than hundred or less than one respectively (however, using + MAX_PRIORITY and MIN_PRIORITY is probably clearer). + + Here is a snippet of code illustrating how to add a hook for some + event 'foo' so that it is the highest priority hook: + + @verbatim + wm.hooks.add('foo', my_foo_hook, wm.hooks.MAX_PRIORITY) + @endverbatim + + In general, you should not care about the order in which hooks + execute. That is, you should implement hook functions so that the + final result is the same regardless of the order of their + execution. Having said that, however, such idealism is not always + feasible, and, depending on the specific situation, perhaps not + even wholly desirable. Thus, if you do need to control the order + in which your hooks execute, you should be aware that the order + in which you add hooks can be significant, especially if you use + MIN_PRIORITY and MAX_PRIORITY. + + For example, let's say you have two hooks A and B for some key + "foo" and want to execute A before B and B before every other + hook for "foo." If you add the hooks in the following order: + + @verbatim + wm.hooks.add('foo', A, wm.hooks.MAX_PRIORITY) + wm.hooks.add('foo', B, wm.hooks.MAX_PRIORITY) + @endverbatim + + B will end up at a higher priority than A and will execute before + A instead of the other way around. This is because, MIN_PRIORITY + and MAX_PRIORITY look at the current minimum and maximum + priorities and are not absolute values. To achieve the effect + described above, you should first add B and then A. + Alternatively, you could also do the following: + + @verbatim + wm.hooks.add('foo', A, wm.hooks.MAX_PRIORITY) + wm.hooks.add('foo', B, wm.hooks.max_priority('foo')) + @endverbatim + + The above code will first add A at the current highest priority + for "foo" and then add B at the same priority as A. + + @note As mentioned earlier, although you can use any + (reasonable) type for the hook map's keys, in Minx, we use + strings as the keys. That is, pass a string as the first + parameter k. + + """ + if k not in self._hooks: + self._hooks[k] = priority_queue() + + if p == None or not isinstance(p, int): + p = self.default_priority() + elif p < 1: + p = self.min_priority(k) + if p == -1: + p = self.default_priority() - 1 + else: + p = max(1, p - 1) + elif p > 100: + p = self.max_priority(k) + if p == -1: + p = self.default_priority() + 1 + else: + p = min(p + 1, 100) + + if self._hooks[k].add(f, p): + logger.info('added hook: [{}, {}, {}]'.format(k, f.__name__, p)) + + # Remove all hooks for specified name + def remove(self, k): + """Remove all hooks for specified name. + + @param k Name of hook that is to be removed. + + This function will remove all the hooks corresponding to the + name k. + + Please understand that this is a fairly dangerous function! It + is intended to be used in conjunction with hooks for key + bindings. Specifically, you can use it to remove the default key + bindings. Using it to remove other hooks will almost certainly + lead to trouble. + + @note As mentioned earlier, although you can use any + (reasonable) type for the hook map's keys, in Minx, we use + strings as the keys. That is, pass a string for the parameter k. + + """ + if k in self._hooks: + del self._hooks[k] + logger.info('removed {} hooks'.format(k)) + + # Rename specified hook + def rename(self, k, n): + """Rename the specified hook. + + @param k Current name of hook to be renamed. + @param n New hook name. + + This function will assign the name n to the hooks currently + identified by k. If the hook map does not contain any hooks + corresponding to k, the function will do nothing. However, if + there are already hooks assigned to n and to k, the older hooks + for n will be replaced by the ones for k. + + Needless to say, this is a fairly dangerous function! Use it + only to rename the default key bindings. Renaming other hooks is + a Very Bad Idea (TM). + + @note Both k and n should be strings. Although you can use any + (reasonable) type as keys for the hook map, Minx convention is + to use strings. Other key types will result in "ghost" hooks, + i.e., hook functions that are never triggered. + + """ + if k in self._hooks: + self._hooks[n] = self._hooks[k] + del self._hooks[k] + logger.info('renamed {} hooks to {}'.format(k, n)) + + # Call all the functions corresponding to a key + def trigger(self, k, *p): + """Execute functions for a given key. + + @param k The name of the hook whose functions we want to call. + @param p Parameters to be passed to the hook functions. + @return List of hook return values. + + This method triggers all the functions corresponding to k, + calling them in order of descending priorities. Functions that + have the same priority will be executed in the order in which + they were added to the hook map. + + Each function will be passed the parameters in the positional + args tuple p. Thus, hook functions can take an arbitrary number + of arguments. Furthermore, these hook function parameters can be + of any type; this method doesn't care and simply passes them + through to the hooks as-is. + + This function collects the return values of all the hook + functions for key k in a list. Callers of this function can use + the return values in whatever way they please. For example, + depending on the specific hook, you may: + + @li Simply ignore the hook return values. + @li Use only the first return value, i.e., the value + returned by the highest-priority hook. + @li Use the last return value (i.e., last hook executed). + @li Use all the return values. + + If a hook wants or needs to cut short the execution of the + remaining hooks in its chain, it can raise a short_circuit + exception. If the hook also wants to return a value along with + cutting short its hook chain, it will have to pass the return + value via the short_circuit exception. + + The hooks.trigger() function will stop executing the hooks for + key k when it sees a short_circuit exception. + + @note As mentioned earlier, although you can use any + (reasonable) type for the hook map's keys, in Minx, we use + strings as the keys. That is, pass a string as the first + parameter k. + + """ + ret = [] + if k in self._hooks: + logger.debug('triggering {} hooks'.format(k)) + try: + for f in self._hooks[k]: + ret.append(f(*p)) + except short_circuit as e: + ret.append(e.retval) + return ret + + # Get all the currently defined keys + def names(self): + """Get names of currently defined hooks. + + @return List of strings containing names of currently defined hooks. + + This function returns the names of all the currently defined + hooks. Callers should not rely on the returned names being in + any particular order. + + @note Although Minx uses strings for the keys in its hook map, + if end-users add hooks using keys of other types, the returned + list will contain those types as well. That is, Minx makes no + effort to enforce using strings as hook names. If you do, that's + fine; however, those particular hooks will not be triggered (so + don't waste your time and your computer's resources). + + """ + return self._hooks.keys() + + # What is the maximum priority for some key? + def max_priority(self, k): + """Priority value of highest-priority hook for some key. + + @param k The name of the hook. + @return Priority (integer) of highest-priority hook corresponding to k. + + This function is intended to allow end-users to determine the + priority of the current highest-priority hook function + corresponding to k so that they can insert higher priority + functions. Here is a somewhat contrived snippet of code + illustrating typical usage: + + @verbatim + #!/usr/bin/env python + + import minx + + def my_manage_hook(w): + prop = w.properties() + if prop['class'].lower() == 'gkrellm': + return False + return True + + # Create window manager + wm = minx.core.wm() + + # Ensure my_manage_hook is the first to execute + m = wm.hooks.max_priority('manage_hook') + if m < 0: # no manage_hook installed + m = wm.hooks.default_priority() + 1 + else: # some module installed a manage_hook + m = m + 1 + wm.hooks.add('manage_hook', my_manage_hook, m) + + # Okay, let's run the window manager... + wm.start() + @endverbatim + + The above example is contrived because if that is all your Minx + start-up file contained, there would be no manage_hook + installed and, so, no need to check to ensure that + my_manage_hook() would execute first. Furthermore, usually, + only a single manage_hook would be needed; there's no point to + having multiple manage hooks. + + Moreover, to add a hook at the highest priority, you can simply + pass MAX_PRIORITY to the call to hooks.add() instead of going + about it yourself. For example, to add the highest priority hook + for some event 'foo', you could just call the hooks.add() method + as shown below: + + @verbatim + wm.hooks.add('foo', my_foo_hook, wm.hooks.MAX_PRIORITY) + @endverbatim + + Nonetheless, the example serves to illustrate how and why you + might want to use the hooks.max_priority() function. + + Note that if the hook map does not contain any hooks for the + key k, this function will return a negative number. + + @note As mentioned earlier, although you can use any + (reasonable) type for the hook map's keys, in Minx, we use + strings as the keys. That is, pass a string for the parameter k. + + """ + if k in self._hooks: + return self._hooks[k].max_priority() + return -1 + + # What is the minimum priority for some key? + def min_priority(self, k): + """Priority value of lowest-priority hook for some key. + + @param k The name of the hook. + @return Priority (integer) of lowest-priority hook corresponding to k. + + This function is intended to allow end-users to determine the + priority of the current lowest-priority hook function + corresponding to k so that they can insert lower priority + functions. + + Here is a somewhat contrived snippet of code illustrating + typical usage: + + @verbatim + #!/usr/bin/env python + + import minx + + def my_manage_hook(w): + prop = w.properties() + if prop['class'].lower() == 'gkrellm': + return False + return True + + # Create window manager + wm = minx.core.wm() + + # Ensure my_manage_hook is the last to execute + m = wm.hooks.min_priority('manage_hook') + if m < 0: # no manage_hook installed + m = wm.hooks.default_priority() - 1 + else: # some module installed a manage_hook + m = m - 1 + wm.hooks.add('manage_hook', my_manage_hook, m) + + # Okay, let's run the window manager... + wm.start() + @endverbatim + + The above example is contrived because if that is all your Minx + start-up file contained, there would be no manage_hook + installed and, so, no need to check to ensure that + my_manage_hook() would execute last. Furthermore, usually, + only a single manage_hook would be needed; there's no point to + having multiple manage hooks. + + Moreover, to add a hook at the lowest priority, you can simply + pass MIN_PRIORITY to the call to hooks.add() instead of going + about it yourself. For example, to add the lowest priority hook + for some event 'foo', you could just call the hooks.add() method + as shown below: + + @verbatim + wm.hooks.add('foo', my_foo_hook, wm.hooks.MIN_PRIORITY) + @endverbatim + + Nonetheless, the example serves to illustrate how and why you + might want to use the hooks.min_priority() function. + + If the hook map does not contain any hooks for the key k, this + function will return a negative number. + + @note Typically, you would want your hooks to execute before + currently installed hooks. Thus, this function is much less + useful than hooks.max_priority(), but is provided for the sake + of completeness and just in case someone or some situation + finds it necessary. + + """ + if k in self._hooks: + return self._hooks[k].min_priority() + return -1 + + # Default hook priority + def default_priority(self): + """Default priority for hooks. + + @return Integer specifying default hook priority. + + To allow end-users greater control over the execution order of + hook functions, the minx.core.hooks.hooks class arranges hook + functions according to priorities, which must be integers in + the range [1, 100]. Higher priority hooks are executed before + lower priority ones. + + If end-users don't specify the priority for a hook, it will be + assigned the default priority, which is the value returned by + this function. + + @note Since hook priorities must lie in [1, 100], we use 50 as + the default priority as this will allow end-users to add their + hooks both above and below the default level. + + """ + return 50 + +#------------------------- CLASS DEFINITION ---------------------------- + +class short_circuit(Exception): + """@ingroup grp_minx_core_hooks + An exception for cutting short the execution of a chain of hooks. + + This class allows an end-user hook function to have Minx stop + excecuting the remaining hooks in its chain of hooks. + + @note In addition to being a customization mechanism available to + end-users, Minx also uses hooks internally for various purposes. + All hooks, internal and external, are stored in a single hook map + maintained by the @ref minx.core.wm.wm "main window manager object". + This design allows for a very powerful way of customizing the + window manager because it gives you access to even the low-level X + event processing... + + @par + However, with great power comes great responsibilty! In general, it + would be unwise to short-circuit Minx's internal hooks. That is, + just because you can, doesn't mean you should. As a rule + of thumb, use short-circuting only when you're absolutely sure it + won't have an adverse effect. + + @par + For example, the manage_hook is expected to return True or + False depending on whether it wants a particular window managed or + not. While it would be unusual to have more than one + manage_hook (because one hook can perform multiple checks + on window properties for all window types), if you do have more than + one manage_hook, the one that returns a False can + short-circuit the rest because Minx will only manage a window if all + the manage hooks return True. Thus, if even one returns False, + there's no point executing the remaining hooks; that one can simply + short-circuit the rest of the manage_hook chain. + + """ + def __init__(self, ret = None): + """Create a short_circuit exception with the specified return value. + + @param ret The hook's return value (default = None). + + If a function raises an exception, it cannot also communicate a + return value to its caller using the usual function return + mechanism. Instead, we allow the short-circuiting hook to stuff + its return value into the short_circuit exception. The + hooks.trigger() function will extract the return value from its + short_circuit exception handler and also stop executing the + remaining hooks in the short-circuiting hook's chain. + + Here is an example of an end-user hook short-circuiting its + chain of hooks and returning a value: + + @verbatim + #!/usr/bin/env python + + import minx + from minx.core.hooks import short_circuit + + def my_manage_hook(w): + prop = w.properties() + if prop['class'].lower() == 'gkrellm': + raise short_circuit(False) + return True + + wm = minx.core.wm() + wm.hooks.add('manage_hook', my_manage_hook) + wm.start() + @endverbatim + + @note The return value can be anything the hook function wants + to return. The exception and hooks classes don't care about it; + they simply pass it back to the Minx function that triggered the + hooks. + + """ + self.retval = ret + +#------------------------- CLASS DEFINITION ---------------------------- + +class priority_queue: + """@ingroup grp_minx_core_hooks + A helper class for Minx's hooks implementation. + + This class implements a priority queue that the hooks class can use + to prioritize its hook function lists. + + @note This class is not a general-purpose priority queue + implementation. It is specifically designed for use by the + minx.core.hooks.hooks class. It is also not meant to be used by + end-users, who should treat it as internal to Minx. + + """ + + # Constructor + def __init__(self): + """Create an empty priority queue. + + """ + self._queue = [] # the priority queue + self._values = {} # all values stored in p.q. (to prevent duplication) + self._count = 0 # for ordering values of the same priority + + # Add element to priority queue + # NOTE: We negate priority because the standard library's heapq + # module defaults to a min heap while we want a max heap (so that + # high-priority hooks are executed first). + def add(self, v, p): + """Add an element with specified priority. + + @param v The value to be added to the priority queue. + @param p The priority of the value to be added. + @return False if priority queue already contains v; True otherwise. + + This function adds v with priority p to the priority queue. + This function does not perform any checks on the value of p, + requiring its caller to implement appropriate policies + regarding priorities. + + If the priority queue already contains v, it will not be added + again. Thus, clients can make repeated calls to this function + with the same value without having to worry about creating + duplicate entries. + + """ + if v not in self._values: + heappush(self._queue, (-p, self._count, v)) # NOTE: -p for max heap + self._values[v] = True # to prevent duplicate entries + self._count += 1 # for ordering values of same priority + return True + return False # priority queue already contains v + + # Priority of highest-priority item + def max_priority(self): + """Return priority of highest-priority item in priority queue. + + If the priority queue is empty, this function will return -1. + + """ + # Each item in the priority queue is a 3-tuple, the first element + # of which is the negated priority (for making a max heap). + if len(self._queue) <= 0: + return -1 + return -self._queue[0][0] + + # Priority of lowest-priority item + def min_priority(self): + """Return priority of lowest-priority item in priority queue. + + If the priority queue is empty, this function will return -1. + + """ + # Each item in the priority queue is a 3-tuple, the first element + # of which is the negated priority (for making a max heap). + if len(self._queue) <= 0: + return -1 + return -self._queue[-1][0] + + # Iterator interface + def __iter__(self): + """Iterator interface to priority queue. + + This function returns an iterator for accessing the priority + queue's elements in order of descending priority. + + """ + # Helper class to implement priority queue iterator + class _iterator_adaptor: + def __init__(self, pq): + self._iter = iter(pq) + def next(self): + p, c, v = self._iter.next() + return v # queue stores priority and count; we only want value + return _iterator_adaptor(self._queue) + +#----------------------------------------------------------------------- + +############################################## +# Editor config: # +############################################## +# Local Variables: # +# indent-tabs-mode: nil # +# py-indent-offset: 4 # +# python-indent: 4 # +# End: # +############################################## +# vim: set expandtab shiftwidth=4 tabstop=4: # +############################################## ADDED core/layman.py Index: core/layman.py ================================================================== --- core/layman.py +++ core/layman.py @@ -0,0 +1,289 @@ +## +# @file layman.py +# @brief Class to manage layouts. +# @defgroup grp_minx_core_layman Layout Manager +# + +# +# Copyright (C) 2012 The Minx Project Developers +# +# See wiki/copyright.wiki in the top-level directory of the Minx source +# distribution for the full list of authors who have contributed to this +# project. +# + +# +# This file is part of Minx. +# +# Minx 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, either version 3 of the License, or (at your +# option) any later version. +# +# Minx 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 Minx. If not, see . +# + +#-------------------------- MODULE DOC STRING --------------------------- + +"""@ingroup grp_minx_core_layman +A helper for managing layouts. + +This module defines helper classes for dealing with different layouts. +The layouts member of the @ref minx.core.wm.wm "main window manager object" +is an instance of the layout manager class. Both the main window manager +object as well as end-user code will use the layouts object to interface +with the different layout objects that Minx currently has. + +""" + +#------------------------------- IMPORTS -------------------------------- + +# Standard library +import logging + +# Minx +from minx import layout + +#---------------------------- MODULE LOGGER ----------------------------- + +logger = logging.getLogger(__name__) + +#-------------------------------- CLASS --------------------------------- + +class layman: + """@ingroup grp_minx_core_layman + Layout manager. + + This class keeps track of the different layout objects created by + end-users and also provides a convenient API for dealing with these + layouts. + + """ + + # Constructor + def __init__(self, wm): + """Construct layout manager. + + @param wm The @ref minx.core.wm.wm "main window manager object". + + The layout manager is meant to be used by the + @ref minx.core.wm.wm "main window manager object". When the + layout manager is created, we squirrel away a reference to the + main wm object so we can later get access to the "global" + @ref minx.core.config.config "config" object and any other things + we might need from the wm object. + + """ + self._wm = wm + self._layouts = [] + + # Add a layout object to internal list + def add(self, layout): + """Add given layout object to the layout manager. + + @param layout The layout object to be added. + + This method adds the specified layout object to the layout + manager's internal list and performs other necessary bookkeeping. + + """ + self._layouts.append(layout) + + # Find layout corresponding to specified window or name + def find(self, w): + """Return layout object given either its X window or the window's name. + + @param w A string or the @ref minxlib::window "minxlib.window" to find. + @return Layout matching w. + + This method searches all the layouts currently being managed by + the layout manager to see if any of them corresponds to the + window w. If w is a string, then this method will look for a + layout whose X window has the string w in its name. + + If there is no matching layout, this function will raise an + unknown_layout exception. + + """ + if isinstance(w, str): + logger.debug('looking for layout with "{}" in its name'. + format(w)) + for layout in self._layouts: + name = layout.window.properties()['name'] + if w in name: + return layout + else: # assume w refers to a minxlib.window + logger.debug('looking for layout corresponding to window ID {}'. + format(w.id)) + for layout in self._layouts: + if layout.window == w: + return layout + raise unknown_layout(w) + + # Return layout with window that has the input focus + def focused_layout(self): + """Return current layout. + + This method returns the layout that has the top-level window that + currently has the input focus. If no window has the input focus + or if there is no layout corresponding to the focused window, + this method will return None. + + """ + logger.debug('looking for focused layout') + w = self._wm.display.get_focused_window() + logger.debug('focused window = {}'.format(w.id)) + if w.id == 0: + logger.debug('no window currently focused') + return None + + try: + p = w.parent() + logger.debug('focused window = {}, its parent = {}'. + format(w.id, p.id)) + return self.find(p) + except unknown_layout: + logger.debug('no layout corresponding to focused window {}'. + format(w.id)) + return None + + # Find layout for new window + def receptive_layout(self, w): + """Find a layout to manage a new window. + + @param w The @ref minxlib::window "minxlib.window" to be managed. + + When a new window is created, Minx has to find a layout that will + manage it. For lack of a better term, we refer to this layout as + the receptive layout (because it will "receive" the new window). + To find a layout willing and able to manage the new window, this + function follows the procedure outlined below: + + @li Trigger the receptive_layout_hook. + @li Failing that, check the focused layout. + @li Failing that, search the remaining layouts. + @li Failing that, create a new default layout. + + The receptive_layout_hook gives end-user code an + opportunity to fine-tune the layout to be used for each window. + If you have multiple functions for this hook, only the return + value of the first one will be considered. That is, use only one + hook for this; in fact, you shouldn't need an entire chain of + hooks for the receptive_layout_hook. + + Minx will only accept the return value of the + receptive_layout_hook if all of the following conditions + are true: + + @li The return value is not None. + @li It is an instance of the + @ref minx.layout.base.base "minx.layout.base" class. + @li The layout is on the same screen as w. + @li The layout is willing to manage w. + + If the above-mentioned hook is not defined or if it returns + something not fulfilling the above conditions, we check if the + layout with the window that is currently focused is on the same + screen as the new window and is willing to manage it. If so, this + function will return the currently focused layout as the + receptive layout, i.e., the layout that will manage the new + window. + + If no window has the input focus, or if the focused layout is not + on the same screen as the new window, or if it is unwilling to + manage the new window, then we search the layout manager's + remaining layouts for a layout that is on the same screen as the + new window and is willing to manage it. + + If no such layout exists, Minx will fall back to creating a new + layout object on the same screen as the new window and use that + as the receptive layout. The layout class used for this fallback + policy is referred to as the default layout. Currently, Minx uses + the @ref minx.layout.full.full "full layout" as the default + layout class. + + @note End-user code should not need to call this function. + Specifically, don't call this function from your + receptive_layout_hook. + + """ + s = w.screen() + logger.debug('looking for receptive layout for window {} on screen {}'. + format(w.id, s)) + + logger.debug('looking for receptive layout via user hook') + U = self._wm.hooks.trigger('receptive_layout_hook', w) + if len(U) > 0: + logger.info('receptive_layout_hook returned {}'.format(U[0])) + if (U[0] != None and isinstance(U[0], layout.base) and + U[0].window.screen() == s and U[0].will_manage(w)): + if not U[0] in self._layouts: + self.add(U[0]) + return U[0] # use first layout returned by hook + else: + logger.warning('bad layout ({}) '.format(U[0]) + + 'returned by receptive_layout_hook') + + F = self.focused_layout() + logger.debug('checking focused layout {} for receptivity'.format(F)) + if F != None and F.window.screen() == s and F.will_manage(w): + logger.info('focused layout {} can and will manage window {}'. + format(F, w.id)) + return F + + logger.debug('checking remaining layouts for receptivity') + for L in self._layouts: + if L == F: + continue + if L.window.screen() == s and L.will_manage(w): + logger.info('layout {} can and will manage window {}'. + format(L, w.id)) + return L + + logger.debug('no receptive layout found') + D = layout.full(self._wm, self._wm.root_windows[s]) + self.add(D) + logger.info('falling back to default layout {} for window {}'. + format(D, w.id)) + return D + +#----------------------------- EXCEPTIONS ------------------------------- + +class unknown_layout(Exception): + """@ingroup grp_minx_core_layman + Unknown layout exception. + + The layout manager raises this exception when it fails to find a + layout object corresponding to some window. + + """ + def __init__(self, w): + """Construct an unknown_layout. + + @param w The @ref minxlib::window "minxlib.window" or window name + for which there is no corresponding layout. + + The parameter w can be accessed as the exception's window member. + + """ + self.window = w + +#------------------------------------------------------------------------ + +############################################## +# Editor config: # +############################################## +# Local Variables: # +# indent-tabs-mode: nil # +# py-indent-offset: 4 # +# python-indent: 4 # +# End: # +############################################## +# vim: set expandtab shiftwidth=4 tabstop=4: # +############################################## ADDED core/window.py Index: core/window.py ================================================================== --- core/window.py +++ core/window.py @@ -0,0 +1,107 @@ +## +# @file window.py +# @brief Convenience functions for working with windows. +# @defgroup grp_minx_core_window Window Convenience API +# + +# +# Copyright (C) 2012 The Minx Project Developers +# +# See wiki/copyright.wiki in the top-level directory of the Minx source +# distribution for the full list of authors who have contributed to this +# project. +# + +# +# This file is part of Minx. +# +# Minx 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, either version 3 of the License, or (at your +# option) any later version. +# +# Minx 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 Minx. If not, see . +# + +#-------------------------- MODULE DOC STRING --------------------------- + +"""@ingroup grp_minx_core_window +Convenience functions for working with windows. + +This module defines some convenience functions for dealing with +minxlib.window objects. Although you could simply call minxlib.window +functions directly, the functions defined in this module are more +convenient and also take into account things such as settings in +minx.core.config. + +""" + +#------------------------------- IMPORTS -------------------------------- + +# Standard library +import logging + +# minx +import minxlib + +#---------------------------- MODULE LOGGER ----------------------------- + +logger = logging.getLogger(__name__) + +#------------------------ CONVENIENCE FUNCTIONS ------------------------- + +# Reset border color and size to match focused window and then focus +# the window. +def focus(w, c): + """@ingroup grp_minx_core_window + Focus the specified window. + + @param w The minxlib.window object to be focused. + @param c The minx.core.config object containing Minx settings. + + This function sets the window's border attributes to those of the + active window and then raises and focuses the window. + + """ + logger.info('setting window {} border attributes: 0x{:0<6X}, {}'. + format(w.id, c.active_border_color, c.active_border_size)) + w.set_border_attr(c.active_border_color, c.active_border_size) + + logger.debug('focusing window {}'.format(w.id)) + w.focus() + +# Reset border color and size to match unfocused windows +def unfocus(w, c): + """@ingroup grp_minx_core_window + Unfocus the specified window. + + @param w The minxlib.window object to be unfocused. + @param c The minx.core.config object containing Minx settings. + + This function sets the window's border attributes to those of + inactive windows. + + """ + logger.info('setting window {} border attributes: 0x{:0<6X}, {}'. + format(w.id, c.inactive_border_color, c.inactive_border_size)) + w.set_border_attr(c.inactive_border_color, c.inactive_border_size) + +#------------------------------------------------------------------------ + +############################################## +# Editor config: # +############################################## +# Local Variables: # +# indent-tabs-mode: nil # +# py-indent-offset: 4 # +# python-indent: 4 # +# End: # +############################################## +# vim: set expandtab shiftwidth=4 tabstop=4: # +############################################## ADDED core/wm.py Index: core/wm.py ================================================================== --- core/wm.py +++ core/wm.py @@ -0,0 +1,760 @@ +# +# wm.py -- main object for interacting with the Minx window manager +# + +# +# Copyright (C) 2012 The Minx Project Developers +# +# See wiki/copyright.wiki in the top-level directory of the Minx source +# distribution for the full list of authors who have contributed to this +# project. +# + +# +# This file is part of Minx. +# +# Minx 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, either version 3 of the License, or (at your +# option) any later version. +# +# Minx 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 Minx. If not, see . +# + +#------------------------ MODULE DOC STRING ---------------------------- + +"""@package minx.core.wm +Main interface object for Minx's end-users. + +This file defines the minx.core.wm.wm class, which can be used simply as +minx.core.wm. + +""" + +#----------------------------- IMPORTS --------------------------------- + +# Standard library +import shlex, subprocess +import re, logging, logging.config +import sys, traceback + +# Minx core +import window +from layman import layman +from xevents import xevents +from hooks import hooks +from config import config +from focus_list import focus_list +import minxlib + +# Minx layouts +from minx import layout + +#-------------------------- MODULE LOGGER ------------------------------ + +logger = logging.getLogger(__name__) + +#------------------------- CLASS DEFINITION ---------------------------- + +class wm: + """Main window manager object. + + This class encapsulates all the internal state required by the + window manager. Typically, you will create an instance of this class + in order to start the window manager. For example, the simplest Minx + configuration would be a file with the following contents: + + @verbatim + #!/usr/bin/env python + import minx + minx.core.wm().start() + @endverbatim + + Of course, the above code requires the minx package to be in your + Python path. But, assuming that is the case, it would start Minx + with the default key bindings, hooks, and other settings. + + To make simple customizations, create an instance of the + @ref minx.core.config.config "config" class and pass that to the wm + constructor: + + @verbatim + #!/usr/bin/env python + + import minx + + conf = minx.core.config() + conf.active_border_color = 0x00FF00 # green + + minx.core.wm(conf).start() + @endverbatim + + In addition to customizing window border attributes, the config + class enables logging to be configured. Here is an example: + + @verbatim + #!/usr/bin/env python + + import logging + import minx + + conf = minx.core.config() + conf.log_to_file('minx.log') + conf.log_level(logging.DEBUG) + + minx.core.wm(conf).start() + @endverbatim + + The Logging HOWTO provides more + details on logging configuration. + + Apart from the simple attribute customizations shown above, if you + would also like to change the way Minx responds to various events, + you will have to define hook functions and pass those to Minx. Here + is a simple example: + + @verbatim + #!/usr/bin/env python + + import minx + + def my_manage_hook(w): + prop = w.properties() + if prop['class'].lower() == 'gkrellm': + return False + return True + + wm = minx.core.wm() + wm.hooks.add('manage_hook', my_manage_hook) + wm.start() + @endverbatim + + The above configuration will setup a manage hook that instructs Minx + to ignore the GKrellM window. Have a look at the + Hooks HOWTO for lots more + info about configuring Minx using hooks. Additionally, the + Hooks List documents all the + hooks that Minx currently supports. + + One important use of hooks is for defining custom key bindings. The + Key Bindings HOWTO has the + details. + + To customize how Minx arranges windows, you can: + + @li Specify the layouts you would like to use + @li Specify the layout for particular windows + @li Implement your own layouts + + Here is an example that illustrates the first two of the above + possibilities: + + @verbatim + #!/usr/bin/env python + + import minx + + def my_init_hook(m): + scr = m.root_windows + m.layouts.add(minx.layout.tall(m, scr[0])) + m.layouts.add(minx.layout.rows(m, scr[1])) + + def my_receptive_layout_hook(w): + prop = w.properties() + if 'gimp' in prop['class'].lower(): + global wm + try: + L = wm.layouts.find('gimp') + return L + except minx.core.layman.unknown_layout: + parent = wm.root_windows[w.screen()] + return minx.layout.gimp(wm, parent) + return None + + wm = minx.core.wm() + wm.hooks.add('init_hook', my_init_hook) + wm.hooks.add('receptive_layout_hook', my_receptive_layout_hook) + wm.start() + @endverbatim + + The init hook allows + you to setup the layouts you want to use. If you don't supply an init + hook, Minx will use a default layout for each screen. At this time, + the default layout is the @ref minx.layout.full.full "full" layout, + which shows one window at a time by resizing windows to occupy the + entire screen. In the above example, we use the tall layout for the + first screen and the rows layout for the second screen. Of course, + this would only work if you actually have two screens. Please also + note that the tall and rows layout classes have not yet been + implemented; we use these nonexistent layouts just to illustrate the + intended spirit of layouts specification. + + The receptive + layout hook allows you to either create or find a layout for each + new window that Minx manages. In the above example, we created an + instance of the gimp layout when Gimp starts up and use that for + subsequent windows. For all other window types, we fall back to + either the tall or rows layouts setup in the init hook. If, for some + reason, the tall and rows layouts refuse to manage the new window, + Minx will fall back its default, viz., the full layout. Again, like + tall and rows, the gimp layout does not exist yet; it was used merely + to illustrate how the receptive layout hook is intended to work. + + Documentation for implementing layouts will come later. + + @note Although this class is defined in the minx.core.wm "package" + and, technically, must be accessed as minx.core.wm.wm, it can, in + fact (and as illustrated in the code snippets above), be referred to + simply as minx.core.wm. + + """ + # Constructor + def __init__(self, conf = None): + """Create the main window manager object. + + @param conf Config object containing simple customizations. + + The wm class implements the main interface to the Minx window + manager. You will have to first create an instance of this class + and then call its start() method to run the window manager. This + constructor simply sets up some internal attributes, the most + important of which are: + + @li config + @li hooks + @li layouts + + The @ref minx.core.config.config "config" object specifies + various settings such as the border colors, sizes, etc. To + customize these settings, you will (typically) create a config + object, change its various attributes, and pass that object into + this constructor. (Alternatively, you could first create a wm + object and then access its config attribute.) If you don't supply + a config object, the window manager will use default settings. + + The wm object's @ref minx.core.hooks.hooks "hooks" attribute + maps names of hook functions (i.e., strings) to prioritized + lists of callables. Minx will trigger these hooks at appropriate + points in its event processing workflow. Minx uses hooks both + internally (to handle various events) and "externally" (to allow + end-users to customize its behaviour). + + To supply your own hooks, after creating a wm instance, use its + hooks attribute to add hook functions for various events. Take a + look at the Hooks HOWTO + for more on customizing Minx with hook functions. The + Hooks List documents all + the hooks currently supported by Minx. + + As mentioned earlier, Minx leverages its hooks mechanism to + allow end-users to define custom key bindings. The + Key Bindings HOWTO + explains this feature. + + One thing to keep in mind about hooks is that, since Minx uses + them internally, it is easy to override its internal hooks and + even disable them. Usually, this will cause Bad Things (TM) to + happen. Thus, you should exercise care when dealing with + "dangerous" hooks. As long as you stick to "external" hooks used + by Minx specifically to effect customization, you should be + okay. + + Finally, the @ref minx.core.layman.layman "layouts" object + provides an interface for dealing with window layouts. By + default, Minx uses the @ref minx.layout.full.full "full" layout, + which shows one window at a time, resizing windows so that they + occupy the entire screen. You can use the + init hook to add + your preferred layouts to the layout manager. + + Another way to customize layout selection is to use the + receptive + layout hook, which provides a mechanism for deciding a layout + for each new window. + + """ + if conf == None: # create a default config object + conf = config() + self.config = conf + + # Configure Minx's root logger + logging.config.dictConfig(conf.logger) + logger.info('starting Minx') + + # Other initialization + logger.debug('creating focus list') + self._focus_list = focus_list() + + logger.debug('creating layout manager') + self.layouts = layman(self) + + logger.info('setting up X event handlers and default keybindings') + self.hooks = hooks() + self._xevents = xevents(self) + self._init_keybindings() + + self._quit = False + + # Default keybindings + def _init_keybindings(self): + self.hooks.add( 'A-Tab', self.focus_next) + self.hooks.add('S-A-Tab', self.focus_prev) + self.hooks.add( 'A-F4', self.kill) + self.hooks.add( 'C-F4', self.nuke) + self.hooks.add( 'C-A-T', lambda: self.spawn(self.config.terminal)) + self.hooks.add( 'C-A-X', self.quit) + + # Start the window manager + def start(self): + """Start the window manager. + + Call this method after creating a wm instance to get Minx up and + running. Here is an example showing how to start Minx with its + default settings, key bindings, etc.: + + @verbatim + #!/usr/bin/env python + import minx + minx.core.wm().start() + @endverbatim + + If you would like to customize Minx by specifying your own hooks + to be able to deal with various events, the initialization and + start-up sequence will be a three step procedure as shown below: + + @verbatim + #!/usr/bin/env python + + import minx + + # Define your hook functions + def my_manage_hook(w): + return True # actually, you would do something more useful + + # Step one: create window manager object + wm = minx.core.wm() + + # Step two: customize using hooks + wm.hooks.add('manage_hook', my_manage_hook) + wm.hooks.add( 'F1', wm.focus_next) # custom key binding + wm.hooks.add('S-F1', wm.focus_prev) # custom key binding + + # Step three: start the window manager + wm.start() + @endverbatim + + """ + self.connect_x() + self.configure_x() + self.hooks.trigger('init_hook', self) + self.manage_existing() + self.event_loop() + + # Connect to the X server + def connect_x(self): + """Connect to the X server. + + This method connects to the X server. If we are unable to do so, + it will raise a minxlib.connection_error. + + @note This method is mostly an internal one meant to be used by + Minx itself. In fact, it is called by wm.start(). You should not + really need to call it yourself. However, we provide this as a + public method to allow customization in case you want to do + something after connecting to X but before the other steps in + Minx's usual start-up sequence. + + @par + If you decide not to use wm.start(), look at the Minx code to + see how and what you will need to do to effectively use this + function. + + """ + try: + conf = self.config + logger.info('connecting to X server (synchronous = {})'. + format(conf.synchronize_xlib)) + self.display = minxlib.display(sync = conf.synchronize_xlib) + except minxlib.connection_error as e: + logger.critical(e) + raise + + # Configure X to send us events we need to manage windows + def configure_x(self): + """Configure X server to send Minx events it needs to manage windows. + + This method sets up the event mask for all the root windows so + that Minx gets the notifications it needs to be able to manage + windows. It also sets up passive keyboard grabs to make the + keybindings mechanism work. + + @note This method is mostly an internal one meant to be used by + Minx itself. In fact, it is called by wm.start(). You should not + really need to call it yourself. However, we provide this as a + public method to allow customization in case you want to do + something after connecting to and setting up X but before the + remaining steps in Minx's usual start-up sequence. + + @par + If you decide not to use wm.start(), look at the Minx code to + see how and what you will need to do to effectively use this + function. + + """ + # Find hooks that correspond to key bindings + logger.info('finding key binding hooks') + km = '|'.join(self.display.get_keyboard_mapping()) + kb = re.compile('^(([CAS]|M[1-5]?)-)*(' + km + ')$') + key_bindings = [n for n in self.hooks.names() if kb.search(n)] + #logger.debug('km = {}'.format(km)) + logger.debug('key bindings hooks = {}'.format(' '.join(key_bindings))) + + # Setup event masks and passive grabs for all screens + self.root_windows = self.display.get_root_windows() + for w in self.root_windows: + logger.info('configuring root window {}'.format(w.id)) + w.select_events(minxlib.substructure_redirect_mask | + minxlib.substructure_notify_mask | + minxlib.key_press_mask) + for k in key_bindings: + w.grab_key(k) + + # Manage extant top-level windows (we won't get create notifications + # for these). + def manage_existing(self): + """Manage existing top-level windows. + + Minx uses this function to get the list of existing top-level + windows and add them to its internal data structures so it can + manage them. This is necessary, for example, when an end-user's + ~/.xinitrc starts X programs before starting the window manager. + It is also required when the window manager is restarted or when + a user switches from another running window manager to Minx. + + @note This method is mostly an internal one meant to be used by + Minx itself. In fact, it is called by wm.start(). You should not + really need to call it yourself. However, we provide this as a + public method to allow customization in case you want to do + something after connecting to X, setting it up, and managing the + existing top-level windows but before entering the event loop. + + @par + If you decide not to use wm.start(), look at the Minx code to + see how and what you will need to do to effectively use this + function. + + """ + logger.info('managing existing top-level windows') + for w in self.display.get_top_level_windows(): + prop = w.properties() + logger.debug('window {} class = {}, name = {}'. + format(w.id, prop['class'], prop['name'])) + if prop['class'] != 'minx.layout' and self.manage(w): + self.layouts.receptive_layout(w).add(w) + + # Event loop + def event_loop(self): + """Retrieve and process X events. + + This method implements Minx's event loop. Once we enter this + function, Minx will initiate an infinite loop, wherein it + blocks, waiting for the X server to send it events. When it + receives an event notification, it will trigger its internal + event processing hooks to respond appropriately. + + @note This method is mostly an internal one meant to be used by + Minx itself. In fact, it is called by wm.start(). You should not + really need to call it yourself. However, we provide this as a + public method to allow customization in case you want to do + something after connecting to X, setting it up, and managing the + existing top-level windows but right before entering the event + loop. + + @par + If you decide not to use wm.start(), look at the Minx code to + see how and what you will need to do to effectively use this + function. + + """ + logger.info('entering event loop') + while not self._quit: + try: + logger.debug('waiting for event') + e = self.display.get_event() + logger.debug('got event {}'.format(e)) + self.hooks.trigger(str(e), e) + + # Tried to query the window hierarchy of a non-existent + # window. This happens when a window is destroyed and we + # receive various other notifications before the destroy + # notification comes in. Well, there's not much to be done in + # this situation; so, just log the error and keep going. + except minxlib.query_tree_error as e: + logger.warning(e) + + # Tried to focus a non-existent window (happens sometimes + # upon window destruction, causing the resulting unmap and + # focus notification handlers to go out-of-sync with the X + # server's internal state). We can use the protocol error + # that ensues as an opportunity to sync the internal states + # of the window manager and X server. + except minxlib.set_focus_error as e: + logger.warning(e) + self._focus_list.remove(e.resource_id) + + # Tried to change attributes of a non-existent window. This + # used to happen when a window was destroyed and the + # resulting focus_out event handler's attempt to set its + # border color to the inactive window border color causes + # unnecessary pain... + # + # However, now that we no longer rely on X's focus change + # events to perform the window border update, this exception + # should not be generated and its handler no longer + # required. Nonetheless, it is in place for now; we can + # remove it later on during development when the design has + # stabilized enough for us to be absolutely sure this bit of + # code is no longer required. + except minxlib.change_window_attributes as e: + logger.warning(e) + if e.error_code == minxlib.bad_window: + pass # ignore protocol error (should be okay) + + # Generic protocol error + except minxlib.protocol_error as e: + logger.warning(e) + + # Some other exception: log it and keep going + except: + logger.warning('received {} exception'. + format(sys.exc_info()[0])) + logger.debug(traceback.format_exc()) + + logger.info('exiting event loop') + + + # API to focus next window + def focus_next(self): + """Focus next window. + + This function passes input focus to the next window in the + window manager's list of top-level windows that can accept input + focus. It is meant to be invoked via a key binding's hook. Here + is an example of intended usage: + + @verbatim + #!/usr/bin/env python + + import minx + + wm = minx.core.wm() + wm.hooks.add( 'F1', wm.focus_next) + wm.hooks.add('S-F1', wm.focus_prev) + wm.start() + @endverbatim + + With that in your Minx start-up script, you will be able to + cycle input focus with F1 and SHIFT + F1 (in addition to the + default key bindings for focus cycling, viz., ALT + Tab and + ALT + SHIFT + Tab). + + """ + L = self._focus_list + old_head = L.head() + L.forward() + new_head = L.head() + if new_head != old_head: + logger.info('switching focus from {} to {}'. + format(old_head.id, new_head.id)) + window.unfocus(old_head, self.config) + window. focus(new_head, self.config) + #else: new_head == old_head ==> focus list empty or only one window + + # API to focus previous window + def focus_prev(self): + """Focus previous window. + + This function passes input focus to the previous window in the + window manager's list of top-level windows that can accept input + focus. It is meant to be invoked via a key binding's hook. Here + is an example of intended usage: + + @verbatim + #!/usr/bin/env python + + import minx + + wm = minx.core.wm() + wm.hooks.add( 'F1', wm.focus_next) + wm.hooks.add('S-F1', wm.focus_prev) + wm.start() + @endverbatim + + With that in your Minx start-up script, you will be able to + cycle input focus with F1 and SHIFT + F1 (in addition to the + default key bindings for focus cycling, viz., ALT + Tab and + ALT + SHIFT + Tab). + + """ + L = self._focus_list + old_head = L.head() + L.backward() + new_head = L.head() + if new_head != old_head: + logger.info('switching focus from {} to {}'. + format(old_head.id, new_head.id)) + window.unfocus(old_head, self.config) + window. focus(new_head, self.config) + #else: new_head == old_head ==> focus list empty or only one window + + # Kill a window + def kill(self, w = None): + """Kill window. + + @param w The @ref minxlib::window "minxlib.window" to be killed. + + This function kills the X client application that created the + window w and all of its other windows and X resources. If w is + not supplied by the caller, this function will kill the + currently focused window. + + """ + if w == None: # kill the currently focused window + w = self._focus_list.head() + if w != None: # there is a focused window to kill + w.kill() + + # Kill a window using brute force + def nuke(self, w = None): + """Kill window using brute force. + + @param w The @ref minxlib::window "minxlib.window" to be nuked. + + This function kills the X client application that created the + window w and all of its other windows and X resources. If w is + not supplied by the caller, this function will kill the + currently focused window. + + @note Whereas the kill() function attempts a graceful shutdown, + this function resorts to brute force. It is meant to be used on + windows that advertise support for the WM_DELETE_WINDOW protocol + but don't implement it properly, thus, requiring a brute force + kill (i.e., a nuking). + + """ + if w == None: # kill the currently focused window + w = self._focus_list.head() + if w != None: # there is a focused window to kill + w.nuke() + + # Start an application + def spawn(self, cmd): + """Run specified command. + + @param cmd A string containing the command and command-line arguments. + + This function can be used to run arbitrary commands. Minx does + not wait for the command to complete or communicate with it any + further after starting it. The intent of this function is to + facilitate launching GUI applications via window manager key + bindings. + + If the command fails, Minx will write details to its log (if + logging has been enabled). + + """ + logger.info('running command: {}'.format(cmd)) + try: + subprocess.Popen(shlex.split(cmd)) + except: + logger.warning('{} exception on command: {}'. + format(sys.exc_info()[0], cmd)) + logger.debug(traceback.format_exc()) + + # Add newly created top-level window to focus list + def add_to_focus_list(self, w): + """Add a newly created top-level window to the focus list. + + @param w The @ref minxlib::window "minxlib.window" to be added. + + This function is meant to be used internally by Minx. + Specifically, the wm constructor calls it to take over + management of any top-level windows that might have been created + before Minx was started. Also, the MapNotify handler in + the xevents class uses it to add a newly mapped top-level window + to the focus list and to focus it. + + End-users should not use this function. + + """ + L = self._focus_list + f = L.head() + if f != None: + logger.debug('unfocusing {}'.format(f.id)) + window.unfocus(f, self.config) + logger.debug('adding {} to focus list and focusing'.format(w.id)) + L.add(w) + window.focus(w, self.config) + + # Whether or not manage the given window + def manage(self, w): + """Check if given window should be managed or not. + + @param w The @ref minxlib::window "minxlib.window" to be checked. + @return True if Minx should manage w, false if it should ignore w. + + This function triggers the manage hooks setup by end-users and + checks all their return values. If all the manage hooks return + True, then this function will return True. If any of the manage + hooks returns False, this function will return False. Thus, + Minx will only manage a top-level window if all the manage + hooks return True. + + @note This function is meant to be used internally by Minx. + End-user code should refrain from calling it. + + """ + # hooks.trigger() returns a list of return values of each hook + # it calls. The manage_hook expects a Boolean return value: + # True to indicate that the window w should be managed, False + # to make Minx ignore w. Thus, to determine whether w should be + # managed, we have to examine the each of these return values + # and confirm that they're all True. + for r in self.hooks.trigger('manage_hook', w): + if r == False: # some manage hook returned False + return False # therefore, ignore w + + # If we get here, all manage hooks passed w ==> we should + # manage it... + return True + + # Quit window manager + def quit(self): + """Quit the window manager. + + This function sets a flag that will eventually result in Minx + exiting its event loop. It is meant to be invoked via a key + binding or other similar action. + + """ + self._quit = True + +#----------------------------------------------------------------------- + +############################################## +# Editor config: # +############################################## +# Local Variables: # +# indent-tabs-mode: nil # +# py-indent-offset: 4 # +# python-indent: 4 # +# End: # +############################################## +# vim: set expandtab shiftwidth=4 tabstop=4: # +############################################## ADDED core/xevents.py Index: core/xevents.py ================================================================== --- core/xevents.py +++ core/xevents.py @@ -0,0 +1,204 @@ +# +# xevents.py -- X event handlers +# + +# +# Copyright (C) 2012 The Minx Project Developers +# +# See wiki/copyright.wiki in the top-level directory of the Minx source +# distribution for the full list of authors who have contributed to this +# project. +# + +# +# This file is part of Minx. +# +# Minx 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, either version 3 of the License, or (at your +# option) any later version. +# +# Minx 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 Minx. If not, see . +# + +#------------------------ MODULE DOC STRING ---------------------------- + +"""@package minx.core.xevents +Helper class for handling various X events. + +""" + +#----------------------------- IMPORTS --------------------------------- + +# Standard library +import logging + +# Minx +import layman +import window +import minxlib + +#-------------------------- MODULE LOGGER ------------------------------ + +logger = logging.getLogger(__name__) + +#------------------------- CLASS DEFINITION ---------------------------- + +class xevents: + """Helper class for handling various X events. + + This class encapsulates the X event handlers so that the + @ref minx.core.wm.wm "main window manager object" can delegate this + low-level functionality. This class is not really meant to be used + by end-users; it should be considered internal to Minx. + + """ + + # Constructor + def __init__(self, wm): + """Initialize X event handlers. + + @param wm The @ref minx.core.wm.wm "main window manager object". + + This class is meant to be used by @ref minx.core.wm.wm "minx.core.wm" + so the main window manager object can delegate all the low-level + X event handling. Consequently, the xevents object needs a + reference to the main window manager object so it can access and + manipulate the window manager's internal state in response to + various X events. + + """ + self._wm = wm + self._init_hooks() + + # Helper function to initialize the mapping between X events and + # their corresponding handler functions. + # + # NOTE: This relies on the string forms of all minxlib::event + # subclasses being "x_something". Thus, the mapping between event + # types and handlers uses strings as keys and functions as the + # corresponding values. + def _init_hooks(self): + logger.debug('setting up X event handlers') + h = self._wm.hooks + h.add('x_create_notify', self._on_create) + h.add('x_configure_request', self._on_configure_request) + h.add('x_map_request', self._on_map_request) + h.add('x_map_notify', self._on_map) + h.add('x_unmap_notify', self._on_unmap) + h.add('x_key_press', self._on_key_press) + + # Whether or not window specified by x_create_notify event should + # be managed by Minx or ignored. + def _manage(self, e): + prop = e.target.properties() + logger.debug('window {} class = {}, name = {}'. + format(e.target.id, prop['class'], prop['name'])) + return (not e.override_redirect and + prop['class'] != 'minx.layout' and + e.parent in self._wm.root_windows and + self._wm.manage(e.target)) + + # When a new top-level window is created, the window manager has to + # update its internal state to include this new window in its list + # of top-level windows it has to manage. + # + # NOTE: We do not add new top-level windows to the focus list right + # away. Instead, we wait for them to be mapped first before putting + # them on the focus list. This is because some applications (e.g., + # GNOME and XFCE terminal) create hidden top-level windows that + # should never receive input focus. + def _on_create(self, e): + if self._manage (e): + self._wm.layouts.receptive_layout(e.target).add(e.target) + + # What to do when a window wants to be configured: pass configure + # request through as-is except for top-level windows, which have to + # be resized according to their managing layout's policy. + def _on_configure_request(self, e): + logger.info ('configure request for window {}'.format(e.target.id)) + logger.debug('configure request details: ' + + 'geom = {}x{}+{}+{}, bw = {}, mask = {:#010b}'. + format(e.width, e.height, e.x, e.y, + e.border_width, e.value_mask)) + try: + layout = self._wm.layouts.find(e.parent) + x, y, w, h = layout.configure_request(e.target, e.x, e.y, + e.width, e.height) + + except layman.unknown_layout: # pass configure request through as-is + logger.debug('window {} not a top-level window'. + format(e.target.id)) + x, y, w, h = (e.x, e.y, e.width, e.height) + + logger.debug('setting window {} geometry to {}x{}+{}+{}'. + format(e.target.id, w, h, x, y)) + e.target.configure(x, y, w, h, e.border_width, + e.above, e.stack_mode, e.value_mask) + + # What to do when a window wants to be shown on-screen: top-level + # windows will be passed to their managing layout for possible + # reconfiguration before being mapped; all other windows are mapped + # as-is. + def _on_map_request(self, e): + logger.info('map request for window {}'.format(e.target.id)) + try: + layout = self._wm.layouts.find(e.parent) + layout.map_request(e.target) + except layman.unknown_layout: + pass + e.target.show() + + # What to do when a top-level window is successfully shown + # on-screen: we should add it to the window manager's focus list and + # focus it. + def _on_map(self, e): + try: + self._wm.layouts.find(e.parent) + logger.debug('adding top-level window {} to focus list'. + format(e.target.id)) + self._wm.add_to_focus_list(e.target) + except layman.unknown_layout: # not a top-level window + pass + + # What to do when a top-level window is taken off-screen: remove it + # from the window manager's focus list and focus the next window on + # the focus list. + def _on_unmap(self, e): + try: + self._wm.layouts.find(e.parent) + L = self._wm._focus_list + logger.info('removing {} from focus list'.format(e.target.id)) + L.remove(e.target) + f = L.head() + if f != None: + logger.info('focusing {}'.format(f.id)) + window.focus(f, self._wm.config) + except layman.unknown_layout: # not a top-level window + pass + + # What to do when a key is pressed. + def _on_key_press(self, e): + logger.info('keypress: {}, keycode: {}, mask: {:#06x}'. + format(e.key, e.keycode, e.mask)) + self._wm.hooks.trigger(e.key) + +#----------------------------------------------------------------------- + +############################################## +# Editor config: # +############################################## +# Local Variables: # +# indent-tabs-mode: nil # +# py-indent-offset: 4 # +# python-indent: 4 # +# End: # +############################################## +# vim: set expandtab shiftwidth=4 tabstop=4: # +############################################## ADDED dox/CMakeLists.txt Index: dox/CMakeLists.txt ================================================================== --- dox/CMakeLists.txt +++ dox/CMakeLists.txt @@ -0,0 +1,63 @@ +# +# Build spec for Minx's API docs +# + +# +# Copyright (C) 2012 The Minx Project Developers +# +# See wiki/copyright.wiki for the full list of authors who have +# contributed to this project. +# + +# +# This file is part of Minx. +# +# Minx 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, either version 3 of the License, or (at your +# option) any later version. +# +# Minx 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 Minx. If not, see . +# + +# Min cmake version +cmake_minimum_required(VERSION 2.8) + +# Project name +project(minxdoc) + +# Check if we have Xinerama and, if so, set a CMake variable that will +# tell Doxygen so that it generates the documentation for the optional +# Xinerama-dependent parts of Minx. +find_package(X11) +if(X11_Xinerama_FOUND) + set(minxdoc_PREDEFINED MINXLIB_HAS_XINERAMA) +endif() + +# Custom target for API documentation +find_package(Doxygen) +if(DOXYGEN_FOUND) + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/doxyfile.in" + "${CMAKE_CURRENT_BINARY_DIR}/doxyfile" @ONLY) + add_custom_target(doc + ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/doxyfile + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Generating documentation") +endif(DOXYGEN_FOUND) + +############################################## +# Editor config: # +############################################## +# Local Variables: # +# indent-tabs-mode: nil # +# sh-basic-offset: 4 # +# End: # +############################################## +# vim: set expandtab shiftwidth=4 tabstop=4: # +############################################## ADDED dox/dox.css Index: dox/dox.css ================================================================== --- dox/dox.css +++ dox/dox.css @@ -0,0 +1,949 @@ +/* The standard CSS for doxygen */ + +body, table, div, p, dl { + font-family: Lucida Grande, Verdana, Geneva, Arial, sans-serif; + font-size: 13px; + line-height: 1.3; +} + +/* @group Heading Levels */ + +h1 { + font-size: 150%; +} + +.title { + font-size: 150%; + font-weight: bold; + margin: 10px 2px; +} + +h2 { + font-size: 120%; +} + +h3 { + font-size: 100%; +} + +dt { + font-weight: bold; +} + +div.multicol { + -moz-column-gap: 1em; + -webkit-column-gap: 1em; + -moz-column-count: 3; + -webkit-column-count: 3; +} + +p.startli, p.startdd, p.starttd { + margin-top: 2px; +} + +p.endli { + margin-bottom: 0px; +} + +p.enddd { + margin-bottom: 4px; +} + +p.endtd { + margin-bottom: 2px; +} + +/* @end */ + +caption { + font-weight: bold; +} + +span.legend { + font-size: 70%; + text-align: center; +} + +h3.version { + font-size: 90%; + text-align: center; +} + +div.qindex, div.navtab{ + background-color: #EBEFF6; + border: 1px solid #A3B4D7; + text-align: center; +} + +div.qindex, div.navpath { + width: 100%; + line-height: 140%; +} + +div.navtab { + margin-right: 15px; +} + +/* @group Link Styling */ + +a { + color: #3D578C; + font-weight: normal; + text-decoration: none; +} + +.contents a:visited { + color: #4665A2; +} + +a:hover { + text-decoration: underline; +} + +a.qindex { + font-weight: bold; +} + +a.qindexHL { + font-weight: bold; + background-color: #9CAFD4; + color: #ffffff; + border: 1px double #869DCA; +} + +.contents a.qindexHL:visited { + color: #ffffff; +} + +a.el { + font-weight: bold; +} + +a.elRef { +} + +a.code, a.code:visited { + color: #4665A2; +} + +a.codeRef, a.codeRef:visited { + color: #4665A2; +} + +/* @end */ + +dl.el { + margin-left: -1cm; +} + +.fragment { + font-family: monospace, fixed; + font-size: 105%; +} + +pre.fragment { + border: 1px solid #C4CFE5; + background-color: #FBFCFD; + padding: 4px 6px; + margin: 4px 8px 4px 2px; + overflow: auto; + word-wrap: break-word; + font-size: 9pt; + line-height: 125%; +} + +div.ah { + background-color: black; + font-weight: bold; + color: #ffffff; + margin-bottom: 3px; + margin-top: 3px; + padding: 0.2em; + border: solid thin #333; + border-radius: 0.5em; + -webkit-border-radius: .5em; + -moz-border-radius: .5em; + box-shadow: 2px 2px 3px #999; + -webkit-box-shadow: 2px 2px 3px #999; + -moz-box-shadow: rgba(0, 0, 0, 0.15) 2px 2px 2px; + background-image: -webkit-gradient(linear, left top, left bottom, from(#eee), to(#000),color-stop(0.3, #444)); + background-image: -moz-linear-gradient(center top, #eee 0%, #444 40%, #000); +} + +div.groupHeader { + margin-left: 16px; + margin-top: 12px; + font-weight: bold; +} + +div.groupText { + margin-left: 16px; + font-style: italic; +} + +body { + background-color: white; + color: black; + margin: 0; +} + +div.contents { + margin-top: 10px; + margin-left: 8px; + margin-right: 8px; +} + +td.indexkey { + background-color: #EBEFF6; + font-weight: bold; + border: 1px solid #C4CFE5; + margin: 2px 0px 2px 0; + padding: 2px 10px; + white-space: nowrap; + vertical-align: top; +} + +td.indexvalue { + background-color: #EBEFF6; + border: 1px solid #C4CFE5; + padding: 2px 10px; + margin: 2px 0px; +} + +tr.memlist { + background-color: #EEF1F7; +} + +p.formulaDsp { + text-align: center; +} + +img.formulaDsp { + +} + +img.formulaInl { + vertical-align: middle; +} + +div.center { + text-align: center; + margin-top: 0px; + margin-bottom: 0px; + padding: 0px; +} + +div.center img { + border: 0px; +} + +address.footer { + text-align: right; + padding-right: 12px; +} + +img.footer { + border: 0px; + vertical-align: middle; +} + +/* @group Code Colorization */ + +span.keyword { + color: #008000 +} + +span.keywordtype { + color: #604020 +} + +span.keywordflow { + color: #e08000 +} + +span.comment { + color: #800000 +} + +span.preprocessor { + color: #806020 +} + +span.stringliteral { + color: #002080 +} + +span.charliteral { + color: #008080 +} + +span.vhdldigit { + color: #ff00ff +} + +span.vhdlchar { + color: #000000 +} + +span.vhdlkeyword { + color: #700070 +} + +span.vhdllogic { + color: #ff0000 +} + +/* @end */ + +/* +.search { + color: #003399; + font-weight: bold; +} + +form.search { + margin-bottom: 0px; + margin-top: 0px; +} + +input.search { + font-size: 75%; + color: #000080; + font-weight: normal; + background-color: #e8eef2; +} +*/ + +td.tiny { + font-size: 75%; +} + +.dirtab { + padding: 4px; + border-collapse: collapse; + border: 1px solid #A3B4D7; +} + +th.dirtab { + background: #EBEFF6; + font-weight: bold; +} + +hr { + height: 0px; + border: none; + border-top: 1px solid #4A6AAA; +} + +hr.footer { + height: 1px; +} + +/* @group Member Descriptions */ + +table.memberdecls { + border-spacing: 0px; + padding: 0px; +} + +.mdescLeft, .mdescRight, +.memItemLeft, .memItemRight, +.memTemplItemLeft, .memTemplItemRight, .memTemplParams { + background-color: #F9FAFC; + border: none; + margin: 4px; + padding: 1px 0 0 8px; +} + +.mdescLeft, .mdescRight { + padding: 0px 8px 4px 8px; + color: #555; +} + +.memItemLeft, .memItemRight, .memTemplParams { + border-top: 1px solid #C4CFE5; +} + +.memItemLeft, .memTemplItemLeft { + white-space: nowrap; +} + +.memItemRight { + width: 100%; +} + +.memTemplParams { + color: #4665A2; + white-space: nowrap; +} + +/* @end */ + +/* @group Member Details */ + +/* Styles for detailed member documentation */ + +.memtemplate { + font-size: 80%; + color: #4665A2; + font-weight: normal; + margin-left: 9px; +} + +.memnav { + background-color: #EBEFF6; + border: 1px solid #A3B4D7; + text-align: center; + margin: 2px; + margin-right: 15px; + padding: 2px; +} + +.mempage { + width: 100%; +} + +.memitem { + padding: 0; + margin-bottom: 10px; + margin-right: 5px; +} + +.memname { + white-space: nowrap; + font-weight: bold; + margin-left: 6px; +} + +.memproto, dl.reflist dt { + border-top: 1px solid #A8B8D9; + border-left: 1px solid #A8B8D9; + border-right: 1px solid #A8B8D9; + padding: 6px 0px 6px 0px; + color: #253555; + font-weight: bold; + text-shadow: 0px 1px 1px rgba(255, 255, 255, 0.9); + /* opera specific markup */ + box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15); + border-top-right-radius: 8px; + border-top-left-radius: 8px; + /* firefox specific markup */ + -moz-box-shadow: rgba(0, 0, 0, 0.15) 5px 5px 5px; + -moz-border-radius-topright: 8px; + -moz-border-radius-topleft: 8px; + /* webkit specific markup */ + -webkit-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15); + -webkit-border-top-right-radius: 8px; + -webkit-border-top-left-radius: 8px; + background-image:url('nav_f.png'); + background-repeat:repeat-x; + background-color: #E2E8F2; + +} + +.memdoc, dl.reflist dd { + border-bottom: 1px solid #A8B8D9; + border-left: 1px solid #A8B8D9; + border-right: 1px solid #A8B8D9; + padding: 2px 5px; + background-color: #FBFCFD; + border-top-width: 0; + /* opera specific markup */ + border-bottom-left-radius: 8px; + border-bottom-right-radius: 8px; + box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15); + /* firefox specific markup */ + -moz-border-radius-bottomleft: 8px; + -moz-border-radius-bottomright: 8px; + -moz-box-shadow: rgba(0, 0, 0, 0.15) 5px 5px 5px; + background-image: -moz-linear-gradient(center top, #FFFFFF 0%, #FFFFFF 60%, #F7F8FB 95%, #EEF1F7); + /* webkit specific markup */ + -webkit-border-bottom-left-radius: 8px; + -webkit-border-bottom-right-radius: 8px; + -webkit-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15); + background-image: -webkit-gradient(linear,center top,center bottom,from(#FFFFFF), color-stop(0.6,#FFFFFF), color-stop(0.60,#FFFFFF), color-stop(0.95,#F7F8FB), to(#EEF1F7)); +} + +dl.reflist dt { + padding: 5px; +} + +dl.reflist dd { + margin: 0px 0px 10px 0px; + padding: 5px; +} + +.paramkey { + text-align: right; +} + +.paramtype { + white-space: nowrap; +} + +.paramname { + color: #602020; + white-space: nowrap; +} +.paramname em { + font-style: normal; +} + +.params, .retval, .exception, .tparams { + border-spacing: 6px 2px; +} + +.params .paramname, .retval .paramname { + font-weight: bold; + vertical-align: top; +} + +.params .paramtype { + font-style: italic; + vertical-align: top; +} + +.params .paramdir { + font-family: "courier new",courier,monospace; + vertical-align: top; +} + + + + +/* @end */ + +/* @group Directory (tree) */ + +/* for the tree view */ + +.ftvtree { + font-family: sans-serif; + margin: 0px; +} + +/* these are for tree view when used as main index */ + +.directory { + font-size: 9pt; + font-weight: bold; + margin: 5px; +} + +.directory h3 { + margin: 0px; + margin-top: 1em; + font-size: 11pt; +} + +/* +The following two styles can be used to replace the root node title +with an image of your choice. Simply uncomment the next two styles, +specify the name of your image and be sure to set 'height' to the +proper pixel height of your image. +*/ + +/* +.directory h3.swap { + height: 61px; + background-repeat: no-repeat; + background-image: url("yourimage.gif"); +} +.directory h3.swap span { + display: none; +} +*/ + +.directory > h3 { + margin-top: 0; +} + +.directory p { + margin: 0px; + white-space: nowrap; +} + +.directory div { + display: none; + margin: 0px; +} + +.directory img { + vertical-align: -30%; +} + +/* these are for tree view when not used as main index */ + +.directory-alt { + font-size: 100%; + font-weight: bold; +} + +.directory-alt h3 { + margin: 0px; + margin-top: 1em; + font-size: 11pt; +} + +.directory-alt > h3 { + margin-top: 0; +} + +.directory-alt p { + margin: 0px; + white-space: nowrap; +} + +.directory-alt div { + display: none; + margin: 0px; +} + +.directory-alt img { + vertical-align: -30%; +} + +/* @end */ + +div.dynheader { + margin-top: 8px; +} + +address { + font-style: normal; + color: #2A3D61; +} + +table.doxtable { + border-collapse:collapse; +} + +table.doxtable td, table.doxtable th { + border: 1px solid #2D4068; + padding: 3px 7px 2px; +} + +table.doxtable th { + background-color: #374F7F; + color: #FFFFFF; + font-size: 110%; + padding-bottom: 4px; + padding-top: 5px; + text-align:left; +} + +table.fieldtable { + width: 100%; + margin-bottom: 10px; + border: 1px solid #A8B8D9; + border-spacing: 0px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + border-radius: 4px; + -moz-box-shadow: rgba(0, 0, 0, 0.15) 2px 2px 2px; + -webkit-box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.15); + box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.15); +} + +.fieldtable td, .fieldtable th { + padding: 3px 7px 2px; +} + +.fieldtable td.fieldtype, .fieldtable td.fieldname { + white-space: nowrap; + border-right: 1px solid #A8B8D9; + border-bottom: 1px solid #A8B8D9; + vertical-align: top; +} + +.fieldtable td.fielddoc { + border-bottom: 1px solid #A8B8D9; + width: 100%; +} + +.fieldtable tr:last-child td { + border-bottom: none; +} + +.fieldtable th { + background-image:url('nav_f.png'); + background-repeat:repeat-x; + background-color: #E2E8F2; + font-size: 90%; + color: #253555; + padding-bottom: 4px; + padding-top: 5px; + text-align:left; + -moz-border-radius-topleft: 4px; + -moz-border-radius-topright: 4px; + -webkit-border-top-left-radius: 4px; + -webkit-border-top-right-radius: 4px; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + border-bottom: 1px solid #A8B8D9; +} + + +.tabsearch { + top: 0px; + left: 10px; + height: 36px; + background-image: url('tab_b.png'); + z-index: 101; + overflow: hidden; + font-size: 13px; +} + +.navpath ul +{ + font-size: 11px; + background-image:url('tab_b.png'); + background-repeat:repeat-x; + height:30px; + line-height:30px; + color:#8AA0CC; + border:solid 1px #C2CDE4; + overflow:hidden; + margin:0px; + padding:0px; +} + +.navpath li +{ + list-style-type:none; + float:left; + padding-left:10px; + padding-right:15px; + background-image:url('bc_s.png'); + background-repeat:no-repeat; + background-position:right; + color:#364D7C; +} + +.navpath li.navelem a +{ + height:32px; + display:block; + text-decoration: none; + outline: none; +} + +.navpath li.navelem a:hover +{ + color:#6884BD; +} + +.navpath li.footer +{ + list-style-type:none; + float:right; + padding-left:10px; + padding-right:15px; + background-image:none; + background-repeat:no-repeat; + background-position:right; + color:#364D7C; + font-size: 8pt; +} + + +div.summary +{ + float: right; + font-size: 8pt; + padding-right: 5px; + width: 50%; + text-align: right; +} + +div.summary a +{ + white-space: nowrap; +} + +div.ingroups +{ + margin-left: 5px; + font-size: 8pt; + padding-left: 5px; + width: 50%; + text-align: left; +} + +div.ingroups a +{ + white-space: nowrap; +} + +div.header +{ + background-image:url('nav_h.png'); + background-repeat:repeat-x; + background-color: #F9FAFC; + margin: 0px; + border-bottom: 1px solid #C4CFE5; +} + +div.headertitle +{ + padding: 5px 5px 5px 7px; +} + +dl +{ + padding: 0 0 0 10px; +} + +dl.note, dl.warning, dl.attention, dl.pre, dl.post, dl.invariant, dl.deprecated, dl.todo, dl.test, dl.bug +{ + border-left:4px solid; + padding: 0 0 0 6px; +} + +dl.note +{ + border-color: #D0C000; +} + +dl.warning, dl.attention +{ + border-color: #FF0000; +} + +dl.pre, dl.post, dl.invariant +{ + border-color: #00D000; +} + +dl.deprecated +{ + border-color: #505050; +} + +dl.todo +{ + border-color: #00C0E0; +} + +dl.test +{ + border-color: #3030E0; +} + +dl.bug +{ + border-color: #C08050; +} + +#projectlogo +{ + text-align: center; + vertical-align: bottom; + border-collapse: separate; +} + +#projectlogo img +{ + border: 0px none; +} + +#projectname +{ + font: 300% Tahoma, Arial,sans-serif; + margin: 0px; + padding: 2px 0px; +} + +#projectbrief +{ + font: 120% Tahoma, Arial,sans-serif; + margin: 0px; + padding: 0px; +} + +#projectnumber +{ + font: 50% Tahoma, Arial,sans-serif; + margin: 0px; + padding: 0px; +} + +#titlearea +{ + padding: 0px; + margin: 0px; + width: 100%; + border-bottom: 1px solid #5373B4; +} + +.image +{ + text-align: center; +} + +.dotgraph +{ + text-align: center; +} + +.mscgraph +{ + text-align: center; +} + +.caption +{ + font-weight: bold; +} + +div.zoom +{ + border: 1px solid #90A5CE; +} + +dl.citelist { + margin-bottom:50px; +} + +dl.citelist dt { + color:#334975; + float:left; + font-weight:bold; + margin-right:10px; + padding:5px; +} + +dl.citelist dd { + margin:2px 0; + padding:5px 0; +} + +@media print +{ + #top { display: none; } + #side-nav { display: none; } + #nav-path { display: none; } + body { overflow:visible; } + h1, h2, h3, h4, h5, h6 { page-break-after: avoid; } + .summary { display: none; } + .memitem { page-break-inside: avoid; } + #doc-content + { + margin-left:0 !important; + height:auto !important; + width:auto !important; + overflow:inherit; + display:inline; + } + pre.fragment + { + overflow: visible; + text-wrap: unrestricted; + white-space: -moz-pre-wrap; /* Moz */ + white-space: -pre-wrap; /* Opera 4-6 */ + white-space: -o-pre-wrap; /* Opera 7 */ + white-space: pre-wrap; /* CSS3 */ + word-wrap: break-word; /* IE 5.5+ */ + } +} + ADDED dox/doxyfile.in Index: dox/doxyfile.in ================================================================== --- dox/doxyfile.in +++ dox/doxyfile.in @@ -0,0 +1,1806 @@ +# Doxyfile 1.8.0 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or sequence of words) that should +# identify the project. Note that if you do not use Doxywizard you need +# to put quotes around the project name if it contains spaces. + +PROJECT_NAME = Minx + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer +# a quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = "API Docs" + +# With the PROJECT_LOGO tag one can specify an logo or icon that is +# included in the documentation. The maximum height of the logo should not +# exceed 55 pixels and the maximum width should not exceed 200 pixels. +# Doxygen will copy the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = @CMAKE_SOURCE_DIR@/api + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, +# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English +# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, +# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, +# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = YES + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful if your file system +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = boostpylink="Boost.Python" + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding +# "class=itcl::class" will allow you to use the command class in the +# itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given extension. +# Doxygen has a built-in mapping, but you can override or extend it using this +# tag. The format is ext=language, where ext is a file extension, and language +# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C, +# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make +# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C +# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions +# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If MARKDOWN_SUPPORT is enabled (the default) then doxygen pre-processes all +# comments according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you +# can mix doxygen, HTML, and XML commands with Markdown formatting. +# Disable only in case of backward compatibilities issues. + +MARKDOWN_SUPPORT = NO + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also makes the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter +# and setter methods for a property. Setting this option to YES (the default) +# will make doxygen replace the get and set methods by a property in the +# documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = YES + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and +# unions are shown inside the group in which they are included (e.g. using +# @ingroup) instead of on a separate page (for HTML and Man pages) or +# section (for LaTeX and RTF). + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and +# unions with only public data fields will be shown inline in the documentation +# of the scope in which they are defined (i.e. file, namespace, or group +# documentation), provided this scope is documented. If set to NO (the default), +# structs, classes, and unions are shown on a separate page (for HTML and Man +# pages) or section (for LaTeX and RTF). + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penalty. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will roughly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols. + +SYMBOL_CACHE_SIZE = 0 + +# Similar to the SYMBOL_CACHE_SIZE the size of the symbol lookup cache can be +# set using LOOKUP_CACHE_SIZE. This cache is used to resolve symbols given +# their name and scope. Since this can be an expensive process and often the +# same symbol appear multiple times in the code, doxygen keeps a cache of +# pre-resolved symbols. If the cache is too small doxygen will become slower. +# If the cache is too large, memory is wasted. The cache size is given by this +# formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal +# scope will be included in the documentation. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = NO + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespaces are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = YES + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = YES + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = YES + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen +# will list include files with double quotes in the documentation +# rather than with sharp brackets. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen +# will sort the (brief and detailed) documentation of class members so that +# constructors and destructors are listed first. If set to NO (the default) +# the constructors will appear in the respective orders defined by +# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. +# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO +# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to +# do proper type resolution of all parameters of a function it will reject a +# match between the prototype and the implementation of a member function even +# if there is only one candidate or it is obvious which candidate to choose +# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen +# will still accept a match between prototype and implementation in such cases. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or macro consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and macros in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = NO + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. The default is NO. + +SHOW_DIRECTORIES = NO + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = NO + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = NO + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. The create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. +# You can optionally specify a file name after the option, if omitted +# DoxygenLayout.xml will be used as the name of the layout file. + +#LAYOUT_FILE = @CMAKE_CURRENT_SOURCE_DIR@/layout.xml +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files +# containing the references data. This must be a list of .bib files. The +# .bib extension is automatically appended if omitted. Using this command +# requires the bibtex tool to be installed. See also +# http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style +# of the bibliography can be controlled using LATEX_BIB_STYLE. To use this +# feature you need bibtex and perl available in the search path. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = YES + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = NO + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = NO + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# The WARN_NO_PARAMDOC option can be enabled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = @CMAKE_SOURCE_DIR@/minxlib \ + @CMAKE_SOURCE_DIR@/core \ + @CMAKE_SOURCE_DIR@/layout \ + @CMAKE_SOURCE_DIR@/dox + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh +# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py +# *.f90 *.f *.for *.vhd *.vhdl + +FILE_PATTERNS = *.hh \ + *.py + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = __init__.py + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty or if +# non of the patterns match the file name, INPUT_FILTER is applied. + +FILTER_PATTERNS = *.py=@CMAKE_CURRENT_SOURCE_DIR@/doxypy --autobrief + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) +# and it is also possible to disable source filtering for a specific pattern +# using *.ext= (so without naming a filter). This option only has effect when +# FILTER_SOURCE_FILES is enabled. + +FILTER_SOURCE_PATTERNS = + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = YES + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = . + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. Note that when using a custom header you are responsible +# for the proper inclusion of any scripts and style sheets that doxygen +# needs, which is dependent on the configuration options used. +# It is advised to generate a default header using "doxygen -w html +# header.html footer.html stylesheet.css YourConfigFile" and then modify +# that header. Note that the header is subject to change so you typically +# have to redo this when upgrading to a newer version of doxygen or when +# changing the value of configuration settings such as GENERATE_TREEVIEW! + +HTML_HEADER = @CMAKE_CURRENT_SOURCE_DIR@/header.html + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = @CMAKE_CURRENT_SOURCE_DIR@/footer.html + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# style sheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = @CMAKE_CURRENT_SOURCE_DIR@/dox.css + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that +# the files will be copied as-is; there are no commands or markers available. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. +# Doxygen will adjust the colors in the style sheet and background images +# according to this color. Hue is specified as an angle on a colorwheel, +# see http://en.wikipedia.org/wiki/Hue for more information. +# For instance the value 0 represents red, 60 is yellow, 120 is green, +# 180 is cyan, 240 is blue, 300 purple, and 360 is red again. +# The allowed range is 0 to 359. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of +# the colors in the HTML output. For a value of 0 the output will use +# grayscales only. A value of 255 will produce the most vivid colors. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to +# the luminance component of the colors in the HTML output. Values below +# 100 gradually make the output lighter, whereas values above 100 make +# the output darker. The value divided by 100 is the actual gamma applied, +# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, +# and 100 does not change the gamma. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting +# this to NO can help when comparing the output of multiple runs. + +HTML_TIMESTAMP = NO + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. For this to work a browser that supports +# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox +# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). + +HTML_DYNAMIC_SECTIONS = NO + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated +# that can be used as input for Qt's qhelpgenerator to generate a +# Qt Compressed Help (.qch) of the generated HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to +# add. For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see +# +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's +# filter section matches. +# +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files +# will be generated, which together with the HTML files, form an Eclipse help +# plugin. To install this plugin and make it available under the help contents +# menu in Eclipse, the contents of the directory containing the HTML and XML +# files needs to be copied into the plugins directory of eclipse. The name of +# the directory within the plugins directory should be the same as +# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before +# the help appears. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have +# this name. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) +# at top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. Since the tabs have the same information as the +# navigation tree you can set this option to NO if you already set +# GENERATE_TREEVIEW to YES. + +DISABLE_INDEX = YES + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to YES, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). +# Windows users are probably better off using the HTML help feature. +# Since the tree basically has the same information as the tab index you +# could consider to set DISABLE_INDEX to NO when enabling this option. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values +# (range [0,1..20]) that doxygen will group on one line in the generated HTML +# documentation. Note that a value of 0 will completely suppress the enum +# values from appearing in the overview section. + +ENUM_VALUES_PER_LINE = 4 + +# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories, +# and Class Hierarchy pages using a tree view instead of an ordered list. + +USE_INLINE_TREES = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open +# links to external symbols imported via tag files in a separate window. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are +# not supported properly for IE 6.0, but are supported on all modern browsers. +# Note that when changing this option you need to delete any form_*.png files +# in the HTML output before the changes have effect. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax +# (see http://www.mathjax.org) which uses client side Javascript for the +# rendering instead of using prerendered bitmaps. Use this if you do not +# have LaTeX installed or if you want to formulas look prettier in the HTML +# output. When enabled you may also need to install MathJax separately and +# configure the path to it using the MATHJAX_RELPATH option. + +USE_MATHJAX = NO + +# When MathJax is enabled you need to specify the location relative to the +# HTML output directory using the MATHJAX_RELPATH option. The destination +# directory should contain the MathJax.js script. For instance, if the mathjax +# directory is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to +# the MathJax Content Delivery Network so you can quickly see the result without +# installing MathJax. However, it is strongly recommended to install a local +# copy of MathJax from http://www.mathjax.org before deployment. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension +# names that should be enabled during MathJax rendering. + +MATHJAX_EXTENSIONS = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box +# for the HTML output. The underlying search engine uses javascript +# and DHTML and should work on any modern browser. Note that when using +# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets +# (GENERATE_DOCSET) there is already a search function so this one should +# typically be disabled. For large projects the javascript based search engine +# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. + +SEARCHENGINE = NO + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a PHP enabled web server instead of at the web client +# using Javascript. Doxygen will generate the search PHP script and index +# file to put on the web server. The advantage of the server +# based approach is that it scales better to large projects and allows +# full text search. The disadvantages are that it is more difficult to setup +# and does not have live searching capabilities. + +SERVER_BASED_SEARCH = NO + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. +# Note that when enabling USE_PDFLATEX this option is only used for +# generating bitmaps for formulas in the HTML output, but not in the +# Makefile that is written to the output directory. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4 + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for +# the generated latex document. The footer should contain everything after +# the last chapter. If it is left blank doxygen will generate a +# standard footer. Notice: only use this tag if you know what you are doing! + +LATEX_FOOTER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +# If LATEX_SOURCE_CODE is set to YES then doxygen will include +# source code with syntax highlighting in the LaTeX output. +# Note that which sources are shown also depends on other settings +# such as SOURCE_BROWSER. + +LATEX_SOURCE_CODE = NO + +# The LATEX_BIB_STYLE tag can be used to specify the style to use for the +# bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See +# http://en.wikipedia.org/wiki/BibTeX for more info. + +LATEX_BIB_STYLE = plain + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load style sheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. This is useful +# if you want to understand what is going on. On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# pointed to by INCLUDE_PATH will be searched when a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = @minxdoc_PREDEFINED@ + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition that +# overrules the definition found in the source code. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all references to function-like macros +# that are alone on a line, have an all uppercase name, and do not end with a +# semicolon, because these will confuse the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. For each +# tag file the location of the external documentation should be added. The +# format of a tag file without this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths +# or URLs. Note that each tag file must have a unique name (where the name does +# NOT include the path). If a tag file is not located in the directory in which +# doxygen is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option also works with HAVE_DOT disabled, but it is recommended to +# install and use dot, since it yields more powerful graphs. + +CLASS_DIAGRAMS = NO + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = NO + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is +# allowed to run in parallel. When set to 0 (the default) doxygen will +# base this on the number of processors available in the system. You can set it +# explicitly to a value larger than 0 to get control over the balance +# between CPU load and processing speed. + +DOT_NUM_THREADS = 0 + +# By default doxygen will use the Helvetica font for all dot files that +# doxygen generates. When you want a differently looking font you can specify +# the font name using DOT_FONTNAME. You need to make sure dot is able to find +# the font, which can be done by putting it in a standard location or by setting +# the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the +# directory containing the font. + +DOT_FONTNAME = Helvetica + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the Helvetica font. +# If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to +# set the path where dot can find it. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = NO + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = NO + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = NO + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If the UML_LOOK tag is enabled, the fields and methods are shown inside +# the class node. If there are many fields or methods and many nodes the +# graph may become too big to be useful. The UML_LIMIT_NUM_FIELDS +# threshold limits the number of items for each type to make the size more +# managable. Set this to 0 for no limit. Note that the threshold may be +# exceeded by 50% before the limit is enforced. + +UML_LIMIT_NUM_FIELDS = 10 + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = NO + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will generate a graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = NO + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = NO + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are svg, png, jpg, or gif. +# If left blank png will be used. If you choose svg you need to set +# HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible in IE 9+ (other browsers do not have this requirement). + +DOT_IMAGE_FORMAT = png + +# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to +# enable generation of interactive SVG images that allow zooming and panning. +# Note that this requires a modern browser other than Internet Explorer. +# Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you +# need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible. Older versions of IE do not have SVG support. + +INTERACTIVE_SVG = NO + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the +# \mscfile command). + +MSCFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = NO + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES ADDED dox/doxypy Index: dox/doxypy ================================================================== --- dox/doxypy +++ dox/doxypy @@ -0,0 +1,437 @@ +#!/usr/bin/env python + +######################################################################## +# # +# This file is distributed with Minx but is not written by the Minx # +# Project Developers. This file's original authors and their contact # +# information are provided below in the __author__ and __website__ # +# variables. The Minx Project Developers would like to thank the # +# authors of this program for making it available under the terms of # +# the GPL, which allows us to bundle it with the Minx sources. # +# # +# NOTE: Recent versions of Debian (e.g., squeeze) and Ubuntu (e.g., # +# 12.04), make this program available in the python-doxypy package. # +# However, it has not yet made its way into the FreeBSD ports # +# collection (as of version 9). To simplify Minx development across # +# these two platforms, we simply bundle doxypy with Minx. If and when # +# doxypy becomes part of the package repositories of both these # +# operating systems, we should no longer need to distribute doxypy # +# along with Minx's source code. # +# # +# NOTE 2: This notice is the only change made to this file by the Minx # +# Project. Everything else is identical to the upstream version. # +# # +######################################################################## + +__applicationName__ = "doxypy" +__blurb__ = """ +doxypy is an input filter for Doxygen. It preprocesses python +files so that docstrings of classes and functions are reformatted +into Doxygen-conform documentation blocks. +""" + +__doc__ = __blurb__ + \ +""" +In order to make Doxygen preprocess files through doxypy, simply +add the following lines to your Doxyfile: + FILTER_SOURCE_FILES = YES + INPUT_FILTER = "python /path/to/doxypy.py" +""" + +__version__ = "0.4.2" +__date__ = "14th October 2009" +__website__ = "http://code.foosel.org/doxypy" + +__author__ = ( + "Philippe 'demod' Neumann (doxypy at demod dot org)", + "Gina 'foosel' Haeussge (gina at foosel dot net)" +) + +__licenseName__ = "GPL v2" +__license__ = """This program 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, either version 2 of the License, or +(at your option) any later version. + +This program 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 this program. If not, see . +""" + +import sys +import re + +from optparse import OptionParser, OptionGroup + +class FSM(object): + """Implements a finite state machine. + + Transitions are given as 4-tuples, consisting of an origin state, a target + state, a condition for the transition (given as a reference to a function + which gets called with a given piece of input) and a pointer to a function + to be called upon the execution of the given transition. + """ + + """ + @var transitions holds the transitions + @var current_state holds the current state + @var current_input holds the current input + @var current_transition hold the currently active transition + """ + + def __init__(self, start_state=None, transitions=[]): + self.transitions = transitions + self.current_state = start_state + self.current_input = None + self.current_transition = None + + def setStartState(self, state): + self.current_state = state + + def addTransition(self, from_state, to_state, condition, callback): + self.transitions.append([from_state, to_state, condition, callback]) + + def makeTransition(self, input): + """Makes a transition based on the given input. + + @param input input to parse by the FSM + """ + for transition in self.transitions: + [from_state, to_state, condition, callback] = transition + if from_state == self.current_state: + match = condition(input) + if match: + self.current_state = to_state + self.current_input = input + self.current_transition = transition + if options.debug: + print >>sys.stderr, "# FSM: executing (%s -> %s) for line '%s'" % (from_state, to_state, input) + callback(match) + return + +class Doxypy(object): + def __init__(self): + string_prefixes = "[uU]?[rR]?" + + self.start_single_comment_re = re.compile("^\s*%s(''')" % string_prefixes) + self.end_single_comment_re = re.compile("(''')\s*$") + + self.start_double_comment_re = re.compile("^\s*%s(\"\"\")" % string_prefixes) + self.end_double_comment_re = re.compile("(\"\"\")\s*$") + + self.single_comment_re = re.compile("^\s*%s(''').*(''')\s*$" % string_prefixes) + self.double_comment_re = re.compile("^\s*%s(\"\"\").*(\"\"\")\s*$" % string_prefixes) + + self.defclass_re = re.compile("^(\s*)(def .+:|class .+:)") + self.empty_re = re.compile("^\s*$") + self.hashline_re = re.compile("^\s*#.*$") + self.importline_re = re.compile("^\s*(import |from .+ import)") + + self.multiline_defclass_start_re = re.compile("^(\s*)(def|class)(\s.*)?$") + self.multiline_defclass_end_re = re.compile(":\s*$") + + ## Transition list format + # ["FROM", "TO", condition, action] + transitions = [ + ### FILEHEAD + + # single line comments + ["FILEHEAD", "FILEHEAD", self.single_comment_re.search, self.appendCommentLine], + ["FILEHEAD", "FILEHEAD", self.double_comment_re.search, self.appendCommentLine], + + # multiline comments + ["FILEHEAD", "FILEHEAD_COMMENT_SINGLE", self.start_single_comment_re.search, self.appendCommentLine], + ["FILEHEAD_COMMENT_SINGLE", "FILEHEAD", self.end_single_comment_re.search, self.appendCommentLine], + ["FILEHEAD_COMMENT_SINGLE", "FILEHEAD_COMMENT_SINGLE", self.catchall, self.appendCommentLine], + ["FILEHEAD", "FILEHEAD_COMMENT_DOUBLE", self.start_double_comment_re.search, self.appendCommentLine], + ["FILEHEAD_COMMENT_DOUBLE", "FILEHEAD", self.end_double_comment_re.search, self.appendCommentLine], + ["FILEHEAD_COMMENT_DOUBLE", "FILEHEAD_COMMENT_DOUBLE", self.catchall, self.appendCommentLine], + + # other lines + ["FILEHEAD", "FILEHEAD", self.empty_re.search, self.appendFileheadLine], + ["FILEHEAD", "FILEHEAD", self.hashline_re.search, self.appendFileheadLine], + ["FILEHEAD", "FILEHEAD", self.importline_re.search, self.appendFileheadLine], + ["FILEHEAD", "DEFCLASS", self.defclass_re.search, self.resetCommentSearch], + ["FILEHEAD", "DEFCLASS_MULTI", self.multiline_defclass_start_re.search, self.resetCommentSearch], + ["FILEHEAD", "DEFCLASS_BODY", self.catchall, self.appendFileheadLine], + + ### DEFCLASS + + # single line comments + ["DEFCLASS", "DEFCLASS_BODY", self.single_comment_re.search, self.appendCommentLine], + ["DEFCLASS", "DEFCLASS_BODY", self.double_comment_re.search, self.appendCommentLine], + + # multiline comments + ["DEFCLASS", "COMMENT_SINGLE", self.start_single_comment_re.search, self.appendCommentLine], + ["COMMENT_SINGLE", "DEFCLASS_BODY", self.end_single_comment_re.search, self.appendCommentLine], + ["COMMENT_SINGLE", "COMMENT_SINGLE", self.catchall, self.appendCommentLine], + ["DEFCLASS", "COMMENT_DOUBLE", self.start_double_comment_re.search, self.appendCommentLine], + ["COMMENT_DOUBLE", "DEFCLASS_BODY", self.end_double_comment_re.search, self.appendCommentLine], + ["COMMENT_DOUBLE", "COMMENT_DOUBLE", self.catchall, self.appendCommentLine], + + # other lines + ["DEFCLASS", "DEFCLASS", self.empty_re.search, self.appendDefclassLine], + ["DEFCLASS", "DEFCLASS", self.defclass_re.search, self.resetCommentSearch], + ["DEFCLASS", "DEFCLASS_MULTI", self.multiline_defclass_start_re.search, self.resetCommentSearch], + ["DEFCLASS", "DEFCLASS_BODY", self.catchall, self.stopCommentSearch], + + ### DEFCLASS_BODY + + ["DEFCLASS_BODY", "DEFCLASS", self.defclass_re.search, self.startCommentSearch], + ["DEFCLASS_BODY", "DEFCLASS_MULTI", self.multiline_defclass_start_re.search, self.startCommentSearch], + ["DEFCLASS_BODY", "DEFCLASS_BODY", self.catchall, self.appendNormalLine], + + ### DEFCLASS_MULTI + ["DEFCLASS_MULTI", "DEFCLASS", self.multiline_defclass_end_re.search, self.appendDefclassLine], + ["DEFCLASS_MULTI", "DEFCLASS_MULTI", self.catchall, self.appendDefclassLine], + ] + + self.fsm = FSM("FILEHEAD", transitions) + self.outstream = sys.stdout + + self.output = [] + self.comment = [] + self.filehead = [] + self.defclass = [] + self.indent = "" + + def __closeComment(self): + """Appends any open comment block and triggering block to the output.""" + + if options.autobrief: + if len(self.comment) == 1 \ + or (len(self.comment) > 2 and self.comment[1].strip() == ''): + self.comment[0] = self.__docstringSummaryToBrief(self.comment[0]) + + if self.comment: + block = self.makeCommentBlock() + self.output.extend(block) + + if self.defclass: + self.output.extend(self.defclass) + + def __docstringSummaryToBrief(self, line): + """Adds \\brief to the docstrings summary line. + + A \\brief is prepended, provided no other doxygen command is at the + start of the line. + """ + stripped = line.strip() + if stripped and not stripped[0] in ('@', '\\'): + return "\\brief " + line + else: + return line + + def __flushBuffer(self): + """Flushes the current outputbuffer to the outstream.""" + if self.output: + try: + if options.debug: + print >>sys.stderr, "# OUTPUT: ", self.output + print >>self.outstream, "\n".join(self.output) + self.outstream.flush() + except IOError: + # Fix for FS#33. Catches "broken pipe" when doxygen closes + # stdout prematurely upon usage of INPUT_FILTER, INLINE_SOURCES + # and FILTER_SOURCE_FILES. + pass + self.output = [] + + def catchall(self, input): + """The catchall-condition, always returns true.""" + return True + + def resetCommentSearch(self, match): + """Restarts a new comment search for a different triggering line. + + Closes the current commentblock and starts a new comment search. + """ + if options.debug: + print >>sys.stderr, "# CALLBACK: resetCommentSearch" + self.__closeComment() + self.startCommentSearch(match) + + def startCommentSearch(self, match): + """Starts a new comment search. + + Saves the triggering line, resets the current comment and saves + the current indentation. + """ + if options.debug: + print >>sys.stderr, "# CALLBACK: startCommentSearch" + self.defclass = [self.fsm.current_input] + self.comment = [] + self.indent = match.group(1) + + def stopCommentSearch(self, match): + """Stops a comment search. + + Closes the current commentblock, resets the triggering line and + appends the current line to the output. + """ + if options.debug: + print >>sys.stderr, "# CALLBACK: stopCommentSearch" + self.__closeComment() + + self.defclass = [] + self.output.append(self.fsm.current_input) + + def appendFileheadLine(self, match): + """Appends a line in the FILEHEAD state. + + Closes the open comment block, resets it and appends the current line. + """ + if options.debug: + print >>sys.stderr, "# CALLBACK: appendFileheadLine" + self.__closeComment() + self.comment = [] + self.output.append(self.fsm.current_input) + + def appendCommentLine(self, match): + """Appends a comment line. + + The comment delimiter is removed from multiline start and ends as + well as singleline comments. + """ + if options.debug: + print >>sys.stderr, "# CALLBACK: appendCommentLine" + (from_state, to_state, condition, callback) = self.fsm.current_transition + + # single line comment + if (from_state == "DEFCLASS" and to_state == "DEFCLASS_BODY") \ + or (from_state == "FILEHEAD" and to_state == "FILEHEAD"): + # remove comment delimiter from begin and end of the line + activeCommentDelim = match.group(1) + line = self.fsm.current_input + self.comment.append(line[line.find(activeCommentDelim)+len(activeCommentDelim):line.rfind(activeCommentDelim)]) + + if (to_state == "DEFCLASS_BODY"): + self.__closeComment() + self.defclass = [] + # multiline start + elif from_state == "DEFCLASS" or from_state == "FILEHEAD": + # remove comment delimiter from begin of the line + activeCommentDelim = match.group(1) + line = self.fsm.current_input + self.comment.append(line[line.find(activeCommentDelim)+len(activeCommentDelim):]) + # multiline end + elif to_state == "DEFCLASS_BODY" or to_state == "FILEHEAD": + # remove comment delimiter from end of the line + activeCommentDelim = match.group(1) + line = self.fsm.current_input + self.comment.append(line[0:line.rfind(activeCommentDelim)]) + if (to_state == "DEFCLASS_BODY"): + self.__closeComment() + self.defclass = [] + # in multiline comment + else: + # just append the comment line + self.comment.append(self.fsm.current_input) + + def appendNormalLine(self, match): + """Appends a line to the output.""" + if options.debug: + print >>sys.stderr, "# CALLBACK: appendNormalLine" + self.output.append(self.fsm.current_input) + + def appendDefclassLine(self, match): + """Appends a line to the triggering block.""" + if options.debug: + print >>sys.stderr, "# CALLBACK: appendDefclassLine" + self.defclass.append(self.fsm.current_input) + + def makeCommentBlock(self): + """Indents the current comment block with respect to the current + indentation level. + + @returns a list of indented comment lines + """ + doxyStart = "##" + commentLines = self.comment + + commentLines = map(lambda x: "%s# %s" % (self.indent, x), commentLines) + l = [self.indent + doxyStart] + l.extend(commentLines) + + return l + + def parse(self, input): + """Parses a python file given as input string and returns the doxygen- + compatible representation. + + @param input the python code to parse + @returns the modified python code + """ + lines = input.split("\n") + + for line in lines: + self.fsm.makeTransition(line) + + if self.fsm.current_state == "DEFCLASS": + self.__closeComment() + + return "\n".join(self.output) + + def parseFile(self, filename): + """Parses a python file given as input string and returns the doxygen- + compatible representation. + + @param input the python code to parse + @returns the modified python code + """ + f = open(filename, 'r') + + for line in f: + self.parseLine(line.rstrip('\r\n')) + if self.fsm.current_state == "DEFCLASS": + self.__closeComment() + self.__flushBuffer() + f.close() + + def parseLine(self, line): + """Parse one line of python and flush the resulting output to the + outstream. + + @param line the python code line to parse + """ + self.fsm.makeTransition(line) + self.__flushBuffer() + +def optParse(): + """Parses commandline options.""" + parser = OptionParser(prog=__applicationName__, version="%prog " + __version__) + + parser.set_usage("%prog [options] filename") + parser.add_option("--autobrief", + action="store_true", dest="autobrief", + help="use the docstring summary line as \\brief description" + ) + parser.add_option("--debug", + action="store_true", dest="debug", + help="enable debug output on stderr" + ) + + ## parse options + global options + (options, filename) = parser.parse_args() + + if not filename: + print >>sys.stderr, "No filename given." + sys.exit(-1) + + return filename[0] + +def main(): + """Starts the parser on the file given by the filename as the first + argument on the commandline. + """ + filename = optParse() + fsm = Doxypy() + fsm.parseFile(filename) + +if __name__ == "__main__": + main() ADDED dox/footer.html Index: dox/footer.html ================================================================== --- dox/footer.html +++ dox/footer.html @@ -0,0 +1,2 @@ + + ADDED dox/header.html Index: dox/header.html ================================================================== --- dox/header.html +++ dox/header.html @@ -0,0 +1,56 @@ + + + + + +$projectname: $title +$title + + +$treeview +$search +$mathjax + + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + +
+
+ $projectname + +  $projectnumber + +
+ + + +
+
$projectbrief
+
$searchbox
+
+ ADDED dox/mainpage.hh Index: dox/mainpage.hh ================================================================== --- dox/mainpage.hh +++ dox/mainpage.hh @@ -0,0 +1,124 @@ +/** + @file mainpage.hh + @brief Contents of main page of API docs. + + This file exists only to be able to put stuff on the + Doxygen-generated docs' main page. It is not actually used by + minxlib or any other part of Minx. +*/ + +/* + Copyright (C) 2012 The Minx Project Developers + + See wiki/copyright.wiki for the full list of authors who have + contributed to this project. +*/ + +/* + This file is part of Minx. + + Minx 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, either version 3 of the License, or + (at your option) any later version. + + Minx 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 Minx. If not, see . +*/ + +//----------------------- API DOCS MAIN PAGE --------------------------- + +/** + @mainpage Minx API Documentation + + Minx is not really a tiling window manager. Rather it is a library + that allows you to write your own tiling window manager. However, in + case you don't want to write your own window manager, it provides a + default configuration so that it can be used with minimal effort. + Minx also tries to make it easy to change the defaults to suit your + tastes. + + However, since it is impossible to anticipate everyone's + preferences, Minx provides the necessary infrastructure to radically + alter its behaviour. These pages document the various classes and + functions that Minx implements to enable end-users to write a highly + customized window manager. + +
+ + @section sec_core Minx Core + + Here are the different classes in Minx's Python core: + + @li @ref minx.core.wm.wm "wm" + @li @ref minx.core.config.config "config" + @li @ref grp_minx_core_hooks "hooks" + @li @ref grp_minx_core_layman "layman" + @li @ref grp_minx_version "version" + @li @ref grp_minx_core_window "window" + @li @ref minx.core.focus_list.focus_list "focus_list" + @li @ref minx.core.xevents.xevents "xevents" + + Not all of the above classes are meant to be used directly by + end-users. At this time, in fact, only the @ref minx.core.wm.wm "wm", + @ref minx.core.config.config "config", and + @ref grp_minx_core_hooks "hooks" classes are for public + consumption. The @ref grp_minx_version "minx.version" object may + also be used. Everything else should be considered internal to Minx. + +
+ + @section sec_layouts Minx Layouts + + Minx comes with the following layout classes: + + @li @ref minx.layout.base.base "base" + @li @ref minx.layout.full.full "full" + + You can implement your own layouts by deriving from the + @ref minx.layout.base.base "minx.layout.base" class and overriding + some of its methods. + +
+ + @section sec_minxlib minxlib + + As you can tell from the above, Minx is written in Python and is + largely concerned with the details of laying out windows, + circulating input focus, and so on. However, in order to be able to + actually achieve that functionality, it needs to talk to the + underlying display server, which it does using minxlib, a custom + Xlib wrapper written in C++. + + minxlib provides the Python parts of Minx a high-level, + object-oriented API for talking to the X server. In general, + end-users will rely on Minx's Python classes to effect various + customizations and should not need to use minxlib directly. + Nonetheless, the following pages document its public interface: + + @li @ref minxlib::display "display" + @li @ref minxlib::window "window" + @li @ref grp_minxlib_events "Event Classes" + @li @ref grp_minxlib_exceptions "Exception Classes" + @li @ref grp_minxlib_keymap "Key Bindings API" + @li @ref minxlib::logging "logging" + @li @ref minxlib::version "version" +*/ + +//---------------------------------------------------------------------- + +/**********************************************/ +/* Editor config: */ +/**********************************************/ +/* Local Variables: */ +/* indent-tabs-mode: nil */ +/* c-basic-offset: 4 */ +/* End: */ +/**********************************************/ +/* vim: set expandtab shiftwidth=4 tabstop=4: */ +/**********************************************/ ADDED layout/__init__.py Index: layout/__init__.py ================================================================== --- layout/__init__.py +++ layout/__init__.py @@ -0,0 +1,53 @@ +# +# __init__.py -- initialization for minx.layout module +# + +# +# Copyright (C) 2012 The Minx Project Developers +# +# See wiki/copyright.wiki in the top-level directory of the Minx source +# distribution for the full list of authors who have contributed to this +# project. +# + +# +# This file is part of Minx. +# +# Minx 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, either version 3 of the License, or (at your +# option) any later version. +# +# Minx 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 Minx. If not, see . +# + +#------------------------------- IMPORTS -------------------------------- + +# Pull in various classes from minx.layout so that end-users see them, +# for example, as minx.layout.full instead of minx.layout.full.full, and +# so on. +from base import base +from full import full + +#------------------------------- EXPORTS -------------------------------- + +__all__ = ["base", "full"] + +#----------------------------------------------------------------------- + +############################################## +# Editor config: # +############################################## +# Local Variables: # +# indent-tabs-mode: nil # +# py-indent-offset: 4 # +# End: # +############################################## +# vim: set expandtab shiftwidth=4 tabstop=4: # +############################################## ADDED layout/base.py Index: layout/base.py ================================================================== --- layout/base.py +++ layout/base.py @@ -0,0 +1,391 @@ +# +# base.py -- base class for Minx layouts +# + +# +# Copyright (C) 2012 The Minx Project Developers +# +# See wiki/copyright.wiki in the top-level directory of the Minx source +# distribution for the full list of authors who have contributed to this +# project. +# + +# +# This file is part of Minx. +# +# Minx 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, either version 3 of the License, or (at your +# option) any later version. +# +# Minx 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 Minx. If not, see . +# + +#-------------------------- MODULE DOC STRING --------------------------- + +"""@package minx.layout.base +Base class for Minx layouts. + +This module implements a base class for layouts. + +""" + +#------------------------------- IMPORTS -------------------------------- + +# Standard library +import logging + +# Minx +import minx.core.layman +from minx.core import minxlib + +#---------------------------- MODULE LOGGER ----------------------------- + +logger = logging.getLogger(__name__) + +#--------------------------- EVENT HANDLERS ----------------------------- + +# Since layouts use X windows to manage their windows, the layout window +# itself will be visible when a layout has no windows to manage. We don't +# want that; we want layouts to be invisible. To effect that +# invisibility, we map the layout's window when a window is added to the +# layout and unmap it when it no longer has any visible windows. +# +# The following event handlers take care of the above-mentioned strategy +# for keeping layouts invisible. +# +# NOTE: Rather than rely on the event dispatching implemented in xevents, +# we add our own hooks for the relevant X events because we don't want +# layout subclasses to be able to override this mechanism nor require +# them to have to call base class methods in order to ensure correct +# operation of things such as layout invisibility. + +# Need the layout manager maintained by the main window manager object so +# that we can find the layouts for different windows. +_layouts = None + +# When a window is reparented, show its layout if the layout isn't +# already visible and then let the layout object know so it can readjust +# window geometries as required. +def _on_reparent(e): + try: + logger.debug('window {} reparented by {}'. + format(e.target.id, e.new_parent.id)) + + layout = _layouts.find(e.new_parent) + logger.debug('window {} being managed by layout {}'. + format(e.target.id, layout)) + + if not layout.window.is_mapped(): + logger.debug('showing layout {}'.format(layout)) + layout.window.show() + + layout.reparented(e.target) + + except minx.core.layman.unknown_layout: + logger.warning('no layout corresponding to window {}'. + format(e.new_parent)) + logger.debug('window {} not being managed (bogus reparent_notify?)'. + format(e.target.id)) + +# When a window is unmapped, also unmap its layout if the layout has no +# more visible windows. +def _on_unmap(e): + try: + logger.debug('window {} unmapped'.format(e.target.id)) + + layout = _layouts.find(e.parent) + logger.debug('window {} being managed by layout {}'. + format(e.target.id, layout)) + + for w in layout.window.children(): + if w.is_mapped(): + logger.debug('layout {} has visible child {}; no need to hide'. + format(layout, w.id)) + return + + logger.debug('hiding layout {} because it has no visible children'. + format(layout)) + layout.window.hide() + + except minx.core.layman.unknown_layout: + pass + +# Whenever focus changes, inform the layouts managing the affected +# windows (in case they need to readjust window geometries to take +# un/focused border sizes into account). +def _on_focus_in(e): + try: + _layouts.find(e.target.parent()).focused(e.target) + except minx.core.layman.unknown_layout: + pass + +def _on_focus_out(e): + try: + _layouts.find(e.target.parent()).unfocused(e.target) + except minx.core.layman.unknown_layout: + pass + +#-------------------------------- CLASS --------------------------------- + +class base: + """A base class for Minx layouts. + + This class implements a base class from which all Minx layout classes + must be derived. This class is not meant to be instantiated; instead, + you should instantiate one of the layout subclasses provided by Minx + (or one of the custom layouts you implement yourself). + + """ + + # Constructor + def __init__(self, m, p, r = None): + """Layout base class constructor. + + @param m The @ref minx.core.wm.wm "main window manager object". + @param p The layout's parent @ref minxlib::window "minxlib.window". + @param r The rectangle within parent window assigned to this layout. + + Each layout class's constructor must call this base class + constructor, passing it at least the parameters m and p, which we + need because a layout is basically an X window encapsulated by a + Python object with some additional methods tacked on. Thus, in + order to be able to create the layout window, we need the display + connection (which is in the @ref minx.core.wm.wm "wm" class) and + the parent window of the layout window. + + The parameter r is optional. It specifies the rectangle within + the parent window that the layout will occupy. It should be a + tuple of four integers. The first two integers in this tuple + specify the x and y coordinates respectively of the layout + window's top-left corner; the third and fourth integers are the + layout window's width and height respectively. + + If r is not supplied by the subclass constructor, the layout will + occupy the entire area of the parent window. + + After creating the layout's window, this method sets some + properties on the newly created window and then maps it on screen. + + @note As mentioned earlier, this class is not meant to be + instantiated directly. Thus, this constructor must only be called + by subclasses. + + """ + global _layouts + if _layouts == None: + _layouts = m.layouts + m.hooks.add('x_reparent_notify', _on_reparent, m.hooks.MAX_PRIORITY) + m.hooks.add( 'x_unmap_notify', _on_unmap, m.hooks.MAX_PRIORITY) + m.hooks.add('x_focus_in', _on_focus_in ) + m.hooks.add('x_focus_out', _on_focus_out) + + x, y, w, h = p.geometry()[:4] # don't need border width + if r != None: # use supplied rectangle instead of entire parent window + x, y, w, h = r + + self.window = m.display.create_window(p, x, y, w, h) + + name = 'minx.layout.{}.{}'.format(self.__class__.__name__, + self.window.id) + self.window.set_properties({'class': 'minx.layout', 'name' : name}) + self.window.select_events(minxlib.substructure_redirect_mask | + minxlib.substructure_notify_mask) + + # Convert layout to string + def __str__(self): + """Return layout's name. + + This method will convert the layout object to a human-readable + string representation (which is useful for debugging as well as + identifying layouts). + + """ + prop = self.window.properties() + return prop['name'] + + # Check if layout will manage window + def will_manage(self, w): + """Check if this layout is willing to manage specified window. + + @param w The @ref minxlib::window "minxlib.window" to be managed. + + This method returns True if the layout is willing to manage the + window w, False otherwise. + + The intent of this function is to allow layouts to decide whether + or not they want to accept a window. For example, if a layout is + written to expressly work with a particular application, it can + return True for that application's windows and False for all the + others. + + This method may be overridden by layout subclasses. The base + class implementation returns True. Thus, unless a subclass + overrides this function, it will be taken as willing to manage + all windows. + + """ + logger.debug('layout {} will manage window {}'.format(self, w.id)) + return True + + # Manage specified window + def add(self, w): + """Add a window to be managed by this layout. + + @param w The @ref minxlib::window "minxlib.window" to be managed. + + This method tells the layout that it should manage the given + window, which involves, among other things, reparenting the + client window so that this layout becomes its parent. + + """ + w.select_events(minxlib.focus_change_mask) + w.reparent(self.window) + + # Reparent notification + def reparented(self, w): + """Reparent notification. + + @param w The @ref minxlib::window "minxlib.window" that was reparented. + + When the @ref minx.core.wm.wm "main window manager object" + receives a reparent_notify message in its event loop, it + will eventually end up calling this method on the layout object + that has reparented the window w. + + This method is meant to be overridden by layout subclasses. The + base class implementation does nothing. + + """ + logger.warning('unhandled reparent_notify by layout ({})'. + format(self)) + + # Configure requests + def configure_request(self, n, x, y, w, h): + """Decide window geometry on configure requests for top-level windows. + + @param n The @ref minxlib::window "minxlib.window" to be configured. + @param x x-coordinate of window's top-left corner. + @param y y-coordinate of window's top-left corner. + @param w Window width. + @param h Window height. + @return Tuple containing new geometry. + + When the @ref minx.core.wm.wm "main window manager object" + receives a configure_request message in its event loop, + it will eventually end up calling this method on the layout + object that has reparented the window n. This method is meant to + be overridden by layout subclasses; the base class implementation + does nothing. + + Subclasses should return a 4-tuple of integers containing the new + window geometry to apply in accordance with their particular + layout policies. The first two numbers in this tuple specify the + x and y coordinates of the window's top-left corner; the third + and fourth numbers are the window's width and height + respectively. + + The @ref minx.core.wm.wm "wm" object's configure_request + event handler will use the returned tuple to honour the window's + configure request. To clarify, layouts should not directly add a + hook for x_configure_request; instead, they should let + the main window manager object do that and override this method + to let the main wm object know how to configure the + windows they are managing. + + """ + logger.warning('unhandled configure_request by layout ({})'. + format(self)) + return (x, y, w, h) + + # Map requests + def map_request(self, w): + """Resize top-level window before it gets mapped to screen. + + @param w The @ref minxlib::window "minxlib.window" to be mapped. + + When the @ref minx.core.wm.wm "main window manager object" + receives a map_request message in its event loop, it + will eventually end up calling this method on the layout object + that has reparented the window w. This method is meant to be + overridden by layout subclasses; the base class implementation + does nothing. + + The intent of this method is to give layouts an opportunity to + move and resize the windows they are managing just before a + window gets mapped. Layouts should not themselves map the window + (though no harm should come from that). Rather, they should let + the @ref minx.core.wm.wm "main window manager object" map windows + and, in this method, if necessary, restrict themselves to + reconfiguring their windows to match their layout policy. + + """ + logger.warning('unhandled map_request by layout ({})'. + format(self)) + + # Focus change events + def focused(self, w): + """Focus-in notification. + + @param w The focused @ref minxlib::window "minxlib.window". + + When the @ref minx.core.wm.wm "main window manager object" + receives a focus_in message in its event loop, it will + eventually end up calling this method on the layout object that + has reparented the window w. This method is meant to be + overridden by layout subclasses; the base class implementation + does nothing. + + The intent of this method is to inform layouts about changes in + the input focus. When a window receives the input focus, the + window manager may change its border color and size. And when + that happens, the layout managing the newly focused window may + well have to adjust the geometries of one/more/all of the windows + it is managing to take into account the new border size. + + """ + logger.warning('unhandled focus_in for window {} by layout {}'. + format(w.id, self)) + + def unfocused(self, w): + """Focus-out notification. + + @param w The @ref minxlib::window "minxlib.window" that lost focus. + + When the @ref minx.core.wm.wm "main window manager object" + receives a focus_out message in its event loop, it will + eventually end up calling this method on the layout object that + has reparented the window w. This method is meant to be + overridden by layout subclasses; the base class implementation + does nothing. + + The intent of this method is to inform layouts about changes in + the input focus. When a window loses the input focus, the window + manager may change its border color and size. Of course, when + that happens, the layout managing the newly unfocused window may + well have to adjust the geometries of one/more/all of the windows + it is managing to take into account the new border size. + + """ + logger.warning('unhandled focus_out for window {} by layout {}'. + format(w.id, self)) + +#----------------------------------------------------------------------- + +############################################## +# Editor config: # +############################################## +# Local Variables: # +# indent-tabs-mode: nil # +# py-indent-offset: 4 # +# python-indent: 4 # +# End: # +############################################## +# vim: set expandtab shiftwidth=4 tabstop=4: # +############################################## ADDED layout/full.py Index: layout/full.py ================================================================== --- layout/full.py +++ layout/full.py @@ -0,0 +1,224 @@ +# +# full.py -- layout that occupies the entire area of its parent window +# + +# +# Copyright (C) 2012 The Minx Project Developers +# +# See wiki/copyright.wiki in the top-level directory of the Minx source +# distribution for the full list of authors who have contributed to this +# project. +# + +# +# This file is part of Minx. +# +# Minx 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, either version 3 of the License, or (at your +# option) any later version. +# +# Minx 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 Minx. If not, see . +# + +#-------------------------- MODULE DOC STRING --------------------------- + +"""@package minx.layout.full +Layout that occupies the entire area of its parent window. + +This module implements a layout that resizes all the windows it manages +so that they occupy the entire area of the layout's parent window. + +""" + +#------------------------------- IMPORTS -------------------------------- + +# Standard library +import logging + +# Minx +from base import base + +#-------------------------- MODULE LOGGER ------------------------------ + +logger = logging.getLogger(__name__) + +#-------------------------------- CLASS --------------------------------- + +class full(base): + """Layout to occupy full area of parent window. + + This layout resizes all the windows it manages so that they occupy + the entire area of the layout's parent window. Obviously, this + strategy means that only one application window will be visible at a + time. + + """ + + # Create layout object + def __init__(self, m, p, r = None): + """Create a full layout object. + + @param m The @ref minx.core.wm.wm "main window manager object." + @param p The layout's parent @ref minxlib::window "minxlib.window". + @param r The rectangle within parent window assigned to this layout. + + When you create a full layout object, you should supply the main + window manager object so that the layout can access the X server + connection, any config settings it needs, etc. Additionally, the + layout needs to know its parent window. + + The optional parameter r specifies the rectangle within the + parent window that the layout will occupy. If it is not supplied, + the layout will occupy the entire area of the parent window. If + it is given, it should be a tuple containing four integers, the + first two of which are the x and y coordinates of the layout's + target area's top-left corner and the remaining two are the + rectangle's width and height. + + Since Minx layouts are X windows, this constructor will create a + child window of the specified parent, set appropriate properties + to mark it as a layout, set the event mask, etc. + + """ + base.__init__(self, m, p, r) + + # Reparent notification + def reparented(self, w): + """Reparent notification. + + @param w The @ref minxlib::window "minxlib.window" that was reparented. + + When this layout manages a window, in the resulting reparent + notification, we ensure that the size of the managed window w + matches the size of the layout itself. + + """ + logger.info('window {} reparented by full layout ({})'. + format(w.id, self.window.id)) + self._resize(w) + + # Configure request + def configure_request(self, n, x, y, w, h): + """Configure request handler. + + @param n The @ref minxlib::window "minxlib.window" to be configured. + @param x x-coordinate of window's top-left corner. + @param y y-coordinate of window's top-left corner. + @param w Window width. + @param h Window height. + @return Tuple containing new geometry. + + This layout resizes the windows it manages to always occupy the + entire area available to the layout. Thus, in response to a + configure request for one of its windows, it will return the + layout's geometry (adjusted to take into account the target + window's border width). + + """ + logger.debug('full layout {} configuring top-level window {}'. + format(self.window.id, n.id)) + + b = n.geometry()[4] # window's border width + d = self.window.geometry()[:4] + logger.debug('window {} border width = {}, '.format(n.id, b) + + 'layout geometry = {}x{}+{}+{}'.format(d[2], d[3], + d[0], d[1])) + d[2] -= 2*b # reduce window width to ensure borders are visible + d[3] -= 2*b # reduce window height to ensure borders are visible + return (0, 0, d[2], d[3]) + + # Map request + def map_request(self, w): + """Resize top-level window before it gets mapped to screen. + + @param w The @ref minxlib::window "minxlib.window" to be mapped. + + Before the full layout maps a window, it will compare the + window's geometry against its own. If the two don't match, it + will resize the window to make it fill the layout's entire area. + + """ + logger.debug('full layout {} resizing window {} before mapping'. + format(self.window.id, w.id)) + self._resize(w) + + # Focus change events + def focused(self, w): + """Focus-in notification. + + @param w The focused @ref minxlib::window "minxlib.window". + + When a window receives the input focus, resize it to account for + potentially new border size. + + """ + logger.debug('full layout {} resizing focused window {}'. + format(self.window.id, w.id)) + self._resize(w) + + def unfocused(self, w): + """Focus-out notification. + + @param w The unfocused @ref minxlib::window "minxlib.window". + + When a window loses the input focus, resize it to account for + potentially new border size. + + """ + logger.debug('full layout {} resizing unfocused window {}'. + format(self.window.id, w.id)) + self._resize(w) + + # Resize window to fill layout + def _resize(self, w): + """Resize top-level window to fill entire layout area. + + @param w The @ref minxlib::window "minxlib.window" to be mapped. + + This is a helper method that compares the window w's geometry + against the full layout's geometry and, if the two don't match, + resizes w to make it fill the layout's entire area (taking into + account the border width of w). + + @note This is a private method meant to be used only by the full + layout class itself and should not be called directly by clients. + + """ + b = w.geometry() # window geometry with border width + g = b[:4] # window geometry w/o border width + d = self.window.geometry() + logger.debug('window geometry: {}x{}+{}+{} [{}], '. + format(b[2], b[3], b[0], b[1], b[4]) + + 'full layout {} geometry: {}x{}+{}+{}'. + format(self.window.id, d[2], d[3], d[0], d[1])) + + d[2] -= 2*b[4] # reduce window width to ensure borders are visible + d[3] -= 2*b[4] # reduce window height to ensure borders are visible + if g[2:] == d[2:4]: # window and layout have the same size + pass + else: # need to adjust window size to match layout + logger.info('full layout {} resetting window {} '. + format(self.window.id, w.id) + + 'geometry to {}x{}+0+0'.format(d[2], d[3])) + w.move_resize(0, 0, d[2], d[3]) + +#----------------------------------------------------------------------- + +############################################## +# Editor config: # +############################################## +# Local Variables: # +# indent-tabs-mode: nil # +# py-indent-offset: 4 # +# python-indent: 4 # +# End: # +############################################## +# vim: set expandtab shiftwidth=4 tabstop=4: # +############################################## ADDED minxlib/CMakeLists.txt Index: minxlib/CMakeLists.txt ================================================================== --- minxlib/CMakeLists.txt +++ minxlib/CMakeLists.txt @@ -0,0 +1,92 @@ +# +# minxlib build spec +# + +# +# Copyright (C) 2012 The Minx Project Developers +# +# See wiki/copyright.wiki in the top-level directory of the Minx source +# distribution for the full list of authors who have contributed to this +# project. +# + +# +# This file is part of Minx. +# +# Minx 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, either version 3 of the License, or (at your +# option) any later version. +# +# Minx 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 Minx. If not, see . +# + +# Min cmake version +cmake_minimum_required(VERSION 2.8) + +# Project name +project(minxlib) + +# Library checks +find_package(X11 REQUIRED) +find_package(PythonLibs REQUIRED) +find_package(Boost COMPONENTS python REQUIRED) + +# Compiler flags +add_definitions(-Wall -Wextra) +include_directories(${X11_INCLUDE_DIR} + ${Boost_INCLUDE_DIRS} + ${PYTHON_INCLUDE_DIRS}) + +# Check if we have Xinerama +if(X11_Xinerama_FOUND) + set(X11_LIBRARIES ${X11_LIBRARIES} ${X11_Xinerama_LIB}) + add_definitions(-DMINXLIB_HAS_XINERAMA) + set(minxlib_DOXYGEN_DEFINES MINXLIB_HAS_XINERAMA) +endif() + +# Libraries we need to link against +set(minxlib_LIBS ${minxlib_LIBS} + ${X11_LIBRARIES} ${Boost_LIBRARIES} ${PYTHON_LIBRARIES}) + +# Sources +set(minxlib_SOURCES python.cc type_info.cc util.cc logging.cc keymap.cc + exception.cc event.cc display.cc + window.cc root_window.cc) + +# Add version.cc to source list if we're on the release branch (version +# numbering API does not exist on development branches). +if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/version.cc") + set(minxlib_SOURCES ${minxlib_SOURCES} version.cc) + add_definitions(-DMINXLIB_HAS_VERSION_API) +endif() + +# Final output from sources +add_library(minxlib SHARED ${minxlib_SOURCES}) +target_link_libraries(minxlib ${minxlib_LIBS}) + +# Since minxlib is meant to be a Python module usable by the Minx core, +# we have to adjust some of its output parameters. Firstly, we have to +# send it to the minx.core directory. And, we have to elide the lib +# prefix from the name of the .so file to allow Python modules to import +# it as "minxlib" rather than "libminxlib". +set_target_properties(minxlib PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/core) +set_target_properties(minxlib PROPERTIES PREFIX "") # don't want "lib" prefix + +############################################## +# Editor config: # +############################################## +# Local Variables: # +# indent-tabs-mode: nil # +# sh-basic-offset: 4 # +# End: # +############################################## +# vim: set expandtab shiftwidth=4 tabstop=4: # +############################################## ADDED minxlib/display.cc Index: minxlib/display.cc ================================================================== --- minxlib/display.cc +++ minxlib/display.cc @@ -0,0 +1,298 @@ +/* + @file display.cc + @brief Implementation of API defined in display.hh. +*/ + +/* + Copyright (C) 2012 The Minx Project Developers + + See wiki/copyright.wiki in the top-level directory of the Minx + source distribution for the full list of authors who have + contributed to this project. +*/ + +/* + This file is part of Minx. + + Minx 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, either version 3 of the License, or + (at your option) any later version. + + Minx 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 Minx. If not, see . +*/ + +//----------------------------- HEADERS -------------------------------- + +// minxlib +#include "display.hh" +#include "root_window.hh" +#include "window.hh" +#include "event.hh" +#include "logging.hh" +#include "python.hh" + +// Xlib +#ifdef MINXLIB_HAS_XINERAMA +#include +#endif + +// Standard C++ +#include + +//------------------ INITIALIZATION AND CLEAN-UP ----------------------- + +namespace minxlib { + +// Interface object for taking advantage of Python's standard logging +// module's facilities. +static logging logger ; + +// For keeping track of which minxlib display object corresponds to +// which Xlib Display object (because Xlib error handlers don't pass +// back any client data). +std::map display::m_display_map ; + +// Constructor: connect to X server and setup error handlers +display::display(const std::string& name, bool sync) + : m_display(XOpenDisplay(const_cast(name.c_str()))), + m_fatal(true) // wait to check above connection before declaring okay +{ + if (!m_display) { + std::string server(XDisplayName(const_cast(name.c_str()))) ; + logger.critical() << "unable to connect to X server " << server ; + connection_error::raise(server) ; + } + + m_fatal = false ; + m_display_map[m_display] = this ; + logger.debug() << "registered display object " + << reinterpret_cast(m_display) << " -> " + << reinterpret_cast(this) ; + + if (sync) { + logger.info() << "synchronizing Xlib"; + XSynchronize(m_display, True) ; + } + XSetErrorHandler(report_protocol_errors) ; + XSetIOErrorHandler( report_fatal_errors) ; +} + +// Destructor: close connection to X server +display::~display() +{ + if (!m_fatal) { + logger.info() << "closing connection to X server" ; + XCloseDisplay(m_display) ; + } + + logger.debug() << "erasing display object [" + << reinterpret_cast(m_display) + << ", " + << reinterpret_cast(this) + << "] from internal registry" ; + m_display_map.erase(m_display) ; +} + +//------------------------- ERROR HANDLING ----------------------------- + +// DEVNOTE: We should not throw a protocol_error exception from this +// callback function. In fact, we should not throw any C++ exceptions +// from this callback because it is called by Xlib, a C library that +// likely cannot and does not play well with exceptions, stack unwinding +// etc. See, for example, +// http://stackoverflow.com/questions/10318363/is-it-safe-for-xs-error-handler-to-throw-exceptions. +// +// In fact, throwing an exception in this callback results in the Minx +// process going into some weird state where it starts consuming 100% +// CPU. So, really, that's a very bad idea... +// +// Since minxlib is meant to be used from Python, what we really want to +// do is inform the Python interpreter of an error condition and have it +// raise an exception. We can still leverage the Python interface +// provided by Boost.Python as long as we don't throw an exception, +// i.e., no calls to boost::python::throw_error_already_set(). +// +// This is why minxlib's exception classes hide their constructors and +// require clients (such as the minxlib::display class) call a raise +// method to deliver exceptions to the Python core of Minx. That way, +// each exception class can implement an appropriate way to report +// errors to the Python side of Minx (whether that is using the Python C +// API or actually throwing a C++ exception). +int display::report_protocol_errors(Display*, XErrorEvent* e) +{ + protocol_error::raise(e) ; + return 0 ; +} + +// See comment preceding previous function, viz., +// display::report_protocol_errors(). The same applies to this one. +int display::report_fatal_errors(Display* d) +{ + m_display_map[d]->m_fatal = true ; + fatal_error::raise() ; + return -1 ; +} + +//---------------------------- Xlib API -------------------------------- + +window +display:: +create_window(const window& parent, int x, int y, int width, int height) +{ + throw_fatal() ; + + Window w = XCreateSimpleWindow(m_display, parent, + x, y, width, height, 0, 0, 0) ; + return window(m_display, w) ; +} + +std::vector display::get_root_windows() +{ + throw_fatal() ; + + std::vector root_windows ; + + int n = 0 ; +#ifdef MINXLIB_HAS_XINERAMA + XineramaScreenInfo* screens = XineramaQueryScreens(m_display, &n) ; + if (n > 0) + { + logger.info() << "Xinerama enabled; " << n << " screens" ; + root_windows.reserve(n) ; + for (int i = 0; i < n; ++i) + root_windows.push_back(root_window(m_display, i, screens[i])) ; + XFree(screens) ; + } + else +#endif + { + n = ScreenCount(m_display) ; + logger.info() << "no Xinerama; " << n << " independent screens" ; + root_windows.reserve(n) ; + for (int i = 0; i < n; ++i) + root_windows.push_back(root_window(m_display, i)) ; + } + return root_windows ; +} + +std::vector display::get_top_level_windows() +{ + throw_fatal() ; + + std::vector windows ; + int num_screens = ScreenCount(m_display) ; + for (int screen = 0; screen < num_screens; ++screen) + { + logger.debug() << "getting top-level windows on screen " << screen ; + Window root, parent ; + Window* children = 0 ; + unsigned int num_children ; + Status s = XQueryTree(m_display, RootWindow(m_display, screen), + &root, &parent, &children, &num_children) ; + if (!s) { + logger.warning() << "unable to query screen " << screen ; + continue ; + } + if (children) { + for (unsigned int i = 0; i < num_children; ++i) { + logger.debug() << "screen " << screen + << ", window " << i << " = " << children[i] ; + windows.push_back(window(m_display, children[i])) ; + } + XFree(children) ; + } + } + logger.info() << "total number of top-level windows = " << windows.size() ; + return windows ; +} + +window display::get_focused_window() +{ + throw_fatal() ; + + Window w ; int r ; + XGetInputFocus(m_display, &w, &r) ; + logger.debug() << "currently focused window = " << w ; + return window(m_display, (w == PointerRoot || w == None) ? 0 : w) ; +} + +std::vector display::get_keyboard_mapping() +{ + throw_fatal() ; + + int min_keycode, max_keycode ; + XDisplayKeycodes(m_display, &min_keycode, &max_keycode) ; + logger.debug() << "keycode range = [" + << min_keycode << ' ' << max_keycode << ']' ; + + const int keycode_count = max_keycode - min_keycode + 1 ; + int keysyms_per_keycode ; + KeySym* keysyms = + XGetKeyboardMapping(m_display, min_keycode, keycode_count, + &keysyms_per_keycode) ; + logger.debug() << "keysyms per keycode = " << keysyms_per_keycode ; + + std::set mapping ; + if (keysyms) { + const int n = keycode_count * keysyms_per_keycode ; + logger.debug() << "converting " << n << " total keysyms to strings" ; + for (int i = 0; i < n; ++i) { + char*p = XKeysymToString(keysyms[i]) ; + if (p != NULL) + mapping.insert(p) ; + } + XFree(keysyms) ; + } + else + logger.warning() << "XGetKeyboardMapping() returned no keysyms" ; + return std::vector(mapping.begin(), mapping.end()) ; +} + +boost::shared_ptr display::get_event() +{ + throw_fatal() ; + + XEvent e ; + XNextEvent(m_display, &e) ; + return event::create(e, m_display) ; +} + +//------------------------ PYTHON INTERFACE ---------------------------- + +// Export display class to Python +void display::pythonize() +{ + py::class_("display", + py::init((py::arg("name") = std::string(""), + py::arg("sync") = false ))). + def("create_window", &display::create_window ). + def("get_root_windows", &display::get_root_windows). + def("get_top_level_windows", &display::get_top_level_windows). + def("get_focused_window", &display::get_focused_window ). + def("get_keyboard_mapping", &display::get_keyboard_mapping ). + def("get_event", &display::get_event) ; + + logger = logging::get_logger("display") ; +} + +} // namespace minxlib + +//---------------------------------------------------------------------- + +/**********************************************/ +/* Editor config: */ +/**********************************************/ +/* Local Variables: */ +/* indent-tabs-mode: nil */ +/* c-basic-offset: 4 */ +/* End: */ +/**********************************************/ +/* vim: set expandtab shiftwidth=4 tabstop=4: */ +/**********************************************/ ADDED minxlib/display.hh Index: minxlib/display.hh ================================================================== --- minxlib/display.hh +++ minxlib/display.hh @@ -0,0 +1,361 @@ +/** + @file display.hh + @brief Encapsulation of connection to X server. + + This file defines a class that takes care of the details of + connecting to the X server and provides an API for the rest of Minx + to interface with the relevant aspects of Xlib. + + DEVNOTE: Since Minx is implemented in Python, the minxlib API is + exported from C++ to Python using Boost.Python. We could have used + python-xlib instead of writing a custom Xlib wrapper for Minx. + Unfortunately, python-xlib is not very well documented, which makes + it difficult to use. Since the C interface to Xlib is reasonably + well documented, it is much easier to create an appropriate wrapper + around that. + + Furthermore, a custom wrapper allows us to abstract away the + interface to the display server, thus, making it easier to port Minx + to, say, Wayland (if and when that becomes necessary). The Python + parts of Minx can remain unchanged; all we would have to do is + implement the appropriate interfaces to the new display server + protocol in minxlib. At least, that is the hope... + + NOTE: minxlib is not meant to be a generic Xlib wrapper. It is + designed specifically to be used by Minx. So, only those aspects of + Xlib that Minx needs are wrapped. Moreover, the wrapping provides + high-level API's that are only sensible for Minx. +*/ + +/* + Copyright (C) 2012 The Minx Project Developers + + See wiki/copyright.wiki for the full list of authors who have + contributed to this project. +*/ + +/* + This file is part of Minx. + + Minx 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, either version 3 of the License, or + (at your option) any later version. + + Minx 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 Minx. If not, see . +*/ + +#ifndef MINXLIB_DISPLAY_DOT_HH +#define MINXLIB_DISPLAY_DOT_HH + +//----------------------------- HEADERS -------------------------------- + +// minxlib +#include "exception.hh" + +// Xlib +#include + +// Boost +#include +#include + +// Standard C++ +#include +#include +#include + +//------------------------- CLASS DEFINITION --------------------------- + +namespace minxlib { + +// Forward declarations +class event ; +class window; +class root_window ; + +/** + @brief Encapsulate the details of the connection to the X server. + + This class provides an API for the Python parts of Minx to be able + to talk to the display server. It wraps around the relevant parts of + Xlib and exposes its functionality to Python via @boostpylink. + + This class is meant to be used by the @ref minx.core.wm.wm + "minx.core.wm" Python class. When end-user Minx configs start-up the + window manager, they will instantiate minx.core.wm, which, in turn, + will create an instance of minxlib::display to establish the + connection to the X server. + + @note This class should only be instantiated once and really should + not be subclassed. However, there is nothing in its implementation + to prevent you from creating multiple minxlib::display objects or + from subclassing it. + + @par + Nonetheless, it is rare to ever want to have more than one instance + of this class, to subclass it, or to want to access it from multiple + thread contexts. If you need to do such things, you will have to + take care of the details of tracking the multiple connections, + accessing them from different threads, etc. + + @par + In short, don't do these funky things; minxlib and the minx library + are not designed for it and you really shouldn't need such + functionality anyway (multithreaded window manager with multiple + connections to the X server? really? why? don't you have enough + problems in your life already?). +*/ +class display: boost::noncopyable { + // A display object's basic purpose is to wrap around the Xlib + // Display structure. + Display* m_display ; + + // Keep track of the instances of this class and their corresponding + // X connections. We need this because the Xlib error handler + // callbacks don't support passing back any client data. Thus, if we + // encounter a fatal error, there's no way to have Xlib pass back + // the display object corresponding to the Display pointer that we + // can then readily mark as being in an unusable state. + // Consequently, we have to keep track of this ourselves. + // + // DEVNOTE: We're maintaining this map so that in case of fatal X + // errors, we can set a fatal flag in the display object. Then, if + // some smartass end-user decides to ignore the fatal error + // condition and tries to continue using the display object, we can + // keep throwing fatal_error exceptions to foil this attempt at + // abuse. + // + // DEVNOTE 2: Usually, there will only be one connection to the X + // server. So, we may be able to get away with a single static + // Display*. However, we cannot be guaranteed that there will only + // ever be one instance of this class. Explicitly guaranteeing it + // ourselves by limiting the number of display objects that can be + // created will complicate the implementation and, potentially, the + // interface of this class. Much easier to just use a map and be + // done with it even though the map will pretty much always contain + // just a single element. + static std::map m_display_map ; + + // Flag to indicate whether or not a fatal error has occurred on + // this connection to the X server. + bool m_fatal ; + + // Helper function to check fatal flag and throw a fatal_error in + // case it is set. This function should be called at the beginning + // of each public API. + void throw_fatal() {if (m_fatal) fatal_error::throw_it() ;} + +public: + /** + @brief Open a connection to the X server. + @param name Name of the X server. + @param sync Flag for synchronizing Xlib requests. + @return A valid connection to the display server. + + Instantiating a minxlib::display object automatically sets up a + connection to the X server. If the connection fails, a + connection_error will be thrown. + + The name parameter will usually be an empty string, in which + case, the contructor will connect to the X server specified by + the DISPLAY environment variable. If specified, it should be + something like "host:0.0". Look up X server and Xlib + documentation to get the low-down on this. + + The sync flag indicates whether we want Xlib requests to be + synchronous or not. By default, it is false, i.e., Xlib requests + are buffered. Turning synchronization on is useful for + debugging. Note that the choice between synchronous and buffered + mode of operation is decided at creation time. That is, it + cannot be toggled after a display object has been instantiated. + + In addition to establishing the connection to the display + server, this constructor will also setup Xlib error handlers. + When the X server flags errors by invoking the handlers, they + will be reported to Python clients via appropriate + @ref grp_minxlib_exceptions "Python exceptions". + + In case a fatal error is reported, clients are expected to + clean-up and then terminate the process. The display object will + become unusable after a fatal_error exception is thrown. Any + attempt to use it after a fatal error will result in continued + fatal_error exceptions. + */ + display(const std::string& name = std::string(), bool sync = false) ; + + /** + @brief Close the connection to the X server. + @return Nothing. + + When a display object is destroyed, it will automatically sever + its connection to the display server. + */ + ~display() ; + + /** + @brief Export the display class to minxlib Python module. + @return Nothing. + + This function exposes the display class's interface so that it + can be used by the Python parts of Minx. It is meant to be + called by the Boost.Python initialization code in python.cc. + */ + static void pythonize() ; + + /** + @brief Create a window. + @param p The new window's parent window. + @param x The x-coordinate of the new window's origin. + @param y The y-coordinate of the new window's origin. + @param w The new window's width. + @param h The new window's height. + @return The newly created window. + + This function creates a new window by calling + XCreateSimpleWindow(), wraps the result in a minxlib::window + object and returns that. + + The parameter p must be an existing window; x and y are + relative to the parent's origin. + + @note Although this function can churn out arbitrary windows + parented by arbitrary other windows, end-user code should most + definitely not abuse it in that manner. It is meant to be used + only to create layouts. + */ + window create_window(const window& p, int x, int y, int w, int h) ; + + /** + @brief Retrieve the list of root windows. + @return List of root windows. + + This function queries the X server for all its screens and their + corresponding root windows. The returned list holds the root + windows in the screen order, i.e., the first element of the list + is the root window for the first screen, the second is the root + window for the second screen, so on and so forth. + + @note On multi-head setups with Xinerama, there is only one + screen, and, thus, only one root window as far as the X server is + concerned. Nonetheless, this function will still return as many + root windows as there are physical monitors; each of these root + windows will have the same window ID, but their geometries will + be different. + */ + std::vector get_root_windows() ; + + /** + @brief Retrieve top-level windows across all screens. + @return List of top-level windows. + + This function queries the X server for all the top-level windows + on all screens. Do not rely on the returned list being in any + particular order. + + This function is meant to be used by + @ref minx.core.wm.wm "minx.core.wm" just before it enters its + event loop so that Minx can manage any extant top-level windows + that were created before Minx was started. + */ + std::vector get_top_level_windows() ; + + /** + @brief Get the window with the input focus. + @return Currently focused window. + + This function uses XGetInputFocus() to determine which X window + currently has the input focus and returns that wrapped in a + @ref minxlib::window "minxlib::window" object. + + If no window has the input focus, this function will return a + window with the ID zero. + */ + window get_focused_window() ; + + /** + @brief Retrieve currently supported keysyms in string form. + @return List of strings containing current keysyms. + + The X server assigns unique integer codes to each physical key + on the keyboard. However, since different keyboard models and + brands can have different keycodes, applications usually deal + with keysyms, which are the symbols on the key caps (this is an + oversimplification, but good enough for explanatory purposes). + + This function queries the X server for its mapping between + keycodes and keysyms, converts the keysyms to strings, and + returns the resulting list. + */ + std::vector get_keyboard_mapping() ; + + /** + @brief Retrieve the next event from the X server. + @return A minxlib::event object describing the X event. + + This function removes the next event from the X event queue and + returns it to its caller via an instance of a subclass of + minxlib::event. This function is meant to be used by + @ref minx.core.wm.wm "minx.core.wm" in its event loop. + */ + boost::shared_ptr get_event() ; + +private: + /** + @brief Report protocol errors via an exception. + @param d The connection to the X server. + @param e The Xlib error structure. + @return Nothing. + + This function serves as the callback for the Xlib error handler. + It simply raises an exception so that Python clients are + informed of X protocol errors. + + In some cases, it may be possible to recover from protocol + errors. In others, you may have little choice but to clean-up + and quit. + */ + static int report_protocol_errors(Display* d, XErrorEvent* e) ; + + /** + @brief Report fatal X errors via an exception. + @param d The connection to the X server. + @return Nothing. + + This function serves as the callback for the Xlib I/O error + handler. It simply raises an exception so that Python clients + are informed of fatal X errors. When a client receives a + fatal_error exception, it is expected to clean-up and terminate + its process. Making further Xlib calls via minxlib will result + in continued fatal_error exceptions. + + DEVNOTE: Since this Xlib callback does not support passing back + arbitrary client data, we have to resort to using the + m_display_map defined above to be able to keep throwing + fatal_error exceptions after an X fatal error. + */ + static int report_fatal_errors(Display* d) ; +} ; // class display + +} // namespace minxlib + +//---------------------------------------------------------------------- + +#endif // #ifndef MINXLIB_DISPLAY_DOT_HH + +/**********************************************/ +/* Editor config: */ +/**********************************************/ +/* Local Variables: */ +/* indent-tabs-mode: nil */ +/* c-basic-offset: 4 */ +/* End: */ +/**********************************************/ +/* vim: set expandtab shiftwidth=4 tabstop=4: */ +/**********************************************/ ADDED minxlib/event.cc Index: minxlib/event.cc ================================================================== --- minxlib/event.cc +++ minxlib/event.cc @@ -0,0 +1,487 @@ +/* + @file event.cc + @brief Implementation of the API defined in event.hh. +*/ + +/* + Copyright (C) 2012 The Minx Project Developers + + See wiki/copyright.wiki in the top-level directory of the Minx + source distribution for the full list of authors who have + contributed to this project. +*/ + +/* + This file is part of Minx. + + Minx 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, either version 3 of the License, or + (at your option) any later version. + + Minx 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 Minx. If not, see . +*/ + +//----------------------------- HEADERS -------------------------------- + +// minxlib +#include "event.hh" +#include "logging.hh" +#include "python.hh" +#include "keymap.hh" + +// Xlib +#include + +// Boost +#include + +// Standard C++ +#include +#include +#include + +//------------------------ EVENTS BASE CLASS --------------------------- + +namespace minxlib { + +static logging logger ; + +// Public API for creating event objects +boost::shared_ptr event::create(const XEvent& e, Display* d) +{ + typedef factory event_factory ; + try + { + boost::shared_ptr p(event_factory::create(e.type, e, d)) ; + return p ; + } + catch (event_factory::unknown_type&) + { + logger.warning() << "unknown event type " << e.type + << " on window " << e.xany.window ; + boost::shared_ptr p(new event(e, d, e.xany.window)) ; + return p ; + } +} + +// Protected constructor +event::event(const XEvent& e, Display* d, Window w) + : type(e.type), + serial(e.xany.serial), + send_event(e.xany.send_event == True), + target(d, w) +{} + +// Virtual destructor +event::~event(){} + +// Base class exposed to Python +void event::pythonize() +{ + py::class_ >("event", py::no_init). + def_readonly("type", &event::type ). + def_readonly("serial", &event::serial ). + def_readonly("send_event", &event::send_event). + def_readonly("target", &event::target ). + def(py::self_ns::str(py::self_ns::self)) ; // __str__ + + // Call pythonize for all the subclasses to export the event class + // hierarchy to Python. + BOOST_FOREACH(py_func p, registry::range()) + p() ; + + // Create logger for the events module + logger = logging::get_logger("event") ; +} + +// For Boost.Python to generate __str__ +std::ostream& operator<<(std::ostream& os, const event&) +{ + return os << "x_any_event" ; +} + +// Helper macro to generate the boilerplate code for registering event +// subclasses with the event factory and defining a stream output +// operator so Boost.Python can generate __str__. +#define register_minxlib_event(xlib_code, subclass) \ + std::ostream& operator<<(std::ostream& os, const subclass&) { \ + return os << "x_" #subclass ; \ + } \ + bool subclass::registered = event::registry::add(xlib_code, \ + create_object, \ + subclass::pythonize) + +//---------------- SUBSTRUCTURE REDIRECTION EVENTS --------------------- + +// Map request events +register_minxlib_event(MapRequest, map_request) ; + +map_request::map_request(const XEvent& e, Display* d) + : event (e, d, e.xmaprequest.window), + parent(d, e.xmaprequest.parent) +{} + +void map_request::pythonize() +{ + py::class_ >("map_request", py::no_init). + def_readonly("parent", &map_request::parent). + def(py::self_ns::str(py::self_ns::self)) ; // __str__ +} + +// Configure request events +register_minxlib_event(ConfigureRequest, configure_request) ; + +configure_request::configure_request(const XEvent& e, Display* d) + : event (e, d, e.xconfigurerequest.window), + parent(d, e.xconfigurerequest.parent), + x(e.xconfigurerequest.x), y(e.xconfigurerequest.y), + width(e.xconfigurerequest.width), height(e.xconfigurerequest.height), + border_width(e.xconfigurerequest.border_width), + above(d, e.xconfigurerequest.above), + stack_mode(e.xconfigurerequest.detail), + value_mask(e.xconfigurerequest.value_mask) +{} + +void configure_request::pythonize() +{ + py::class_ >("configure_request", + py::no_init ). + def_readonly("parent", &configure_request::parent ). + def_readonly("x", &configure_request::x ). + def_readonly("y", &configure_request::y ). + def_readonly("width", &configure_request::width ). + def_readonly("height", &configure_request::height ). + def_readonly("border_width", &configure_request::border_width). + def_readonly("above", &configure_request::above ). + def_readonly("stack_mode", &configure_request::stack_mode ). + def_readonly("value_mask", &configure_request::value_mask ). + def(py::self_ns::str(py::self_ns::self)) ; // __str__ +} + +// Circulate request events +register_minxlib_event(CirculateRequest, circulate_request) ; + +circulate_request::circulate_request(const XEvent& e, Display* d) + : event (e, d, e.xcirculaterequest.window), + parent(d, e.xcirculaterequest.parent), + place(e.xcirculaterequest.place) +{} + +void circulate_request::pythonize() +{ + py::class_ >("circulate_request", + py::no_init). + def_readonly("parent", &circulate_request::parent). + def_readonly("place", &circulate_request::place ). + def(py::self_ns::str(py::self_ns::self)) ; // __str__ +} + +//------------- STRUCTURE AND SUBSTRUCTURE NOTIFICATIONS --------------- + +// Window creation +register_minxlib_event(CreateNotify, create_notify) ; + +create_notify::create_notify(const XEvent& e, Display* d) + : event (e, d, e.xcreatewindow.window), + parent(d, e.xcreatewindow.parent), + x(e.xcreatewindow.x), y(e.xcreatewindow.y), + width(e.xcreatewindow.width), height(e.xcreatewindow.height), + border_width(e.xcreatewindow.border_width), + override_redirect(e.xcreatewindow.override_redirect == True) +{} + +void create_notify::pythonize() +{ + py::class_ >("create_notify", py::no_init). + def_readonly("parent", &create_notify::parent ). + def_readonly("x", &create_notify::x ). + def_readonly("y", &create_notify::y ). + def_readonly("width", &create_notify::width ). + def_readonly("height", &create_notify::height ). + def_readonly("border_width", &create_notify::border_width ). + def_readonly("override_redirect", &create_notify::override_redirect ). + def(py::self_ns::str(py::self_ns::self)) ; // __str__ +} + +// Window reparenting +register_minxlib_event(ReparentNotify, reparent_notify) ; + +reparent_notify::reparent_notify(const XEvent& e, Display* d) + : event (e, d, e.xreparent.window), + parent(d, e.xreparent.event ), + new_parent(d, e.xreparent.parent), + x(e.xreparent.x), y(e.xreparent.y), + override_redirect(e.xreparent.override_redirect == True) +{} + +void reparent_notify::pythonize() +{ + py::class_ >("reparent_notify", + py::no_init). + def_readonly("parent", &reparent_notify::parent ). + def_readonly("new_parent", &reparent_notify::new_parent ). + def_readonly("x", &reparent_notify::x ). + def_readonly("y", &reparent_notify::y ). + def_readonly("override_redirect", &reparent_notify::override_redirect). + def(py::self_ns::str(py::self_ns::self)) ; // __str__ +} + +// Window configuration +register_minxlib_event(ConfigureNotify, configure_notify) ; + +configure_notify::configure_notify(const XEvent& e, Display* d) + : event (e, d, e.xconfigure.window), + parent(d, e.xconfigure.event ), + x(e.xconfigure.x), y(e.xconfigure.y), + width(e.xconfigure.width), height(e.xconfigure.height), + border_width(e.xconfigure.border_width), + above(d, e.xconfigure.above), + override_redirect(e.xconfigure.override_redirect == True) +{} + +void configure_notify::pythonize() +{ + py::class_ >("configure_notify", + py::no_init). + def_readonly("parent", &configure_notify::parent ). + def_readonly("x", &configure_notify::x ). + def_readonly("y", &configure_notify::y ). + def_readonly("width", &configure_notify::width ). + def_readonly("height", &configure_notify::height ). + def_readonly("border_width", &configure_notify::border_width ). + def_readonly("above", &configure_notify::above ). + def_readonly("override_redirect",&configure_notify::override_redirect). + def(py::self_ns::str(py::self_ns::self)) ; // __str__ +} + +// Gravity notifications +register_minxlib_event(GravityNotify, gravity_notify) ; + +gravity_notify::gravity_notify(const XEvent& e, Display* d) + : event (e, d, e.xgravity.window), + parent(d, e.xgravity.event ), + x(e.xgravity.x), y(e.xgravity.y) +{} + +void gravity_notify::pythonize() +{ + py::class_ >("gravity_notify", + py::no_init). + def_readonly("parent", &gravity_notify::parent). + def_readonly("x", &gravity_notify::x ). + def_readonly("y", &gravity_notify::y ). + def(py::self_ns::str(py::self_ns::self)) ; // __str__ +} + +// Window mapping +register_minxlib_event(MapNotify, map_notify) ; + +map_notify::map_notify(const XEvent& e, Display* d) + : event (e, d, e.xmap.window), + parent(d, e.xmap.event ), + override_redirect(e.xmap.override_redirect == True) +{} + +void map_notify::pythonize() +{ + py::class_ >("map_notify", py::no_init ). + def_readonly("parent", &map_notify::parent ). + def_readonly("override_redirect", &map_notify::override_redirect). + def(py::self_ns::str(py::self_ns::self)) ; // __str__ +} + +// Window unmapping +register_minxlib_event(UnmapNotify, unmap_notify) ; + +unmap_notify::unmap_notify(const XEvent& e, Display* d) + : event (e, d, e.xunmap.window), + parent(d, e.xunmap.event ), + from_configure(e.xunmap.from_configure == True) +{} + +void unmap_notify::pythonize() +{ + py::class_ >("unmap_notify", py::no_init). + def_readonly("parent", &unmap_notify::parent ). + def_readonly("from_configure", &unmap_notify::from_configure). + def(py::self_ns::str(py::self_ns::self)) ; // __str__ +} + +// Circulate notifications +register_minxlib_event(CirculateNotify, circulate_notify) ; + +circulate_notify::circulate_notify(const XEvent& e, Display* d) + : event (e, d, e.xcirculate.window), + parent(d, e.xcirculate.event ), + place (e.xcirculate.place) +{} + +void circulate_notify::pythonize() +{ + py::class_ >("circulate_notify", + py::no_init). + def_readonly("parent", &circulate_notify::parent). + def_readonly("place", &circulate_notify::place ). + def(py::self_ns::str(py::self_ns::self)) ; // __str__ +} + +// Window destruction +register_minxlib_event(DestroyNotify, destroy_notify) ; + +destroy_notify::destroy_notify(const XEvent& e, Display* d) + : event (e, d, e.xdestroywindow.window), + parent(d, e.xdestroywindow.event ) +{} + +void destroy_notify::pythonize() +{ + py::class_ >("destroy_notify", + py::no_init). + def_readonly("parent", &destroy_notify::parent). + def(py::self_ns::str(py::self_ns::self)) ; // __str__ +} + +//------------------- KEYBOARD AND FOCUS EVENTS ------------------------ + +// If a given keycode and modifier mask correspond to a key binding, +// we'll return the hook name supplied by user for the passive grab for +// that keystroke. Otherwise, we'll simply translate the keycode to a +// string (via translation to a keysym). +static std::string keycode_to_string(Display* d, KeyCode k, unsigned int m) +{ + using std::dec ; using std::hex ; using std::setw ; using std::setfill ; + logger.debug() << "key event: key code = " << dec << static_cast(k) + << ", modifier mask = 0x" + << hex << setw(4) << setfill('0') << m ; + try + { + return keymap::get(k, m) ; // user-defined name for a key binding + } + catch (std::out_of_range&) // no key binding + { + logger.debug() << "no key binding for [0x" + << hex << setw(4) << setfill('0') << m << " + 0x" + << hex << setw(4) << setfill('0') + << static_cast(k) << ']' ; + return XKeysymToString(XkbKeycodeToKeysym(d, k, 0, 0)) ; + } +} + +// Common attributes of keyboard events +key_event_details::key_event_details(const XKeyEvent& e, Display* d) + : root(d, e.root), child(d, e.subwindow), + time(e.time), + x(e.x), y(e.y), x_root(e.x_root), y_root(e.y_root), + mask(e.state), keycode(e.keycode), + key(keycode_to_string(d, e.keycode, e.state)), + same_screen(e.same_screen == True) +{} + +// Key presses +register_minxlib_event(KeyPress, key_press) ; + +key_press::key_press(const XEvent& e, Display* d) + : event(e, d, e.xkey.window), + key_event_details(e.xkey, d) +{} + +void key_press::pythonize() +{ + py::class_ >("key_press", py::no_init). + def_readonly("root", &key_press::root ). + def_readonly("child", &key_press::child ). + def_readonly("time", &key_press::time ). + def_readonly("x", &key_press::x ). + def_readonly("y", &key_press::y ). + def_readonly("x_root", &key_press::x_root ). + def_readonly("y_root", &key_press::y_root ). + def_readonly("mask", &key_press::mask ). + def_readonly("keycode", &key_press::keycode ). + def_readonly("key", &key_press::key ). + def_readonly("same_screen", &key_press::same_screen). + def(py::self_ns::str(py::self_ns::self)) ; // __str__ +} + +// Key releases +register_minxlib_event(KeyRelease, key_release) ; + +key_release::key_release(const XEvent& e, Display* d) + : event(e, d, e.xkey.window), + key_event_details(e.xkey, d) +{} + +void key_release::pythonize() +{ + py::class_ >("key_release", py::no_init). + def_readonly("root", &key_release::root ). + def_readonly("child", &key_release::child ). + def_readonly("time", &key_release::time ). + def_readonly("x", &key_release::x ). + def_readonly("y", &key_release::y ). + def_readonly("x_root", &key_release::x_root ). + def_readonly("y_root", &key_release::y_root ). + def_readonly("mask", &key_release::mask ). + def_readonly("keycode", &key_release::keycode ). + def_readonly("key", &key_release::key ). + def_readonly("same_screen", &key_release::same_screen). + def(py::self_ns::str(py::self_ns::self)) ; // __str__ +} + +// Common attributes of focus change events +focus_change_details::focus_change_details(const XFocusChangeEvent& e) + : mode(e.mode), detail(e.detail) +{} + +// FocusIn +register_minxlib_event(FocusIn, focus_in) ; + +focus_in::focus_in(const XEvent& e, Display* d) + : event(e, d, e.xfocus.window), + focus_change_details(e.xfocus) +{} + +void focus_in::pythonize() +{ + py::class_ >("focus_in", py::no_init). + def_readonly("mode", &focus_in::mode ). + def_readonly("detail", &focus_in::detail). + def(py::self_ns::str(py::self_ns::self)) ; // __str__ +} + +// FocusOut +register_minxlib_event(FocusOut, focus_out) ; + +focus_out::focus_out(const XEvent& e, Display* d) + : event(e, d, e.xfocus.window), + focus_change_details(e.xfocus) +{} + +void focus_out::pythonize() +{ + py::class_ >("focus_out", py::no_init). + def_readonly("mode", &focus_out::mode ). + def_readonly("detail", &focus_out::detail). + def(py::self_ns::str(py::self_ns::self)) ; // __str__ +} + +} // namespace minxlib + +//---------------------------------------------------------------------- + +/**********************************************/ +/* Editor config: */ +/**********************************************/ +/* Local Variables: */ +/* indent-tabs-mode: nil */ +/* c-basic-offset: 4 */ +/* End: */ +/**********************************************/ +/* vim: set expandtab shiftwidth=4 tabstop=4: */ +/**********************************************/ ADDED minxlib/event.hh Index: minxlib/event.hh ================================================================== --- minxlib/event.hh +++ minxlib/event.hh @@ -0,0 +1,772 @@ +/** + @file event.hh + @brief Encapsulation of X events. + @defgroup grp_minxlib_events Minxlib's Event Hierarchy + + This file defines classes that wrap around the various X event + structures and provide a Python interface to these structures for + the rest of Minx to use. +*/ + +/* + Copyright (C) 2012 The Minx Project Developers + + See wiki/copyright.wiki in the top-level directory of the Minx + source distribution for the full list of authors who have + contributed to this project. +*/ + +/* + This file is part of Minx. + + Minx 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, either version 3 of the License, or + (at your option) any later version. + + Minx 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 Minx. If not, see . +*/ + +#ifndef MINXLIB_EVENT_DOT_HH +#define MINXLIB_EVENT_DOT_HH + +//----------------------------- HEADERS -------------------------------- + +// minxlib +#include "window.hh" +#include "factory.hh" + +// Xlib +#include + +// Boost +#include + +// Standard C++ +#include + +//--------------------- BASE CLASS FOR X EVENTS ------------------------ + +namespace minxlib { + +/** + @ingroup grp_minxlib_events + @brief A generic X event, i.e., XAnyEvent. + + This is a base class for the @ref grp_minxlib_events "event hierarchy" + exposed by minxlib to the Python parts (i.e., the rest) of Minx. The + "main" window manager object, viz., @ref minx.core.wm.wm + "minx.core.wm", will receive instances of subclasses of this class + in its event loop when it calls minxlib.display.get_event(). + + If the Minx core receives an instance of this class itself, that + means the event in question is an unregistered type. For registered + event types, when an event is retrieved from the X event queue, we + examine the event structure's type field and then create an object + corresponding to that type and return the resulting object via a + pointer to this class. + + For example, if we get a key press event from the X server, the + XEvent will be used to create a minxlib::key_press object (which is + derived from minxlib::event) and, then, we will return the key_press + as a minxlib::event*. + + This module, viz., event.hh, uses an object factory to achieve the + subclass object creation procedure described above. Unregistered + event types are simply those XEvent::type values for which + minxlib has no corresponding wrapper class derived from + minxlib::event. + + It should be alright for the Minx core to ignore such unregistered + events because, in all likelihood, the window manager won't care for + them. However, if it becomes necessary to respond to an event that + is not properly wrapped by minxlib, then the correct way to effect + that would be to implement the wrapper class in this module so that + the Python parts of Minx see an event object derived from + minxlib.event rather than an instance of minxlib.event itself. + + On the Python side of Minx, the event type can, of course, be + determined using the isinstance() function or the object's + __class__ attribute. However, it may be more convenient to + simply use the str() function to get a string + representation of the event object and use that instead. + + All minxlib.event objects have names of the form "x_something". For + example, a key press event is called "x_key_press"; map requests are + "x_map_request"; so on and so forth. The "x" in these object names + indicate that the events are X events. +*/ +struct event { + /** + @brief Event type as specified in X11/X.h. + + This field specifies the type of event using one of the codes in + the X11/X.h header. The minxlib @ref grp_minxlib_events "event + hierarchy" uses the value of this field in conjunction with an + object factory to create instances of subclasses of the + minxlib::event class. + */ + const int type ; + + /** + @brief Last request number processed by X server. + + The X server "tags" each event it generates with a serial + number. This field records that number. It is not really used in + minxlib or in Minx's Python core. + */ + const unsigned long serial ; + + /** + @brief True if triggered by SendEvent request. + + This flag will be true if the event is an "artificial" one, + i.e., generated by an application calling the SendEvent function + rather than by an actual key press, mouse move, etc. + */ + const bool send_event ; + + /** + @brief Event's target window. + + This field specifies the window on which the event occurred. + Note that it is a minxlib::window object, which wraps around the + Xlib notion of an X window. + */ + const window target ; + +protected: + /** + @brief Create a wrapper object for an X event. + @param e The X event structure. + @param d The display object associated with the event. + @param w The Xlib ID of the target window for the event. + @return A properly constructed event object. + + A protected constructor because event objects are meant to be + instantiated using the create() factory method with only + subclasses constructing the base data members. + + @note The fifth member of all Xlib event structures is a Window + ID. However, in some of them, this field refers to the event's + target window, while, in others, it refers to the target + window's parent. + + @par + In this base class, we don't know exactly which Xlib event + structure to use from the XEvent union; only the subclasses that + wrap around specific event types have that information. + Therefore, we require all subclasses to specify the correct ID + to use for the target window and ignore whatever is in + XEvent::xany. + */ + event(const XEvent& e, Display* d, Window w) ; + + /** + @brief Signature of factory functions for creating events. + + Each event subclass is instantiated by a factory function that + takes an XEvent structure and a display pointer and returns a + subclass instance upcast to minxlib::event. This type provides a + convenient name for factory functions matching the + above-mentioned signature. + + All minxlib::event subclasses are required to implement a + factory function matching this signature and to register it with + the base class's + @ref minxlib::event::registry "registry of subclass object factories". + Usually, the factory function will simply be the appropriate + create_object function from minxlib's + @ref grp_factory "factory infrastructure". + + */ + typedef event* (*cr_func)(const XEvent&, Display*) ; + + /** + @brief Signature of Pythonize functions. + + In addition to a factory function, each subclass of + minxlib::event has to define a Pythonize function that will have + the appropriate @boostpylink incantations in order to export its + interface to minxlib's Python module. + + All subclass Pythonize functions will be called by + event::pythonize(), which is invoked by the main Python + initialization module python.cc. Pythonize functions take no + parameters and return nothing. + + Like the factory functions, subclasses must register their + Pythonize functions with the + @ref event::registry "base class's registry of object factories", + which stores pointers to the Pythonize functions in addition to + the factory functions. + + @note Subclass Pythonize functions are usually static member + functions in their respective classes. + */ + typedef void (*py_func)() ; + + /** + @brief Registry of subclass object factories and Pythonize functions. + + This type defines the event object factory's registry, which + maps the Xlib event type codes to the corresponding event + subclass's factory function. Additionally, we store the + Pythonize functions in the registry as well, which obviates the + need for another map to hold them. + + For the @ref grp_factory "factory pattern" to work, event + subclasses must define a static boolean data member and, in its + initializer, call event::registry::add(), passing it the + appropriate Xlib event code, the subclass's factory function, + and its Pythonize function. + */ + typedef factory_map registry ; + +public: + /** + @brief Factory method for creating events. + @param e The X event structure for which we want a wrapper object. + @param d The display object associated with the event. + @return Event object created on heap and wrapped in a smart pointer. + + We would like to expose the Xlib event types in such a way that + the Python parts of Minx see a high-level, object-oriented + interface to event processing instead of having to deal with + low-level integers describing event types, etc. To achieve that, + we use the @ref grp_factory "factory design pattern" to create + objects based on the XEvent::type field. + + This method provides the public interface to the above-mentioned + event object factory. It is meant to be used by + minxlib::display::get_event() in order to convert low-level X + event structures into appropriate object types belonging to + minxlib's @ref grp_minxlib_events "event hierarchy". + + @note minxlib does not provide wrapper classes for all Xlib + event types. Unwrapped event types will be reported via an + instance of this, i.e., the event base class. + + @par + @boostpylink has all sorts of magic in it to convert C++ + objects to their Python equivalents and vice versa. Since + instances of minxlib::event are created using an object factory, + i.e., on the heap and accessed via pointers, we need to wrap + event objects in a boost::shared_ptr to make the Boost.Python + conversion magic work with minimal effort. + */ + static boost::shared_ptr create(const XEvent& e, Display* d) ; + + /** + @brief Export event class to minxlib Python module. + @return Nothing. + + This function exposes the event class's interface so that it + can be used by the Python parts of Minx. It is meant to be + called by the @boostpylink initialization code in python.cc. + + @note This function will call the Pythonize functions of all the + minxlib::event subclasses, thus, exporting the entire @ref + grp_minxlib_events "minxlib event hierarchy" to Python. For this to + work, all subclasses must register their Pythonize routines with + the @ref minxlib::event::registry "base class object factory registry" + as described earlier. + */ + static void pythonize() ; + + /** + @brief Event object clean-up. + @return Nothing. + + Because minxlib::event is a base class for other event types, it + is a good idea for it to have a virtual destructor. This is + especially necessary for the event class hierarchy because all + events are created using a polymorphic factory that upcasts all + subclass instances to minxlib::event*. Consequently, we have to + provide a virtual destructor to ensure subclass objects are + properly cleaned up when the base class pointers are deleted. + + Furthermore, to get the magic conversions in @boostpylink to + work properly, we need at least one virtual method in the + minxlib::event base class. Otherwise, on the Python side, + instances of subclasses of this class will not have the correct + type; the Python interpreter will see those objects as instances + of minxlib.event rather than their actual derived types. + */ + virtual ~event() ; +} ; // class event + +// Each event subclass follows the same pattern: it has some public data +// members corresponding to the relevant fields of the XEvent type and +// then a constructor, a Pythonize function, a static boolean data +// member, and a friend declaration for the appropriate create_event +// factory function. +// +// Instead of typing out all of that over and over, we use the following +// macro to do it for us. +#define MINXLIB_EVENT_SUBCLASS_BOILERPLATE(subclass_name) \ + private: \ + subclass_name(const XEvent&, Display*) ; \ + static void pythonize(); \ + static bool registered ; \ + friend event* create_object(const XEvent&, \ + Display*) + +//---------------- SUBSTRUCTURE REDIRECTION EVENTS --------------------- + +/** + @ingroup grp_minxlib_events + @brief Encapsulation of MapRequest events. + + We can ask the X server to redirect requests to show windows to Minx + so that the window manager can decide what to do. These redirections + are delivered via MapRequest events, which are wrapped by minxlib + and delivered to Minx's Python core via instances of this class. +*/ +struct map_request: public event { + /** + @brief The parent window of the window to be mapped. + + Since window managers are usually only concerned with top-level + windows, we check this field to determine how to handle the map + request. If it is equal to the root window of some screen, the + target window is a top-level window that may need to be handled + in a special way. Non top-level windows will usually have their + map requests honoured "as-is." + */ + const window parent ; + + MINXLIB_EVENT_SUBCLASS_BOILERPLATE(map_request) ; +} ; + +/** + @ingroup grp_minxlib_events + @brief Encapsulation of ConfigureRequest events. + + We can ask the X server to redirect requests to move and resize + windows to Minx so that the window manager can decide exactly how to + honour these configuration requests. These redirections are + delivered via ConfigureRequest events, which are wrapped by minxlib + and delivered to Minx's Python core via instances of this class. +*/ +struct configure_request: public event { + /** + @brief The parent window of the window to be mapped. + + Since window managers are usually only concerned with top-level + windows, we check this field to determine how to handle the + configure request. If it is equal to the root window of some + screen, the target window is a top-level window that may need to + be handled in a special way (e.g., subject to the current layout + policy). Non top-level windows will usually have their + configure requests honoured "as-is." + */ + const window parent ; + + const int x ; ///< Origin x-coordinate relative to parent. + const int y ; ///< Origin y-coordinate relative to parent. + const int width ; ///< Window width. + const int height; ///< Window height. + const int border_width ; ///< Size of window border. + + const window above ; ///< Sibling for stacking. + const int stack_mode; ///< Above, Below, TopIf, BottomIf, Opposite. + + /** + @brief Bit mask indicating what has to be configured. + + This field specifies which of the above fields are relevant to + the configure request. Consult Xlib documentation for more + information. + */ + const unsigned long value_mask ; + + MINXLIB_EVENT_SUBCLASS_BOILERPLATE(configure_request) ; +} ; + +/** + @ingroup grp_minxlib_events + @brief Encapsulation of CirculateRequest events. + + When an application tries to circulate windows, we can have the X + server redirect these requests to Minx to allow the window manager + to handle them as it sees fit. These requests are delivered by + minxlib to Minx's Python core via instances of this class. + + @note Since Minx is a tiling window manager and window circulation + is mostly related to restacking windows, this event type is not very + relevant to us. +*/ +struct circulate_request: public event { + /** + @brief Parent of window requesting circulation. + + Since window managers are usually only concerned with top-level + windows, we check this field to determine how to handle the + circulate request. If it is equal to the root window of some + screen, the target window is a top-level window that may need to + be handled in a special way. Non top-level windows will usually + have their circulate requests honoured "as-is." + + @note As mentioned earlier, being a tiling window manager, + window circulation and stacking are not things Minx cares about. + */ + const window parent ; + + /** + @brief Window stack placement (PlaceOnTop, PlaceOnBottom). + + This field specifies whether the target window should be placed + at the top or the bottom of the stack, which, for Minx (it being + a tiling window manager and all), is pretty much useless. + */ + const int place ; + + MINXLIB_EVENT_SUBCLASS_BOILERPLATE(circulate_request) ; +} ; + +//------------- STRUCTURE AND SUBSTRUCTURE NOTIFICATIONS --------------- + +/** + @ingroup grp_minxlib_events + @brief Encapsulation of CreateNotify events. + + Minx's Python core can use these notifications to know when a new + window is created. +*/ +struct create_notify: public event { + /** + @brief Parent of new window. + + This field specifies the parent window of the new window. If it + is equal to the root window of some screen, the target window is + a top-level window that we need to be concerned with. We usually + don't care about non top-level windows. + */ + const window parent ; + + const int x ; ///< Origin x-coordinate relative to parent. + const int y ; ///< Origin y-coordinate relative to parent. + const int width ; ///< Window width. + const int height; ///< Window height. + const int border_width ; ///< Size of window border. + + /** + @brief Should the window manager ignore this window? + + If this flag is on, the window manager should ignore the new + window. + */ + const bool override_redirect ; + + MINXLIB_EVENT_SUBCLASS_BOILERPLATE(create_notify) ; +} ; + +/** + @ingroup grp_minxlib_events + @brief Encapsulation of ReparentNotify events. + + When a window is reparented (typically by window managers), the X + server notifies interested clients using these events. +*/ +struct reparent_notify: public event { + // Additional attributes of a reparent notify event + const window parent ; ///< Target window's old or new parent. + const window new_parent ; ///< Target window's new parent. + const int x ; ///< Window origin x-coordinate. + const int y ; ///< Window origin y-coordinate. + const bool override_redirect ; ///< Window managers should ignore if true. + + MINXLIB_EVENT_SUBCLASS_BOILERPLATE(reparent_notify) ; +} ; + +/** + @ingroup grp_minxlib_events + @brief Encapsulation of ConfigureNotify events. + + The X server sends these events after a window has been moved, + resized, or otherwise configured. +*/ +struct configure_notify: public event { + /** + @brief Target window's parent. + + This field specifies the parent window of the configured window. + If it is equal to the root window of some screen, the target + window is a top-level window that we need to be concerned with. + We usually don't care about non top-level windows. + */ + const window parent ; + + const int x ; ///< Origin x-coordinate relative to parent. + const int y ; ///< Origin y-coordinate relative to parent. + const int width ; ///< Window width. + const int height; ///< Window height. + const int border_width ; ///< Size of window border. + + /** + @brief Sibling for stacking. + + This has something to do with stacking operations, which, for + Minx, has pretty much no use. + */ + const window above ; + + /** + @brief Should the window manager ignore this window? + + If this flag is on, the window manager should ignore the + configured window. + */ + const bool override_redirect ; + + MINXLIB_EVENT_SUBCLASS_BOILERPLATE(configure_notify) ; +} ; + +/** + @ingroup grp_minxlib_events + @brief Encapsulation of GravityNotify events. + + Who the heck knows what the hell this is about! Look at the relevant + Xlib documentation for the gory details. We don't care about it in + Minx. But, because it's easy, minxlib provides the encapsulation + anyway... +*/ +struct gravity_notify: public event { + // Additional attributes of a gravity notify event + const window parent ; ///< Target window's old or new parent. + const int x ; ///< Window origin x-coordinate. + const int y ; ///< Window origin y-coordinate. + + MINXLIB_EVENT_SUBCLASS_BOILERPLATE(gravity_notify) ; +} ; + +/** + @ingroup grp_minxlib_events + @brief Encapsulation of MapNotify events. + + The X server sends these events after a window is shown on the + screen. This is an important event for window managers because + windows cannot be focused until they have been mapped. +*/ +struct map_notify: public event { + /** + @brief Target window's parent. + + This field specifies the parent window of the event's target + window. If that's the root window of some screen, we have + ourselves a top-level window that the window manager cares + about; otherwise, it's some application's child window that we + need not bother with. + */ + const window parent ; + + /** + @brief Should the window manager ignore this window? + + If this flag is on, the window manager should ignore the + configured window. + */ + const bool override_redirect ; + + MINXLIB_EVENT_SUBCLASS_BOILERPLATE(map_notify) ; +} ; + +/** + @ingroup grp_minxlib_events + @brief Encapsulation of UnmapNotify events. + + The X server sends these events after a window is hidden. This is an + important event for window managers because a window can no longer + be focused after it has been unmapped. +*/ +struct unmap_notify: public event { + /** + @brief Target window's parent. + + This field specifies the parent window of the unmap + notification's target window. If that's the root window of some + screen, we have ourselves a top-level window that the window + manager cares about; otherwise, it's some application's child + window that we need not bother with. + */ + const window parent ; + + /** + @brief True if unmap notification is due to parent window resize. + + Not really sure what this is about; consult Xlib documentation + for details. Maybe we don't care too much about this for Minx? + */ + const bool from_configure ; + + MINXLIB_EVENT_SUBCLASS_BOILERPLATE(unmap_notify) ; +} ; + +/** + @ingroup grp_minxlib_events + @brief Encapsulation of CirculateNotify events. + + These events are sent by the X server after window circulation is + complete. Minx does not worry about these events too much. +*/ +struct circulate_notify: public event { + // Additional attributes of a circulate notify event + const window parent; ///< Target window's parent. + const int place ; ///< New position in stacking order: top, bottom. + + MINXLIB_EVENT_SUBCLASS_BOILERPLATE(circulate_notify) ; +} ; + +/** + @ingroup grp_minxlib_events + @brief Encapsulation of DestroyNotify events. + + These events are sent when windows are destroyed. +*/ +struct destroy_notify: public event { + /** + @brief Parent window (for SubstructureNotify). + + XDestroyWindowEvent::event refers to either the destroyed window + or its parent depending on whether we chose StructureNotify or + SubstructureNotify in the event mask. Minx usually cares about + substructure notifications for root windows. Thus, we interpret + the XDestroyWindowEvent::event value as the target window's + parent. + */ + const window parent ; + + MINXLIB_EVENT_SUBCLASS_BOILERPLATE(destroy_notify) ; +} ; + +//------------------- KEYBOARD AND FOCUS EVENTS ------------------------ + +/** + @ingroup grp_minxlib_events + @brief Various attributes common to both key presses and releases. + + Xlib reports the same stuff for KeyPress and KeyRelease events and + uses a typedef to alias the two event structures. Since minxlib uses + two different types for the two event types, we put the relevant + fields into this base class and have the relevant event subclasses + derive from this class as well as minxlib::event. + + @note Xlib's XKeyEvent structure contains a modifier mask and a + keycode, which we copy into this structure. Additionally, if the + keyboard event's modifier mask and keycode correspond to a named key + binding (configured by end-users), we "translate" that into the + user-defined name and return it via this structure's key field. If, + however, the event does not correspond to a user-defined key + binding, the key field will simply be a string representing the + keycode. +*/ +struct key_event_details { + const window root ; ///< Target window's root window. + const window child; ///< Target's child that received event. + const Time time ; ///< Millisecond timestamp of event. + const int x, y ; ///< Mouse coords relative to target window. + const int x_root, y_root;///< Mouse coords relative to root. + const unsigned int mask; ///< State of mouse buttons & modifier keys. + const unsigned int keycode; ///< Number representing physical key. + const std::string key ; ///< String representation of key pressed. + const bool same_screen ; ///< True if target and root on same screen. + +protected: + /** + @brief Initialize keyboard event attributes. + @param e The XEvent structure detailing the keyboard event. + @param d The X server connection associated with the event. + @return A properly constructed key_event_details object. + + A protected constructor because only key_press and key_release + objects should construct instances of this class. + */ + key_event_details(const XKeyEvent& e, Display* d) ; +} ; + +/** + @ingroup grp_minxlib_events + @brief Encapsulation of KeyPress events. +*/ +struct key_press: public event, public key_event_details { + MINXLIB_EVENT_SUBCLASS_BOILERPLATE(key_press) ; +} ; + +/** + @ingroup grp_minxlib_events + @brief Encapsulation of KeyRelease events. +*/ +struct key_release: public event, public key_event_details { + MINXLIB_EVENT_SUBCLASS_BOILERPLATE(key_release) ; +} ; + +/** + @ingroup grp_minxlib_events + @brief Various attributes common to both focus-in and focus-out events. + + Xlib reports the same stuff for FocusIn and FocusOut events and + uses a typedef to alias the two event structures. Since minxlib uses + two different types for the two event types, we put the relevant + fields into this base class and have the relevant event subclasses + derive from this class as well as minxlib::event. +*/ +struct focus_change_details { + const int mode ; ///< Normal or grab mode (see Xlib documentation). + const int detail ; ///< More mode related info (see Xlib documentation). + +protected: + /** + @brief Initialize focus change event attributes. + @param e The Xlib structure detailing the focus change event. + @return A properly constructed focus_change_details object. + + A protected constructor because only focus_in and focus_out + objects should construct instances of this class. + */ + focus_change_details(const XFocusChangeEvent& e) ; +} ; + +/** + @ingroup grp_minxlib_events + @brief Encapsulation of FocusIn events. +*/ +struct focus_in: public event, public focus_change_details { + MINXLIB_EVENT_SUBCLASS_BOILERPLATE(focus_in) ; +} ; + +/** + @ingroup grp_minxlib_events + @brief Encapsulation of FocusOut events. +*/ +struct focus_out: public event, public focus_change_details { + MINXLIB_EVENT_SUBCLASS_BOILERPLATE(focus_out) ; +} ; + +// Get rid of the boilerplate macro to ensure it can't be used outside +// this file. +#undef MINXLIB_EVENT_SUBCLASS_BOILERPLATE + +} // namespace minxlib + +//---------------------------------------------------------------------- + +#endif // #ifndef MINXLIB_EVENT_DOT_HH + +/**********************************************/ +/* Editor config: */ +/**********************************************/ +/* Local Variables: */ +/* indent-tabs-mode: nil */ +/* c-basic-offset: 4 */ +/* End: */ +/**********************************************/ +/* vim: set expandtab shiftwidth=4 tabstop=4: */ +/**********************************************/ ADDED minxlib/exception.cc Index: minxlib/exception.cc ================================================================== --- minxlib/exception.cc +++ minxlib/exception.cc @@ -0,0 +1,435 @@ +/* + @file exception.cc + @brief Implementation of API defined in excpetion.hh. +*/ + +/* + Copyright (C) 2012 The Minx Project Developers + + See wiki/copyright.wiki in the top-level directory of the Minx + source distribution for the full list of authors who have + contributed to this project. +*/ + +/* + This file is part of Minx. + + Minx 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, either version 3 of the License, or + (at your option) any later version. + + Minx 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 Minx. If not, see . +*/ + +//----------------------------- HEADERS -------------------------------- + +// minxlib +#include "exception.hh" +#include "util.hh" + +// Boost +#include + +//---------------------- EXCEPTION BASE CLASS -------------------------- + +namespace minxlib { + +// Initialization and clean-up +exception::exception(const std::string& msg) + : std::runtime_error(msg) +{} + +exception::~exception() throw() {} + +/* + @brief Create Python exception class. + @param name Name of the Python class. + @param base Base class from which to derive new exception class. + @return The PyObject representing the new Python exception class. + + This function is lifted from: + http://code.activestate.com/lists/python-cplusplus-sig/16646/ + + See also: + http://stackoverflow.com/questions/9620268/boost-python-custom-exception-class +*/ +static PyObject* create_py_exception(const char* name, PyObject* base) +{ + // Figure out fully scoped class name + std::string scope_name = + py::extract(py::scope().attr("__name__")) ; + std::string qualified_name = scope_name + "." + name ; + + // Create the exception class + PyObject* py_exception = + PyErr_NewException(const_cast(qualified_name.c_str()), base, 0) ; + if (!py_exception) + py::throw_error_already_set() ; + + // Inject new class into Python interpreter + py::scope().attr(name) = py::handle<>(py::borrowed(py_exception)) ; + return py_exception ; +} + +// Map for the pythonize routines of direct subclasses of +// minxlib::exception. +exception::pythonize_map exception::m_pythonize_map ; + +// For direct subclasses: register your pythonize function +bool exception::register_pythonize(type_info t, exception::py_func f) +{ + m_pythonize_map[t] = f ; + return true ; +} + +// Map to store all Python exception class objects for the minxlib +// exception hierarchy. +exception::py_exception_map exception::m_py_exception_map ; + +// For all subclasses: register your Python exception class +void exception::set_py_exception(type_info t, PyObject* e) +{ + m_py_exception_map[t] = e ; +} + +// For all subclasses: retrieve the Python exception class corresponding +// to your type. +PyObject* exception::get_py_exception(type_info t) +{ + py_exception_map::const_iterator it = m_py_exception_map.find(t) ; + if ( it == m_py_exception_map.end()) // unregistered subclass! BUG! + return m_py_exception_map[typeid(exception)] ; + return it->second ; +} + +// Expose C++ exception class to Python +void exception::pythonize() +{ + const char* py_name = "exception"; + + register_translator() ; + py::class_(py_name, py::no_init) ; + m_py_exception_map[typeid(exception)] = + create_py_exception(py_name, PyExc_Exception) ; + + // Call pythonize for all the direct subclasses to export the + // minxlib exception hierarchy to Python. + BOOST_FOREACH(pythonize_map::value_type& p, m_pythonize_map) + p.second() ; +} + +/* + @brief Output operator for minxlib::exception objects. + @param s The output stream to which the exception should be sent. + @param e The exception object to be output. + @return The output stream. + + This function converts the given minxlib::exception object to a + human-readable form. Boost.Python uses it to define the __str__ + methods for the Python classes that represent minxlib exceptions. + That way, when the Python parts of Minx receive minxlib exceptions, + they can print something comprehensible instead of hex addresses and + other such gobbledygook. + + NOTE: Defining this operator for the exception base class makes it + available for all minxlib exception types and obviates the need to + define the operator for all minxlib::exception subclasses. Of + course, if the conversion to string provided by this function is + inadequate for some particular subclass, we can always overload this + function for that class. + + Alternatively, we could override std::exception::what() in the + subclass and avoid overloading this operator. +*/ +std::ostream& operator<<(std::ostream& os, const exception& e) +{ + return os << e.what() ; +} + +//---------------------- X CONNECTION ERRORS --------------------------- + +// This error will usually be reported when the window manager starts up +// and fails to connect to the X server. Since it is not triggered in an +// Xlib callback, but rather from a "straight" C++/Python context, we +// can throw this exception and have Boost.Python perform the necessary +// translation between the language run-time boundaries. +void connection_error::raise(const std::string& display_name) +{ + throw connection_error(display_name) ; +} + +connection_error::connection_error(const std::string& display_name) + : exception(std::string("unable to connect to display ") + display_name) +{} + +bool connection_error::register_me = + register_pythonize(typeid(connection_error), + &connection_error::pythonize) ; + +void connection_error::pythonize() +{ + const char* py_name = "connection_error" ; + PyObject* py_base = get_py_exception(typeid(exception)) ; + + register_translator() ; + py::class_ >(py_name, py::no_init) ; + set_py_exception(typeid(connection_error), + create_py_exception(py_name, py_base)) ; +} + +//------------------------- FATAL X ERRORS ----------------------------- + +// Fatal X errors are reported via an Xlib callback (see +// minxlib::display::report_fatal_errors()). We cannot and should not +// throw an exception from that callback because it wreaks all sorts of +// havoc. However, we can raise a Python error without any problems... +void fatal_error::raise() +{ + fatal_error f ; + py::object except(f) ; + PyErr_SetObject(get_py_exception(typeid(fatal_error)), + py::incref(except.ptr())) ; +} + +// In some situations, it may be okay to throw a fatal_error and let +// Boost.Python take care of translating the exception at the Python/C++ +// boundary. +void fatal_error::throw_it() +{ + throw fatal_error() ; +} + +fatal_error::fatal_error() + : exception("fatal X error") +{} + +bool fatal_error::register_me = + register_pythonize(typeid(fatal_error), + &fatal_error::pythonize) ; + +void fatal_error::pythonize() +{ + const char* py_name = "fatal_error" ; + PyObject* py_base = get_py_exception(typeid(exception)) ; + + register_translator() ; + py::class_ >(py_name, py::no_init). + def(py::self_ns::str(py::self_ns::self)) ; // __str__ + set_py_exception(typeid(fatal_error), + create_py_exception(py_name, py_base)) ; +} + +//----------------------- X PROTOCOL ERRORS ---------------------------- + +// Public API for raising X protocol errors. These errors are reported +// via an Xlib callback (see minxlib::display::report_protocol_errors()) +// and, so, cannot and should not be thrown. Instead, we inform Python +// clients by raising an appropriate Python exception without generating +// a corresponding C++ exception. +void protocol_error::raise(const XErrorEvent* e) +{ + // Use factory to create appropriate protocol_error sub-type based + // on the protocol error's request code. + boost::shared_ptr p(create(e)) ; + py::object except(p) ; // Python wrapper for above object + + // Get Python exception class corresponding to protocol error + PyObject* py_exc_raw = get_py_exception(typeid(*p)) ; + py::object py_exc_wrapped(py::handle<>(py::borrowed(py_exc_raw))) ; + + // Set protocol error attributes using their values from the actual + // exception instance... + py_exc_wrapped.attr("serial") = p->serial ; + py_exc_wrapped.attr("error_code") = p->error_code ; + py_exc_wrapped.attr("request_code") = p->request_code ; + py_exc_wrapped.attr("minor_code") = p->minor_code ; + py_exc_wrapped.attr("resource_id") = p->resource_id ; + + // That's it; now we can inform the Python side that some X protocol + // request resulted in an X protocol error... + // + // NOTE: Thanks to the above attributes in the protocol_error class, + // on the Python side, we can get the protocol error's request code, + // error code, etc., directly from the exception object. Moreover, + // since the exception object's value is set to the protocol_error + // (sub-type) instance with the following call to PyErr_SetObject(), + // we can also access these attributes from the args member of the + // exception object. + // + // That is, on the Python side, we can do either: + // catch minxlib.protocol_error as e: + // print(e.error_code, e.request_code, e.resource_id) + // + // or: + // catch minxlib.protocol_error as e: + // print(e.args[0].error_code, e.args[0].request_code, + // e.args[0].resource_id) + // + // Obviously, the first approach is the preferred one because it + // involves less typing. + PyErr_SetObject(py_exc_raw, py::incref(except.ptr())) ; +} + +// Factory method for creating different types of protocol errors +boost::shared_ptr protocol_error::create(const XErrorEvent* e) +{ + typedef factory + error_factory ; + try + { + boost::shared_ptr + p(error_factory::create(e->request_code, e)) ; + return p ; + } + catch (error_factory::unknown_type&) + { + boost::shared_ptr p(new protocol_error(e)) ; + return p ; + } +} + +// "Generic" protocol error constructor +protocol_error::protocol_error(const XErrorEvent* e) + : exception(to_str(e)), + serial(e->serial), + error_code(e->error_code), + request_code(e->request_code), + minor_code(e->minor_code), + resource_id(e->resourceid) +{} + +bool protocol_error::register_me = + register_pythonize(typeid(protocol_error), + &protocol_error::pythonize) ; + +// Expose protocol_error and its subclasses to Python +void protocol_error::pythonize() +{ + const char* py_name = "protocol_error" ; + + // Create Boost.Python wrapper for protocol_error class + register_translator() ; + py::class_, + boost::shared_ptr >(py_name, py::no_init). + def_readonly("serial", &protocol_error::serial ). + def_readonly("error_code", &protocol_error::error_code ). + def_readonly("request_code", &protocol_error::request_code). + def_readonly("minor_code", &protocol_error::minor_code ). + def_readonly("resource_id", &protocol_error::resource_id ). + def(py::self_ns::str(py::self_ns::self)) ; // __str__ + + // Create Python exception class for protocol errors + PyObject* py_exc_raw = + create_py_exception(py_name, get_py_exception(typeid(exception))) ; + py::object py_exc_wrapped(py::handle<>(py::borrowed(py_exc_raw))) ; + + // All protocol errors have certain common attributes (serial, + // error_code, etc.). So, let's create those attributes in the + // Python protocol_error class... + // + // NOTE: Here, we initialize these attributes to zero. When we + // actually raise a protocol error, we'll copy their values from the + // protocol error's instance (which will be created with the + // protocol_error factory). + py_exc_wrapped.attr("serial") = 0UL ; + py_exc_wrapped.attr("error_code") = 0UL ; + py_exc_wrapped.attr("request_code") = 0UL ; + py_exc_wrapped.attr("minor_code") = 0UL ; + py_exc_wrapped.attr("resource_id") = 0UL ; + + // Register Python protocol_error class with base class map + set_py_exception(typeid(protocol_error), py_exc_raw) ; + + // Call pythonize for all the subclasses to export the + // protocol_error exception hierarchy to Python. + BOOST_FOREACH(py_func p, registry::range()) + p() ; + + // Export the enumeration for the error code's possible values + py::enum_("failed_request_reason"). + value("bad_access", bad_access ). + value("bad_alloc", bad_alloc ). + value("bad_atom", bad_atom ). + value("bad_color", bad_color ). + value("bad_cursor", bad_cursor ). + value("bad_drawable", bad_drawable ). + value("bad_font", bad_font ). + value("bad_gc", bad_gc ). + value("bad_id_choice", bad_id_choice ). + value("bad_implementation", bad_implementation). + value("bad_length", bad_length ). + value("bad_match", bad_match ). + value("bad_name", bad_name ). + value("bad_pixmap", bad_pixmap ). + value("bad_request", bad_request ). + value("bad_value", bad_value ). + value("bad_window", bad_window ). + export_values() ; +} + +// As mentioned in exception.hh, each protocol_error subclass follows +// the exact same pattern: +// +// - a constructor +// - a Pythonize function +// - a static bool data member for registering with base class factory +// - a friend declaration for the factory function +// +// exception.hh uses a macro for the above boilerplate. We do the same +// here because definitions of the above functions and data member all +// do pretty much the exact same thing except that the class names are +// different. This avoids unnecessary repetition and is, therefore, less +// error-prone. Defining a new protocol_error subclass is a simple +// matter of using the macros in the header file and here. +#define DEFINE_PROTOCOL_ERROR_SUBCLASS(subclass, request_code) \ + \ + subclass::subclass(const XErrorEvent* e): protocol_error(e) {} \ + \ + void subclass::pythonize() \ + { \ + const char* py_name = #subclass ; \ + PyObject* py_base = get_py_exception(typeid(protocol_error)) ; \ + \ + register_translator() ; \ + py::class_ >(py_name, \ + py::no_init). \ + def(py::self_ns::str(py::self_ns::self)) ; \ + set_py_exception(typeid(subclass), \ + create_py_exception(py_name, py_base)) ; \ + } \ + \ + bool subclass::registered = protocol_error::registry:: \ + add(request_code, \ + create_object,\ + subclass::pythonize) + +// Various subclasses of protocol_error +DEFINE_PROTOCOL_ERROR_SUBCLASS(query_tree_error, X_QueryTree) ; +DEFINE_PROTOCOL_ERROR_SUBCLASS( set_focus_error, X_SetInputFocus) ; +DEFINE_PROTOCOL_ERROR_SUBCLASS(change_window_attributes, + X_ChangeWindowAttributes) ; + +// Get rid of boilerplate macro +#undef DEFINE_PROTOCOL_ERROR_SUBCLASS + +} // namespace minxlib + +//---------------------------------------------------------------------- + +/**********************************************/ +/* Editor config: */ +/**********************************************/ +/* Local Variables: */ +/* indent-tabs-mode: nil */ +/* c-basic-offset: 4 */ +/* End: */ +/**********************************************/ +/* vim: set expandtab shiftwidth=4 tabstop=4: */ +/**********************************************/ ADDED minxlib/exception.hh Index: minxlib/exception.hh ================================================================== --- minxlib/exception.hh +++ minxlib/exception.hh @@ -0,0 +1,746 @@ +/** + @file exception.hh + @brief Various classes for exceptions generated by minxlib. + @defgroup grp_minxlib_exceptions minxlib's Exception Classes + + This file defines classes for minxlib exceptions. These exception + classes are exposed as Python classes derived from Python's standard + Exception class. +*/ + +/* + Copyright (C) 2012 The Minx Project Developers + + See wiki/copyright.wiki in the top-level directory of the Minx + source distribution for the full list of authors who have + contributed to this project. +*/ + +/* + This file is part of Minx. + + Minx 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, either version 3 of the License, or + (at your option) any later version. + + Minx 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 Minx. If not, see . +*/ + +#ifndef MINXLIB_EXCEPTION_DOT_HH +#define MINXLIB_EXCEPTION_DOT_HH + +//----------------------------- HEADERS -------------------------------- + +// minxlib +#include "factory.hh" +#include "type_info.hh" +#include "python.hh" + +// Xlib +#include +#include + +// Boost +#include + +// Standard C++ +#include +#include +#include +#include +#include + +//---------------------- EXCEPTION BASE CLASS -------------------------- + +namespace minxlib { + +/** + @ingroup grp_minxlib_exceptions + @brief Base class for minxlib exceptions. + + This class acts as a base for the various exceptions thrown by + minxlib. This class is exposed as a Python class that is derived + from Python's standard Exception class. Thus, subclasses of this one + inherit Python's Exception interface. + + @note This class cannot be instantiated directly by client code + (neither inside minxlib nor in the Python side of Minx). It is meant + to be a common base from which other, specific types of errors are + derived. +*/ +class exception: public std::runtime_error { +protected: + /** + @brief Signature for Pythonize functions. + + Each subclass has to define a static Pythonize function that + will export its interface to minxlib's Python module using the + appropriate @boostpylink incantations. All the Pythonize + functions of direct subclasses will be called by + exception::pythonize(), which is called by the main Python + initialization module python.cc. + + @note The Pythonize functions of indirect subclasses, i.e., + subclasses of subclasses of minxlib::exception, should be called + by the Pythonize functions of their respective direct base + classes. To clarify, if we have two classes foo and bar derived + from some_error, which is derived from minxlib::exception, + exception::pythonize() will call some_error::pythonize(), which + should, in turn, call foo::pythonize() and bar::pythonize(). + */ + typedef void (*py_func)() ; + +private: + // python.cc's Boost.Python module initialization sequence calls + // this class's Pythonize routine to expose this class's Python + // interface. To keep python.cc's dependencies small, it expects + // exception::pythonize() to setup the Python interfaces for itelf + // as well as its subclasses. + // + // Of course, we don't want the exception base class to have to know + // all the classes derived from it because every time we add a new + // exception type, we will have to update the base class Pythonize + // function. Instead, we use this map to store the Pythonize + // functions for all the subclasses and require them to register + // themselves by adding their Pythonize functions to this map. + // + // NOTE: This map of Pythonize functions is meant to be used by + // direct subclasses of the exception class. An exception subclass + // that has its own subclasses should provide its own mechanism for + // registration of subclass Pythonize functions. + // + // If indirect subclasses were to use this map, it is possible that + // their Pythonize functions will get called before their direct + // base class's Pythonize. For example, the protocol_error has + // subclasses (set_focus_error, etc.). If set_focus_error were to + // use this map itself, its Pythonize could be called before + // protocol_error::pythonize(), which would be problematic because + // Boost.Python needs to export the base class interface before + // getting to a subclass. + // + // Therefore, only direct subclasses should register their Pythonize + // functions in this map. Subclasses of subclasses of exception + // should rely on registration mechanisms provided by their direct + // base classes and not use this map. + typedef std::map pythonize_map ; + static pythonize_map m_pythonize_map ; + + // For each C++ exception type, we need a corresponding Python class + // object to represent it on the Python side of Minx. This map stores + // references to these Python class objects for all of minxlib's + // different exception types. All subclasses (direct and indirect) + // are expected to create their Python exception classes in their + // respective Pythonize routines and add that class object to this + // map. + typedef std::map py_exception_map ; + static py_exception_map m_py_exception_map ; + +protected: + /** + @brief Construct base exception object. + @param msg An error message describing the problem. + @return A properly constructed minxlib::exception object. + + A protected constructor to ensure that only subclasses can + construct objects of this type. The msg parameter is simply + passed as-is to the std::runtime_error constructor. + */ + exception(const std::string& msg) ; + + /** + @brief Register subclass's pythonize function. + @param t Subclass typeid. + @param f Pointer to subclass's pythonize function. + @return Nothing really. + + The main Python initialization module, python.cc, calls + exception::pythonize() to export the entire @ref grp_minxlib_exceptions + "minxlib exception class hierarchy" to the Python parts of Minx. + All subclasses of minxlib::exception are expected to implement a + Pythonize function that takes care of exporting their respective + Python interfaces. To ensure that + minxlib::exception::pythonize() can call the Pythonize functions + of subclasses, all direct subclasses of minxlib::exception + should call this function to let the base exception class know + the address of the subclass's Pythonize function. + + @note Only direct subclasses of minxlib::exception should use + this function. If a subclass of a subclass of minxlib::exception + registers its Pythonize function using this routine, it could + happen that the indirect subclass's Pythonize function gets + called before its base class's Pythonize function, which can + mess up the @boostpylink interface initialization sequence. Base + classes of subclasses of subclasses of minxlib::exception should + provide their own mechanisms for registering the Pythonize + functions of their subclasses. + + To make the registration mechanism described above work, direct + subclasses of minxlib::exception should declare a static bool + variable and in its initializer, call this function. + */ + static bool register_pythonize(type_info t, py_func f) ; + + /** + @brief Register Python exception class object for a subclass. + @param t Subclass typeid. + @param e The subclass's Python exception class. + @return Nothing. + + Each minxlib exception class must have a corresponding Python + exception class object, which must be registered with the + minxlib::exception base class. In their respective Pythonize + functions, all subclasses of minxlib::exception have to create a + Python exception class corresponding to their C++ types and then + call this function to insert that Python class object into the + base class's registry of exception classes. + + @note Unlike register_pythonize(), which is meant only for + direct subclasses of minxlib::exception, this function can and + should be used by all subclasses (direct or indirect). + */ + static void set_py_exception(type_info t, PyObject* e) ; + + /** + @brief Retrieve Python exception class for specified C++ type. + @param t C++ exception class/subclass typeid. + @return Python exception class object corresponding to t. + + When we raise a minxlib exception to inform the Python side of + Minx of some problem, we need the Python exception class + corresponding to the C++ exception type. This function retrieves + the desired Python exception class object from the registry + maintained by the minxlib::exception base class. + + If a (direct or indirect) subclass of minxlib::exception fails to + register a Python exception class corresponding to its C++ type, + this function will return the Python exception class + corresponding to the minxlib::exception class. If this ever + happens, it indicates a bug in the implementation of type t! + */ + static PyObject* get_py_exception(type_info t) ; + + /** + @brief Generic Boost.Python exception translator. + @param e C++ exception object that needs to be translated to Python. + @return Nothing. + + This function sets up an exception translator for an exception + of type T, which is expected to be either minxlib::exception or + one of its subclasses. + + All these translators follow the same pattern, viz., calling + PyErr_SetString() using the Python exception class corresponding + to type T as stored in exception::m_py_exception_map. Thus, we + can use a generic function for this purpose. + */ + template + static void translate(const T& e) ; + + /** + @brief Register @boostpylink exception translator for type T. + @return Nothing. + + This function is meant to be used in the Pythonize routines of + minxlib::exception and its subclasses that need an exception + translator for their type. It simply generates a translator for + type T (which is the exception::translate() function) and calls + the appropriate @boostpylink API to register + exception::translate() as the exception translator for type T. + */ + template + static void register_translator() { + py::register_exception_translator(translate) ; + } + +public: + /** + @brief Expose exception interface to Python. + @return Nothing. + + This function is meant to be called as part of the @boostpylink + module initialization. It will call the Pythonize functions of + all minxlib::exception subclasses in order to export the entire + @ref grp_minxlib_exceptions "minxlib exception hierarchy" to Python. + */ + static void pythonize() ; + + /** + @brief Virtual destructor. + @return Nothing. + + Since this is a base class, it's good to have a virtual + destructor in place. + + @note std::runtime_error already has a virtual destructor. + However, if we skip this definition, the compiler complains + about this class's auto-generated destructor having looser throw + specifications than the base class. + */ + virtual ~exception() throw() ; +} ; // class exception + +// Generic exception translator that can be used by minxlib::exception +// and all its subclasses. +template +void exception::translate(const T& e) +{ + PyErr_SetString(get_py_exception(typeid(T)), e.what()) ; +} + +//---------------------- X CONNECTION ERRORS --------------------------- + +/** + @ingroup grp_minxlib_exceptions + @brief An exception object to indicate failure to connect to X server. + + When a minxlib::display object is created, if it fails to connect to + the X server, the display class's constructor will throw an instance + of this class to indicate the failure. On the Python side of Minx, + this class is derived from minxlib.exception, which, in turn, is + derived from the standard Python Exception class. +*/ +struct connection_error: public exception { + /** + @brief Throw a connection error. + @param display_name Name of display to which connection failed. + @return Nothing. + + This function throws a connection_error. Clients cannot directly + instantiate this class and throw it. Instead, they must call + this function to have the object created and thrown. + + When raising a connection_error, the client must specify the + name of X display to which the connection attempt failed. + XDisplayName(), for example, will return something usable; refer + to the Xlib documentation for further details. + + @note All the minxlib exception classes hide their constructors + and require clients to call a raise() function instead. This is + because, in some cases (errors to be reported from Xlib + callbacks), throwing a C++ exception has very undesirable + effects (e.g., the Minx process consuming 100% CPU). For those + cases, we want to simply raise a Python exception without + generating the corresponding C++ exception. Hiding the exception + class constructors ensures that clients can only generate + exceptions in the appropriate way. + + @par + For connection errors, since they occur from a normal C++/Python + context rather than from inside an Xlib callback, it is alright + to throw a C++ exception and have Boost.Python perform the + necessary translations. However, to keep minxlib's exception + interface uniform, we hide this class's constructor as well and + require clients to use raise(), which, internally, instantiates + and throws a connection_error. + */ + static void raise(const std::string& display_name) ; + +private: + /** + @brief Construct a connection_error object. + @return Properly constructed connection_error object. + + Clients should specify the appropriate display name when + creating a connection_error. XDisplayName(), for example, will + return something usable. + */ + connection_error(const std::string& display_name) ; + + /** + @brief Expose connection_error exception interface to Python. + @return Nothing. + + This function is meant to be called as part of the Boost.Python + module initialization. + */ + static void pythonize() ; + + // Register with base class so its pythonize calls this subclass's + // pythonize. + static bool register_me ; +} ; // struct connection_error + +//------------------------- FATAL X ERRORS ----------------------------- + +/** + @ingroup grp_minxlib_exceptions + @brief An exception object to indicate a fatal X error. + + When something fatal happens (such as losing the connection to the X + server), minxlib will notify its Python clients by raising an + instance of this class as an exception. Like all minxlib exceptions, + on the Python side of Minx, this class is derived from + minxlib.exception, which, in turn, is derived from the standard + Python Exception class. + + After a fatal_error is raised, clients should clean-up and + terminate. Attempting further use of Xlib via minxlib will result in + continued fatal_error exceptions. +*/ +struct fatal_error: public exception { + /** + @brief Report a fatal X error. + @return Nothing. + + This function raises a minxlib.fatal_error so the Python side of + Minx is informed of catastrophic failures such as losing the + connection to the X server. + + @note C++ clients cannot directly instantiate this class and + throw it. Instead, they must call this function to have the + error properly reported to the Python side. + + @par + All the minxlib exception classes hide their constructors and + require clients to call a raise() function instead. This is + because, in some cases (errors to be reported from Xlib + callbacks), throwing a C++ exception has very undesirable + effects (e.g., the Minx process consuming 100% CPU). For those + cases, we want to simply raise a Python exception without + generating the corresponding C++ exception. Hiding the exception + class constructors ensures that clients can only generate + exceptions in the appropriate way. + */ + static void raise() ; + + /** + @brief Throw a fatal_error in C++ rather than raising it in Python. + @return Nothing. + + When a fatal X error is encountered, + minxlib::display::report_fatal_errors() will raise a + minxlib.fatal_error on the Python side without throwing a + minxlib::fatal_error C++ exception because we cannot throw C++ + exceptions from Xlib callbacks. + + After that, minxlib::display will set an internal flag to + indicate that a fatal error has been encountered and that its + connection to the X server can no longer be used. Clients that + try to ignore the first fatal_error and keep using minxlib's X + API will then keep receiving more fatal_error exceptions. + + However, in this situation, it should be okay to throw a C++ + fatal_error and let @boostpylink perform the necessary exception + translation at the Python/C++ boundary because the exception is + not generated from inside an Xlib callback. + + This method allows the minxlib::display class and other minxlib + clients to report a fatal error by throwing the exception rather + than simply raising it in the Python interpreter. + */ + static void throw_it() ; + +private: + /** + @brief Construct a fatal_error object. + @return Properly constructed fatal_error object. + + A private constructor because the exception represented by this + class is meant to be generated within the Xlib I/O error handler + and we cannot throw C++ exceptions from inside Xlib callbacks. + Therefore, we hide the constructor to ensure that clients + generate this type of error in the proper way. + */ + fatal_error() ; + + /** + @brief Expose fatal_error exception interface to Python. + @return Nothing. + + This function is meant to be called as part of the Boost.Python + module initialization. + */ + static void pythonize() ; + + // Register with base class so its pythonize calls this subclass's + // pythonize. + static bool register_me ; +} ; // struct fatal_error + +//----------------------- X PROTOCOL ERRORS ---------------------------- + +/** + @ingroup grp_minxlib_exceptions + @brief A base class to indicate X protocol errors. + + When an X protocol request fails, minxlib will notify its Python + clients by raising an instance of this class or one of its + subclasses as an exception. Like all minxlib exceptions, on the + Python side of Minx, this class is derived from minxlib.exception, + which, in turn, is derived from the standard Python Exception class. +*/ +struct protocol_error: public exception { + /** + @brief Serial number of failed request. + + All requests to the X server have a serial number that starts at + one. This field contains the request number corresponding to the + protocol request that failed, thus, resulting in a + protocol_error. + + @note This is a low-level detail of the X protocol and is not + really used in Minx. + */ + const unsigned long serial ; + + /** + @brief Names for different error codes. + + This enumeration specifies the reason for a failed protocol + request. It is simply the Xlib names converted from CamelCase to + a saner naming convention: + + + + + + + + + + + + + + + + + + + + +
minxlib Xlib
bad_access BadAccess
bad_alloc BadAlloc
bad_atom BadAtom
bad_color BadColor
bad_cursor BadCursor
bad_drawable BadDrawable
bad_font BadFont
bad_gc BadGC
bad_id_choice BadIDChoice
bad_implementationBadImplementation
bad_length BadLength
bad_match BadMatch
bad_name BadName
bad_pixmap BadPixmap
bad_request BadRequest
bad_value BadValue
bad_window BadWindow
+ */ + enum failed_request_reason { + bad_access = BadAccess, + bad_alloc = BadAlloc, + bad_atom = BadAtom, + bad_color = BadColor, + bad_cursor = BadCursor, + bad_drawable = BadDrawable, + bad_font = BadFont, + bad_gc = BadGC, + bad_id_choice = BadIDChoice, + bad_implementation = BadImplementation, + bad_length = BadLength, + bad_match = BadMatch, + bad_name = BadName, + bad_pixmap = BadPixmap, + bad_request = BadRequest, + bad_value = BadValue, + bad_window = BadWindow, + } ; + + /** + @brief Error code of failed request. + + This value specifies the reason for the failed request and will + be one of the values of the failed_request_reason enumeration. + */ + const unsigned long error_code ; + + /** + @brief Major opcode of failed request. + + This member is the opcode of the failed X protocol request as + defined in X11/Xproto.h. + + minxlib does not provide an enumeration for the different + protocol request codes. However, some of the request codes are + made available as specific exception types derived from + protocol_error. Those that are not wrapped thusly can only be + gleaned from the string representation of protocol_error, which + will contain an appropriate name for the request code. + */ + const unsigned long request_code ; + + /** + @brief Minor opcode of failed request. + + No idea what the hell this is. Not used in Minx anyway. + */ + const unsigned long minor_code ; + + /** + @brief Resource ID associated with failed request. + + This member specifies the particular X object (window, GC, + atom, whatever) that was involved with the protocol request that + failed. + + For example, if we try to set the input focus to a window that + has been unmapped or destroyed, the resulting protocol_error's + resource_id will be the ID of that window. Since the X protocol + is asynchronous, handling protocol_error exceptions and using + this ID is about the only way to keep the window manager's + internal state in-sync with the X server's internal state. + */ + const unsigned long resource_id ; + + /** + @brief Report an X protocol error. + @param e XErrorEvent structure containing protocol error details. + @return Nothing. + + This function raises a minxlib.protocol_error to inform the + Python side of Minx of various X-related errors such as + attempting to set the input focus on a non-existent or unmapped + window. + + Most X protocol errors will be reported using + minxlib.protocol_error. However, certain protocol errors have + specific types that are derived from this class. The Python side + of Minx can use these subclass types to easily catch and handle + specific errors. + + @note C++ clients cannot directly instantiate this class and + throw it. Instead, they must call this function to have the + error properly reported to the Python side. This is because + throwing a C++ exception from inside an Xlib callback makes the + Minx process enter some sort of infinite loop and consume 100% + CPU. Basically, interaction between C and C++ with regards to + exceptions and stack unwinding is ill-defined (or, at least when + it comes to Xlib and minxlib, it is). + */ + static void raise(const XErrorEvent* e) ; + +private: + /** + @brief Create a protocol_error exception. + @param e XErrorEvent structure containing protocol error details. + @return Shared pointer to protocol_error or subclass instance. + + This function serves as the factory method for creating + protocol_error objects. For certain errors, minxlib can provide + more detailed information by raising an instance of a subclass + of this class as an exception. + + For example, when an X_SetInputFocus request fails, the error + will be reported via an instance of minxlib.set_focus_error, + which is derived from minxlib.protocol_error (i.e., the Python + interface to this class). Thus, the Minx Python core can readily + make out the type of error it is dealing with simply by catching + that particular exception type instead of having to examine the + various fields of a minxlib.protocol_error. + + This factory method creates a subclass instance corresponding to + the XErrorEvent's request code. Not all request codes are + supported by minxlib. In those cases, a protocol_error object + will be returned. + + Note that this factory method is private! Only the raise + function can use it. Clients do not need to access it directly. + */ + static boost::shared_ptr create(const XErrorEvent* e) ; + +protected: + /** + @brief Construct a protocol_error object. + @param e The XErrorEvent structure detailing the error. + @return Properly constructed protocol_error object. + + This class is meant to be used by the Xlib error handler, + which is implemented in minxlib by + display::report_protocol_errors(). That function will be + passed an XErrorEvent by Xlib, which it will, in turn, pass + to this constructor. The constructor converts the error to + human-readable form and also records the various fields in + the protocol_error object it creates. + + @note This is a protected constructor because protocol_error + instances are meant to be created using the create() factory + method and only subclasses may initialize the base. + */ + protocol_error(const XErrorEvent* e) ; + + /** + @brief Signature of factory function. + + Each protocol_error subclass will be instantiated by a factory + function that takes an XErrorEvent and returns an instance of + the subclass upcast to minxlib::protocol_error. + */ + typedef protocol_error* (*cr_func)(const XErrorEvent*) ; + + /** + @brief Registry of factory functions for instantiating subclasses. + + This type defines the protocol_error object factory's registry, + which maps the X protocol error codes to the corresponding + protocol_error subclass's factory function. Additionally, we + store the Pythonize functions in the registry as well, which + obviates the need for another map to hold them. + + For the @ref grp_factory "factory pattern" to work, + protocol_error subclasses must define a static boolean data + member and, in its initializer, call + protocol_error::registry::add(), passing it the appropriate X + protocol error code, the subclass's factory function, and its + Pythonize function. + */ + typedef factory_map registry ; + +private: + /** + @brief Expose protocol_error exception interface to Python. + @return Nothing. + + This function is meant to be called as part of the Boost.Python + module initialization. + */ + static void pythonize() ; + + // Register with base class so its pythonize calls this subclass's + // pythonize. + static bool register_me ; +} ; // struct protocol_error + +//----------------- SPECIFIC PROTOCOL ERROR TYPES ---------------------- + +// Each protocol_error subclass follows the same pattern: a constructor, +// a Pythonize function, a static boolean data member, and a friend +// declaration for the appropriate create_error factory function. +// +// Instead of typing out all of that over and over, we use the following +// macro to do it for us. +#define DECLARE_PROTOCOL_ERROR_SUBCLASS(subclass) \ + class subclass: public protocol_error { \ + subclass(const XErrorEvent*) ; \ + static void pythonize(); \ + static bool registered ; \ + friend protocol_error* create_object(const XErrorEvent*) ; \ + } + +// Input focus related errors +DECLARE_PROTOCOL_ERROR_SUBCLASS(query_tree_error) ; +DECLARE_PROTOCOL_ERROR_SUBCLASS( set_focus_error) ; +DECLARE_PROTOCOL_ERROR_SUBCLASS(change_window_attributes) ; + +// Get rid of the boilerplate macro to ensure it can't be used outside +// this file. +#undef DECLARE_PROTOCOL_ERROR_SUBCLASS + +} // namespace minxlib + +//---------------------------------------------------------------------- + +#endif // #ifndef MINXLIB_EXCEPTION_DOT_HH + +/**********************************************/ +/* Editor config: */ +/**********************************************/ +/* Local Variables: */ +/* indent-tabs-mode: nil */ +/* c-basic-offset: 4 */ +/* End: */ +/**********************************************/ +/* vim: set expandtab shiftwidth=4 tabstop=4: */ +/**********************************************/ ADDED minxlib/factory.hh Index: minxlib/factory.hh ================================================================== --- minxlib/factory.hh +++ minxlib/factory.hh @@ -0,0 +1,735 @@ +/** + @file factory.hh + @brief Generic object factory. + @defgroup grp_factory minxlib's Object Factory Framework + + This file defines classes that provide a generic object factory + framework for use in other parts of minxlib. + + @note This framework is designed specifically for use in minxlib. It + is not some bulletproof, industrial strength concoction suitable for + wide reuse. + + Here is a sample program illustrating how this framework is meant to + be used: + + @code + #include "factory.hh" + + #include + + #include + #include + #include + + namespace minxlib { + + class base { + std::string m_name ; + protected: + base(const std::string& s) { + std::ostringstream str ; + str << s << '_' << reinterpret_cast(this) ; + m_name = str.str() ; + } + + // Base class should specify these types to ease registration + // with factory. + typedef base* (*create_func)() ; + typedef factory_map registry ; + + public: + // Usually, base class will provide a factory method for + // instantiating subclasses in the hierarchy. + static boost::shared_ptr create(int t) { + typedef factory object_factory ; + try { + boost::shared_ptr p(object_factory::create(t)) ; + return p ; + } + catch (object_factory::unknown_type&) { + boost::shared_ptr p(new base("base")) ; + return p ; + } + } + + const std::string& name() const {return m_name ;} + virtual ~base(){} + } ; + + std::ostream& operator<<(std::ostream& os, const base& b) + { + return os << b.name() ; + } + + class derived_one: public base { + derived_one(): base("derived_one"){} + + // Subclasses should have these to make factory work + static bool registered ; + friend base* create_object() ; + } ; + + bool derived_one::registered = + base::registry::add(1, create_object) ; + + class derived_two: public base { + derived_two(): base("derived_two"){} + + // Subclasses should have these to make factory work + static bool registered ; + friend base* create_object() ; + } ; + + bool derived_two::registered = + base::registry::add(2, create_object) ; + + } // namespace minxlib + + int main() + { + const int n = 10 ; + int id[n] = {0, 1, 2, 2, 1, 3, 1, 2, 1, -1} ; + for (int i = 0; i < n; ++i) { + boost::shared_ptr + p(minxlib::base::create(id[i])) ; + std::cout << "object " << (i+1) << ": " << id[i] + << ' ' << *p << '\n' ; + } + return 0 ; + } + @endcode + + The above code shows classes that are created using their default + constructors. The factory::create() methods and create_object() + functions have overloads that support up to two arguments. + + Moreover, the above sample program does not illustrate the + factory_map's support for storing arbitrary, client-supplied data + for each class. See the implementations of minxlib::event (in + event.hh and event.cc) and minxlib::protocol_error + (exception.{hh,cc}) for the low-down on that feature. +*/ + +/* + Copyright (C) 2012 The Minx Project Developers + + See wiki/copyright.wiki for the full list of authors who have + contributed to this project. +*/ + +/* + This file is part of Minx. + + Minx 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, either version 3 of the License, or + (at your option) any later version. + + Minx 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 Minx. If not, see . +*/ + +#ifndef MINXLIB_FACTORY_DOT_HH +#define MINXLIB_FACTORY_DOT_HH + +//----------------------------- HEADERS -------------------------------- + +// Boost +#include +#include +#include +#include +#include + +// Standard C++ +#include +#include +#include +#include + +//-------------------- OBJECT FACTORY REGISTRY ------------------------- + +namespace minxlib { + +// An empty type for use as default template arguments +namespace {struct null_type {} ;} + +/** + @ingroup grp_factory + @brief A registry mapping keys to factory functions. + + This class implements a singleton map that holds the subclass + factory functions (type F) for classes in a hierarchy identified by + keys of type K. In addition to the factory functions, it can also + hold arbitrary data of type D. + + The factory functions are used by the factory class defined in this + file. However, the optional arbitrary data is only held and can be + used by client classes in whatever way they see fit. In minxlib, + this is usually used for the pythonize functions; see event.hh (for + example). + + @note To use more than a single item of arbitrary data, you can use + a boost::tuple. + + @par + As mentioned earlier, this object factory framework is designed + specifically for minxlib and is not meant for wide reuse. One of + the implications of this is that the types K, F, and D should be + cheap to construct and copy. In minxlib, K is typically an integral + type, F and D are function pointers. +*/ +template +class factory_map: public boost::noncopyable { + // We stuff the factory function and client-supplied arbitrary data + // into an STL pair so we can store both together in the factory + // map. + typedef std::pair value_type ; + + // This type defines the mapping between class identifiers and their + // corresponding factory functions plus arbitrary data. + typedef std::map map_type ; + + // We use iterators to allow clients to retrieve their data stored + // in this map. + // + // NOTE: We only provide const iterators. That is, once data is + // stored in this map, it can only be read back. + typedef typename map_type::const_iterator map_iterator ; + + // Okay, enough of the clarifying typedefs; here's the actual data + // structure. + map_type m_map ; + + /** + @brief Initialize the factory registry. + @return Nothing really. + + A private constructor because this class is meant to be a + singleton and we don't want clients trying to instantiate it. + */ + factory_map() ; + + /** + @brief Instantiate lone instance of this class. + @return Reference to the lone instance. + + This function creates a static instance of this class when it is + called for the first time. Then (and on subsequent invocations) + it returns a reference to that static instance. + + This method is also private because we don't want clients to + care about this implementation detail. They should use the + static methods in the class's public interface and let those + functions worry about the fact that this class is a singleton. + + @note This class uses the fairly standard singleton idiom as + described in Scott Meyers's book "Effective C++." However, as + Andrei Alexandrescu points out in "Modern C++ Design," there are + limitations and problems with this approach. + + @par + Reiterating what was mentioned earlier: this object factory + framework is not bulletproof, nor is it meant to be so. For + Minx's intended use-case, this should work just fine. + */ + static factory_map& instance() ; + + /** + @brief Find value corresponding to given key. + @param k The key whose value we want. + @return The value corresponding to k or a bad_key exception. + + This function looks up the key k in the map of keys to factory + functions plus user-supplied data and returns that value in an + STL pair whose first element is the factory function and whose + second element is the user-supplied data. + + If the map does not hold any value corresponding to the given + key k, this function will throw a bad_key exception. + */ + value_type find(K k) const ; + +public: + /** + @brief Register a factory and optional data. + @param k The key identifying a class in some hierarchy. + @param f The factory function for the class identified by k. + @param d Optional data to be stored along with the class's factory. + @return True (i.e., useless return value). + + This function is meant to be used by subclasses in a class + hierarchy that is to be instantiated with an object factory. The + typical usage pattern would be for each subclass in the + hierarchy to define a static bool data member and call this + function in that data member's initializer. + */ + static bool add(K k, F f, D d = null_type()) { + instance().m_map[k] = value_type(f, d) ; + return true ; + } + + /** + @brief Retrieve the factory function for a class. + @param k They key identifying the class whose factory we want. + @return The factory function for the specified class. + + This function is meant to be called by the minxlib::factory + class. Other clients should have little use for it. + */ + static F get_factory(K k) {return instance().find(k).first ;} + + /** + @brief Retrieve the user-supplied arbitrary data for a class. + @param k They key identifying the class whose user data we want. + @return The user data for the specified class. + + This function is meant to be called by clients so they can get + the arbitrary data they supplied when registering their + factories. Typically, however, clients will iterate over the map + and use the data values through that process. + */ + static D get_data(K k) {return instance().find(k).second ;} + + /** + @brief Exception to indicate nonexistent key. + + When a client requests a factory function or client data + associated with a key that is not in the map, an instance of + this class will be thrown. + */ + struct bad_key: public std::runtime_error { + const K key ; + bad_key(K k); + } ; + + /** + @brief Iterator for accessing client-supplied arbitrary data. + + Although clients may retrieve the arbitrary data they supply by + passing the desired key to the factory_map::get_data() function, + it may be more convenient to iterate over the underlying map. + This inner class provides the necessary interface for iteration. + + @note This iterator class is built on the const_iterator + provided by the underlying STL map. Thus, it only supports read + access to the client-supplied data. + + @par + This class implements its functionality using Boost.Iterator, + whose magic obviates the need for any public methods. + */ + class iterator: public boost::iterator_adaptor { + // Construct using underlying STL map's (const) iterator. + // DEVNOTE: iterator_adaptor_ member is courtesy Boost.Iterator. + iterator(map_iterator& i): iterator::iterator_adaptor_(i){} + + // Move to next element in sequence + void increment() {++this->base_reference() ;} + + // Retrieve data currently being "pointed" at + D& dereference() const { + return const_cast(this->base_reference()->second.second) ; + } + + // We want only the factory_map (outer) class to be able to + // instantiate this iterator. + friend class factory_map ; + + // Standard Boost.Iterator idiom + friend class boost::iterator_core_access ; + } ; // class iterator + + /** + @brief Read-only iterators. + + This class's iterators are already read-only. Therefore, the + const_iterator type is the same as the iterator type. + */ + typedef iterator const_iterator ; + + /** + @brief Obtain read-only iterator to first data item. + @return Iterator to first item. + + When some function wants to iterate over all the arbitrary data + items supplied during the registration of the various subclass + factories, it can request an iterator to the first item of the + underlying sequence using this function. + + @note Once data is stored in a factory_map, clients may only + obtain read-only access to it. That is, the iterator returned by + this function is a const_iterator. + */ + static const_iterator begin() ; + + /** + @brief Obtain read-only iterator to one past last data item. + @return Iterator pointing to one-past last item. + + When some function wants to iterate over all the arbitrary data + items supplied during the registration of the various subclass + factories, it can request an iterator to one-past the last item + of the underlying sequence using this function. + + @note Once data is stored in a factory_map, clients may only + obtain read-only access to it. That is, the iterator returned by + this function is a const_iterator. + */ + static const_iterator end() ; + + /** + @brief Pack begin and end iterators in an STL pair. + @return STL pair containing begin and end iterators. + + This function is meant to be used in conjunction with + BOOST_FOREACH. Here is an illustrative (albeit non-buildable) + example: + + @code + class foo { ... } ; + class bar { ... } ; + typedef foo* (*create_foo)() ; + typedef factory_map registry ; + + // code to register various factory functions and bar + // objects in registry + + void f() + { + BOOST_FOREACH(const bar& b, registry::range()) + b.do_something() ; + } + @endcode + */ + static std::pair range() { + return std::make_pair(begin(), end()) ; + } +} ; // class factory_map + +// Private constructor +template +factory_map::factory_map(){} + +// Singleton method +template +factory_map& factory_map::instance() +{ + static factory_map m ; + return m ; +} + +// Map lookup +template +typename factory_map::value_type factory_map::find(K k) const +{ + typename map_type::const_iterator it = m_map.find(k) ; + if (it == m_map.end()) + throw bad_key(k) ; + return it->second ; +} + +// Iterators for accessing client-supplied arbitrary data stored along +// with factory functions. +template +typename factory_map::iterator factory_map::begin() +{ + const factory_map& m = instance(); + map_iterator it = m.m_map.begin(); + return iterator(it) ; +} + +template +typename factory_map::iterator factory_map::end() +{ + const factory_map& m = instance(); + map_iterator it = m.m_map.end() ; + return iterator(it) ; +} + +// Exception for unknown key +template +factory_map::bad_key::bad_key(K k) + : std::runtime_error(std::string("unknown key: " + + boost::lexical_cast(k))), + key(k) +{} + +//--------------------- GENERIC OBJECT FACTORY ------------------------- + +/** + @ingroup grp_factory + @brief Object factory for subclasses of type T. + + This class defines an object factory for subclasses of some base + type T. Each subclass is expected to be identified uniquely by a + value of type K (usually, an integral type). A function matching the + signature specified by type F (usually a function pointer) should be + registered with the factory and it should use the new operator to + create an instance of the subclass identifed by K and upcast that to + a T*. + + The type M is expected to be an associative container that maps + subclass identifier keys of type K to their factory functions of + type F. By default, this type is factory_map, which stores keys + and factory functions and no client data. In minxlib, however, we + usually use a factory_map, where D is usually a function + pointer for the pythonize functions. + + The factory_map class defined above is good enough for most purposes + and there should be little need to implement a whole new type M. + However, if such madness becomes necessary, then, at the very least, + from the factory class's perspective, the type M has to define a + static get_factory() method that will return an F given a K. + + @note There is an implicit assumption that the type M will be a + singleton. +*/ +template > +class factory: boost::noncopyable { + // An undefined, private constructor to ensure nobody tries to + // instantiate this class, whose public interface consists mostly of + // overloaded versions of static create() methods that produce + // objects of type T given class identifiers of type K. + factory() ; + +public: + /** + @brief Exception to indicate an unregistered subclass. + + For the factory pattern to work, subclasses in a hierarchy + should register their factory functions with this framework. + Otherwise, if a client, usually the base class factory method, + asks the factory for an instance of a type it knows nothing + about, the factory will complain by throwing an instance of this + exception class. + */ + struct unknown_type: public std::runtime_error { + const K key ; + unknown_type(K k) ; + } ; + + /** + @brief Create an instance of a subclass of T. + @param k Identifier for the desired subclass. + @return An instance of the desired subclass or an exception. + + This function is for creating objects of class identified by k + using the default constructor for that class. The object will be + upcast to the base class T. + + This function will retrieve the factory function for the desired + class from the factory map (type M of this template) and use + that to create the object. The subclass identified by k should + have registered its factory function with the factory map. The + factory function should create an instance of the subclass using + the new operator and upcast the result to the base class type T. + + If this function fails to retrieve a factory function for the + given key k, it will throw an unknown_type exception. Clients + should be prepared to handle this exception. + */ + static T* create(K k) ; + + /** + @brief Create an instance of a subclass of T. + @param k Identifier for the desired subclass. + @param a Argument for the subclass's constructor. + @return An instance of the desired subclass or an exception. + + This function is for creating objects of class identified by k + using the one-argument constructor for that class. The object + will be upcast to the base class T. + + This function will retrieve the factory function for the desired + class from the factory map (type M of this template) and use + that to create the object. The subclass identified by k should + have registered its factory function with the factory map. The + factory function should create an instance of the subclass using + the new operator, passing the constructor the argument a, and + upcast the result to the base class type T. + + If this function fails to retrieve a factory function for the + given key k, it will throw an unknown_type exception. Clients + should be prepared to handle this exception. + */ + template + static T* create(K k, A a) ; + + /** + @brief Create an instance of a subclass of T. + @param k Identifier for the desired subclass. + @param a First argument for the subclass's constructor. + @param b Second argument for the subclass's constructor. + @return An instance of the desired subclass or an exception. + + This function is for creating objects of class identified by k + using the two-argument constructor for that class. The object + will be upcast to the base class T. + + This function will retrieve the factory function for the desired + class from the factory map (type M of this template) and use + that to create the object. The subclass identified by k should + have registered its factory function with the factory map. The + factory function should create an instance of the subclass using + the new operator, passing the constructor the arguments a and b, + and upcast the result to the base class type T. + + If this function fails to retrieve a factory function for the + given key k, it will throw an unknown_type exception. Clients + should be prepared to handle this exception. + */ + template + static T* create(K k, A a, B b) ; +} ; + +// Exception for unregistered subclasses +template +factory::unknown_type::unknown_type(K k) + : std::runtime_error(std::string("unknown object type: " + + boost::lexical_cast(k))), + key(k) +{} + +// Default constructor factory method +template +T* factory::create(K k) +{ + try + { + F f = M::get_factory(k) ; + return f() ; + } + catch (typename M::bad_key&) + { + throw unknown_type(k) ; + } +} + +// One argument constructor factory method +template +template +T* factory::create(K k, A a) +{ + try + { + F f = M::get_factory(k) ; + return f(a) ; + } + catch (typename M::bad_key&) + { + throw unknown_type(k) ; + } +} + +// Two argument constructor factory method +template +template +T* factory::create(K k, A a, B b) +{ + try + { + F f = M::get_factory(k) ; + return f(a, b) ; + } + catch (typename M::bad_key&) + { + throw unknown_type(k) ; + } +} + +// DEVNOTE: Will Boost.Preprocessor help automate the above boilerplate +// and the repetition in the definitions of the overloaded +// create_object() functions below? Should figure it out sometime... + +//--------------------- GENERIC FACTORY FUNCTIONS ----------------------- + +/** + @ingroup grp_factory + @brief Create object of type D using new operator and upcast to B. + @return Dynamically allocated derived class instance. + + When using this object factory framework, more often than not, client + classes will need a factory function to create a concrete type. + Usually, these factory functions all follow the same pattern. Thus, + instead of each client supplying its own factory function, we define + this template function that different class hierarchies can use. The + sample code at the beginning of this file illustrates how these + factory functions can be used. +*/ +template +B* create_object() +{ + typedef boost::is_base_of is_base ; + BOOST_STATIC_ASSERT(is_base::value) ; + return new D ; +} + +/** + @ingroup grp_factory + @brief Create object of type D using new operator and upcast to B. + @param a1 First constructor argument. + @return Dynamically allocated derived class instance. + + When using this object factory framework, more often than not, client + classes will need a factory function to create a concrete type. + Usually, these factory functions all follow the same pattern. Thus, + instead of each client supplying its own factory function, we define + this template function that different class hierarchies can use. The + sample code at the beginning of this file illustrates how these + factory functions can be used. +*/ +template +B* create_object(A1 a1) +{ + typedef boost::is_base_of is_base ; + BOOST_STATIC_ASSERT(is_base::value) ; + return new D(a1) ; +} + +/** + @ingroup grp_factory + @brief Create object of type D using new operator and upcast to B. + @param a1 First constructor argument. + @param a2 Second constructor argument. + @return Dynamically allocated derived class instance. + + When using this object factory framework, more often than not, client + classes will need a factory function to create a concrete type. + Usually, these factory functions all follow the same pattern. Thus, + instead of each client supplying its own factory function, we define + this template function that different class hierarchies can use. The + sample code at the beginning of this file illustrates how these + factory functions can be used. +*/ +template +B* create_object(A1 a1, A2 a2) +{ + typedef boost::is_base_of is_base ; + BOOST_STATIC_ASSERT(is_base::value) ; + return new D(a1, a2) ; +} + +} // namespace minxlib + +//---------------------------------------------------------------------- + +#endif // #ifndef MINXLIB_FACTORY_DOT_HH + +/**********************************************/ +/* Editor config: */ +/**********************************************/ +/* Local Variables: */ +/* indent-tabs-mode: nil */ +/* c-basic-offset: 4 */ +/* End: */ +/**********************************************/ +/* vim: set expandtab shiftwidth=4 tabstop=4: */ +/**********************************************/ ADDED minxlib/keymap.cc Index: minxlib/keymap.cc ================================================================== --- minxlib/keymap.cc +++ minxlib/keymap.cc @@ -0,0 +1,139 @@ +/* + @file keymap.cc + @brief Implementation of API defined in keymap.hh. +*/ + +/* + Copyright (C) 2012 The Minx Project Developers + + See wiki/copyright.wiki in the top-level directory of the Minx + source distribution for the full list of authors who have + contributed to this project. +*/ + +/* + This file is part of Minx. + + Minx 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, either version 3 of the License, or + (at your option) any later version. + + Minx 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 Minx. If not, see . +*/ + +//----------------------------- HEADERS -------------------------------- + +// minxlib +#include "keymap.hh" +#include "logging.hh" +#include "util.hh" + +// Xlib +#include + +// Standard C++ +#include +#include + +//-------------------------- IMPLEMENTATION ---------------------------- + +namespace { + +// Module logger +minxlib::logging logger ; + +// Store names of user-defined key bindings in this map, which is +// indexed by combining modmask + keycode keystroke combinations. +std::map kmap ; + +// Which modifiers to consider, which ones to ignore... +uint32_t modmask = 0x00FF ; // default: consider all modifiers + +// Combine keycode k and modifier mask m into a key for above map +uint32_t make_key(KeyCode k, unsigned int m) +{ + uint32_t lo = k & 0xFFFF ; + uint32_t hi = m & modmask; + return ((hi << 16) | lo) ; +} + +} // anonymous namespace + +//--------------------------- PUBLIC API ------------------------------- + +namespace minxlib { +namespace keymap { + +// Insert key binding name s into the keymap +void bind(KeyCode k, unsigned int m, const std::string& s) +{ + using std::hex ; using std::setw ; using std::setfill ; + uint32_t i = make_key(k, m) ; + logger.debug() << "inserting keybinding " << s << " [0x" + << hex << setw(4) << setfill('0') << m << " + 0x" + << hex << setw(4) << setfill('0') << static_cast(k) + << "] at index 0x"<< setfill('0') << setw(8) << hex << i ; + kmap[i] = s ; +} + +// Retrieve key binding name from keymap +std::string get(KeyCode k, unsigned int m) +{ + using std::hex ; using std::setw ; using std::setfill ; + uint32_t i = make_key(k, m) ; + logger.debug() << "looking for keybinding for [0x" + << hex << setw(4) << setfill('0') << m << " + 0x" + << hex << setw(4) << setfill('0') << static_cast(k) + << "] at index 0x"<< setfill('0') << setw(8) << hex << i ; + return kmap.at(i) ; // throws std::out_of_range +} + +// Set modmask so as to ignore any Lock modifiers +uint32_t ignore_lock_modifiers(XModifierKeymap* mod_kmap, Display* d) +{ + const KeySym locks[] = { + #ifdef XK_MISCELLANY + XK_Scroll_Lock, XK_Kana_Lock, XK_Num_Lock, XK_Caps_Lock, XK_Shift_Lock, + #endif + #ifdef XK_XKB_KEYS + XK_ISO_Lock, XK_ISO_Level3_Lock, XK_ISO_Level5_Lock, + XK_ISO_Group_Lock, XK_ISO_Next_Group_Lock, XK_ISO_Prev_Group_Lock, + XK_ISO_First_Group_Lock, XK_ISO_Last_Group_Lock, + #endif + } ; + const int n = sizeof(locks)/sizeof(locks[0]) ; + + uint32_t mask = 0U ; + for(int i = 0; i < n; ++i) + mask |= minxlib::modmask(locks[i], mod_kmap, d) ; + ::modmask = (~mask & 0xFF) ; + return ::modmask ; +} + +void pythonize() +{ + logger = logging::get_logger("keymap") ; +} + +} // namespace keymap +} // namespace minxlib + +//---------------------------------------------------------------------- + +/**********************************************/ +/* Editor config: */ +/**********************************************/ +/* Local Variables: */ +/* indent-tabs-mode: nil */ +/* c-basic-offset: 4 */ +/* End: */ +/**********************************************/ +/* vim: set expandtab shiftwidth=4 tabstop=4: */ +/**********************************************/ ADDED minxlib/keymap.hh Index: minxlib/keymap.hh ================================================================== --- minxlib/keymap.hh +++ minxlib/keymap.hh @@ -0,0 +1,192 @@ +/** + @file keymap.hh + @brief Mapping keyboard events to names of user-defined key bindings. + @defgroup grp_minxlib_keymap Key Bindings + + This file defines an API for keeping track of the names users have + defined for their key bindings and to translate X keyboard events to + those names. +*/ + +/* + Copyright (C) 2012 The Minx Project Developers + + See wiki/copyright.wiki in the top-level directory of the Minx + source distribution for the full list of authors who have + contributed to this project. +*/ + +/* + This file is part of Minx. + + Minx 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, either version 3 of the License, or + (at your option) any later version. + + Minx 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 Minx. If not, see . +*/ + +#ifndef MINXLIB_KEYMAP_DOT_HH +#define MINXLIB_KEYMAP_DOT_HH + +//----------------------------- HEADERS -------------------------------- + +// Xlib +#include + +// Standard C++ +#include + +// Standard C +#include + +//--------------------------- PUBLIC API ------------------------------- + +namespace minxlib { +namespace keymap { + +/** + @ingroup grp_minxlib_keymap + @brief Record a user-defined key binding. + @param k The X keycode for the key binding. + @param m The modifier mask for the key binding. + @param s The name of the key binding. + @return Nothing. + + An important feature of Minx is its support for key bindings. Users + specify functions they want executed in response to particular + keystrokes. For example, a user may want to launch a terminal window + when s/he presses CTRL + ALT + T. This key combination may be + specified as "C-A-T" or "A-C-T". + + Now, when the user presses CTRL + ALT + T, minxlib will have to + translate that keystroke to whatever the user specified. That is, if + s/he configured it as "C-A-T", then minxlib will have to return + "C-A-T" in the key_press event; similarly, if the key binding was + specified as "A-C-T", we will have to return "A-C-T". + + In order to perform the translation described above, we have to + remember the string supplied to minxlib when the passive grabs are + setup for the key bindings (see the implementations of + minx.core.wm.wm.configure_x() and minxlib::window::grab_key() for + the gory details of how key bindings are configured). Then, when we + receive keyboard events from X, we will have to consult the key map + to see if it has an entry corresponding to the pressed (or released) + key and, if so, report the event as a key binding rather than a + simple key press. + + This function inserts the user-defined key binding name + corresponding to the given keycode and modifier mask into the key + map data structure implemented by this module. It is meant to be + used by minxlib::window::grab_key(), i.e., when Minx sets up passive + grabs for all the user-defined (or default) key bindings. +*/ +void bind(KeyCode k, unsigned int m, const std::string& s) ; + +/** + @ingroup grp_minxlib_keymap + @brief Return name of user-defined key binding. + @param k The X keycode for the key binding. + @param m The modifier mask for the key binding. + @return Key binding, if any, for given keycode and modifier mask. + + To make key bindings work, Minx sets up passive keyboard grabs for + the key combinations configured by end-users. Then, when we receive + keyboard events from the X server, we check if the event's keycode + and modifier mask correspond to one of the passive grabs that was + setup earlier on and, if so, report that key binding's user-defined + name as part of the keyboard event. + + Minx's Python core can then use the key binding name to trigger + hooks for that keystroke and not worry about the other low-level + details of the keyboard event. + + This function returns the name of the key binding corresponding to + the supplied keycode and modifier mask. It is meant to be used by + the minxlib::key_event_details constructor, i.e., when we're + creating a minxlib::event for a keyboard event. + + If there is no key binding setup for the given keycode and modifier + mask, this function will throw an std::out_of_range exception. +*/ +std::string get(KeyCode k, unsigned int m) ; + +/** + @ingroup grp_minxlib_keymap + @brief Ignore lock modifiers in key events. + @param m X server's modifier keymap. + @param d X server's interface object. + @return The mask for testing modifiers in key events. + + Lock modifiers mess up the translation from key events to key + binding names. For example, let's say a user has configured Minx to + respond to F1 and S-F1. Let's also say that this user's X modifier + map has NumLock setup as Mod2 and that the NumLock key is on. + + Now, when s/he presses F1 or SHIFT + F1, the resulting key event + will have the Mod2 bit set in its modifier mask, i.e., to Minx, the + key event will look like Mod2 + F1 or Mod2 + SHIFT + F1 rather than + a plain F1 or SHIFT + F1. Consequently, when we combine the key + event's keycode and modifier mask to index the internal map storing + key bindings, we will get an index for a non-existent key binding, + which, of course, means that, while NumLock is on, Minx will not + recognize the F1 or SHIFT + F1 key bindings. + + The long and short of it is that a modifier key that acts as a lock + (Caps Lock, Num Lock, Scroll Lock, etc.) will screw around with key + events in such a way as to cause Minx's key binding mechanism to + fail. Thus, we want to ignore any modifiers that are tied to such + Lock keys. + + This function sets up an internal mask to do just that. It is meant + to be called early on during Minx's initialization sequence. An + ideal time to call it is after connecting to the X server but just + before setting up the passive grabs that will make the key bindings + mechanism work; that's why it's called from + minxlib::window::grab_key(). + + @note This function's return value is meant to be used for + debugging. That is, it doesn't convey anything particularly useful + to its caller; what we want is to see it in Minx's log so we can + double-check using the xmodmap program that the key binding mapping + will work correctly. +*/ +uint32_t ignore_lock_modifiers(XModifierKeymap* m, Display* d) ; + +/** + @ingroup grp_minxlib_keymap + @brief Setup keymap module's logger. + @return Nothing. + + The keymap module does not really have anything exposed to Minx's + Python core via @boostpylink. However, we do use the Python-based + logging infrastructure. That's why we need this "pythonization" + function, which is meant to be called by the Boost.Python + initialization code in python.cc. +*/ +void pythonize() ; + +} // namespace keymap +} // namespace minxlib + +//---------------------------------------------------------------------- + +#endif // #ifndef MINXLIB_KEYMAP_DOT_HH + +/**********************************************/ +/* Editor config: */ +/**********************************************/ +/* Local Variables: */ +/* indent-tabs-mode: nil */ +/* c-basic-offset: 4 */ +/* End: */ +/**********************************************/ +/* vim: set expandtab shiftwidth=4 tabstop=4: */ +/**********************************************/ ADDED minxlib/logging.cc Index: minxlib/logging.cc ================================================================== --- minxlib/logging.cc +++ minxlib/logging.cc @@ -0,0 +1,146 @@ +/* + @file logging.cc + @brief Implementation of API defined in logging.hh. +*/ + +/* + Copyright (C) 2012 The Minx Project Developers + + See wiki/copyright.wiki in the top-level directory of the Minx + source distribution for the full list of authors who have + contributed to this project. +*/ + +/* + This file is part of Minx. + + Minx 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, either version 3 of the License, or + (at your option) any later version. + + Minx 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 Minx. If not, see . +*/ + +//----------------------------- HEADERS -------------------------------- + +// minxlib +#include "logging.hh" + +//-------------------------- INITIALIZATION ---------------------------- + +namespace minxlib { + +// Default constructor (only used to allow global instances in client +// modules prior to Python module initialization). +logging::logging(){} + +// "Main" constructor for initializing logger objects in client modules +logging::logging(const py::object& logger) + : m_logger(logger), + m_log_function(m_logger.attr("log")) +{} + +// The log levels supported by the Python logging module +py::object DEBUG; +py::object INFO ; +py::object WARNING ; +py::object ERROR; +py::object CRITICAL; + +// Initialize a client class's logger object (meant to be called in the +// class's Pythonize function). +logging logging::get_logger(const std::string& class_name) +{ + static bool log_levels_initialized = false ; + + py::object module = py::import ("logging") ; + py::object create = module.attr("getLogger") ; + + // Individual logger objects for different modules are, obviously, + // different logger instances. However, all of them share the same + // logging levels. Instead of creating per-logger-object instances + // of the log level objects, we initialize those objects just once. + if (!log_levels_initialized) { + DEBUG = module.attr("DEBUG"); + INFO = module.attr("INFO") ; + WARNING = module.attr("WARNING") ; + ERROR = module.attr("ERROR"); + CRITICAL = module.attr("CRITICAL"); + log_levels_initialized = true; + } + + return logging(create(std::string("minx.minxlib.") + class_name)) ; +} + +//----------------------- LOGGING INTERFACE ---------------------------- + +// Create a log object to write log messages at the specified level +// using the supplied logger object's logging function. +logging::log::log(py::object& log_function, py::object& log_level) + : m_log(log_function), + m_lvl(log_level) +{} + +// Copy constructor to work around private std::ostringstream copy +// constructor. +logging::log::log(const logging::log& g) + : m_log(g.m_log), + m_lvl(g.m_lvl), + m_msg(g.m_msg.str()) +{} + +// When temporary log object goes out of scope, write the log message +// collected via chained stream output operators to the Minx log via +// Python's standard logging module. +logging::log::~log() +{ + m_log(m_lvl, m_msg.str().c_str()) ; +} + +// Public functions for emitting log messages at different levels +logging::log logging::debug() +{ + return log(m_log_function, DEBUG) ; +} + +logging::log logging::info() +{ + return log(m_log_function, INFO) ; +} + +logging::log logging::warning() +{ + return log(m_log_function, WARNING) ; +} + +logging::log logging::error() +{ + return log(m_log_function, ERROR) ; +} + +logging::log logging::critical() +{ + return log(m_log_function, CRITICAL) ; +} + +} // namespace minxlib + +//---------------------------------------------------------------------- + +/**********************************************/ +/* Editor config: */ +/**********************************************/ +/* Local Variables: */ +/* indent-tabs-mode: nil */ +/* c-basic-offset: 4 */ +/* End: */ +/**********************************************/ +/* vim: set expandtab shiftwidth=4 tabstop=4: */ +/**********************************************/ ADDED minxlib/logging.hh Index: minxlib/logging.hh ================================================================== --- minxlib/logging.hh +++ minxlib/logging.hh @@ -0,0 +1,349 @@ +/** + @file logging.hh + @brief Interface to Python's logging API. + + Since minxlib is basically a Python extension module, it makes sense + to use Python's standard logging module so that log messages from + minxlib can be integrated with the log messages output by Minx's + Python parts. + + This file defines an API that the rest of minxlib can use to emit + log messages. +*/ + +/* + Copyright (C) 2012 The Minx Project Developers + + See wiki/copyright.wiki in the top-level directory of the Minx + source distribution for the full list of authors who have + contributed to this project. +*/ + +/* + This file is part of Minx. + + Minx 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, either version 3 of the License, or + (at your option) any later version. + + Minx 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 Minx. If not, see . +*/ + +#ifndef MINXLIB_LOGGING_DOT_HH +#define MINXLIB_LOGGING_DOT_HH + +//----------------------------- HEADERS -------------------------------- + +// minxlib +#include "python.hh" + +// Standard C++ +#include +#include + +//------------------------- CLASS DEFINITION --------------------------- + +namespace minxlib { + +/** + @brief Interface for logging via Python. + + This class provides an API for the remaining minxlib classes to emit + log messages via Python's standard logging module. This stratgey + allows minxlib's log messages to be integrated with the log messages + output by the rest of Minx and avoid implementing a custom logging + facility within minxlib. + + Additionally, leveraging Python's existing logging functionality + ensures that end-users can configure Minx's logging support in a + uniform way. That is, end-users don't have to be aware of the fact + that minxlib is actually written in C++; to them, all of Minx is a + collection of Python modules and classes whose logging support works + the same. + + This class does not have a Python interface. It is meant to be used + only within minxlib. The following snippet of code illustrates the + typical and intended usage pattern: + + @code + namespace minxlib { + + // Global logger object for some minxlib class + static logging logger ; + + void some_class::pythonize() + { + // After exporting some_class to Python via Boost.Python: + logger = logging::get_logger("some_class") ; + } + + void some_class::some_func() + { + int i = 5 ; + logger.debug() << "the value of i is " << i ; + logger.info () << "this is an informational message" ; + if (something_horrible_has_happened) + logger.critical() << "all hell just broke loose!" ; + } + + } // namespace minxlib + @endcode +*/ +class logging { + // The Python logger object that this class encapsulates + py::object m_logger ; + + // The logger object's log function, which we use to emit log + // messages. + py::object m_log_function ; + +public: + /** + @brief Default constructor. + @return Nothing really. + + This logging API is meant to be used in the following manner: + each minxlib class that needs to emit log messages should define + a static global logger object in its .cc file + and initialize the object in its Pythonize function by calling + this class's get_logger() method. The client class can then use + the logger object to emit log messages at different + levels. + + The default constructor allows "empty" logger objects to be + created at global scope within a .cc file. It has no + other purpose. Therefore, this constructor is not (and should + not be) used in any other situation. + */ + logging() ; + + /** + @brief Create a logger object for the specified class. + @param name minxlib class for which we want a logger. + @return A logger object for the specified class. + + This function returns a logger object that can be used by the + named class to send log messages to the Minx log. It is meant to + be called by the Pythonize functions of the various minxlib + modules that send log messages to the Minx log. The .cc + files for these modules should define a static global + logger object that is initialized by calling this + function in the Pythonize function of the above-mentioned + classes. + + See the sample code in the class description for intended usage + pattern. + */ + static logging get_logger(const std::string& name) ; + +private: + /** + @brief Construct logger object. + @param logger The Python logger object. + @return Properly constructed logger for named class. + + The get_logger() method uses this constructor to return logger + objects to clients. It is responsible for importing the Python + logging module and calling logging.getLogger() to + obtain the desired logger object. get_logger() should then pass + that Python logger object to this constructor so we can store it + for later use. + */ + logging(const py::object& logger) ; + + /** + @brief Interface for emitting log messages. + + This inner class implements a stream output operator so minxlib + classes can send log messages to the Minx log via Python. It is + private to the logging class to ensure that it can only be + created via the appropriate logging functions that are in the + logging class's public interface. + */ + class log { + // Reference to the outer class's logger object's logging + // function. + py::object& m_log ; + + // Reference to the log level Python object (has to be provided + // by the outer class's public logging methods). + py::object& m_lvl ; + + // We will collect the output of chained stream operator + // applications in this string stream and then write it out to + // the Minx log via the above logging function. + std::ostringstream m_msg ; + + public: + /** + @brief Create a log object for the specified level. + @param f Reference to Python logger object's log function. + @param v Reference to Python logging module's level constant. + @return Properly constructed log object for use by C++ classes. + + This class is meant to be instantiated by logging::debug(), + logging::info(), logging::warning(), logging::error(), and + logging::critical(). These functions should supply the outer + class's reference to the Python logger object's log method + and the appropriate log level from the Python logging + module. + */ + log(py::object& f, py::object& v) ; + + /** + @brief Copy constructor. + @param g The source log object that we want to copy. + @return Copy of g. + + std::ostringstream (and, in fact, any STL stream) cannot be + copied. Therefore, we need this copy constructor to be able + to work around that. When logging::info(), logging::debug(), + etc. instantiate this class and then return the resulting + object, this copy constructor will be invoked and it will + take care of copying the internal std::ostringstream from g + to the instance being returned by the logging functions. + + @note Without this explicit copy constructor, the compiler + will generate a default copy constructor for this class, + which will perform a memberwise copy. However, since the + copy constructors of the STL stream classes are private, we + will then end up with a compiler error. + */ + log(const log& g) ; + + /** + @brief Generic stream operator. + @param t The object to be output. + @return Reference to this log object to allow chaining. + + To create log entries, clients should instantiate the + logging::log class (using the logging::warning(), + logging::debug(), etc. functions) and "build" the full + message by chaining multiple calls to this operator. When + the (usually temporary) log object goes out of scope, its + destructor will emit the message via the Python logging + module. + */ + template log& operator<<(const T& t) { + m_msg << t ; + return *this ; + } + + /** + @brief Conversion operator. + @return The internal std::ostringstream object. + + This function converts logging::log objects to + std::ostringstream, which allows clients to simply reuse + their std::ostream output operators and not have to provide + an overload for use with logging::log. + */ + operator std::ostringstream&() {return m_msg ;} + + /** + @brief Write message to Minx log via Python. + + As mentioned earlier, this class is meant to be used as a + temporary. When that temporary object goes out of scope, we + will write the log message collected in the internal + std::ostringstream using the Python logging module. + */ + ~log() ; + } ; // class log + +public: + /** + @brief Write debug messages to Minx log. + @return A log object whose stream operator will emit a log message. + + This function returns an object that you can use to emit debug + messages with the stream output operator. Here is some sample + code that shows how the object returned by this function is + meant to be used: + + @code + logger.debug() << "value of x = " << x ; + @endcode + + In the above snippet, logger will usually be a static + global variable within the client class's .cc file and + it would have been initialized in the client class's Pythonize + function. The sample code in the class description shows more + details. + + @note The object returned by this function is an instance of a + private inner class defined inside the logging class. Therefore, + you cannot store the returned object in a local variable. The + only thing you can do with it is use the stream output operator + as shown above. This ensures that the inner class that takes + care of the details of logging can only be used as a temporary + so that, when it goes out of scope, its destructor can write the + debug message to the Minx log via Python's logging module. + */ + log debug() ; + + /** + @brief Write informational messages to Minx log. + @return A log object whose stream operator will emit a log message. + + This function returns an object that you can use to emit + informational messages with the stream output operator. The + usage pattern is the same as described for the debug() function. + */ + log info() ; + + /** + @brief Write warnings to Minx log. + @return A log object whose stream operator will emit a log message. + + This function returns an object that you can use to emit + warnings with the stream output operator. The usage pattern is + the same as described for the debug() function. + */ + log warning() ; + + /** + @brief Write errors to Minx log. + @return A log object whose stream operator will emit a log message. + + This function returns an object that you can use to emit errors + with the stream output operator. The usage pattern is the same + as described for the debug() function. + */ + log error() ; + + /** + @brief Write critical errors to Minx log. + @return A log object whose stream operator will emit a log message. + + This function returns an object that you can use to emit + messages about critical errors with the stream output operator. + The usage pattern is the same as described for the debug() + function. + */ + log critical() ; +} ; // class logging + +} // namespace minxlib + +//---------------------------------------------------------------------- + +#endif // #ifndef MINXLIB_LOGGING_DOT_HH + +/**********************************************/ +/* Editor config: */ +/**********************************************/ +/* Local Variables: */ +/* indent-tabs-mode: nil */ +/* c-basic-offset: 4 */ +/* End: */ +/**********************************************/ +/* vim: set expandtab shiftwidth=4 tabstop=4: */ +/**********************************************/ ADDED minxlib/python.cc Index: minxlib/python.cc ================================================================== --- minxlib/python.cc +++ minxlib/python.cc @@ -0,0 +1,124 @@ +/* + \file python.cc + \brief Exporting minxlib to Python. + + This file takes care of the details of exposing minxlib's C++ + functionality as a Python module so it can be used by the rest of + Minx, which is implemented in Python. + + DEVNOTE: Minx is implemented mostly in Python. minxlib is a custom + interface to Xlib on which the rest of Minx relies. We decided not + to use python-xlib because that package is poorly documented whereas + the C interface to Xlib has enough documentation to enable us to + write minxlib. + + Another benefit of minxlib is that it serves as an abstraction for + interfacing with a display server. Thus, when things like Wayland + become the method of choice for talking to the display server on + Linux systems, the window management logic in Minx can remain + unchanged. Instead, we would simply reimplement the display server + interface provided by minxlib. +*/ + +/* + Copyright (C) 2012 The Minx Project Developers + + See wiki/copyright.wiki for the full list of authors who have + contributed to this project. +*/ + +/* + This file is part of Minx. + + Minx 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, either version 3 of the License, or + (at your option) any later version. + + Minx 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 Minx. If not, see . +*/ + +//----------------------------- HEADERS -------------------------------- + +// minxlib +#include "display.hh" +#include "root_window.hh" +#include "window.hh" +#include "event.hh" +#include "exception.hh" +#include "keymap.hh" +#ifdef MINXLIB_HAS_VERSION_API +#include "version.hh" +#endif +#include "python.hh" + +//------------------------ PYTHON INTERFACE ---------------------------- + +// Register Boost.Python converters to convert C++ objects to their +// Python equivalents and vice versa (so that things like std::vector +// become Python lists and vice versa). +static void register_converters() +{ + using namespace minxlib ; + + typedef std::vector int_list ; + py::to_python_converter >() ; + + typedef std::vector string_list ; + py::to_python_converter >() ; + + typedef std::vector window_list ; + py::to_python_converter >() ; + + typedef std::vector root_window_list ; + py::to_python_converter >() ; + + typedef std::map window_properties ; + py::to_python_converter >() ; + iterable_converter().from_python() ; +} + +// Standard Boost.Python idiom for exporting C++ classes and functions +// as part of a Python module. In this case, we expose the relevant +// parts of minxlib as a Python module named "minxlib". +// +// DEVNOTE: The order in which we call the initialization functions +// matters! For example, since root_window is derived from window, we +// must setup the window class's Python wrapper before root_window. +BOOST_PYTHON_MODULE(minxlib) +{ + register_converters() ; + + using namespace minxlib ; + exception ::pythonize(); + display ::pythonize(); + window ::pythonize(); + root_window::pythonize(); + event ::pythonize(); + keymap ::pythonize(); +#ifdef MINXLIB_HAS_VERSION_API + version ::pythonize(); +#endif +} + +//---------------------------------------------------------------------- + +/**********************************************/ +/* Editor config: */ +/**********************************************/ +/* Local Variables: */ +/* indent-tabs-mode: nil */ +/* c-basic-offset: 4 */ +/* End: */ +/**********************************************/ +/* vim: set expandtab shiftwidth=4 tabstop=4: */ +/**********************************************/ ADDED minxlib/python.hh Index: minxlib/python.hh ================================================================== --- minxlib/python.hh +++ minxlib/python.hh @@ -0,0 +1,408 @@ +/* + @file python.hh + @brief Useful stuff for using Boost.Python. + + This file defines some functions and classes that can be used to + implement Boost.Python converters. It also suppresses spurious + warnings that emanate from boost/python.hpp. + + @note This file is only used inside of minxlib. That's why it is not + documented with Doxygen (this comment starts with a single asterisk + rather than two). Of course, we do have explanatory comments, and we + even use Doxygen mark-up; however, this does not show up in the Minx + API docs. +*/ + +/* + Copyright (C) 2012 The Minx Project Developers + + See wiki/copyright.wiki in the top-level directory of the Minx + source distribution for the full list of authors who have + contributed to this project. +*/ + +/* + This file is part of Minx. + + Minx 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, either version 3 of the License, or + (at your option) any later version. + + Minx 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 Minx. If not, see . +*/ + +#ifndef MINXLIB_PYTHON_DOT_HH +#define MINXLIB_PYTHON_DOT_HH + +//---------------------------- HEADERS --------------------------------- + +/* + minxlib is built with -Wextra, which, among other warnings, turns on + the "enumeral and non-enumeral type in conditional expression" + complaint when compiling boost/python.hpp. + + We use a pragma that makes the compiler treat this file as a system + header, which suppresses the above-mentioned annoying and useless + warning, thereby avoiding unnecessary clutter in the build log. All + minxlib modules that need boost/python.hpp should instead include + this file. + + DEVNOTE: Once g++ is able to compile boost/python.hpp without any + warnings (either because the compiler gets it right or because Boost + does), this pragma should become unnecessary. +*/ +#ifdef __GNUC__ +#pragma GCC system_header +#endif + +// Now include the Boost header that causes the pain... +#include +#include + +// Other Boost headers +#include +#include +#include + +// Standard C++ +#include +#include +#include +#include + +//----------------------------- NAMESPACE ------------------------------- + +namespace minxlib { + +// This namespace alias is used throughout minxlib. So, instead of +// repeating it in each .cc file, just put it here. +namespace py = boost::python ; + +//----------------------------- TO PYTHON ------------------------------- + +/* + Converters to turn C++ objects into their Python equivalents. More + info at: + + General/Tutorial Explanation of Boost.Python Converters: + http://misspent.wordpress.com/2009/09/27/how-to-write-boost-python-converters/ + + Tuple Conversion: + http://mail.python.org/pipermail/cplusplus-sig/2006-May/010324.html +*/ +namespace { + +// Base case for tuples::cons recursion when we want to convert from C++ +// tuples to Python tuples. +py::tuple to_python(boost::tuples::null_type) +{ + return py::tuple() ; +} + +// General function for converting Boost tuples to Python equivalents +// using the implicit conversion from a tuple to a cons "cell." +template +py::object to_python(const boost::tuples::cons& cons) +{ + return py::make_tuple(cons.get_head()) + to_python(cons.get_tail()) ; +} + +// Generic function for converting an STL vector into a Python list +template +py::object to_python(const std::vector& v) +{ + py::list list ; + BOOST_FOREACH(const T& t, v) + list.append(t) ; + return list ; +} + +// Generic funtion for converting an STL map into a Python dict +template +py::object to_python(const std::map& m) +{ + typedef typename std::map::value_type P ; + py::dict dict ; + BOOST_FOREACH(const P& p, m) + dict[p.first] = p.second ; + return dict ; +} + +} // namespace + +/* + @brief Converter for turning C++ objects into their Python equivalents. + + This handy template is meant to be used in conjunction with + boost::python::to_python_converter(). It relies on overloaded + versions of a to_python() helper function to works its magic. That + is, for each distinct type we want to convert, we will have to + provide a suitable definition of to_python() that takes an instance + of that type as a parameter and returns the Python equivalent. + + So far, this file defines versions of to_python() that perform the + following conversions: + + @li STL vectors to Python lists + @li STL maps to Python dicts + @li Boost tuples to Python tuples + + Please see display::pythonize() and window::pythonize() for examples + of how this converter is meant to be used. +*/ +template +struct universal_converter { + static PyObject* convert(const T& t) { + return py::incref(to_python(t).ptr()) ; + } +} ; + +//---------------------------- FROM PYTHON ------------------------------ + +/* + @brief A key-value iterator for converting Python dicts to STL maps. + + Boost.Python's stl_input_iterator iterates over a Python container by + using its __iter__ method. Unfortunately, for dicts, __iter__ + iterates only over the keys. Thus, trying to in-place construct an + STL map (see the construct() function in the iterable_converter + defined later in this file) doesn't work because an STL map stores + key-value pairs. Consequently, when the iterable_converter + dereferences a dict's iterator, we need to return an STL pair + containing both the key and the corresponding value that can be + inserted into the STL map being built from the Python dict. + + This class implements an adaptor for Boost.Python's + stl_input_iterator to achieve the dereferencing effect described + above. + + The type M is expected to be an std::map and it should be the same as + the type C passed to iterable_converter::construct(). +*/ +template +class stl_map_iterator: public boost::iterator_adaptor< + stl_map_iterator, // standard boost::iterator_adaptor idiom + py::stl_input_iterator, // base iter. + typename M::value_type, // should be std::pair + std::forward_iterator_tag, // iterator category + typename M::value_type> // ref. type (make new pair, not ret. ref.) +{ + // When it is derefenced, the base iterator type, viz., the + // Boost.Python stl_input_iterator that this class adapts, will + // return a key belonging to its underlying Python dict. In order to + // be able to convert that to a key-value pair, we need the + // underlying Python container so we can look up the values + // corresponding to keys. + py::object m_dict ; + + // STL map's key and value types (they're basically std::pair objects) + typedef typename M::value_type:: first_type key_type ; + typedef typename M::value_type::second_type val_type ; + + // Handy shortcut for referring to the base iterator type that this + // class is adapting. + typedef py::stl_input_iterator base_type ; + +public: + /** + @brief Iterator to the beginning of the underlying container. + @param d The Python dict we want to convert to an STL map. + @param i The Boost.Python stl_input_iterator for the STL map. + @return Iterator to beginning of STL map being constructed from Py dict. + + This constructor uses the Boost.Python stl_input_iterator and its + underlying Python dict to make a suitable iterator for the + corresponding C++ map that we will build with the + iterable_converter defined later in this file. + */ + stl_map_iterator(const py::object& d, base_type& i) ; + + /** + @brief Iterator to end of STL map being built from Python dict. + @return Iterator to end of STL map. + + This constructor builds the iterator returned by the end() + function in C++ container classes, i.e., it is the iterator to + one past the end of the map that will be made by the + iterable_converter class from a Python dict. + */ + stl_map_iterator() ; + +private: + // Move to next element in sequence (standard Boost iterator adaptor idiom) + void increment() {++this->base_reference() ;} + + /** + @brief Return key-value pair for STL map from Python dict's key. + @return Key-value pair corresponding to a key. + + As mentioned above, Boost.Python's stl_input_iterator, when + applied to a dict, will return only a key. However, in the + corresponding STL map, we want to insert a key-value pair. This + function is where this map iterator works its magic... + + It first dereferences the base iterator (i.e., the + stl_input_iterator it is adapting) to get the key from the Python + dict. After that, it looks up that key in the Python dict to get + the value corresponding to the key. Finally, it returns an STL + pair combining the key and value. + */ + typename M::value_type dereference() const ; + + // Standard Boost.Iterator idiom + friend class boost::iterator_core_access ; +} ; + +// Beginning of container +template +stl_map_iterator::stl_map_iterator(const py::object& d, base_type& i) + : stl_map_iterator::iterator_adaptor_(i), + m_dict(d) +{} + +// End of container +template +stl_map_iterator::stl_map_iterator() +{} + +// Convert key to key-value pair +template +typename M::value_type stl_map_iterator::dereference() const +{ + key_type key = *(this->base_reference()) ; + val_type val = py::extract(m_dict[key]) ; + return std::make_pair(key, val) ; +} + +/* + @brief Convert Python iterable objects to their C++ equivalents. + + This class provides an API for registering a Boost.Python converter + for converting Python lists, tuples, dicts, etc. to C++ equivalents. + It is meant to be used when we want to expose a C++ function that + takes a list, map, etc. to Python and want its Python interface to + accept the Python equivalent. + + @note This code is lifted almost verbatim from the following Stack + Overflow post: + + http://stackoverflow.com/questions/15842126/feeding-a-python-list-into-a-function-taking-in-a-vector-with-boost-python + + @par + However, the support for converting Python dicts to STL maps is a + Minx-specific enhancement. +*/ +struct iterable_converter { + /* + @brief Register a Boost.Python converter to convert to C++ type C. + @return Ref. to this object to allow chaining calls to from_python(). + + This function registers a Boost.Python converter that will + produce a C++ object of type C given its Python equivalent. + Typically, the Python object will be a list or a dict and the + type C will be an STL container such as list or map. + + You should call this function as part of the Boost.Python module + initialization sequence. In Minx, this would be in the + pythonize() function of a class being exposed to Python. For + example, if you have a C++ function that takes an STL list of + integers and you want to expose it to Python so that it can + accept a Python list of ints, in the corresponding pythonize() + function you would do something like this: + + @code + iterable_converter().from_python >() ; + @endcode + + Continuing the above example, if you have another function that + takes an STL map of ints to strings, the pythonize() function + would be as follows: + + @code + iterable_converter().from_python >(). + from_python >() ; + @endcode + */ + template + iterable_converter& from_python() { + py::converter::registry::push_back(&iterable_converter::convertible, + &iterable_converter::construct, + py::type_id()) ; + return *this ; + } + +private: + // Check if supplied Python object supports Python's iterator protocol + static void* convertible(PyObject* obj) { + return PyObject_GetIter(obj) ? obj : 0 ; + } + + /* + @brief Convert Python iterable object into C++ type C. + @param obj The Python iterable object to be converted. + @param dat Raw memory area for in-place construction of C. + @return C++ container C via parameter dat. + + This function performs the Boost.Python black magic that iterates + over the Python container obj and constructs the requested C++ + container (type C). Typically, C will be an STL list, vector, or + map. + */ + template + static void construct(PyObject* obj, + py::converter::rvalue_from_python_stage1_data* dat) + { + // For in-place construction of C++ container in the raw memory + // area provided by Boost.Python, we need to be able view the raw + // bytes as an object of type C... + typedef py::converter::rvalue_from_python_storage storage_type ; + void* storage = reinterpret_cast(dat)->storage.bytes ; + + // Python ref-count book-keeping + py::handle<> handle(py::borrowed(obj)) ; + py::object iterable(handle) ; + + // Converting a Python dict to an STL map needs special handling + // because Boost.Python doesn't seem to provide a ready-made + // (key, value) iterator, which requires us to roll our own... + if (PyDict_Check(obj)) + { + typedef typename C::value_type::first_type key_type ; + typedef py::stl_input_iterator key_iterator ; + typedef stl_map_iterator iterator ; + key_iterator begin = key_iterator(iterable) ; + dat->convertible = new(storage) C(iterator(iterable, begin), + iterator()) ; + } + else // Boost.Python takes care of flat lists and tuples without a hitch + { + typedef py::stl_input_iterator iterator ; + dat->convertible = new(storage) C(iterator(iterable), + iterator()) ; + } + } +} ; + +//----------------------------------------------------------------------- + +} // namespace minxlib + +#endif // #ifndef MINXLIB_PYTHON_DOT_HH + +/**********************************************/ +/* Editor config: */ +/**********************************************/ +/* Local Variables: */ +/* indent-tabs-mode: nil */ +/* c-basic-offset: 4 */ +/* End: */ +/**********************************************/ +/* vim: set expandtab shiftwidth=4 tabstop=4: */ +/**********************************************/ ADDED minxlib/root_window.cc Index: minxlib/root_window.cc ================================================================== --- minxlib/root_window.cc +++ minxlib/root_window.cc @@ -0,0 +1,223 @@ +/* + @file root_window.cc + @brief Implementation of API defined in root_window.hh. +*/ + +/* + Copyright (C) 2012 The Minx Project Developers + + See wiki/copyright.wiki in the top-level directory of the Minx + source distribution for the full list of authors who have + contributed to this project. +*/ + +/* + This file is part of Minx. + + Minx 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, either version 3 of the License, or + (at your option) any later version. + + Minx 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 Minx. If not, see . +*/ + +//----------------------------- HEADERS -------------------------------- + +// minxlib +#include "root_window.hh" +#include "logging.hh" +#include "python.hh" + +// Xlib +#include + +// Standard C++ +#include +#include +#include + +//-------------------------- INITIALIZATION ---------------------------- + +namespace minxlib { + +static logging logger ; + +// Quick helper to dump geometry to Minx log +static void dump_geometry(const std::vector& geom, Window id, int screen) +{ + std::ostringstream str ; + std::copy(geom.begin(), geom.end(), std::ostream_iterator(str, " ")) ; + logger.debug() << "screen " << screen << "'s root window (" << id + << ") geometry = [" << str.str() << ']' ; +} + +// Root window constructor when not using Xinerama +root_window::root_window(Display* d, int s) + : window(d, RootWindow(d, s)), + m_screen(s), + m_geom(window::geometry()) +{ + dump_geometry(m_geom, m_id, m_screen) ; +} + +#ifdef MINXLIB_HAS_XINERAMA +// Root window constructor when using Xinerama +root_window::root_window(Display* d, int s, const XineramaScreenInfo& i) + : window(d, RootWindow(d, 0)), + m_screen(s) +{ + logger.info() << "setting screen " << s + << "'s root window ("<< m_id + << ") geometry from Xinerama"; + + m_geom.reserve(5) ; + m_geom.push_back(i.x_org) ; + m_geom.push_back(i.y_org) ; + m_geom.push_back(i.width) ; + m_geom.push_back(i.height); + m_geom.push_back(0) ; + + dump_geometry(m_geom, m_id, m_screen) ; +} +#endif // MINXLIB_HAS_XINERAMA + +//--------------------------- PROPERTIES ------------------------------ + +void root_window::set_properties(const std::map&) +{ + logger.warning() << "attempting to set properties on screen " + << m_screen << "'s root window " << m_id ; +} + +//-------------------- PARENT-CHILD RELATIONSHIPS ----------------------- + +void root_window::reparent(const window&) +{ + logger.warning() << "attempting to reparent screen " << m_screen + << "'s root window " << m_id ; +} + +window root_window::parent() +{ + logger.warning() << "attempting to get parent of screen " << m_screen + << "'s root window " << m_id ; + return window(m_display, 0) ; +} + +int root_window::screen() +{ + return m_screen ; +} + +//----------------------- WINDOW VISIBILITY ---------------------------- + +void root_window::show() +{ + logger.warning() << "attempting to show screen " << m_screen + << "'s root window " << m_id ; +} + +void root_window::hide() +{ + logger.warning() << "attempting to hide screen " << m_screen + << "'s root window " << m_id ; +} + +bool root_window::is_mapped() +{ + return true ; +} + +//------------------------ WINDOW GEOMETRY ----------------------------- + +void root_window::move_resize(int, int, int, int) +{ + logger.warning() << "attempting to move/resize screen " << m_screen + << "'s root window " << m_id ; +} + +void +root_window:: +configure(int, int, int, int, int, const window*, int, unsigned int) +{ + logger.warning() << "attempting to configure screen " << m_screen + << "'s root window " << m_id ; +} + +void root_window::set_border_attr(unsigned long, unsigned int) +{ + logger.warning() << "attempting to set border attributes on screen " + << m_screen << "'s root window " << m_id ; +} + +std::vector root_window::geometry() +{ + return m_geom ; +} + +//---------------------------- INPUT FOCUS ------------------------------ + +void root_window::focus() +{ + logger.warning() << "attempting to focus screen " << m_screen + << "'s root window " << m_id ; +} + +//----------------------- WINDOW DESTRUCTION --------------------------- + +void root_window::kill() +{ + logger.warning() << "attempting to kill screen " << m_screen + << "'s root window " << m_id ; +} + +void root_window::nuke() +{ + logger.warning() << "attempting to nuke screen " << m_screen + << "'s root window " << m_id ; +} + +//------------------------ PYTHON INTERFACE ---------------------------- + +void root_window::pythonize() +{ + py::class_ >("root_window", py::no_init). + def("set_properties", &root_window::set_properties ). + def("reparent", &root_window::reparent ). + def("parent", &root_window::parent ). + def("screen", &root_window::screen ). + def("show", &root_window::show ). + def("hide", &root_window::hide ). + def("is_mapped", &root_window::is_mapped ). + def("move_resize", &root_window::move_resize ). + def("configure", &root_window::configure ). + def("set_border_attr", &root_window::set_border_attr). + def("geometry", &root_window::geometry ). + def("focus", &root_window::focus ). + def("kill", &root_window::kill ). + def("nuke", &root_window::nuke ); + + logger = logging::get_logger("root_window") ; +} + +} // namespace minxlib + +//---------------------------------------------------------------------- + +/**********************************************/ +/* Editor config: */ +/**********************************************/ +/* Local Variables: */ +/* indent-tabs-mode: nil */ +/* c-basic-offset: 4 */ +/* End: */ +/**********************************************/ +/* vim: set expandtab shiftwidth=4 tabstop=4: */ +/**********************************************/ ADDED minxlib/root_window.hh Index: minxlib/root_window.hh ================================================================== --- minxlib/root_window.hh +++ minxlib/root_window.hh @@ -0,0 +1,309 @@ +/** + @file root_window.hh + @brief Encapsulation of an X root window. + + This file defines a class that wraps around the notion of an X + root window and provides a Python interface for the rest of Minx. +*/ + +/* + Copyright (C) 2012 The Minx Project Developers + + See wiki/copyright.wiki in the top-level directory of the Minx + source distribution for the full list of authors who have + contributed to this project. +*/ + +/* + This file is part of Minx. + + Minx 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, either version 3 of the License, or + (at your option) any later version. + + Minx 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 Minx. If not, see . +*/ + +#ifndef MINXLIB_ROOT_WINDOW_DOT_HH +#define MINXLIB_ROOT_WINDOW_DOT_HH + +//----------------------------- HEADERS -------------------------------- + +// Minx +#include "window.hh" + +// Xlib +#ifdef MINXLIB_HAS_XINERAMA +#include +#endif + +// Standard C++ +#include + +//------------------------- CLASS DEFINITION --------------------------- + +namespace minxlib { + +/** + @brief Encapsulate the details of an X root window. + + This class provides an API for the Python parts of Minx to be able to + deal with X root windows. It wraps around the relevant parts of Xlib + and exposes its functionality to Python via @boostpylink. + + Although we could just represent a root window as just another + minxlib::window, we have a dedicated subclass for root windows to be + able to deal with Xinerama. When Xinerama is active, a multi-head + setup will have just one root window. However, to make Minx's layout + functionality work properly across multiple physical monitors, + minxlib always presents as many root windows as there are physical + monitors. With Xinerama, these root windows will have the same window + ID, but will have different geometries. +*/ +class root_window: public window { + // When we create a root window, we specify which screen it is on. + int m_screen ; + + // Root windows have fixed geometry. + std::vector m_geom ; + +public: + /** + @brief Create a wrapper object for a root window. + @param d The display object to which the window is "linked." + @param s The zero-based screen index whose root window we want. + @return A valid wrapper object for the specified screen's root window. + + This constructor is meant to be used when Xinerama is not active. + In multi-head setups without Xinerama, the X server will be + configured with independent displays. + */ + root_window(Display* d, int s) ; + +#ifdef MINXLIB_HAS_XINERAMA + /** + @brief Create a wrapper object for a root window with Xinerama active. + @param d The display object to which the window is "linked." + @param s The zero-based screen index whose root window we want. + @param i The Xinerama screen info structure for the specified screen. + @return A valid wrapper object for the specified screen's root window. + + This constructor is meant to be used when Xinerama is active. It + is the caller's responsibility to obtain the XineramaScreenInfo + structures by calling XineramaQueryScreens() and then freeing + them once all the necessary root window objects have been + created. + */ + root_window(Display* d, int s, const XineramaScreenInfo& i) ; +#endif + + /** + @brief Export the window class to minxlib Python module. + @return Nothing. + + This function exposes the root window class's interface so that + it can be used by the Python parts of Minx. It is meant to be + called by the Boost.Python initialization code in python.cc. + */ + static void pythonize() ; + + /** + @brief Set window properties. + @param prop An STL map of strings to strings. + @return Nothing. + + This is an override of minxlib::window::set_properties(). minxlib + does not allow root window properties to be set. Thus, this + implementation simply logs a warning to the Minx log and does + nothing else. + */ + void set_properties(const std::map& prop) ; + + /** + @brief Reparent this window to another. + @param p New parent window. + @return Nothing. + + This is an override of minxlib::window::reparent(). minxlib does + not allow root windows to be reparented. Thus, this + implementation simply logs a warning to the Minx log and does + nothing else. + */ + void reparent(const window& p) ; + + /** + @brief Get this root window's parent window. + @return Window with ID zero. + + This is an override of minxlib::window::parent(). minxlib does + not allow querying root windows for their parents. Thus, this + implementation simply logs a warning to the Minx log and returns + a window object with ID zero. + */ + window parent() ; + + /** + @brief Get screen number of this root window. + @return Root window's screen number. + + This function simply returns the screen index used when this root + window object was created. + */ + int screen() ; + + /** + @brief Show the window, i.e., map it. + @return Nothing. + + This is an override of minxlib::window::show(). Since root + windows are always visible, this function simply logs a warning + to the Minx log. + */ + void show() ; + + /** + @brief Hide this window, i.e., unmap it. + @return Nothing. + + This is an override of minxlib::window::hide(). Since root + windows are always visible and cannot be unmapped, this function + simply logs a warning to the Minx log. + */ + void hide() ; + + /** + @brief Check if root window is currently mapped or not. + @return True (root windows are always mapped). + */ + bool is_mapped() ; + + /** + @brief Move and resize the window. + @param x Window's x coordinate relative to parent's origin. + @param y Window's y coordinate relative to parent's origin. + @param w Window's width (not counting its border). + @param h Window's height (not counting its border). + @return Nothing. + + This is an override of minxlib::window::move_resize(). minxlib + does not allow root windows to be moved and/or resized. Thus, + this implementation simply logs a warning to the Minx log and + does nothing else. + */ + void move_resize(int x, int y, int w, int h) ; + + /** + @brief Configure the window. + @param x Window's x coordinate relative to parent's origin. + @param y Window's y coordinate relative to parent's origin. + @param w Window's width (not counting its border). + @param h Window's height (not counting its border). + @param b Window's border width. + @param s Window's sibling for stacking operations. + @param t Window's stacking mode. + @param v Value mask to determine what to configure. + @return Nothing. + + This is an override of minxlib::window::configure(). minxlib does + not allow root windows to be configured. Thus, this + implementation simply logs a warning to the Minx log and does + nothing else. + */ + void configure(int x, int y, int w, int h, int b, + const window* s, int t, unsigned int v) ; + + /** + @brief Set window's border color and size. + @param c Three-byte RGB spec. + @param s Border size (in pixels). + @return Nothing. + + This is an override of minxlib::window::set_border_attr(). + minxlib does not allow root window borders to be changed. Thus, + this implementation simply logs a warning to the Minx log and + does nothing else. + */ + void set_border_attr(unsigned long c, unsigned int s) ; + + /** + @brief Retrieve window's size, position, and border width. + @return STL vector of ints containing window geometry. + + Root windows have a fixed geometry. When a root window object is + constructed, we determine its geometry and store that in a data + member. This function simply returns the above-mentioned data + member, which is an STL vector of integers containing the + following five values: + + @li Element 0: x-coordinate of root window's top-left corner + @li Element 1: y-coordinate of root window's top-left corner + @li Element 2: root window width + @li Element 3: root window height + @li Element 4: root window's border width (always zero) + + @note For non-Xinerama setups, this function simply calls + minxlib::window::geometry(), which, in turn, calls + XGetGeometry(). If the call to XGetGeometry() fails, this + function will return an empty vector (and, eventually, the X + server will raise a protocol error). + + @par + On the Python side, the STL vector returned by this function + will be converted into a Python list. + */ + std::vector geometry() ; + + /** + @brief Set input focus on this root window and raise it. + @return Nothing. + + This is an override of minxlib::window::focus(). minxlib does not + allow root windows to be focused. Thus, this implementation + simply logs a warning to the Minx log and does nothing else. + */ + void focus() ; + + /** + @brief Kill this window. + @return Nothing. + + This is an override of minxlib::window::kill(). minxlib does not + allow root windows to be killed. Thus, this implementation simply + logs a warning to the Minx log and does nothing else. + */ + void kill() ; + + /** + @brief Kill this window using brute force. + @return Nothing. + + This is an override of minxlib::window::nuke(). minxlib does not + allow root windows to be killed. Thus, this implementation simply + logs a warning to the Minx log and does nothing else. + */ + void nuke() ; +} ; // class window + +} // namespace minxlib + +//---------------------------------------------------------------------- + +#endif // #ifndef MINXLIB_ROOT_WINDOW_DOT_HH + +/**********************************************/ +/* Editor config: */ +/**********************************************/ +/* Local Variables: */ +/* indent-tabs-mode: nil */ +/* c-basic-offset: 4 */ +/* End: */ +/**********************************************/ +/* vim: set expandtab shiftwidth=4 tabstop=4: */ +/**********************************************/ ADDED minxlib/type_info.cc Index: minxlib/type_info.cc ================================================================== --- minxlib/type_info.cc +++ minxlib/type_info.cc @@ -0,0 +1,70 @@ +/* + \file type_info.cc + \brief Non-inline functions of minxlib::type_info. +*/ + +/* + Copyright (C) 2012 The Minx Project Developers + + See wiki/copyright.wiki for the full list of authors who have + contributed to this project. +*/ + +/* + This file is part of Minx. + + Minx 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, either version 3 of the License, or + (at your option) any later version. + + Minx 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 Minx. If not, see . +*/ + +//----------------------------- HEADERS -------------------------------- + +// minxlib +#include "type_info.hh" + +//-------------------------- INITIALIZATION ----------------------------- + +namespace minxlib { + +// Default constructor +type_info::type_info() +{ + class empty {} ; + m_type_info = &typeid(empty) ; +} + +// Convert std::type_info to wrapped type_info +type_info::type_info(const std::type_info& t) + : m_type_info(&t) +{} + +// Stream output operator for use with boost::lexical_cast +std::ostream& operator<<(std::ostream& os, const type_info& t) +{ + return os << t.m_type_info->name() ; +} + +} // namespace minxlib + +//---------------------------------------------------------------------- + +/**********************************************/ +/* Editor config: */ +/**********************************************/ +/* Local Variables: */ +/* indent-tabs-mode: nil */ +/* c-basic-offset: 4 */ +/* End: */ +/**********************************************/ +/* vim: set expandtab shiftwidth=4 tabstop=4: */ +/**********************************************/ ADDED minxlib/type_info.hh Index: minxlib/type_info.hh ================================================================== --- minxlib/type_info.hh +++ minxlib/type_info.hh @@ -0,0 +1,172 @@ +/** + @file type_info.hh + @brief Wrapper around std::type_info. + + As pointed out by Andrei Alexandrescu in "Modern C++ Design," using + std::type_info::name() to uniquely and reliably identify different + classes is usually a bad idea. Instead, we use the wrapper around + std::type_info defined in this file. +*/ + +/* + Copyright (C) 2012 The Minx Project Developers + + See wiki/copyright.wiki for the full list of authors who have + contributed to this project. +*/ + +/* + This file is part of Minx. + + Minx 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, either version 3 of the License, or + (at your option) any later version. + + Minx 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 Minx. If not, see . +*/ + +#ifndef MINXLIB_TYPE_INFO_DOT_HH +#define MINXLIB_TYPE_INFO_DOT_HH + +//----------------------------- HEADERS -------------------------------- + +// Standard C++ +#include +#include + +//------------------------- CLASS DEFINITION ---------------------------- + +namespace minxlib { + +/** + @brief Wrapper around std::type_info. + + Because std::type_info::name() is unreliable, we can use this wrapper + class when we want to identify different classes in a unique way so + as to be able to use class typeid's in conjunction with object + factories and other such constructions. + + The only thing you can do with minxlib::type_info objects are create + them and compare them using the relational operators (i.e., ==, !=, + <, >, <=, and >=). +*/ +class type_info { + // Can't copy std::type_info, but can store it via a pointer. See + // chapter two of "Modern C++ Design" by Andrei Alexandrescu. + const std::type_info* m_type_info ; + +public: + /** + @brief Default constructor for std::type_info wrapper. + @return std::type_info wrapper object. + + A default constructor so we can store type_info wrapper objects + in STL containers. + */ + type_info() ; + + /** + @brief Construct type_info wrapper given std::type_info. + @param t Result of typeid operator on some object or type. + @return std::type_info wrapper object. + */ + type_info(const std::type_info& t) ; + + // Some operators need access to the wrapper object's innards + friend bool operator==(const type_info&, const type_info&) ; + friend bool operator< (const type_info&, const type_info&) ; + friend std::ostream& operator<<(std::ostream&, const type_info&) ; +} ; + +//------------------------- INLINE FUNCTIONS ---------------------------- + +/** + @brief Check if two type_info objects are equal. + @param a LHS of equality operator. + @param b RHS of equality operator. + @return True if a == b, false otherwise. +*/ +inline bool operator==(const type_info& a, const type_info& b) +{ + return *(a.m_type_info) == *(b.m_type_info) ; +} + +/** + @brief Check if two type_info objects are not equal. + @param a LHS of inequality operator. + @param b RHS of inequality operator. + @return True if a != b, false if a == b. +*/ +inline bool operator!=(const type_info& a, const type_info& b) +{ + return !(a == b) ; +} + +/** + @brief Check if one type_info object less than another. + @param a LHS of < operator. + @param b RHS of < operator. + @return True if a < b, false if a >= b. +*/ +inline bool operator< (const type_info& a, const type_info& b) +{ + return a.m_type_info->before(*(b.m_type_info)) ; +} + +/** + @brief Check if one type_info object greater than another. + @param a LHS of > operator. + @param b RHS of > operator. + @return True if a > b, false if a <= b. +*/ +inline bool operator> (const type_info& a, const type_info& b) +{ + return b < a ; +} + +/** + @brief Check if one type_info object less than or equal to another. + @param a LHS of <= operator. + @param b RHS of <= operator. + @return True if a <= b, false if a > b. +*/ +inline bool operator<=(const type_info& a, const type_info& b) +{ + return !(a > b) ; +} + +/** + @brief Check if one type_info object greater than or equal to another. + @param a LHS of >= operator. + @param b RHS of >= operator. + @return True if a >= b, false if a < b. +*/ +inline bool operator>=(const type_info& a, const type_info& b) +{ + return !(a < b) ; +} +///@} + +} // namespace minxlib + +//---------------------------------------------------------------------- + +#endif // #ifndef MINXLIB_TYPE_INFO_DOT_HH + +/**********************************************/ +/* Editor config: */ +/**********************************************/ +/* Local Variables: */ +/* indent-tabs-mode: nil */ +/* c-basic-offset: 4 */ +/* End: */ +/**********************************************/ +/* vim: set expandtab shiftwidth=4 tabstop=4: */ +/**********************************************/ ADDED minxlib/util.cc Index: minxlib/util.cc ================================================================== --- minxlib/util.cc +++ minxlib/util.cc @@ -0,0 +1,270 @@ +/* + \file util.cc + \brief Implementation of API defined in util.hh. +*/ + +/* + Copyright (C) 2012 The Minx Project Developers + + See wiki/copyright.wiki in the top-level directory of the Minx + source distribution for the full list of authors who have + contributed to this project. +*/ + +/* + This file is part of Minx. + + Minx 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, either version 3 of the License, or + (at your option) any later version. + + Minx 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 Minx. If not, see . +*/ + +//----------------------------- HEADERS -------------------------------- + +// minxlib +#include "util.hh" + +// Xlib +#include + +// Standard C++ +#include +#include + +//----------------------- Xlib ERROR PRINTING -------------------------- + +namespace minxlib { + +// Helper macro to simplify switch-case construct in to_str() function. +// Takes the symbolic names for error codes and converts them to +// equivalent strings. +#define STRINGIZE_CASE_LABEL(e, s) case e: s << #e ; break ; + +// Return a string representation of X protocol errors +std::string to_str(const XErrorEvent* e) +{ + std::ostringstream str ; + str << "X protocol error [" ; + switch (e->error_code) + { + STRINGIZE_CASE_LABEL(BadAccess, str) ; + STRINGIZE_CASE_LABEL(BadAlloc, str) ; + STRINGIZE_CASE_LABEL(BadAtom, str) ; + STRINGIZE_CASE_LABEL(BadColor, str) ; + STRINGIZE_CASE_LABEL(BadCursor, str) ; + STRINGIZE_CASE_LABEL(BadDrawable, str) ; + STRINGIZE_CASE_LABEL(BadFont, str) ; + STRINGIZE_CASE_LABEL(BadGC, str) ; + STRINGIZE_CASE_LABEL(BadIDChoice, str) ; + STRINGIZE_CASE_LABEL(BadImplementation, str) ; + STRINGIZE_CASE_LABEL(BadLength, str) ; + STRINGIZE_CASE_LABEL(BadMatch, str) ; + STRINGIZE_CASE_LABEL(BadName, str) ; + STRINGIZE_CASE_LABEL(BadPixmap, str) ; + STRINGIZE_CASE_LABEL(BadRequest, str) ; + STRINGIZE_CASE_LABEL(BadValue, str) ; + STRINGIZE_CASE_LABEL(BadWindow, str) ; + } + str << ' ' ; + switch (e->request_code) + { + STRINGIZE_CASE_LABEL(X_CreateWindow, str) ; + STRINGIZE_CASE_LABEL(X_ChangeWindowAttributes, str) ; + STRINGIZE_CASE_LABEL(X_GetWindowAttributes, str) ; + STRINGIZE_CASE_LABEL(X_DestroyWindow, str) ; + STRINGIZE_CASE_LABEL(X_DestroySubwindows, str) ; + STRINGIZE_CASE_LABEL(X_ChangeSaveSet, str) ; + STRINGIZE_CASE_LABEL(X_ReparentWindow, str) ; + STRINGIZE_CASE_LABEL(X_MapWindow, str) ; + STRINGIZE_CASE_LABEL(X_MapSubwindows, str) ; + STRINGIZE_CASE_LABEL(X_UnmapWindow, str) ; + STRINGIZE_CASE_LABEL(X_UnmapSubwindows, str) ; + STRINGIZE_CASE_LABEL(X_ConfigureWindow, str) ; + STRINGIZE_CASE_LABEL(X_CirculateWindow, str) ; + STRINGIZE_CASE_LABEL(X_GetGeometry, str) ; + STRINGIZE_CASE_LABEL(X_QueryTree, str) ; + STRINGIZE_CASE_LABEL(X_InternAtom, str) ; + STRINGIZE_CASE_LABEL(X_GetAtomName, str) ; + STRINGIZE_CASE_LABEL(X_ChangeProperty, str) ; + STRINGIZE_CASE_LABEL(X_DeleteProperty, str) ; + STRINGIZE_CASE_LABEL(X_GetProperty, str) ; + STRINGIZE_CASE_LABEL(X_ListProperties, str) ; + STRINGIZE_CASE_LABEL(X_SetSelectionOwner, str) ; + STRINGIZE_CASE_LABEL(X_GetSelectionOwner, str) ; + STRINGIZE_CASE_LABEL(X_ConvertSelection, str) ; + STRINGIZE_CASE_LABEL(X_SendEvent, str) ; + STRINGIZE_CASE_LABEL(X_GrabPointer, str) ; + STRINGIZE_CASE_LABEL(X_UngrabPointer, str) ; + STRINGIZE_CASE_LABEL(X_GrabButton, str) ; + STRINGIZE_CASE_LABEL(X_UngrabButton, str) ; + STRINGIZE_CASE_LABEL(X_ChangeActivePointerGrab, str) ; + STRINGIZE_CASE_LABEL(X_GrabKeyboard, str) ; + STRINGIZE_CASE_LABEL(X_UngrabKeyboard, str) ; + STRINGIZE_CASE_LABEL(X_GrabKey, str) ; + STRINGIZE_CASE_LABEL(X_UngrabKey, str) ; + STRINGIZE_CASE_LABEL(X_AllowEvents, str) ; + STRINGIZE_CASE_LABEL(X_GrabServer, str) ; + STRINGIZE_CASE_LABEL(X_UngrabServer, str) ; + STRINGIZE_CASE_LABEL(X_QueryPointer, str) ; + STRINGIZE_CASE_LABEL(X_GetMotionEvents, str) ; + STRINGIZE_CASE_LABEL(X_TranslateCoords, str) ; + STRINGIZE_CASE_LABEL(X_WarpPointer, str) ; + STRINGIZE_CASE_LABEL(X_SetInputFocus, str) ; + STRINGIZE_CASE_LABEL(X_GetInputFocus, str) ; + STRINGIZE_CASE_LABEL(X_QueryKeymap, str) ; + STRINGIZE_CASE_LABEL(X_OpenFont, str) ; + STRINGIZE_CASE_LABEL(X_CloseFont, str) ; + STRINGIZE_CASE_LABEL(X_QueryFont, str) ; + STRINGIZE_CASE_LABEL(X_QueryTextExtents, str) ; + STRINGIZE_CASE_LABEL(X_ListFonts, str) ; + STRINGIZE_CASE_LABEL(X_ListFontsWithInfo, str) ; + STRINGIZE_CASE_LABEL(X_SetFontPath, str) ; + STRINGIZE_CASE_LABEL(X_GetFontPath, str) ; + STRINGIZE_CASE_LABEL(X_CreatePixmap, str) ; + STRINGIZE_CASE_LABEL(X_FreePixmap, str) ; + STRINGIZE_CASE_LABEL(X_CreateGC, str) ; + STRINGIZE_CASE_LABEL(X_ChangeGC, str) ; + STRINGIZE_CASE_LABEL(X_CopyGC, str) ; + STRINGIZE_CASE_LABEL(X_SetDashes, str) ; + STRINGIZE_CASE_LABEL(X_SetClipRectangles, str) ; + STRINGIZE_CASE_LABEL(X_FreeGC, str) ; + STRINGIZE_CASE_LABEL(X_ClearArea, str) ; + STRINGIZE_CASE_LABEL(X_CopyArea, str) ; + STRINGIZE_CASE_LABEL(X_CopyPlane, str) ; + STRINGIZE_CASE_LABEL(X_PolyPoint, str) ; + STRINGIZE_CASE_LABEL(X_PolyLine, str) ; + STRINGIZE_CASE_LABEL(X_PolySegment, str) ; + STRINGIZE_CASE_LABEL(X_PolyRectangle, str) ; + STRINGIZE_CASE_LABEL(X_PolyArc, str) ; + STRINGIZE_CASE_LABEL(X_FillPoly, str) ; + STRINGIZE_CASE_LABEL(X_PolyFillRectangle, str) ; + STRINGIZE_CASE_LABEL(X_PolyFillArc, str) ; + STRINGIZE_CASE_LABEL(X_PutImage, str) ; + STRINGIZE_CASE_LABEL(X_GetImage, str) ; + STRINGIZE_CASE_LABEL(X_PolyText8, str) ; + STRINGIZE_CASE_LABEL(X_PolyText16, str) ; + STRINGIZE_CASE_LABEL(X_ImageText8, str) ; + STRINGIZE_CASE_LABEL(X_ImageText16, str) ; + STRINGIZE_CASE_LABEL(X_CreateColormap, str) ; + STRINGIZE_CASE_LABEL(X_FreeColormap, str) ; + STRINGIZE_CASE_LABEL(X_CopyColormapAndFree, str) ; + STRINGIZE_CASE_LABEL(X_InstallColormap, str) ; + STRINGIZE_CASE_LABEL(X_UninstallColormap, str) ; + STRINGIZE_CASE_LABEL(X_ListInstalledColormaps, str) ; + STRINGIZE_CASE_LABEL(X_AllocColor, str) ; + STRINGIZE_CASE_LABEL(X_AllocNamedColor, str) ; + STRINGIZE_CASE_LABEL(X_AllocColorCells, str) ; + STRINGIZE_CASE_LABEL(X_AllocColorPlanes, str) ; + STRINGIZE_CASE_LABEL(X_FreeColors, str) ; + STRINGIZE_CASE_LABEL(X_StoreColors, str) ; + STRINGIZE_CASE_LABEL(X_StoreNamedColor, str) ; + STRINGIZE_CASE_LABEL(X_QueryColors, str) ; + STRINGIZE_CASE_LABEL(X_LookupColor, str) ; + STRINGIZE_CASE_LABEL(X_CreateCursor, str) ; + STRINGIZE_CASE_LABEL(X_CreateGlyphCursor, str) ; + STRINGIZE_CASE_LABEL(X_FreeCursor, str) ; + STRINGIZE_CASE_LABEL(X_RecolorCursor, str) ; + STRINGIZE_CASE_LABEL(X_QueryBestSize, str) ; + STRINGIZE_CASE_LABEL(X_QueryExtension, str) ; + STRINGIZE_CASE_LABEL(X_ListExtensions, str) ; + STRINGIZE_CASE_LABEL(X_ChangeKeyboardMapping, str) ; + STRINGIZE_CASE_LABEL(X_GetKeyboardMapping, str) ; + STRINGIZE_CASE_LABEL(X_ChangeKeyboardControl, str) ; + STRINGIZE_CASE_LABEL(X_GetKeyboardControl, str) ; + STRINGIZE_CASE_LABEL(X_Bell, str) ; + STRINGIZE_CASE_LABEL(X_ChangePointerControl, str) ; + STRINGIZE_CASE_LABEL(X_GetPointerControl, str) ; + STRINGIZE_CASE_LABEL(X_SetScreenSaver, str) ; + STRINGIZE_CASE_LABEL(X_GetScreenSaver, str) ; + STRINGIZE_CASE_LABEL(X_ChangeHosts, str) ; + STRINGIZE_CASE_LABEL(X_ListHosts, str) ; + STRINGIZE_CASE_LABEL(X_SetAccessControl, str) ; + STRINGIZE_CASE_LABEL(X_SetCloseDownMode, str) ; + STRINGIZE_CASE_LABEL(X_KillClient, str) ; + STRINGIZE_CASE_LABEL(X_RotateProperties, str) ; + STRINGIZE_CASE_LABEL(X_ForceScreenSaver, str) ; + STRINGIZE_CASE_LABEL(X_SetPointerMapping, str) ; + STRINGIZE_CASE_LABEL(X_GetPointerMapping, str) ; + STRINGIZE_CASE_LABEL(X_SetModifierMapping, str) ; + STRINGIZE_CASE_LABEL(X_GetModifierMapping, str) ; + STRINGIZE_CASE_LABEL(X_NoOperation, str) ; + } + str << ' ' << e->resourceid << ']' ; + return str.str() ; +} + +//--------------------- X MODIFIER MAP HACKERY ------------------------- + +uint32_t modmask(KeySym keysym, XModifierKeymap* modmap, Display* d) +{ + KeyCode keycode = XKeysymToKeycode(d, keysym) ; + if (keycode == 0) // if we can't convert keysym to keycode, then + return 0U ; // the modifier map can't contain the given key + + const int n = 8 * modmap->max_keypermod ; + KeyCode* beg = modmap->modifiermap ; + KeyCode* end = beg + n ; + KeyCode* k = std::find(beg, end, keycode) ; + if (k == end) // keycode is not in modifier map + return 0U ; + + // If we get here, the modifier map does have the requested key + return (1U << (static_cast(k - beg)/modmap->max_keypermod)) & 0xFF ; +} + +//---------------------------- RECTANGLES ------------------------------- + +rect::rect() + : left(0), right(0), bottom(0), top(0) +{} + +rect::rect(int x, int y, int w, int h) + : left(x), right(x + w), bottom(y + h), top(y) +{} + +bool rect::intersects(const rect& r) const +{ + return !(left > r.right || right < r.left || + bottom < r.top || top > r.bottom) ; +} + +rect rect::intersection(const rect& r) const +{ + rect x ; + if (this->intersects(r)) { + x.left = std::max(left, r.left) ; + x.right = std::min(right, r.right ) ; + x.bottom = std::max(bottom, r.bottom) ; + x.top = std::min(top, r.top) ; + } + return x ; +} + +int rect::area() const +{ + return (right - left) * (bottom - top) ; +} + +} // namespace minxlib + +//---------------------------------------------------------------------- + +/**********************************************/ +/* Editor config: */ +/**********************************************/ +/* Local Variables: */ +/* indent-tabs-mode: nil */ +/* c-basic-offset: 4 */ +/* End: */ +/**********************************************/ +/* vim: set expandtab shiftwidth=4 tabstop=4: */ +/**********************************************/ ADDED minxlib/util.hh Index: minxlib/util.hh ================================================================== --- minxlib/util.hh +++ minxlib/util.hh @@ -0,0 +1,140 @@ +/** + @file util.hh + @brief Various utility routines for minxlib. +*/ + +/* + Copyright (C) 2012 The Minx Project Developers + + See wiki/copyright.wiki in the top-level directory of the Minx + source distribution for the full list of authors who have + contributed to this project. +*/ + +/* + This file is part of Minx. + + Minx 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, either version 3 of the License, or + (at your option) any later version. + + Minx 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 Minx. If not, see . +*/ + +#ifndef MINXLIB_UTIL_DOT_HH +#define MINXLIB_UTIL_DOT_HH + +//------------------------------ HEADERS -------------------------------- + +// Xlib +#include + +// Standard C++ +#include + +// Standard C +#include + +//------------------------------- XLIB ---------------------------------- + +namespace minxlib { + +/** + @brief Return a string representation of X protocol errors. + @param e Xlib error structure containing the details of the protocol error. + @return A string describing the error. + + This function converts the Xlib error and request codes into + human-readable form. +*/ +std::string to_str(const XErrorEvent* e) ; + +/** + @brief Return a bit mask for given key if it is in the modifier map. + @param k The keysym to look for in the modifier map. + @param m The X modifier keymap. + @param d The X server's interface object. + @return Mask for identifying modifier within modifier keymap. + + This function checks if the given key is part of the X modifier map. + If so, it will return a mask that will have a one in the bit + position corresponding to that modifier. Otherwise, it returns zero. +*/ +uint32_t modmask(KeySym k, XModifierKeymap* m, Display* d) ; + +//---------------------------- RECTANGLES ------------------------------- + +/** + @brief Helper class for rectangle intersections. + + This class represents a rectangle aligned with the principal axes + with x increasing from left to right and y from top to bottom. The + main purpose of this class is to find the intersections between + windows. +*/ +class rect { + // The four edges of the rectangle. + int left, right, bottom, top ; + + // Private default constructor to create an empty rectangle. + rect() ; + +public: + /** + @brief Create a rectangle object. + @param x x-coordinate of top-left corner. + @param y y-coordinate of top-left corner. + @param w Rectangle width. + @param h Rectangle height. + @return Rectangle object. + */ + rect(int x, int y, int w, int h) ; + + /** + @brief Does this rectangle intersect with another? + @param r The other rectangle. + @return True if this rectangle intersects r; false otherwise. + */ + bool intersects(const rect& r) const ; + + /** + @brief Compute intersection of this rectangle with another. + @param r The other rectangle. + @return Intersection of this rectangle with r. + + This method returns the intersection of this rectangle object + with the rectangle r. If there is no intersection, the returned + rectangle will be empty (i.e., zero width and height). + */ + rect intersection(const rect& r) const ; + + /** + @brief What is the area of this rectangle? + @return Area of rectangle. + */ + int area() const ; +} ; + +} // namespace minxlib + +//----------------------------------------------------------------------- + +#endif // #ifndef MINXLIB_UTIL_DOT_HH + +/**********************************************/ +/* Editor config: */ +/**********************************************/ +/* Local Variables: */ +/* indent-tabs-mode: nil */ +/* c-basic-offset: 4 */ +/* End: */ +/**********************************************/ +/* vim: set expandtab shiftwidth=4 tabstop=4: */ +/**********************************************/ ADDED minxlib/window.cc Index: minxlib/window.cc ================================================================== --- minxlib/window.cc +++ minxlib/window.cc @@ -0,0 +1,614 @@ +/* + @file window.cc + @brief Implementation of API defined in window.hh. +*/ + +/* + Copyright (C) 2012 The Minx Project Developers + + See wiki/copyright.wiki in the top-level directory of the Minx + source distribution for the full list of authors who have + contributed to this project. +*/ + +/* + This file is part of Minx. + + Minx 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, either version 3 of the License, or + (at your option) any later version. + + Minx 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 Minx. If not, see . +*/ + +//----------------------------- HEADERS -------------------------------- + +// minxlib +#include "window.hh" +#include "logging.hh" +#include "python.hh" +#include "keymap.hh" +#include "util.hh" + +// Xlib +#include +#ifdef MINXLIB_HAS_XINERAMA +#include +#endif + +// Boost +#include +#include + +#include + +// Standard C++ +#include +#include +#include +#include +#include + +// Standard C +#include + +//-------------------- INITIALIZATION AND CLEAN-UP ---------------------- + +namespace minxlib { + +static logging logger ; + +window::window(Display* d, Window w) + : m_display(d), m_id(w) +{} + +window::~window(){} + +//--------------------------- PROPERTIES ------------------------------ + +// Convert strings in X text property to semicolon separated string +static std::string property_to_string(XTextProperty* prop) +{ + char** strings ; int n ; + if (XTextPropertyToStringList(prop, &strings, &n)) { + std::ostringstream str ; + std::copy(strings, strings + n, + std::ostream_iterator(str, ";")) ; + XFreeStringList(strings) ; + + std::string s(str.str()) ; + return s.substr(0, s.length() - 1) ; // remove last semicolon + } + return std::string() ; +} + +std::map window::properties() +{ + std::map prop ; + prop["name" ] = "" ; + prop["icon_name"] = "" ; + prop["class" ] = "" ; + prop["res_name" ] = "" ; + + XTextProperty tp ; + if (XGetWMName(m_display, m_id, &tp)) + prop["name"] = property_to_string(&tp) ; + + if (XGetWMIconName(m_display, m_id, &tp)) + prop["icon_name"] = property_to_string(&tp) ; + + XClassHint ch ; + if (XGetClassHint(m_display, m_id, &ch)) { + prop["class"] = ch.res_class ; + prop["res_name"] = ch.res_name ; + XFree(ch.res_class) ; + XFree(ch.res_name ) ; + } + + return prop ; +} + +void window::set_properties(const std::map& prop) +{ + std::map::const_iterator it = prop.find("name") ; + if (it != prop.end()) { + XTextProperty tp ; + char* p = const_cast(it->second.c_str()) ; + if (XStringListToTextProperty(&p, 1, &tp)) { + XSetWMName(m_display, m_id, &tp) ; + XFree(tp.value) ; + logger.debug() << "set WM_NAME for window " << m_id + << " to " << it->second ; + } + } + + it = prop.find("icon_name") ; + if (it != prop.end()) { + XTextProperty tp ; + char* p = const_cast(it->second.c_str()) ; + if (XStringListToTextProperty(&p, 1, &tp)) { + XSetWMIconName(m_display, m_id, &tp) ; + XFree(tp.value) ; + logger.debug() << "set WM_ICON_NAME for window " << m_id + << " to " << it->second ; + } + } + + XClassHint ch ; + ch.res_class = 0 ; + ch.res_name = 0 ; + it = prop.find("class") ; + if (it != prop.end()) { + ch.res_class = new char[it->second.length() + 1] ; + strcpy(ch.res_class, it->second.c_str()) ; + } + it = prop.find("res_name") ; + if (it != prop.end()) { + ch.res_name = new char[it->second.length() + 1] ; + strcpy(ch.res_name, it->second.c_str()) ; + } + if (ch.res_class || ch.res_name) { + XSetClassHint(m_display, m_id, &ch) ; + logger.debug() << "set WM_CLASS for window " << m_id + << " to (" << (ch.res_class ? ch.res_class : "NULL") + << ", " << (ch.res_name ? ch.res_name : "NULL") + << ')' ; + } + delete[] ch.res_class; + delete[] ch.res_name ; +} + +//-------------------- PARENT-CHILD RELATIONSHIPS ----------------------- + +void window::reparent(const window& new_parent) +{ + logger.debug() << "setting " << m_id << "'s parent to " << new_parent ; + XReparentWindow(m_display, m_id, new_parent, 0, 0) ; +} + +window window::parent() +{ + logger.debug() << "getting " << m_id << "'s parent" ; + + Window root, parent_id ; + Window* children = 0 ; + unsigned int num_children ; + Status s = XQueryTree(m_display, m_id, + &root, &parent_id, &children, &num_children) ; + if (children) // whatever, don't care about these little buggers + XFree(children) ; + if (!s) { + logger.error() << "unable to query tree for window " << m_id ; + return window(m_display, 0) ; // throw exception instead? + } + + logger.debug() << "window " << m_id << "'s parent = " << parent_id ; + return window(m_display, parent_id) ; +} + +std::vector window::children() +{ + logger.debug() << "getting " << m_id << "'s children" ; + + std::vector windows ; + + Window root, parent ; + Window* children = 0 ; + unsigned int num_children ; + Status s = XQueryTree(m_display, m_id, + &root, &parent, &children, &num_children) ; + if (s) { + if (children) { + windows.reserve(num_children) ; + for (unsigned int i = 0; i < num_children; ++i) + windows.push_back(window(m_display, children[i])) ; + XFree(children) ; + } + } + else + logger.error() << "unable to query tree for window " << m_id ; + + logger.debug() << "window " << m_id << " has " << windows.size() + << " children" ; + return windows ; +} + +int window::screen() +{ + XWindowAttributes attr ; + Status s = XGetWindowAttributes(m_display, m_id, &attr) ; + if (!s) { + logger.error() << "unable to get screen index for window " << m_id ; + return -1 ; // throw exception instead? + } + + int screen_number = -1 ; +#ifdef MINXLIB_HAS_XINERAMA + int n = 0 ; + XineramaScreenInfo* screens = XineramaQueryScreens(m_display, &n) ; + if (n > 0) + { + logger.debug() << "Xinerama enabled; " << n << " screens" ; + + // Find the physical screen that has the most of this window + int max_area = -1 ; + rect w(attr.x, attr.y, attr.width, attr.height) ; + for (int i = 0; i < n; ++i) + { + rect r(screens[i].x_org, screens[i].y_org, + screens[i].width, screens[i].height) ; + rect x(w.intersection(r)) ; + int area = x.area() ; + if (area > max_area) + { + max_area = area ; + screen_number = i ; + } + } + XFree(screens) ; + } + else +#endif + { + logger.debug() << "Xinerama unavailable or inactive" ; + screen_number = XScreenNumberOfScreen(attr.screen) ; + } + logger.debug() << "window " << m_id << " on screen " << screen_number ; + return screen_number ; +} + +//----------------------------- EVENTS --------------------------------- + +void window::select_events(long mask) +{ + using std::hex ; using std::setw ; using std::setfill ; + logger.debug() << "setting input mask for window " << m_id + << " to 0x" << hex << setw(8) << setfill('0') << mask ; + XSelectInput(m_display, m_id, mask) ; +} + +//-------------------- KEYBOARD/MOUSE GRABBING ------------------------- + +void window::grab_key(const std::string& key_binding) +{ + using std::dec ; using std::hex ; using std::setw ; using std::setfill ; + logger.info() << "setting up passive grab for \"" << key_binding + << "\" on window " << m_id ; + + std::vector split ; + boost::algorithm::split(split, key_binding, + boost::algorithm::is_any_of("-"), + boost::algorithm::token_compress_on) ; + + // Last string after split is the key itself + std::string key = split.back() ; + KeySym keysym = XStringToKeysym(key.c_str()) ; + if (keysym == NoSymbol) { + logger.warning() << "no keysym matching \"" << key << '"' ; + return ; + } + + KeyCode keycode = XKeysymToKeycode(m_display, keysym) ; + if (keycode == 0) { + logger.warning() << "no keycode matching \"" << key << '"'; + return ; + } + logger.debug() << "keycode for \"" << key << "\" = " + << dec << static_cast(keycode) << " (0x" + << hex << setw(4) << setfill('0') + << static_cast(keycode) << ')' ; + + // Setup map for modifier short-hands and also initialize the list of + // masks for the different lock modifiers. + typedef std::map modmap ; + static modmap modifier_masks ; + static std::vector lock_masks; + if (modifier_masks.empty()) { + modifier_masks["C" ] = ControlMask ; + modifier_masks["S" ] = ShiftMask; + modifier_masks["M1"] = Mod1Mask ; + modifier_masks["M2"] = Mod2Mask ; + modifier_masks["M3"] = Mod3Mask ; + modifier_masks["M4"] = Mod4Mask ; + modifier_masks["M5"] = Mod5Mask ; + + // Now for ALT and META short-hands and the lock masks + XModifierKeymap* mkmap = XGetModifierMapping(m_display) ; + if (mkmap) { + modifier_masks["A"] = modmask(XK_Alt_L , mkmap, m_display) ; + modifier_masks["M"] = modmask(XK_Meta_L, mkmap, m_display) ; + uint32_t mask = + keymap::ignore_lock_modifiers(mkmap, m_display) ; + logger.debug() << "set modifier testing mask to 0x" + << hex << setw(4) << setfill('0') << mask ; + XFreeModifiermap(mkmap) ; + + mask = ~mask & 0xFF ; + logger.debug() << "lock masks = 0x" + << hex << setw(4) << setfill('0') << mask ; + for (int i = 0; i < 8; ++i) + if (mask & (1 << i)) + lock_masks.push_back(1 << i) ; + } + } + + // First n-1 strings of split are the modifiers + split.pop_back() ; + unsigned int mask = 0 ; + BOOST_FOREACH(const std::string& s, split) + { + modmap::const_iterator it = modifier_masks.find(s) ; + if (it != modifier_masks.end()) + mask |= it->second ; + } + if (!mask) + mask = AnyModifier ; + logger.debug() << "modifier mask for \"" << key_binding << "\" = 0x" + << hex << setw(4) << setfill('0') << mask ; + + // Setup primary grab for key binding using plain modifier mask + XGrabKey(m_display, keycode, mask, m_id, + False, GrabModeAsync, GrabModeAsync) ; + + // Setup additional grabs with modifiers augmented with lock masks + if (mask != AnyModifier) { + unsigned int n = (1 << lock_masks.size()) - 1 ; + for (unsigned int i = 1; i <= n; ++i) { + uint32_t lock_mask = 0; + for (unsigned int j = 0; j < lock_masks.size(); ++j) + if (i & (1 << j)) + lock_mask |= lock_masks[j] ; + + logger.debug() << "setting up additional grab for \"" + << key_binding << "\" with modifier mask 0x" + << hex << setw(4) << setfill('0') + << (mask | lock_mask) ; + XGrabKey(m_display, keycode, mask | lock_mask, m_id, + False, GrabModeAsync, GrabModeAsync) ; + } + } + + // Store mapping between grab and key binding name so we can + // translate keyboard events to the named key bindings as specified + // by Minx's end-users. + keymap::bind(keycode, mask, key_binding) ; +} + +//----------------------- WINDOW VISIBILITY ---------------------------- + +void window::show() +{ + logger.debug() << "mapping window " << m_id ; + XMapWindow(m_display, m_id) ; +} + +void window::hide() +{ + logger.debug() << "unmapping window " << m_id ; + XUnmapWindow(m_display, m_id) ; +} + +bool window::is_mapped() +{ + XWindowAttributes attr ; + Status s = XGetWindowAttributes(m_display, m_id, &attr) ; + if (!s) { + logger.error() << "unable to check map state for window " << m_id ; + return false ; // throw exception instead? + } + return attr.map_state != IsUnmapped ; +} + +//------------------------ WINDOW GEOMETRY ----------------------------- + +void window::move_resize(int x, int y, int w, int h) +{ + logger.debug() << "setting geometry for window " << m_id << " to " + << w << 'x' << h << '+' << x << '+' << y; + XMoveResizeWindow(m_display, m_id, x, y, w, h) ; +} + +void window::configure(int x, int y, int width, int height, + int border_width, const window* sibling, + int stacking_mode, unsigned int value_mask) +{ + using std::hex ; using std::setw ; using std::setfill ; + logger.debug() << "configuring window " << m_id << ": geom = " + << width << 'x' << height << '+' << x << '+' << y + << ", bw = " << border_width + << ", mask = 0x" << hex << setw(8) << setfill('0') + << value_mask ; + + XWindowChanges wc ; + wc.x = x ; + wc.y = y ; + wc.width = width ; + wc.height = height; + wc.border_width = border_width ; + wc.sibling = sibling ? sibling->m_id : 0 ; + wc.stack_mode = stacking_mode; + XConfigureWindow(m_display, m_id, value_mask, &wc) ; +} + +void window::set_border_attr(unsigned long c, unsigned int s) +{ + using std::hex ; using std::setw ; using std::setfill ; + logger.debug() << "setting window " << m_id + << " border color = 0x" << hex << setw(6) << setfill('0') + << c + << ", size = " << s ; + XSetWindowBorderWidth(m_display, m_id, s) ; + XSetWindowBorder (m_display, m_id, c) ; +} + +std::vector window::geometry() +{ + std::vector geom ; + Window r ; + int x, y ; + unsigned int w, h, b, d ; + Status s = XGetGeometry(m_display, m_id, &r, &x, &y, &w, &h, &b, &d) ; + if (s) { + geom.reserve(5) ; + geom.push_back(x) ; + geom.push_back(y) ; + geom.push_back(w) ; + geom.push_back(h) ; + geom.push_back(b) ; + } + else + logger.error() << "failed to get window geometry for ID " << m_id; + return geom ; +} + +//---------------------------- INPUT FOCUS ------------------------------ + +void window::focus() +{ + logger.debug() << "raising and focusing window " << m_id ; + XRaiseWindow (m_display, m_id) ; + XSetInputFocus(m_display, m_id, + RevertToPointerRoot, CurrentTime) ; +} + +//----------------------- WINDOW DESTRUCTION --------------------------- + +// Check if specified window wants to participate in the +// WM_DELETE_WINDOW protocol. If so, fill out XEvent's +// XClientMessageEvent structure and return true; else, leave XEvent +// unfilled and return false. +static bool delete_window(Display* d, Window w, XEvent* e) +{ + bool dw = false ;// start off assuming window doesn't do WM_DELETE_WINDOW + Atom* p = 0 ; // list of WM_PROTOCOLS window does + int n = 0 ; // number of items in above array + Status ok = XGetWMProtocols(d, w, &p, &n) ; + if (ok && n > 0) { + Atom wm_delete = XInternAtom(d, "WM_DELETE_WINDOW", False) ; + for (int i = 0; i < n; ++i) + if (p[i] == wm_delete) { + e->xclient.type = ClientMessage ; + e->xclient.display = d ; + e->xclient.window = w ; + e->xclient.message_type = XInternAtom(d, "WM_PROTOCOLS", False); + e->xclient.format = 32 ; + e->xclient.data.l[0] = wm_delete ; + dw = true ; + break ; + } + XFree(p) ; + } + return dw ; +} + +void window::kill() +{ + XEvent e ; + if (delete_window(m_display, m_id, &e)) { + if (XSendEvent(m_display, m_id, False, 0L, &e)) { + logger.debug() << "sent WM_DELETE_WINDOW to window " << m_id ; + return ; + } + logger.warning() << "WM_DELETE_WINDOW on window " << m_id << " failed" ; + } + nuke() ; +} + +void window::nuke() +{ + logger.debug() << "killing window " << m_id << " with brute force" ; + XKillClient(m_display, m_id) ; +} + +//------------------------ PYTHON INTERFACE ---------------------------- + +void window::pythonize() +{ + py::class_("window", py::no_init). + def(py::self == py::self). + def(py::self != py::self). + def(py::self == py::other() ). + def(py::self != py::other() ). + def_readonly("id", &window::m_id). + def("properties", &window::properties ). + def("set_properties", &window::set_properties ). + def("reparent", &window::reparent ). + def("parent", &window::parent ). + def("children", &window::children ). + def("screen", &window::screen ). + def("select_events", &window::select_events ). + def("grab_key", &window::grab_key ). + def("show", &window::show ). + def("hide", &window::hide ). + def("is_mapped", &window::is_mapped ). + def("move_resize", &window::move_resize ). + def("configure", &window::configure ). + def("set_border_attr", &window::set_border_attr). + def("geometry", &window::geometry ). + def("focus", &window::focus ). + def("kill", &window::kill ). + def("nuke", &window::nuke ); + + py::enum_("event_mask"). + value("no_event_mask", no_event_mask ). + value("key_press_mask", key_press_mask ). + value("key_release_mask", key_release_mask ). + value("button_press_mask", button_press_mask ). + value("button_release_mask", button_release_mask ). + value("enter_window_mask", enter_window_mask ). + value("leave_window_mask", leave_window_mask ). + value("pointer_motion_mask", pointer_motion_mask ). + value("pointer_motion_hint_mask", pointer_motion_hint_mask ). + value("button1_motion_mask", button1_motion_mask ). + value("button2_motion_mask", button2_motion_mask ). + value("button3_motion_mask", button3_motion_mask ). + value("button4_motion_mask", button4_motion_mask ). + value("button5_motion_mask", button5_motion_mask ). + value("button_motion_mask", button_motion_mask ). + value("keymap_state_mask", keymap_state_mask ). + value("exposure_mask", exposure_mask ). + value("visibility_change_mask", visibility_change_mask ). + value("structure_notify_mask", structure_notify_mask ). + value("resize_redirect_mask", resize_redirect_mask ). + value("substructure_notify_mask", substructure_notify_mask ). + value("substructure_redirect_mask", substructure_redirect_mask). + value("focus_change_mask", focus_change_mask ). + value("property_change_mask", property_change_mask ). + value("colormap_change_mask", colormap_change_mask ). + value("owner_grab_button_mask", owner_grab_button_mask ). + export_values() ; + + py::enum_("configure_mask"). + value("configure_x", configure_x). + value("configure_y", configure_y). + value("configure_width", configure_width ). + value("configure_height", configure_height). + value("configure_border_width", configure_border_width). + value("configure_sibling", configure_sibling ). + value("configure_stack_mode", configure_stack_mode ). + export_values() ; + + logger = logging::get_logger("window") ; +} + +} // namespace minxlib + +//---------------------------------------------------------------------- + +/**********************************************/ +/* Editor config: */ +/**********************************************/ +/* Local Variables: */ +/* indent-tabs-mode: nil */ +/* c-basic-offset: 4 */ +/* End: */ +/**********************************************/ +/* vim: set expandtab shiftwidth=4 tabstop=4: */ +/**********************************************/ ADDED minxlib/window.hh Index: minxlib/window.hh ================================================================== --- minxlib/window.hh +++ minxlib/window.hh @@ -0,0 +1,599 @@ +/** + @file window.hh + @brief Encapsulation of an X window. + + This file defines a class that wraps around the notion of an X + window and provides a Python interface for the rest of Minx. +*/ + +/* + Copyright (C) 2012 The Minx Project Developers + + See wiki/copyright.wiki in the top-level directory of the Minx + source distribution for the full list of authors who have + contributed to this project. +*/ + +/* + This file is part of Minx. + + Minx 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, either version 3 of the License, or + (at your option) any later version. + + Minx 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 Minx. If not, see . +*/ + +#ifndef MINXLIB_WINDOW_DOT_HH +#define MINXLIB_WINDOW_DOT_HH + +//----------------------------- HEADERS -------------------------------- + +// Xlib +#include + +// Standard C++ +#include +#include +#include +#include + +//------------------------- CLASS DEFINITION --------------------------- + +namespace minxlib { + +/** + @brief Encapsulate the details of an X window. + + This class provides an API for the Python parts of Minx to be able + to deal with X windows. It wraps around the relevant parts of Xlib + and exposes its functionality to Python via @boostpylink. +*/ +class window { +protected: + // All Xlib calls will require a display object... + Display* m_display ; + + // A window object's basic purpose is to wrap around the Xlib Window + // data structure. + Window m_id ; + +public: + /** + @brief Create a wrapper object for an existing Xlib window. + @param d The display object to which the window is "linked." + @param w The Xlib Window ID. + @return A valid window wrapper for the given Xlib window. + */ + window(Display* d, Window w) ; + + /** + @brief Export the window class to minxlib Python module. + @return Nothing. + + This function exposes the window class's interface so that it + can be used by the Python parts of Minx. It is meant to be + called by the Boost.Python initialization code in python.cc. + */ + static void pythonize() ; + + /** + @brief Window equality operator. + @param w The minxlib::window against which this should be checked. + @return True if this window equals w; false otherwise. + + Two window objects are considered equal if they have the same ID + and belong to the same display. + */ + bool operator==(const window& w) const { + return m_display == w.m_display && m_id == w.m_id ; + } + + /** + @brief Window inequality operator. + @param w The minxlib::window against which this should be checked. + @return True if this window does not equal w; false if it does. + + Two window objects are considered equal if they have the same ID + and belong to the same display. + */ + bool operator!=(const window& w) const {return !operator==(w) ;} + + /** + @brief Window equality operator. + @param w The Xlib window ID against which this should be checked. + @return True if this window's ID equals w; false otherwise. + + This version of the equality operator only checks this object's + window ID against w. + */ + bool operator==(Window w) const {return m_id == w ;} + + /** + @brief Window inequality operator. + @param w The Xlib window ID against which this should be checked. + @return True if this window's ID does not equal w; false otherwise. + + This version of the inequality operator only checks this + object's window ID against w. + */ + bool operator!=(Window w) const {return m_id != w ;} + + /** + @brief Convert to an Xlib Window. + @return This object cast to an Xlib Window. + + This cast operator allows us to pass minxlib::window objects + seamlessly to Xlib functions. + */ + operator Window() const {return m_id ;} + + /** + @brief Retrieve window properties from X server. + @return An STL map of strings to strings. + + This function gets the WM_NAME, WM_ICON_NAME, and WM_CLASS + properties for this window and returns them in an STL map of + strings to strings as shown below: + + + + + + + +
Key Value
name WM_NAME
icon_name WM_ICON_NAME
class WM_CLASS.res_class
res_name WM_CLASS.res_name
+ + If we are unable to retrieve a particular property, that key's + value will be an empty string. For example, if some window + doesn't have a WM_CLASS property, the "class" and "res_name" + keys for that window will be empty. + + On the Python side of Minx, the above STL map will be returned + as a Python dict. + + @note Although rare, the WM_NAME and WM_ICON_NAME X properties + may contain more than one string as values. If that is the case, + these multiple values in the returned map/dict will be + represented as semicolon separated strings. For example, if some + window's WM_NAME property has values "foo" and "bar", the value + of the "name" key in the returned map will be "foo;bar". + */ + std::map properties() ; + + /** + @brief Set window properties. + @param prop An STL map of strings to strings. + @return Nothing. + + This function sets the window properties specified in its prop + parameter. The following properties are supported: + + + + + + + + + + +
minxlib NameXlib Property
name WM_NAME
icon_name WM_ICON_NAME
class WM_CLASS.res_class
res_name WM_CLASS.res_name
+ + For example, let's say you have a minxlib::window object + w and set its properties as shown by the snippet of code + below: + + @code + std::map prop ; + prop["class"] = "foo" ; + prop["name" ] = "bar" ; + w.set_properties(prop); + @endcode + + That will set the WM_CLASS.res_class and WM_NAME properties of + the window w. + */ + virtual void set_properties(const std::map& prop); + + /** + @brief Reparent this window to another. + @param p New parent window. + @return Nothing. + */ + virtual void reparent(const window& p) ; + + /** + @brief Get this window's parent window. + @return Parent window. + + This function uses XQueryTree() to determine the parent window of + the X window encapsulated by this object. If the call to + XQueryTree() fails, this function will return a window object + with the id member set to zero. Eventually, on XQueryTree() + failure, X will generate a protocol error; that's why we don't + bother throwing an exception. + */ + virtual window parent() ; + + /** + @brief Get this window's children. + @return Window's children. + + This function uses XQueryTree() to determine the list of child + windows of the X window encapsulated by this object. If the call + to XQueryTree() fails, this function will return an empty list + and, eventually, X will generate a protocol error. + */ + std::vector children() ; + + /** + @brief Get screen number of this window. + @return Window's screen number. + + This function returns the zero-based index of the physical + screen this window is on. If Xinerama is active and this window + overlaps two or more screens, this function will return the + screen that has most of the window. + + @note This function calls XGetWindowAttributes(). If the call to + XGetWindowAttributes() fails, this function will return -1 and, + eventually, the X server will raise a protocol error. + */ + virtual int screen() ; + + /** + @brief An enumeration for the different event masks. + + This enumeration simply provides C++ names for the Xlib event + masks that are exported to Python via @boostpylink's enum + exporting facility. + + minxlib's names for the event masks and their corresponding Xlib + names are shown below: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
minxlib Xlib
no_event_maskNoEventMask
key_press_maskKeyPressMask
key_release_maskKeyReleaseMask
button_press_maskButtonPressMask
button_release_maskButtonReleaseMask
enter_window_maskEnterWindowMask
leave_window_maskLeaveWindowMask
pointer_motion_maskPointerMotionMask
pointer_motion_hint_maskPointerMotionHintMask
button1_motion_maskButton1MotionMask
button2_motion_maskButton2MotionMask
button3_motion_maskButton3MotionMask
button4_motion_maskButton4MotionMask
button5_motion_maskButton5MotionMask
button_motion_maskButtonMotionMask
keymap_state_maskKeymapStateMask
exposure_maskExposureMask
visibility_change_maskVisibilityChangeMask
structure_notify_maskStructureNotifyMask
resize_redirect_maskResizeRedirectMask
substructure_notify_mask + SubstructureNotifyMask
substructure_redirect_maskSubstructureRedirectMask
focus_change_mask + FocusChangeMask
property_change_maskPropertyChangeMask
colormap_change_maskColormapChangeMask
owner_grab_button_maskOwnerGrabButtonMask
+ */ + enum event_mask { + no_event_mask = NoEventMask, + key_press_mask = KeyPressMask, + key_release_mask = KeyReleaseMask, + button_press_mask = ButtonPressMask, + button_release_mask = ButtonReleaseMask, + enter_window_mask = EnterWindowMask, + leave_window_mask = LeaveWindowMask, + pointer_motion_mask = PointerMotionMask, + pointer_motion_hint_mask = PointerMotionHintMask, + button1_motion_mask = Button1MotionMask, + button2_motion_mask = Button2MotionMask, + button3_motion_mask = Button3MotionMask, + button4_motion_mask = Button4MotionMask, + button5_motion_mask = Button5MotionMask, + button_motion_mask = ButtonMotionMask, + keymap_state_mask = KeymapStateMask, + exposure_mask = ExposureMask, + visibility_change_mask = VisibilityChangeMask, + structure_notify_mask = StructureNotifyMask, + resize_redirect_mask = ResizeRedirectMask, + substructure_notify_mask = SubstructureNotifyMask, + substructure_redirect_mask = SubstructureRedirectMask, + focus_change_mask = FocusChangeMask, + property_change_mask = PropertyChangeMask, + colormap_change_mask = ColormapChangeMask, + owner_grab_button_mask = OwnerGrabButtonMask, + } ; + + /** + @brief Set the X event mask for this window. + @param mask The X event mask. + @return Nothing. + + This method allows clients to specify the events they are + interested in receiving for this window. The mask parameter + should be a bitwise OR of the event_mask enum and is passed + as-is to XSelectInput(). See the relevant Xlib documentation for + the details about the event mask. + */ + void select_events(long mask) ; + + /** + @brief Setup a passive keyboard grab. + @param s String describing key sequence for activating grab. + @return Nothing. + + The string s is expected to name a key binding and should be of + the form: + + @verbatim + (([CAS]|M[1-5]?)-)*key + @endverbatim + + The parenthesized part plus the asterisk in the above expression + are a regular expression while "key" stands for the + string form of a valid X keysym. Here are some examples of key + binding specifications: + + + + + + + + +
C-A-T CTRL + ALT + T
S-F1 SHIFT + F1
A-Tab ALT + Tab
M-R META + R
M5-S Mod5 + S
C-C CTRL + C
+ + Minx's key bindings specification is designed to be similar to + the way key bindings are specified in Emacs. + + This function interprets its parameter s in the manner described + above and infers the appropriate keycode and corresponding + modifier mask for the specified key binding. It then sets up a + passive grab for that keycode and modifier mask and also records + the name of the key binding (viz., s) in a key map that is + indexed using the keycode and modifier mask. + + Later, when the X server sends Minx keyboard events, we will use + the event's keycode and modifier mask to look-up the key binding + in the above-mentioned key map and return that name as part of + the event to Minx's Python core. + */ + void grab_key(const std::string& s) ; + + /** + @brief Show the window, i.e., map it. + @return Nothing. + */ + virtual void show() ; + + /** + @brief Hide this window, i.e., unmap it. + @return Nothing. + */ + virtual void hide() ; + + /** + @brief Check if window is currently mapped or not. + @return True if window is mapped, false otherwise. + + This function uses XGetWindowAttributes() and checks the value + of the XWindowAttributes's map_state field to see if the window + is mapped or not. It'll return true if map_state equals + IsViewable or IsUnviewable and false if either map_state is + IsUnmapped or if the call to XGetWindowAttributes() fails. + + @note If XGetWindowAttributes() fails, we would expect a + minxlib::protocol_error to be raised soon after. That's why this + function returns false instead of throwing an exception. + */ + virtual bool is_mapped() ; + + /** + @brief Move and resize the window. + @param x Window's x coordinate relative to parent's origin. + @param y Window's y coordinate relative to parent's origin. + @param w Window's width (not counting its border). + @param h Window's height (not counting its border). + @return Nothing. + + This function sets the window geometry by calling + XMoveResizeWindow(). Please consult Xlib documentation for + further details. + */ + virtual void move_resize(int x, int y, int w, int h) ; + + /** + @brief An enumeration for the different configure values. + + This enumeration simply provides C++ names for the Xlib value + mask for configure requests. These names are exported to Python + via @boostpylink's enum exporting facility. + + The minxlib and corresponding Xlib names for these enums are + shown below: + + + + + + + + + + +
minxlib Xlib
configure_x CWX
configure_y CWY
configure_width CWWidth
configure_height CWHeight
configure_border_width CWBorderWidth
configure_sibling CWSibling
configure_stack_mode CWStackMode
+ */ + enum configure_mask { + configure_x = CWX, + configure_y = CWY, + configure_width = CWWidth, + configure_height = CWHeight, + configure_border_width = CWBorderWidth, + configure_sibling = CWSibling, + configure_stack_mode = CWStackMode, + } ; + + /** + @brief Configure the window. + @param x Window's x coordinate relative to parent's origin. + @param y Window's y coordinate relative to parent's origin. + @param w Window's width (not counting its border). + @param h Window's height (not counting its border). + @param b Window's border width. + @param s Window's sibling for stacking operations. + @param t Window's stacking mode. + @param v Value mask to determine what to configure. + @return Nothing. + + This function calls XConfigureWindow() using v as the value_mask + parameter to the Xlib function and using the other parameters to + fill out the XWindowChanges structure. The value_mask is a + bitwise OR of the configure_mask enums. + + Please consult Xlib documentation for further details. For + example, the following page may be instructive: + + http://tronche.com/gui/x/xlib/window/configure.html + */ + virtual void configure(int x, int y, int w, int h, int b, + const window* s, int t, unsigned int v) ; + + /** + @brief Set window's border color and size. + @param c Three-byte RGB spec. + @param s Border size (in pixels). + @return Nothing. + + This function calls XSetWindowBorderWidth() and + XSetWindowBorder() to specify the window's border color and size + to the desired values. + */ + virtual void set_border_attr(unsigned long c, unsigned int s) ; + + /** + @brief Retrieve window's size, position, and border width. + @return STL vector of ints containing window geometry. + + This function uses XGetGeometry() to determine the window's size, + position, and border width. It returns an STL vector of integers + containing the following five values: + + @li Element 0: x-coordinate of window's top-left corner + @li Element 1: y-coordinate of window's top-left corner + @li Element 2: window width + @li Element 3: window height + @li Element 4: window's border width + + If the call to XGetGeometry() fails, this function will return an + empty vector. + + @note On the Python side, the STL vector returned by this + function will be converted into a Python list. Also, since we + would expect a minxlib::protocol_error to be raised soon after a + failed XGetGeometry(), we return and empty list instead of + throwing an exception in case the call to XGetGeometry() bombs. + */ + virtual std::vector geometry() ; + + /** + @brief Set input focus on this window and raise it. + @return Nothing. + */ + virtual void focus() ; + + /** + @brief Kill this window. + @return Nothing. + + This method destroys the X window encapsulated by this object by + killing the X client application that created the window. If the + window supports the WM_DELETE_WINDOW protocol, this method will + use that to kill the window and its client application. + Otherwise, it will simply use the XKillClient() function to + destroy the window and its client by brute force. + */ + virtual void kill() ; + + /** + @brief Kill this window using brute force. + @return Nothing. + + This method destroys the X window encapsulated by this object + without trying a graceful shutdown via WM_DELETE_WINDOW. It is + meant to be used on those clients whose windows advertise + support for WM_DELETE_WINDOW but don't implement the delete + protocol properly and stay alive without good cause despite a + user-initiated kill request. + */ + virtual void nuke() ; + + /** + @brief Destructor. + @return Nothing. + + Clean-up for when a window object is deleted. Really, there's + nothing to do because this class is only a thin wrapper around X + windows and we only maintain a pointer to the Xlib display + structure and an integral ID. We don't actually create or acquire + anything when this class is instantiated; consequently, there's + nothing to destroy or release. + */ + virtual ~window() ; +} ; // class window + +} // namespace minxlib + +//---------------------------------------------------------------------- + +#endif // #ifndef MINXLIB_WINDOW_DOT_HH + +/**********************************************/ +/* Editor config: */ +/**********************************************/ +/* Local Variables: */ +/* indent-tabs-mode: nil */ +/* c-basic-offset: 4 */ +/* End: */ +/**********************************************/ +/* vim: set expandtab shiftwidth=4 tabstop=4: */ +/**********************************************/ ADDED mk Index: mk ================================================================== --- mk +++ mk @@ -0,0 +1,65 @@ +#!/usr/bin/env sh +# +# mk -- build minxlib +# +# Convenience script for building minxlib without having to switch to +# the out-of-source build directory and other such hassles. +# + +# +# Copyright (C) 2012 The Minx Project Developers +# +# See wiki/copyright.wiki for the full list of authors who have +# contributed to this project. +# + +# +# This file is part of Minx. +# +# Minx 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, either version 3 of the License, or (at your +# option) any later version. +# +# Minx 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 Minx. If not, see . +# + +#------------------------------- MAIN ---------------------------------- + +# Restrict executable search path +PATH="/usr/local/bin:/usr/bin:/bin" + +# Decide whether we want release or debug builds +BUILD=release # default +[ "$1" = "-d" -o "$1" = "--debug" ] && { BUILD=debug ; shift ; } + +# Switch to the correct directory, i.e., the one containing this script +ABS_NAME=`readlink -f "$0"` +cd `dirname "$ABS_NAME"` + +# Okay, now let's build the dang thing +[ -s build/$BUILD/Makefile ] || { # no Makefile ==> need to run cmake first + rm -rf build/$BUILD + mkdir -p build/$BUILD + cd build/$BUILD + cmake -DCMAKE_BUILD_TYPE=$BUILD ../.. || exit 1 + cd - +} +make -j4 -C build/$BUILD $@ + +#----------------------------------------------------------------------- + +# Editor config: +# +# Local Variables: +# indent-tabs-mode: nil +# sh-basic-offset: 4 +# End: +# +# vim: expandtab shiftwidth=4 tabstop=4 ADDED wiki/build.wiki Index: wiki/build.wiki ================================================================== --- wiki/build.wiki +++ wiki/build.wiki @@ -0,0 +1,230 @@ +Building and Testing + +

Building and Testing HOWTO

+ +Eventually, the goal is to have Minx integrate well with different +package management systems. When that happens, getting Minx working +should be a fairly automated procedure. However, in these early days of +development, the process is a bit of a slog... + +

Prerequisites

+ +You will need the following software to be able to grab Minx's source +code, build its C++ component (viz., minxlib), and then run the window +manager: + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SoftwareDebian/Ubuntu
Package
FreeBSD
Package
Comments
Fossilfossildevel/fossil
CMakecmakedevel/cmake
Boostlibboost-all-devdevel/boost-all
+ devel/boost-python-libs
Pythoninstalled by aboveinstalled by above (?)
Xephyrxserver-xephyrx11-servers/xephyr
Xdmxxdmxx11-servers/xorg-dmxOptional (if you want to test multi-monitor setup)
Doxygendoxygendevel/doxygenOptional (if you want to build API documentation)
+
+ +In the above table, the package names are valid for a recent +Debian-based system (e.g., +[http://www.debian.org/releases/squeeze/|Debian squeeze], +[http://releases.ubuntu.com/precise/|Ubuntu 12.04]). You should be able +to simply apt-get install the above packages to pull in the +necessary dependencies. + +For a recent +[http://www5.us.freebsd.org/releases/9.0R/announce.html|FreeBSD] system, +the package names are all relative to /usr/ports. You should be +able to install the packages using one of the following: + + * pkg_add + * make ; make install + * portmaster + * whatever other package management facilites exist on FreeBSD + +Personally, I use portmaster. + +

Get the Code

+ +Before you can check-out the source code, you will have to create a +directory where the Minx Fossil repository can be stored and another for +the working copy: + + + cd + mkdir fossil minx + + +Now, you can clone the Minx repository and check-out the sources: + + + fossil clone http://chiselapp.com/user/minxdude/repository/minx \ + fossil/minx.fossil + cd minx + fossil open ~/fossil/minx.fossil + fossil update rel1.x + + +At this point, you should have the Minx sources in ~/minx. You +should see at least these two subdirectories: minxlib and +core. The former contains the C++ part of Minx, the latter has +the Python code. + +

Build minxlib

+ +The top-level directory has a script named mk for building +minxlib and the API documentation. So, assuming you are in +~/minx, all you should need to do is: + + + ./mk + + +And, if you want to build the API documentation: + + + ./mk doc + + +But note that the rel1.x branch already has the API documentation +[/doc/rel1.x/api/index.html|checked in to the repository]. So you really +shouldn't need to build the documentation. + +

Configure Minx

+ +Minx is actually a Python library that allows you to write your own +tiling window manager. Thus, there is no command to start Minx. Rather, +you write a small Python program that loads Minx and starts the window +manager. + +You can put the above-mentioned program wherever you like. Here, we will +assume it goes to ~/.minx/init.py. So, fire up your favourite +editor and add the following content to ~/.minx/init.py: + + + #!/usr/bin/env python + + import os, sys + sys.path.append(os.path.join(os.environ['HOME'])) + + import minx + minx.core.wm().start() + + +After saving the above file, make it executable: + + + chmod +x ~/.minx/init.py + + +That's it, we're now ready to put Minx through its paces... + +

Test-drive Minx

+ +

Single Monitor

+ +Currently, Minx is very far from being ready for prime-time. All it does +is allow you to switch input focus from one top-level window to another. +So don't edit your ~/.xinitrc and restart X just yet! Instead, +run Xephyr: + + + Xephyr :1 2> /dev/null & + + +You don't have to redirect stderr to /dev/null. I do it +because I find Xephyr's chatter distracting and unnecessary. + +Once Xephyr comes up, start Minx: + + + DISPLAY=:1 ~/.minx/init.py & + + +Now, give Minx a few windows it can switch between. I usually start up a +few terminals and message windows (you can use any X applications you +like): + + + DISPLAY=:1 xterm & + DISPLAY=:1 xmessage foo & + DISPLAY=:1 xmessage bar & + + +To cycle input focus between the above windows, switch to the Xephyr +window and have it grab input focus by pressing CTRL + SHIFT. +Then, press ALT + Tab and/or ALT + SHIFT + Tab repeatedly +move the input focus forwards or backwards through the applications you +started. When you're done testing, use CTRL + SHIFT again to have +Xephyr release its grab on the keyboard and mouse so you can kill it and +return to your normal X session. + +NOTE: If ALT + Tab doesn't work, try customizing the key +bindings for focus cycling as described in the +[/doc/ckout/wiki/key-bindings.wiki|Key Bindings HOWTO]. + +

Ubuntu Peculiarities

+ +Sometimes, on Ubuntu 12.04, typing into a terminal window generates junk +characters when other windows are present. That is, if you start Xephyr, +Minx, and then an xterm, you can use the terminal. However, if you start +an xmessage and an xterm, typing into the terminal results in junk. Not +sure why this happens. + +

Dual Monitor

+ +To test Minx's ability to handle dual-head configurations, use Xephyr +and Xdmx like so: + + + Xephyr :2 2> /dev/null & + Xephyr :3 2> /dev/null & + Xdmx :1 -display :2 -display :3 2> /dev/null & + + +Now start the window manager and a couple of applications: + + + DISPLAY=:1 ~/.minx/init.py & + DISPLAY=:1.0 xterm & + DISPLAY=:1.0 xmessage foo & + DISPLAY=:1.1 xterm & + DISPLAY=:1.1 xmessage bar & + + +You should be able to use ALT + Tab to switch between the above +xterm and xmessage windows across both screens. + +NOTE: Again, if ALT + Tab doesn't work, try customizing +the key bindings (F1 and SHIFT + F1 are usually quite +trustworthy). + +

Peculiarities

+ +One issue to keep in mind with this dual-monitor test is that key press +events only seem to work on the first Xephyr window. For some reason, +when the second Xephyr window is focused, no keyboard events are +generated. Not sure why this happens. ADDED wiki/copyright.wiki Index: wiki/copyright.wiki ================================================================== --- wiki/copyright.wiki +++ wiki/copyright.wiki @@ -0,0 +1,23 @@ +Copyright + +

The Minx Project's Developers and Copyright Holders

+ +Minx is a tiling X window manager. It is released under the auspices of version +3 of the GNU General Public License. Instead of cluttering each source file with +copyright notices containing author info, we put the list of people who have +contributed to Minx in this file and a short notice in each source file pointing +to this one +([http://softwarefreedom.org/resources/2012/ManagingCopyrightInformation.html|per +these recommendations]). + +With that short explanation out of the way, here is the list of people who +have contributed to [/doc/ckout/wiki/home.wiki|the Minx project]: + +
+ + + + + + +
Manu Viswanathan<minxdude@gmail.com>
ADDED wiki/design.wiki Index: wiki/design.wiki ================================================================== --- wiki/design.wiki +++ wiki/design.wiki @@ -0,0 +1,206 @@ +Design Notes + +

Design and Implementation Notes

+ +The following diagram shows a high-level view of the different classes +and roughly how they're connected to each other. + + + +The minx.core and minx.layout classes are written in +Python whereas the minxlib classes are written in C++ and +exposed to Minx's Python core. + +The organization of the code mirrors the above diagram. All the +minx.core classes are in the minx/core directory, the +minx.layout classes in minx/layout, and the +minxlib classes are in the minx/minxlib directory. + +
+ +

minx.core

+ +

minx.core.wm

+ +This class is the main window manager object that end-users will create. +It maintains all the necessary state and provides the interface for +customizing the window manager. + +When you create a wm object, you can supply a config +object to its constructor to customize various attributes such as the +border colors. To further customize Minx's responses to various events, +you can add your own hook functions to the wm.hooks object. For +layouts, you can deal with the wm.layouts object, which is an +instance of the minx.core.layman class. + +After these setup steps, call wm.start() to run the window +manager. The start() method connects to the X server by creating +a minxlib::display object, performs some initialization so as to +receive various window management related events, and then enters the +event loop. The actual event handling is delegated to the +minx.core.xevents object, which has the necessary event handler +callbacks. + +X errors are handled by minx.core.wm as part of its event loop. + +

minx.core.hooks

+ +This class implements a mapping between strings and corresponding +function lists. The string keys identify different events. For example, +all X events are of the form "x_something" +(x_map_request, x_create_notify, etc.). Most end-user +hooks are of the form "some_hook". Key bindings are also handled +as hooks (their names are not of the form "some_hook"; they +follow a different [/doc/ckout/wiki/key-bindings.wiki|pattern]). + +All objects within the system that have a reference to the wm +object can get at the hook map and add functions to it. This map is +meant to be used inside the window manager itself as well as by +end-users (for customizing Minx). + +

minx.core.xevents

+ +The xevents class is a simple helper. Initially, all the X +event handlers were in minx.core.wm. But that made wm +too big. So we split the event handlers into their own class, viz., +minx.core.xevents. + +When end-user code instantiates minx.core.wm, the wm +constructor creates an xevents object, passing it a reference +to itself. Through this reference to wm, xevents is +able to add its handlers to wm.hooks. + +Now, when wm gets events from the X server, it triggers the +appropriate handler in xevents via its map of hooks. + +

minx.core.layman

+ +This class implements a layout manager, which keeps track of the +different layouts. The layman class maintains a list of layout +objects and provides an API for adding layouts, looking for them, and so +on. + +

minx.core.focus_list

+ +Not all top-level windows can be focused. For example, the GNOME and XFCE +terminal programs create a couple of hidden top-level windows. Therefore, +only a subset of the top-level windows can actually receive input focus. +These windows are put in the focus_list by the xevents +object. This usually happens when the X server sends a MapNotify +event for a top-level window to Minx. When minx.core.wm receives +an UnmapNotify for a top-level window, xevents will +remove the corresponding minxlib::window from the +focus_list. + +

minx.core.config

+ +This class allows end-users to effect simple customizations such as +changing the window border colors and sizes. End-users will have to +instantiate this class and change its attributes to get the desired +customizations. The resulting config object should be passed to +the minx.core.wm constructor. Any object that has a reference +to the wm object can then get at its config instance +to access the different settings. + +If end-users do not create a custom config, Minx will use +default values for all its supported customizations. + +
+ +

minx.layout

+ +

minx.layout.base

+ +This is a base class for all Minx layouts. For the most part, it contains +empty implementations for most events and other functions that are meant +to be overridden by subclasses. Additionally, the +minx.layout.base module contains a few event handlers for +various bits of layout related functionality. + +

minx.layout.full

+ +This layout implements a policy of showing one window at a time, resizing +all the windows it manages to occupy the entire area available to it. + +
+ +

minxlib

+ +minxlib is a small C++ library that provides a high-level, +object-oriented API to Minx's Python core for interfacing with Xlib. We +could have used [http://python-xlib.sourceforge.net/|python-xlib] +instead of writing our own custom Xlib wrapper. Unfortunately, the +[http://python-xlib.sourceforge.net/doc/html/index.html|documentation] +of python-xlib is a little thin. Rather than struggle with that, it +seemed like a good idea to just implement minxlib. + +Furthermore, a custom abstraction should make it easier to port Minx to +[http://wayland.freedesktop.org/|Wayland] whenever that becomes the +preferred means of talking to the video hardware. + +Note that minxlib is not a thin wrapper around Xlib. It provides an API +that the rest of Minx actually needs. That is, the primary goal of +minxlib is not to wrap around all of Xlib but only that part of it that +we actually need for Minx. Moreover, minxlib provides a high-level API +that goes beyond the primitive one implemented by Xlib. For example, +minxlib has a function to return a list of all the root windows across +all screens. Thus, the Python parts of Minx don't have to deal with the +low-level Xlib functions to enumerate the root windows one-by-one. + +

minxlib::display

+ +The display class encapsulates the connection to the X server. +It provides the conduit for retrieving events from the server and for +converting X errors into exception objects that Minx's Python core can +handle. + +

minxlib::window and minxlib::root_window

+ +This class encapsulates an X window, taking care of the details of +specifying X event masks and setting up key bindings. It also provides +various window-related operations such as showing, hiding, resizing, +moving, focusing, etc. + +The root_window class is derived from minxlib::window +and provides an encapsulation of X's root windows, taking into account +support for Xinerama. Minx provides one root_window for each +physical screen. If Xinerama is active, there'll only be one logical +screen; however, Minx will return multiple root_window objects, +each with the same X ID but different geometries. + +

minxlib::event and minxlib::exception

+ +These two classes provide an object-oriented interface to X events and +errors. Different types of events are converted into instances of +subclasses of minxlib::event. Similarly, different kinds of +errors are converted into instances of classes derived from +minxlib::exception (which, on the Python side, is derived from +Python's Exception base class). + +Like the rest of minxlib, the events and exceptions interface is +incomplete, wrapping around only those parts of Xlib that Minx actually +needs. + +

minxlib::keymap

+ +The keymap is used to keep track of the user-defined (or +internally assigned) key binding names. When a keystroke matching a key +binding is generated, the key event is "translated" so as to report that +key binding to the Python core. + +

minxlib::logging

+ +To help with debugging, Minx emits log messages. Instead of implementing +a separate logging API in C++, minxlib simply takes advantage of +Python's standard logging module. The minxlib::logging +class provides a C++ logging API that uses the Python logging +module. Thus, minxlib's log messages end up in the same place as the +other Minx log messages. Moreover, the logging configuration is the same +as for Minx's Python parts; that is, end-users see all of Minx as a +Python library and can remain blissfully unaware of the fact that the +minxlib component is written in C++. + +All the other minxlib classes use the minxlib::logging API to +emit log messages to the Minx log. To make this work, each minxlib +module instantiates a logger object and uses that object to send +messages to the Minx log. ADDED wiki/faq.wiki Index: wiki/faq.wiki ================================================================== --- wiki/faq.wiki +++ wiki/faq.wiki @@ -0,0 +1,530 @@ +FAQ + +

Frequently Asked Questions

+ +[#general-questions|About Minx] + # [#what-is-minx|What is Minx?] + # [#why-yawm|Why write yet another (tiling) window manager?] + # [#why-not-qtile|Why not qtile?] + +[#config-questions|Using Minx] + # [#how-start|How do I start Minx?] + # [#simple-customizations|How do I customize border colors, etc.?] + # [#how-hooks|How do I customize Minx's behaviour?] + # [#how-key-bindings|How do I specify my own key bindings?] + # [#how-launch-apps|How can I launch applications with key strokes?] + # [#minx-version|Which version of Minx am I using?] + +[#dev-questions|Design and Implementation] + # [#what-is-minxlib|What is minxlib?] + # [#why-not-pyxlib|Why not use python-xlib?] + # [#why-not-xcb|Why not use XCB instead of Xlib?] + # [#how-build|How do I build Minx?] + # [#why-fossil|Why Fossil instead of git or mercurial?] + # [#branches|Why dev1.x/rel1.x branches? Will there be 2.x, 3.x, etc.?] + # [#why-doxygen|Why Doxygen instead of Sphinx?] + +
+ + +

About Minx

+ + +

What is Minx?

+ +Minx Is Not Xmonad. That is, it is a poor +imitator. Thus, like [http://xmonad.org/|xmonad], Minx attempts to +layout the top-level windows on your X screens by automatically tiling +them. + +Minx's overarching goals are: + + * Minimalism + * Extensibility + * Ease of configuration + +Minx does not paint icons on your root window, have a clickable taskbar +or panel for window selection, etc. All it does is manage the top-level +windows as per your settings (layout algorithm to use, keybindings, and +so on). Additionally, the only decoration Minx puts on top-level windows +is a simple border. By default, this border is one pixel thick and is +red for the currently focused window and white for all the others (but, +of course, you can change that). + +Another way in which Minx is minimal is in the small number of core +principles it implements, namely: + + * Hooks + * Layout support + * State injection + +That's it. All other features are built on top of this basic +infrastructure. For example, Minx has no built-in notion of a workspace. +Instead, it sports "bookmarks," which capture "snapshots" that record +the current layout, window configuration, etc. This custom state is then +added to the window manager, which has functions for searching for that +state, retrieving it, and so on. + +Apart from keeping the window manager's core small, these core ideas +also help make it highly customizable and extensible. + +Finally, when it comes to ease of configuration, Minx's defaults aim for +a reasonable out-of-the-box end-user experience. Things that people +would commonly want to customize involve a small and straightforward +amount of Python code. But even more advanced configuration (e.g., +implementing custom layouts, altering focus switching policy, etc.) is +also fairly easy. + +
+ + +

Yes, but xmonad is all that and more. So why bother writing yet +another (tiling) window manager?

+ +Mostly because I was not patient enough to unlearn enough of my +imperative programming habits to fully understand +[http://haskell.org/|Haskell] and, thereby, configure +[http://xmonad.org/|xmonad] to get the exact behaviour I wanted. + +Of course, I could have switched to a different tiling window manager. +And I did look into [http://awesome.naquadah.org/|awesome], +[http://i3wm.org/|i3], [http://www.nongnu.org/stumpwm/|StumpWM], and one +or two others. Needless to say, in the end, I decided that if I wanted a +window manager that worked exactly how I wanted, I'd best write my own. + +In retrospect, I should probably have just spent the time and effort to +really learn Haskell! When I started Minx, I thought to myself "There +are a gazillion window managers out there. How hard can it be? +[http://swizec.com/blog/programmers-are-optimists/swizec/4509|I should +be done in a few weeks.]" + +Now, more than a few weeks into it, I can see this is going to take a +while because: + + # Low-level X programming is not quite as straightforward as I thought + it would be. + # Figuring out the various support libraries (e.g., + [http://boost.org/|Boost]) involves a fair amount of trial and error. + # Learning a new set of tools ([http://fossil-scm.org/|version control], + [#why-doxygen|documentation generator], + [http://www.graphviz.org/|diagramming language]) also takes time. + # Cleaning up code and writing good documentation slows down + development a good deal. + +On the other hand, at this point, I am committed to the project and am +learning new things. So, I suppose I shouldn't complain. + +
+ + +

Fine, if you're too dumb to grok Haskell and want to stick with +something you know, i.e., Python, why not use qtile?

+ +I did consider [http://qtile.org/|qtile]. But I thought its dependencies +were somewhat onerous and I was in no mood to check-out and build a +bunch of libraries from their source-control repositories just to get a +window manager working. If I was going to be using a "ready-made" window +manager (and not roll my own), I just wanted to be able to install a +couple of packages, read some docs to figure out how to customize it, +and move on with my life. + +Moreover, qtile has some things in it that, in my opinion, do not +belong in a minimal window manager, viz., widgets. Such functionality +should be left to other programs (e.g., +[http://adesklets.sourceforge.net/|adesklets], +[http://screenlets.org/|screenlets]). + +Minx, in contrast, only needs stock Xlib, [http://boost.org/|Boost], and +[http://python.org/|Python]. You can install these using your system's +package manager. + +But, really, my arguments for rejecting [http://xmonad.org/|xmonad], +[http://qtile.org/|qtile], and other window managers in favour of +writing my own are fairly weak and can be boiled down to "Because I can +and I want to." So, before you flame me about it, if you like one of +these other window managers, please continue to use 'em; they're quite +fine programs and I am not dissing them or trying to proselytize you to +switch to Minx. + +
+ + +

Using Minx

+ + +

How do I start Minx?

+ +At this time, Minx cannot even be considered alpha software. Therefore, +it does not do anything very useful and does not come neatly packaged. +You have to check-out its source code and run it in a nested X server. +See the [/doc/ckout/wiki/build.wiki|Building and Testing HOWTO] for more +details. + +
+ + +

How do I customize border colors, sizes, and other such simple +attributes?

+ +By default, window borders are one pixel; the active window's border is +red, and inactive window borders are white. To change these defaults, +you will have to create a minx.core.config object, assign the +desired values to its attributes, and then pass this config +object to the minx.core.wm constructor. Here is some sample +code to show how to go about this: + + + #!/usr/bin/env python + + import minx + + conf = minx.core.config() + conf.active_border_size = 2 + conf.active_border_color = 0x00FF00 # green + conf.inactive_border_color = 0x00FFFF # cyan + + minx.core.wm(conf).start() + + +For more info about the config class, have a look at its +[/doc/ckout/api/classminx_1_1core_1_1config_1_1config.html|API documentation]. + +In addition to the border attributes, the minx.core.config +class also allows you to configure logging, which can be useful for +debugging. For details on this, refer to the +[/doc/ckout/wiki/logging.wiki|logging HOWTO]. + +
+ + +

How can I define my own hook functions to tweak how Minx handles +various events?

+ +The [/doc/ckout/api/classminx_1_1core_1_1wm_1_1wm.html|main window +manager object] maintains a hooks attribute that maps +predefined names to their corresponding hook functions. By default, most +of these name-to-function mappings are empty. To add your own hook, you +will have to call wm.hooks.add() after creating the wm +object but before calling its start() method. + +Here is a short example: + + + #!/usr/bin/env python + + import minx + + def my_manage_hook(w): + prop = w.properties() + if prop['class'] == 'Xmessage' and prop['name'] == 'bar': + return False + return True + + wm = minx.core.wm() + wm.hooks.add('manage_hook', my_manage_hook) + wm.start() + + +With that bit of code, Minx will call my_manage_hook() whenever +a new window is created, which checks whether the window is an Xmessage +named "bar"; if so, the hook returns false to indicate to Minx that the +window should not be managed. Other windows will be passed through to +Minx for it to manage normally. + +The [/doc/ckout/wiki/hooks-howto.wiki|Hooks HOWTO] has a lot more +information about customization via hooks. The +[/doc/ckout/wiki/hooks-list.wiki|Hooks List] documents all the currently +supported hooks. + +
+ + +

How can I specify my own key bindings to trigger various +functions?

+ +Key bindings are simply hooks whose names follow the pattern shown +below: + + + [modifier-]*key + + +Thus, a key binding's name consists of zero or more hyphen-separated +modifiers followed in the end by the name of a key. The supported +modifiers are: + + * C for CTRL + * A for ALT + * S for SHIFT + * M for META + * M1 through M5 for Mod1 through Mod5 + +The following examples should clarify how key bindings are named: + +
+ + + + + + + + +
Name Key Binding
C-A-T CTRL + ALT + T
S-F1 SHIFT + F1
A-Tab ALT + Tab
M-R META + R
M5-S Mod5 + S
C-C CTRL + C
+
+ +To add your own key bindings, you will have to use +[#how-hooks|Minx's hooks support]. The following example shows you how +you can use (in addition to the default ALT + Tab and ALT + SHIFT + Tab) +the F1 and SHIFT + F1 keys to cycle the input focus: + + + #!/usr/bin/env python + + import minx + + wm = minx.core.wm() + wm.hooks.add( 'F1', wm.focus_next) + wm.hooks.add('S-F1', wm.focus_prev) + wm.start() + + +For more on key bindings, be sure to read the +[/doc/ckout/wiki/key-bindings.wiki|Key Bindings HOWTO]. Additionally, +the [/doc/ckout/wiki/hooks-list.wiki#default-key-bindings|Hooks List] +documents all the default key bindings. + +
+ + +

How can I launch applications using custom key bindings?

+ +The [/doc/rel1.x/api/classminx_1_1core_1_1wm_1_1wm.html|wm] class has a +spawn() function that you can use for this purpose. In fact, +there is a +[/doc/rel1.x/wiki/hooks-list.wiki#launch_terminal|default key binding] +that calls wm.spawn() to launch a terminal window. In a similar +vein, you could define keys as shown below: + + + #!/usr/bin/env python + + import minx + + wm = minx.core.wm() + wm.hooks.add('C-A-E', lambda: wm.spawn('emacs')) + wm.hooks.add('C-A-F', lambda: wm.spawn('firefox')) + wm.hooks.add('C-A-G', lambda: wm.spawn('gimp')) + wm.start() + + +With that, CTRL + ALT + E will launch Emacs, CTRL + ALT + F will launch +Firefox, and CTRL + ALT + G will launch The Gimp. + +
+ + +

Which version of Minx am I using?

+ +The minx module defines a version object. Converting +this to a string will yield something of the form a.b.c, where +a is the major version number, b the minor version +number, and c the patch level. + +Additionally, the minx.version object has three read-only +attributes, viz., major, minor, and patch. +These attributes are integers and allow you to individually determine +and/or compare the three components of the full version number. + +Here is some sample code illustrating version number determination: + + + #!/usr/bin/env python + + import minx + + try: + print('Minx version string: {}'.format(minx.version)) + print('Minx version: major = {}, minor = {}, patch = {}'. + format(minx.version.major, minx.version.minor, minx.version.patch)) + except AttributeError: # no version attribute in minx module + pass # probably using development version of Minx + + +Note that minx.version is not available in development versions +of Minx. Only officially released versions of Minx have a version number +assigned to them. + +
+ + +

Design and Implementation

+ + +

What is minxlib?

+ +Minx is written in [http://python.org/|Python]. minxlib is an Xlib +wrapper written in [http://www.stroustrup.com/C++.html|C++] with its API +exposed to the Python-based Minx core via +[http://www.boost.org/doc/libs/1_52_0/libs/python/doc/|Boost.Python]. +minxlib provides a high-level, object-oriented interface to Xlib that +the Python parts of Minx can use. + +
+ + +

Why not use python-xlib instead of writing a custom Xlib wrapper?

+ +Lack of +[http://python-xlib.sourceforge.net/doc/html/index.html|documentation]. + +Whatever its [http://www.remlab.net/op/xlib.shtml|deficiencies], the C +version of Xlib is pretty +[http://menehune.opt.wfu.edu/Kokua/Irix_6.5.21_doc_cd/usr/share/Insight/library/SGI_bookshelves/SGI_Developer/books/XLib_PG/sgi_html/|well-documented] +(also [http://www.itimdp4.com/manpages/Xlib.html|this]). + +When I tried python-xlib, I found there was a certain amount of +guesswork involved in figuring out certain functions and just didn't +want to struggle with it. + +Additionally, by encapsulating the connection with the underlying +display server in a custom abstraction, making Minx support something +like [http://wayland.freedesktop.org/|Wayland] will (hopefully) be a +simple matter of reworking minxlib while leaving the higher-level window +management logic unchanged. + +
+ + +

Why not use XCB instead of Xlib?

+ +Again, [http://xcb.freedesktop.org/tutorial/|lack of documentation]. + +
+ + +

How do I build Minx?

+ +Minx is written mostly in Python; so there's nothing to build. There is, +however, a small C++ component, viz., minxlib, which you will have to +build. See the [/doc/ckout/wiki/build.wiki|Building and Testing HOWTO] +for the gory details. + +
+ + +

Why are you using Fossil, an obscure version control system, instead +of something more well known like SVN, git, or Mercurial?

+ +I wanted to be able to do initial development without the need for a +server. That pretty much ruled out [http://svnbook.red-bean.com/|SVN]. + +I am using [http://git-scm.com/|git] at work. While I appreciate some of +its [http://git-scm.com/book/en/Git-Branching|features], I find it +[http://steveko.wordpress.com/2012/02/24/10-things-i-hate-about-git/|somewhat +tiresome]. When it comes to version control, I firmly believe that, like +plumbing, it should not stick out the walls. Put another way, I don't +want to be an expert mechanic and have a full theoretical understanding +of internal combustion engines to be able to drive my car. + +While git may be perfect for the Linux kernel and other large projects +that have adopted it, it is overkill for Minx, which will never reach +that scale (in fact, I will probably be its only developer and user). So +I wanted a simpler DVCS that would afford me the +[http://www.catb.org/esr/writings/cups-horror.html|luxury of ignorance]. + +So far, I am happy with [http://fossil-scm.org/|Fossil]. I really like: + + * its built-in wiki, which makes it easy to produce version-controlled + documentation like this FAQ; + * its built-in bug tracker and the ability to link between check-in + comments and bug reports; + * its Web interface for viewing timelines, check-ins, diffs, wiki + pages, etc.; + * the ease of setting up a server for pushing and pulling code; + * the clean separation between repositories and working copies and + the fact that the entire repository is a single SQL database. + +Fossil is certainly not perfect. Some annoyances include: + + * Confusing merge markers and lack of tracking merge conflicts to + serve as reminders in case they're not explicitly resolved. This + means you have to read the output of an update command very + carefully and remember the files that have conflicts because Fossil + won't keep track of that for you. Additionally, since there's no + explicit conflict resolution step, you have to clean up the + temporary files related to the merge conflict yourself. + * No commit mails. The RSS feed doesn't quite work for me; I'd much + rather have an email show up in my inbox letting me know of commits + and other activity on a repo. Apparently, a hooks feature is in the + works; so this shortcoming may soon be a thing of the past. + * The schism between what you can do using the Web interface and the + command line. Some features of the system seem to be accessible + only via the Web interface (e.g., ammending check-in comments) + while others only work via the command line. In other words, the + software has two different and disjoint user interfaces, something + I find annoying and (for some reason) disconcerting. + * The command set seems to be a bit of a hodge-podge. Shouldn't + open be better termed checkout? Is close + really necessary? + * Minor bugs such as the inability to include enumerations in check-in + comments because lines with '#' in them get filtered out. + * Sometimes, documentation can be a little thin and some of it is + downright confusing (what the hell does the second paragraph of the + help merge command mean?). + +Despite these warts, all in all, I like Fossil. It doesn't impose an +undue cognitive load and allows me to concentrate on Minx development +instead of having to slow down to think about version control. + +I have never used [http://mercurial.selenic.com/|Mercurial]. So I cannot +comment on it. + +NOTE: If you like another version control system (SVN, git, +Mercurial, whatever), please continue to use it for your projects. I am +not trying to put them down in any way. I am only pointing out why I am +using Fossil for Minx. In other words, use what you like and don't tell +me about it; I don't care about your opinion. + +
+ + +

What's the deal with the dev1.x and rel1.x branches? Will there be +2.x, 3.x, etc. branches in the future?

+ +Yes, once I'm done with developing 1.x, I plan on a distinctly different +approach for the 2.x series. There will only be these two series, no 3.x +and beyond. + +
+ + +

Why are you using Doxygen to document the Minx API? Since Minx is +essentially a Python library, wouldn't it be better to use Sphinx, which +not only rhymes with Minx, but also seems to be a de facto standard now +for Python projects?

+ +I did consider [http://sphinx-doc.org/|Sphinx]. In fact, originally, I +wanted to put the Minx API documentation on +[http://readthedocs.org/|ReadTheDocs]. However, I balked once I realized +that getting Sphinx to automatically generate the documentation for +[#what-is-minxlib|minxlib], the C++ part of Minx, would be +[http://michaeljones.github.com/breathe/|a hassle]. + +Setting up a bridge between [http://sphinx-doc.org/|Sphinx] and +[http://www.doxygen.org/|Doxygen] seems like too much work without +enough of a payoff especially because: + + * Doxygen integrates well with [http://www.cmake.org/|CMake], which + is the build system for minxlib. + * Doxygen already supports both C++ and Python. While not + [http://code.foosel.org/doxypy/|entirely hassle-free], it's a lot + more straightforward compared to Sphinx + Breathe + Doxygen + + CMake. + * AFAICT [http://readthedocs.org/|ReadTheDocs] doesn't support + [http://michaeljones.github.com/breathe/|Breathe]. + * [http://fossil-scm.org/index.html/doc/trunk/www/embeddeddoc.wiki|Fossil] + can serve arbitrary HTML content from a repo (which means that I + can simply check-in release versions of Doxygen-generated API docs + into the Minx repo and dish 'em out from there, i.e., no need for + ReadTheDocs). ADDED wiki/home.wiki Index: wiki/home.wiki ================================================================== --- wiki/home.wiki +++ wiki/home.wiki @@ -0,0 +1,70 @@ +Home + +

About Minx

+ +Minx is a tiling X window manager. Here are some of its main features and +design goals: + + * Minimalism: Minx does not put any decorations on or around + top-level windows except for a simple border. It also does not + support task bars, panels, icons, menus, etc. All it does is + implement window layout policies that can be tailored to meet + personal tastes. + + * Customizability: Minx provides hooks into each and every + aspect of its operation so that end-users can alter its behaviour to + match their exact workflow. + + * Extensibility: Functionality not in the Minx core can be + added with minimal effort. For example, the bookmarks feature (aka + workspaces), although part of the core distribution, is, in fact, + implemented by taking advantage of Minx's ability to add arbitrary + state to the window manager object. In a similar vein, new layouts + are quite straightforward to implement. + + * Ease of use: The default configuration (hopefully) provides a + reasonable out-of-the-box experience (albeit different from + full-blown desktop environments such as GNOME or KDE and, of course, + subject to the constraints imposed by the desire to remain minimal). + However, changing the defaults is easy. + + * Keyboard centrism: All of Minx's actions can be accessed from + the keyboard, including window resizing and moving with as few as + 4-5 keystrokes. The mouse, however, is supported. + + * Documentation: Minx has no [#documentation|dark corners]. For + users, there are plenty of how-to's explaining common as well as + not-so-common configuration tasks. For developers, the code contains + comments explaining intent, rationale, etc. There are also Wiki + pages providing design overviews, explaining how the code is + structured, why it is that way, and, also, the development + methodology (branching policy, etc.). + +Enjoy. + +

News

+ + + + +

Documentation

+ +

For End-users

+ + * [/doc/ckout/wiki/faq.wiki|Frequently Asked Questions] + * [/doc/ckout/wiki/build.wiki|Building and Testing HOWTO] + * [/doc/ckout/wiki/hooks-howto.wiki|Hooks HOWTO] + * [/doc/ckout/wiki/hooks-list.wiki|List of Currently Supported Hooks] + * [/doc/ckout/wiki/key-bindings.wiki|Key Bindings HOWTO] + * [/doc/ckout/wiki/logging.wiki|Logging HOWTO] + +

For Developers

+ + * [/doc/ckout/wiki/design.wiki|Design and Implementation Notes] + * [/doc/ckout/wiki/process.wiki|Branching and Release Policy] + * [/doc/ckout/api/index.html|API documentation] + +

Legalese

+ + * [/doc/ckout/wiki/copyright.wiki|Copyright] + * [http://www.gnu.org/licenses/gpl-3.0-standalone.html|License] ADDED wiki/hooks-howto.wiki Index: wiki/hooks-howto.wiki ================================================================== --- wiki/hooks-howto.wiki +++ wiki/hooks-howto.wiki @@ -0,0 +1,185 @@ +Hooks HOWTO + +Instead of responding to different events in fixed ways, Minx allows +end-users to customize its responses by defining their own hook +functions. Thus, for example, when a new window is created, when a key +is pressed, and so on, Minx will trigger the hooks for that particular +event and act based on what the hooks return. + +This page shows you how to use Minx's hooking functionality and makes +various recommendations about [#dos-donts|do's and don'ts] when writing +your own hooks. + +

Introductory Examples

+ +The basic pattern for defining hooks is to create the +[/doc/ckout/api/classminx_1_1core_1_1wm_1_1wm.html|main window manager object] +and call the add() method of its hooks attribute +before calling the wm object's start() method. The +following subsections illustrate the procedure. + +

manage_hook

+ +Minx triggers the +[/doc/ckout/wiki/hooks-list.wiki#manage_hook|manage_hook] when +a new window is created. It will pass to each manage_hook a +[/doc/ckout/api/classminxlib_1_1window.html|minxlib.window] object and +expects a True or False return value to decide whether +the new window should be managed by Minx or ignored. + +Typically, in a manage_hook, you will examine the new window's +properties and decide whether to return True (to have Minx +manage the window) or False (to have the window ignored). For +example, the following start-up script makes Minx ignore +[http://members.dslextreme.com/users/billw/gkrellm/gkrellm.html|GKrellM] +but manage everything else: + + + #!/usr/bin/env python + + import minx + + def my_manage_hook(w): + prop = w.properties() + if 'gkrellm' in prop['name'].lower(): + return False + return True + + wm = minx.core.wm() + wm.hooks.add('manage_hook', my_manage_hook) + wm.start() + + +With that in place, you can position GKrellM at one of your screen +corners and Minx will leave it alone. + +

Multiple Hook Functions

+ +For something like the manage_hook, a single hook function is +more than enough (you can check for all windows you want Minx to ignore +using a regular expression). However, in general, you can define +multiple functions for any hook as shown below: + + + #!/usr/bin/env python + + import minx + + def ignore_foo(w): + prop = w.properties() + if prop['class'] == 'Xmessage' and prop['name'] == 'foo': + return False + return True + + def ignore_bar(w): + prop = w.properties() + if prop['class'] == 'Xmessage' and prop['name'] == 'bar': + return False + return True + + wm = minx.core.wm() + wm.hooks.add('manage_hook', ignore_foo) + wm.hooks.add('manage_hook', ignore_bar) + wm.start() + + +Now, if you start a few xmessages with the following commands: + + + xmessage -name foo 'Minx, please ignore me.' & + xmessage -name bar 'Minx, please ignore me too.' & + xmessage 'Minx, don't you dare ignore me!' & + + +The first two xmessage windows will be ignored while the third will be +managed. + +Of course, as mentioned above, you don't really need two +manage_hook functions to achieve the above. But this example +was only for illustration. + +

Short-circuiting a Hook Chain

+ +In some situations, when you have multiple functions for a given hook, +you may want to skip executing the remaining hooks after a particular +hook is executed. You can do this by raising a +[/doc/ckout/api/classminx_1_1core_1_1hooks_1_1short__circuit.html|short_circuit] +exception: + + + #!/usr/bin/env python + + import minx + from minx.core.hooks import short_circuit + + def ignore_foo(w): + prop = w.properties() + if prop['class'] == 'Xmessage' and prop['name'] == 'foo': + raise short_circuit(False) + return True + + def ignore_bar(w): + prop = w.properties() + if prop['class'] == 'Xmessage' and prop['name'] == 'bar': + return False + return True + + wm = minx.core.wm() + wm.hooks.add('manage_hook', ignore_foo) + wm.hooks.add('manage_hook', ignore_bar) + wm.start() + + +Now, if ignore_foo() finds its condition is satisfied, there's +no need to execute the remaining manage_hook functions. So, it +raises a short_circuit exception and passes back a +False through the exception to make Minx skip the remaining +manage_hook functions and to make it ignore the xmessage +named "foo". + +If, however, the new window is not an xmessage named "foo", +ignore_foo() will return True without raising an +exception and Minx will continue to the next function in the +manage_hook chain, viz., ignore_bar(). Since +ignore_bar() is the last manage_hook in the above +example, there's no point in it raising an exception (because there are +no more hooks left in the chain to short-circuit). + +Again, the above example is contrived because you will usually not need +more than one manage_hook. + + +

Hook Do's and Don'ts

+ +Here are some things to keep in mind when writing hook functions: + + * Do make it snappy! That is, don't launch some + long-winded, heavy-duty computation in a hook; that will make your + window manager very sluggish. Instead, keep your hooks short and + make them perform simple operations that complete quickly. + + * Don't ever raise an exception in a hook or let one "leak" + out. This will kill your window manager! Instead, if you are doing + something that can fail with an exception (e.g., opening a file), + be sure to handle it in the hook itself. + + NOTE: The only exception to raising exceptions from inside + hooks is when you want to short-circuit the remaining hooks in a + chain with a minx.core.hooks.short_circuit exception. But + even this, you should only do when absolutely necessary (in + general, it's bad form to short-circuit because various modules may + depend on their hooks being executed). + + * Do collapse multiple hooks into a single one where possible + (if, of course, that actually makes sense). In other words, + don't use two hooks where one will do; the more functions + you define for a hook, the more the window manager has to do + before it can respond to an event. For example, multiple + manage_hook functions are almost never necesary. + + * Don't override internal hooks. Remember that Minx uses hooks + not only to effect end-user customization, but also to define its + own internal event handlers. This means that you can actually hook + into all the low-level X event handling. However, doing that is + almost certainly a very bad idea; do it only if you need to and + only if you really know what you're doing. ADDED wiki/hooks-list.wiki Index: wiki/hooks-list.wiki ================================================================== --- wiki/hooks-list.wiki +++ wiki/hooks-list.wiki @@ -0,0 +1,350 @@ +Currently Supported Hooks + +Hooks are a central feature of Minx and are used internally to be able +to respond to various events generated by the X server. Minx also allows +end-users to define their own hooks and triggers those hooks in response +to different events/situations. + +This page documents all of Minx's currently supported hooks, both +internal ones and those meant for end-users. Here is the complete list: + +[#end-user-hooks|End-user Hooks] + # [#init_hook|init_hook] + # [#manage_hook|manage_hook] + # [#receptive_layout_hook|receptive_layout_hook] + +[#default-key-bindings|Default Key Bindings] + # [#focus_next|A-Tab] + # [#focus_prev|S-A-Tab] + # [#focus_kill|A-F4] + # [#focus_nuke|C-F4] + # [#launch_terminal|C-A-T] + # [#quit_wm|C-A-X] + +[#internal-hook|Internal Hooks] + # [#x_create_notify|x_create_notify] + # [#x_destroy_notify|x_destroy_notify] + # [#x_configure_request|x_configure_request] + # [#x_map_request|x_map_request] + # [#x_map_notify|x_map_notify] + # [#x_unmap_notify|x_unmap_notify] + # [#x_configure_notify|x_configure_notify] + # [#x_key_press|x_key_press] + +
+ + +

End-user Hooks

+ +Minx triggers these hooks to allow end-users to customize how the window +manager responds to different events. + + +

init_hook

+ +When you start Minx (by calling the +[/doc/ckout/api/classminx_1_1core_1_1wm_1_1wm.html|wm] object's start +method), it goes through the following sequence of steps: + + # Connect to the X server + # Configure X event masks and key bindings + # Trigger the init hook + # Manage existing windows + # Initiate the X event loop + +The intent of the init hook is to allow you to insert some custom +initialization before Minx starts managing windows and processing events +but after it has obtained a valid X connection. When the init hook is +called, you can be sure that the wm object's display +and root_windows attributes are available to you. The +display attribute is an instance of the +[/doc/ckout/api/classminxlib_1_1display.html|minxlib::display] +class and isn't something you would usually need to deal with directly. +However, wm.root_windows is almost certainly an attribute of +interest: it contains a list of +[/doc/ckout/api/classminxlib_1_1root__window.html|minxlib::root_window] +objects, one for each physical screen you have. + +Typically, in the init hook, you will create layouts for each screen as +shown below: + + + #!/usr/bin/env python + + import minx + + def my_init_hook(m): + scr = m.root_windows + m.layouts.add(minx.layout.tall(m, scr[0])) + if len(scr) > 1: + for w in scr[1:]: + m.layouts.add(minx.layout.full(m, w)) + + wm = minx.core.wm() + wm.hooks.add('init_hook', my_init_hook) + wm.start() + + +The above example sets up the first screen to use the tall layout and the +full layout on the remaining screens. Note that the tall layout has not +yet been implemented; it is used above merely for illustration. + +There is no per-screen restriction on the number of layouts nor any +requirement that a layout occupy the entire screen. We kept it simple for +the sake of the example. However, you can create multiple layouts per +screen, assign them to different parts of the screens, and use whatever +criteria that fit your needs and that you can code up. + + +

manage_hook

+ +Minx triggers this hook when a new window is created. It will pass to +each manage_hook a +[/doc/ckout/api/classminxlib_1_1window.html|minxlib.window] object and +expects a True or False return value to decide whether +the new window should be managed by Minx or ignored. + +Typically, in a manage_hook, you will examine the new window's +properties and decide whether to return True (to have Minx +manage the window) or False (to have the window ignored). For +example, the following start-up script makes Minx ignore +[http://members.dslextreme.com/users/billw/gkrellm/gkrellm.html|GKrellM] +but manage everything else: + + + #!/usr/bin/env python + + import minx + + def my_manage_hook(w): + prop = w.properties() + if 'gkrellm' in prop['name'].lower(): + return False + return True + + wm = minx.core.wm() + wm.hooks.add('manage_hook', my_manage_hook) + wm.start() + + +With that in place, you can position GKrellM at one of your screen +corners and Minx will leave it alone. + +Although you can define multiple functions for any hook, you should not +define more than are actually necessary. For the manage_hook, +you almost never need more than one in place. A single +manage_hook is enough to determine whether a given window +should be managed or ignored. For example, the following +manage_hook ignores GKrellM as well as xmessages named "foo" +and "bar": + + + #!/usr/bin/env python + + import minx + + def my_manage_hook(w): + prop = w.properties() + if 'gkrellm' in prop['name'].lower(): + return False + + c = prop['class'] + n = prop['name' ] + if c == 'Xmessage' and (n == 'foo' or n == 'bar'): + return False + + return True + + wm = minx.core.wm() + wm.hooks.add('manage_hook', my_manage_hook) + wm.start() + + +In a similar vein, you can use regular expressions and other such +techniques to check against a multitude of patterns to decide whether or +not to manage a given window without needing more than one +manage_hook. + + +

receptive_layout_hook

+ +Once a window gets past the [#manage_hook|manage hook], to manage it, +Minx will assign it to a layout. The receptive layout hook, allows you to +find or create a layout for each new window. Here is an example: + + + #!/usr/bin/env python + + import minx + + def my_receptive_layout_hook(w): + prop = w.properties() + if 'gimp' in prop['class'].lower(): + global wm + try: + L = wm.layouts.find('gimp') + return L + except minx.core.layman.unknown_layout: + parent = wm.root_windows[w.screen()] + return minx.layout.gimp(wm, parent) + return None + + wm = minx.core.wm() + wm.hooks.add('receptive_layout_hook', my_receptive_layout_hook) + wm.start() + + +The above example returns a new gimp layout for the first Gimp window and +reuses that layout for subsequent windows that Gimp creates. For all +other window types, it returns nothing, which makes Minx use its default +policies for layout selection (read all about that on the +[/doc/ckout/api/classminx_1_1core_1_1layman_1_1layman.html|layout +manager's API page]). + +Please note that there is no gimp layout. Thus, the above code won't +actually work; it is only meant to illustrate how the receptive layout +hook is meant to be used. + +
+ + +

Default Key Bindings

+ +Key bindings are simply hooks whose names follow the pattern described +[/doc/ckout/wiki/key-bindings.wiki|here]. Whenever a keyboard event +matching a key binding is generated, Minx will translate it into the +name of the key binding and then trigger the hooks corresponding to that +name. + +This section documents the default key bindings that Minx sets up. Of +course, you can change these key bindings or even remove them as +described in the [/doc/ckout/wiki/key-bindings.wiki|Key Bindings HOWTO]. + + +

A-Tab

+ +Bowing to convention, Minx uses ALT + Tab to move input focus to the +next top-level window in its list of windows. + + +

S-A-Tab

+ +ALT + SHIFT + Tab is setup to move input focus to the previous window in +the list of top-level windows currently being managed. + +NOTE: The order in which you specify modifiers is not +significant. Thus, the above key binding could just as well have been +A-S-Tab instead of S-A-Tab. The only reason we chose +S-A-Tab is because, with appropriate spacing, the code for this +and A-Tab lines up nicely. No deep mystery here. + + +

A-F4

+ +ALT + F4 kills the currently focused window. If the window supports the +WM_DELETE_WINDOW protocol, Minx will use that to kill the +window; otherwise, it will nuke the window using a brute force kill. + + +

C-F4

+ +CTRL + F4 kills the currently focused window using brute force. This key +binding is meant for those applications that advertise support for the +WM_DELETE_WINDOW protocol but fail to implement it properly, +i.e., ignore the WM_DELETE_WINDOW request without good cause. +With this key binding, Minx will use brute force to kill the window, +without bothering with niceties such as WM_DELETE_WINDOW. + + +

C-A-T

+ +CTRL + ALT + T will launch a terminal window. By default, Minx uses +xterm, but you can change that as shown below: + + + #!/usr/bin/env python + + import minx + + conf = minx.core.config() + conf.terminal = 'gnome-terminal' + + wm = minx.core.wm(conf) + wm.start() + + + +

C-A-X

+ +This key binding makes Minx get out of its event loop. Usually, this will +also quit the window manager (unless you have code in your Minx init file +after the call to wm.start()). + +
+ + +

Internal Hooks

+ +Minx defines and uses these hooks internally to respond to events +generated by the X server. In general, you should not override these +hooks unless you really, really need to and know what you're +doing. + + +

x_create_notify

+ +minx.core.xevents uses this hook to handle creation of new +windows. The hook is passed a +[/doc/ckout/api/structminxlib_1_1create__notify.html|create_notify] +event. Minx responds to this event by checking if the new window should +be managed or not and, if so, it adds the window to its internal data +structures. + +Minx triggers the [#manage_hook|manage_hook] as part of the +checks to determine whether or not to manage the new window. + + +

x_destroy_notify

+ +The minx.core.window module uses this hook to clear its +internal data structures when a top-level window is destroyed. + + +

x_configure_request

+ +minx.core.xevents uses this hook to honor X configure requests. + + +

x_map_request

+ +Both minx.core.xevents and minx.core.window define +x_map_request hooks. The minx.core.window hook handles +X map requests for top-level windows while the one in the +xevents module handles the map requests for non-top-level +windows. + + +

x_map_notify

+ +minx.core.xevents defines an x_map_notify hook to add +newly mapped windows to the window manager's +[/doc/ckout/api/classminx_1_1core_1_1focus__list_1_1focus__list.html|focus_list]. + + +

x_unmap_notify

+ +minx.core.xevents defines an x_unmap_notify hook to +remove an unmapped window from the window manager's focus list and to +focus the next window in the list. + + +

x_configure_notify

+ +The minx.core.window module defines an +x_configure_notify hook to be able to update some internal +state for a top-level window after it has been configured. + + +

x_key_press

+ +minx.core.xevents responds to key presses using this hook. ADDED wiki/img/Makefile Index: wiki/img/Makefile ================================================================== --- wiki/img/Makefile +++ wiki/img/Makefile @@ -0,0 +1,43 @@ +# +# Converting GraphViz diagrams to PNG images for use in Minx wiki pages +# + +# +# Copyright (C) 2012 The Minx Project Developers +# +# See the COPYRIGHT file at the top-level directory of this +# distribution for the full list of authors who have contributed to this +# project. +# + +# +# This file is part of Minx. +# +# Minx 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, either version 3 of the License, or (at your +# option) any later version. +# +# Minx 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 Minx. If not, see . +# + +# Rule for converting .dot and .asy files to .png +# NOTE: GraphViz's dot program and the Asymptote compiler should be in PATH. +.SUFFIXES: .asy .dot .png +.dot.png: + dot $< -Tpng -o $@ + +.asy.png: + asy -f png -o $@ $< + +# List of images to be built +TARGETS = logo.png object_diagram.png + +# Main rule for building all the targets +all: $(TARGETS) ADDED wiki/img/logo.asy Index: wiki/img/logo.asy ================================================================== --- wiki/img/logo.asy +++ wiki/img/logo.asy @@ -0,0 +1,105 @@ +// +// @file logo.asy +// @brief Draw Minx logo. +// +// This short Asymptote program draws the Minx logo, which, in ASCII +// art, looks as shown below: +// +// +-----+---+ +// | M | | +// +-+---+ X | +// |i| N | | +// +-+---+---+ +// +// The idea is to show that Minx is a tiling window manager. Each +// rectangle in the logo represents a tiled window. One of the tiles +// will be shown with an emphasized border (i.e., different color) to +// indicate that it has the input focus. +// + +// +// Copyright (C) 2012 The Minx Project Developers +// +// See wiki/copyright.wiki in the top-level directory of the Minx +// source distribution for the full list of authors who have +// contributed to this project. +// + +// +// This file is part of Minx. +// +// Minx 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, either version 3 of the License, or +// (at your option) any later version. +// +// Minx 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 Minx. If not, see . +// + +//------------------------------ MAIN ---------------------------------- + +// Init +real phi = (1 + sqrt(5))/2 ; // Golden Ratio +real logo_width = 1.25 * phi ; +real logo_height = logo_width/phi ; + +defaultpen(2) ; // draw thick window/tile borders + +// NOTE: Use picture size slightly larger than above width and height +// extents. Otherwise, top and right edges of the logo don't show up. +size(logo_width * 1.01cm, logo_height * 1.01cm) ; + +// The outer box (see ASCII art in file's header comment) +pair outer_bl = (0,0) ; +pair outer_br = (logo_width,0) ; +pair outer_tr = (logo_width, logo_height) ; +pair outer_tl = (0, logo_height) ; +draw(outer_bl--outer_br--outer_tr--outer_tl--cycle) ; + +// Vertical partition between M and X +pair vert_mx_b = outer_bl + (outer_br - outer_bl) * phi/(phi + 1) ; +pair vert_mx_t = (vert_mx_b.x, logo_height) ; +draw(vert_mx_b--vert_mx_t) ; + +// Horizontal partition between M and i and N +pair horz_min_l = outer_bl + (outer_tl - outer_bl)/(phi + 1) ; +pair horz_min_r = (vert_mx_b.x, horz_min_l.y) ; +draw(horz_min_l--horz_min_r) ; + +// Vertical partition between i and N +pair vert_in_b = outer_bl + (vert_mx_b - outer_bl)/(phi + 1) ; +pair vert_in_t = (vert_in_b.x, horz_min_l.y) ; +draw(vert_in_b--vert_in_t) ; + +// The labels within the above boxes +label("\textbf{\LARGE M}", + ((outer_bl.x + vert_mx_b.x)/2, (horz_min_l.y + outer_tl.y)/2)) ; +label("\textbf{i}", + ((outer_bl.x + vert_in_b.x)/2, ( outer_bl.y + horz_min_l.y)/2)) ; +label("\textbf{N}", + ((vert_in_b.x + vert_mx_b.x)/2, ( outer_bl.y + horz_min_l.y)/2)) ; +label("\textbf{\Large X}", + ((vert_mx_b.x + outer_br.x)/2, ( outer_br.y + outer_tr.y)/2)) ; + +// Finally, highlight one of the tiles to indicate that it has the input +// focus... +//draw(horz_min_l--horz_min_r--vert_mx_t--outer_tl--cycle, red) ; // M tile +draw(vert_mx_b--outer_br--outer_tr--vert_mx_t--cycle, red) ; // X tile + +//---------------------------------------------------------------------- + +//////////////////////////////////////////////// +// Editor config: // +//////////////////////////////////////////////// +// Local Variables: // +// indent-tabs-mode: nil // +// End: // +//////////////////////////////////////////////// +// vim: set expandtab shiftwidth=4 tabstop=4: // +//////////////////////////////////////////////// ADDED wiki/img/logo.png Index: wiki/img/logo.png ================================================================== --- wiki/img/logo.png +++ wiki/img/logo.png cannot compute difference between binary files ADDED wiki/img/object_diagram.dot Index: wiki/img/object_diagram.dot ================================================================== --- wiki/img/object_diagram.dot +++ wiki/img/object_diagram.dot @@ -0,0 +1,84 @@ +/* + @file object_diagram.dot + @brief High-level block diagram illustrating Minx architecture. +*/ + +/* + Copyright (C) 2012 The Minx Project Developers + + See wiki/copyright.wiki in the top-level directory of the Minx + source distribution for the full list of authors who have + contributed to this project. +*/ + +/* + This file is part of Minx. + + Minx 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, either version 3 of the License, or + (at your option) any later version. + + Minx 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 Minx. If not, see . +*/ + +//----------------------------- DIAGRAM -------------------------------- + +graph minx_main { + // minx.core objects + wm [label = "minx.core.wm" ] + focus_list [label = "minx.core.focus_list" ] + xevents [label = "minx.core.xevents" ] + hooks [label = "minx.core.hooks" ] + config [label = "minx.core.config" ] + layman [label = "minx.core.layman" ] + + // minx.layout objects + base [label = "minx.layout.base"] + full [label = "minx.layout.full"] + + // minxlib objects + display [label = "minxlib::display" ] + window [label = "minxlib::window" ] + rwindow [label = "minxlib::root_window"] + event [label = "minxlib::event" ] + keymap [label = "minxlib::keymap" ] + exception [label = "minxlib::exception" ] + logging [label = "minxlib::logging" ] + + // Connections between minx.core, minx.layout, and minxlib objects + wm -- layman -- base -- full + wm -- display --event + wm -- xevents -- base -- window + wm -- focus_list -- window + wm -- hooks + wm -- config + wm -- rwindow + + // Internal connections inside minxlib + display -- window -- rwindow -- logging + display -- exception + window -- keymap -- event -- logging + display -- logging + window -- logging + keymap -- logging +} + +//---------------------------------------------------------------------- + +/**********************************************/ +/* Editor config: */ +/**********************************************/ +/* Local Variables: */ +/* indent-tabs-mode: nil */ +/* c-basic-offset: 4 */ +/* End: */ +/**********************************************/ +/* vim: set expandtab shiftwidth=4 tabstop=4: */ +/**********************************************/ ADDED wiki/img/object_diagram.png Index: wiki/img/object_diagram.png ================================================================== --- wiki/img/object_diagram.png +++ wiki/img/object_diagram.png cannot compute difference between binary files ADDED wiki/key-bindings.wiki Index: wiki/key-bindings.wiki ================================================================== --- wiki/key-bindings.wiki +++ wiki/key-bindings.wiki @@ -0,0 +1,161 @@ +Key Bindings HOWTO + +Minx is a keyboard-centric window manager. Thus, it allows you to bind +arbitrary keystroke combinations to arbitrary functions. Minx uses its +[/doc/ckout/wiki/hooks-howto.wiki|hooks mechanism] to support key +bindings. This page explains how you can customize Minx by specifying +your own key bindings. + +

Key Binding Names

+ +A key binding is simply a hook with a special pattern to its name. This +pattern is shown below: + + + ^(([CAS]|M[1-5]?)-)*key$ + + +The above quasi-regular expression can be boiled down to: + + + [modifier-]*key + + +Thus, the name of a key binding consists of zero or more +hyphen-separated modifier key specifications followed by a single +non-modifier key. The supported modifiers are: + + * C for CTRL + * A for ALT + * S for SHIFT + * M for META + * M1 through M5 for Mod1 through Mod5 + +The key specification is the name of a keysym as returned by the X +server. Usually, these would be the printable characters on your +keyboard and the function and special keys (Home, Insert, Page Up, +etc.). Hopefully, the following examples clarify the above technobabble: + +
+ + + + + + + + + +
Name Key Binding
C-A-T CTRL + ALT + T
S-F1 SHIFT + F1
A-Tab ALT + Tab
M-R META + R
M5-S Mod5 + S
C-C CTRL + C
A-S-S ALT + SHIFT + S
+
+ +If you're familiar with [http://www.gnu.org/software/emacs/|Emacs], +Minx's key bindings specification should be quite familiar. + +

Adding Key Bindings

+ +Because key bindings are just specially named hooks, adding a new key +binding is simply a matter of adding a new hook. The following example +shows you how to get Minx to cycle input focus using F1 and SHIFT + F1 +and to launch The Gimp with Mod4 + G: + + + #!/usr/bin/env python + + import minx + + wm = minx.core.wm() + wm.hooks.add( 'F1', wm.focus_next) + wm.hooks.add('S-F1', wm.focus_prev) + wm.hooks.add('M4-G', lambda: wm.spawn('gimp')) + wm.start() + + +You are not restricted to tying keys to window manager functions. The +hook functions for key bindings can do pretty much anything you can code +up in Python. The following non-functional example illustrates the +principle: + + + #!/usr/bin/env python + + import minx + + def skynet(): + fire_all_nukes(get_launch_codes()) + wait_for_explosions() + if not kill_remaining_humans(): + invent_time_machine() + send_terminator_to_1984() + try: + kill_sarah_connor() + except unable_to_kill_sarah_connor: + just_finish_em_off_in_the_future() + + # Okay humans terminated; now... + get_those_pesky_bears() + + wm = minx.core.wm() + wm.hooks.add('C-A-Delete', skynet) + wm.start() + + +Now, when you press CTRL + ALT + DEL, you will end up destroying +humanity. Umm, that's pretty rude; don't do it, m'kay. + +NOTE: Initiating global mayhem will probably be fairly +time-consuming. And, as pointed out in the +[/doc/ckout/wiki/hooks-howto.wiki#dos-donts|Hooks HOWTO], hook +functions should be quick. Thus, you may want to execute the +skynet() function in the above example in another thread or +process so that the window manager can return to the more mundane task +of managing your windows. + +

Removing Key Bindings

+ +In the benign focus cycling example from the previous section, we setup +F1 and SHIFT + F1 to switch input focus from window to window. But Minx +already has +[/doc/ckout/wiki/hooks-list.wiki#default-key-bindings|defaults] for +that, viz., ALT + Tab and ALT + SHIFT + Tab. Once you've specified your +own key bindings for some purpose, you may want to disable the defaults +(if any). To do that, you will have to call the +[/doc/ckout/api/classminx_1_1core_1_1hooks_1_1hooks.html#a0be6448ad78e08223bc2174f8e20fc68|remove()] +method on the window manager object's hooks attribute. + +Here's some relevant code: + + + #!/usr/bin/env python + + import minx + + wm = minx.core.wm() + wm.hooks.add( 'F1', wm.focus_next) + wm.hooks.add('S-F1', wm.focus_prev) + wm.hooks.remove( 'A-Tab') + wm.hooks.remove('S-A-Tab') + wm.start() + + +Now, the only way to switch input focus will be to use F1 and +SHIFT + F1. Of course, after removing the default key bindings, you can +also rebind those keys to some other functions. + +

Renaming Key Bindings

+ +Another way to install your own key bindings for existing window manager +functions is to +[/doc/ckout/api/classminx_1_1core_1_1hooks_1_1hooks.html#a7184c493ebf93af23091df6fc8ec0bcf|rename] +the defaults: + + + #!/usr/bin/env python + + import minx + + wm = minx.core.wm() + wm.hooks.rename( 'A-Tab', 'F1') + wm.hooks.rename('S-A-Tab', 'S-F1') + wm.start() + ADDED wiki/logging.wiki Index: wiki/logging.wiki ================================================================== --- wiki/logging.wiki +++ wiki/logging.wiki @@ -0,0 +1,219 @@ +Logging + +To help with debugging, you can configure Minx to output log messages. +This page details how to go about doing this. + +

Simple Configuration

+ +

Logging to Console

+ +In your Minx start-up script, before creating a minx.core.wm +object, create a minx.core.config and customize its +logger attribute. The minx.core.config.logger +attribute is a complicated dict-of-dict-of-dict structure. While you can +mess around with that directly, the minx.core.config class +provides a few functions to ease the pain. + +A fairly common logging target is the console. The following start-up +script will achieve that: + + + #!/usr/bin/env python + + import minx + + conf = minx.core.config() + conf.log_to_console() + + minx.core.wm(conf).start() + + +That's it. If you save the above Python script as +~/.minx/init.py and run it as described in the +[/doc/dev1.x/wiki/build.wiki|building and testing instructions], you +will see log messages of various events output to STDERR. + +Remember: you will have to adjust the Python module search path to be +able to import the minx module. We have not shown that here. As +mentioned above, the [/doc/dev1.x/wiki/build.wiki|build and test HOWTO] +has the gory details. + +

Logging to a File

+ +Another very common logging target is a file. To send log messages to +a file, try something like this: + + + #!/usr/bin/env python + + import os + import minx + + conf = minx.core.config() + conf.log_to_file(os.path.join(os.environ['HOME'], '.minx', 'log')) + + minx.core.wm(conf).start() + + +With the above configuration, you should see Minx's log messages in +~/.minx/log. If the ~/.minx directory does not already +exist, you will have to create it. + +Again, remember to adjust the Python module search path to be able to +import minx. + +

Logging Verbosity

+ +When logging is turned on as shown above, by default, Minx will output +warnings, errors, and critical error messages. You can increase or +decrease the amount of log messages by adjusting the logging level as +shown below: + + + #!/usr/bin/env python + + import os, logging + import minx + + conf = minx.core.config() + conf.log_to_file(os.path.join(os.environ['HOME'], '.minx', 'log')) + conf.log_level(logging.DEBUG) + + minx.core.wm(conf).start() + + +Now, ~/.minx/log will contain debug and informational messages +in addition to warnings, errors, and critical messages. + +Let's say you only want to see errors and critical errors. Then, instead +of logging.DEBUG, use logging.ERROR. See +[http://docs.python.org/2/howto/logging.html#logging-levels|the Python +documentation] for the supported logging levels. + +
+ +

Complete Control of Logging Configuration

+ +If, for some reason, the above simple logging setups don't meet your +needs, you will have to get your hands dirty with the +minx.core.config.logger attribute's dict-of-dict-of-dict +structure. + +

Rotating Logs

+ +To start off, let's see how to setup a rotating set of log files: + + + #!/usr/bin/env python + + import os, logging + import minx + + conf = minx.core.config() + handler = conf.logger['handlers']['minx_handler'] + handler['class' ] = 'logging.handlers.RotatingFileHandler' + handler['filename' ] = os.path.join(os.environ['HOME'], '.minx', 'log') + handler['maxBytes' ] = 1024 * 1024 + handler['backupCount'] = 4 + + minx.core.wm(conf).start() + + +This will create a set of five rotating logs in ~/.minx: +log, log.1, log.2, log.3, and +log.4. The maximum size of each log will be 1MB. + +

minx.core.config.logger

+ +Now, let's have a closer look at exactly what is in the +minx.core.config.logger attribute. As mentioned earlier, +minx.core.config.logger is a dict-of-dict-of-dict. Its default +value is shown below (using indentation rather than braces): + + + minx.core.config.logger: + version: 1 + formatters: + minx_formatter: + format: %(asctime)s.%(msecs)03d %(levelname)-9s%(name)-18s%(lineno)-5s%(funcName)s\n %(message)s + datefmt: %Y-%m-%d %H:%M:%S + filters: + minx_filter: + (): _minx_logging_filter + handlers: + minx_handler: + class: logging.NullHandler + formatter: minx_formatter + filters: minx_filter + loggers: + minx: + handlers: minx_handler + + +

Log Message Formatter

+ +If you don't like the format of Minx's log messages, you can change it +by twiddling the minx_formatter dict as shown below: + + + #!/usr/bin/env python + + import os + import minx + + conf = minx.core.config() + formatter = conf.logger['formatters']['minx_formatter'] + formatter['format'] = '%(levelname)s: %(message)s' + conf.log_to_console() + + minx.core.wm(conf).start() + + +Take a look at the +[http://docs.python.org/2/library/logging.html#logrecord-attributes|Python +documentation] for the low-down on what you can put into the format +string and for the details of changing the timestamp. + +

The Log Message Filter

+ +The default log message format prints the logger name, function name, +and source file line number. All Minx loggers are rooted in the +minx logger namespace. It seems redundant to have the +"minx." prefix in each logger name. Thus, we use a filter to +remove this prefix from each log record. + +Furthermore, the function name and line number output by the minxlib +module are incorrect. So, the above-mentioned filter removes these +things as well for log messages emitted by minxlib. + +If you're using Minx in conjunction with another library that has +identically named modules, you may want to disable Minx's log message +filter, which you can do by deleting the filter from the handler as +shown below: + + + #!/usr/bin/env python + + import os + import minx + + conf = minx.core.config() + handler = conf.logger['handlers']['minx_handler'] + del handler['filters'] + conf.log_to_console() + + minx.core.wm(conf).start() + + +If you disable the filter, you will likely also want to adjust the +formatter to take the extra "minx." prefix for each log record. +And be aware that the function names and line numbers for messages from +minxlib will be incorrect. + +

Full Control

+ +Instead of modifying the logging parameters provided by +minx.core.config.logger, you can simply install your own +configuration dictionary. Have a look at the +[http://docs.python.org/2/library/logging.config.html#logging-config-dictschema|Python +documentation] for the gory details. ADDED wiki/milestones.wiki Index: wiki/milestones.wiki ================================================================== --- wiki/milestones.wiki +++ wiki/milestones.wiki @@ -0,0 +1,92 @@ +Milestones + +This page identifies some milestones for guiding and tracking development. We +note down potential release versions and their corresponding features (plus any +implementation ideas). This "plan" is not meant to be set in stone; it is merely +an aid to help record and crystallize ideas as Minx evolves... + + +

Version 0.1.0

+ +We should be able to cleanly switch keyboard focus from one top-level window to +the next with a single keystroke, say, F1. The first window to be created will +have the focus initially. Focus should never go "off-screen," i.e., to a hidden +window (e.g., the hidden top-level windows created by the XFCE4 Terminal +program) or revert to the root window while there are extant top-level windows +capable of receiving the input focus. + +

Testing

+ + # Start Xephyr. + # Start 2-4 terminals using --geometry to position them next to each other. + # Switch to the Xephyr window. + # Use F1 to switch between the terminals. If F1 does not seem to work, try + capturing the keyboard with CTRL + SHIFT; some versions of Xephyr seem to + need this. + +

Implementation Implications

+ +

minxlib

+ + * display and window classes to encapsulate relevant + portions of the Xlib API and export from C++ to Python. + * event class hierarchy to expose required Xlib events (create + notify, map, configure, keypress, etc.) from C++ to Python. + * Xlib error handling and exceptions forwarded from C++ to Python. + +

minx.core

+ + * wm class with basic event loop. This is the main end-user class + for starting Minx and maintaining the window manager state. + * window class to keep track of top-level windows (how many, which + one is focused, whether they've been mapped, resized, etc.). + * Basic hook infrastructure to be able to respond to events internally. + + +

Patches to Version 0.1.0

+ + * Version 0.1.1: Draw a one-pixel border around all top-level windows. + The focused window will have a red border, others will be white. + * Version 0.1.2: Allow users to customize border size and color. + * Version 0.1.3: Add logging support to enable debugging. + * Version 0.1.4: On start-up, query extant top-level windows and + manage them. + * Version 0.1.5: Add support for ignoring windows based on their + properties (class, name, etc.). + * Version 0.1.6: Allow users to customize key bindings. + * Version 0.1.7: Add function for killing windows. + * Version 0.1.8: Add function for launching new applications. + + +

Version 0.2.x: Layouts and Related Functionality

+ + * Version 0.2.0: The focused window should fill the entire + screen, i.e., the full layout. + * Version 0.2.1: The manual layout, i.e., laying out windows by + splitting horizontally or vertically (a la Emacs C-x 2 or C-x 3 + respectively). + * Version 0.2.2: Resizing tiled windows with the keyboard. + * Version 0.2.3: Laying out windows in rows and columns. + * Version 0.2.4: An implementation of XMonad's tall layout. + * Version 0.2.5: Laying out windows in a grid. + * Version 0.2.6: Support for moving windows between layouts. + * Version 0.2.7: Support for moving and resizing whole layouts. + * Version 0.2.8: Moving and resizing windows and layouts with + the mouse. + * Version 0.2.9: A specialized layout for The GIMP. + +

Remaining Milestones

+ + * Version 0.3.0: Snapshots (aka workspaces) via custom user state. + * Version 0.4.0: Focus policy infrastructure. + * Version 0.5.0: Defaults (so out-of-box user experience with minimal + config is sane). + * Version 0.6.0: Packaging for various systems (FreeBSD, + Debian-based, Arch). + * Version 0.7.0: Documentation for users and hackers. + * Version 0.8.0: Session management (if feasible or to the extent + feasible). + * Version 1.0.0: Various fixes/tweaks (e.g., equivalent of a --replace + option, sending window manager commands over local socket, helper apps such + as window switcher and resizer) and release into the wild as "ready for + prime-time." ADDED wiki/process.wiki Index: wiki/process.wiki ================================================================== --- wiki/process.wiki +++ wiki/process.wiki @@ -0,0 +1,64 @@ +Development Process + +

About this Page

+ +Minx is not a large project. In all likelihood, I will be the only +developer and its only user (well, there may be at most a handful of +others who might use it). Given that, the project is not well-served by a +heavy-duty process and nor does it have one. However, there is some +method to the madness... + +
+ +

Development and Branching Policy

+ +dev1.x is the main development branch. Usually, development will +proceed linearly on this branch. However, if there are others +contributing to the project and/or there is a major feature that will +continue for a while, we will create feature branches off off +dev1.x. When the feature is done, the branch will be merged onto +dev1.x and then, usually, closed. + +rel1.x is the release branch. When we are ready to make a release, +we will merge from dev1.x and then tag (after tweaking as +required). + +

Release-only Artifacts

+ +There are certain things that live only on the release branch. For +example, the version numbering API, the changelog, release notes, and so +on. These things are never merged into the development branches. +Consequently, no development ever occurs on the rel1.x branch. We +only merge to rel1.x, never from it to dev1.x or some other +branch. + +

Hotfixes

+ +If we find a bug on rel1.x that needs an urgent fix, we will +branch off off dev1.x at the point where that particular release +was made, apply the fix on this "hotfix" branch and then merge the hotfix +branch to both dev1.x and rel1.x (tagging the release by +increasing the version number's patch level). + +Although this approach is a little more complicated and results in a +slightly messier revision history, it allows us to maintain certain +release-only items (changelog, version numbering API, etc.) solely on +rel1.x without accidentally getting them onto a development branch +(where they do not belong). + +
+ +

Making a Release

+ + # Merge from dev1.x to rel1.x. + # Build and test. + # Build API docs and check-in. + # Replace all ckout in docs with rel1.x. + # Update wiki/changelog.wiki and wiki/relnotes.wiki. + # Update News section of wiki/home.wiki plus any other + docs that need updating. + # Update version number in minxlib/version.cc and commit --tag + as vA.B.C (where major number A, minor number B, and patch level C + all match the new version number assigned in + minxlib/version.cc). + # Push to [http://chiselapp.com|chiselapp]. ADDED wiki/todo.wiki Index: wiki/todo.wiki ================================================================== --- wiki/todo.wiki +++ wiki/todo.wiki @@ -0,0 +1,17 @@ +TODO + +

Current TODO List

+ +To get to [/doc/dev1.x/wiki/milestones.wiki#v02x|version 0.2.0], the +following tasks have to be completed: + +

DONE

+ + * Update design notes. + + * Update home page. + + * Add rectangle to full constructor. Test by leaving some space on the + right on the right monitor of a dual-monitor test. + +

PENDING