#!/usr/bin/python
# -*- coding: utf-8 -*-
#
#    bl-exit: Bunsenlabs exit dialog, offering various exit options
#     via both GUI and CLI
#    Copyright (C) 2012 Philip Newborough  <corenominal@corenominal.org>
#    Copyright (C) 2016 xaos52  <xaos52@gmail.com>
#    Copyright (C) 2017 damo  <damo@bunsenlabs.org>
#
#    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 3 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 <http://www.gnu.org/licenses/>.

from __future__ import print_function

import os
display = os.environ.get('DISPLAY') is not None

import sys
import getpass
import subprocess
import dbus
import struct
import ConfigParser

__me__ = 'bl-exit'
__version__ = '2.2.1'

# Translate command-line option to method - command line only
actionToMethod = dict(
	cancel='Cancel', c='Cancel',
	logout='Logout', l='Logout',
	suspend='Suspend', s='Suspend',
	hybridsleep='HybridSleep', y='HybridSleep',
	hibernate='Hibernate', i='Hibernate',
	reboot='Reboot', b='Reboot',
	poweroff='PowerOff', p='PowerOff'
)


class CanDoItError(Exception):
	pass


class BlexitBase(object):

	def __init__(self):
		self.dbus_iface = None

	def setup_dbus_connection(self):
		try:
			bus = dbus.SystemBus()
			dbus_object = bus.get_object('org.freedesktop.login1',
										 '/org/freedesktop/login1')
			self.dbus_iface = dbus.Interface(dbus_object,
											 'org.freedesktop.login1.Manager')
		except bus.DBusException as e:
			self.on_error(str(e))

	def can_do_action(self, action):
		# There is no 'CanLogout' method
		if action == "Logout":
			return "yes"
		actionMethod = "Can{}".format(action)
		response = self.send_dbus(actionMethod)
		return str(response)

	def do_action(self, action):
		print_message("do_action: {}".format(action))
		self.send_dbus(action)

	def send_dbus(self, method):
		try:
			if self.dbus_iface is None:
				self.setup_dbus_connection()
			if method[:3] == "Can":
				command = "self.dbus_iface.{}()".format(method)
			else:
				command = "self.dbus_iface.{}(['True'])".format(method)
			response = eval(command)
			return str(response)
		except dbus.DBusException as e:
			self.on_error(str(e))

	def on_error(self, string):
		print_message("{} {}".format(__me__, string))
		sys.exit(1)

	def on_warning(self, string):
		print_message("{} {}".format(__me__, string))

	def openbox_exit(self):
		subprocess.check_output(["openbox", "--exit"])

	def logout(self):
		try:
			self.openbox_exit()
		except subprocess.CalledProcessError as e:
			self.on_error(e.output)

	def action_from_command_line(self, action):
		try:
			self.do_action(action)
		except (subprocess.CalledProcessError, CanDoItError, KeyError) as e:
			self.on_error(str(e))

	def main(self):
		opts = get_options()
		if opts.logout:
			self.logout()
		else:
			if opts.suspend:
				action = "suspend"
			elif opts.hibernate:
				action = "hibernate"
			elif opts.hybridsleep:
				action = "hybridsleep"
			elif opts.reboot:
				action = "reboot"
			elif opts.poweroff:
				action = "poweroff"
			self.setup_dbus_connection()
			self.action_from_command_line(actionToMethod[action])

