diff --git a/khal/api.py b/khal/api.py new file mode 100644 index 000000000..1db9595ee --- /dev/null +++ b/khal/api.py @@ -0,0 +1,3 @@ +from .ui.colors import register_color_theme + +__all__ = ["register_color_theme"] diff --git a/khal/cli.py b/khal/cli.py index 538ff9d4d..0c6132b34 100644 --- a/khal/cli.py +++ b/khal/cli.py @@ -29,6 +29,7 @@ import click import click_log +import xdg from . import __version__, controllers, khalendar from .exceptions import FatalError @@ -252,6 +253,7 @@ def cli(ctx, config): logger = logging.getLogger('khal') logger.handlers = [logging.FileHandler(ctx.logfilepath)] prepare_context(ctx, config) + find_load_plugins() @cli.command() @multi_calendar_option @@ -512,15 +514,21 @@ def interactive(ctx, include_calendar, exclude_calendar, mouse): def interactive_cli(ctx, config, include_calendar, exclude_calendar, mouse): '''Interactive UI. Also launchable via `khal interactive`.''' prepare_context(ctx, config) + find_load_plugins() if mouse is not None: ctx.obj['conf']['default']['enable_mouse'] = mouse - controllers.interactive( - build_collection( - ctx.obj['conf'], - multi_calendar_select(ctx, include_calendar, exclude_calendar) - ), - ctx.obj['conf'] - ) + try: + controllers.interactive( + build_collection( + ctx.obj['conf'], + multi_calendar_select(ctx, include_calendar, exclude_calendar) + ), + ctx.obj['conf'] + ) + except FatalError as error: + logger.debug(error, exc_info=True) + logger.fatal(error) + sys.exit(1) @cli.command() @multi_calendar_option @@ -698,5 +706,18 @@ def configure(ctx): return cli, interactive_cli +def find_load_plugins(): + """Find and load all plugins in the plugin directory.""" + # check all folders in $XDG_DATA_HOME/khal/plugins if they contain a + # __init__.py file. If so, import them. + plugin_dir = os.path.join(xdg.BaseDirectory.xdg_data_home, 'khal', 'plugins') + if not os.path.isdir(plugin_dir): + return + sys.path.append(plugin_dir) + for plugin in os.listdir(plugin_dir): + if os.path.isfile(os.path.join(plugin_dir, plugin, '__init__.py')): + logger.debug(f'loading plugin {plugin}') + __import__(plugin) + main_khal, main_ikhal = _get_cli() diff --git a/khal/settings/khal.spec b/khal/settings/khal.spec index ec4e8eefd..951a169aa 100644 --- a/khal/settings/khal.spec +++ b/khal/settings/khal.spec @@ -258,7 +258,7 @@ blank_line_before_day = boolean(default=False) # # __ http://urwid.org/manual/displayattributes.html # .. _github: https://github.com/pimutils/khal/issues -theme = option('dark', 'light', default='dark') +theme = string(default='dark') # Whether to show a visible frame (with *box drawing* characters) around some # (groups of) elements or not. There are currently several different frame diff --git a/khal/ui/__init__.py b/khal/ui/__init__.py index 806501b43..cb7e222ae 100644 --- a/khal/ui/__init__.py +++ b/khal/ui/__init__.py @@ -31,7 +31,7 @@ from .. import utils from ..khalendar import CalendarCollection -from ..khalendar.exceptions import ReadOnlyCalendarError +from ..khalendar.exceptions import FatalError, ReadOnlyCalendarError from . import colors from .base import Pane, Window from .editor import EventEditor, ExportDialog @@ -1356,6 +1356,17 @@ def start_pane( color_mode: Literal['rgb', '256colors']='rgb', ): """Open the user interface with the given initial pane.""" + + # we can't use configobj to do validation of theme for us, because when the + # config is passed, we haven't loaded plugins yet. We could load plugins + # earlier, but then logging wouldn't work at plugin loading time. + # We do this early, so that logger messages still get shown in a regular way + theme = colors.themes.get(pane._conf['view']['theme']) + if theme is None: + logger.fatal(f'Invalid theme {pane._conf["view"]["theme"]} configured') + logger.fatal(f'Available themes are: {", ".join(colors.themes.keys())}') + raise FatalError + quit_keys = quit_keys or ['q'] frame = Window( @@ -1406,7 +1417,6 @@ def emit(self, record): logger.addHandler(header_handler) frame.open(pane, callback) - theme = getattr(colors, pane._conf['view']['theme']) palette = _add_calendar_colors( theme, pane.collection, color_mode=color_mode, base='calendar', attr_template='calendar {}', diff --git a/khal/ui/colors.py b/khal/ui/colors.py index 5cffb116d..055a56364 100644 --- a/khal/ui/colors.py +++ b/khal/ui/colors.py @@ -107,3 +107,7 @@ ] themes: Dict[str, List[Tuple[str, ...]]] = {'light': light, 'dark': dark} + + +def register_color_theme(name: str, theme: List[Tuple[str, ...]]): + themes[name] = theme