my conky circle theme

Message
Author
django013
Posts: 185
Joined: Sat Feb 11, 2023 3:25 am

my conky circle theme

#1 Post by django013 »

Hi,

I'd like to share my conky stuff:
MX-Circles1.jpg
Attached file is an archive. Rename it to *.tar.xz
It contains the font files, a lua-script and a python install script.

The python script tries to read your hardware and generates the conky config files and copies the files to their destinations.
Each group of circles consists of relative positions, so it is easy to move the entire group around.

Update
I changed script according the recomendations of fehlix so that it is now completely written in lua.
You'll need to install "lua-filesystem" to avoid script errors.
The updated archive is a zip, that contains a *.tar.xz file.
The new script works in two modes:
  • for big hardware will create the picture from above
  • smaller hardware will create this picture:
    MX-Circles0.jpg
Both pictures without manual polishing.

How to install
  • extract the archive to $HOME/.conky
  • open a terminal and go to $HOME/.conky/MX-Circles
  • call "conky -c MX-Circles" - which will determine your hardware and create the config files
  • when the rings appear hit Strg+C to terminate conky
  • start the real conky with "conky -c MX-Circles -d"
Have fun!
You do not have the required permissions to view the files attached to this post.
Last edited by django013 on Fri Feb 07, 2025 11:17 pm, edited 2 times in total.
MC: ASUS PRIME A320M-K, AMD Ryzen 5 3600, NVIDIA Quadro P400
WS: ASRock X670E Steel Legend, AMD Ryzen 5 7600X, NVIDIA GTX 1660 SUPER

User avatar
AVLinux
Posts: 2998
Joined: Wed Jul 15, 2020 1:15 am

Re: my conky circle theme

#2 Post by AVLinux »

Wow, that's cool!

You've put a LOT of work into those!

User avatar
CharlesV
Global Moderator
Posts: 7310
Joined: Sun Jul 07, 2019 5:11 pm

Re: my conky circle theme

#3 Post by CharlesV »

+1 that is VERY nice!
*QSI = Quick System Info from menu (Copy for Forum)
*MXPI = MX Package Installer
*Please check the solved checkbox on the post that solved it.
*Linux -This is the way!

User avatar
Germ
Posts: 171
Joined: Sat Sep 03, 2022 9:42 am

Re: my conky circle theme

#4 Post by Germ »

Looks great!
calm down.... it's only ones and zeroes...
Linux User #274693

django013
Posts: 185
Joined: Sat Feb 11, 2023 3:25 am

Re: my conky circle theme

#5 Post by django013 »

Thank you guys!

Here's a workaround for sensor handling (until I know, howto call inline lua functions).
Save this as /usr/bin/conky_printTemperature
(of cause you need an installed lua interpreter for it to work)

Code: Select all

#!/usr/bin/lua

function cpT(path)
    local f = io.open(path, "rb")
    if not f then return 0 end
    local rv = f:read("*a")
    f:close()
    return tonumber(rv) / 1000.0
end

print(cpT(arg[1]))
and here's the installer, that uses above script:

Code: Select all

#!/usr/bin/env python3
#
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2021 django013
#
# project: conky.arcs theme for conky
# purpose: installation script for conky.arcs theme
#          Script explores running system and generates matching
#          gui elements for conky system monitor.
import os
import sys
import glob
import array
import logging as log
import pathlib
import subprocess
from operator import itemgetter, attrgetter, methodcaller


def conkyCAPsFailed(app):
    print(f'conky [{app}] is not capable to run conky.arcs theme.')
    print('conky.arcs needs conky with lua and cairo support.')
    print('Try to install "conky-all" or use an appimage from ')
    print('https://github.com/brndnmtthws/conky/releases')
    print('')
    exit(-1)


def first(iterable, condition = lambda x: True):
    return next(x for x in iterable if condition(x))


def usage():
    print('conky.arc is a theme for system monitor app "conky"')
    print('this install-script evaluates your system and generates')
    print('matching config file.')
    print('')
    print('if conky is not found by PATH environment, ...')
    print('please provide the path to conky application as commandline ')
    print('argument. Install script then checks capability of conky.')
    print('')
    exit(0)


class ConfigReader:
  def __init__(self, conky):
      hasLua, hasCairo = self.evalConky(conky)
      if not hasLua or not hasCairo:
         conkyCAPsFailed(conky)
      self.app     = conky
      self.curdir  = os.getcwd()
      self.homedir = pathlib.Path.home()
      self.sensors = self.getSensors()
      self.nCPU    = self.countCPU()
      self.netIF   = self.networkIF()
      self.md      = self.mounted_drives()
      self.hds     = self.harddisks()
      self.cpuFreqs()


  def countCPU(self, maxCPU = 6):
      cpus0 = glob.glob('/sys/devices/system/cpu/cpu?')
      cpus1 = glob.glob('/sys/devices/system/cpu/cpu??')
      cpus = sorted(cpus0 + cpus1)

      return len(cpus)


  def cpuFreqs(self):
      f0 = 10000
      f1 = 0
      with open('/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq') as f:
           for line in f:
               f0 = int(line) / 1000

      with open('/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq') as f:
           for line in f:
               f1 = int(line) / 1000
      self.fCPU0 = f0
      self.fCPU1 = f1


  def evalConky(self, app):
      p = pathlib.Path(app)
      if not p.exists():
         print('conky NOT found. Please install "conky-all"')
         print('')
         exit(0)
      p = subprocess.Popen([app, '--version'], stdout=subprocess.PIPE)
      line = p.stdout.readline()
      hasLua   = False
      hasCairo = False
      while line:
            if line.strip().startswith(b'Lua bind'):
               hasLua = True
               line = p.stdout.readline()
               while line.strip().startswith(b'*'):
                     parts = line.strip().split(b' ')
                     if parts[1] == b'Cairo':
                        hasCairo = True
                        return [hasLua, hasCairo]
                     line = p.stdout.readline()
            line = p.stdout.readline()
      return [hasLua, hasCairo]


  def getSensors(self):
      sensors = []
      paths = glob.glob('/sys/class/hwmon/hwmon*')
      for i in range(0, len(paths)):
          sensor = {}
          with open('/sys/class/hwmon/hwmon{}/name'.format(i)) as f:
              name = f.read().strip()
              sensor['type'] = name
              sensor['name'] = name
              if ('nvme' in name):
                  subname = first(glob.glob('/sys/class/hwmon/hwmon{0}/device/nvme*'.format(i)))
                  subname = os.path.basename(subname)
                  sensor['name'] = subname
              elif ('spd5118' in name):
                  subname = 'SMBus PIIX4'
                  sensor['name'] = subname
              temp = 0
              path = '/sys/class/hwmon/hwmon{}/temp1_input'.format(i)
              p = pathlib.Path(path)
              if not p.exists():
                  continue
              sensor['path'] = path
              with open(path) as ft:
                   temp = float(ft.read()) / 1000.0
              sensor['temp'] = temp
              sensors.append(sensor)
      sensors.sort(key=lambda x: x['name'], reverse=False)
      return sensors


  def mounted_drives(self):
      drives = []
      check = []    # avoid duplicates
      with open('/proc/mounts') as f:
           for line in f:
               if not line.startswith('/'):
                  continue
               if 'loop' in line:
                  continue
               parts = line.split()
               mountPoint = {}
               if parts[0] in check:
                  continue
               else:
                  check.append(parts[0])
                  sp = parts[0].split('/')
                  mountPoint['part'] = sp[2]
                  mountPoint['mounted'] = parts[1]
               drives.append(mountPoint)
      drives.sort(key=lambda x: x['mounted'], reverse=False)
      return drives


  def harddisks(self):
      disks0 = sorted(glob.glob('/dev/sd?'))
      disks1 = sorted(glob.glob('/dev/nvme?n?'))
      hdds = {}
      for d in disks0:
          parts = d.split('/')
          hdds[parts[2]] = d
      for d in disks1:
          parts = d.split('/')
          hdds[parts[2]] = d
      return hdds


  def networkIF(self):
      nif = []
      with open('/proc/net/dev') as f:
           for line in f:
               ifName = line.split()[0]
               if ifName == 'Inter-|' or ifName == 'face' or ifName == 'lo:':
                  continue
               nif.append(ifName.split(':')[0])
      return nif


  def dump(self):
      print('============================================================')
      print('conky [{0}] has lua and cairo support'.format(self.app))
      print('curdir:\t' + self.curdir)
      print('homedir:\t' + str(self.homedir))
      print('found {0} cpu cores'.format(self.nCPU))
      if len(self.sensors) < 2:
         print('NO temperatures for gpu!')
      print('network interfaces:')
      for n in self.netIF:
          print('\tfound interface:\t{0}'.format(n))
      print('harddisks:')
      for d in self.hds:
          print('\tfound disk:\t{0}'.format(d))
      print('mounted partitions:')
      for d in self.md:
          print('\t{0}\twith path:\t{1}'.format(d['part'], d['mounted']))
      print('sensors')
      for s in self.sensors:
          print('sensor {0} has temp {1}\t\tat: {2}'.format(s['name'], s['temp'], s['path']))
      print('============================================================')