if display:
	"""Testing for display here because we want to be able to run the script
	in a non-graphical environment as well. Without the test, importing
	gtk.Window in a non-graphical environment spits out some errors and crashes
	the application."""
	import pygtk
	pygtk.require('2.0')
	import gtk
	from time import sleep


	class Blexit(BlexitBase):
		"""A dialog offering the user to log out, suspend, reboot or shut down.
		"""
		def _key_press_event(self, widget, event):
			keyval = event.keyval
			keyval_name = gtk.gdk.keyval_name(keyval)
			state = event.state
			alt = (state & gtk.gdk.MOD1_MASK)
			# Suspend Shortcut
			if alt and keyval_name == 's':
				Blexit.suspend_action(self, widget)
			# Logout Shortcut
			elif alt and keyval_name == 'l':
				Blexit.logout_action(self, widget)
			# Shutdown shortcut
			elif alt and keyval_name == 'p':
				Blexit.shutdown_action(self, widget)
			# Reboot Shortcut
			elif alt and keyval_name =='b':
				Blexit.reboot_action(self, widget)
			# Hibernate Shortcut
			elif alt and keyval_name == 'i':
				Blexit.hibernate_action(self, widget)
			# Hybrid Sleep Shortcut
			elif alt and keyval_name == 'y':
				Blexit.hybridsleep_action(self,  widget)
			# Cancel Shortcut
			elif keyval_name == 'Escape':
				Blexit.cancel_action(self, widget)
			else:
				return False
			return True

		def __init__(self, cp, config_file):
			BlexitBase.__init__(self)
			self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
			self.window.set_name('blexit')
			self.cp = cp
			self.config_file = config_file
			self.debug = False
			self.selectedAction = None
			self.window.set_decorated(True)
			self.window.connect("delete_event", self.destroy)
			self.window.connect("destroy_event", self.destroy)
			self.window.connect("key-press-event", self._key_press_event)
			self.window.set_resizable(False)
			self.window.set_keep_above(True)
			self.window.stick()
			self.window.set_position(gtk.WIN_POS_CENTER)
			windowicon = self.window.render_icon(gtk.STOCK_QUIT, gtk.ICON_SIZE_DIALOG)
			self.window.set_icon(windowicon)

		def configure(self):
			if self.config_file:
				try:
					self.cp.read(self.config_file)
				except ConfigParser.ParsingError as e:
					print_message("{}: {}".format(__me__, str(e)))
					sys.exit(1)
			else:
				# No config file present:
				# self.cp.add_section("Default")
				# NOTE: add_section raises value error when the section name
				# evaluates to DEFAULT (or any of its case-insensitive
				# variants)
				for section in "hibernate", "hybridsleep":
					self.cp.add_section(section)
					self.cp.set(section, "show", "never")
				for section in "cancel", "logout", "suspend", "reboot", "poweroff":
					self.cp.add_section(section)
					self.cp.set(section, "show", "always")

		def set_custom_style(self):
			try:
				stylesdir = self.cp.get('style', 'dir')
				rcfile = self.cp.get('style', 'rcfile')
				stylerc = os.path.join(os.path.dirname(self.config_file), stylesdir, rcfile)
				if not os.path.isfile(stylerc):
					self.on_debug("custom style rc file does not exist")
					return None
				gtk.rc_parse(stylerc)
				settings = gtk.settings_get_for_screen(self.window.get_screen())
				gtk.rc_reset_styles(settings)
			except:
				self.on_debug("custom style not configured or parse error")
				pass

		def construct_ui(self):
			self.window.set_title("Log out " + getpass.getuser() + "?")
			self.window.height = 80

			# Cancel key (Escape)
			accelgroup = gtk.AccelGroup()
			key, mod = gtk.accelerator_parse('Escape')
			accelgroup.connect_group(key, mod, gtk.ACCEL_VISIBLE,
									 gtk.main_quit)
			self.window.add_accel_group(accelgroup)

			self.button_width = 100
			self.button_height = 50
			self.button_border_width = 4
			self.window.set_border_width(10)
			self.button_box = gtk.HButtonBox()
			self.button_box.set_spacing(0)
			self.button_box.set_layout(gtk.BUTTONBOX_SPREAD)
			self.build_button_visibility_array()
			visible_button_count = 0
			for button in self.bva:
				(action, label, actionfunc, method, show, onError) = button
				if not show == 0:
					visible_button_count += 1
					self.add_button(show, actionfunc, label=label)
			self.status = gtk.Label()
			label_box = gtk.HBox()
			label_box.pack_start(self.status)

			# allow for username of about twenty characters: len(title) + 200
			# approximation: counting characters, not size of rendered string
			if visible_button_count == 0:
				self.window.width = len(title) + 300
			elif visible_button_count <= 2:
				self.window.width = max (
					(self.button_width + 10) * visible_button_count,
					len(title) + 300)
			else:
				self.window.width = -1
			self.window.set_size_request(self.window.width, self.window.height)
			vbox = gtk.VBox()
			vbox.pack_start(self.button_box)
			vbox.pack_start(label_box)
			self.window.add(vbox)
			self.window.show_all()

		def destroy(self, widget=None, event=None, data=None):
			self.window.hide_all()
			gtk.main_quit()

		def build_button_visibility_array(self):
			"""Determine button visibily using bl-exit configuration file.
			Build self.bva, an array of tuples, one entry per button,
			containing (action, label, actionfunction, actionMethod, show,
			onerror)
			"""
			self.bva = []
			bva = [
				('cancel', '_Cancel', self.cancel_action),
				('logout', '_Log out', self.logout_action),
				('suspend', '_Suspend', self.suspend_action),
				('hibernate', 'H_ibernate', self.hibernate_action),
				('hybridsleep', 'H_ybrid sleep', self.hybridsleep_action),
				('reboot', 'Re_boot', self.reboot_action),
				('poweroff', '_Power off', self.shutdown_action)
			]
			show_values = dict(never=0, always=1, maybe=2)
			"""Values that the 'show' keyword can take in the configuration
			file."""
			onerror_values = dict(novisual=0, visual=1)
			"""Values that the 'onerror' keyword can take in the configuration
			file."""
			# Per button default settings
			per_button_show_defaults = dict(
				cancel='always',
				logout='always',
				suspend='always',
				hibernate='never',
				hybridsleep='never',
				reboot='always',
				poweroff='always'
			)
			for (action, label, actionfunction) in bva:
				# Defaults.
				show = show_values[per_button_show_defaults[action]]
				onError = onerror_values['novisual']
				for section in ['default', action]:
					try:
						try:
							getshow = self.cp.get(section, 'show')
							if getshow in show_values:
								show = show_values[getshow]
								if show == 2:
									candoit = self.can_do_action(
										actionToMethod[action])
									if not candoit == 'yes':
										show = 3
							self.on_debug("config section {} show={}".format(section,show))
						except ConfigParser.NoOptionError as e:
							self.on_debug("config section {}  no option show".format(section))
							pass

						try:
							getonerror = self.cp.get(section, 'onerror')
							if getonerror in onerror_values:
								onError = onerror_values[getonerror]
							self.on_debug("config section {} onerror={}".format(section,onError))
						except ConfigParser.NoOptionError as e:
							self.on_debug("config section {} no option onerror".format(section))
							pass
					except ConfigParser.NoSectionError as e:
						self.on_debug("config section {} not present".format(section))
						pass

				self.bva.append(tuple([action, label, actionfunction,
									   actionToMethod[action], show,
									   onError]))

		def main(self):
			self.configure()
			self.set_custom_style()
			self.construct_ui()
			gtk.main()

		def add_button(self, show, action, label=None, stock=None):
			if stock is not None:
				button = gtk.Button(stock=stock)
			else:
				button = gtk.Button(label=label)
			button.set_size_request(self.button_width, self.button_height)
			if show == 3:
				button.set_sensitive(False)
			button.set_border_width(self.button_border_width)
			button.connect("clicked", action)
			self.button_box.pack_start(button)

		def disable_buttons(self):
			self.button_box.foreach(lambda button:
									button.set_sensitive(False))

		def cancel_action(self, button):
			self.disable_buttons()
			gtk.main_quit()

		def get_onerror(self):
			onerror = 0
			if self.selectedAction is not None:
				for item in self.bva:
					(action, label, actionfunction, actionMethod, show,
					 onerror) = item
					if action == self.selected_action:
						return onerror
			return onerror

		def on_error(self, e):
			onerror = self.get_onerror()
			if onerror == 0:
				print_message("{}: {}".format(__me__, str(e)))
				sys.exit(1)
			else:
				emDialog = gtk.MessageDialog(parent=None, flags=0,
											 type=gtk.MESSAGE_INFO,
											 buttons=gtk.BUTTONS_OK,
											 message_format=None)
				if emDialog:
					emDialog.set_markup("{}".format(e))
					emDialog.run()

		def on_warning(self, e):
			e = "{} {}".format(__me__, str(e))
			if self.debug:
				e = "DEBUG {}".format(e)
			print_message(e)

		def on_debug(self, e):
			if self.debug:
				self.on_warning(e)

		def cancel_action(self, button):
			self.destroy()

		def logout_action(self, button):
			self.disable_buttons()
			self.selected_action = 'logout'
			self.status.set_label("Exiting Openbox, please standby...")
			self.openbox_exit()
			self.destroy()

		def suspend_action(self, button):
			self.disable_buttons()
			self.selected_action = 'suspend'
			self.status.set_label("Suspending, please standby...")
			self.do_action("Suspend")
			self.destroy()

		def hibernate_action(self, button):
			self.disable_buttons()
			self.selected_action = 'hibernate'
			self.status.set_label("Hibernating, please standby...")
			self.do_action("Hibernate")
			self.destroy()

		def hybridsleep_action(self, button):
			self.disable_buttons()
			self.selected_action = 'hybridsleep'
			self.status.set_label("Hibernating + Sleeping, please standby...")
			self.do_action("HybridSleep")
			self.destroy()

		def reboot_action(self, button):
			self.disable_buttons()
			self.selected_action = 'reboot'
			self.status.set_label("Rebooting, please standby...")
			self.do_action("Reboot")
			self.destroy()

		def shutdown_action(self, button):
			self.disable_buttons()
			self.selected_action = 'poweroff'
			self.status.set_label("Shutting down, please standby...")
			self.do_action("PowerOff")
			self.destroy()


	class BlexitThemeDetail():
		"""
		:param value
		Value for the theme detail
		:param required
		When a theme detail is not configured for a theme, and the detail
		is configured as being required, the default detail value is substituted.
		When required is False, nothing is substituted and the detail is not set.
		Sane defaults are used.
		:param value_type
		'int' and 'float' are recognized.
		All else defaults to 'string
		"""
		def __init__(self, value, required, value_type):
			self.value = value
			self.required = required
			self.value_type = value_type

	default_theme_settings = dict(
		name=BlexitThemeDetail('Dark Theme', False, 'string'),
		author=BlexitThemeDetail('MerlinElMago', False, 'string'),
		dialogHeight=BlexitThemeDetail(120, False, 'int'),
		sleepDelay=BlexitThemeDetail(0.003, False, 'float'),
		overallOpacity=BlexitThemeDetail(100, False, 'int'),
		buttonSpacing=BlexitThemeDetail(10, False, 'int'),
		iconpath=BlexitThemeDetail('/usr/share/bunsen-exit', True, 'string'),
		buttonImageCancel=BlexitThemeDetail('cancel.png', False, 'string'),
		buttonImagePowerOff=BlexitThemeDetail('poweroff.png', False, 'string'),
		buttonImageReboot=BlexitThemeDetail('reboot.png', False, 'string'),
		buttonImageSuspend=BlexitThemeDetail('sleep.png', False, 'string'),
		buttonImageLogout=BlexitThemeDetail('logout.png', False, 'string'),
		buttonImageHybridSleep=BlexitThemeDetail('hibernate.png', False, 'string'),
		buttonImageHibernate=BlexitThemeDetail('hibernate.png', False, 'string'),
		windowWidthAdjustment=BlexitThemeDetail(0, False, 'float')
	)


	class BlexitTheme():
		def __init__(self, theme, settings, blexit):
			self.theme = theme
			self.settings = settings
			self.blexit = blexit

		def set_detail(self, key, value):
			self.settings[key] = value

		def set_details_from_config(self, cp, default_theme):
			for key in default_theme_settings.iterkeys():
				default_theme_detail = default_theme_settings[key]
				try:
					config_value = cp.get(self.theme, key)
				except ConfigParser.NoOptionError as e:
					self.blexit.on_debug("theme config option {} is not set for theme {}".format(key, self.theme))
					config_value = None
					pass
				if config_value is not None:
					if default_theme_detail.value_type == 'int':
						try:
							config_value = int(config_value)
						except:
							self.blexit.on_debug("theme config option {} is not an int".format(key, self.theme))
							config_value = default_theme_detail.value
					elif default_theme_detail.value_type == 'float':
						try:
							default_theme_detail.config_value =float(config_value)
						except:
							self.bl-exit.on_debug("theme config option {} is not a float".format(key, self.theme))
							config_value = default_theme_detail.value
				else:
					if default_theme_detail.required:
						config_value = default_theme_detail.value
				if config_value is not None:
					self.set_detail(key, config_value)


	class BlexitMerlin(Blexit):
		"""A dialog offering the user to log out, suspend, reboot or shut down.
		With a graphical UI initially developed by MerlinElMago.
		:param cp: ConfigParser instance
		:param config_file: path to blexit config file
		"""
		def __init__(self, cp, config_file):
			Blexit.__init__(self, cp, config_file)
			self.window.set_decorated(False)
			self.tooltips = gtk.Tooltips()

		def configure(self):
			Blexit.configure(self)
			default_theme = BlexitTheme('dark', default_theme_settings, self)
			self.configured_theme = BlexitTheme(self.cp.get('theme', 'theme'), dict(), self)
			self.configured_theme.set_details_from_config(self.cp, default_theme)
			print_message('Loading theme \'' + self.configured_theme.settings.get('name', self.configured_theme.theme) + '\' by ' +
						  self.configured_theme.settings.get('author', 'not set'))

		def construct_ui(self):
			# get width of the monitor where the cursor is
			width = 800 # fallback width
			try:
				display=gtk.gdk.Display(gtk.gdk.get_display())
				screen, x, y, flags=display.get_pointer()
				curmon = screen.get_monitor_at_point(x, y)
				_, _, screenwidth, _ = screen.get_monitor_geometry(curmon)
			except:
				self.bl_exit.on_error('Error in construct_ui: Not running under X')
			finally:
				del x, y, display, screen, curmon

			try:
				_width_adjustment = float(self.configured_theme.settings.get('windowWidthAdjustment', 0))
				if _width_adjustment > 0:
					width = int( screenwidth*_width_adjustment)
				if width > screenwidth:
					width = -1
			except:
				self.on_debug('Problem with windowWidthAdjustment')

			# self.icon_heights is initialized here, and appended to in add_button
			# Delay setting window dimensions until after building self.bva
			try:
				self.icon_heights = [int(self.configured_theme.settings.get('dialogHeight'))]
			except:
				self.on_debug("dialogHeight is not set or is not an int")
				self.icon_heights = []
				pass
			self.icon_widths = []
			# Cancel key (Escape)
			accelgroup = gtk.AccelGroup()
			key, mod = gtk.accelerator_parse('Escape')
			accelgroup.connect_group(key, mod, gtk.ACCEL_VISIBLE, gtk.main_quit)
			self.window.add_accel_group(accelgroup)

			self.button_box = gtk.HBox()
			self.button_box.set_spacing(0)

			try:
				self.button_box.set_size_request(-1, int(self.configured_theme.settings.get('dialogHeight', -1)))
			except:
				self.on_debug("dialogHeight is not set or is not an int")
				pass

			try:
				self.button_box.set_spacing(int(self.configured_theme.settings.get('buttonSpacing', 0)))
			except:
				self.on_debug("dialogHeight is not set or is not an int")
				pass

			self.build_button_visibility_array()
			for button in self.bva:
				(action, label, actionfunc, method, show, onError) = button
				if not show == 0:
					icon_width = self.add_button(show, actionfunc, label=label, btype=actionToMethod[action])
					#get list of returned icon widths
					self.icon_widths.append(icon_width)
			self.status = gtk.Label()

			if len(self.icon_heights) > 0:
				self.dialogHeight = max(self.icon_heights)
			else:
				self.dialogHeight = -1
				
			# set minimum window width
			icons_total_w = sum(self.icon_widths)
			spaces = self.configured_theme.settings.get('buttonSpacing', 0)
			spacing_total = int(len(self.icon_widths))*spaces*2
			
			if sum(self.icon_widths) > width:
				width = sum(self.icon_widths) + spacing_total
			
			self.window.set_size_request(width, self.dialogHeight)

			vbox = gtk.VBox()
			vbox.pack_start(self.button_box)
			self.window.add(vbox)
			self.window.set_opacity(0)
			self.window.show_all()
			try:
				for o in range(1, int(self.configured_theme.settings.get('overallOpacity'))):
					sleep(float(self.configured_theme.settings.get('sleepDelay')))
					while gtk.events_pending():
						gtk.main_iteration(False)
						self.window.set_opacity(float(o)/100.0)
			except:
				self.on_debug("Opacity is not fully configured")
				pass
			self.window.set_keep_above(True)

		def main(self):
			self.configure()
			self.set_custom_style()
			self.construct_ui()
			gtk.main()

		def add_button(self, show, action, label=None, btype=None):

			def find_image_file_for_button():
				_filename = self.configured_theme.settings.get('buttonImage' + str(btype), 'nonexistant')
				if _filename is None:
					return None
				_iconpath = self.configured_theme.settings.get('iconpath')
				if _iconpath is None:
					return None
				for _dir in _iconpath.split(os.pathsep):
					_dir = os.path.expanduser(_dir)
					if os.path.exists(os.path.join(_dir, _filename)):
						return os.path.join(_dir, _filename)

			button = gtk.Button()
			button.set_relief(gtk.RELIEF_NONE)
			image = gtk.Image()
			_filename = find_image_file_for_button()
			if _filename is not None:
				image.set_from_file(_filename)
				icon_w,icon_h = self.get_image_info(_filename)
				self.icon_heights.append(icon_h)
			else:
				image.set_from_file('/usr/share/gtk-doc/html/pygtk/icons/stock_broken_image_24.png')
				self.on_warning("image file for '{}' not found.".format(str(btype)))
			button.set_image(image)

			if show == 3:
				button.set_sensitive(False)
			button.set_border_width(0)
			button.connect("clicked", action)
			self.button_box.pack_start(button, expand=True, fill=True, padding=0)

			self.tooltips.set_tip(button, str(btype))
			return icon_w

		def get_image_info(self, img_path):
			'''Test if icon is png, and get icon width(px),icon height(px)'''
			width = -1
			height = -1
			try:
				with open(img_path, 'rb') as f:
					data = f.read()
				if data[:8] == '\211PNG\r\n\032\n' and (data[12:16] == 'IHDR'): # check if png
					width, height = struct.unpack('>LL', data[16:24])
			except:
				self.on_warning("get_image_info failed for '{}'".format(img_path))
			return width,height


