aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-09-17 19:08:13 +0200
committerDanilo M. <danix@danix.xyz>2026-09-17 19:08:13 +0200
commit9b18e17eee451a9a900f792add2c4c2eeb2af533 (patch)
tree7466a0953c5a4c9d7b3200723baf4d21a9bf225d
parentf1be1bef9c3563fd78f811f54e5a64b1d993e4da (diff)
downloadconky-theme-udt-9b18e17eee451a9a900f792add2c4c2eeb2af533.tar.gz
conky-theme-udt-9b18e17eee451a9a900f792add2c4c2eeb2af533.zip
feat: parse the CPU and motherboard model strings
Stripping by rule rather than by a table of known parts: a leading vendor word, trademark noise, a trailing core count or clock. The Intel fixture exists to keep it a rule. DMI placeholders read as no board at all. 'To Be Filled By O.E.M.' on the dashboard is worse than a blank row. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
-rw-r--r--lib/data.lua65
-rw-r--r--test/fixtures/proc_cpuinfo12
-rw-r--r--test/fixtures/proc_cpuinfo_intel6
-rw-r--r--test/test_data.lua35
4 files changed, 118 insertions, 0 deletions
diff --git a/lib/data.lua b/lib/data.lua
index 13e5ebd..1afc9c9 100644
--- a/lib/data.lua
+++ b/lib/data.lua
@@ -297,4 +297,69 @@ function M.kv_parse(text)
return out
end
+-- The CPU's marketing name, stripped to what identifies it.
+--
+-- '/proc/cpuinfo' repeats the model name once per thread; the first is enough.
+-- The stripping is rules, not a table of known CPUs: a leading vendor word,
+-- the registered-trademark noise, and a trailing core count or clock, all of
+-- which are constant boilerplate that costs a third of the row on a card
+-- measured in pixels.
+--
+-- AMD Ryzen 7 9700X 8-Core Processor -> Ryzen 7 9700X
+-- Intel(R) Core(TM) i7-8700K CPU @ 3.7GHz -> Core i7-8700K
+function M.cpu_model(cpuinfo)
+ if type(cpuinfo) ~= 'string' then return nil end
+ local s = cpuinfo:match('model name%s*:%s*([^\n]+)')
+ if not s then return nil end
+ s = s:gsub('%(R%)', ''):gsub('%(TM%)', ''):gsub('%(tm%)', '')
+ s = s:gsub('^%s*AMD%s+', ''):gsub('^%s*Intel%s+', '')
+ s = s:gsub('%s+%d+%-Core Processor.*$', '')
+ s = s:gsub('%s+CPU%s*@.*$', '')
+ s = s:gsub('%s+Processor%s*$', '')
+ s = s:gsub('%s+', ' '):gsub('^%s+', ''):gsub('%s+$', '')
+ if s == '' then return nil end
+ return s
+end
+
+-- The motherboard, from the two world-readable DMI files.
+--
+-- board_vendor and board_name are readable without privilege, unlike the
+-- serial fields in the same directory. The vendor's corporate suffix is
+-- dropped because every vendor has one and none of it identifies the board.
+--
+-- A board reporting the DMI placeholder is treated as no board at all:
+-- 'To Be Filled By O.E.M.' on the dashboard is worse than a blank row.
+local DMI_PLACEHOLDER = {
+ ['to be filled by o.e.m.'] = true,
+ ['system manufacturer'] = true,
+ ['default string'] = true,
+ ['unknown'] = true,
+ ['n/a'] = true,
+}
+
+local function dmi_clean(s)
+ if type(s) ~= 'string' then return nil end
+ s = s:gsub('%s+', ' '):gsub('^%s+', ''):gsub('%s+$', '')
+ if s == '' or DMI_PLACEHOLDER[s:lower()] then return nil end
+ return s
+end
+
+function M.board_name(vendor, name)
+ vendor = dmi_clean(vendor)
+ name = dmi_clean(name)
+ if vendor then
+ -- Corporate boilerplate, longest first so 'Co., Ltd.' does not leave 'Co.'
+ vendor = vendor:gsub('%s+Technology Co%.,? Ltd%.?$', '')
+ vendor = vendor:gsub('%s+COMPUTER INC%.?$', '')
+ vendor = vendor:gsub('%s+Corporation$', ''):gsub('%s+Corp%.?$', '')
+ vendor = vendor:gsub('%s+Inc%.?$', ''):gsub('%s+INC%.?$', '')
+ vendor = vendor:gsub('%s+Co%.,?%s*Ltd%.?$', ''):gsub('%s+CO%.,?%s*LTD%.?$', '')
+ vendor = vendor:gsub('%s+GmbH$', ''):gsub('%s+LLC$', '')
+ vendor = vendor:gsub('%s+$', '')
+ if vendor == '' then vendor = nil end
+ end
+ if vendor and name then return vendor .. ' ' .. name end
+ return name or vendor
+end
+
return M
diff --git a/test/fixtures/proc_cpuinfo b/test/fixtures/proc_cpuinfo
new file mode 100644
index 0000000..e9cd523
--- /dev/null
+++ b/test/fixtures/proc_cpuinfo
@@ -0,0 +1,12 @@
+processor : 0
+vendor_id : AuthenticAMD
+cpu family : 26
+model : 68
+model name : AMD Ryzen 7 9700X 8-Core Processor
+stepping : 0
+cpu MHz : 4491.436
+cache size : 1024 KB
+
+processor : 1
+vendor_id : AuthenticAMD
+model name : AMD Ryzen 7 9700X 8-Core Processor
diff --git a/test/fixtures/proc_cpuinfo_intel b/test/fixtures/proc_cpuinfo_intel
new file mode 100644
index 0000000..886b589
--- /dev/null
+++ b/test/fixtures/proc_cpuinfo_intel
@@ -0,0 +1,6 @@
+processor : 0
+vendor_id : GenuineIntel
+cpu family : 6
+model : 158
+model name : Intel(R) Core(TM) i7-8700K CPU @ 3.70GHz
+stepping : 10
diff --git a/test/test_data.lua b/test/test_data.lua
index fdf0e2f..3e741e6 100644
--- a/test/test_data.lua
+++ b/test/test_data.lua
@@ -239,4 +239,39 @@ assert(type(data.kv_parse('')) == 'table', 'empty input gives a table')
assert(type(data.kv_parse(nil)) == 'table', 'nil gives a table')
assert(data.kv_parse('keyonly\n').keyonly == nil, 'a line with no value is skipped')
+-- === Hardware identification ==============================================
+-- Both strings are constant for the machine's life, so the widget memoizes
+-- them. What is tested here is the stripping, which must be a rule rather
+-- than a special case for one host: the Intel fixture exists to prove it.
+assert(data.cpu_model(read('test/fixtures/proc_cpuinfo')) == 'Ryzen 7 9700X',
+ 'amd cpu, got ' .. tostring(data.cpu_model(read('test/fixtures/proc_cpuinfo'))))
+assert(data.cpu_model(read('test/fixtures/proc_cpuinfo_intel')) == 'Core i7-8700K',
+ 'intel cpu, got ' .. tostring(data.cpu_model(read('test/fixtures/proc_cpuinfo_intel'))))
+
+-- Unparseable input yields nil, so the card shows nothing rather than a
+-- half-stripped string.
+assert(data.cpu_model('') == nil, 'empty cpuinfo gives nil')
+assert(data.cpu_model(nil) == nil, 'nil cpuinfo gives nil')
+assert(data.cpu_model('processor\t: 0\n') == nil, 'cpuinfo with no model name gives nil')
+
+-- The board is two sysfs files joined, with the vendor's corporate suffix
+-- dropped: it is boilerplate on every board and costs a third of the row.
+assert(data.board_name('Gigabyte Technology Co., Ltd.\n', 'X870 EAGLE WIFI7\n')
+ == 'Gigabyte X870 EAGLE WIFI7',
+ 'board, got ' .. tostring(data.board_name('Gigabyte Technology Co., Ltd.\n', 'X870 EAGLE WIFI7\n')))
+assert(data.board_name('ASUSTeK COMPUTER INC.\n', 'PRIME B650-PLUS\n')
+ == 'ASUSTeK PRIME B650-PLUS',
+ 'asus board, got ' .. tostring(data.board_name('ASUSTeK COMPUTER INC.\n', 'PRIME B650-PLUS\n')))
+
+-- A machine that reports one and not the other shows what it has.
+assert(data.board_name(nil, 'X870 EAGLE WIFI7\n') == 'X870 EAGLE WIFI7',
+ 'name alone, got ' .. tostring(data.board_name(nil, 'X870 EAGLE WIFI7\n')))
+assert(data.board_name('Gigabyte\n', nil) == 'Gigabyte', 'vendor alone')
+assert(data.board_name(nil, nil) == nil, 'neither gives nil')
+
+-- A virtual machine reports placeholder DMI strings. Showing 'To be filled by
+-- O.E.M.' as the motherboard is worse than showing nothing.
+assert(data.board_name('To Be Filled By O.E.M.\n', 'To Be Filled By O.E.M.\n') == nil,
+ 'placeholder DMI gives nil')
+
print('test_data: all assertions passed')