class ConfigWriter:
  def __init__(self, cfgReader):
      self.cr = cfgReader
      self.base = str(self.cr.homedir) + '/.conky/MX-Circles'
      self.LuaTemplates = {}
      self.ConkyTemplates = {}
      self.ConkyTemplates['intro'] = """conky.config = {
    background = false,
    update_interval = 2,
    cpu_avg_samples = 2,
    net_avg_samples = 2,
    temperature_unit = 'celsius',
    double_buffer = true,
    no_buffers = true,
    text_buffer_size = 2048,
    gap_x = 2,
    gap_y = 50,
    minimum_width = 1020,
    minimum_height = 1200,
    own_window = true,
    own_window_type = 'normal',
    own_window_transparent = true,
    own_window_argb_visual = true,
    own_window_hints = 'undecorated,below,sticky,skip_taskbar,skip_pager',
    border_inner_margin = 0,
    border_outer_margin = 0,
    alignment = 'top_right',
    draw_shades = false,
    draw_outline = false,
    draw_borders = false,
    draw_graph_borders = false,
    override_utf8_locale = true,
    use_xft = true,
    xftalpha = 0.5,
    font = 'Cuprum:size=7',
    top_cpu_separate = true,
    lua_load = './conky.arcs.lua',
    lua_draw_hook_post = 'main'
}

conky.text = [[
${voffset 460}"""
      self.ConkyTemplates['diskRead'] = '\n${{goto {0}}}${{diskiograph_read    44, 300 ADFF2F 32CD32 -t -l}}'
      self.ConkyTemplates['diskWrite'] = '\n${{goto {0}}}${{diskiograph_write   44, 300 FF0000 8B0000 -t -l}}'
      self.ConkyTemplates['netDown'] = '\n${{goto {0}}}${{downspeedgraph {1} 21, 240 ADFF2F 32CD32 -t -l}}'
      self.ConkyTemplates['netUp'] = '\n${{goto {0}}}${{upspeedgraph   {1} 21, 240 FF0000 8B0000 -t -l}}'
      self.ConkyTemplates['fin'] = '\n]]'
      self.LuaTemplates['intro'] = """local config = {}

function config.setup()
  return {"""
      self.LuaTemplates['inter0'] = """
    },
    sub = {"""
      self.LuaTemplates['inter1'] = """
    },
    rings = {"""
      self.LuaTemplates['fin'] = """
}
end

return config"""
      self.LuaTemplates['main0'] = """
  x     = 11,
  y     =  0,
  fs    = 18,
  font  = 'Cuprum',
  hFont = 'Vast Shadow',
  rCol  = '95275C',
  rBgC  = '565656',
  color = 'FFFFFF',
  col1  = 'F2B70E',
  col2  = 'E90812',
  col3  = 'CDCDCD',
  bgCol = '993636',
  alpha = 0.9,
  sb    = false,
  uptime = {
  --[[
      x/y the position where the text should be aligned to
      a: L means text ends at x/y or is on the left side of position
      a: R means text starts at x/y or is on the right side of position
      text starting with '$' will be translated by conky engine
    ]]--
    { x = 37, y = 2, a = 'L', s = 30, text = 'Uptime' },
    { x = 38, y = 2, a = 'R', s = 30, text = '${uptime_short}' },
  },
  date = {"""
      self.LuaTemplates['mainDate'] = """
    {{ x = 27, y = {0}, a = 'L', s = 40, text = '${{time %A}}' }},
    {{ x = 29, y = {0}, a = 'R', s = 40, text = '${{time %x}}' }},
  }},
"""
      self.LuaTemplates['mainTime'] = """
  time = {{
    {{ x = 35, y = {0}, a = 'L', s = 80, text = '${{time %H}}' }},
    {{ x = 37, y = {0}, a = 'R', s = 80, text = ':' }},
    {{ x = 41, y = {0}, a = 'R', s = 80, text = '${{time %M}}' }},
  }},
  sys = {{"""
      self.LuaTemplates['main1'] = """
    {{ x = 37, y =  {0},   a = 'L', s = 20, text = 'public IP:' }},
    {{ x = 38, y =  {0},   a = 'R', s = 20, text = "${{execi 5 wget -q -O - checkip.dyndns.org | sed -e 's/[^[:digit:]\\\\|.]//g'}}" }},
    {{ x = 15, y = {1},   a = 'R', s = 30, text = "${{execi 3600 cat /etc/mx-version | awk '{{ print $1 \\" \\" $2; }}' | sed -e 's/_/ /g'}}" }},
    {{ x = 15, y = {2},   a = 'R', s = 20, text = "${{execi 3600 grep PRETTY_NAME /etc/os-release | sed -e 's/=/ /' | awk '{{ print $2 \\" \\" $3 \\" \\" $4 \\" \\" $5; }}' | sed -e 's/\\"//g'}}" }},"""
      self.LuaTemplates['IOheaders'] = """
    {{ x = 87, y = {0},   a = 'R', s = 25, text = 'network Traffic' }},
    {{ x = 87, y = {1},   a = 'R', s = 25, text = 'disk Traffic' }},
  }},
"""
      self.LuaTemplates['netMain'] = "\n    {{ x = 37, y =  {0},   a = 'L', s = 20, text = 'local {1}:' }},\n    {{ x = 38, y =  {0},   a = 'R', s = 20, text = '${{addr {1}}}' }},"
      self.LuaTemplates['cpuInit'] = """
  cpu = {
    x = 79,
    y =  0,
    w = 30,
    h = 13,
    level1 = 3500,
    level2 = 4500,
    main = {"""
      self.LuaTemplates['cpuMain0'] = """
      {{ x =  0, y =  {0}, a = 'R', text = 'CPU' }},
      {{ x = 15, y =  {0}, a = 'L', text = '${{cpu cpu{1}}}%' }},"""
      self.LuaTemplates['cpuMainN'] = """
      {{ x =  0, y =  {0}, a = 'R', text = 'CPU {1}' }},
      {{ x = 10, y =  {0}, a = 'L', text = '${{freq {1}}}' }},
      {{ x = 15, y =  {0}, a = 'L', text = '${{cpu cpu{1}}}%' }},"""
      self.LuaTemplates['cpuTop'] = """
      {{ x =  6, y = {0}, a = 'R', text = '${{top name {1}}}' }},
      {{ x = 26, y = {0}, a = 'L', text = '${{top cpu {1}}}%' }},"""
      self.LuaTemplates['cpuRing0'] = "\n      {{ x = 15, y = {0}, r = {1}, h = 0.9, d = 'cw', name = 'cpu', arg = '', max = 100, sa = 0, ea = 300, ba = {2}, fg = 'F38A07' }},"
      self.LuaTemplates['cpuRingN'] = "\n      {{ x = 15, y = {0}, r = {1}, h = 0.9, d = 'cw', name = 'cpu', arg = 'cpu{2}', max = 100, sa = 0, ea = 300, ba = {3} }},"
      self.LuaTemplates['cpuFin'] = """
    },
  },
"""
      self.LuaTemplates['memInit'] = """
  mem = {
    x = 49,
    y = 38,
    w = 36,
    h = 16,
    main = {
      { x = 15, y = 1, a = 'R', text = 'Mem:' },
      { x = 28, y = 1, a = 'L', text = '${mem}' },
      { x = 29, y = 1, a = 'L', text = '/' },
      { x = 30, y = 1, a = 'R', text = '${memmax}' },
      { x = 15, y = 2, a = 'R', text = 'Swap:' },
      { x = 28, y = 2, a = 'L', text = '${swap}' },
      { x = 29, y = 2, a = 'L', text = '/' },
      { x = 30, y = 2, a = 'R', text = '${swapmax}' },
    },
    sub = {"""
      self.LuaTemplates['memVal'] = """
      {{ x = 15, y =  {0}, a = 'R', text = '{2} Down' }},
      {{ x = 36, y =  {0}, a = 'L', text = '${{totaldown {2}}}' }},
      {{ x = 15, y =  {1}, a = 'R', text = '{2} UP' }},
      {{ x = 36, y =  {1}, a = 'L', text = '${{totalup {2}}}' }},
"""
      self.LuaTemplates['memTop'] = """
      {{ x = 10, y =  {0}, a = 'R', text = '${{top_mem name {1}}}' }},
      {{ x = 28, y =  {0}, a = 'L', text = '${{top_mem mem  {1}}}%' }},"""
      self.LuaTemplates['memRing0'] = """
      {{ x = 15, y = {0}, r = {1}, h = 0.9, d = 'ccw', name = 'memperc',    arg = '',     max = 100, sa = 0, ea = 65, ba = 0.8 }},
      {{ x = 15, y = {0}, r = {2}, h = 0.5, d = 'ccw', name = 'swapperc',   arg = '',     max = 100, sa = 0, ea = 60, ba = 0.8 }},"""
      self.LuaTemplates['memRingN'] = """
      {{ x = 15, y = {0}, r = {1}, h = 0.8, d = 'ccw', name = 'downspeedf', arg = '{2}', max = 100, sa = 0, ea = 55, ba = {3} }},
      {{ x = 15, y = {0}, r = {4}, h = 0.9, d = 'ccw', name = 'upspeedf',   arg = '{2}', max = 100, sa = 0, ea = 45, ba = {3} }},"""
      self.LuaTemplates['memFin'] = """    },
  },
"""
      self.LuaTemplates['hddInit'] = """
  lh  = 1,
  lw  = 1,
  hdd = {
    x = 87,
    y = 60,
    w = 29,
    h =  6,
    main = {"""
      self.LuaTemplates['hddMain'] = """
      {{ x = 11.5, y = {0},  a = 'R', text = '{1}' }},"""
      self.LuaTemplates['hddSub']  = """
      {{ x = 22, y = {0},  a = 'R', text = '{1}' }},"""
      self.LuaTemplates['hddRing'] = """
      {{ x = 11, y = {0}, r = {1}, h = 0.7, d = 'cw', name = 'fs_used_perc', arg = '{2}', max = 100, sa = 180, ea = 455, ba = {3} }},"""
      self.LuaTemplates['hddFin'] = """    },
  },
"""
      self.LuaTemplates['tempInit'] = """
  temp = {
    x = 36,
    y = 17,
    w = 31,
    h = 16,
    main = {"""
      self.LuaTemplates['tempMain_notYet'] = """
      {{ x = 10, y = {0}, a = 'R', text = '${{lua printTemperature({1})}}°' }},"""
      self.LuaTemplates['tempMain'] = """
      {{ x = 10, y = {0}, a = 'R', text = '${{exec conky_printTemperature {1}}}°' }},"""
      self.LuaTemplates['tempName'] = """
      {{ x =  9, y = {0}, a = 'L', text = '{1}' }},"""
      self.LuaTemplates['tempRing'] = """
      {{ x = 15, y = {0}, r = {1}, h = 0.8, d = 'cw', name = 'exec conky_printTemperature', arg = '{2}', max = 100, sa = 0, ea = 270, ba = {3}}},"""
      self.LuaTemplates['tempFin'] = """
    },
  },"""


  def copyFonts(self):
      fd = '/usr/share/fonts/truetype/freefonts'
      print("need to install some fonts\n")
      subprocess.check_call(['sudo', 'mkdir', '-p', fd])
      fonts = glob.glob('fonts/*.ttf')
      for f in fonts:
          subprocess.check_call(['sudo', 'cp', f, fd])


  def install(self):
      p = pathlib.Path(self.base)
      if not p.exists():
          os.makedirs(self.base)
      self.copyFonts()
      subprocess.check_call(['cp', 'conky.arcs.lua', self.base])
      self.writeConkyConfig()
      self.writeLuaConfig()


  def writeConkyConfig(self):
      with open(self.base + '/MX-Circles', 'w') as f:
           f.write(self.ConkyTemplates['intro'])
           for ifn in self.cr.netIF:
               f.write(self.ConkyTemplates['netDown'].format(750, ifn))
           for ifn in self.cr.netIF:
               f.write(self.ConkyTemplates['netUp'].format(750, ifn))
           f.write(self.ConkyTemplates['diskRead'].format(710))
           f.write(self.ConkyTemplates['diskWrite'].format(710))
           f.write(self.ConkyTemplates['fin'])


  def writeLuaConfig(self):
      with open(self.base + '/config.lua', 'w') as f:
           f.write(self.LuaTemplates['intro'])
           self.writeMainSection(f)
           self.writeCpuSection(f)
           self.writeIOSection(f)
           self.writeMemSection(f)
           self.writeHddSection(f)
           self.writeTempSection(f)
           f.write(self.LuaTemplates['fin'])


  def writeMainSection(self, f):
      f.write(self.LuaTemplates['main0'])
      y = 5 + len(self.cr.netIF)
      f.write(self.LuaTemplates['mainDate'].format(y))
      y = 9 + len(self.cr.netIF)
      f.write(self.LuaTemplates['mainTime'].format(y))
      y = 3
      for ifn in self.cr.netIF:
          f.write(self.LuaTemplates['netMain'].format(y, ifn))
          y += 1
      y0 =  y
      y1 =  8 + y
      y2 =  9 + y
      f.write(self.LuaTemplates['main1'].format(y0, y1, y2))
      y0 = 30 + len(self.cr.netIF) * 2
      y1 = 36 + len(self.cr.netIF) * 3
      f.write(self.LuaTemplates['IOheaders'].format(y0, y1))


  def writeCpuSection(self, f):
      f.write(self.LuaTemplates['cpuInit'])
      f.write(self.LuaTemplates['cpuMain0'].format(1, 0))
      for i in range(1, 1 + self.cr.nCPU):
          f.write(self.LuaTemplates['cpuMainN'].format(1 + i, i))
      f.write(self.LuaTemplates['inter0'])
      for i in range(0, min(10, self.cr.nCPU)):
          f.write(self.LuaTemplates['cpuTop'].format(3 + self.cr.nCPU + i, 1 + i))
      f.write(self.LuaTemplates['inter1'])
      trans = 0.7
      f.write(self.LuaTemplates['cpuRing0'].format(1 + self.cr.nCPU, 2 + self.cr.nCPU, trans))
      for i in range(0, self.cr.nCPU):
          if (i % 2 == 0):
             trans -= 0.1
          trans = max(0.3, trans)
          f.write(self.LuaTemplates['cpuRingN'].format(1 + self.cr.nCPU, 1 + self.cr.nCPU - i, 1 + i, trans))
      f.write(self.LuaTemplates['cpuFin'])


  def writeIOSection(self, f):
      f.write("""
  io = {
    x = 73,
    y = 28,
    w = 35,
    h =  9,
    main = {
      { x = 15, y = 5, a = 'R', text = 'Disk Reads' },
      { x = 14, y = 5, a = 'L', text = '${diskio_read}' },
      { x = 15, y = 6, a = 'R', text = 'Disk Writes' },
      { x = 14, y = 6, a = 'L', text = '${diskio_write}' },
    },
    sub = {
      { x = 15, y =  7, a = 'R', text = '${top_io name 1}' },
      { x = 14, y =  7, a = 'L', text = '${top_io io_write 1}' },
      { x = 15, y =  8, a = 'R', text = '${top_io name 2}' },
      { x = 14, y =  8, a = 'L', text = '${top_io io_write 2}' },
      { x = 15, y =  9, a = 'R', text = '${top_io name 3}' },
      { x = 14, y =  9, a = 'L', text = '${top_io io_write 3}' },
    },
    rings = {
      { x = 6, y = 3, r = 2, h = 0.8, d = 'ccw', name = 'top_io io_perc 1', arg = '', max = 100, sa = 80, ea = 180, ba = 0.3  },
      { x = 6, y = 3, r = 3, h = 0.8, d = 'ccw', name = 'top_io io_perc 2', arg = '', max = 100, sa = 80, ea = 180, ba = 0.45 },
      { x = 6, y = 3, r = 4, h = 0.8, d = 'ccw', name = 'top_io io_perc 3', arg = '', max = 100, sa = 80, ea = 180, ba = 0.6  },
    },
  },
""")


  def writeMemSection(self, f):
      f.write(self.LuaTemplates['memInit'])
      n=0
      for nif in self.cr.netIF:
          f.write(self.LuaTemplates['memVal'].format(3 + n, 4 + n, nif))
          n += 2
      mx = 2 * len(self.cr.netIF)
      for i in range(1, min(8, 2 + mx)):
          f.write(self.LuaTemplates['memTop'].format(3 + mx + i, i))
      f.write(self.LuaTemplates['inter1'])
      f.write(self.LuaTemplates['memRing0'].format(2 + mx, 3 + mx, 2 + mx))
      n=0
      trans = 0.6
      for nif in self.cr.netIF:
          f.write(self.LuaTemplates['memRingN'].format(2 + mx, 1 + mx - n, nif, trans, mx - n))
          trans -= 0.2
          n += 2
          trans = max(0.3, trans)
      f.write(self.LuaTemplates['memFin'])


  def writeHddSection(self, f):
      f.write(self.LuaTemplates['hddInit'])
      mx = len(self.cr.md)
      n=0
      for mp in self.cr.md:
          f.write(self.LuaTemplates['hddMain'].format(4 + n, mp['part']))
          n += 1
      f.write(self.LuaTemplates['inter0'])
      n=0
      for mp in self.cr.md:
          f.write(self.LuaTemplates['hddSub'].format(4 + n, mp['mounted']))
          n += 1
      f.write(self.LuaTemplates['inter1'])
      revDrives = self.cr.md
      revDrives.sort(key=lambda x: x['mounted'], reverse=True)
      trans = 0.9
      n=0
      for mp in revDrives:
          f.write(self.LuaTemplates['hddRing'].format(0, 1 + mx - n, mp['mounted'], trans))
          n += 1
          if (n % 2 == 1):
             trans -= 0.1
          trans = max(0.3, trans)
      f.write(self.LuaTemplates['hddFin'])


  def writeTempSection(self, f):
      f.write(self.LuaTemplates['tempInit'])
      n=0
      for s in self.cr.sensors:
          f.write(self.LuaTemplates['tempMain'].format(1 + n, s['path']))
          n += 1
      f.write(self.LuaTemplates['inter0'])
      n=0
      for s in self.cr.sensors:
          f.write(self.LuaTemplates['tempName'].format(1 + n, s['name']))
          n += 1
      f.write(self.LuaTemplates['inter1'])
      mx = len(self.cr.sensors)
      trans = 0.9
      n=0
      for s in self.cr.sensors:
          f.write(self.LuaTemplates['tempRing'].format(mx, 1 + mx - n, s['path'], trans))
          n += 1
          trans -= 0.1
          trans = max(0.3, trans)
      f.write(self.LuaTemplates['tempFin'])