def print_message(m):
		print (str(m), file=sys.stderr)

def get_options():
	result = None
	import argparse
	parser = argparse.ArgumentParser(description="Bunsenlabs exit")
	if display:
		parser.add_argument("-l", "--logout", help="Log out",
							action="store_true")
	parser.add_argument("-s", "--suspend", help="Suspend",
						action="store_true")
	parser.add_argument("-i", "--hibernate", help="Hibernate",
						action="store_true")
	parser.add_argument("-y", "--hybridsleep", help="Hybrid sleep",
						action="store_true")
	parser.add_argument("-b", "--reboot", help="Reboot",
						action="store_true")
	parser.add_argument("-p", "--poweroff", help="Power off",
						action="store_true")
	parser.parse_args(sys.argv[1:])
	"""No check if more than one option was specified. Take the first option and
	discard the other"""
	result = parser.parse_args()
	return result

def get_config_file():
	"""Determine config directory: first try the environment variable
	XDG_CONFIG_HOME according to XDG specification and as a fallback
	use ~/.config/bl-exit. Use /etc/xdg/bl-exit/bl-exitrc as a last
	resort."""
	config_file = None
	config_dirs = []
	xdg_config_dir = os.getenv('XDG_CONFIG_HOME')
	if xdg_config_dir:
		config_dirs.append(xdg_config_dir)
	user_config_dir = os.path.expanduser('~/.config')
	try:
		if not (xdg_config_dir and os.path.samefile(user_config_dir,
													xdg_config_dir)):
			config_dirs.append(user_config_dir)
	except OSError as e:
		print_message(e)
		pass
	config_dirs.append('/etc/xdg')
	for config_dir in config_dirs:
		config_dir = config_dir + '/bl-exit'
		if os.path.isdir(config_dir):
			maybe_config_file = config_dir + '/bl-exitrc'
			if os.path.isfile(maybe_config_file):
				config_file = maybe_config_file
				break

	return config_file

