text: # Copyright (c) 2011 matt
  # Copyright (c) 2011 Paul Colomiets
  # Copyright (c) 2011-2014 Tycho Andersen
  # Copyright (c) 2012 dmpayton
  # Copyright (c) 2012 hbc
  # Copyright (c) 2012 Tim Neumann
  # Copyright (c) 2012 uberj
  # Copyright (c) 2012-2013 Craig Barnes
  # Copyright (c) 2013 Tao Sauvage
  # Copyright (c) 2014 Sean Vig
  # Copyright (c) 2014 dequis
  # Copyright (c) 2014 Sebastien Blot
  #
  # Permission is hereby granted, free of charge, to any person obtaining a copy
  # of this software and associated documentation files (the "Software"), to deal
  # in the Software without restriction, including without limitation the rights
  # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  # copies of the Software, and to permit persons to whom the Software is
  # furnished to do so, subject to the following conditions:
  #
  # The above copyright notice and this permission notice shall be included in
  # all copies or substantial portions of the Software.
  #
  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  # SOFTWARE.
  
  from __future__ import division
  
  import cairocffi
  import os
  from libqtile import bar
  from libqtile.log_utils import logger
  from . import base
  
  BAT_DIR = '/sys/class/power_supply'
  CHARGED = 'Full'
  CHARGING = 'Charging'
  DISCHARGING = 'Discharging'
  UNKNOWN = 'Unknown'
  
  BATTERY_INFO_FILES = {
      'energy_now_file': ['energy_now', 'charge_now'],
      'energy_full_file': ['energy_full', 'charge_full'],
      'power_now_file': ['power_now', 'current_now'],
      'status_file': ['status'],
  }
  
  
  def default_icon_path():
      # default icons are in libqtile/resources/battery-icons
      root = os.sep.join(os.path.abspath(__file__).split(os.sep)[:-2])
      return os.path.join(root, 'resources', 'battery-icons')
  
  
  class _Battery(base._TextBox):
      """Base battery class"""
  
      filenames = {}
  
      defaults = [
          ('battery_name', 'BAT0', 'ACPI name of a battery, usually BAT0'),
          (
              'status_file',
              'status',
              'Name of status file in'
              ' /sys/class/power_supply/battery_name'
          ),
          (
              'energy_now_file',
              None,
              'Name of file with the '
              'current energy in /sys/class/power_supply/battery_name'
          ),
          (
              'energy_full_file',
              None,
              'Name of file with the maximum'
              ' energy in /sys/class/power_supply/battery_name'
          ),
          (
              'power_now_file',
              None,
              'Name of file with the current'
              ' power draw in /sys/class/power_supply/battery_name'
          ),
          ('update_delay', 60, 'The delay in seconds between updates'),
      ]
  
      def __init__(self, **config):
          base._TextBox.__init__(self, "BAT", bar.CALCULATED, **config)
          self.add_defaults(_Battery.defaults)
  
      def _load_file(self, name):
          try:
              path = os.path.join(BAT_DIR, self.battery_name, name)
              with open(path, 'r') as f:
                  return f.read().strip()
          except IOError:
              if name == 'current_now':
                  return 0
              return False
          except Exception:
              logger.exception("Failed to get %s" % name)
  
      def _get_param(self, name):
          if name in self.filenames and self.filenames[name]:
              return self._load_file(self.filenames[name])
          elif name not in self.filenames:
              # Don't have the file name cached, figure it out
  
              # Don't modify the global list! Copy with [:]
              file_list = BATTERY_INFO_FILES.get(name, [])[:]
  
              if getattr(self, name, None):
                  # If a file is manually specified, check it first
                  file_list.insert(0, getattr(self, name))
  
              # Iterate over the possibilities, and return the first valid value
              for file in file_list:
                  value = self._load_file(file)
                  if value is not False and value is not None:
                      self.filenames[name] = file
                      return value
  
          # If we made it this far, we don't have a valid file.
          # Set it to None to avoid trying the next time.
          self.filenames[name] = None
  
          return None
  
      def _get_info(self):
          try:
              info = {
                  'stat': self._get_param('status_file'),
                  'now': float(self._get_param('energy_now_file')),
                  'full': float(self._get_param('energy_full_file')),
                  'power': float(self._get_param('power_now_file')),
              }
          except TypeError:
              return False
          return info
  
  
  class Battery(_Battery):
      """
          A simple but flexible text-based battery widget.
      """
      orientations = base.ORIENTATION_HORIZONTAL
      defaults = [
          ('charge_char', '^', 'Character to indicate the battery is charging'),
          ('discharge_char',
           'V',
           'Character to indicate the battery is discharging'
           ),
          ('error_message', 'Error', 'Error message if something is wrong'),
          ('format',
           '{char} {percent:2.0%} {hour:d}:{min:02d}',
           'Display format'
           ),
          ('hide_threshold', None, 'Hide the text when there is enough energy'),
          ('low_percentage',
           0.10,
           "Indicates when to use the low_foreground color 0 < x < 1"
           ),
          ('low_foreground', 'FF0000', 'Font color on low battery'),
      ]
  
      def __init__(self, **config):
          _Battery.__init__(self, **config)
          self.add_defaults(Battery.defaults)
  
      def timer_setup(self):
          update_delay = self.update()
          if update_delay is None and self.update_delay is not None:
              self.timeout_add(self.update_delay, self.timer_setup)
          elif update_delay:
              self.timeout_add(update_delay, self.timer_setup)
  
      def _configure(self, qtile, bar):
          if self.configured:
              self.update()
          _Battery._configure(self, qtile, bar)
  
      def _get_text(self):
          info = self._get_info()
          if info is False:
              return self.error_message
  
          # Set the charging character
          try:
              # hide the text when it's higher than threshold, but still
              # display `full` when the battery is fully charged.
              if self.hide_threshold and \
                      info['now'] / info['full'] * 100.0 >= \
                      self.hide_threshold and \
                      info['stat'] != CHARGED:
                  return ''
              elif info['stat'] == DISCHARGING:
                  char = self.discharge_char
                  time = info['now'] / info['power']
              elif info['stat'] == CHARGING:
                  char = self.charge_char
                  time = (info['full'] - info['now']) / info['power']
              # if percent charge >0 and <50, but not discharging or charging, then return the current status
              # TODO: make this configurable, don't just use 50% as arbitrary cut-off, maybe check if plugged in
              elif info['now'] > 0 and \
                      info['stat'] == UNKNOWN and \
                      int(info['now'] / info['full']) != 1:
                  return '~' + str(int(info['now'] / info['full'] * 100)) + '%'
              # battery is empty and not charging
              elif info['now'] == 0 and info['stat'] == UNKNOWN:
                  return 'Empty'
              else:
                  return 'Full'
          except ZeroDivisionError:
              time = -1
  
          # Calculate the battery percentage and time left
          if time >= 0:
              hour = int(time)
              min = int(time * 60) % 60
          else:
              hour = -1
              min = -1
          percent = info['now'] / info['full']
          if info['stat'] == DISCHARGING and percent < self.low_percentage:
              self.layout.colour = self.low_foreground
          else:
              self.layout.colour = self.foreground
  
          return self.format.format(
              char=char,
              percent=percent,
              hour=hour,
              min=min
          )
  
      def update(self):
          ntext = self._get_text()
          if ntext != self.text:
              self.text = ntext
              self.bar.draw()
  
  
  class BatteryIcon(_Battery):
      """Battery life indicator widget."""
  
      orientations = base.ORIENTATION_HORIZONTAL
      defaults = [
          ('theme_path', default_icon_path(), 'Path of the icons'),
          ('custom_icons', {}, 'dict containing key->filename icon map'),
      ]
  
      def __init__(self, **config):
          _Battery.__init__(self, **config)
          self.add_defaults(BatteryIcon.defaults)
  
          if self.theme_path:
              self.length_type = bar.STATIC
              self.length = 0
          self.surfaces = {}
          self.current_icon = 'battery-missing'
          self.icons = dict([(x, '{0}.png'.format(x)) for x in (
              'battery-missing',
              'battery-caution',
              'battery-low',
              'battery-good',
              'battery-full',
              'battery-caution-charging',
              'battery-low-charging',
              'battery-good-charging',
              'battery-full-charging',
              'battery-full-charged',
          )])
          self.icons.update(self.custom_icons)
  
      def timer_setup(self):
          self.update()
          self.timeout_add(self.update_delay, self.timer_setup)
  
      def _configure(self, qtile, bar):
          base._TextBox._configure(self, qtile, bar)
          self.setup_images()
  
      def _get_icon_key(self):
          key = 'battery'
          info = self._get_info()
          if info is False or not info.get('full'):
              key += '-missing'
          else:
              percent = info['now'] / info['full']
              if percent < .2:
                  key += '-caution'
              elif percent < .4:
                  key += '-low'
              elif percent < .8:
                  key += '-good'
              else:
                  key += '-full'
  
              if info['stat'] == CHARGING:
                  key += '-charging'
              elif info['stat'] == CHARGED:
                  key += '-charged'
          return key
  
      def update(self):
          icon = self._get_icon_key()
          if icon != self.current_icon:
              self.current_icon = icon
              self.draw()
  
      def draw(self):
          if self.theme_path:
              self.drawer.clear(self.background or self.bar.background)
              self.drawer.ctx.set_source(self.surfaces[self.current_icon])
              self.drawer.ctx.paint()
              self.drawer.draw(offsetx=self.offset, width=self.length)
          else:
              self.text = self.current_icon[8:]
              base._TextBox.draw(self)
  
      def setup_images(self):
          for key, name in self.icons.items():
              try:
                  path = os.path.join(self.theme_path, name)
                  img = cairocffi.ImageSurface.create_from_png(path)
              except cairocffi.Error:
                  self.theme_path = None
                  logger.warning('Battery Icon switching to text mode')
                  return
              input_width = img.get_width()
              input_height = img.get_height()
  
              sp = input_height / (self.bar.height - 1)
  
              width = input_width / sp
              if width > self.length:
                  self.length = int(width) + self.actual_padding * 2
  
              imgpat = cairocffi.SurfacePattern(img)
  
              scaler = cairocffi.Matrix()
  
              scaler.scale(sp, sp)
              scaler.translate(self.actual_padding * -1, 0)
              imgpat.set_matrix(scaler)
  
              imgpat.set_filter(cairocffi.FILTER_BEST)
              self.surfaces[key] = imgpat
  