if __name__ == "__main__":
   tmp = subprocess.check_output(["/bin/bash", "which", "conky"])
   if not tmp:
      usage()
   conkyapp = tmp.decode('utf-8').strip()
   if len(sys.argv) > 1:
      if sys.argv[1] == '--help':
         usage()
      else:
         p = pathlib.Path(sys.argv[1])
         if p.exists():
            conkyapp = sys.argv[1]
         else:
            usage()
   cr = ConfigReader(conkyapp)
   cr.dump()
   cw = ConfigWriter(cr)
   cw.install()
MC: ASUS PRIME A320M-K, AMD Ryzen 5 3600, NVIDIA Quadro P400
WS: ASRock X670E Steel Legend, AMD Ryzen 5 7600X, NVIDIA GTX 1660 SUPER

User avatar
fehlix
Developer
Posts: 12693
Joined: Wed Apr 11, 2018 5:09 pm

Re: my conky circle theme

#6 Post by fehlix »

django013 wrote: Sun Feb 02, 2025 1:14 am Thank you guys!

Here's a workaround for sensor handling (until I know, howto call inline lua functions).
Save this as /usr/bin/conky_printTemperature
(of cause you need an installed lua interpreter for it to work)
Hmm, ... OK actually I made it work with lua function call ${lua conky_printTemperature /sys..hwmon...path}, so no need the the exec external script.
I did actually placed the lua function into config.lua and renamed your original, also copied into config.lua.
With your original I couldn't get rid of the decimal place, so I adjusted to