def get_config_theme_entry(section, item, cp):
	"""Get 'theme' entry from [theme] section.
	:param
	  section: String, config section
	  item:    String, config item
	  cp:      ConfigParser, instance
	:out
	  string or None"""
	if (cp.has_section(section)):
		try:
			_item = cp.get(section, item)
		except ConfigParser.NoOptionError:
			_item = None
		return _item
	else:
		return None

def main():
	'''
	The script works both in a graphical and a non-graphical environment.

	In a graphical environment, the BlExitWindow instance is only shown when
	the script is launched without arguments. The user selects the action she
	wants by clicking the right button.

	WHen  the script is launched In a non-graphical environment the requested
	action should be one of the accepted arguments and the action is executed
	without asking for confirmation - as if the script was launched from the
	command line.

	In a non-graphical environment, one of the accepted actions must be
	specified as an argument.
	'''
	if display and len(sys.argv[1:]) == 0:
		try:
			# import ConfigParser
			cp = ConfigParser.RawConfigParser()
			config_file = get_config_file()
			if config_file:
				cp.read(config_file)
				_theme = get_config_theme_entry('theme', 'theme', cp)
				if (_theme is not None and
					_theme != 'classic'):
					blexit = BlexitMerlin(cp, config_file)
				else:
					blexit = Blexit(cp, config_file)
			else:
				blexit = Blexit(cp, config_file)
		except ConfigParser.ParsingError as e:
			print_message(str(e))
			return 1
		except ConfigParser.NoOptionError as e:
			print_message(str(e))
			blexit = Blexit(cp, config_file)
	else:
		blexit = BlexitBase()

	return blexit.main()

if __name__ == "__main__":
	sys.exit(main())