Code: Select all

function conky_printTemperature(path)
    local f = io.open(path, "rb")
    if not f then return string.format("% 3.0f", 0) end
    local rv = f:read("*a")
    f:close()
    local temp = tonumber(rv) / 1000.0
    return string.format("% 3.0f", temp)
end
But actually in this special hwmon-case, we don't need a lua function to be execute b/c we can just use conky's build hwmon, e.g. this way;
within the install script changing the below the line
sensor['path'] = path
by adding and hwmon entry into the sensor-table, which holds the parameter to the conky-hwmon call:
sensor['hwmon'] = f"{i} temp 1"
so it look like this:

Code: Select all

              sensor['path'] = path
              sensor['hwmon'] = f"{i} temp 1"
or if you want to be a bit more flexible in cases where temp1_input would not the right one but another one e.g temp2_input
convert to hwmon-path to the conky-hwmon paramter e.g with a "replace" like this:

Code: Select all

  sensor['hwmon'] = (
            path.replace("/sys/class/hwmon/hwmon", "hwmon ")
                .replace("/temp", " temp ")
                .replace("_input", "")
        )
And we can now adjust within lua-conky snipplets these two exec calls :

Code: Select all

       {{ x = 10, y = {0}, a = 'R', text = '${{exec conky_printTemperature {1}}}°' }},"""
and

Code: Select all

       {{ x = 15, y = {0}, r = {1}, h = 0.8, d = 'cw', name = 'exec conky_printTemperature', arg = '{2}', max = 100, sa = 0, ea = 270, ba = {3}}},"""
with these two conky-hwmon calls:

Code: Select all

      {{ x = 10, y = {0}, a = 'R', text = '${{hwmon {1}}}°' }},"""
and

Code: Select all

      {{ x = 15, y = {0}, r = {1}, h = 0.8, d = 'cw', name = 'hwmon', arg = '{2}', max = 100, sa = 0, ea = 270, ba = {3}}},"""
The temps are now properly displayed and also the temp-rings filled accordingly.
Also perhaps, you may consider to filter-out those temp-sensors which do not properly report temperature indicated by a value of "0" e.g. like "acpitz", which seem not to be activated.
A further note. The install-script is relay nice, but maybe you consider to move that "setup" logic into
a lua-function e.g. named like circle_setup(), where you re-formulate the python-logic into this lua-function,
and populate a "global" setup-table. The circle_setup() you could now call
within "MX-Circles" conky.config below the lua_load like this:

Code: Select all

    lua_load = './conky.arcs.lua',
    lua_draw_hook_post = 'main',
    lua_startup_hook = 'circle_setup',
The startup_hook is called only once at start or reload, where you put all detected properties into a global setup-table.
In addition if you would keep the fonts e.g within the sub folder of the conky ".conky/MX-Circles/fonts" you could now refresh the fontconfig cache with the setup-function e.g with something like "fc-cache -r ~/.conky/MX-Circles/fonts",
which would only be needed to run when those fonts are not already available with font-config cache, e.g. which you can check with "fc-list | grep fontname".
What else ... maybe adjust the layout a bit within the setup script, so it will fit into smaller screens, even on my 1920x1080 display the conky was a bit to large b/c the disks-rings didn't fit into the screen properly .
OK, so that's it from my side with a few comments.
Thanks

User avatar
AK-47
Developer
Posts: 1206
Joined: Sun Mar 24, 2019 7:04 pm

Re: my conky circle theme

#7 Post by AK-47 »

This is gorgeous.

django013
Posts: 185
Joined: Sat Feb 11, 2023 3:25 am

Re: my conky circle theme

#8 Post by django013 »

Thank you fehlix, for your interest!

I know about the problem with decimal points from original post. Therefore I wanted to replace the exec calls.
My problem in using hwmon from conky: I don't know how to enumerate sensors instead of inputs.
I have 4 sensors of type nvme ...
... that's why I wanted to go for /system filesystem directly.

Any way - I got it with the help from conky forum.
If you're interested, here are the new working files (and the best: new version takes only half of system load :happy: )
First the lua script, that should be used without modifications:

Code: Select all

--[[
#
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2021-2025 django013
#
# project: conky.arcs theme for conky
# purpose: lua script which processes config.lua file and draws
#          system monitor with conky provided values
#          All customizable stuff is placed in config.lua
#          this file contains code to process that config file
#          and for so no need to change this script
]]--
require 'cairo'
cfg = require 'config'
settings = cfg.setup()


function splitString(str, sep)
  if sep == nil then
    sep = "%s"
  end
  local t = {}
  for s in string.gmatch(str, "([^"..sep.."]+)") do
    table.insert(t, s)
  end
  return t
end


function printTemperature(path, unit)
  local f = io.open(path, "rb")
  if not f then return 0 end
  local rv = f:read("*a")
  f:close()
  if (not unit == nil) then
     return string.format('%s°', tonumber(rv))
  else
     return string.format('%s', tonumber(rv))
  end
end


function printTemperatureMilli(path, unit)
  local f = io.open(path, "rb")
  if not f then return 0 end
  local rv = f:read("*a")
  f:close()
  if (not unit == nil) then
     return string.format('%s°', tonumber(rv) / 1000.0)
  else
     return string.format('%s', tonumber(rv) / 1000.0)
  end
end


function drawBackground(ctx, xOff, yOff, cfg)
  local w = cfg['w'] * settings['lw']
  local h = cfg['h'] * settings['lh']

  cairo_set_line_width(ctx, 0)
  cairo_rectangle(ctx, xOff, yOff, w, h)
  cairo_set_source_rgba(ctx, rgb2r_g_b(settings['bgCol'], 0.5))
  cairo_fill_preserve(ctx)
  cairo_stroke(ctx)
end


function drawRing(ctx, xOff, yOff, rd, cfg)
  local r  = rd['r'] * settings['lh']
  local cx = xOff + rd['x'] * settings['lw']
  local cy = yOff + rd['y'] * settings['lh'] + 1.7 * settings['lh']
  local s  = ''
  local sa = deg2rad(rd['sa'])
  local ea = deg2rad(rd['ea'])
  local ca = rd['ea'] - rd['sa']

  if (rd['name'] == 'printTemperature') then
     s = printTemperature(rd['arg'])
  elseif (rd['name'] == 'printTemperatureMilli') then
     s = printTemperatureMilli(rd['arg'])
  else
     s = conky_parse(string.format('${%s %s}', rd['name'], rd['arg']))
  end
  local v = tonumber(s)

  if v == nil then v = 0 end
  if v > 100  then v = 100 end
  if rd['d'] == 'ccw' then
     ca = 360 - rd['ea'] + rd['sa']
  end
  ca = ca / 100.0 * v
  if rd['d'] == 'ccw' then
    cairo_arc_negative(ctx, cx, cy, r, sa, ea)
  else
    cairo_arc(ctx, cx, cy, r, sa, ea)
  end
  local bg = rd['bg']
  local ba = rd['ba']
  local fg = rd['fg']

  if bg == nil then bg = settings['rBgC'] end
  if ba == nil then ba = settings['alpha'] end
  if fg == nil then fg = settings['rCol']  end
  cairo_set_source_rgba(ctx, rgb2r_g_b(bg, ba))
  cairo_set_line_width(ctx, settings['lh'] * rd['h'])
  cairo_stroke(ctx)

  if rd['d'] == 'ccw' then
    cairo_arc_negative(ctx, cx, cy, r, sa, deg2rad(rd['sa'] - ca))
  else
    cairo_arc(ctx, cx, cy, r, sa, deg2rad(rd['sa'] + ca))
  end
  cairo_set_source_rgba(ctx, rgb2r_g_b(fg, 0.8))
  cairo_set_line_width(ctx, settings['lh'] * rd['h'])
  cairo_stroke(ctx)
end


function printMainText(ctx, te, xOff, yOff, td, cfg)
  local x = xOff + td['x'] * settings['lw']
  local y = yOff + td['y'] * settings['lh']
  local c = settings['color']
  local a = settings['alpha']
  local fs = td['s']

  if fs == nil then fs = 0 end
  if td['text']:sub(0, 1) == '$' then
     s = conky_parse(td['text'])
     if td['text']:sub(3,5) == 'fre' then
        v = tonumber(s)
        if v == nil then v = 0 end
        local l1 = cfg['level1']
        local l2 = cfg['level2']

        if l1 == nil then l1 = 2000 end
        if l2 == nil then l2 = 2990 end
        if v > l2 then     c,a = settings['col2'],1
        elseif v > l1 then c,a = settings['col1'],1
        end
        cairo_select_font_face(ctx
                             , settings['font']
                             , CAIRO_FONT_SLANT_NORMAL
                             , CAIRO_FONT_WEIGHT_BOLD)
     else
        if fs > 0 then
           cairo_set_font_size(ctx, fs)
        else
           cairo_select_font_face(ctx
                                , settings['font']
                                , CAIRO_FONT_SLANT_NORMAL
                                , CAIRO_FONT_WEIGHT_NORMAL)
        end
     end
  elseif td['text']:sub(0,4) == 'LIF:' then
     local p = splitString(td['text'], ' ')
     if (p[2] == 'printTemperature') then
        s = printTemperature(p[3], '°')
    elseif (p[2] == 'printTemperatureMilli') then
        s = printTemperatureMilli(p[3], '°')
     end
  else
     if fs > 0 then
        cairo_set_font_size(ctx, fs)
     end
     s = td['text']
  end

  if td['a'] == 'L' then
     cairo_text_extents(ctx, s, te)
     x = x - te.width
  end
  cairo_move_to(ctx, x, y)
  cairo_set_source_rgba(ctx, rgb2r_g_b(c, settings['alpha']))
  cairo_show_text(ctx, s)
  cairo_stroke(ctx)
end


function printText(ctx, te, xOff, yOff, td)
  local x = xOff + td['x'] * settings['lw']
  local y = yOff + td['y'] * settings['lh']

  if td['text']:sub(0,1) == '$' then
     s = conky_parse(td['text'])
  else
     s = td['text']
  end
  if td['a'] == 'L' then
     cairo_text_extents(ctx, s, te)
     x = x - te.width
  end
  cairo_move_to(ctx, x, y)
  cairo_show_text(ctx, s)
  cairo_stroke(ctx)
end


function deg2rad(d)
  return (d - 90) * math.pi / 180
end


function rgb2r_g_b(col, alpha)
  local c = tonumber(col, 16)

  return ((c / 0x10000) % 0x100) / 255.
       , ((c / 0x100)   % 0x100) / 255.
       ,  (c % 0x100)            / 255.
       ,  alpha
end


function genStatics(ctx, te)
  local xOff = settings['x'] * settings['lw']
  local yOff = settings['y'] * settings['lh']
  cairo_select_font_face(ctx
                       , settings['hFont']
                       , CAIRO_FONT_SLANT_NORMAL
                       , CAIRO_FONT_WEIGHT_NORMAL)
  cairo_set_font_size(ctx, 30)
  cairo_set_source_rgba(ctx, rgb2r_g_b(settings['color'], settings['alpha']))

  for i in pairs(settings['uptime']) do
      printMainText(ctx, te, xOff, yOff, settings['uptime'][i])
  end
  for i in pairs(settings['date']) do
      printMainText(ctx, te, xOff, yOff, settings['date'][i])
  end
  for i in pairs(settings['time']) do
      printMainText(ctx, te, xOff, yOff, settings['time'][i])
  end
  cairo_select_font_face(ctx
                       , settings['font']
                       , CAIRO_FONT_SLANT_NORMAL
                       , CAIRO_FONT_WEIGHT_NORMAL)
  cairo_set_font_size(ctx, settings['fs'])
  cairo_set_source_rgba(ctx, rgb2r_g_b(settings['color'], settings['alpha']))
  for i in pairs(settings['sys']) do
      printMainText(ctx, te, xOff, yOff, settings['sys'][i])
  end
end


function genPanel(ctx, te, panelName)
  local cfg = settings[panelName]
  local xOff = cfg['x'] * settings['lw']
  local yOff = cfg['y'] * settings['lh']

  if settings['sb'] then drawBackground(ctx, xOff, yOff, cfg) end
  for i in pairs(cfg['rings']) do
      drawRing(ctx, xOff, yOff, cfg['rings'][i], cfg)
  end
  for i in pairs(cfg['main']) do
      printMainText(ctx, te, xOff, yOff, cfg['main'][i], cfg)
  end
  local fg = cfg['color']

  if fg == nil then fg = settings['col3'] end
  cairo_set_source_rgba(ctx, rgb2r_g_b(fg, settings['alpha']))
  for i in pairs(cfg['sub']) do
      printText(ctx, te, xOff, yOff, cfg['sub'][i])
  end
end


function conky_main()
  if conky_window == nil then return end
  local cs = cairo_xlib_surface_create(conky_window.display
                                     , conky_window.drawable
                                     , conky_window.visual
                                     , conky_window.width
                                     , conky_window.height)
  local ctx = cairo_create(cs)
  local updates = conky_parse('${updates}')

  nUpdate = tonumber(updates)
  if nUpdate > 2 then
     local te = cairo_text_extents_t:create()

     tolua.takeownership(te)
     cairo_select_font_face(ctx
                          , settings['font']
                          , CAIRO_FONT_SLANT_NORMAL
                          , CAIRO_FONT_WEIGHT_NORMAL)
     cairo_set_font_size(ctx, settings['fs'])
     cairo_text_extents(ctx, 'Wy', te)
     -- calculate position units based on font size
     settings['lh'] = te.height
     settings['lw'] = te.width * 0.4

     genStatics(ctx, te)
     cairo_select_font_face(ctx
                          , settings['font']
                          , CAIRO_FONT_SLANT_NORMAL
                          , CAIRO_FONT_WEIGHT_NORMAL)
     cairo_set_font_size(ctx, settings['fs'])
     genPanel(ctx, te, 'cpu')
     genPanel(ctx, te, 'io')
     genPanel(ctx, te, 'mem')
     genPanel(ctx, te, 'sensors')
     genPanel(ctx, te, 'hddTemp')
     genPanel(ctx, te, 'hdd')
  end
  cairo_surface_destroy(cs)
  cairo_destroy(ctx)
end
... and the installer that checks existing hardware:

Code: Select all

#!/usr/bin/env python3
#
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2021-2025 django013
#
# project: conky.arcs theme for conky
# purpose: installation script for conky.arcs theme
#          Script explores running system and generates matching
#          gui elements for conky system monitor.
import os
import sys
import glob
import array
import logging as log
import pathlib
import subprocess
from operator import itemgetter, attrgetter, methodcaller


def conkyCAPsFailed(app):
    print(f'conky [{app}] is not capable to run conky.arcs theme.')
    print('conky.arcs needs conky with lua and cairo support.')
    print('Try to install "conky-all" or use an appimage from ')
    print('https://github.com/brndnmtthws/conky/releases')
    print('')
    exit(-1)


def first(iterable, condition = lambda x: True):
    return next(x for x in iterable if condition(x))


def usage():
    print('conky.arc is a theme for system monitor app "conky"')
    print('this install-script evaluates your system and generates')
    print('matching config file.')
    print('')
    print('if conky is not found by PATH environment, ...')
    print('please provide the path to conky application as commandline ')
    print('argument. Install script then checks capability of conky.')
    print('')
    exit(0)


class ConfigReader:
  def __init__(self, conky):
      hasLua, hasCairo = self.evalConky(conky)
      if not hasLua or not hasCairo:
         conkyCAPsFailed(conky)
      self.app     = conky
      self.curdir  = os.getcwd()
      self.homedir = pathlib.Path.home()
      self.sensors = self.getSensors()
      self.nCPU    = self.countCPU()
      self.netIF   = self.networkIF()
      self.md      = self.mounted_drives()
      self.hds     = self.harddisks()
      self.hdTemps = self.driveTemps()
      self.cpuFreqs()


  def countCPU(self, maxCPU = 6):
      cpus0 = glob.glob('/sys/devices/system/cpu/cpu?')
      cpus1 = glob.glob('/sys/devices/system/cpu/cpu??')
      cpus = sorted(cpus0 + cpus1)

      return len(cpus)


  def cpuFreqs(self):
      f0 = 10000
      f1 = 0
      with open('/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq') as f:
           for line in f:
               f0 = int(line) / 1000

      with open('/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq') as f:
           for line in f:
               f1 = int(line) / 1000
      self.fCPU0 = f0
      self.fCPU1 = f1


  def driveTemps(self):
      driveTemps = []
      for d in glob.glob('/tmp/temps/*'):
          p = d.split('/')
          dt = {}
          dt['name'] = p[3]
          dt['path'] = d
          driveTemps.append(dt)
      return driveTemps


  def evalConky(self, app):
      p = pathlib.Path(app)
      if not p.exists():
         print('conky NOT found. Please install "conky-all"')
         print('')
         exit(0)
      p = subprocess.Popen([app, '--version'], stdout=subprocess.PIPE)
      line = p.stdout.readline()
      hasLua   = False
      hasCairo = False
      while line:
            if line.strip().startswith(b'Lua bind'):
               hasLua = True
               line = p.stdout.readline()
               while line.strip().startswith(b'*'):
                     parts = line.strip().split(b' ')
                     if parts[1] == b'Cairo':
                        hasCairo = True
                        return [hasLua, hasCairo]
                     line = p.stdout.readline()
            line = p.stdout.readline()
      return [hasLua, hasCairo]


  def getSensors(self):
      sensors = []
      paths = glob.glob('/sys/class/hwmon/hwmon*')
      for i in range(0, len(paths)):
          sensor = {}
          with open('/sys/class/hwmon/hwmon{}/name'.format(i)) as f:
              name = f.read().strip()
              sensor['type'] = name
              sensor['name'] = name
              if ('nvme' in name):
                  subname = first(glob.glob('/sys/class/hwmon/hwmon{0}/device/nvme*'.format(i)))
                  subname = os.path.basename(subname)
                  sensor['name'] = subname
              elif ('spd5118' in name):
                  subname = 'SMBus PIIX4'
                  sensor['name'] = subname
              temp = 0
              path = '/sys/class/hwmon/hwmon{}/temp1_input'.format(i)
              p = pathlib.Path(path)
              if not p.exists():
                  continue
              sensor['path'] = path
              with open(path) as ft:
                   temp = float(ft.read()) / 1000.0
              sensor['temp'] = temp
              sensors.append(sensor)
      sensors.sort(key=lambda x: x['name'], reverse=False)
      return sensors


  def mounted_drives(self):
      drives = []
      check = []    # avoid duplicates
      with open('/proc/mounts') as f:
           for line in f:
               if not line.startswith('/'):
                  continue
               if 'loop' in line:
                  continue
               parts = line.split()
               mountPoint = {}
               if parts[0] in check:
                  continue
               else:
                  check.append(parts[0])
                  sp = parts[0].split('/')
                  mountPoint['part'] = sp[2]
                  mountPoint['mounted'] = parts[1]
               drives.append(mountPoint)
      drives.sort(key=lambda x: x['mounted'], reverse=False)
      return drives


  def harddisks(self):
      disks0 = sorted(glob.glob('/dev/sd?'))
      disks1 = sorted(glob.glob('/dev/nvme?n?'))
      hdds = {}
      for d in disks0:
          parts = d.split('/')
          hdds[parts[2]] = d
      for d in disks1:
          parts = d.split('/')
          hdds[parts[2]] = d
      return hdds


  def networkIF(self):
      nif = []
      with open('/proc/net/dev') as f:
           for line in f:
               ifName = line.split()[0]
               if ifName == 'Inter-|' or ifName == 'face' or ifName == 'lo:':
                  continue
               nif.append(ifName.split(':')[0])
      return nif


  def dump(self):
      print('============================================================')
      print('conky [{0}] has lua and cairo support'.format(self.app))
      print('curdir:\t' + self.curdir)
      print('homedir:\t' + str(self.homedir))
      print('found {0} cpu cores'.format(self.nCPU))
      if len(self.sensors) < 2:
         print('NO temperatures for gpu!')
      print('network interfaces:')
      for n in self.netIF:
          print('\tfound interface:\t{0}'.format(n))
      print('harddisks:')
      for d in self.hds:
          print('\tfound disk:\t{0}'.format(d))
      print('mounted partitions:')
      for d in self.md:
          print('\t{0}\twith path:\t{1}'.format(d['part'], d['mounted']))
      print('sensors')
      for s in self.sensors:
          print('sensor {0} has temp {1}\t\tat: {2}'.format(s['name'], s['temp'], s['path']))
      print('drive temperatures:')
      for d in self.hdTemps:
          print('drive {0} with temp-path {1}'.format(d['name'], d['path']))
      print('============================================================')


class ConfigWriter:
  def __init__(self, cfgReader):
      self.cr = cfgReader
      self.base = str(self.cr.homedir) + '/.conky/MX-Circles'
      self.LuaTemplates = {}
      self.ConkyTemplates = {}
      self.ConkyTemplates['intro'] = """conky.config = {
    background = false,
    update_interval = 2,
    cpu_avg_samples = 2,
    net_avg_samples = 2,
    temperature_unit = 'celsius',
    double_buffer = true,
    no_buffers = true,
    text_buffer_size = 2048,
    gap_x = 2,
    gap_y = 50,
    minimum_width = 1020,
    minimum_height = 1200,
    own_window = true,
    own_window_type = 'normal',
    own_window_transparent = true,
    own_window_argb_visual = true,
    own_window_hints = 'undecorated,below,sticky,skip_taskbar,skip_pager',
    border_inner_margin = 0,
    border_outer_margin = 0,
    alignment = 'top_right',
    draw_shades = false,
    draw_outline = false,
    draw_borders = false,
    draw_graph_borders = false,
    override_utf8_locale = true,
    use_xft = true,
    xftalpha = 0.5,
    font = 'Cuprum:size=7',
    top_cpu_separate = true,
    lua_load = './conky.arcs.lua',
    lua_draw_hook_post = 'main'
}

conky.text = [[
${voffset 460}"""
      self.ConkyTemplates['diskRead'] = '\n${{goto {0}}}${{diskiograph_read    44, 300 ADFF2F 32CD32 -t -l}}'
      self.ConkyTemplates['diskWrite'] = '\n${{goto {0}}}${{diskiograph_write   44, 300 FF0000 8B0000 -t -l}}'
      self.ConkyTemplates['netDown'] = '\n${{goto {0}}}${{downspeedgraph {1} 21, 240 ADFF2F 32CD32 -t -l}}'
      self.ConkyTemplates['netUp'] = '\n${{goto {0}}}${{upspeedgraph   {1} 21, 240 FF0000 8B0000 -t -l}}'
      self.ConkyTemplates['fin'] = '\n]]'
      self.LuaTemplates['intro'] = """local config = {}

function config.setup()
  return {"""
      self.LuaTemplates['inter0'] = """
    },
    sub = {"""
      self.LuaTemplates['inter1'] = """
    },
    rings = {"""
      self.LuaTemplates['fin'] = """
}
end

return config"""
      self.LuaTemplates['main0'] = """
  x     = 11,
  y     =  0,
  fs    = 18,
  font  = 'Cuprum',
  hFont = 'Vast Shadow',
  rCol  = '95275C',
  rBgC  = '565656',
  color = 'FFFFFF',
  col1  = 'F2B70E',
  col2  = 'E90812',
  col3  = 'CDCDCD',
  bgCol = '993636',
  alpha = 0.9,
  sb    = false,
  uptime = {
  --[[
      x/y the position where the text should be aligned to
      a: L means text ends at x/y or is on the left side of position
      a: R means text starts at x/y or is on the right side of position
      text starting with '$' will be translated by conky engine
    ]]--
    { x = 37, y = 2, a = 'L', s = 30, text = 'Uptime' },
    { x = 38, y = 2, a = 'R', s = 30, text = '${uptime_short}' },
  },
  date = {"""
      self.LuaTemplates['mainDate'] = """
    {{ x = 27, y = {0}, a = 'L', s = 40, text = '${{time %A}}' }},
    {{ x = 29, y = {0}, a = 'R', s = 40, text = '${{time %x}}' }},
  }},
"""
      self.LuaTemplates['mainTime'] = """
  time = {{
    {{ x = 35, y = {0}, a = 'L', s = 80, text = '${{time %H}}' }},
    {{ x = 37, y = {0}, a = 'R', s = 80, text = ':' }},
    {{ x = 41, y = {0}, a = 'R', s = 80, text = '${{time %M}}' }},
  }},
  sys = {{"""
      self.LuaTemplates['main1'] = """
    {{ x = 37, y =  {0},   a = 'L', s = 20, text = 'public IP:' }},
    {{ x = 38, y =  {0},   a = 'R', s = 20, text = "${{execi 5 wget -q -O - checkip.dyndns.org | sed -e 's/[^[:digit:]\\\\|.]//g'}}" }},
    {{ x = 15, y = {1},   a = 'R', s = 30, text = "${{execi 3600 cat /etc/mx-version | awk '{{ print $1 \\" \\" $2; }}' | sed -e 's/_/ /g'}}" }},
    {{ x = 15, y = {2},   a = 'R', s = 20, text = "${{execi 3600 grep PRETTY_NAME /etc/os-release | sed -e 's/=/ /' | awk '{{ print $2 \\" \\" $3 \\" \\" $4 \\" \\" $5; }}' | sed -e 's/\\"//g'}}" }},"""
      self.LuaTemplates['IOheaders'] = """
    {{ x = 87, y = {0},   a = 'R', s = 25, text = 'network Traffic' }},
    {{ x = 87, y = {1},   a = 'R', s = 25, text = 'disk Traffic' }},
  }},
"""
      self.LuaTemplates['netMain'] = "\n    {{ x = 37, y =  {0},   a = 'L', s = 20, text = 'local {1}:' }},\n    {{ x = 38, y =  {0},   a = 'R', s = 20, text = '${{addr {1}}}' }},"
      self.LuaTemplates['cpuInit'] = """
  cpu = {
    x = 79,
    y =  0,
    w = 30,
    h = 13,
    level1 = 3500,
    level2 = 4500,
    main = {"""
      self.LuaTemplates['cpuMain0'] = """
      {{ x =  0, y =  {0}, a = 'R', text = 'CPU' }},
      {{ x = 15, y =  {0}, a = 'L', text = '${{cpu cpu{1}}}%' }},"""
      self.LuaTemplates['cpuMainN'] = """
      {{ x =  0, y =  {0}, a = 'R', text = 'CPU {1}' }},
      {{ x = 10, y =  {0}, a = 'L', text = '${{freq {1}}}' }},
      {{ x = 15, y =  {0}, a = 'L', text = '${{cpu cpu{1}}}%' }},"""
      self.LuaTemplates['cpuTop'] = """
      {{ x =  6, y = {0}, a = 'R', text = '${{top name {1}}}' }},
      {{ x = 26, y = {0}, a = 'L', text = '${{top cpu {1}}}%' }},"""
      self.LuaTemplates['cpuRing0'] = "\n      {{ x = 15, y = {0}, r = {1}, h = 0.9, d = 'cw', name = 'cpu', arg = '', max = 100, sa = 0, ea = 300, ba = {2}, fg = 'F38A07' }},"
      self.LuaTemplates['cpuRingN'] = "\n      {{ x = 15, y = {0}, r = {1}, h = 0.9, d = 'cw', name = 'cpu', arg = 'cpu{2}', max = 100, sa = 0, ea = 300, ba = {3} }},"
      self.LuaTemplates['cpuFin'] = """
    },
  },
"""
      self.LuaTemplates['memInit'] = """
  mem = {
    x = 49,
    y = 38,
    w = 36,
    h = 16,
    main = {
      { x = 15, y = 1, a = 'R', text = 'Mem:' },
      { x = 28, y = 1, a = 'L', text = '${mem}' },
      { x = 29, y = 1, a = 'L', text = '/' },
      { x = 30, y = 1, a = 'R', text = '${memmax}' },
      { x = 15, y = 2, a = 'R', text = 'Swap:' },
      { x = 28, y = 2, a = 'L', text = '${swap}' },
      { x = 29, y = 2, a = 'L', text = '/' },
      { x = 30, y = 2, a = 'R', text = '${swapmax}' },
    },
    sub = {"""
      self.LuaTemplates['memVal'] = """
      {{ x = 15, y =  {0}, a = 'R', text = '{2} Down' }},
      {{ x = 36, y =  {0}, a = 'L', text = '${{totaldown {2}}}' }},
      {{ x = 15, y =  {1}, a = 'R', text = '{2} UP' }},
      {{ x = 36, y =  {1}, a = 'L', text = '${{totalup {2}}}' }},
"""
      self.LuaTemplates['memTop'] = """
      {{ x = 10, y =  {0}, a = 'R', text = '${{top_mem name {1}}}' }},
      {{ x = 28, y =  {0}, a = 'L', text = '${{top_mem mem  {1}}}%' }},"""
      self.LuaTemplates['memRing0'] = """
      {{ x = 15, y = {0}, r = {1}, h = 0.9, d = 'ccw', name = 'memperc',    arg = '',     max = 100, sa = 0, ea = 65, ba = 0.8 }},
      {{ x = 15, y = {0}, r = {2}, h = 0.5, d = 'ccw', name = 'swapperc',   arg = '',     max = 100, sa = 0, ea = 60, ba = 0.8 }},"""
      self.LuaTemplates['memRingN'] = """
      {{ x = 15, y = {0}, r = {1}, h = 0.8, d = 'ccw', name = 'downspeedf', arg = '{2}', max = 100, sa = 0, ea = 55, ba = {3} }},
      {{ x = 15, y = {0}, r = {4}, h = 0.9, d = 'ccw', name = 'upspeedf',   arg = '{2}', max = 100, sa = 0, ea = 45, ba = {3} }},"""
      self.LuaTemplates['memFin'] = """    },
  },
"""
      self.LuaTemplates['hddInit'] = """
  lh  = 1,
  lw  = 1,
  hdd = {
    x = 87,
    y = 60,
    w = 29,
    h =  6,
    main = {"""
      self.LuaTemplates['hddMain'] = """
      {{ x = 11.5, y = {0},  a = 'R', text = '{1}' }},"""
      self.LuaTemplates['hddSub']  = """
      {{ x = 22, y = {0},  a = 'R', text = '{1}' }},"""
      self.LuaTemplates['hddRing'] = """
      {{ x = 11, y = {0}, r = {1}, h = 0.7, d = 'cw', name = 'fs_used_perc', arg = '{2}', max = 100, sa = 180, ea = 455, ba = {3} }},"""
      self.LuaTemplates['hddFin'] = """    },
  },
"""
      self.LuaTemplates['sensorsInit'] = """
  sensors = {
    x = 36,
    y = 17,
    w = 31,
    h = 16,
    main = {"""
      self.LuaTemplates['sensorsMain'] = """
      {{ x = 10, y = {0}, a = 'R', text = 'LIF: printTemperatureMilli {1}' }},"""
      self.LuaTemplates['sensorsName'] = """
      {{ x =  9, y = {0}, a = 'L', text = '{1}' }},"""
      self.LuaTemplates['sensorsRing'] = """
      {{ x = 15, y = {0}, r = {1}, h = 0.8, d = 'cw', name = 'printTemperatureMilli', arg = '{2}', max = 90, sa = 0, ea = 270, ba = {3}}},"""
      self.LuaTemplates['sensorsFin'] = """
    },
  },
"""
      self.LuaTemplates['hddTempInit'] = """
  hddTemp = {
    x = 43,
    y = 57,
    w = 31,
    h = 16,
    main = {"""
      self.LuaTemplates['hddTempMain'] = """
      {{ x = 10, y = {0}, a = 'R', text = 'LIF: printTemperature {1}' }},"""
      self.LuaTemplates['hddTempName'] = """
      {{ x =  9, y = {0}, a = 'L', text = '{1}' }},"""
      self.LuaTemplates['hddTempRing'] = """
      {{ x = 15, y = {0}, r = {1}, h = 0.8, d = 'cw', name = 'printTemperature', arg = '{2}', max = 90, sa = 0, ea = 270, ba = {3}}},"""
      self.LuaTemplates['hddTempFin'] = """
    },
  },"""


  def copyFonts(self):
      fd = '/usr/share/fonts/truetype/freefonts'
      print("need to install some fonts\n")
      subprocess.check_call(['sudo', 'mkdir', '-p', fd])
      fonts = glob.glob('fonts/*.ttf')
      for f in fonts:
          subprocess.check_call(['sudo', 'cp', f, fd])


  def install(self):
      p = pathlib.Path(self.base)
      if not p.exists():
          os.makedirs(self.base)
      self.copyFonts()
      subprocess.check_call(['cp', 'conky.arcs.lua', self.base])
      self.writeConkyConfig()
      self.writeLuaConfig()


  def writeConkyConfig(self):
      with open(self.base + '/MX-Circles', 'w') as f:
           f.write(self.ConkyTemplates['intro'])
           for ifn in self.cr.netIF:
               f.write(self.ConkyTemplates['netDown'].format(750, ifn))
           for ifn in self.cr.netIF:
               f.write(self.ConkyTemplates['netUp'].format(750, ifn))
           f.write(self.ConkyTemplates['diskRead'].format(710))
           f.write(self.ConkyTemplates['diskWrite'].format(710))
           f.write(self.ConkyTemplates['fin'])


  def writeLuaConfig(self):
      with open(self.base + '/config.lua', 'w') as f:
           f.write(self.LuaTemplates['intro'])
           self.writeMainSection(f)
           self.writeCpuSection(f)
           self.writeIOSection(f)
           self.writeMemSection(f)
           self.writeHddSection(f)
           self.writeSensorsSection(f)
           self.writeHddTempSection(f)
           f.write(self.LuaTemplates['fin'])


  def writeMainSection(self, f):
      f.write(self.LuaTemplates['main0'])
      y = 5 + len(self.cr.netIF)
      f.write(self.LuaTemplates['mainDate'].format(y))
      y = 9 + len(self.cr.netIF)
      f.write(self.LuaTemplates['mainTime'].format(y))
      y = 3
      for ifn in self.cr.netIF:
          f.write(self.LuaTemplates['netMain'].format(y, ifn))
          y += 1
      y0 =  y
      y1 =  8 + y
      y2 =  9 + y
      f.write(self.LuaTemplates['main1'].format(y0, y1, y2))
      y0 = 30 + len(self.cr.netIF) * 2
      y1 = 36 + len(self.cr.netIF) * 3
      f.write(self.LuaTemplates['IOheaders'].format(y0, y1))


  def writeCpuSection(self, f):
      f.write(self.LuaTemplates['cpuInit'])
      f.write(self.LuaTemplates['cpuMain0'].format(1, 0))
      for i in range(1, 1 + self.cr.nCPU):
          f.write(self.LuaTemplates['cpuMainN'].format(1 + i, i))
      f.write(self.LuaTemplates['inter0'])
      for i in range(0, min(10, self.cr.nCPU)):
          f.write(self.LuaTemplates['cpuTop'].format(3 + self.cr.nCPU + i, 1 + i))
      f.write(self.LuaTemplates['inter1'])
      trans = 0.7
      f.write(self.LuaTemplates['cpuRing0'].format(1 + self.cr.nCPU, 2 + self.cr.nCPU, trans))
      for i in range(0, self.cr.nCPU):
          if (i % 2 == 0):
             trans -= 0.1
          trans = max(0.3, trans)
          f.write(self.LuaTemplates['cpuRingN'].format(1 + self.cr.nCPU, 1 + self.cr.nCPU - i, 1 + i, trans))
      f.write(self.LuaTemplates['cpuFin'])


  def writeIOSection(self, f):
      f.write("""
  io = {
    x = 73,
    y = 28,
    w = 35,
    h =  9,
    main = {
      { x = 15, y = 5, a = 'R', text = 'Disk Reads' },
      { x = 14, y = 5, a = 'L', text = '${diskio_read}' },
      { x = 15, y = 6, a = 'R', text = 'Disk Writes' },
      { x = 14, y = 6, a = 'L', text = '${diskio_write}' },
    },
    sub = {
      { x = 15, y =  7, a = 'R', text = '${top_io name 1}' },
      { x = 14, y =  7, a = 'L', text = '${top_io io_write 1}' },
      { x = 15, y =  8, a = 'R', text = '${top_io name 2}' },
      { x = 14, y =  8, a = 'L', text = '${top_io io_write 2}' },
      { x = 15, y =  9, a = 'R', text = '${top_io name 3}' },
      { x = 14, y =  9, a = 'L', text = '${top_io io_write 3}' },
    },
    rings = {
      { x = 6, y = 3, r = 2, h = 0.8, d = 'ccw', name = 'top_io io_perc 1', arg = '', max = 100, sa = 80, ea = 180, ba = 0.3  },
      { x = 6, y = 3, r = 3, h = 0.8, d = 'ccw', name = 'top_io io_perc 2', arg = '', max = 100, sa = 80, ea = 180, ba = 0.45 },
      { x = 6, y = 3, r = 4, h = 0.8, d = 'ccw', name = 'top_io io_perc 3', arg = '', max = 100, sa = 80, ea = 180, ba = 0.6  },
    },
  },
""")


  def writeMemSection(self, f):
      f.write(self.LuaTemplates['memInit'])
      n=0
      for nif in self.cr.netIF:
          f.write(self.LuaTemplates['memVal'].format(3 + n, 4 + n, nif))
          n += 2
      mx = 2 * len(self.cr.netIF)
      for i in range(1, min(8, 2 + mx)):
          f.write(self.LuaTemplates['memTop'].format(3 + mx + i, i))
      f.write(self.LuaTemplates['inter1'])
      f.write(self.LuaTemplates['memRing0'].format(2 + mx, 3 + mx, 2 + mx))
      n=0
      trans = 0.6
      for nif in self.cr.netIF:
          f.write(self.LuaTemplates['memRingN'].format(2 + mx, 1 + mx - n, nif, trans, mx - n))
          trans -= 0.2
          n += 2
          trans = max(0.3, trans)
      f.write(self.LuaTemplates['memFin'])


  def writeHddSection(self, f):
      f.write(self.LuaTemplates['hddInit'])
      mx = len(self.cr.md)
      n=0
      for mp in self.cr.md:
          f.write(self.LuaTemplates['hddMain'].format(4 + n, mp['part']))
          n += 1
      f.write(self.LuaTemplates['inter0'])
      n=0
      for mp in self.cr.md:
          f.write(self.LuaTemplates['hddSub'].format(4 + n, mp['mounted']))
          n += 1
      f.write(self.LuaTemplates['inter1'])
      revDrives = self.cr.md
      revDrives.sort(key=lambda x: x['mounted'], reverse=True)
      trans = 0.9
      n=0
      for mp in revDrives:
          f.write(self.LuaTemplates['hddRing'].format(0, 1 + mx - n, mp['mounted'], trans))
          n += 1
          if (n % 2 == 1):
             trans -= 0.1
          trans = max(0.3, trans)
      f.write(self.LuaTemplates['hddFin'])


  def writeSensorsSection(self, f):
      f.write(self.LuaTemplates['sensorsInit'])
      n=0
      for s in self.cr.sensors:
          f.write(self.LuaTemplates['sensorsMain'].format(1 + n, s['path']))
          n += 1
      f.write(self.LuaTemplates['inter0'])
      n=0
      for s in self.cr.sensors:
          f.write(self.LuaTemplates['sensorsName'].format(1 + n, s['name']))
          n += 1
      f.write(self.LuaTemplates['inter1'])
      mx = len(self.cr.sensors)
      trans = 0.9
      n=0
      for s in self.cr.sensors:
          f.write(self.LuaTemplates['sensorsRing'].format(mx, 1 + mx - n, s['path'], trans))
          n += 1
          trans -= 0.1
          trans = max(0.3, trans)
      f.write(self.LuaTemplates['sensorsFin'])


  def writeHddTempSection(self, f):
      f.write(self.LuaTemplates['hddTempInit'])
      n=0
      for d in self.cr.hdTemps:
          f.write(self.LuaTemplates['hddTempMain'].format(1 + n, d['path']))
          n += 1
      f.write(self.LuaTemplates['inter0'])
      n=0
      for d in self.cr.hdTemps:
          f.write(self.LuaTemplates['hddTempName'].format(1 + n, d['name']))
          n += 1
      f.write(self.LuaTemplates['inter1'])
      mx = len(self.cr.hdTemps)
      trans = 0.9
      n=0
      for d in self.cr.hdTemps:
          f.write(self.LuaTemplates['hddTempRing'].format(mx, 1 + mx - n, d['path'], trans))
          n += 1
          trans -= 0.1
          trans = max(0.3, trans)
      f.write(self.LuaTemplates['hddTempFin'])


if __name__ == "__main__":
   tmp = subprocess.check_output(["/bin/bash", "which", "conky"])
   if not tmp:
      usage()
   conkyapp = tmp.decode('utf-8').strip()
   if len(sys.argv) > 1:
      if sys.argv[1] == '--help':
         usage()
      else:
         p = pathlib.Path(sys.argv[1])
         if p.exists():
            conkyapp = sys.argv[1]
         else:
            usage()
   cr = ConfigReader(conkyapp)
   cr.dump()
   cw = ConfigWriter(cr)
   cw.install()
I need a bit to understand your setup hints, but I'll go for it.

... about polishing:
the installer is about to create the config setup from existing hardware.
As each group of rings is a single gui element, manual polishing by adjusting just x/y from the group is a question of few minutes.
I use 3 monitors rotated to portrait mode, so - for me - size is no issue :eek:

As I noted, that the nvme-sensor does not reflect the temperature of the drive, but possibly the temperature of the mainboard chip, I added former used drive temperatures like this:
Conky_MX-Circles_2.jpg
Can you post the config.lua from the installer on your system together with a screen shot?
Then I could check what to optimize.
You do not have the required permissions to view the files attached to this post.
Last edited by django013 on Mon Feb 03, 2025 9:33 am, edited 1 time in total.
MC: ASUS PRIME A320M-K, AMD Ryzen 5 3600, NVIDIA Quadro P400
WS: ASRock X670E Steel Legend, AMD Ryzen 5 7600X, NVIDIA GTX 1660 SUPER

User avatar
fehlix
Developer
Posts: 12693
Joined: Wed Apr 11, 2018 5:09 pm

Re: my conky circle theme

#9 Post by fehlix »

django013 wrote: Mon Feb 03, 2025 9:15 am I know about the problem with decimal points from original post. Therefore I wanted to replace the exec calls.
My problem in using hwmon from conky: I don't know how to enumerate sensors instead of inputs.
I have 4 sensors of type nvme ...
... that's why I wanted to go for /system filesystem directly.

Any way - I got it with the help from conky forum.
Maybe explain this issue, so somone can follow.
As long as all different sensores for all nvme's do have a different hwmon sys-path, no need to get it directly,
b/c syspath mappes 1-1 into hwmon call. At least here with having 2 nvme's and they do show separately,
using only hwmon-calls:
temps-rings.jpg
You do not have the required permissions to view the files attached to this post.

django013
Posts: 185
Joined: Sat Feb 11, 2023 3:25 am

Re: my conky circle theme

#10 Post by django013 »

but maybe you consider to move that "setup" logic into
a lua-function e.g. named like circle_setup(), where you re-formulate the python-logic into this lua-function,
and populate a "global" setup-table.
I thought already doing this.
conky.arcs.lua starts with:

Code: Select all

cfg = require 'config'
settings = cfg.setup()
... and generated config.lua looks like this:

Code: Select all

local config = {}

function config.setup()
  return {
  ...
  }
end

return config
and function conky_main then uses global table "settings"
Maybe explain this issue, so somone can follow.
may be I don't understand the point, you're leading me to.

Code: Select all

$ cat /sys/class/hwmon/hwmon0/name
nvme
which is the same for all nvme-sensors.

... and

Code: Select all

$ ll /sys/class/hwmon/hwmon0/device/
insgesamt 0
drwxr-xr-x 10 root root    0  3. Feb 14:08 nvme1n1
...
so from device's subdirectory I take the "real" name of the sensor
MC: ASUS PRIME A320M-K, AMD Ryzen 5 3600, NVIDIA Quadro P400
WS: ASRock X670E Steel Legend, AMD Ryzen 5 7600X, NVIDIA GTX 1660 SUPER

Post Reply

Return to “Community Submissions”