mirror of
https://github.com/fastfetch-cli/fastfetch.git
synced 2026-09-12 10:22:12 +02:00
Compare commits
92 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b27503a385 | |||
| 39024abae9 | |||
| 4dba32aabb | |||
| 753ddec3c5 | |||
| b04587e0cb | |||
| 3abcf4195f | |||
| af8a42270c | |||
| 1c9e521775 | |||
| 7fe08bddd0 | |||
| 41da49f579 | |||
| f169165599 | |||
| 2c4caf8e0d | |||
| 51b370a105 | |||
| 14cf6bfdc1 | |||
| 4d00dc6802 | |||
| d7de7c41ca | |||
| d80fe3f004 | |||
| dcce4ccdb0 | |||
| 191c5f2ef5 | |||
| 1dc794fac0 | |||
| 5936c1cef4 | |||
| 04f26a29eb | |||
| c9ad429a1b | |||
| 2b42db58da | |||
| 32757b0e43 | |||
| c5598d2361 | |||
| 203cd7cbcf | |||
| fb309134b5 | |||
| 8b162e89de | |||
| 9670b585fb | |||
| 3b76ade662 | |||
| 1b1df2e731 | |||
| 6c19a8b4ec | |||
| 71728247ab | |||
| 4344aa2ff1 | |||
| b7d8c12015 | |||
| 63d16ca854 | |||
| f15a1e96ce | |||
| 55bfcd6d86 | |||
| 880284fa9c | |||
| 143939f19b | |||
| a979d8ae81 | |||
| b5897f784f | |||
| 83cc7a94a4 | |||
| 5edda068f2 | |||
| 6d5cb520ed | |||
| e7ecba4460 | |||
| e0c239729b | |||
| 6570a7accc | |||
| 0c4ec62cac | |||
| 5d11b65d53 | |||
| 8b40117c08 | |||
| b1f2bf3526 | |||
| d8452f71ed | |||
| 2a269bfd4c | |||
| cafe82b462 | |||
| db8ee8159d | |||
| f0765b1a13 | |||
| 93566cb73c | |||
| 6d0d093d62 | |||
| 54fe4ffece | |||
| 124bbb06f9 | |||
| f04a53337d | |||
| 514efab8eb | |||
| 232cd6659b | |||
| 7c93f9c82a | |||
| e5b6e3612a | |||
| 6cb15885c7 | |||
| e6d90c2081 | |||
| 679d868b4a | |||
| 72d240f325 | |||
| 2ca7e037ef | |||
| 5e9c31ac31 | |||
| bcb48774a0 | |||
| 9ff7969acf | |||
| 7fbd06a135 | |||
| bbb906ba10 | |||
| 2de674ec9a | |||
| b700d9ded5 | |||
| 359cb0a79a | |||
| 45f9de6cd2 | |||
| 233c481f7a | |||
| b677f1e902 | |||
| 8d82eedaef | |||
| 4dc27b365f | |||
| e5c3fa4023 | |||
| bdba78c45c | |||
| f95167e907 | |||
| 3908c39ce2 | |||
| 858d3749f4 | |||
| 51f36fa4b4 | |||
| bfe30eec79 |
+1
-1
@@ -2,4 +2,4 @@
|
||||
check-filenames =
|
||||
builtin = clear,rare,usage,informal
|
||||
skip = */.git,*/cmake-build-*,*/.idea,*/completions,*/presets,*/screenshots,*/tests,*/3rdparty,*/logo/ascii,./src/detection/gpu/asahi_drm.h,./src/detection/cpu/cpu_x86.c,./src/detection/gpu/gpu_bsddrm.c,./src/detection/displayserver/linux/wayland/*-protocol.*,./build,./build-*,./build_*,./src/common/impl/genconfig.c
|
||||
ignore-words-list = iterm,compiletime,unknwn,pengwin,siduction,master,slave,sur,doas,conexant,ags,bu,renderD,SME,Carmel,kinda
|
||||
ignore-words-list = iterm,compiletime,unknwn,pengwin,siduction,master,slave,sur,doas,conexant,ags,bu,renderD,SME,Carmel,kinda,symbole,connexion,comando,processus,lokale,adres,titel,som,environnement,Sistem,Lokal,Lokal,Sistem,Paket,Hava
|
||||
|
||||
@@ -0,0 +1,405 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Fastfetch · Benchmark</title>
|
||||
<style>
|
||||
:root {
|
||||
color-scheme: light;
|
||||
--page: #f5f7fb;
|
||||
--card: #fff;
|
||||
--text: #172033;
|
||||
--muted: #64748b;
|
||||
--border: #dbe3ef;
|
||||
--accent: #2563eb;
|
||||
--accent-soft: #dbeafe;
|
||||
--chart-text: #334155;
|
||||
--chart-grid: #cbd5e1;
|
||||
}
|
||||
:root[data-theme="dark"] {
|
||||
color-scheme: dark;
|
||||
--page: #0f172a; --card: #172033; --text: #e5edf8; --muted: #9aabc1; --border: #334155; --accent-soft: #1e3a8a;
|
||||
--chart-text: #f1f5f9;
|
||||
--chart-grid: #475569;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body { max-width: 1440px; margin: 0 auto; padding: 2.5rem 1.25rem 4rem; background: var(--page); color: var(--text); font: 16px/1.5 system-ui, -apple-system, sans-serif; }
|
||||
header { margin-bottom: 1.75rem; }
|
||||
.header-row { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
|
||||
h1 { margin: 0; font-size: clamp(2rem, 4vw, 3rem); letter-spacing: -.04em; }
|
||||
h2 { margin: 0; font-size: 1.25rem; }
|
||||
p { color: var(--muted); }
|
||||
.intro { margin: .6rem 0 1rem; }
|
||||
.links a { color: var(--accent); font-weight: 600; text-decoration: none; }
|
||||
.links a:hover { text-decoration: underline; }
|
||||
.theme-toggle, .fullscreen { border: 1px solid var(--border); border-radius: .55rem; padding: .5rem .8rem; background: var(--card); color: var(--text); cursor: pointer; font: inherit; font-weight: 600; }
|
||||
.theme-toggle:hover, .fullscreen:hover { border-color: var(--accent); color: var(--accent); }
|
||||
.tabs { display: flex; flex-wrap: wrap; gap: .6rem; margin: 1.5rem 0; padding: .35rem; border: 1px solid var(--border); border-radius: .8rem; background: var(--card); }
|
||||
.tab { border: 0; border-radius: .55rem; padding: .65rem 1.15rem; background: transparent; color: var(--muted); cursor: pointer; font: inherit; font-weight: 700; }
|
||||
.tab:hover, .tab[aria-selected="true"] { background: var(--accent); color: #fff; }
|
||||
.tab-panel[hidden] { display: none; }
|
||||
.platform-title { display: flex; align-items: center; gap: .65rem; margin: 1.5rem 0 1rem; }
|
||||
.platform-title::before { content: ''; width: .35rem; height: 1.4rem; border-radius: 1rem; background: var(--accent); }
|
||||
.charts { display: flex; flex-direction: column; gap: 1.25rem; }
|
||||
.chart { min-width: 0; padding: 1.25rem; border: 1px solid var(--border); border-radius: 1rem; background: var(--card); box-shadow: 0 8px 28px #0f172a0c; }
|
||||
.chart:fullscreen { overflow: auto; padding: 2rem; background: var(--page); }
|
||||
.chart:fullscreen .canvas-wrap { height: calc(100vh - 13rem); }
|
||||
.chart-header { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; margin-bottom: .75rem; }
|
||||
.chart-header span { color: var(--muted); font-size: .85rem; }
|
||||
.canvas-wrap { position: relative; height: 360px; }
|
||||
.chart-actions { display: flex; align-items: center; gap: .75rem; }
|
||||
.legend-actions { display: flex; flex-wrap: wrap; gap: .45rem; margin-top: 1rem; }
|
||||
.legend-action { border: 1px solid var(--border); border-radius: .45rem; padding: .35rem .6rem; background: var(--card); color: var(--muted); cursor: pointer; font: inherit; font-size: .8rem; }
|
||||
.legend-action:hover { border-color: var(--accent); color: var(--accent); }
|
||||
.legend { display: flex; flex-wrap: wrap; gap: .35rem .9rem; margin-top: 1rem; }
|
||||
.legend-item { display: inline-flex; align-items: center; gap: .4rem; border: 0; padding: .15rem 0; background: transparent; color: var(--text); cursor: pointer; font: inherit; font-size: .85rem; }
|
||||
.legend-item.is-hidden { color: var(--muted); }
|
||||
.legend-swatch { width: .75rem; height: .75rem; border-radius: 50%; }
|
||||
.legend-item.is-hidden .legend-swatch { opacity: .35; }
|
||||
.chart-tooltip { position: fixed; z-index: 1000; pointer-events: none; width: min(90vw, 900px); max-height: calc(100vh - 24px); overflow: hidden; padding: .7rem .85rem; border: 1px solid var(--border); border-radius: .6rem; background: var(--card); color: var(--text); box-shadow: 0 10px 30px #0f172a33; font-size: .8rem; opacity: 0; transition: opacity .1s ease; }
|
||||
.chart-tooltip-title { margin-bottom: .35rem; font-weight: 700; white-space: normal; overflow-wrap: anywhere; }
|
||||
.chart-tooltip-body { max-height: calc(100vh - 76px); overflow: auto; columns: 220px auto; column-gap: 1.5rem; }
|
||||
.chart-tooltip-row { display: flex; align-items: center; gap: .45rem; break-inside: avoid; white-space: nowrap; }
|
||||
.chart-tooltip-swatch { width: .55rem; height: .55rem; border-radius: 50%; flex: 0 0 auto; }
|
||||
.empty { padding: 2rem 0; color: var(--muted); }
|
||||
</style>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js/dist/chart.umd.min.js"></script>
|
||||
<script src="data.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="header-row">
|
||||
<h1>Fastfetch Benchmark</h1>
|
||||
<button id="theme-toggle" class="theme-toggle" type="button"></button>
|
||||
</div>
|
||||
<p class="intro">Performance history grouped by CPU family. Each chart contains all modules and total process time for one operating system and architecture. Module curves are available from the legend and hidden by default.</p>
|
||||
<p class="links"><a href="data.js">Download raw benchmark data</a></p>
|
||||
</header>
|
||||
<nav id="tabs" class="tabs" aria-label="CPU architecture"></nav>
|
||||
<main id="panels"></main>
|
||||
<script>
|
||||
(() => {
|
||||
const benchmarkData = window.BENCHMARK_DATA || {};
|
||||
const entries = Object.values(benchmarkData.entries || {}).flat();
|
||||
const groups = new Map();
|
||||
|
||||
function architectureFamily(arch) {
|
||||
const normalized = arch.toLowerCase();
|
||||
if (/^(amd64|x86_64|x86|i[3-6]86)$/.test(normalized)) return 'Intel';
|
||||
if (/^(aarch64|arm64|armv[5-8].*|arm)$/.test(normalized)) return 'ARM';
|
||||
return 'Other';
|
||||
}
|
||||
|
||||
function parseName(name) {
|
||||
let match = name.match(/^fastfetch (linux|macos|windows) ([^ ]+) (module .+|total process time)$/i);
|
||||
if (match) {
|
||||
return { platform: match[1].toLowerCase(), arch: match[2], metric: match[3] };
|
||||
}
|
||||
|
||||
// Keep older macOS records visible while new records use the platform field.
|
||||
match = name.match(/^fastfetch ([^ ]+) (module .+|total process time)$/i);
|
||||
return match ? { platform: 'macos', arch: match[1], metric: match[2] } : null;
|
||||
}
|
||||
|
||||
function entryMetadata(entry) {
|
||||
const raw = entry.commit && entry.commit.timestamp || entry.date;
|
||||
const date = new Date(raw);
|
||||
return {
|
||||
date: Number.isNaN(date.valueOf()) ? String(entry.date || '') : date.toISOString(),
|
||||
sha: entry.commit && entry.commit.id ? entry.commit.id : 'unknown'
|
||||
};
|
||||
}
|
||||
|
||||
for (const entry of entries) {
|
||||
const metricsByGroup = new Map();
|
||||
for (const bench of entry.benches || []) {
|
||||
const parsed = parseName(bench.name);
|
||||
if (!parsed) continue;
|
||||
const key = `${parsed.platform} ${parsed.arch}`;
|
||||
if (!metricsByGroup.has(key)) metricsByGroup.set(key, new Map());
|
||||
metricsByGroup.get(key).set(parsed.metric, bench.value);
|
||||
}
|
||||
|
||||
for (const [key, metrics] of metricsByGroup) {
|
||||
if (!groups.has(key)) groups.set(key, []);
|
||||
groups.get(key).push({ ...entryMetadata(entry), metrics });
|
||||
}
|
||||
}
|
||||
|
||||
const colors = [
|
||||
'#2563eb', '#dc2626', '#16a34a', '#9333ea', '#ea580c', '#0891b2',
|
||||
'#db2777', '#65a30d', '#4f46e5', '#ca8a04', '#0f766e', '#be123c'
|
||||
];
|
||||
const tabs = document.getElementById('tabs');
|
||||
const panels = document.getElementById('panels');
|
||||
const root = document.documentElement;
|
||||
const themeToggle = document.getElementById('theme-toggle');
|
||||
const savedTheme = localStorage.getItem('fastfetch-benchmark-theme');
|
||||
let theme = savedTheme || (matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
|
||||
const chartsByInstance = [];
|
||||
|
||||
function chartColors() {
|
||||
const styles = getComputedStyle(root);
|
||||
return {
|
||||
text: styles.getPropertyValue('--chart-text').trim(),
|
||||
grid: styles.getPropertyValue('--chart-grid').trim()
|
||||
};
|
||||
}
|
||||
|
||||
function externalTooltipHandler(context) {
|
||||
const { chart, tooltip } = context;
|
||||
let tooltipElement = chart.canvas.parentNode.querySelector('.chart-tooltip');
|
||||
if (!tooltipElement) {
|
||||
tooltipElement = document.createElement('div');
|
||||
tooltipElement.className = 'chart-tooltip';
|
||||
chart.canvas.parentNode.appendChild(tooltipElement);
|
||||
}
|
||||
|
||||
if (tooltip.opacity === 0) {
|
||||
tooltipElement.style.opacity = '0';
|
||||
return;
|
||||
}
|
||||
|
||||
tooltipElement.replaceChildren();
|
||||
const title = document.createElement('div');
|
||||
title.className = 'chart-tooltip-title';
|
||||
const row = chart._benchmarkRows[tooltip.dataPoints[0].dataIndex];
|
||||
title.textContent = `${new Date(row.date).toLocaleString()} · ${row.sha}`;
|
||||
tooltipElement.appendChild(title);
|
||||
const bodyElement = document.createElement('div');
|
||||
bodyElement.className = 'chart-tooltip-body';
|
||||
for (const body of [...tooltip.dataPoints].sort((a, b) => b.parsed.y - a.parsed.y)) {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'chart-tooltip-row';
|
||||
const swatch = document.createElement('span');
|
||||
swatch.className = 'chart-tooltip-swatch';
|
||||
swatch.style.background = body.dataset.borderColor;
|
||||
const value = document.createElement('span');
|
||||
value.textContent = `${body.dataset.label}: ${body.formattedValue} ms`;
|
||||
item.append(swatch, value);
|
||||
bodyElement.appendChild(item);
|
||||
}
|
||||
tooltipElement.appendChild(bodyElement);
|
||||
|
||||
const canvasRect = chart.canvas.getBoundingClientRect();
|
||||
const margin = 12;
|
||||
let left = canvasRect.left + tooltip.caretX + margin;
|
||||
let top = canvasRect.top + tooltip.caretY + margin;
|
||||
tooltipElement.style.left = `${left}px`;
|
||||
tooltipElement.style.top = `${top}px`;
|
||||
tooltipElement.style.opacity = '0';
|
||||
const tooltipRect = tooltipElement.getBoundingClientRect();
|
||||
if (left + tooltipRect.width > window.innerWidth - margin) left = canvasRect.left + tooltip.caretX - tooltipRect.width - margin;
|
||||
if (top + tooltipRect.height > window.innerHeight - margin) top = canvasRect.top + tooltip.caretY - tooltipRect.height - margin;
|
||||
tooltipElement.style.left = `${Math.max(margin, left)}px`;
|
||||
tooltipElement.style.top = `${Math.max(margin, top)}px`;
|
||||
tooltipElement.style.opacity = '1';
|
||||
}
|
||||
|
||||
function updateTheme() {
|
||||
root.dataset.theme = theme;
|
||||
themeToggle.textContent = theme === 'dark' ? '☀ Light mode' : '☾ Dark mode';
|
||||
themeToggle.setAttribute('aria-label', `Switch to ${theme === 'dark' ? 'light' : 'dark'} mode`);
|
||||
const colors = chartColors();
|
||||
Chart.defaults.color = colors.text;
|
||||
Chart.defaults.borderColor = colors.grid;
|
||||
for (const chart of chartsByInstance) {
|
||||
chart.options.scales.x.title.color = colors.text;
|
||||
chart.options.scales.x.ticks.color = colors.text;
|
||||
chart.options.scales.x.grid.color = colors.grid;
|
||||
chart.options.scales.y.title.color = colors.text;
|
||||
chart.options.scales.y.ticks.color = colors.text;
|
||||
chart.options.scales.y.grid.color = colors.grid;
|
||||
chart.update();
|
||||
}
|
||||
}
|
||||
themeToggle.addEventListener('click', () => {
|
||||
theme = theme === 'dark' ? 'light' : 'dark';
|
||||
localStorage.setItem('fastfetch-benchmark-theme', theme);
|
||||
updateTheme();
|
||||
});
|
||||
updateTheme();
|
||||
const families = new Map();
|
||||
|
||||
for (const [key, rows] of groups) {
|
||||
const arch = key.slice(key.lastIndexOf(' ') + 1);
|
||||
const family = architectureFamily(arch);
|
||||
if (!families.has(family)) families.set(family, []);
|
||||
families.get(family).push({ key, rows });
|
||||
}
|
||||
|
||||
const familyOrder = ['Intel', 'ARM', 'Other'];
|
||||
const availableFamilies = familyOrder.filter(family => families.has(family));
|
||||
|
||||
for (const [index, family] of availableFamilies.entries()) {
|
||||
const tab = document.createElement('button');
|
||||
const panelId = `panel-${family.toLowerCase()}`;
|
||||
tab.className = 'tab';
|
||||
tab.type = 'button';
|
||||
tab.id = `tab-${family.toLowerCase()}`;
|
||||
tab.setAttribute('role', 'tab');
|
||||
tab.textContent = family;
|
||||
tab.setAttribute('aria-controls', panelId);
|
||||
tab.setAttribute('aria-selected', index === 0 ? 'true' : 'false');
|
||||
tabs.appendChild(tab);
|
||||
|
||||
const panel = document.createElement('section');
|
||||
panel.className = 'tab-panel';
|
||||
panel.id = panelId;
|
||||
panel.setAttribute('role', 'tabpanel');
|
||||
panel.hidden = index !== 0;
|
||||
panel.setAttribute('aria-labelledby', tab.id);
|
||||
const title = document.createElement('h2');
|
||||
title.className = 'platform-title';
|
||||
title.textContent = `${family} processors`;
|
||||
panel.appendChild(title);
|
||||
const chartGrid = document.createElement('div');
|
||||
chartGrid.className = 'charts';
|
||||
panel.appendChild(chartGrid);
|
||||
panels.appendChild(panel);
|
||||
|
||||
tab.addEventListener('click', () => {
|
||||
for (const otherTab of tabs.children) otherTab.setAttribute('aria-selected', otherTab === tab ? 'true' : 'false');
|
||||
for (const otherPanel of panels.children) otherPanel.hidden = otherPanel !== panel;
|
||||
});
|
||||
|
||||
for (const { key, rows } of families.get(family).sort((a, b) => a.key.localeCompare(b.key))) {
|
||||
rows.sort((a, b) => a.date.localeCompare(b.date));
|
||||
const metrics = [...new Set(rows.flatMap(row => [...row.metrics.keys()]))].sort((a, b) => {
|
||||
if (a === 'total process time') return -1;
|
||||
if (b === 'total process time') return 1;
|
||||
return a.localeCompare(b);
|
||||
});
|
||||
const section = document.createElement('article');
|
||||
section.className = 'chart';
|
||||
const chartHeader = document.createElement('div');
|
||||
chartHeader.className = 'chart-header';
|
||||
const chartTitle = document.createElement('h2');
|
||||
chartTitle.textContent = key;
|
||||
const chartActions = document.createElement('div');
|
||||
chartActions.className = 'chart-actions';
|
||||
const chartHint = document.createElement('span');
|
||||
chartHint.textContent = `${rows.length} run${rows.length === 1 ? '' : 's'}`;
|
||||
const fullscreen = document.createElement('button');
|
||||
fullscreen.className = 'fullscreen';
|
||||
fullscreen.type = 'button';
|
||||
fullscreen.textContent = '⛶ Fullscreen';
|
||||
fullscreen.addEventListener('click', () => {
|
||||
if (document.fullscreenElement) document.exitFullscreen();
|
||||
else section.requestFullscreen();
|
||||
});
|
||||
chartActions.append(chartHint, fullscreen);
|
||||
chartHeader.append(chartTitle, chartActions);
|
||||
const canvasWrap = document.createElement('div');
|
||||
canvasWrap.className = 'canvas-wrap';
|
||||
const canvas = document.createElement('canvas');
|
||||
canvasWrap.appendChild(canvas);
|
||||
section.append(chartHeader, canvasWrap);
|
||||
chartGrid.appendChild(section);
|
||||
|
||||
const colorsForChart = chartColors();
|
||||
const chart = new Chart(canvas, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: rows.map(row => row.sha.slice(0, 7)),
|
||||
datasets: metrics.map((metric, index) => ({
|
||||
label: metric,
|
||||
data: rows.map(row => row.metrics.has(metric) ? row.metrics.get(metric) : null),
|
||||
borderColor: colors[index % colors.length],
|
||||
backgroundColor: colors[index % colors.length],
|
||||
pointRadius: 3,
|
||||
tension: 0.2,
|
||||
spanGaps: true,
|
||||
hidden: metric !== 'total process time'
|
||||
}))
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
interaction: { mode: 'index', intersect: false },
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
tooltip: {
|
||||
enabled: false,
|
||||
external: externalTooltipHandler,
|
||||
callbacks: {
|
||||
title: tooltipItems => {
|
||||
const row = rows[tooltipItems[0].dataIndex];
|
||||
return `${row.date} · ${row.sha}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: { title: { display: true, text: 'Date', color: colorsForChart.text }, ticks: { color: colorsForChart.text }, grid: { color: colorsForChart.grid } },
|
||||
y: { beginAtZero: true, title: { display: true, text: 'Milliseconds', color: colorsForChart.text }, ticks: { color: colorsForChart.text }, grid: { color: colorsForChart.grid } }
|
||||
}
|
||||
}
|
||||
});
|
||||
chart._benchmarkRows = rows;
|
||||
chartsByInstance.push(chart);
|
||||
const legendActions = document.createElement('div');
|
||||
legendActions.className = 'legend-actions';
|
||||
const legend = document.createElement('div');
|
||||
legend.className = 'legend';
|
||||
const legendItems = [];
|
||||
|
||||
function refreshLegend() {
|
||||
legendItems.forEach((item, metricIndex) => {
|
||||
item.classList.toggle('is-hidden', !chart.isDatasetVisible(metricIndex));
|
||||
});
|
||||
}
|
||||
|
||||
function setAllLegends(visible) {
|
||||
metrics.forEach((metric, metricIndex) => chart.setDatasetVisibility(metricIndex, visible));
|
||||
refreshLegend();
|
||||
chart.update();
|
||||
}
|
||||
|
||||
function addLegendAction(label, action) {
|
||||
const button = document.createElement('button');
|
||||
button.className = 'legend-action';
|
||||
button.type = 'button';
|
||||
button.textContent = label;
|
||||
button.addEventListener('click', action);
|
||||
legendActions.appendChild(button);
|
||||
}
|
||||
|
||||
addLegendAction('Show all', () => setAllLegends(true));
|
||||
addLegendAction('Toggle all', () => {
|
||||
metrics.forEach((metric, metricIndex) => chart.setDatasetVisibility(metricIndex, !chart.isDatasetVisible(metricIndex)));
|
||||
refreshLegend();
|
||||
chart.update();
|
||||
});
|
||||
addLegendAction('Reset', () => {
|
||||
metrics.forEach((metric, metricIndex) => chart.setDatasetVisibility(metricIndex, metric === 'total process time'));
|
||||
refreshLegend();
|
||||
chart.update();
|
||||
});
|
||||
|
||||
metrics.forEach((metric, metricIndex) => {
|
||||
const item = document.createElement('button');
|
||||
item.className = `legend-item${chart.isDatasetVisible(metricIndex) ? '' : ' is-hidden'}`;
|
||||
item.type = 'button';
|
||||
item.innerHTML = `<span class="legend-swatch" style="background:${colors[metricIndex % colors.length]}"></span>${metric}`;
|
||||
item.addEventListener('click', () => {
|
||||
chart.setDatasetVisibility(metricIndex, !chart.isDatasetVisible(metricIndex));
|
||||
item.classList.toggle('is-hidden', !chart.isDatasetVisible(metricIndex));
|
||||
chart.update();
|
||||
});
|
||||
legendItems.push(item);
|
||||
legend.appendChild(item);
|
||||
});
|
||||
section.append(legendActions, legend);
|
||||
}
|
||||
}
|
||||
|
||||
if (!groups.size) {
|
||||
tabs.hidden = true;
|
||||
panels.innerHTML = '<p class="empty">No benchmark data available.</p>';
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,55 @@
|
||||
name: Reusable Benchmark
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
benchmark:
|
||||
runs-on: ubuntu-latest
|
||||
concurrency:
|
||||
group: benchmark
|
||||
cancel-in-progress: false
|
||||
steps:
|
||||
- name: checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: download benchmark data
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
pattern: benchmark-*
|
||||
path: benchmarks
|
||||
|
||||
- name: merge benchmark data
|
||||
run: jq -s 'add' benchmarks/*/benchmark.json > benchmark.json
|
||||
|
||||
- name: save benchmark dashboard
|
||||
run: cp .github/benchmark-index.html "$RUNNER_TEMP/benchmark-index.html"
|
||||
|
||||
- name: store benchmark result
|
||||
uses: benchmark-action/github-action-benchmark@52576c92bccf6ac60c8223ec7eb2565637cae9ba # v1.22.1
|
||||
with:
|
||||
name: Fastfetch
|
||||
tool: customSmallerIsBetter
|
||||
output-file-path: benchmark.json
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
ref: ${{ github.sha }}
|
||||
auto-push: true
|
||||
summary-always: true
|
||||
benchmark-data-dir-path: dev/bench
|
||||
|
||||
- name: publish benchmark dashboard
|
||||
run: |
|
||||
git fetch origin gh-pages
|
||||
git switch --force-create gh-pages FETCH_HEAD
|
||||
mkdir -p dev/bench
|
||||
cp "$RUNNER_TEMP/benchmark-index.html" dev/bench/index.html
|
||||
git config user.name github-action-benchmark
|
||||
git config user.email github@users.noreply.github.com
|
||||
git add dev/bench/index.html
|
||||
if ! git diff --cached --quiet; then
|
||||
git commit -m "Update benchmark dashboard"
|
||||
git push origin gh-pages
|
||||
fi
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Start VM
|
||||
uses: cross-platform-actions/action@5ea7e8e4677bd726033a10b094ba1c5762b15dee # v1.3.0
|
||||
uses: cross-platform-actions/action@24ef01df165c76df1ed2b9f9e9212e78dc2fc963 # v1.4.0
|
||||
with:
|
||||
operating_system: dragonflybsd
|
||||
version: '6.4.2'
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Start VM
|
||||
uses: cross-platform-actions/action@5ea7e8e4677bd726033a10b094ba1c5762b15dee # v1.3.0
|
||||
uses: cross-platform-actions/action@24ef01df165c76df1ed2b9f9e9212e78dc2fc963 # v1.4.0
|
||||
with:
|
||||
operating_system: freebsd
|
||||
version: '15.1'
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Start VM
|
||||
uses: cross-platform-actions/action@5ea7e8e4677bd726033a10b094ba1c5762b15dee # v1.3.0
|
||||
uses: cross-platform-actions/action@24ef01df165c76df1ed2b9f9e9212e78dc2fc963 # v1.4.0
|
||||
with:
|
||||
operating_system: haiku
|
||||
version: 'r1beta5'
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: run VM
|
||||
uses: uraimo/run-on-arch-action@f9b26e3a1a408d5fd530d20c17b9f3f4428ff8d9 # v3.1.0
|
||||
uses: uraimo/run-on-arch-action@460cb8e6d9f726a588fc9b5e681c8a6cab09ae41 # v3.2.0
|
||||
id: runcmd
|
||||
with:
|
||||
arch: armv7
|
||||
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
run: sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
|
||||
|
||||
- name: install required packages
|
||||
run: sudo apt-get update && sudo apt-get install -y gcc-13 libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev libdconf-dev libdbus-1-dev libmagickcore-dev libsqlite3-dev librpm-dev libegl-dev libglx-dev ocl-icd-opencl-dev libpulse-dev libdrm-dev libelf-dev libddcutil-dev libefl-all-dev libunwind-dev liblua5.4-dev libvdpau-dev libva-dev rpm ninja-build
|
||||
run: sudo apt-get update && sudo apt-get install -y gcc-13 jq libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev libdconf-dev libdbus-1-dev libmagickcore-dev libsqlite3-dev librpm-dev libegl-dev libglx-dev ocl-icd-opencl-dev libpulse-dev libdrm-dev libelf-dev libddcutil-dev libefl-all-dev libunwind-dev liblua5.4-dev libvdpau-dev libva-dev rpm ninja-build
|
||||
|
||||
- name: install linuxbrew packages
|
||||
run: |
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
|
||||
- name: Initialize CodeQL
|
||||
if: inputs.arch == 'amd64'
|
||||
uses: github/codeql-action/init@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
|
||||
uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8
|
||||
with:
|
||||
languages: c
|
||||
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
|
||||
- name: perform CodeQL analysis
|
||||
if: inputs.arch == 'amd64'
|
||||
uses: github/codeql-action/analyze@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
|
||||
uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8
|
||||
|
||||
- name: list features
|
||||
run: ./fastfetch --list-features
|
||||
@@ -73,7 +73,29 @@ jobs:
|
||||
run: time ./fastfetch -c presets/ci.jsonc --stat false
|
||||
|
||||
- name: run fastfetch --format json
|
||||
run: time ./fastfetch -c presets/ci.jsonc --format json
|
||||
run: |
|
||||
{ time -p ./fastfetch -c presets/ci.jsonc --format json > fastfetch.raw.json 2> fastfetch.error; } 2> fastfetch.time
|
||||
sed -n '/^[[:space:]]*\[/,$p' fastfetch.raw.json > fastfetch.json
|
||||
cat fastfetch.json
|
||||
cat fastfetch.error >&2
|
||||
cat fastfetch.time >&2
|
||||
|
||||
- name: prepare benchmark data
|
||||
run: |
|
||||
real_seconds=$(awk '$1 == "real" { print $2 }' fastfetch.time)
|
||||
jq -e 'type == "array"' fastfetch.json > /dev/null
|
||||
jq --arg platform "linux" --arg arch "${{ inputs.arch }}" --argjson real_seconds "$real_seconds" '
|
||||
map(select(.stat | type == "number") | {
|
||||
name: ("fastfetch " + $platform + " " + $arch + " module " + .type),
|
||||
unit: "ms",
|
||||
value: .stat
|
||||
}) + [{
|
||||
name: ("fastfetch " + $platform + " " + $arch + " total process time"),
|
||||
unit: "ms",
|
||||
value: ($real_seconds * 1000)
|
||||
}]
|
||||
' fastfetch.json > benchmark.json
|
||||
cat benchmark.json
|
||||
|
||||
- name: run flashfetch
|
||||
run: time ./flashfetch
|
||||
@@ -105,3 +127,9 @@ jobs:
|
||||
with:
|
||||
name: fastfetch-linux-${{ inputs.arch }}
|
||||
path: ./fastfetch-*.*
|
||||
|
||||
- name: upload benchmark data
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: benchmark-linux-${{ inputs.arch }}
|
||||
path: benchmark.json
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: run VM
|
||||
uses: uraimo/run-on-arch-action@f9b26e3a1a408d5fd530d20c17b9f3f4428ff8d9 # v3.1.0
|
||||
uses: uraimo/run-on-arch-action@460cb8e6d9f726a588fc9b5e681c8a6cab09ae41 # v3.2.0
|
||||
id: runcmd
|
||||
with:
|
||||
arch: ${{ inputs.arch }}
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
- name: install required packages
|
||||
run: |
|
||||
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 brew update
|
||||
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 brew install --overwrite vulkan-loader vulkan-headers molten-vk imagemagick chafa lua quickjs-ng
|
||||
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 brew install --overwrite vulkan-loader vulkan-headers molten-vk imagemagick chafa lua quickjs-ng jq
|
||||
|
||||
- name: configure project
|
||||
run: cmake -DSET_TWEAK=Off -DBUILD_TESTS=On .
|
||||
@@ -41,10 +41,32 @@ jobs:
|
||||
run: ./fastfetch --list-features
|
||||
|
||||
- name: run fastfetch
|
||||
run: time ./fastfetch -c presets/ci.jsonc --structure-disabled vulkan --stat false
|
||||
run: time ./fastfetch -c presets/ci.jsonc --structure-disabled vulkan:bluetooth:bluetoothradio --stat false
|
||||
|
||||
- name: run fastfetch --format json
|
||||
run: time ./fastfetch -c presets/ci.jsonc --structure-disabled vulkan --format json
|
||||
run: |
|
||||
{ time -p ./fastfetch -c presets/ci.jsonc --structure-disabled vulkan:bluetooth:bluetoothradio --format json > fastfetch.raw.json 2> fastfetch.error; } 2> fastfetch.time
|
||||
sed -n '/^[[:space:]]*\[/,$p' fastfetch.raw.json > fastfetch.json
|
||||
cat fastfetch.json
|
||||
cat fastfetch.error >&2
|
||||
cat fastfetch.time >&2
|
||||
|
||||
- name: prepare benchmark data
|
||||
run: |
|
||||
real_seconds=$(awk '$1 == "real" { print $2 }' fastfetch.time)
|
||||
jq -e 'type == "array"' fastfetch.json > /dev/null
|
||||
jq --arg platform "macos" --arg arch "${{ inputs.arch }}" --argjson real_seconds "$real_seconds" '
|
||||
map(select(.stat | type == "number") | {
|
||||
name: ("fastfetch " + $platform + " " + $arch + " module " + .type),
|
||||
unit: "ms",
|
||||
value: .stat
|
||||
}) + [{
|
||||
name: ("fastfetch " + $platform + " " + $arch + " total process time"),
|
||||
unit: "ms",
|
||||
value: ($real_seconds * 1000)
|
||||
}]
|
||||
' fastfetch.json > benchmark.json
|
||||
cat benchmark.json
|
||||
|
||||
- name: run flashfetch
|
||||
run: time ./flashfetch
|
||||
@@ -60,3 +82,9 @@ jobs:
|
||||
with:
|
||||
name: fastfetch-macos-${{ inputs.arch }}
|
||||
path: ./fastfetch-*.*
|
||||
|
||||
- name: upload benchmark data
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: benchmark-macos-${{ inputs.arch }}
|
||||
path: benchmark.json
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Start VM
|
||||
uses: cross-platform-actions/action@5ea7e8e4677bd726033a10b094ba1c5762b15dee # v1.3.0
|
||||
uses: cross-platform-actions/action@24ef01df165c76df1ed2b9f9e9212e78dc2fc963 # v1.4.0
|
||||
with:
|
||||
operating_system: netbsd
|
||||
version: '10.1'
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Start VM
|
||||
uses: cross-platform-actions/action@5ea7e8e4677bd726033a10b094ba1c5762b15dee # v1.3.0
|
||||
uses: cross-platform-actions/action@24ef01df165c76df1ed2b9f9e9212e78dc2fc963 # v1.4.0
|
||||
with:
|
||||
operating_system: omnios
|
||||
version: 'r151058'
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Start VM
|
||||
uses: cross-platform-actions/action@5ea7e8e4677bd726033a10b094ba1c5762b15dee # v1.3.0
|
||||
uses: cross-platform-actions/action@24ef01df165c76df1ed2b9f9e9212e78dc2fc963 # v1.4.0
|
||||
with:
|
||||
operating_system: openbsd
|
||||
version: '7.9'
|
||||
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
with:
|
||||
msystem: ${{ inputs.msystem }}
|
||||
update: true
|
||||
install: git mingw-w64-clang-${{ inputs.msys-arch }}-7zip mingw-w64-clang-${{ inputs.msys-arch }}-cmake mingw-w64-clang-${{ inputs.msys-arch }}-clang mingw-w64-clang-${{ inputs.msys-arch }}-vulkan-loader mingw-w64-clang-${{ inputs.msys-arch }}-vulkan-headers mingw-w64-clang-${{ inputs.msys-arch }}-opencl-icd mingw-w64-clang-${{ inputs.msys-arch }}-opencl-headers mingw-w64-clang-${{ inputs.msys-arch }}-cppwinrt mingw-w64-clang-${{ inputs.msys-arch }}-imagemagick mingw-w64-clang-${{ inputs.msys-arch }}-chafa mingw-w64-clang-${{ inputs.msys-arch }}-lua mingw-w64-clang-${{ inputs.msys-arch }}-quickjs-ng mingw-w64-clang-${{ inputs.msys-arch }}-directx-headers
|
||||
install: git mingw-w64-clang-${{ inputs.msys-arch }}-7zip mingw-w64-clang-${{ inputs.msys-arch }}-cmake mingw-w64-clang-${{ inputs.msys-arch }}-clang mingw-w64-clang-${{ inputs.msys-arch }}-jq mingw-w64-clang-${{ inputs.msys-arch }}-vulkan-loader mingw-w64-clang-${{ inputs.msys-arch }}-vulkan-headers mingw-w64-clang-${{ inputs.msys-arch }}-opencl-icd mingw-w64-clang-${{ inputs.msys-arch }}-opencl-headers mingw-w64-clang-${{ inputs.msys-arch }}-cppwinrt mingw-w64-clang-${{ inputs.msys-arch }}-imagemagick mingw-w64-clang-${{ inputs.msys-arch }}-chafa mingw-w64-clang-${{ inputs.msys-arch }}-lua mingw-w64-clang-${{ inputs.msys-arch }}-quickjs-ng mingw-w64-clang-${{ inputs.msys-arch }}-directx-headers
|
||||
|
||||
- name: print msys version
|
||||
run: uname -a
|
||||
@@ -70,7 +70,29 @@ jobs:
|
||||
run: time ./fastfetch -c presets/ci.jsonc --stat false
|
||||
|
||||
- name: run fastfetch --format json
|
||||
run: time ./fastfetch -c presets/ci.jsonc --format json
|
||||
run: |
|
||||
{ time -p ./fastfetch -c presets/ci.jsonc --format json > fastfetch.raw.json 2> fastfetch.error; } 2> fastfetch.time
|
||||
sed -n '/^[[:space:]]*\[/,$p' fastfetch.raw.json > fastfetch.json
|
||||
cat fastfetch.json
|
||||
cat fastfetch.error >&2
|
||||
cat fastfetch.time >&2
|
||||
|
||||
- name: prepare benchmark data
|
||||
run: |
|
||||
real_seconds=$(awk '$1 == "real" { print $2 }' fastfetch.time)
|
||||
jq -e 'type == "array"' fastfetch.json > /dev/null
|
||||
jq --arg platform "windows" --arg arch "${{ inputs.arch }}" --argjson real_seconds "$real_seconds" '
|
||||
map(select(.stat | type == "number") | {
|
||||
name: ("fastfetch " + $platform + " " + $arch + " module " + .type),
|
||||
unit: "ms",
|
||||
value: .stat
|
||||
}) + [{
|
||||
name: ("fastfetch " + $platform + " " + $arch + " total process time"),
|
||||
unit: "ms",
|
||||
value: ($real_seconds * 1000)
|
||||
}]
|
||||
' fastfetch.json > benchmark.json
|
||||
cat benchmark.json
|
||||
|
||||
- name: run flashfetch
|
||||
run: time ./flashfetch
|
||||
@@ -94,7 +116,7 @@ jobs:
|
||||
|
||||
- if: github.event_name == 'push' && github.repository == 'fastfetch-cli/fastfetch'
|
||||
name: submit signing request
|
||||
uses: signpath/github-action-submit-signing-request@b9d91eadd323de506c0c81cf0c7fe7438f3360fd # v2.2
|
||||
uses: signpath/github-action-submit-signing-request@c92b958760219087e01f8d67a1669ed57afe2627 # v2.3
|
||||
with:
|
||||
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
|
||||
organization-id: '${{ vars.SIGNPATH_ORG_ID }}'
|
||||
@@ -116,3 +138,9 @@ jobs:
|
||||
name: fastfetch-windows-${{ inputs.arch }}
|
||||
path: ./fastfetch-windows-${{ inputs.arch }}.*
|
||||
overwrite: true
|
||||
|
||||
- name: upload benchmark data
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: benchmark-windows-${{ inputs.arch }}
|
||||
path: benchmark.json
|
||||
|
||||
@@ -176,6 +176,17 @@ jobs:
|
||||
secrets:
|
||||
SIGNPATH_API_TOKEN: ${{ secrets.SIGNPATH_API_TOKEN }}
|
||||
|
||||
benchmark:
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/dev' && github.repository == 'fastfetch-cli/fastfetch'
|
||||
name: Benchmark
|
||||
needs:
|
||||
- linux-hosts
|
||||
- macos-hosts
|
||||
- windows-hosts
|
||||
permissions:
|
||||
contents: write
|
||||
uses: ./.github/workflows/build-benchmark.yml
|
||||
|
||||
release:
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/master' && github.repository == 'fastfetch-cli/fastfetch'
|
||||
name: Release
|
||||
|
||||
+37
-2
@@ -1,8 +1,43 @@
|
||||
# Unreleased
|
||||
|
||||
Changes:
|
||||
* The DE / WM / LM modules now reports the full name "Desktop Environment" / "Window Manager" / "Login Manager" instead of the abbreviations.
|
||||
|
||||
Features:
|
||||
* Improved Login Manager (LM) module
|
||||
* Added support on macOS, Windows and POSIX systems without systemd (by process list enumeration).
|
||||
* Reports pretty name on Linux.
|
||||
* Added Top module to print processes with the highest CPU, memory or disk I/O usage. (Top)
|
||||
* Added module key localization support. (General)
|
||||
* Added a new option `--key-language <?lang>` (`display.key.language: "<?lang>"` in JSON config). When left empty, it defaults to the system locale.
|
||||
* See `fastfetch -h key-language` for all supported languages.
|
||||
* Improved Wi-Fi module
|
||||
* Added Wifi channel width detection, exposed via `{channel-width}` in custom format (reports 0 when not supported).
|
||||
* Improved Wifi channel frequency accuracy and fortified detection on Windows, macOS.
|
||||
* Added Wifi detection support on SunOS, Haiku.
|
||||
* Added Battery detection support on SunOS. (Battery, SunOS)
|
||||
* Added terminal font size detection support on Termux. (TerminalFont, Android)
|
||||
* Improved GTK theme/icons/font detection on standalone WMs such as Hyprland, sway, niri and i3. (Theme, Linux)
|
||||
* Improved fish completion scripts to print enum descriptions. (Completion)
|
||||
* Enabled Wayland support on Android. (DisplayServer, Android)
|
||||
* Added DE detection support for ASUS Zenfone. (DE, Android)
|
||||
|
||||
Bugfixes:
|
||||
* Fixed I/O rate calculation precision in DiskIO and NetIO, and prevented division by zero. (DiskIO / NetIO)
|
||||
* Fixed the fast path of ash version detection. (Shell)
|
||||
* Fixed memory usage detection support on x86-32 FreeBSD (Memory, FreeBSD)
|
||||
* Note: Although 32-bit systems are still supported, they are deprecated and barely tested. Users are encouraged to upgrade to 64-bit systems.
|
||||
* Some internal cleanups and optimizations.
|
||||
|
||||
Logos:
|
||||
* Added macOS_old
|
||||
* Added Omarchy, Omarchy2
|
||||
|
||||
# 2.67.1
|
||||
|
||||
Bugfixes:
|
||||
* Fixed a `Symbol not found` error when running on macOS 10.15. (Codec, macOS)
|
||||
* Fixed Alacritty font size never being parsed from the TOML config. (TerminalFont)
|
||||
* Fixed a `Symbol not found` error when running on macOS 10.15. (#2499, Codec, macOS)
|
||||
* Fixed Alacritty font size never being parsed from the TOML config. (#2501, TerminalFont)
|
||||
* Fixed bright colors incorrectly being active when the terminal uses a light theme. (#2509, Display)
|
||||
* Fixed Ghostty terminal font detection (TerminalFont)
|
||||
* Now tries to load the config with the `.ghostty` extension. (#2500)
|
||||
|
||||
+35
-13
@@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.12.0) # target_link_libraries with OBJECT libs & project homepage url
|
||||
cmake_minimum_required(VERSION 3.21.0) # C_STANDARD
|
||||
|
||||
project(fastfetch
|
||||
VERSION 2.67.1
|
||||
@@ -78,7 +78,7 @@ endif()
|
||||
include(CMakeDependentOption)
|
||||
|
||||
cmake_dependent_option(ENABLE_VULKAN "Enable vulkan" ON "LINUX OR APPLE OR FreeBSD OR OpenBSD OR NetBSD OR WIN32 OR ANDROID OR SunOS OR Haiku OR GNU" OFF)
|
||||
cmake_dependent_option(ENABLE_WAYLAND "Enable wayland-client" ON "LINUX OR FreeBSD OR OpenBSD OR NetBSD" OFF)
|
||||
cmake_dependent_option(ENABLE_WAYLAND "Enable wayland-client" ON "ANDROID OR LINUX OR FreeBSD OR OpenBSD OR NetBSD" OFF)
|
||||
cmake_dependent_option(ENABLE_XCB_RANDR "Enable xcb-randr" ON "LINUX OR FreeBSD OR OpenBSD OR NetBSD OR ANDROID OR SunOS OR GNU" OFF)
|
||||
cmake_dependent_option(ENABLE_XRANDR "Enable xrandr" ON "LINUX OR FreeBSD OR OpenBSD OR NetBSD OR ANDROID OR SunOS OR GNU" OFF)
|
||||
cmake_dependent_option(ENABLE_DRM "Enable libdrm" ON "LINUX OR FreeBSD OR OpenBSD OR NetBSD OR SunOS OR GNU" OFF)
|
||||
@@ -484,6 +484,7 @@ set(LIBFASTFETCH_SRC
|
||||
src/detection/physicalmemory/physicalmemory.c
|
||||
src/detection/publicip/publicip.c
|
||||
src/detection/terminaltheme/terminaltheme.c
|
||||
src/detection/top/top.c
|
||||
src/detection/terminalfont/terminalfont.c
|
||||
src/detection/terminalshell/terminalshell.c
|
||||
src/detection/version/version.c
|
||||
@@ -559,6 +560,7 @@ set(LIBFASTFETCH_SRC
|
||||
src/modules/terminalsize/terminalsize.c
|
||||
src/modules/theme/theme.c
|
||||
src/modules/title/title.c
|
||||
src/modules/top/top.c
|
||||
src/modules/tpm/tpm.c
|
||||
src/modules/uptime/uptime.c
|
||||
src/modules/users/users.c
|
||||
@@ -651,6 +653,7 @@ if(LINUX)
|
||||
src/detection/terminalshell/terminalshell_linux.c
|
||||
src/detection/terminalsize/terminalsize_linux.c
|
||||
src/detection/theme/theme_linux.c
|
||||
src/detection/top/top_linux.c
|
||||
src/detection/tpm/tpm_linux.c
|
||||
src/detection/uptime/uptime_linux.c
|
||||
src/detection/users/users_linux.c
|
||||
@@ -691,6 +694,13 @@ elseif(ANDROID)
|
||||
src/detection/physicalmemory/physicalmemory_nosupport.c
|
||||
src/detection/diskio/diskio_linux.c
|
||||
src/detection/displayserver/displayserver_android.c
|
||||
src/detection/displayserver/linux/wayland/wayland.c
|
||||
src/detection/displayserver/linux/wayland/global-output.c
|
||||
src/detection/displayserver/linux/wayland/kde-output.c
|
||||
src/detection/displayserver/linux/wayland/wl-output-protocol.c
|
||||
src/detection/displayserver/linux/wayland/wp-color-management-v1-protocol.c
|
||||
src/detection/displayserver/linux/wayland/kde-output-device-v2-protocol.c
|
||||
src/detection/displayserver/linux/wayland/xdg-output-unstable-v1-protocol.c
|
||||
src/detection/displayserver/linux/common.c
|
||||
src/detection/displayserver/linux/wmde.c
|
||||
src/detection/displayserver/linux/xcb.c
|
||||
@@ -726,6 +736,7 @@ elseif(ANDROID)
|
||||
src/detection/terminalshell/terminalshell_linux.c
|
||||
src/detection/terminalsize/terminalsize_linux.c
|
||||
src/detection/theme/theme_linux.c
|
||||
src/detection/top/top_linux.c
|
||||
src/detection/tpm/tpm_nosupport.c
|
||||
src/detection/uptime/uptime_linux.c
|
||||
src/detection/users/users_linux.c
|
||||
@@ -802,7 +813,6 @@ elseif(FreeBSD)
|
||||
src/detection/os/os_linux.c
|
||||
src/detection/packages/packages_bsd.c
|
||||
src/detection/poweradapter/poweradapter_nosupport.c
|
||||
src/detection/processes/processes_bsd.c
|
||||
src/detection/gtk_qt/qt.c
|
||||
src/detection/sound/sound_bsd.c
|
||||
src/detection/swap/swap_bsd.c
|
||||
@@ -822,11 +832,15 @@ elseif(FreeBSD)
|
||||
if(DragonFly)
|
||||
list(APPEND LIBFASTFETCH_SRC
|
||||
src/detection/bluetooth/bluetooth_nosupport.c
|
||||
src/detection/processes/processes_dbsd.c
|
||||
src/detection/top/top_dbsd.c
|
||||
src/detection/wifi/wifi_nosupport.c
|
||||
)
|
||||
else()
|
||||
list(APPEND LIBFASTFETCH_SRC
|
||||
src/detection/bluetooth/bluetooth_bsd.c
|
||||
src/detection/processes/processes_bsd.c
|
||||
src/detection/top/top_bsd.c
|
||||
src/detection/wifi/wifi_bsd.c
|
||||
)
|
||||
endif()
|
||||
@@ -904,6 +918,7 @@ elseif(NetBSD)
|
||||
src/detection/terminalsize/terminalsize_linux.c
|
||||
src/detection/theme/theme_linux.c
|
||||
src/detection/tpm/tpm_nosupport.c
|
||||
src/detection/top/top_nbsd.c
|
||||
src/detection/uptime/uptime_bsd.c
|
||||
src/detection/users/users_linux.c
|
||||
src/detection/wallpaper/wallpaper_linux.c
|
||||
@@ -964,7 +979,7 @@ elseif(OpenBSD)
|
||||
src/detection/gpu/gpu_bsddrm.c
|
||||
src/detection/gtk_qt/gtk.c
|
||||
src/detection/host/host_obsd.c
|
||||
src/detection/lm/lm_nosupport.c
|
||||
src/detection/lm/lm_linux.c
|
||||
src/detection/icons/icons_linux.c
|
||||
src/detection/initsystem/initsystem_linux.c
|
||||
src/detection/keyboard/keyboard_nosupport.c
|
||||
@@ -990,6 +1005,7 @@ elseif(OpenBSD)
|
||||
src/detection/terminalsize/terminalsize_linux.c
|
||||
src/detection/theme/theme_linux.c
|
||||
src/detection/tpm/tpm_nosupport.c
|
||||
src/detection/top/top_obsd.c
|
||||
src/detection/uptime/uptime_bsd.c
|
||||
src/detection/users/users_obsd.c
|
||||
src/detection/wallpaper/wallpaper_linux.c
|
||||
@@ -1035,7 +1051,7 @@ elseif(APPLE)
|
||||
src/detection/icons/icons_nosupport.c
|
||||
src/detection/initsystem/initsystem_linux.c
|
||||
src/detection/keyboard/keyboard_apple.c
|
||||
src/detection/lm/lm_nosupport.c
|
||||
src/detection/lm/lm_apple.m
|
||||
src/detection/loadavg/loadavg_bsd.c
|
||||
src/detection/libc/libc_apple.c
|
||||
src/detection/locale/locale_linux.c
|
||||
@@ -1050,13 +1066,14 @@ elseif(APPLE)
|
||||
src/detection/packages/packages_apple.c
|
||||
src/detection/packages/packages_nix.c
|
||||
src/detection/poweradapter/poweradapter_apple.c
|
||||
src/detection/processes/processes_bsd.c
|
||||
src/detection/processes/processes_apple.c
|
||||
src/detection/sound/sound_apple.c
|
||||
src/detection/swap/swap_apple.c
|
||||
src/detection/terminalfont/terminalfont_apple.m
|
||||
src/detection/terminalshell/terminalshell_linux.c
|
||||
src/detection/terminalsize/terminalsize_linux.c
|
||||
src/detection/theme/theme_apple.c
|
||||
src/detection/top/top_apple.c
|
||||
src/detection/tpm/tpm_apple.c
|
||||
src/detection/uptime/uptime_bsd.c
|
||||
src/detection/users/users_linux.c
|
||||
@@ -1128,7 +1145,7 @@ elseif(WIN32)
|
||||
src/detection/initsystem/initsystem_windows.c
|
||||
src/detection/keyboard/keyboard_windows.c
|
||||
src/detection/libc/libc_windows.cpp
|
||||
src/detection/lm/lm_nosupport.c
|
||||
src/detection/lm/lm_windows.c
|
||||
src/detection/loadavg/loadavg_nosupport.c
|
||||
src/detection/locale/locale_windows.c
|
||||
src/detection/localip/localip_windows.c
|
||||
@@ -1149,6 +1166,7 @@ elseif(WIN32)
|
||||
src/detection/terminalshell/terminalshell_windows.c
|
||||
src/detection/terminalsize/terminalsize_windows.c
|
||||
src/detection/theme/theme_windows.c
|
||||
src/detection/top/top_windows.c
|
||||
src/detection/tpm/tpm_windows.c
|
||||
src/detection/uptime/uptime_windows.c
|
||||
src/detection/users/users_windows.c
|
||||
@@ -1169,7 +1187,7 @@ elseif(SunOS)
|
||||
src/common/impl/FFPlatform_unix.c
|
||||
src/common/impl/binary_linux.c
|
||||
src/common/impl/kmod_sunos.c
|
||||
src/detection/battery/battery_nosupport.c
|
||||
src/detection/battery/battery_sunos.c
|
||||
src/detection/bios/bios_windows.c
|
||||
src/detection/board/board_windows.c
|
||||
src/detection/bootmgr/bootmgr_nosupport.c
|
||||
@@ -1210,7 +1228,7 @@ elseif(SunOS)
|
||||
src/detection/initsystem/initsystem_linux.c
|
||||
src/detection/keyboard/keyboard_nosupport.c
|
||||
src/detection/libc/libc_nosupport.c
|
||||
src/detection/lm/lm_nosupport.c
|
||||
src/detection/lm/lm_linux.c
|
||||
src/detection/loadavg/loadavg_sunos.c
|
||||
src/detection/locale/locale_linux.c
|
||||
src/detection/localip/localip_linux.c
|
||||
@@ -1223,7 +1241,7 @@ elseif(SunOS)
|
||||
src/detection/os/os_sunos.c
|
||||
src/detection/packages/packages_sunos.c
|
||||
src/detection/poweradapter/poweradapter_nosupport.c
|
||||
src/detection/processes/processes_linux.c
|
||||
src/detection/processes/processes_sunos.c
|
||||
src/detection/gtk_qt/qt.c
|
||||
src/detection/sound/sound_sunos.c
|
||||
src/detection/swap/swap_sunos.c
|
||||
@@ -1231,11 +1249,12 @@ elseif(SunOS)
|
||||
src/detection/terminalshell/terminalshell_linux.c
|
||||
src/detection/terminalsize/terminalsize_linux.c
|
||||
src/detection/theme/theme_linux.c
|
||||
src/detection/top/top_sunos.c
|
||||
src/detection/tpm/tpm_nosupport.c
|
||||
src/detection/uptime/uptime_sunos.c
|
||||
src/detection/users/users_linux.c
|
||||
src/detection/wallpaper/wallpaper_linux.c
|
||||
src/detection/wifi/wifi_nosupport.c
|
||||
src/detection/wifi/wifi_sunos.c
|
||||
src/detection/wm/wm_nosupport.c
|
||||
src/detection/de/de_linux.c
|
||||
src/detection/wmtheme/wmtheme_linux.c
|
||||
@@ -1301,11 +1320,12 @@ elseif(Haiku)
|
||||
src/detection/terminalshell/terminalshell_linux.c
|
||||
src/detection/terminalsize/terminalsize_linux.c
|
||||
src/detection/theme/theme_nosupport.c
|
||||
src/detection/top/top_haiku.c
|
||||
src/detection/tpm/tpm_nosupport.c
|
||||
src/detection/uptime/uptime_haiku.c
|
||||
src/detection/users/users_linux.c
|
||||
src/detection/wallpaper/wallpaper_haiku.cpp
|
||||
src/detection/wifi/wifi_nosupport.c
|
||||
src/detection/wifi/wifi_haiku.cpp
|
||||
src/detection/wm/wm_nosupport.c
|
||||
src/detection/de/de_nosupport.c
|
||||
src/detection/wmtheme/wmtheme_haiku.cpp
|
||||
@@ -1377,7 +1397,7 @@ elseif(GNU)
|
||||
src/detection/packages/packages_linux.c
|
||||
src/detection/packages/packages_nix.c
|
||||
src/detection/poweradapter/poweradapter_nosupport.c
|
||||
src/detection/processes/processes_linux.c
|
||||
src/detection/processes/processes_gnu.c
|
||||
src/detection/gtk_qt/qt.c
|
||||
src/detection/sound/sound_linux.c
|
||||
src/detection/swap/swap_linux.c
|
||||
@@ -1385,6 +1405,7 @@ elseif(GNU)
|
||||
src/detection/terminalshell/terminalshell_linux.c
|
||||
src/detection/terminalsize/terminalsize_linux.c
|
||||
src/detection/theme/theme_linux.c
|
||||
src/detection/top/top_gnu.c
|
||||
src/detection/tpm/tpm_nosupport.c
|
||||
src/detection/uptime/uptime_linux.c
|
||||
src/detection/users/users_linux.c
|
||||
@@ -1995,6 +2016,7 @@ elseif(SunOS)
|
||||
elseif(GNU)
|
||||
target_link_libraries(libfastfetch
|
||||
PRIVATE "m"
|
||||
PRIVATE "ps"
|
||||
)
|
||||
elseif(ANDROID)
|
||||
target_link_libraries(libfastfetch
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# Fastfetch
|
||||
|
||||
[](https://fastfetch-cli.github.io/fastfetch/dev/bench)
|
||||
[](https://github.com/fastfetch-cli/fastfetch/actions)
|
||||
[](https://github.com/fastfetch-cli/fastfetch/blob/dev/LICENSE)
|
||||
[](https://github.com/fastfetch-cli/fastfetch/graphs/contributors)
|
||||
@@ -99,7 +100,7 @@ See the Wiki: https://github.com/fastfetch-cli/fastfetch/wiki/Building
|
||||
* View all data that fastfetch detects: `fastfetch -s <module1>[:<module2>][:<module3>] --format json`
|
||||
* Display help messages: `fastfetch --help`
|
||||
* Generate a minimal config file: `fastfetch [-s <module1>[:<module2>]] --gen-config [</path/to/config.jsonc>]`
|
||||
* Use: `--gen-config-full` to generate a full config file with all optional options
|
||||
* Use [The online configuration generator](https://fastfetch-cli.github.io/fastfetch-config/) to generate a full config file with all optional options
|
||||
|
||||
## Customization
|
||||
|
||||
|
||||
+56
-24
@@ -63,46 +63,78 @@ echo '
|
||||
import json, subprocess, sys
|
||||
|
||||
|
||||
def escape_fish_dq(s: str) -> str:
|
||||
"""Escape special characters for Fish double quotes to prevent unexpected expansions. One slash in output needs four slashes in script"""
|
||||
return str(s).replace("\\\\", "\\\\\\\\").replace("\\"", "\\\\\\"").replace("$", "\\\\$")
|
||||
|
||||
|
||||
def main():
|
||||
data: dict[str, list[dict]] = json.loads(subprocess.check_output(["fastfetch", "--help-raw"]))
|
||||
|
||||
enum_functions = []
|
||||
complete_commands = []
|
||||
|
||||
for key in data:
|
||||
for flag in data[key]:
|
||||
if flag["long"] == "logo-color-[1-9]":
|
||||
long_name = flag.get("long", "")
|
||||
|
||||
# Handle pseudo flag logo-color-[1-9]
|
||||
if long_name == "logo-color-[1-9]":
|
||||
safe_desc_global = escape_fish_dq(flag.get("desc", ""))
|
||||
for i in range(1, 10):
|
||||
print(f"""complete -c fastfetch -d "{flag["desc"]}" -l "logo-color-{i}" -x -a "(__fastfetch_complete_color)" """)
|
||||
complete_commands.append(f"complete -c fastfetch -d \"{safe_desc_global}\" -l \"logo-color-{i}\" -x -a \"(__fastfetch_complete_color)\"")
|
||||
continue
|
||||
|
||||
if flag.get("pseudo", False):
|
||||
continue
|
||||
|
||||
command_prefix = f"""complete -c fastfetch -d "{flag["desc"]}" -l "{flag["long"]}\""""
|
||||
safe_desc_global = escape_fish_dq(flag.get("desc", ""))
|
||||
command_prefix = f"complete -c fastfetch -d \"{safe_desc_global}\" -l \"{long_name}\""
|
||||
|
||||
if "short" in flag:
|
||||
command_prefix += f""" -o {flag["short"]}"""
|
||||
short_name = flag["short"]
|
||||
command_prefix += f" -o {short_name}"
|
||||
|
||||
if "arg" in flag:
|
||||
type: str = flag["arg"]["type"]
|
||||
if type == "bool":
|
||||
print(f"{command_prefix} -x -a \"(__fastfetch_complete_bool)\"")
|
||||
elif type == "color":
|
||||
print(f"{command_prefix} -x -a \"(__fastfetch_complete_color)\"")
|
||||
elif type == "command":
|
||||
print(f"{command_prefix} -x -a \"(__fastfetch_complete_command)\"")
|
||||
elif type == "config":
|
||||
print(f"{command_prefix} -x -a \"(__fastfetch_complete_config)\"")
|
||||
elif type == "enum":
|
||||
temp: str = " ".join(flag["arg"]["enum"])
|
||||
print(f"{command_prefix} -x -a \"{temp}\"")
|
||||
elif type == "logo":
|
||||
print(f"{command_prefix} -x -a \"(__fastfetch_complete_logo)\"")
|
||||
elif type == "structure":
|
||||
print(f"{command_prefix} -x -a \"(__fish_complete_list : __fastfetch_complete_structure)\"")
|
||||
elif type == "path":
|
||||
print(f"{command_prefix} -r -F")
|
||||
arg_type: str = flag["arg"]["type"]
|
||||
if arg_type == "bool":
|
||||
complete_commands.append(f"{command_prefix} -x -a \"(__fastfetch_complete_bool)\"")
|
||||
elif arg_type == "color":
|
||||
complete_commands.append(f"{command_prefix} -x -a \"(__fastfetch_complete_color)\"")
|
||||
elif arg_type == "command":
|
||||
complete_commands.append(f"{command_prefix} -x -a \"(__fastfetch_complete_command)\"")
|
||||
elif arg_type == "config":
|
||||
complete_commands.append(f"{command_prefix} -x -a \"(__fastfetch_complete_config)\"")
|
||||
elif arg_type == "enum":
|
||||
# Sanitize flag name to create a valid Fish function name
|
||||
func_name = long_name.replace("-", "_").replace("[", "_").replace("]", "_")
|
||||
func_name = f"__fastfetch_complete_enum_{func_name}"
|
||||
|
||||
# Generate the enum function
|
||||
func_lines = [f"function {func_name}"]
|
||||
for enum_val, enum_desc in flag["arg"]["enum"].items():
|
||||
safe_val = escape_fish_dq(enum_val)
|
||||
safe_desc = escape_fish_dq(enum_desc)
|
||||
func_lines.append(f" echo -e \"{safe_val}\\t{safe_desc}\"")
|
||||
func_lines.append("end\n")
|
||||
enum_functions.append("\n".join(func_lines))
|
||||
|
||||
# Append complete command pointing to the new function
|
||||
complete_commands.append(f"{command_prefix} -x -a \"({func_name})\"")
|
||||
elif arg_type == "logo":
|
||||
complete_commands.append(f"{command_prefix} -x -a \"(__fastfetch_complete_logo)\"")
|
||||
elif arg_type == "structure":
|
||||
complete_commands.append(f"{command_prefix} -x -a \"(__fish_complete_list : __fastfetch_complete_structure)\"")
|
||||
elif arg_type == "path":
|
||||
complete_commands.append(f"{command_prefix} -r -F")
|
||||
else:
|
||||
print(f"{command_prefix} -x")
|
||||
complete_commands.append(f"{command_prefix} -x")
|
||||
else:
|
||||
print(f"{command_prefix} -f")
|
||||
complete_commands.append(f"{command_prefix} -f")
|
||||
|
||||
# Print generated enum functions first, then complete rules
|
||||
print("\n".join(enum_functions))
|
||||
print("\n".join(complete_commands))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -585,6 +585,38 @@
|
||||
"default": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"long": "key-language",
|
||||
"desc": "Specify the language used for module keys",
|
||||
"remark": "Leave empty for auto-detection based on the system locale. Unsupported locales fall back to English",
|
||||
"arg": {
|
||||
"type": "enum",
|
||||
"default": "en",
|
||||
"optional": true,
|
||||
"enum": {
|
||||
"en": "English",
|
||||
"ar": "Arabic",
|
||||
"cs": "Czech",
|
||||
"de": "German",
|
||||
"es": "Spanish",
|
||||
"fr": "French",
|
||||
"gl": "Galician",
|
||||
"he": "Hebrew",
|
||||
"id": "Indonesian",
|
||||
"it": "Italian",
|
||||
"ja": "Japanese",
|
||||
"ko": "Korean",
|
||||
"pl": "Polish",
|
||||
"pt": "Portuguese",
|
||||
"ru": "Russian",
|
||||
"tr": "Turkish",
|
||||
"uk": "Ukrainian",
|
||||
"vi": "Vietnamese",
|
||||
"zh_CN": "Simplified Chinese",
|
||||
"zh_TW": "Traditional Chinese"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"long": "bright-color",
|
||||
"desc": "Specify whether keys, title, and ASCII logo should be printed in bright color",
|
||||
|
||||
+220
-7
@@ -322,7 +322,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"cpuFormat": {
|
||||
"description": "Output format for the `CPU` module. See Wiki for formatting syntax\n 1. {name}: Name\n 2. {vendor}: Vendor\n 3. {cores-physical}: Physical core count\n 4. {cores-logical}: Logical core count\n 5. {cores-online}: Online core count\n 6. {freq-base}: Base frequency (formatted)\n 7. {freq-max}: Max frequency (formatted)\n 8. {temperature}: Temperature (formatted)\n 9. {core-types}: Logical core count grouped by frequency\n 10. {packages}: Processor package count\n 11. {march}: CPU microarchitecture\n 12. {numa-nodes}: NUMA node count\n 13. {code-name}: CPU code name\n 14. {technology}: CPU technology",
|
||||
"description": "Output format for the `CPU` module. See Wiki for formatting syntax\n 1. {name}: Name\n 2. {vendor}: Vendor\n 3. {cores-physical}: Physical core count\n 4. {cores-logical}: Logical core count\n 5. {cores-online}: Online core count\n 6. {freq-base}: Base frequency (formatted)\n 7. {freq-max}: Max frequency (formatted)\n 8. {temperature}: Temperature (formatted)\n 9. {core-types}: Logical core count grouped by frequency\n 10. {packages}: Processor package count\n 11. {march}: CPU microarchitecture\n 12. {numa-nodes}: NUMA node count",
|
||||
"type": "string"
|
||||
},
|
||||
"cpucacheFormat": {
|
||||
@@ -505,6 +505,10 @@
|
||||
"description": "Output format for the `Title` module. See Wiki for formatting syntax\n 1. {user-name}: User name\n 2. {host-name}: Host name\n 3. {home-dir}: Home directory\n 4. {exe-path}: Executable path of current process\n 5. {user-shell}: User's default shell\n 6. {user-name-colored}: User name (colored)\n 7. {at-symbol-colored}: @ symbol (colored)\n 8. {host-name-colored}: Host name (colored)\n 9. {full-user-name}: Full user name\n 10. {user-id}: UID (*nix) / SID (Windows)\n 11. {pid}: PID of current process\n 12. {cwd}: CWD with home dir replaced by `~`",
|
||||
"type": "string"
|
||||
},
|
||||
"topFormat": {
|
||||
"description": "Output format for the `Top` module. See Wiki for formatting syntax\n 1. {name}: Process name\n 2. {path}: Executable path\n 3. {pid}: Process ID\n 4. {cpu}: CPU usage\n 5. {mem}: Memory usage (RSS) in bytes\n 6. {disk-read}: Disk read bytes per second\n 7. {disk-write}: Disk write bytes per second\n 8. {cpu-percentage}: CPU usage percentage\n 9. {mem-formatted}: Memory usage (RSS) formatted\n 10. {disk-read-formatted}: Disk read formatted\n 11. {disk-write-formatted}: Disk write formatted",
|
||||
"type": "string"
|
||||
},
|
||||
"themeFormat": {
|
||||
"description": "Output format for the `Theme` module. See Wiki for formatting syntax\n 1. {theme1}: Theme part 1\n 2. {theme2}: Theme part 2",
|
||||
"type": "string"
|
||||
@@ -542,7 +546,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"wifiFormat": {
|
||||
"description": "Output format for the `Wifi` module. See Wiki for formatting syntax\n 1. {inf-desc}: Interface description\n 2. {inf-status}: Interface status\n 3. {status}: Connection status\n 4. {ssid}: Connection SSID\n 5. {bssid}: Connection BSSID\n 6. {protocol}: Connection protocol\n 7. {signal-quality}: Connection signal quality (percentage num)\n 8. {rx-rate}: Connection RX rate\n 9. {tx-rate}: Connection TX rate\n 10. {security}: Connection Security algorithm\n 11. {signal-quality-bar}: Connection signal quality (percentage bar)\n 12. {channel}: Connection channel number\n 13. {band}: Connection channel band in GHz",
|
||||
"description": "Output format for the `Wifi` module. See Wiki for formatting syntax\n 1. {inf-desc}: Interface description\n 2. {inf-status}: Interface status\n 3. {status}: Connection status\n 4. {ssid}: Connection SSID\n 5. {bssid}: Connection BSSID\n 6. {protocol}: Connection protocol\n 7. {signal-quality}: Connection signal quality (percentage num)\n 8. {rx-rate}: Connection RX rate\n 9. {tx-rate}: Connection TX rate\n 10. {security}: Connection Security algorithm\n 11. {signal-quality-bar}: Connection signal quality (percentage bar)\n 12. {channel}: Connection channel number\n 13. {channel-width}: Connection channel width in MHz\n 14. {band}: Connection channel band in GHz",
|
||||
"type": "string"
|
||||
},
|
||||
"wmthemeFormat": {
|
||||
@@ -1057,6 +1061,116 @@
|
||||
"description": "Left padding for keys",
|
||||
"minimum": 0,
|
||||
"default": 0
|
||||
},
|
||||
"language": {
|
||||
"description": "Language used for module keys. `null` for auto detection based on system locale",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "null",
|
||||
"description": "Auto detection based on system locale"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"const": "en",
|
||||
"description": "English"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"const": "ar",
|
||||
"description": "Arabic"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"const": "cs",
|
||||
"description": "Czech"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"const": "de",
|
||||
"description": "German"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"const": "es",
|
||||
"description": "Spanish"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"const": "fr",
|
||||
"description": "French"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"const": "gl",
|
||||
"description": "Galician"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"const": "he",
|
||||
"description": "Hebrew"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"const": "id",
|
||||
"description": "Indonesian"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"const": "it",
|
||||
"description": "Italian"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"const": "ja",
|
||||
"description": "Japanese"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"const": "ko",
|
||||
"description": "Korean"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"const": "pl",
|
||||
"description": "Polish"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"const": "pt",
|
||||
"description": "Portuguese"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"const": "ru",
|
||||
"description": "Russian"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"const": "tr",
|
||||
"description": "Turkish"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"const": "uk",
|
||||
"description": "Ukrainian"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"const": "vi",
|
||||
"description": "Vietnamese"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"const": "zh_CN",
|
||||
"description": "Simplified Chinese"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"const": "zh_TW",
|
||||
"description": "Traditional Chinese"
|
||||
}
|
||||
],
|
||||
"default": "en"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1482,6 +1596,7 @@
|
||||
"poweradapter",
|
||||
"processes",
|
||||
"publicip",
|
||||
"top",
|
||||
"separator",
|
||||
"shell",
|
||||
"sound",
|
||||
@@ -2585,8 +2700,8 @@
|
||||
},
|
||||
"waitTime": {
|
||||
"type": "integer",
|
||||
"description": "Wait time (in ms). Disk I/O = (totalBytesEnd - totalBytesStart) / waitTime",
|
||||
"default": 200,
|
||||
"description": "Wait time (in ms) used to measure the I/O rate (calculated in bytes/sec).\nAvoid setting this too low, as the kernel requires time to accurately update counters.\nIgnored if 'detectTotal' is true.",
|
||||
"default": 500,
|
||||
"minimum": 1
|
||||
},
|
||||
"key": {
|
||||
@@ -3486,8 +3601,8 @@
|
||||
},
|
||||
"waitTime": {
|
||||
"type": "integer",
|
||||
"description": "Wait time (in ms). Net I/O = (totalBytesEnd - totalBytesStart) / waitTime",
|
||||
"default": 200,
|
||||
"description": "Wait time (in ms) used to measure the I/O rate (calculated in bytes/sec).\nAvoid setting this too low, as the kernel requires time to accurately update counters.\nIgnored if 'detectTotal' is true.",
|
||||
"default": 500,
|
||||
"minimum": 1
|
||||
},
|
||||
"key": {
|
||||
@@ -3869,7 +3984,12 @@
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "processes",
|
||||
"description": "Print the number of running processes"
|
||||
"description": "Print the number of running processes and threads"
|
||||
},
|
||||
"countKprocs": {
|
||||
"description": "Whether to take kernel processes and threads into account",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"key": {
|
||||
"$ref": "#/$defs/key"
|
||||
@@ -4314,6 +4434,99 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Top",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "top",
|
||||
"description": "Print processes with the highest CPU or memory usage"
|
||||
},
|
||||
"sort": {
|
||||
"type": "string",
|
||||
"description": "Sort processes by CPU, memory or disk IO usage",
|
||||
"enum": [
|
||||
"cpu",
|
||||
"memory",
|
||||
"disk-read",
|
||||
"disk-write"
|
||||
],
|
||||
"default": "cpu"
|
||||
},
|
||||
"showTypes": {
|
||||
"description": "Specify which resource usages are detected and displayed.\nIf memory is enabled is enabled alone, only a single process snapshot is taken and `waitTime` sampling wait is skipped",
|
||||
"default": [
|
||||
"cpu",
|
||||
"memory",
|
||||
"disk"
|
||||
],
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"cpu",
|
||||
"memory",
|
||||
"disk"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"cpu",
|
||||
"memory",
|
||||
"disk"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"processes": {
|
||||
"type": "integer",
|
||||
"description": "Number of top processes to print",
|
||||
"minimum": 1,
|
||||
"default": 5
|
||||
},
|
||||
"waitTime": {
|
||||
"type": "integer",
|
||||
"description": "Wait time between process samples, in milliseconds. Ignored when showTypes contains \"memory\" only",
|
||||
"minimum": 1,
|
||||
"default": 500
|
||||
},
|
||||
"compact": {
|
||||
"type": "boolean",
|
||||
"description": "Print all process on a single line",
|
||||
"default": false
|
||||
},
|
||||
"percent": {
|
||||
"$ref": "#/$defs/percent"
|
||||
},
|
||||
"key": {
|
||||
"$ref": "#/$defs/key"
|
||||
},
|
||||
"keyColor": {
|
||||
"$ref": "#/$defs/keyColor"
|
||||
},
|
||||
"keyIcon": {
|
||||
"$ref": "#/$defs/keyIcon"
|
||||
},
|
||||
"keyWidth": {
|
||||
"$ref": "#/$defs/keyWidth"
|
||||
},
|
||||
"outputColor": {
|
||||
"$ref": "#/$defs/outputColor"
|
||||
},
|
||||
"format": {
|
||||
"$ref": "#/$defs/topFormat"
|
||||
},
|
||||
"condition": {
|
||||
"$ref": "#/$defs/conditions"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "TPM",
|
||||
"type": "object",
|
||||
|
||||
+5
-4
@@ -99,16 +99,17 @@
|
||||
"gamepad",
|
||||
"mouse",
|
||||
"keyboard",
|
||||
{
|
||||
"type": "weather",
|
||||
"timeout": 1000
|
||||
},
|
||||
"top",
|
||||
"netio",
|
||||
"diskio",
|
||||
{
|
||||
"type": "physicaldisk",
|
||||
"temp": true
|
||||
},
|
||||
{
|
||||
"type": "weather",
|
||||
"timeout": 1000
|
||||
},
|
||||
"tpm",
|
||||
"version",
|
||||
"break",
|
||||
|
||||
+7
-6
@@ -94,23 +94,24 @@
|
||||
"opengl",
|
||||
"opencl",
|
||||
"users",
|
||||
// "bluetooth", // doesn't work on macOS because it requires bluetooth permissions
|
||||
// "bluetoothradio",
|
||||
"bluetooth",
|
||||
"bluetoothradio",
|
||||
"sound",
|
||||
"camera",
|
||||
"gamepad",
|
||||
"mouse",
|
||||
"keyboard",
|
||||
{
|
||||
"type": "weather",
|
||||
"timeout": 1000
|
||||
},
|
||||
"top",
|
||||
"netio",
|
||||
"diskio",
|
||||
{
|
||||
"type": "physicaldisk",
|
||||
"temp": true
|
||||
},
|
||||
{
|
||||
"type": "weather",
|
||||
"timeout": 1000
|
||||
},
|
||||
"tpm",
|
||||
"version",
|
||||
"logo",
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include "common/memrchr.h"
|
||||
#include "common/arrutil.h"
|
||||
|
||||
#ifdef FF_USE_SYSTEM_YYJSON
|
||||
#include <yyjson.h>
|
||||
@@ -628,3 +629,4 @@ static inline void ffStrbufPutTo(const FFstrbuf* strbuf, FILE* file) {
|
||||
bool ffStrbufDecodeHexEscapeSequences(FFstrbuf* strbuf);
|
||||
|
||||
#define FF_STRBUF_AUTO_DESTROY [[gnu::cleanup(ffStrbufDestroy)]] FFstrbuf
|
||||
#define FF_STRBUF_STATIC(str) { .allocated = 0, .length = (uint32_t) sizeof(str) - 1, .chars = str }
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include "common/arrutil.h"
|
||||
#include "common/strutil.h"
|
||||
#include "common/io.h"
|
||||
#include "fastfetch_config.h"
|
||||
#include "common/path.h"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <pwd.h>
|
||||
@@ -238,9 +238,7 @@ static void getDataDirs(FFPlatform* platform) {
|
||||
|
||||
ffPlatformPathAddHome(&platform->dataDirs, platform, "");
|
||||
platformPathAddEnv(&platform->dataDirs, "XDG_DATA_DIRS");
|
||||
#ifdef _PATH_LOCALBASE
|
||||
ffPlatformPathAddAbsolute(&platform->dataDirs, _PATH_LOCALBASE "/share/");
|
||||
#endif
|
||||
ffPlatformPathAddAbsolute(&platform->dataDirs, FF_PATH_PKG_BASE "/share/");
|
||||
ffPlatformPathAddAbsolute(&platform->dataDirs, FASTFETCH_TARGET_DIR_USR "/local/share/");
|
||||
ffPlatformPathAddAbsolute(&platform->dataDirs, FASTFETCH_TARGET_DIR_USR "/share/");
|
||||
}
|
||||
|
||||
@@ -83,6 +83,7 @@ void ffStrbufEnsureFreeNoCheck(FFstrbuf* strbuf, uint32_t free) {
|
||||
|
||||
// Round up to the next power of 2.
|
||||
// If the value is already a power of 2, it will be rounded up to the next power of 2.
|
||||
// allocate = __builtin_stdc_bit_ceil(allocate + 1); // Currently generates worse asm code
|
||||
allocate = 1U << (32 - __builtin_clz(allocate));
|
||||
}
|
||||
|
||||
|
||||
@@ -67,13 +67,13 @@ void ffPrepareCommandOption(FFdata* data) {
|
||||
char* moduleType = nullptr;
|
||||
size_t moduleLen = 0;
|
||||
while (ffStrbufGetdelim(&moduleType, &moduleLen, ':', &data->structure)) {
|
||||
#define FF_IF_MODULE_MATCH(moduleNameConstant) if (moduleLen == strlen(moduleNameConstant) && ffStrEqualsIgnCase(moduleType, moduleNameConstant) && !ffStrbufSeparatedContainIgnCaseS(&data->structureDisabled, moduleNameConstant, ':'))
|
||||
#define FF_IF_MODULE_MATCH(moduleNameConstant) if (ffStrEqualsIgnCase(moduleType, moduleNameConstant) && !ffStrbufSeparatedContainIgnCaseS(&data->structureDisabled, moduleNameConstant, ':'))
|
||||
|
||||
switch (moduleType[0]) {
|
||||
#if !FF_MODULE_DISABLE_CPUUSAGE
|
||||
case 'C':
|
||||
case 'c':
|
||||
FF_IF_MODULE_MATCH(FF_CPUUSAGE_MODULE_NAME)
|
||||
FF_IF_MODULE_MATCH(ffCPUUsageModuleInfo.name)
|
||||
ffPrepareCPUUsage();
|
||||
break;
|
||||
#endif
|
||||
@@ -81,7 +81,7 @@ void ffPrepareCommandOption(FFdata* data) {
|
||||
#if !FF_MODULE_DISABLE_DISKIO
|
||||
case 'D':
|
||||
case 'd':
|
||||
FF_IF_MODULE_MATCH(FF_DISKIO_MODULE_NAME) {
|
||||
FF_IF_MODULE_MATCH(ffDiskIOModuleInfo.name) {
|
||||
[[gnu::cleanup(ffDestroyDiskIOOptions)]] FFDiskIOOptions options;
|
||||
ffInitDiskIOOptions(&options);
|
||||
ffPrepareDiskIO(&options);
|
||||
@@ -92,7 +92,7 @@ void ffPrepareCommandOption(FFdata* data) {
|
||||
#if !FF_MODULE_DISABLE_NETIO
|
||||
case 'N':
|
||||
case 'n':
|
||||
FF_IF_MODULE_MATCH(FF_NETIO_MODULE_NAME) {
|
||||
FF_IF_MODULE_MATCH(ffNetIOModuleInfo.name) {
|
||||
[[gnu::cleanup(ffDestroyNetIOOptions)]] FFNetIOOptions options;
|
||||
ffInitNetIOOptions(&options);
|
||||
ffPrepareNetIO(&options);
|
||||
@@ -103,7 +103,7 @@ void ffPrepareCommandOption(FFdata* data) {
|
||||
#if !FF_MODULE_DISABLE_PUBLICIP
|
||||
case 'P':
|
||||
case 'p':
|
||||
FF_IF_MODULE_MATCH(FF_PUBLICIP_MODULE_NAME) {
|
||||
FF_IF_MODULE_MATCH(ffPublicIPModuleInfo.name) {
|
||||
[[gnu::cleanup(ffDestroyPublicIpOptions)]] FFPublicIPOptions options;
|
||||
ffInitPublicIpOptions(&options);
|
||||
ffPreparePublicIp(&options);
|
||||
@@ -111,10 +111,21 @@ void ffPrepareCommandOption(FFdata* data) {
|
||||
break;
|
||||
#endif
|
||||
|
||||
#if !FF_MODULE_DISABLE_TOP
|
||||
case 'T':
|
||||
case 't':
|
||||
FF_IF_MODULE_MATCH(ffTopModuleInfo.name) {
|
||||
[[gnu::cleanup(ffDestroyTopOptions)]] FFTopOptions options;
|
||||
ffInitTopOptions(&options);
|
||||
ffPrepareTopProcesses(options.showTypes);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
|
||||
#if !FF_MODULE_DISABLE_WEATHER
|
||||
case 'W':
|
||||
case 'w':
|
||||
FF_IF_MODULE_MATCH(FF_WEATHER_MODULE_NAME) {
|
||||
FF_IF_MODULE_MATCH(ffWeatherModuleInfo.name) {
|
||||
[[gnu::cleanup(ffDestroyWeatherOptions)]] FFWeatherOptions options;
|
||||
ffInitWeatherOptions(&options);
|
||||
ffPrepareWeather(&options);
|
||||
|
||||
@@ -136,9 +136,9 @@ static void prepareModuleJsonObject(const char* type, yyjson_val* module) {
|
||||
#if !FF_MODULE_DISABLE_CPUUSAGE
|
||||
case 'c':
|
||||
case 'C': {
|
||||
if (ffStrEqualsIgnCase(type, FF_CPUUSAGE_MODULE_NAME)) {
|
||||
if (ffStrEqualsIgnCase(type, ffCPUUsageModuleInfo.name)) {
|
||||
ffPrepareCPUUsage();
|
||||
} else if (ffStrEqualsIgnCase(type, FF_COMMAND_MODULE_NAME)) {
|
||||
} else if (ffStrEqualsIgnCase(type, ffCommandModuleInfo.name)) {
|
||||
[[gnu::cleanup(ffDestroyCommandOptions)]] FFCommandOptions options;
|
||||
ffInitCommandOptions(&options);
|
||||
if (module) {
|
||||
@@ -153,7 +153,7 @@ static void prepareModuleJsonObject(const char* type, yyjson_val* module) {
|
||||
#if !FF_MODULE_DISABLE_DISKIO
|
||||
case 'd':
|
||||
case 'D': {
|
||||
if (ffStrEqualsIgnCase(type, FF_DISKIO_MODULE_NAME)) {
|
||||
if (ffStrEqualsIgnCase(type, ffDiskIOModuleInfo.name)) {
|
||||
[[gnu::cleanup(ffDestroyDiskIOOptions)]] FFDiskIOOptions options;
|
||||
ffInitDiskIOOptions(&options);
|
||||
if (module) {
|
||||
@@ -168,7 +168,7 @@ static void prepareModuleJsonObject(const char* type, yyjson_val* module) {
|
||||
#if !FF_MODULE_DISABLE_NETIO
|
||||
case 'n':
|
||||
case 'N': {
|
||||
if (ffStrEqualsIgnCase(type, FF_NETIO_MODULE_NAME)) {
|
||||
if (ffStrEqualsIgnCase(type, ffNetIOModuleInfo.name)) {
|
||||
[[gnu::cleanup(ffDestroyNetIOOptions)]] FFNetIOOptions options;
|
||||
ffInitNetIOOptions(&options);
|
||||
if (module) {
|
||||
@@ -183,7 +183,7 @@ static void prepareModuleJsonObject(const char* type, yyjson_val* module) {
|
||||
#if !FF_MODULE_DISABLE_PUBLICIP
|
||||
case 'p':
|
||||
case 'P': {
|
||||
if (ffStrEqualsIgnCase(type, FF_PUBLICIP_MODULE_NAME)) {
|
||||
if (ffStrEqualsIgnCase(type, ffPublicIPModuleInfo.name)) {
|
||||
[[gnu::cleanup(ffDestroyPublicIpOptions)]] FFPublicIPOptions options;
|
||||
ffInitPublicIpOptions(&options);
|
||||
if (module) {
|
||||
@@ -195,10 +195,25 @@ static void prepareModuleJsonObject(const char* type, yyjson_val* module) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !FF_MODULE_DISABLE_TOP
|
||||
case 't':
|
||||
case 'T': {
|
||||
if (ffStrEqualsIgnCase(type, ffTopModuleInfo.name)) {
|
||||
[[gnu::cleanup(ffDestroyTopOptions)]] FFTopOptions options;
|
||||
ffInitTopOptions(&options);
|
||||
if (module) {
|
||||
ffTopModuleInfo.parseJsonObject(&options, module);
|
||||
}
|
||||
ffPrepareTopProcesses(options.showTypes);
|
||||
}
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !FF_MODULE_DISABLE_WEATHER
|
||||
case 'w':
|
||||
case 'W': {
|
||||
if (ffStrEqualsIgnCase(type, FF_WEATHER_MODULE_NAME)) {
|
||||
if (ffStrEqualsIgnCase(type, ffWeatherModuleInfo.name)) {
|
||||
[[gnu::cleanup(ffDestroyWeatherOptions)]] FFWeatherOptions options;
|
||||
ffInitWeatherOptions(&options);
|
||||
if (module) {
|
||||
|
||||
@@ -450,12 +450,10 @@ bool ffSettingsGetSQLite3String(const char* dbPath, const char* query, FFstrbuf*
|
||||
return true;
|
||||
}
|
||||
#else // FF_HAVE_SQLITE3
|
||||
int ffSettingsGetSQLite3Int(const char* dbPath, const char* query) {
|
||||
FF_UNUSED(dbPath, query)
|
||||
int ffSettingsGetSQLite3Int(const char*, const char*) {
|
||||
return 0;
|
||||
}
|
||||
bool ffSettingsGetSQLite3String(const char* dbPath, const char* query, FFstrbuf* result) {
|
||||
FF_UNUSED(dbPath, query, result)
|
||||
bool ffSettingsGetSQLite3String(const char*, const char*, FFstrbuf*) {
|
||||
return false;
|
||||
}
|
||||
#endif // FF_HAVE_SQLITE3
|
||||
|
||||
+48
-23
@@ -22,21 +22,41 @@ const char* ffSysctlGetString(int mib1, int mib2, FFstrbuf* result) {
|
||||
}
|
||||
|
||||
int ffSysctlGetInt(int mib1, int mib2, int defaultValue) {
|
||||
int result;
|
||||
alignas(int) uint8_t result[sizeof(int)];
|
||||
size_t neededLength = sizeof(result);
|
||||
if (sysctl((int[]) { mib1, mib2 }, 2, &result, &neededLength, nullptr, 0) != 0) {
|
||||
return defaultValue;
|
||||
}
|
||||
return result;
|
||||
switch (neededLength) {
|
||||
case sizeof(int32_t):
|
||||
return *(int32_t*)result;
|
||||
case sizeof(int16_t):
|
||||
return *(int16_t*)result;
|
||||
case sizeof(int8_t):
|
||||
return *(int8_t*)result;
|
||||
default:
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t ffSysctlGetInt64(int mib1, int mib2, int64_t defaultValue) {
|
||||
int64_t result;
|
||||
alignas(int64_t) uint8_t result[sizeof(int64_t)];
|
||||
size_t neededLength = sizeof(result);
|
||||
if (sysctl((int[]) { mib1, mib2 }, 2, &result, &neededLength, nullptr, 0) != 0) {
|
||||
return defaultValue;
|
||||
}
|
||||
return result;
|
||||
switch (neededLength) {
|
||||
case sizeof(int64_t):
|
||||
return *(int64_t*)result;
|
||||
case sizeof(int32_t):
|
||||
return *(int32_t*)result;
|
||||
case sizeof(int16_t):
|
||||
return *(int16_t*)result;
|
||||
case sizeof(int8_t):
|
||||
return *(int8_t*)result;
|
||||
default:
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
#else
|
||||
const char* ffSysctlGetString(const char* propName, FFstrbuf* result) {
|
||||
@@ -59,35 +79,40 @@ const char* ffSysctlGetString(const char* propName, FFstrbuf* result) {
|
||||
}
|
||||
|
||||
int ffSysctlGetInt(const char* propName, int defaultValue) {
|
||||
int result;
|
||||
alignas(int) uint8_t result[sizeof(int)];
|
||||
size_t neededLength = sizeof(result);
|
||||
if (sysctlbyname(propName, &result, &neededLength, nullptr, 0) != 0) {
|
||||
return defaultValue;
|
||||
}
|
||||
return result;
|
||||
switch (neededLength) {
|
||||
case sizeof(int32_t):
|
||||
return *(int32_t*)result;
|
||||
case sizeof(int16_t):
|
||||
return *(int16_t*)result;
|
||||
case sizeof(int8_t):
|
||||
return *(int8_t*)result;
|
||||
default:
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t ffSysctlGetInt64(const char* propName, int64_t defaultValue) {
|
||||
int64_t result;
|
||||
alignas(int64_t) uint8_t result[sizeof(int64_t)];
|
||||
size_t neededLength = sizeof(result);
|
||||
if (sysctlbyname(propName, &result, &neededLength, nullptr, 0) != 0) {
|
||||
return defaultValue;
|
||||
}
|
||||
return result;
|
||||
switch (neededLength) {
|
||||
case sizeof(int64_t):
|
||||
return *(int64_t*)result;
|
||||
case sizeof(int32_t):
|
||||
return *(int32_t*)result;
|
||||
case sizeof(int16_t):
|
||||
return *(int16_t*)result;
|
||||
case sizeof(int8_t):
|
||||
return *(int8_t*)result;
|
||||
default:
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
#endif // OpenBSD
|
||||
|
||||
void* ffSysctlGetData(int* request, u_int requestLength, size_t* resultLength) {
|
||||
if (sysctl(request, requestLength, nullptr, resultLength, nullptr, 0) != 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void* data = malloc(*resultLength);
|
||||
|
||||
if (sysctl(request, requestLength, data, resultLength, nullptr, 0) != 0) {
|
||||
free(data);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -18,10 +18,35 @@ typedef struct FFModuleFormatArgList {
|
||||
|
||||
#define FF_FORMAT_ARG_LIST(list) { .args = list, .count = sizeof(list) / sizeof(FFModuleFormatArg) }
|
||||
|
||||
typedef struct FFModuleDisplayName {
|
||||
const char* en; // English
|
||||
|
||||
const char* ar; // Arabic
|
||||
const char* cs; // Czech
|
||||
const char* de; // German
|
||||
const char* es; // Spanish
|
||||
const char* fr; // French
|
||||
const char* gl; // Galician
|
||||
const char* he; // Hebrew
|
||||
const char* id; // Indonesian
|
||||
const char* it; // Italian
|
||||
const char* ja; // Japanese
|
||||
const char* ko; // Korean
|
||||
const char* pl; // Polish
|
||||
const char* pt; // (Brazilian) Portuguese
|
||||
const char* ru; // Russian
|
||||
const char* tr; // Turkish
|
||||
const char* uk; // Ukrainian
|
||||
const char* vi; // Vietnamese
|
||||
const char* zh_CN; // Simplified Chinese
|
||||
const char* zh_TW; // Traditional Chinese
|
||||
} FFModuleDisplayName;
|
||||
|
||||
// Must be the first field of FFModuleOptions
|
||||
typedef struct FFModuleBaseInfo {
|
||||
const char* name;
|
||||
const char* description;
|
||||
FFModuleDisplayName displayName;
|
||||
// A dirty polymorphic implementation in C.
|
||||
// This is UB, because `void*` is not compatible with `FF*Options*`.
|
||||
// However we can't do it better unless we move to C++, so that `option` becomes a `this` pointer
|
||||
@@ -94,3 +119,5 @@ static inline void ffOptionDestroyModuleArg(FFModuleArgs* args) {
|
||||
}
|
||||
|
||||
enum { FF_OPTION_MAX_SIZE = 1 << 8 }; // Maximum size of a single option value, used for static allocation
|
||||
|
||||
#define FF_MODULE_GET_DISPLAY_NAME(moduleName) (*(const char**) ((uint8_t*) &ff ## moduleName ## ModuleInfo.displayName + instance.config.display.keyLanguage))
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "common/FFstrbuf.h"
|
||||
#include "common/strutil.h"
|
||||
#include "fastfetch_config.h"
|
||||
|
||||
const char* ffFindExecutableInPath(const char* name, FFstrbuf* result);
|
||||
static inline bool ffIsAbsolutePath(const char* path) {
|
||||
@@ -19,3 +20,18 @@ char* realpath(const char* __restrict file_name, char* __restrict resolved_name
|
||||
ssize_t freadlink(void* hFile, char* buf, size_t bufsiz);
|
||||
ssize_t readlink(const char* path, char* buf, size_t bufsiz);
|
||||
#endif
|
||||
|
||||
#ifdef __FreeBSD__
|
||||
#include <paths.h>
|
||||
#ifdef _PATH_LOCALBASE
|
||||
#define FF_PATH_PKG_BASE _PATH_LOCALBASE
|
||||
#else
|
||||
#define FF_PATH_PKG_BASE "/usr/local"
|
||||
#endif
|
||||
#elif __OpenBSD__
|
||||
#define FF_PATH_PKG_BASE "/usr/local"
|
||||
#elif __NetBSD__
|
||||
#define FF_PATH_PKG_BASE "/usr/pkg"
|
||||
#else
|
||||
#define FF_PATH_PKG_BASE FASTFETCH_TARGET_DIR_USR
|
||||
#endif
|
||||
@@ -14,4 +14,3 @@ const char* ffSysctlGetString(const char* propName, FFstrbuf* result);
|
||||
[[nodiscard]] int ffSysctlGetInt(const char* propName, int defaultValue);
|
||||
[[nodiscard]] int64_t ffSysctlGetInt64(const char* propName, int64_t defaultValue);
|
||||
#endif
|
||||
[[nodiscard]] void* ffSysctlGetData(int* request, u_int requestLength, size_t* resultLength);
|
||||
|
||||
+57
-1
@@ -11,6 +11,7 @@ enum {
|
||||
SystemFirmwareTableInformation = 76,
|
||||
SystemBootEnvironmentInformation = 90,
|
||||
SystemLogicalProcessorAndGroupInformation = 107,
|
||||
SystemBasicPerformanceInformation = 123,
|
||||
SystemSecureBootInformation = 146,
|
||||
};
|
||||
|
||||
@@ -169,7 +170,7 @@ NTSTATUS NTAPI NtQuerySystemEnvironmentValueEx(
|
||||
);
|
||||
|
||||
NTSTATUS NTAPI RtlGUIDFromString(IN PCUNICODE_STRING GuidString, OUT GUID* Guid);
|
||||
NTSTATUS NTAPI RtlStringFromGUIDEx(IN GUID* Guid, OUT PCUNICODE_STRING GuidString, _In_ BOOLEAN AllocateGuidString);
|
||||
NTSTATUS NTAPI RtlStringFromGUIDEx(IN const GUID* Guid, OUT PUNICODE_STRING GuidString, IN BOOLEAN AllocateGuidString);
|
||||
|
||||
typedef struct _SYSTEM_SECUREBOOT_INFORMATION {
|
||||
BOOLEAN SecureBootEnabled;
|
||||
@@ -198,6 +199,13 @@ typedef struct _SYSTEM_FIRMWARE_TABLE_INFORMATION {
|
||||
_Field_size_bytes_(TableBufferLength) UCHAR TableBuffer[];
|
||||
} SYSTEM_FIRMWARE_TABLE_INFORMATION, *PSYSTEM_FIRMWARE_TABLE_INFORMATION;
|
||||
|
||||
typedef struct _SYSTEM_BASIC_PERFORMANCE_INFORMATION {
|
||||
SIZE_T AvailablePages;
|
||||
SIZE_T CommittedPages;
|
||||
SIZE_T CommitLimit;
|
||||
SIZE_T PeakCommitment;
|
||||
} SYSTEM_BASIC_PERFORMANCE_INFORMATION, *PSYSTEM_BASIC_PERFORMANCE_INFORMATION;
|
||||
|
||||
NTSYSAPI NTSTATUS NTAPI NtDelayExecution(_In_ BOOLEAN Alertable, _In_ PLARGE_INTEGER DelayInterval);
|
||||
|
||||
/**
|
||||
@@ -599,6 +607,54 @@ typedef struct _KUSER_SHARED_DATA {
|
||||
};
|
||||
};
|
||||
|
||||
//
|
||||
// Reserved padding field to preserve structure alignment and compatibility.
|
||||
//
|
||||
|
||||
ULONG DataFlagsPad[1];
|
||||
|
||||
//
|
||||
// Depending on the processor, the code for fast system call will differ,
|
||||
// Stub code is provided pointers below to access the appropriate code.
|
||||
//
|
||||
// N.B. The following field is only used on 32-bit systems.
|
||||
//
|
||||
|
||||
ULONGLONG TestRetInstruction;
|
||||
|
||||
//
|
||||
// Query-performance counter (QPC) frequency, in counts per second.
|
||||
//
|
||||
// N.B. This value represents the fixed frequency of the system's high-resolution
|
||||
// performance counter. It is used by user-mode time routines to convert QPC
|
||||
// ticks into elapsed time without requiring a system call. The frequency is
|
||||
// constant for the lifetime of the system and reflects the hardware or
|
||||
// virtualized timer source selected by the kernel.
|
||||
//
|
||||
|
||||
LONGLONG QpcFrequency;
|
||||
|
||||
//
|
||||
// On AMD64, this value is initialized to a nonzero value if the system
|
||||
// operates with an altered view of the system service call mechanism.
|
||||
//
|
||||
|
||||
ULONG SystemCall;
|
||||
|
||||
//
|
||||
// Reserved field - do not use. Used to be UserCetAvailableEnvironments.
|
||||
//
|
||||
|
||||
ULONG Reserved2;
|
||||
|
||||
//
|
||||
// Full 64 bit version of the number of physical pages in the system.
|
||||
// This can dynamically change as physical memory can be added or removed
|
||||
// from a running system.
|
||||
//
|
||||
|
||||
ULONGLONG FullNumberOfPhysicalPages;
|
||||
|
||||
// ... more fields follow, but we don't need them
|
||||
} KUSER_SHARED_DATA, *PKUSER_SHARED_DATA;
|
||||
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
#include "battery.h"
|
||||
|
||||
#include <kstat.h>
|
||||
|
||||
static inline void kstatFreeWrap(kstat_ctl_t** pkc) {
|
||||
assert(pkc);
|
||||
if (*pkc) {
|
||||
kstat_close(*pkc);
|
||||
}
|
||||
}
|
||||
|
||||
static bool getUint32(kstat_t* ksp, const char* name, uint32_t* value) {
|
||||
kstat_named_t* stat = kstat_data_lookup(ksp, name);
|
||||
if (!stat) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (stat->data_type) {
|
||||
case KSTAT_DATA_UINT32:
|
||||
*value = stat->value.ui32;
|
||||
return true;
|
||||
case KSTAT_DATA_INT32:
|
||||
if (stat->value.i32 >= 0) {
|
||||
*value = (uint32_t) stat->value.i32;
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
#ifdef _INT64_TYPE
|
||||
case KSTAT_DATA_UINT64:
|
||||
if (stat->value.ui64 <= UINT32_MAX) {
|
||||
*value = (uint32_t) stat->value.ui64;
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case KSTAT_DATA_INT64:
|
||||
if (stat->value.i64 >= 0 && (uint64_t) stat->value.i64 <= UINT32_MAX) {
|
||||
*value = (uint32_t) stat->value.i64;
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* ffDetectBattery([[maybe_unused]] FFBatteryOptions* options, FFlist* results) {
|
||||
// https://github.com/illumos/illumos-gate/blob/master/usr/src/cmd/powertop/common/battery.c
|
||||
[[gnu::cleanup(kstatFreeWrap)]] kstat_ctl_t* kc = kstat_open();
|
||||
if (!kc) {
|
||||
return "kstat_open() failed";
|
||||
}
|
||||
|
||||
// illumos exposes battery information through either of these modules.
|
||||
const char* modules[] = { "battery", "acpi_drv" };
|
||||
kstat_t* bif = nullptr;
|
||||
kstat_t* bst = nullptr;
|
||||
for (size_t i = 0; i < ARRAY_SIZE(modules); ++i) {
|
||||
bif = kstat_lookup(kc, modules[i], 0, "battery BIF0");
|
||||
bst = kstat_lookup(kc, modules[i], 0, "battery BST0");
|
||||
if (bif && bst) {
|
||||
break;
|
||||
}
|
||||
bif = nullptr;
|
||||
bst = nullptr;
|
||||
}
|
||||
|
||||
if (!bif || !bst || kstat_read(kc, bif, nullptr) < 0 || kstat_read(kc, bst, nullptr) < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uint32_t lastCapacity, remainingCapacity, rate, state;
|
||||
if (!getUint32(bif, "bif_last_cap", &lastCapacity) ||
|
||||
!getUint32(bst, "bst_rem_cap", &remainingCapacity) ||
|
||||
!getUint32(bst, "bst_state", &state)) {
|
||||
return "kstat_data_lookup() failed";
|
||||
}
|
||||
|
||||
if (lastCapacity == 0 || remainingCapacity == UINT32_MAX) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FFBatteryResult* battery = FF_LIST_ADD(FFBatteryResult, *results);
|
||||
ffStrbufInit(&battery->manufacturer);
|
||||
ffStrbufInit(&battery->manufactureDate);
|
||||
ffStrbufInit(&battery->modelName);
|
||||
ffStrbufInit(&battery->technology);
|
||||
ffStrbufInit(&battery->serial);
|
||||
battery->status = FF_BATTERY_STATUS_NONE;
|
||||
battery->capacity = (double) remainingCapacity * 100.0 / (double) lastCapacity;
|
||||
battery->temperature = FF_BATTERY_TEMP_UNSET;
|
||||
battery->cycleCount = 0;
|
||||
battery->timeRemaining = -1;
|
||||
|
||||
// illumos BST state: bit 0 discharging, bit 1 charging, bit 2 critical.
|
||||
if (state & (1u << 0)) {
|
||||
battery->status |= FF_BATTERY_STATUS_DISCHARGING;
|
||||
}
|
||||
if (state & (1u << 1)) {
|
||||
battery->status |= FF_BATTERY_STATUS_CHARGING;
|
||||
}
|
||||
if (state & (1u << 2)) {
|
||||
battery->status |= FF_BATTERY_STATUS_CRITICAL;
|
||||
}
|
||||
|
||||
if ((state & (1u << 0)) && getUint32(bst, "bst_rate", &rate) && rate > 0 && rate != UINT32_MAX) {
|
||||
battery->timeRemaining = (int32_t) (((uint64_t) remainingCapacity * 3600) / rate);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
@@ -11,29 +11,12 @@
|
||||
#include "detection/displayserver/displayserver.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#ifdef __FreeBSD__
|
||||
#include <paths.h>
|
||||
#ifndef _PATH_LOCALBASE
|
||||
#define _PATH_LOCALBASE "/usr/local"
|
||||
#endif
|
||||
#elif __OpenBSD__
|
||||
#define _PATH_LOCALBASE "/usr/local"
|
||||
#elif __NetBSD__
|
||||
#define _PATH_LOCALBASE "/usr/pkg"
|
||||
#endif
|
||||
|
||||
static void getKDE(FFstrbuf* result, [[maybe_unused]] FFDEOptions* options) {
|
||||
#ifdef _PATH_LOCALBASE
|
||||
ffParsePropFile(_PATH_LOCALBASE "/share/wayland-sessions/plasma.desktop", "X-KDE-PluginInfo-Version =", result);
|
||||
ffParsePropFile(FF_PATH_PKG_BASE "/share/wayland-sessions/plasma.desktop", "X-KDE-PluginInfo-Version =", result);
|
||||
if (result->length == 0) {
|
||||
ffParsePropFile(_PATH_LOCALBASE "/share/xsessions/plasmax11.desktop", "X-KDE-PluginInfo-Version =", result);
|
||||
ffParsePropFile(FF_PATH_PKG_BASE "/share/xsessions/plasmax11.desktop", "X-KDE-PluginInfo-Version =", result);
|
||||
}
|
||||
#else
|
||||
ffParsePropFile(FASTFETCH_TARGET_DIR_USR "/share/wayland-sessions/plasma.desktop", "X-KDE-PluginInfo-Version =", result);
|
||||
if (result->length == 0) {
|
||||
ffParsePropFile(FASTFETCH_TARGET_DIR_USR "/share/xsessions/plasmax11.desktop", "X-KDE-PluginInfo-Version =", result);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (result->length == 0) {
|
||||
ffParsePropFileData("xsessions/plasma.desktop", "X-KDE-PluginInfo-Version =", result);
|
||||
|
||||
@@ -72,8 +72,7 @@ const char* ffDetectDiskIO(FFlist* result, FFDiskIOOptions* options) {
|
||||
uint64_t* prevValue = (uint64_t*) ((uint8_t*) icPrev + off);
|
||||
uint64_t* currValue = (uint64_t*) ((uint8_t*) icCurr + off);
|
||||
uint64_t temp = *currValue;
|
||||
*currValue -= *prevValue;
|
||||
*currValue /= (time2 - time1) / 1000 /* seconds */;
|
||||
*currValue = (*currValue - *prevValue) * 1000 / (time2 - time1); // Calculate per second
|
||||
|
||||
// For next function call
|
||||
*prevValue = temp;
|
||||
|
||||
@@ -174,6 +174,11 @@ static bool detectDE(FFDisplayServerResult* ds) {
|
||||
ffStrbufPrependS(&ds->dePrettyName, "OxygenOS");
|
||||
return true;
|
||||
}
|
||||
if (ffSettingsGetAndroidProperty("ro.product.brand", &ds->dePrettyName) && ffStrbufEqualS(&ds->dePrettyName, "asus")) {
|
||||
ffStrbufClear(&ds->dePrettyName);
|
||||
ffSettingsGetAndroidProperty("ro.build.version.incremental", &ds->dePrettyName);
|
||||
return true;
|
||||
}
|
||||
if (ffSettingsGetAndroidProperty("ro.build.display.id", &ds->dePrettyName)) {
|
||||
if (ffStrbufStartsWithS(&ds->dePrettyName, "RedMagicOS")) {
|
||||
ffStrbufInsertNC(&ds->dePrettyName, strlen("RedMagicOS"), 1, ' ');
|
||||
@@ -187,13 +192,23 @@ static bool detectDE(FFDisplayServerResult* ds) {
|
||||
}
|
||||
|
||||
void ffConnectDisplayServerImpl(FFDisplayServerResult* ds) {
|
||||
const char* error = ffdsConnectXcbRandr(ds);
|
||||
if (error) {
|
||||
error = ffdsConnectXrandr(ds);
|
||||
}
|
||||
if (!error) {
|
||||
ffdsDetectWMDE(ds);
|
||||
return;
|
||||
// Keep the same display-server preference as Linux: Wayland provides the
|
||||
// richest output information, followed by XCB and XRandR.
|
||||
if (instance.config.general.dsForceDrm == FF_DS_FORCE_DRM_TYPE_FALSE) {
|
||||
ffdsConnectWayland(ds);
|
||||
|
||||
if (ds->displays.length == 0) {
|
||||
ffdsConnectXcbRandr(ds);
|
||||
}
|
||||
|
||||
if (ds->displays.length == 0) {
|
||||
ffdsConnectXrandr(ds);
|
||||
}
|
||||
|
||||
if (ds->displays.length > 0) {
|
||||
ffdsDetectWMDE(ds);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// https://source.android.com/docs/core/graphics/surfaceflinger-windowmanager
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <libgen.h>
|
||||
|
||||
#if __FreeBSD__
|
||||
#include <sys/sysctl.h>
|
||||
@@ -376,48 +377,60 @@ static const char* getFromProcesses(FFDisplayServerResult* result) {
|
||||
}
|
||||
|
||||
FF_STRBUF_AUTO_DESTROY procPath = ffStrbufCreateA(64);
|
||||
ffStrbufAppendS(&procPath, "/proc/");
|
||||
|
||||
uint32_t procPathLength = procPath.length;
|
||||
|
||||
FF_STRBUF_AUTO_DESTROY loginuid = ffStrbufCreate();
|
||||
FF_STRBUF_AUTO_DESTROY processName = ffStrbufCreateA(256); // Some processes have large command lines (looking at you chrome)
|
||||
int procfd = dirfd(procdir);
|
||||
|
||||
struct dirent* dirent;
|
||||
while ((dirent = readdir(procdir)) != nullptr) {
|
||||
// Match only folders starting with a number (the pid folders)
|
||||
if (dirent->d_type != DT_DIR || !ffCharIsDigit(dirent->d_name[0])) {
|
||||
if ((dirent->d_type != DT_DIR && dirent->d_type != DT_UNKNOWN) || !ffCharIsDigit(dirent->d_name[0])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ffStrbufAppendS(&procPath, dirent->d_name);
|
||||
ffStrbufSetS(&procPath, dirent->d_name);
|
||||
uint32_t procFolderPathLength = procPath.length;
|
||||
|
||||
// Don't check for processes not owned by the current user.
|
||||
char loginuid[32];
|
||||
ffStrbufAppendS(&procPath, "/loginuid");
|
||||
ffReadFileBuffer(procPath.chars, &loginuid);
|
||||
if (ffStrbufToUInt(&loginuid, (uint64_t) -1) != userId) {
|
||||
ffStrbufSubstrBefore(&procPath, procPathLength);
|
||||
ssize_t bytesRead = ffReadFileDataRelative(procfd, procPath.chars, sizeof(loginuid) - 1, loginuid);
|
||||
if (bytesRead <= 0) {
|
||||
continue;
|
||||
}
|
||||
loginuid[bytesRead] = '\0';
|
||||
if (strtol(loginuid, nullptr, 10) != (long) userId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ffStrbufSubstrBefore(&procPath, procFolderPathLength);
|
||||
|
||||
// We check the cmdline for the process name, because it is not trimmed.
|
||||
ffStrbufAppendS(&procPath, "/cmdline");
|
||||
ffReadFileBuffer(procPath.chars, &processName);
|
||||
ffStrbufTrimRightSpace(&processName);
|
||||
ffStrbufSubstrBeforeFirstC(&processName, '\0'); // Trim the arguments
|
||||
ffStrbufSubstrAfterLastC(&processName, '/');
|
||||
|
||||
ffStrbufSubstrBefore(&procPath, procPathLength);
|
||||
// We check the realpath for the process name
|
||||
ffStrbufAppendS(&procPath, "/exe");
|
||||
char exePath[PATH_MAX];
|
||||
ssize_t exePathLength = readlinkat(procfd, procPath.chars, exePath, sizeof(exePath) - 1);
|
||||
const char* processName;
|
||||
if (exePathLength >= 0) {
|
||||
exePath[exePathLength] = '\0';
|
||||
processName = basename(exePath);
|
||||
} else {
|
||||
ffStrbufSubstrBefore(&procPath, procFolderPathLength);
|
||||
ffStrbufAppendS(&procPath, "/comm");
|
||||
exePathLength = ffReadFileDataRelative(procfd, procPath.chars, sizeof(exePath) - 1, exePath);
|
||||
if (exePathLength <= 0) {
|
||||
continue;
|
||||
}
|
||||
if (exePath[exePathLength - 1] == '\n') {
|
||||
--exePathLength;
|
||||
}
|
||||
exePath[exePathLength] = '\0';
|
||||
processName = exePath;
|
||||
}
|
||||
|
||||
if (result->dePrettyName.length == 0) {
|
||||
applyPrettyNameIfDE(result, processName.chars);
|
||||
applyPrettyNameIfDE(result, processName);
|
||||
}
|
||||
|
||||
if (result->wmPrettyName.length == 0) {
|
||||
applyNameIfWM(result, processName.chars);
|
||||
applyNameIfWM(result, processName);
|
||||
}
|
||||
|
||||
if (result->dePrettyName.length > 0 && result->wmPrettyName.length > 0) {
|
||||
|
||||
@@ -14,74 +14,68 @@
|
||||
#define MTLFeatureSet_macOS_GPUFamily2_v1 ((MTLFeatureSet) 10005)
|
||||
#endif
|
||||
|
||||
const char* ffGpuDetectDriverVersion(FFlist* gpus)
|
||||
{
|
||||
if (@available(macOS 10.7, *))
|
||||
{
|
||||
NSMutableArray* arr = NSMutableArray.new;
|
||||
FF_LIST_FOR_EACH(FFGPUResult, x, *gpus)
|
||||
[arr addObject:@(x->driver.chars)];
|
||||
|
||||
NSDictionary* dict = CFBridgingRelease(KextManagerCopyLoadedKextInfo((__bridge CFArrayRef)arr, (__bridge CFArrayRef)@[@"CFBundleVersion"]));
|
||||
FF_LIST_FOR_EACH(FFGPUResult, x, *gpus)
|
||||
{
|
||||
NSString* version = dict[@(x->driver.chars)][@"CFBundleVersion"];
|
||||
if (version)
|
||||
{
|
||||
ffStrbufAppendC(&x->driver, ' ');
|
||||
ffStrbufAppendS(&x->driver, version.UTF8String);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
const char* ffGpuDetectDriverVersion(FFlist* gpus) {
|
||||
NSMutableArray* arr = NSMutableArray.new;
|
||||
FF_LIST_FOR_EACH (FFGPUResult, x, *gpus) {
|
||||
[arr addObject:@(x->driver.chars)];
|
||||
}
|
||||
return "Unsupported macOS version";
|
||||
|
||||
NSDictionary* dict = CFBridgingRelease(KextManagerCopyLoadedKextInfo((__bridge CFArrayRef) arr, (__bridge CFArrayRef) @[ @"CFBundleVersion" ]));
|
||||
FF_LIST_FOR_EACH (FFGPUResult, x, *gpus) {
|
||||
NSString* version = dict[@(x->driver.chars)][@"CFBundleVersion"];
|
||||
if (version) {
|
||||
ffStrbufAppendC(&x->driver, ' ');
|
||||
ffStrbufAppendS(&x->driver, version.UTF8String);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const char* ffGpuDetectMetal(FFlist* gpus)
|
||||
{
|
||||
if (@available(macOS 10.13, *))
|
||||
{
|
||||
for (id<MTLDevice> device in MTLCopyAllDevices())
|
||||
{
|
||||
FFGPUResult* gpu = nullptr;
|
||||
FF_LIST_FOR_EACH(FFGPUResult, x, *gpus)
|
||||
{
|
||||
if (x->deviceId == device.registryID)
|
||||
{
|
||||
gpu = x;
|
||||
break;
|
||||
}
|
||||
const char* ffGpuDetectMetal(FFlist* gpus) {
|
||||
// Metal.framework is statically linked into the application,
|
||||
// so `MTLCopyAllDevices` is always available, or the application will not start.
|
||||
for (id<MTLDevice> device in MTLCopyAllDevices()) {
|
||||
FFGPUResult* gpu = nullptr;
|
||||
FF_LIST_FOR_EACH (FFGPUResult, x, *gpus) {
|
||||
if (x->deviceId == device.registryID) {
|
||||
gpu = x;
|
||||
break;
|
||||
}
|
||||
if (!gpu) continue;
|
||||
|
||||
#ifndef MAC_OS_X_VERSION_10_15
|
||||
if ([device supportsFeatureSet:MTLFeatureSet_macOS_GPUFamily2_v1])
|
||||
ffStrbufSetStatic(&gpu->platformApi, "Metal Feature Set 2");
|
||||
else if ([device supportsFeatureSet:MTLFeatureSet_macOS_GPUFamily1_v1])
|
||||
ffStrbufSetStatic(&gpu->platformApi, "Metal Feature Set 1");
|
||||
#else // MAC_OS_X_VERSION_10_15
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wunguarded-availability-new"
|
||||
if ([device supportsFamily:MTLGPUFamilyMetal4])
|
||||
ffStrbufSetStatic(&gpu->platformApi, "Metal 4");
|
||||
else if ([device supportsFamily:MTLGPUFamilyMetal3])
|
||||
ffStrbufSetStatic(&gpu->platformApi, "Metal 3");
|
||||
#pragma clang diagnostic pop
|
||||
else if ([device supportsFamily:MTLGPUFamilyCommon3])
|
||||
ffStrbufSetStatic(&gpu->platformApi, "Metal Common 3");
|
||||
else if ([device supportsFamily:MTLGPUFamilyCommon2])
|
||||
ffStrbufSetStatic(&gpu->platformApi, "Metal Common 2");
|
||||
else if ([device supportsFamily:MTLGPUFamilyCommon1])
|
||||
ffStrbufSetStatic(&gpu->platformApi, "Metal Common 1");
|
||||
|
||||
gpu->type = device.hasUnifiedMemory ? FF_GPU_TYPE_INTEGRATED : FF_GPU_TYPE_DISCRETE;
|
||||
gpu->index = (uint32_t) device.locationNumber;
|
||||
|
||||
if (device.hasUnifiedMemory && device.recommendedMaxWorkingSetSize > 0)
|
||||
gpu->shared.total = device.recommendedMaxWorkingSetSize;
|
||||
#endif
|
||||
}
|
||||
return nullptr;
|
||||
if (!gpu) {
|
||||
continue;
|
||||
}
|
||||
|
||||
#ifndef MAC_OS_X_VERSION_10_15
|
||||
if ([device supportsFeatureSet:MTLFeatureSet_macOS_GPUFamily2_v1]) {
|
||||
ffStrbufSetStatic(&gpu->platformApi, "Metal Feature Set 2");
|
||||
} else if ([device supportsFeatureSet:MTLFeatureSet_macOS_GPUFamily1_v1]) {
|
||||
ffStrbufSetStatic(&gpu->platformApi, "Metal Feature Set 1");
|
||||
}
|
||||
#else // MAC_OS_X_VERSION_10_15
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wunguarded-availability-new"
|
||||
if ([device supportsFamily:MTLGPUFamilyMetal4]) {
|
||||
ffStrbufSetStatic(&gpu->platformApi, "Metal 4");
|
||||
} else if ([device supportsFamily:MTLGPUFamilyMetal3]) {
|
||||
ffStrbufSetStatic(&gpu->platformApi, "Metal 3");
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
else if ([device supportsFamily:MTLGPUFamilyCommon3]) {
|
||||
ffStrbufSetStatic(&gpu->platformApi, "Metal Common 3");
|
||||
} else if ([device supportsFamily:MTLGPUFamilyCommon2]) {
|
||||
ffStrbufSetStatic(&gpu->platformApi, "Metal Common 2");
|
||||
} else if ([device supportsFamily:MTLGPUFamilyCommon1]) {
|
||||
ffStrbufSetStatic(&gpu->platformApi, "Metal Common 1");
|
||||
}
|
||||
|
||||
gpu->type = device.hasUnifiedMemory ? FF_GPU_TYPE_INTEGRATED : FF_GPU_TYPE_DISCRETE;
|
||||
gpu->index = (uint32_t) device.locationNumber;
|
||||
|
||||
if (device.hasUnifiedMemory && device.recommendedMaxWorkingSetSize > 0) {
|
||||
gpu->shared.total = device.recommendedMaxWorkingSetSize;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return "Metal API is not supported by this macOS version";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -1,21 +1,10 @@
|
||||
#include "gpu.h"
|
||||
#include "common/io.h"
|
||||
#include "common/path.h"
|
||||
#include "common/properties.h"
|
||||
#include "common/memrchr.h"
|
||||
#include "common/strutil.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#ifdef __FreeBSD__
|
||||
#include <paths.h>
|
||||
#ifndef _PATH_LOCALBASE
|
||||
#define _PATH_LOCALBASE "/usr/local"
|
||||
#endif
|
||||
#elif __OpenBSD__
|
||||
#define _PATH_LOCALBASE "/usr/local"
|
||||
#elif __NetBSD__
|
||||
#define _PATH_LOCALBASE "/usr/pkg"
|
||||
#endif
|
||||
|
||||
#if FF_HAVE_EMBEDDED_PCIIDS
|
||||
#include "fastfetch_pciids.c.inc"
|
||||
#endif
|
||||
@@ -46,9 +35,9 @@ static const FFstrbuf* loadPciIds() {
|
||||
}
|
||||
}
|
||||
#elif __OpenBSD__ || __FreeBSD__ || __NetBSD__
|
||||
ffReadFileBuffer(_PATH_LOCALBASE "/share/hwdata/pci.ids", &pciids);
|
||||
ffReadFileBuffer(FF_PATH_PKG_BASE "/share/hwdata/pci.ids", &pciids);
|
||||
if (pciids.length == 0) {
|
||||
ffReadFileBuffer(_PATH_LOCALBASE "/share/pciids/pci.ids", &pciids);
|
||||
ffReadFileBuffer(FF_PATH_PKG_BASE "/share/pciids/pci.ids", &pciids);
|
||||
}
|
||||
#elif __sun
|
||||
ffReadFileBuffer(FASTFETCH_TARGET_DIR_ROOT "/usr/share/hwdata/pci.ids", &pciids);
|
||||
|
||||
+56
-43
@@ -62,50 +62,63 @@ static void detectGTKFromSettings(FFGTKResult* result) {
|
||||
|
||||
const FFDisplayServerResult* wmde = ffConnectDisplayServer();
|
||||
|
||||
if (ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_XFCE4)) {
|
||||
themeName = ffSettingsGetXFConf("xsettings", "/Net/ThemeName", FF_VARIANT_TYPE_STRING).strValue;
|
||||
iconsName = ffSettingsGetXFConf("xsettings", "/Net/IconThemeName", FF_VARIANT_TYPE_STRING).strValue;
|
||||
fontName = ffSettingsGetXFConf("xsettings", "/Gtk/FontName", FF_VARIANT_TYPE_STRING).strValue;
|
||||
cursorTheme = ffSettingsGetXFConf("xsettings", "/Gtk/CursorThemeName", FF_VARIANT_TYPE_STRING).strValue;
|
||||
cursorSize = ffSettingsGetXFConf("xsettings", "/Gtk/CursorThemeSize", FF_VARIANT_TYPE_INT).intValue;
|
||||
wallpaper = ffSettingsGetXFConfFirstMatch("xfce4-desktop", "/backdrop/screen0", FF_VARIANT_TYPE_STRING, nullptr, testXfconfWallpaperPropKey).strValue;
|
||||
} else if (ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_CINNAMON)) {
|
||||
themeName = ffSettingsGetGnome("/org/cinnamon/desktop/interface/gtk-theme", "org.cinnamon.desktop.interface", nullptr, "gtk-theme", FF_VARIANT_TYPE_STRING).strValue;
|
||||
iconsName = ffSettingsGetGnome("/org/cinnamon/desktop/interface/icon-theme", "org.cinnamon.desktop.interface", nullptr, "icon-theme", FF_VARIANT_TYPE_STRING).strValue;
|
||||
fontName = ffSettingsGetGnome("/org/cinnamon/desktop/interface/font-name", "org.cinnamon.desktop.interface", nullptr, "font-name", FF_VARIANT_TYPE_STRING).strValue;
|
||||
cursorTheme = ffSettingsGetGnome("/org/cinnamon/desktop/interface/cursor-theme", "org.cinnamon.desktop.interface", nullptr, "cursor-theme", FF_VARIANT_TYPE_STRING).strValue;
|
||||
cursorSize = ffSettingsGetGnome("/org/cinnamon/desktop/interface/cursor-size", "org.cinnamon.desktop.interface", nullptr, "cursor-size", FF_VARIANT_TYPE_INT).intValue;
|
||||
wallpaper = ffSettingsGetGnome("/org/cinnamon/desktop/background/picture-uri", "org.cinnamon.desktop.background", nullptr, "picture-uri", FF_VARIANT_TYPE_STRING).strValue;
|
||||
} else if (ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_MATE)) {
|
||||
themeName = ffSettingsGetGnome("/org/mate/interface/gtk-theme", "org.mate.interface", nullptr, "gtk-theme", FF_VARIANT_TYPE_STRING).strValue;
|
||||
iconsName = ffSettingsGetGnome("/org/mate/interface/icon-theme", "org.mate.interface", nullptr, "icon-theme", FF_VARIANT_TYPE_STRING).strValue;
|
||||
fontName = ffSettingsGetGnome("/org/mate/interface/font-name", "org.mate.interface", nullptr, "font-name", FF_VARIANT_TYPE_STRING).strValue;
|
||||
cursorTheme = ffSettingsGetGnome("/org/mate/peripherals-mouse/cursor-theme", "org.mate.peripherals-mouse", nullptr, "cursor-theme", FF_VARIANT_TYPE_STRING).strValue;
|
||||
cursorSize = ffSettingsGetGnome("/org/mate/peripherals-mouse/cursor-size", "org.mate.peripherals-mouse", nullptr, "cursor-size", FF_VARIANT_TYPE_INT).intValue;
|
||||
wallpaper = ffSettingsGetGnome("/org/mate/desktop/background", "org.mate.background", nullptr, "picture-filename", FF_VARIANT_TYPE_STRING).strValue;
|
||||
} else if (
|
||||
ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_GNOME) ||
|
||||
ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_GNOME_CLASSIC) ||
|
||||
ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_UNITY) ||
|
||||
ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_BUDGIE) ||
|
||||
ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_NEBIDE)) {
|
||||
themeName = ffSettingsGetGnome("/org/gnome/desktop/interface/gtk-theme", "org.gnome.desktop.interface", nullptr, "gtk-theme", FF_VARIANT_TYPE_STRING).strValue;
|
||||
iconsName = ffSettingsGetGnome("/org/gnome/desktop/interface/icon-theme", "org.gnome.desktop.interface", nullptr, "icon-theme", FF_VARIANT_TYPE_STRING).strValue;
|
||||
fontName = ffSettingsGetGnome("/org/gnome/desktop/interface/font-name", "org.gnome.desktop.interface", nullptr, "font-name", FF_VARIANT_TYPE_STRING).strValue;
|
||||
cursorTheme = ffSettingsGetGnome("/org/gnome/desktop/interface/cursor-theme", "org.gnome.desktop.interface", nullptr, "cursor-theme", FF_VARIANT_TYPE_STRING).strValue;
|
||||
cursorSize = ffSettingsGetGnome("/org/gnome/desktop/interface/cursor-size", "org.gnome.desktop.interface", nullptr, "cursor-size", FF_VARIANT_TYPE_INT).intValue;
|
||||
wallpaper = ffSettingsGetGnome("/org/gnome/desktop/background/picture-uri", "org.gnome.desktop.background", nullptr, "picture-uri", FF_VARIANT_TYPE_STRING).strValue;
|
||||
} else if (
|
||||
ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_ENLIGHTENMENT)) {
|
||||
ffEnlightenmentSettings settings = {};
|
||||
if (ffSettingsGetEnlightenmentProperty(&settings)) {
|
||||
themeName = settings.theme;
|
||||
iconsName = settings.icon_theme;
|
||||
fontName = settings.font;
|
||||
cursorTheme = settings.use_e_cursor ? "Enlightenment" : "Application";
|
||||
cursorSize = settings.cursor_size;
|
||||
wallpaper = settings.desktop_default_background;
|
||||
if (wmde->dePrettyName.length > 0) {
|
||||
if (ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_XFCE4)) {
|
||||
themeName = ffSettingsGetXFConf("xsettings", "/Net/ThemeName", FF_VARIANT_TYPE_STRING).strValue;
|
||||
iconsName = ffSettingsGetXFConf("xsettings", "/Net/IconThemeName", FF_VARIANT_TYPE_STRING).strValue;
|
||||
fontName = ffSettingsGetXFConf("xsettings", "/Gtk/FontName", FF_VARIANT_TYPE_STRING).strValue;
|
||||
cursorTheme = ffSettingsGetXFConf("xsettings", "/Gtk/CursorThemeName", FF_VARIANT_TYPE_STRING).strValue;
|
||||
cursorSize = ffSettingsGetXFConf("xsettings", "/Gtk/CursorThemeSize", FF_VARIANT_TYPE_INT).intValue;
|
||||
wallpaper = ffSettingsGetXFConfFirstMatch("xfce4-desktop", "/backdrop/screen0", FF_VARIANT_TYPE_STRING, nullptr, testXfconfWallpaperPropKey).strValue;
|
||||
} else if (ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_CINNAMON)) {
|
||||
themeName = ffSettingsGetGnome("/org/cinnamon/desktop/interface/gtk-theme", "org.cinnamon.desktop.interface", nullptr, "gtk-theme", FF_VARIANT_TYPE_STRING).strValue;
|
||||
iconsName = ffSettingsGetGnome("/org/cinnamon/desktop/interface/icon-theme", "org.cinnamon.desktop.interface", nullptr, "icon-theme", FF_VARIANT_TYPE_STRING).strValue;
|
||||
fontName = ffSettingsGetGnome("/org/cinnamon/desktop/interface/font-name", "org.cinnamon.desktop.interface", nullptr, "font-name", FF_VARIANT_TYPE_STRING).strValue;
|
||||
cursorTheme = ffSettingsGetGnome("/org/cinnamon/desktop/interface/cursor-theme", "org.cinnamon.desktop.interface", nullptr, "cursor-theme", FF_VARIANT_TYPE_STRING).strValue;
|
||||
cursorSize = ffSettingsGetGnome("/org/cinnamon/desktop/interface/cursor-size", "org.cinnamon.desktop.interface", nullptr, "cursor-size", FF_VARIANT_TYPE_INT).intValue;
|
||||
wallpaper = ffSettingsGetGnome("/org/cinnamon/desktop/background/picture-uri", "org.cinnamon.desktop.background", nullptr, "picture-uri", FF_VARIANT_TYPE_STRING).strValue;
|
||||
} else if (ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_MATE)) {
|
||||
themeName = ffSettingsGetGnome("/org/mate/interface/gtk-theme", "org.mate.interface", nullptr, "gtk-theme", FF_VARIANT_TYPE_STRING).strValue;
|
||||
iconsName = ffSettingsGetGnome("/org/mate/interface/icon-theme", "org.mate.interface", nullptr, "icon-theme", FF_VARIANT_TYPE_STRING).strValue;
|
||||
fontName = ffSettingsGetGnome("/org/mate/interface/font-name", "org.mate.interface", nullptr, "font-name", FF_VARIANT_TYPE_STRING).strValue;
|
||||
cursorTheme = ffSettingsGetGnome("/org/mate/peripherals-mouse/cursor-theme", "org.mate.peripherals-mouse", nullptr, "cursor-theme", FF_VARIANT_TYPE_STRING).strValue;
|
||||
cursorSize = ffSettingsGetGnome("/org/mate/peripherals-mouse/cursor-size", "org.mate.peripherals-mouse", nullptr, "cursor-size", FF_VARIANT_TYPE_INT).intValue;
|
||||
wallpaper = ffSettingsGetGnome("/org/mate/desktop/background", "org.mate.background", nullptr, "picture-filename", FF_VARIANT_TYPE_STRING).strValue;
|
||||
} else if (
|
||||
ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_GNOME) ||
|
||||
ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_GNOME_CLASSIC) ||
|
||||
ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_UNITY) ||
|
||||
ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_BUDGIE) ||
|
||||
ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_NEBIDE)) {
|
||||
themeName = ffSettingsGetGnome("/org/gnome/desktop/interface/gtk-theme", "org.gnome.desktop.interface", nullptr, "gtk-theme", FF_VARIANT_TYPE_STRING).strValue;
|
||||
iconsName = ffSettingsGetGnome("/org/gnome/desktop/interface/icon-theme", "org.gnome.desktop.interface", nullptr, "icon-theme", FF_VARIANT_TYPE_STRING).strValue;
|
||||
fontName = ffSettingsGetGnome("/org/gnome/desktop/interface/font-name", "org.gnome.desktop.interface", nullptr, "font-name", FF_VARIANT_TYPE_STRING).strValue;
|
||||
cursorTheme = ffSettingsGetGnome("/org/gnome/desktop/interface/cursor-theme", "org.gnome.desktop.interface", nullptr, "cursor-theme", FF_VARIANT_TYPE_STRING).strValue;
|
||||
cursorSize = ffSettingsGetGnome("/org/gnome/desktop/interface/cursor-size", "org.gnome.desktop.interface", nullptr, "cursor-size", FF_VARIANT_TYPE_INT).intValue;
|
||||
wallpaper = ffSettingsGetGnome("/org/gnome/desktop/background/picture-uri", "org.gnome.desktop.background", nullptr, "picture-uri", FF_VARIANT_TYPE_STRING).strValue;
|
||||
} else if (
|
||||
ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_ENLIGHTENMENT)) {
|
||||
ffEnlightenmentSettings settings = {};
|
||||
if (ffSettingsGetEnlightenmentProperty(&settings)) {
|
||||
themeName = settings.theme;
|
||||
iconsName = settings.icon_theme;
|
||||
fontName = settings.font;
|
||||
cursorTheme = settings.use_e_cursor ? "Enlightenment" : "Application";
|
||||
cursorSize = settings.cursor_size;
|
||||
wallpaper = settings.desktop_default_background;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Standalone WMs (Hyprland, sway, niri, i3, ...) report no DE and have no settings
|
||||
// daemon, but GTK apps on them still read org.gnome.desktop.interface.
|
||||
// Read via DConf, not GSettings: GSettings synthesises the schema default for keys
|
||||
// the user never set, DConf returns nothing. Runs last; applyGTKSettings() only
|
||||
// fills still-empty fields, so config files always win.
|
||||
themeName = ffSettingsGetDConf("/org/gnome/desktop/interface/gtk-theme", FF_VARIANT_TYPE_STRING).strValue;
|
||||
iconsName = ffSettingsGetDConf("/org/gnome/desktop/interface/icon-theme", FF_VARIANT_TYPE_STRING).strValue;
|
||||
fontName = ffSettingsGetDConf("/org/gnome/desktop/interface/font-name", FF_VARIANT_TYPE_STRING).strValue;
|
||||
cursorTheme = ffSettingsGetDConf("/org/gnome/desktop/interface/cursor-theme", FF_VARIANT_TYPE_STRING).strValue;
|
||||
cursorSize = ffSettingsGetDConf("/org/gnome/desktop/interface/cursor-size", FF_VARIANT_TYPE_INT).intValue;
|
||||
}
|
||||
|
||||
applyGTKSettings(result, themeName, iconsName, fontName, cursorTheme, cursorSize, wallpaper);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
typedef struct FFLMResult {
|
||||
FFstrbuf service;
|
||||
FFstrbuf type;
|
||||
FFstrbuf prettyName;
|
||||
FFstrbuf version;
|
||||
} FFLMResult;
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
#include "lm.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
const char* ffDetectLM(FFLMResult* result) {
|
||||
ffStrbufSetStatic(&result->service, "loginwindow");
|
||||
ffStrbufSetStatic(&result->prettyName, "Login Window");
|
||||
|
||||
NSError* error;
|
||||
NSDictionary* dict = [NSDictionary dictionaryWithContentsOfURL:[NSURL fileURLWithPath:@"/System/Library/CoreServices/loginwindow.app/Contents/Info.plist" isDirectory:NO]
|
||||
error:&error];
|
||||
|
||||
if (dict) {
|
||||
ffStrbufSetS(&result->version, ((NSString*) dict[@"CFBundleShortVersionString"]).UTF8String);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
+361
-26
@@ -1,18 +1,35 @@
|
||||
#include "lm.h"
|
||||
#include "common/io.h"
|
||||
#include "common/mallocHelper.h"
|
||||
#include "common/properties.h"
|
||||
#include "common/dbus.h"
|
||||
#include "common/processing.h"
|
||||
#include "detection/displayserver/displayserver.h"
|
||||
#include "common/strutil.h"
|
||||
|
||||
#ifdef FF_HAVE_DBUS
|
||||
#include "common/dbus.h"
|
||||
#endif
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#define FF_SYSTEMD_SESSIONS_PATH "/run/systemd/sessions/"
|
||||
#define FF_SYSTEMD_USERS_PATH "/run/systemd/users/"
|
||||
#if __FreeBSD__
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/user.h>
|
||||
#elif __OpenBSD__
|
||||
#include <sys/param.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <kvm.h>
|
||||
#elif __sun
|
||||
#include <procfs.h>
|
||||
#elif __NetBSD__
|
||||
#include <sys/types.h>
|
||||
#include <sys/sysctl.h>
|
||||
#endif
|
||||
|
||||
static const char* getGdmVersion(FFstrbuf* version) {
|
||||
const char* error = ffProcessAppendStdOut(version, (char* const[]) { "gdm", "--version", nullptr });
|
||||
const char* error = ffProcessAppendStdOut(version, (char* const[]) { "gdm3", "--version", nullptr });
|
||||
if (error || version->length == 0) {
|
||||
error = ffProcessAppendStdOut(version, (char* const[]) { "gdm3", "--version", nullptr });
|
||||
error = ffProcessAppendStdOut(version, (char* const[]) { "gdm", "--version", nullptr });
|
||||
if (error || version->length == 0) {
|
||||
return "Failed to get GDM version";
|
||||
}
|
||||
@@ -37,6 +54,8 @@ static const char* getSshdVersion(FFstrbuf* version) {
|
||||
|
||||
#ifdef FF_HAVE_ZLIB
|
||||
#include "common/library.h"
|
||||
#include "common/path.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <zlib.h>
|
||||
|
||||
@@ -49,7 +68,7 @@ static const char* getSddmVersion(FFstrbuf* version) {
|
||||
FF_LIBRARY_LOAD_SYMBOL_MESSAGE(zlib, gzrewind)
|
||||
FF_LIBRARY_LOAD_SYMBOL_MESSAGE(zlib, gzclose)
|
||||
|
||||
gzFile file = ffgzopen(FASTFETCH_TARGET_DIR_USR "/share/man/man1/sddm.1.gz", "rb");
|
||||
gzFile file = ffgzopen(FF_PATH_PKG_BASE "/share/man/man1/sddm.1.gz", "rb");
|
||||
if (file == Z_NULL) {
|
||||
return "ffgzopen(\"/usr/share/man/man1/sddm.1.gz\", \"rb\") failed";
|
||||
}
|
||||
@@ -112,35 +131,360 @@ static const char* getLightdmVersion(FFstrbuf* version) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const char* ffDetectLM(FFLMResult* result) {
|
||||
#if __linux__ && !__ANDROID__
|
||||
#ifdef FF_HAVE_DBUS
|
||||
static const char* detectBySystemdDbus(FFLMResult* result) {
|
||||
// This is the standard way to query the systemd session service name,
|
||||
// and requires no $XDG_SESSION_ID being available
|
||||
FF_DBUS_AUTO_DESTROY_DATA FFDBusData dbus = {};
|
||||
if (ffDBusLoadData(DBUS_BUS_SYSTEM, &dbus) != nullptr) {
|
||||
return "Failed to load system DBus";
|
||||
}
|
||||
|
||||
if (!ffDBusGetPropertyString(
|
||||
&dbus,
|
||||
"org.freedesktop.login1",
|
||||
"/org/freedesktop/login1/session/auto",
|
||||
"org.freedesktop.login1.Session",
|
||||
"Service",
|
||||
&result->service)) {
|
||||
return "Failed to get systemd session Service property";
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
#define FF_SYSTEMD_SESSIONS_PATH "/run/systemd/sessions/"
|
||||
#define FF_SYSTEMD_USERS_PATH "/run/systemd/users/"
|
||||
|
||||
static const char* detectBySystemdPrivate(FFLMResult* result) {
|
||||
FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate();
|
||||
|
||||
FF_STRBUF_AUTO_DESTROY sessionId = ffStrbufCreateS(getenv("XDG_SESSION_ID"));
|
||||
if (sessionId.length == 0) {
|
||||
// On some incorrectly configured systems, $XDG_SESSION_ID is not set. Try finding it ourself
|
||||
// WARNING: This is private data. Do not parse
|
||||
ffStrbufSetF(&path, FF_SYSTEMD_USERS_PATH "%d", instance.state.platform.uid);
|
||||
ffStrbufAppendS(&path, FF_SYSTEMD_USERS_PATH);
|
||||
ffStrbufAppendUInt(&path, instance.state.platform.uid);
|
||||
|
||||
// This is actually buggy, and assumes current user is using DE
|
||||
// `sd_pid_get_session` can be a better option, but we need to find a pid to use
|
||||
if (!ffParsePropFileValues(path.chars, 1, (FFpropquery[]) {
|
||||
{ "DISPLAY=", &sessionId },
|
||||
})) {
|
||||
if (!ffParsePropFile(path.chars, "DISPLAY=", &sessionId)) {
|
||||
return "Failed to get $XDG_SESSION_ID";
|
||||
}
|
||||
}
|
||||
|
||||
ffStrbufSetS(&path, FF_SYSTEMD_SESSIONS_PATH);
|
||||
ffStrbufClear(&path);
|
||||
ffStrbufAppendS(&path, FF_SYSTEMD_SESSIONS_PATH);
|
||||
ffStrbufAppend(&path, &sessionId);
|
||||
|
||||
// WARNING: This is private data. Do not parse
|
||||
if (!ffParsePropFileValues(path.chars, 2, (FFpropquery[]) {
|
||||
{ "SERVICE=", &result->service },
|
||||
{ "TYPE=", &result->type },
|
||||
})) {
|
||||
if (!ffParsePropFile(path.chars, "SERVICE=", &result->service)) {
|
||||
return "Failed to parse " FF_SYSTEMD_SESSIONS_PATH "$XDG_SESSION_ID";
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
static const char* testLms(const char* psName) {
|
||||
static const char* A[] = {
|
||||
"atrium",
|
||||
};
|
||||
static const char* C[] = {
|
||||
"cdm",
|
||||
};
|
||||
static const char* E[] = {
|
||||
"entrance",
|
||||
};
|
||||
static const char* G[] = {
|
||||
"gdm",
|
||||
"gdm3",
|
||||
"greetd",
|
||||
};
|
||||
static const char* L[] = {
|
||||
"lemurs",
|
||||
"lightdm",
|
||||
"lxdm",
|
||||
"ly",
|
||||
};
|
||||
static const char* P[] = {
|
||||
"plasmalogin",
|
||||
};
|
||||
static const char* S[] = {
|
||||
"sddm",
|
||||
"slim",
|
||||
};
|
||||
static const char* T[] = {
|
||||
"tbsm",
|
||||
};
|
||||
static const char* X[] = {
|
||||
"xdm",
|
||||
"xenodm",
|
||||
};
|
||||
|
||||
switch (psName[0]) {
|
||||
#define PS_CASE(letter, array) \
|
||||
case letter: \
|
||||
for (uint32_t i = 0; i < ARRAY_SIZE(array); ++i) { \
|
||||
if (ffStrEqualsIgnCase(psName, array[i])) { \
|
||||
return array[i]; \
|
||||
} \
|
||||
} \
|
||||
break;
|
||||
PS_CASE('a', A)
|
||||
PS_CASE('c', C)
|
||||
PS_CASE('e', E)
|
||||
PS_CASE('g', G)
|
||||
PS_CASE('l', L)
|
||||
PS_CASE('p', P)
|
||||
PS_CASE('s', S)
|
||||
PS_CASE('t', T)
|
||||
PS_CASE('x', X)
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const char* detectByProcesses(FFLMResult* result) {
|
||||
#if __FreeBSD__
|
||||
#ifdef __DragonFly__
|
||||
#define ki_comm kp_comm
|
||||
#endif
|
||||
|
||||
int request[] = { CTL_KERN, KERN_PROC, KERN_PROC_UID, 0 };
|
||||
size_t length = 0;
|
||||
|
||||
if (sysctl(request, ARRAY_SIZE(request), nullptr, &length, nullptr, 0) != 0) {
|
||||
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_UID}, nullptr) failed";
|
||||
}
|
||||
|
||||
FF_AUTO_FREE struct kinfo_proc* procs = (struct kinfo_proc*) malloc(length);
|
||||
if (sysctl(request, ARRAY_SIZE(request), procs, &length, nullptr, 0) != 0) {
|
||||
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_UID}, procs) failed";
|
||||
}
|
||||
|
||||
length /= sizeof(*procs);
|
||||
|
||||
for (struct kinfo_proc* proc = procs; proc < procs + length; ++proc) {
|
||||
const char* lm = testLms(proc->ki_comm);
|
||||
if (lm) {
|
||||
ffStrbufSetStatic(&result->service, lm);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#elif __OpenBSD__
|
||||
kvm_t* kd = kvm_open(nullptr, nullptr, nullptr, KVM_NO_FILES, nullptr);
|
||||
int count = 0;
|
||||
const struct kinfo_proc* proc = kvm_getprocs(kd, KERN_PROC_UID, 0, sizeof(*proc), &count);
|
||||
if (proc) {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
const char* lm = testLms(proc[i].p_comm);
|
||||
if (lm) {
|
||||
ffStrbufSetStatic(&result->service, lm);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
kvm_close(kd);
|
||||
#elif __sun
|
||||
FF_AUTO_CLOSE_DIR DIR* procdir = opendir("/proc");
|
||||
if (procdir == nullptr) {
|
||||
return "opendir(\"/proc\") failed";
|
||||
}
|
||||
|
||||
FF_STRBUF_AUTO_DESTROY procPath = ffStrbufCreateA(64);
|
||||
ffStrbufAppendS(&procPath, "/proc/");
|
||||
|
||||
uint32_t procPathLength = procPath.length;
|
||||
|
||||
struct dirent* dirent;
|
||||
while ((dirent = readdir(procdir)) != nullptr) {
|
||||
if (!ffCharIsDigit(dirent->d_name[0])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ffStrbufAppendS(&procPath, dirent->d_name);
|
||||
ffStrbufAppendS(&procPath, "/psinfo");
|
||||
psinfo_t proc;
|
||||
if (ffReadFileData(procPath.chars, sizeof(proc), &proc) == sizeof(proc)) {
|
||||
ffStrbufSubstrBefore(&procPath, procPathLength);
|
||||
|
||||
if (proc.pr_uid != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const char* lm = testLms(proc.pr_fname);
|
||||
if (lm) {
|
||||
ffStrbufSetS(&result->service, lm);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#elif __linux__ || __GNU__
|
||||
FF_AUTO_CLOSE_DIR DIR* procdir = opendir("/proc");
|
||||
if (procdir == nullptr) {
|
||||
return "opendir(\"/proc\") failed";
|
||||
}
|
||||
|
||||
FF_STRBUF_AUTO_DESTROY procPath = ffStrbufCreateA(64);
|
||||
int procfd = dirfd(procdir);
|
||||
|
||||
struct dirent* dirent;
|
||||
while ((dirent = readdir(procdir)) != nullptr) {
|
||||
// Match only folders starting with a number (the pid folders)
|
||||
if (dirent->d_type != DT_DIR || !ffCharIsDigit(dirent->d_name[0])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ffStrbufSetS(&procPath, dirent->d_name);
|
||||
uint32_t procFolderPathLength = procPath.length;
|
||||
|
||||
// Don't check for processes not owned by root (login managers run as root).
|
||||
char loginuid[32];
|
||||
ffStrbufAppendS(&procPath, "/loginuid");
|
||||
ssize_t bytesRead = ffReadFileDataRelative(procfd, procPath.chars, sizeof(loginuid) - 1, loginuid);
|
||||
if (bytesRead <= 0) {
|
||||
continue;
|
||||
}
|
||||
loginuid[bytesRead] = '\0';
|
||||
|
||||
if (strtol(loginuid, nullptr, 10) != (long) (uint32_t) -1 /* no loginuid */) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ffStrbufSubstrBefore(&procPath, procFolderPathLength);
|
||||
|
||||
ffStrbufAppendS(&procPath, "/comm");
|
||||
char comm[256];
|
||||
bytesRead = ffReadFileDataRelative(procfd, procPath.chars, sizeof(comm) - 1, comm);
|
||||
if (bytesRead <= 0) {
|
||||
continue;
|
||||
}
|
||||
if (comm[bytesRead - 1] == '\n') {
|
||||
--bytesRead;
|
||||
}
|
||||
comm[bytesRead] = '\0';
|
||||
|
||||
const char* lm = testLms(comm);
|
||||
if (lm) {
|
||||
ffStrbufSetS(&result->service, lm);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#elif __NetBSD__
|
||||
int request[] = { CTL_KERN, KERN_PROC2, KERN_PROC_UID, 0, sizeof(struct kinfo_proc2), INT_MAX };
|
||||
|
||||
size_t size = 0;
|
||||
if (sysctl(request, ARRAY_SIZE(request), nullptr, &size, nullptr, 0) != 0) {
|
||||
return "sysctl(KERN_PROC_UID, nullptr) failed";
|
||||
}
|
||||
|
||||
FF_AUTO_FREE struct kinfo_proc2* procs = malloc(size);
|
||||
|
||||
if (sysctl(request, ARRAY_SIZE(request), procs, &size, nullptr, 0) != 0) {
|
||||
return "sysctl(KERN_PROC_UID, procs) failed";
|
||||
}
|
||||
|
||||
for (struct kinfo_proc2* proc = procs; proc < procs + (size / sizeof(struct kinfo_proc2)); proc++) {
|
||||
const char* lm = testLms(proc->p_comm);
|
||||
if (lm) {
|
||||
ffStrbufSetStatic(&result->service, lm);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (result->service.length == 0) {
|
||||
return "Failed to detect login manager by processes";
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const char* ffDetectLM(FFLMResult* result) {
|
||||
const char* error = "";
|
||||
#if __linux__
|
||||
#ifdef FF_HAVE_DBUS
|
||||
error = detectBySystemdDbus(result);
|
||||
#endif
|
||||
if (error != nullptr) {
|
||||
error = detectBySystemdPrivate(result);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (error != nullptr) {
|
||||
error = detectByProcesses(result);
|
||||
}
|
||||
|
||||
if (error != nullptr) {
|
||||
return error;
|
||||
}
|
||||
|
||||
switch (result->service.chars[0]) {
|
||||
case 'a':
|
||||
if (ffStrEqualsIgnCase(result->service.chars, "atrium")) {
|
||||
ffStrbufSetStatic(&result->prettyName, "Atrium");
|
||||
}
|
||||
break;
|
||||
case 'c':
|
||||
if (ffStrEqualsIgnCase(result->service.chars, "cdm")) {
|
||||
ffStrbufSetStatic(&result->prettyName, "CDM");
|
||||
}
|
||||
break;
|
||||
case 'e':
|
||||
if (ffStrEqualsIgnCase(result->service.chars, "entrance")) {
|
||||
ffStrbufSetStatic(&result->prettyName, "Entrance");
|
||||
}
|
||||
break;
|
||||
case 'g':
|
||||
if (ffStrbufStartsWithIgnCaseS(&result->service, "gdm")) {
|
||||
ffStrbufSetStatic(&result->prettyName, "GDM");
|
||||
} else if (ffStrEqualsIgnCase(result->service.chars, "greetd")) {
|
||||
ffStrbufSetStatic(&result->prettyName, "greetd");
|
||||
}
|
||||
break;
|
||||
case 'l':
|
||||
if (ffStrEqualsIgnCase(result->service.chars, "lemurs")) {
|
||||
ffStrbufSetStatic(&result->prettyName, "Lemurs");
|
||||
} else if (ffStrEqualsIgnCase(result->service.chars, "lightdm")) {
|
||||
ffStrbufSetStatic(&result->prettyName, "LightDM");
|
||||
} else if (ffStrEqualsIgnCase(result->service.chars, "lxdm")) {
|
||||
ffStrbufSetStatic(&result->prettyName, "LXDM");
|
||||
} else if (ffStrEqualsIgnCase(result->service.chars, "ly")) {
|
||||
ffStrbufSetStatic(&result->prettyName, "Ly");
|
||||
}
|
||||
break;
|
||||
case 'p':
|
||||
if (ffStrEqualsIgnCase(result->service.chars, "plasmalogin")) {
|
||||
ffStrbufSetStatic(&result->prettyName, "Plasma Login");
|
||||
}
|
||||
break;
|
||||
case 's':
|
||||
if (ffStrEqualsIgnCase(result->service.chars, "sddm")) {
|
||||
ffStrbufSetStatic(&result->prettyName, "SDDM");
|
||||
} else if (ffStrEqualsIgnCase(result->service.chars, "slim")) {
|
||||
ffStrbufSetStatic(&result->prettyName, "SLiM");
|
||||
}
|
||||
break;
|
||||
case 't':
|
||||
if (ffStrEqualsIgnCase(result->service.chars, "tbsm")) {
|
||||
ffStrbufSetStatic(&result->prettyName, "TBSM");
|
||||
}
|
||||
break;
|
||||
case 'x':
|
||||
if (ffStrEqualsIgnCase(result->service.chars, "xdm")) {
|
||||
ffStrbufSetStatic(&result->prettyName, "XDM");
|
||||
} else if (ffStrEqualsIgnCase(result->service.chars, "xenodm")) {
|
||||
ffStrbufSetStatic(&result->prettyName, "Xenodm");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (result->prettyName.length == 0) {
|
||||
ffStrbufSet(&result->prettyName, &result->service);
|
||||
}
|
||||
|
||||
if (instance.config.general.detectVersion) {
|
||||
if (ffStrbufStartsWithS(&result->service, "gdm")) {
|
||||
getGdmVersion(&result->version);
|
||||
@@ -155,14 +499,5 @@ const char* ffDetectLM(FFLMResult* result) {
|
||||
}
|
||||
}
|
||||
|
||||
// Correct char cases
|
||||
if (ffStrbufIgnCaseEqualS(&result->type, FF_WM_PROTOCOL_WAYLAND)) {
|
||||
ffStrbufSetS(&result->type, FF_WM_PROTOCOL_WAYLAND);
|
||||
} else if (ffStrbufIgnCaseEqualS(&result->type, FF_WM_PROTOCOL_X11)) {
|
||||
ffStrbufSetS(&result->type, FF_WM_PROTOCOL_X11);
|
||||
} else if (ffStrbufIgnCaseEqualS(&result->type, FF_WM_PROTOCOL_TTY)) {
|
||||
ffStrbufSetS(&result->type, FF_WM_PROTOCOL_TTY);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
#include "lm.h"
|
||||
|
||||
#include "common/windows/version.h"
|
||||
#include "common/windows/nt.h"
|
||||
|
||||
#include <wchar.h>
|
||||
|
||||
const char* ffDetectLM(FFLMResult* result) {
|
||||
ffStrbufSetStatic(&result->service, "LogonUI");
|
||||
ffStrbufSetStatic(&result->prettyName, "Logon User Interface");
|
||||
|
||||
if (instance.config.general.detectVersion) {
|
||||
wchar_t exePath[MAX_PATH];
|
||||
_snwprintf(exePath, ARRAY_SIZE(exePath), L"%ls\\system32\\%ls", (const wchar_t*) SharedUserData->NtSystemRoot, L"LogonUI.exe");
|
||||
ffGetFileVersion(exePath, nullptr, &result->version);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1,19 +1,26 @@
|
||||
#include "memory.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include "common/windows/nt.h"
|
||||
|
||||
const char* ffDetectMemory(FFMemoryResult* ram) {
|
||||
MEMORYSTATUSEX statex = {
|
||||
.dwLength = sizeof(statex),
|
||||
};
|
||||
// GlobalMemoryStatusEx() internally uses
|
||||
// NtQuerySystemInformation(SystemBasicPerformanceInformation) in Win 7, and
|
||||
// NtQuerySystemInformation(SystemMemoryUsageInformation) in Win 10
|
||||
if (!GlobalMemoryStatusEx(&statex)) {
|
||||
return "GlobalMemoryStatusEx() failed";
|
||||
}
|
||||
// Note: GlobalMemoryStatusEx() internally uses SystemMemoryUsageInformation in Win 10
|
||||
|
||||
ram->bytesTotal = statex.ullTotalPhys;
|
||||
ram->bytesUsed = statex.ullTotalPhys - statex.ullAvailPhys;
|
||||
SYSTEM_BASIC_PERFORMANCE_INFORMATION sbpi;
|
||||
if (!NT_SUCCESS(NtQuerySystemInformation(SystemBasicPerformanceInformation, &sbpi, sizeof(sbpi), NULL)))
|
||||
return "Failed to query memory information";
|
||||
|
||||
uint64_t phyPages =
|
||||
#if _WIN64
|
||||
SharedUserData->FullNumberOfPhysicalPages;
|
||||
if (__builtin_expect(phyPages == 0, false)) { // Not supported on Windows 8.1
|
||||
phyPages = SharedUserData->NumberOfPhysicalPages; // ULONG, maximum 16 TB
|
||||
}
|
||||
#else
|
||||
SharedUserData->NumberOfPhysicalPages;
|
||||
#endif
|
||||
|
||||
uint64_t pageSize = instance.state.platform.sysinfo.pageSize;
|
||||
ram->bytesTotal = phyPages * pageSize;
|
||||
ram->bytesUsed = (phyPages - sbpi.AvailablePages) * pageSize;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -70,8 +70,7 @@ const char* ffDetectNetIO(FFlist* result, FFNetIOOptions* options) {
|
||||
uint64_t* prevValue = (uint64_t*) ((uint8_t*) icPrev + off);
|
||||
uint64_t* currValue = (uint64_t*) ((uint8_t*) icCurr + off);
|
||||
uint64_t temp = *currValue;
|
||||
*currValue -= *prevValue;
|
||||
*currValue /= (time2 - time1) / 1000 /* seconds */;
|
||||
*currValue = (*currValue - *prevValue) * 1000 / (time2 - time1); // Calculate per second
|
||||
*prevValue = temp;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,6 +122,7 @@ const char* ffDetectPhysicalDisk(FFlist* result, FFPhysicalDiskOptions* options)
|
||||
}
|
||||
#else
|
||||
const char* ffDetectPhysicalDisk(FFlist* result, FFPhysicalDiskOptions* options) {
|
||||
FF_UNUSED(result, options);
|
||||
return "Fastfetch was compiled without libgeom support";
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "fastfetch.h"
|
||||
#include "modules/processes/option.h"
|
||||
|
||||
const char* ffDetectProcesses(uint32_t* result);
|
||||
typedef struct FFProcessesResult
|
||||
{
|
||||
uint32_t processes;
|
||||
uint32_t threads;
|
||||
} FFProcessesResult;
|
||||
|
||||
const char* ffDetectProcesses(const FFProcessesOptions* options, FFProcessesResult* result);
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
#include "processes.h"
|
||||
#include "common/mallocHelper.h"
|
||||
|
||||
#include <sys/sysctl.h>
|
||||
#import <libproc.h>
|
||||
|
||||
const char* ffDetectProcesses(const FFProcessesOptions* options, FFProcessesResult* result) {
|
||||
int request[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL };
|
||||
size_t length;
|
||||
|
||||
if (sysctl(request, ARRAY_SIZE(request), nullptr, &length, nullptr, 0) != 0) {
|
||||
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_ALL, nullptr}) failed";
|
||||
}
|
||||
|
||||
// The process table may change between the two sysctl calls; retry with a larger buffer.
|
||||
length += length / 8 + sizeof(struct kinfo_proc);
|
||||
FF_AUTO_FREE struct kinfo_proc* processes = malloc(length);
|
||||
|
||||
if (sysctl(request, ARRAY_SIZE(request), processes, &length, nullptr, 0) != 0) {
|
||||
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_ALL, processes}) failed";
|
||||
}
|
||||
|
||||
uint32_t count = (uint32_t) (length / sizeof(struct kinfo_proc));
|
||||
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
const struct kinfo_proc* proc = &processes[i];
|
||||
if (!options->countKprocs && (proc->kp_proc.p_flag & P_SYSTEM)) {
|
||||
continue;
|
||||
}
|
||||
result->processes++;
|
||||
|
||||
pid_t pid = proc->kp_proc.p_pid;
|
||||
struct proc_taskinfo taskInfo;
|
||||
if (proc_pidinfo(pid, PROC_PIDTASKINFO, 0, &taskInfo, sizeof(taskInfo)) != sizeof(taskInfo)) {
|
||||
continue;
|
||||
}
|
||||
result->threads += taskInfo.pti_threadnum;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1,23 +1,35 @@
|
||||
#include "processes.h"
|
||||
#include "common/mallocHelper.h"
|
||||
|
||||
#include <sys/sysctl.h>
|
||||
#ifdef __FreeBSD__
|
||||
#include <sys/types.h>
|
||||
#include <sys/user.h>
|
||||
#endif
|
||||
#include <sys/types.h>
|
||||
#include <sys/user.h>
|
||||
|
||||
#ifndef KERN_PROC_PROC
|
||||
#define KERN_PROC_PROC KERN_PROC_ALL // Apple
|
||||
#endif
|
||||
|
||||
const char* ffDetectProcesses(uint32_t* result) {
|
||||
const char* ffDetectProcesses(const FFProcessesOptions* options, FFProcessesResult* result) {
|
||||
int request[] = { CTL_KERN, KERN_PROC, KERN_PROC_PROC };
|
||||
size_t length;
|
||||
|
||||
if (sysctl(request, ARRAY_SIZE(request), nullptr, &length, nullptr, 0) != 0) {
|
||||
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_PROC}) failed";
|
||||
}
|
||||
|
||||
*result = (uint32_t) (length / sizeof(struct kinfo_proc));
|
||||
// The process table may change between the two sysctl calls; retry with a larger buffer.
|
||||
length += length / 8 + sizeof(struct kinfo_proc);
|
||||
FF_AUTO_FREE struct kinfo_proc* procs = malloc(length);
|
||||
if (sysctl(request, ARRAY_SIZE(request), procs, &length, nullptr, 0) != 0) {
|
||||
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_PROC}) failed";
|
||||
}
|
||||
|
||||
uint32_t count = (uint32_t) (length / sizeof(struct kinfo_proc));
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
const struct kinfo_proc* proc = &procs[i];
|
||||
|
||||
if (!options->countKprocs && (proc->ki_flag & P_KPROC)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
result->processes++;
|
||||
result->threads += (uint32_t) proc->ki_numthreads;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
#include "processes.h"
|
||||
#include "common/mallocHelper.h"
|
||||
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/user.h>
|
||||
#include <sys/proc.h>
|
||||
|
||||
const char* ffDetectProcesses(const FFProcessesOptions* options, FFProcessesResult* result) {
|
||||
int request[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL | (options->countKprocs ? KERN_PROC_FLAG_LWKT : 0) };
|
||||
size_t length;
|
||||
if (sysctl(request, ARRAY_SIZE(request), nullptr, &length, nullptr, 0) != 0) {
|
||||
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_ALL}) failed";
|
||||
}
|
||||
|
||||
// The process table may change between the two sysctl calls; retry with a larger buffer.
|
||||
length += length / 8 + sizeof(struct kinfo_proc);
|
||||
FF_AUTO_FREE struct kinfo_proc* procs = malloc(length);
|
||||
if (sysctl(request, ARRAY_SIZE(request), procs, &length, nullptr, 0) != 0) {
|
||||
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_ALL}) failed";
|
||||
}
|
||||
|
||||
uint32_t count = (uint32_t) (length / sizeof(struct kinfo_proc));
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
const struct kinfo_proc* proc = &procs[i];
|
||||
|
||||
if (proc->kp_pid == -1) {
|
||||
// Kernel thread, only returned when countKprocs is set via KERN_PROC_FLAG_LWKT
|
||||
result->threads++;
|
||||
continue;
|
||||
}
|
||||
|
||||
result->processes++;
|
||||
result->threads += (uint32_t) proc->kp_nthreads;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#include "processes.h"
|
||||
|
||||
#include <hurd.h>
|
||||
#include <ps.h>
|
||||
|
||||
const char* ffDetectProcesses(const FFProcessesOptions* options, FFProcessesResult* result) {
|
||||
struct ps_context* context = nullptr;
|
||||
if (ps_context_create(getproc(), &context) != 0) {
|
||||
return "ps_context_create(getproc()) failed";
|
||||
}
|
||||
|
||||
const char* error = nullptr;
|
||||
struct proc_stat_list* list = nullptr;
|
||||
if (proc_stat_list_create(context, &list) != 0) {
|
||||
error = "proc_stat_list_create() failed";
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (proc_stat_list_add_all(list, nullptr, nullptr) != 0) {
|
||||
error = "proc_stat_list_add_all() failed";
|
||||
goto done;
|
||||
}
|
||||
|
||||
proc_stat_list_set_flags(list, PSTAT_NUM_THREADS);
|
||||
|
||||
for (uint32_t i = 0; i < list->num_procs; i++) {
|
||||
struct proc_stat* stat = list->proc_stats[i];
|
||||
if (!options->countKprocs && proc_stat_pid(stat) == 2) { // Kernel Task
|
||||
continue;
|
||||
}
|
||||
|
||||
if (proc_stat_has(stat, PSTAT_NUM_THREADS)) {
|
||||
result->threads += proc_stat_num_threads(stat);
|
||||
}
|
||||
}
|
||||
|
||||
result->processes = (uint32_t) list->num_procs;
|
||||
|
||||
done:
|
||||
if (list) {
|
||||
proc_stat_list_free(list);
|
||||
}
|
||||
ps_context_free(context);
|
||||
return error;
|
||||
}
|
||||
@@ -2,13 +2,22 @@
|
||||
|
||||
#include <OS.h>
|
||||
|
||||
const char* ffDetectProcesses(uint32_t* result) {
|
||||
const char* ffDetectProcesses(const FFProcessesOptions* options, FFProcessesResult* result) {
|
||||
system_info info;
|
||||
if (get_system_info(&info) != B_OK) {
|
||||
return "Error getting system info";
|
||||
}
|
||||
|
||||
*result = info.used_teams;
|
||||
result->processes = info.used_teams;
|
||||
result->threads = info.used_threads;
|
||||
|
||||
if (!options->countKprocs) {
|
||||
team_info ti;
|
||||
if (get_team_info(B_SYSTEM_TEAM, &ti) == B_OK) {
|
||||
result->processes--;
|
||||
result->threads -= (uint32_t) ti.thread_count;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -1,27 +1,73 @@
|
||||
#include "processes.h"
|
||||
|
||||
#include "common/io.h"
|
||||
#include "common/memrchr.h"
|
||||
#include "common/strutil.h"
|
||||
|
||||
const char* ffDetectProcesses(uint32_t* result) {
|
||||
#define PF_KTHREAD 0x00200000 // defined in kernel include/linux/sched.h
|
||||
|
||||
const char* ffDetectProcesses(const FFProcessesOptions* options, FFProcessesResult* result) {
|
||||
FF_AUTO_CLOSE_DIR DIR* dir = opendir("/proc");
|
||||
if (dir == nullptr) {
|
||||
return "opendir(\"/proc\") failed";
|
||||
}
|
||||
|
||||
uint32_t num = 0;
|
||||
int procfd = dirfd(dir);
|
||||
|
||||
struct dirent* entry;
|
||||
while ((entry = readdir(dir)) != nullptr) {
|
||||
if (
|
||||
#ifdef _DIRENT_HAVE_D_TYPE
|
||||
(entry->d_type == DT_DIR || entry->d_type == DT_UNKNOWN) &&
|
||||
#endif
|
||||
ffCharIsDigit(entry->d_name[0]))
|
||||
++num;
|
||||
}
|
||||
if ((entry->d_type == DT_DIR || entry->d_type == DT_UNKNOWN) && ffCharIsDigit(entry->d_name[0])) {
|
||||
char statPath[32];
|
||||
strcpy(ffStrCopy(statPath, entry->d_name, sizeof(statPath)), "/stat");
|
||||
|
||||
*result = num;
|
||||
char statBuffer[512];
|
||||
ssize_t statLength = ffReadFileDataRelative(procfd, statPath, sizeof(statBuffer) - 1, statBuffer);
|
||||
if (statLength < 0) {
|
||||
continue;
|
||||
}
|
||||
statBuffer[statLength] = '\0';
|
||||
|
||||
const char* cursor = (const char*) memrchr(statBuffer, ')', (size_t) statLength);
|
||||
if (!cursor) {
|
||||
continue;
|
||||
}
|
||||
cursor += 2; // skip ") "
|
||||
if (__builtin_expect(cursor >= statBuffer + statLength, false)) {
|
||||
goto skip_process;
|
||||
}
|
||||
|
||||
char* p;
|
||||
for (uint32_t field = 3; field <= 20 /*num_threads*/; ++field) {
|
||||
switch (field) {
|
||||
case 9: // flags
|
||||
if ((strtoul(cursor, &p, 10) & PF_KTHREAD) && !options->countKprocs) {
|
||||
goto skip_process;
|
||||
}
|
||||
cursor = p;
|
||||
break;
|
||||
|
||||
case 20: // num_threads
|
||||
result->threads += (uint32_t) strtoul(cursor, &p, 10);
|
||||
cursor = p;
|
||||
break;
|
||||
|
||||
default:
|
||||
while (*cursor != ' ' && __builtin_expect(*cursor != '\0', true)) {
|
||||
++cursor;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
++cursor;
|
||||
if (__builtin_expect(cursor >= statBuffer + statLength, false)) {
|
||||
goto skip_process;
|
||||
}
|
||||
}
|
||||
|
||||
++result->processes;
|
||||
}
|
||||
skip_process:;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,37 @@
|
||||
#include "processes.h"
|
||||
#include "common/mallocHelper.h"
|
||||
|
||||
#include <sys/sysctl.h>
|
||||
|
||||
const char* ffDetectProcesses(uint32_t* result) {
|
||||
int request[] = { CTL_KERN, KERN_PROC2, KERN_PROC_ALL, -1, sizeof(struct kinfo_proc2), 0 };
|
||||
const char* ffDetectProcesses(const FFProcessesOptions* options, FFProcessesResult* result) {
|
||||
int request[] = { CTL_KERN, KERN_PROC2, KERN_PROC_ALL, -1, sizeof(struct kinfo_proc2), INT_MAX };
|
||||
size_t length = 0;
|
||||
|
||||
if (sysctl(request, ARRAY_SIZE(request), nullptr, &length, nullptr, 0) != 0) {
|
||||
return "sysctl({CTL_KERN, KERN_PROC2, KERN_PROC_ALL}) failed";
|
||||
return "sysctl({CTL_KERN, KERN_PROC2, KERN_PROC_ALL, nullptr}) failed";
|
||||
}
|
||||
|
||||
FF_AUTO_FREE struct kinfo_proc2* procs = malloc(length);
|
||||
if (sysctl(request, ARRAY_SIZE(request), procs, &length, nullptr, 0) != 0) {
|
||||
return "sysctl({CTL_KERN, KERN_PROC2, KERN_PROC_ALL, procs}) failed";
|
||||
}
|
||||
|
||||
uint32_t procCount = (uint32_t) (length / sizeof(struct kinfo_proc2));
|
||||
|
||||
for (uint32_t i = 0; i < procCount; ++i) {
|
||||
const struct kinfo_proc2* proc = &procs[i];
|
||||
if (!options->countKprocs && (proc->p_flag & P_SYSTEM)) {
|
||||
continue;
|
||||
}
|
||||
++result->processes;
|
||||
|
||||
int lwpRequest[] = { CTL_KERN, KERN_LWP, procs[i].p_pid, sizeof(struct kinfo_lwp), 0 };
|
||||
size_t lwpLength = 0;
|
||||
|
||||
if (sysctl(lwpRequest, ARRAY_SIZE(lwpRequest), nullptr, &lwpLength, nullptr, 0) == 0) {
|
||||
result->threads += (uint32_t) (lwpLength / sizeof(struct kinfo_lwp));
|
||||
}
|
||||
}
|
||||
|
||||
*result = (uint32_t) (length / sizeof(struct kinfo_proc2));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "processes.h"
|
||||
|
||||
const char* ffDetectProcesses(uint32_t* result) {
|
||||
const char* ffDetectProcesses([[maybe_unused]] FFProcessesResult* result) {
|
||||
return "Not supported on this platform";
|
||||
}
|
||||
|
||||
@@ -1,15 +1,36 @@
|
||||
#include "processes.h"
|
||||
|
||||
#include "common/mallocHelper.h"
|
||||
|
||||
#include <sys/param.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <kvm.h>
|
||||
|
||||
const char* ffDetectProcesses(uint32_t* result) {
|
||||
const char* ffDetectProcesses(const FFProcessesOptions* options, FFProcessesResult* result) {
|
||||
kvm_t* kd = kvm_open(nullptr, nullptr, nullptr, KVM_NO_FILES, nullptr);
|
||||
const void* ret = kvm_getprocs(kd, KERN_PROC_ALL, 0, 1, result);
|
||||
kvm_close(kd);
|
||||
if (!ret) {
|
||||
if (!kd) {
|
||||
return "kvm_open() failed";
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
// KERN_PROC_ALL returns all user-level processes, excluding kernel processes and threads
|
||||
const struct kinfo_proc* procs = kvm_getprocs(kd,
|
||||
(options->countKprocs ? KERN_PROC_KTHREAD : KERN_PROC_ALL) | KERN_PROC_SHOW_THREADS,
|
||||
0, sizeof(struct kinfo_proc), &count);
|
||||
if (!procs) {
|
||||
kvm_close(kd);
|
||||
return "kvm_getprocs() failed";
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; ++i) {
|
||||
const struct kinfo_proc* proc = &procs[i];
|
||||
|
||||
++result->threads;
|
||||
if (proc->p_tid == -1) {
|
||||
++result->processes;
|
||||
}
|
||||
}
|
||||
|
||||
kvm_close(kd);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
#include "processes.h"
|
||||
|
||||
#include "common/io.h"
|
||||
#include "common/strutil.h"
|
||||
|
||||
#include <procfs.h>
|
||||
|
||||
const char* ffDetectProcesses(const FFProcessesOptions* options, FFProcessesResult* result) {
|
||||
FF_AUTO_CLOSE_DIR DIR* dir = opendir("/proc");
|
||||
if (dir == nullptr) {
|
||||
return "opendir(\"/proc\") failed";
|
||||
}
|
||||
|
||||
int procfd = dirfd(dir);
|
||||
|
||||
struct dirent* entry;
|
||||
while ((entry = readdir(dir)) != nullptr) {
|
||||
if (ffCharIsDigit(entry->d_name[0])) {
|
||||
char path[32];
|
||||
char* pidEnd = ffStrCopy(path, entry->d_name, sizeof(path));
|
||||
if (!options->countKprocs) {
|
||||
pstatus_t status;
|
||||
strcpy(pidEnd, "/status");
|
||||
if (ffReadFileDataRelative(procfd, path, sizeof(status), &status) != (ssize_t) sizeof(status) ||
|
||||
status.pr_flags & PR_ISSYS) {
|
||||
continue; // The process may have exited, no permission or is a kernel process
|
||||
}
|
||||
}
|
||||
|
||||
strcpy(pidEnd, "/psinfo");
|
||||
psinfo_t info;
|
||||
if (ffReadFileDataRelative(procfd, path, sizeof(info), &info) == (ssize_t) sizeof(info)) {
|
||||
++result->processes;
|
||||
result->threads += (uint32_t) info.pr_nlwp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <ntstatus.h>
|
||||
#include <winternl.h>
|
||||
|
||||
const char* ffDetectProcesses(uint32_t* result) {
|
||||
const char* ffDetectProcesses(const FFProcessesOptions* options, FFProcessesResult* result) {
|
||||
FF_AUTO_FREE SYSTEM_PROCESS_INFORMATION* pstart = nullptr;
|
||||
|
||||
// Multiple attempts in case processes change while
|
||||
@@ -25,9 +25,15 @@ const char* ffDetectProcesses(uint32_t* result) {
|
||||
}
|
||||
}
|
||||
|
||||
*result = 1; // Init with 1 because we test for ptr->NextEntryOffset
|
||||
for (SYSTEM_PROCESS_INFORMATION* ptr = pstart; ptr->NextEntryOffset; ptr = (SYSTEM_PROCESS_INFORMATION*) ((uint8_t*) ptr + ptr->NextEntryOffset)) {
|
||||
++*result;
|
||||
for (SYSTEM_PROCESS_INFORMATION* ptr = pstart; ; ptr = (SYSTEM_PROCESS_INFORMATION*) ((uint8_t*) ptr + ptr->NextEntryOffset)) {
|
||||
if (!options->countKprocs && ptr->CreateTime.QuadPart == 0) {
|
||||
continue;
|
||||
}
|
||||
++result->processes;
|
||||
result->threads += ptr->NumberOfThreads;
|
||||
if (ptr->NextEntryOffset == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "terminalfont.h"
|
||||
#include "detection/terminalshell/terminalshell.h"
|
||||
#include "common/io.h"
|
||||
#include "common/properties.h"
|
||||
|
||||
#ifdef FF_HAVE_FREETYPE
|
||||
#include "common/library.h"
|
||||
@@ -10,10 +11,17 @@
|
||||
#endif
|
||||
|
||||
#define FF_TERMUX_FONT_PATH FASTFETCH_TARGET_DIR_HOME "/.termux/font.ttf"
|
||||
#define FF_TERMUX_PREF_PATH "/data/data/com.termux/shared_prefs/com.termux_preferences.xml"
|
||||
|
||||
const char* detectTermux(FFTerminalFontResult* terminalFont) {
|
||||
FF_STRBUF_AUTO_DESTROY fontSize = ffStrbufCreate();
|
||||
// SharedPreferences XML: <string name="fontsize">14</string>, in px
|
||||
if (ffParsePropFile(FF_TERMUX_PREF_PATH, "<string name=\"fontsize\">", &fontSize)) {
|
||||
ffStrbufAppendS(&fontSize, "px");
|
||||
}
|
||||
|
||||
if (!ffPathExists(FF_TERMUX_FONT_PATH, FF_PATHTYPE_FILE)) {
|
||||
ffFontInitCopy(&terminalFont->font, "monospace");
|
||||
ffFontInitValues(&terminalFont->font, "monospace", fontSize.length ? fontSize.chars : nullptr);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -39,7 +47,7 @@ const char* detectTermux(FFTerminalFontResult* terminalFont) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
ffFontInitCopy(&terminalFont->font, face->family_name);
|
||||
ffFontInitValues(&terminalFont->font, face->family_name, fontSize.length ? fontSize.chars : nullptr);
|
||||
|
||||
exit:
|
||||
if (face) {
|
||||
@@ -53,7 +61,7 @@ exit:
|
||||
|
||||
#else
|
||||
|
||||
FF_UNUSED(terminalFont);
|
||||
ffFontInitValues(&terminalFont->font, "monospace", fontSize.length ? fontSize.chars : nullptr);
|
||||
return "Fastfetch was built without freetype2 support";
|
||||
|
||||
#endif
|
||||
|
||||
@@ -8,17 +8,8 @@
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdint.h>
|
||||
#ifdef __FreeBSD__
|
||||
#include <paths.h>
|
||||
#ifndef _PATH_LOCALBASE
|
||||
#define _PATH_LOCALBASE "/usr/local"
|
||||
#endif
|
||||
#elif __OpenBSD__
|
||||
#define _PATH_LOCALBASE "/usr/local"
|
||||
#elif __NetBSD__
|
||||
#define _PATH_LOCALBASE "/usr/pkg"
|
||||
#elif _WIN32
|
||||
|
||||
#if _WIN32
|
||||
#include "common/windows/registry.h"
|
||||
#include "common/windows/version.h"
|
||||
#include <windows.h>
|
||||
@@ -194,6 +185,9 @@ static bool extractBusyboxVersion(const char* line, uint32_t len, void* userdata
|
||||
|
||||
static bool getShellVersionAsh(FFstrbuf* exe, FFstrbuf* version) {
|
||||
ffBinaryExtractStrings(exe->chars, extractBusyboxVersion, version, (uint32_t) strlen("BusyBox v0.0.0"));
|
||||
if (version->length > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const char* error = ffStrbufEndsWithS(exe, "busybox")
|
||||
? ffProcessAppendStdErr(version, (char* const[]) { exe->chars, "ash", "--help", nullptr })
|
||||
@@ -660,7 +654,7 @@ static bool getTerminalVersionKitty(FFstrbuf* exe, FFstrbuf* version) {
|
||||
ffReadFileData(FASTFETCH_TARGET_DIR_USR "/lib64/kitty/kitty/constants.py", ARRAY_SIZE(buffer) - 1, buffer) ||
|
||||
ffReadFileData(FASTFETCH_TARGET_DIR_USR "/lib/kitty/kitty/constants.py", ARRAY_SIZE(buffer) - 1, buffer)
|
||||
#else
|
||||
ffReadFileData(_PATH_LOCALBASE "/share/kitty/kitty/constants.py", ARRAY_SIZE(buffer) - 1, buffer)
|
||||
ffReadFileData(FF_PATH_PKG_BASE "/share/kitty/kitty/constants.py", ARRAY_SIZE(buffer) - 1, buffer)
|
||||
#endif
|
||||
) {
|
||||
// Starts from version 0.17.0
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
#include "top.h"
|
||||
|
||||
#include "common/time.h"
|
||||
|
||||
static FFlist first;
|
||||
static double startTick;
|
||||
static FFTopTypes preparedShowTypes = FF_TOP_TYPE_CPU | FF_TOP_TYPE_MEMORY | FF_TOP_TYPE_DISK;
|
||||
|
||||
void ffPrepareTopProcesses(FFTopTypes showTypes) {
|
||||
if ((showTypes & (FF_TOP_TYPE_CPU | FF_TOP_TYPE_DISK)) == 0) {
|
||||
return; // Memory usage is instantaneous; no baseline snapshot is needed
|
||||
}
|
||||
|
||||
if (startTick != 0 && preparedShowTypes == showTypes) {
|
||||
return; // Already prepared
|
||||
}
|
||||
|
||||
if (startTick != 0) {
|
||||
// The set of requested types changed; discard the stale baseline
|
||||
FF_LIST_FOR_EACH (FFTopProcessSnapshot, item, first) {
|
||||
ffStrbufDestroy(&item->name);
|
||||
}
|
||||
ffListDestroy(&first);
|
||||
}
|
||||
|
||||
ffListInit(&first);
|
||||
startTick = ffTimeGetTick();
|
||||
preparedShowTypes = showTypes;
|
||||
ffTopGetProcessSnapshot(&first, showTypes);
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
static int compareCpuResults(const FFTopProcessResult* a, const FFTopProcessResult* b) {
|
||||
if (a->cpuPercent < b->cpuPercent) return 1;
|
||||
if (a->cpuPercent > b->cpuPercent) return -1;
|
||||
return (int) (a->pid - b->pid);
|
||||
}
|
||||
|
||||
static int compareMemoryResults(const FFTopProcessResult* a, const FFTopProcessResult* b) {
|
||||
if (a->memBytes < b->memBytes) return 1;
|
||||
if (a->memBytes > b->memBytes) return -1;
|
||||
return (int) (a->pid - b->pid);
|
||||
}
|
||||
|
||||
static int compareDiskReadResults(const FFTopProcessResult* a, const FFTopProcessResult* b) {
|
||||
if (a->bytesRead < b->bytesRead) return 1;
|
||||
if (a->bytesRead > b->bytesRead) return -1;
|
||||
return (int) (a->pid - b->pid);
|
||||
}
|
||||
|
||||
static int compareDiskWriteResults(const FFTopProcessResult* a, const FFTopProcessResult* b) {
|
||||
if (a->bytesWritten < b->bytesWritten) return 1;
|
||||
if (a->bytesWritten > b->bytesWritten) return -1;
|
||||
return (int) (a->pid - b->pid);
|
||||
}
|
||||
// clang-format on
|
||||
|
||||
const char* ffDetectTopProcesses(FFTopOptions* options, FFlist* result) {
|
||||
ffListClear(result);
|
||||
if (options->nProcesses == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Memory usage is instantaneous; when neither CPU time nor disk IO counters
|
||||
// are requested, a single snapshot suffices and no sampling wait is needed.
|
||||
const bool sampleOnce = (options->showTypes & (FF_TOP_TYPE_CPU | FF_TOP_TYPE_DISK)) == 0;
|
||||
|
||||
if (sampleOnce) {
|
||||
FF_LIST_AUTO_DESTROY snapshots = ffListCreate();
|
||||
const char* error = ffTopGetProcessSnapshot(&snapshots, options->showTypes);
|
||||
if (error) {
|
||||
FF_LIST_FOR_EACH (FFTopProcessSnapshot, item, snapshots) {
|
||||
ffStrbufDestroy(&item->name);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
if (snapshots.length == 0) {
|
||||
return "No processes found";
|
||||
}
|
||||
|
||||
FF_LIST_FOR_EACH (FFTopProcessSnapshot, snap, snapshots) {
|
||||
FFTopProcessResult* item = FF_LIST_ADD(FFTopProcessResult, *result);
|
||||
item->pid = snap->pid;
|
||||
item->memBytes = snap->memBytes;
|
||||
item->bytesRead = 0;
|
||||
item->bytesWritten = 0;
|
||||
item->cpuPercent = 0;
|
||||
item->startTime = snap->startTime;
|
||||
ffStrbufInitMove(&item->name, &snap->name);
|
||||
}
|
||||
} else {
|
||||
if (startTick == 0 || preparedShowTypes != options->showTypes) {
|
||||
ffPrepareTopProcesses(options->showTypes);
|
||||
}
|
||||
|
||||
double elapsedTime = ffTimeGetTick() - startTick;
|
||||
if (elapsedTime < (double) options->waitTime) {
|
||||
ffTimeSleep(options->waitTime - (uint32_t) elapsedTime);
|
||||
}
|
||||
|
||||
if (first.length == 0) {
|
||||
return "No processes found";
|
||||
}
|
||||
|
||||
ffListReserve(result, sizeof(FFTopProcessResult), first.length < options->nProcesses ? first.length : options->nProcesses);
|
||||
|
||||
FF_LIST_AUTO_DESTROY second = ffListCreateA(sizeof(FFTopProcessSnapshot), first.length);
|
||||
const char* error = ffTopGetProcessSnapshot(&second, options->showTypes);
|
||||
const double elapsed = ffTimeGetTick() - startTick;
|
||||
|
||||
if (error || elapsed <= 0) {
|
||||
FF_LIST_FOR_EACH (FFTopProcessSnapshot, item, second) {
|
||||
ffStrbufDestroy(&item->name);
|
||||
}
|
||||
return error ?: "Invalid process sampling interval";
|
||||
}
|
||||
|
||||
FF_LIST_FOR_EACH (FFTopProcessSnapshot, oldItem, first) {
|
||||
FFTopProcessSnapshot* newItem = nullptr;
|
||||
FF_LIST_FOR_EACH (FFTopProcessSnapshot, item, second) {
|
||||
if (item->pid == oldItem->pid) {
|
||||
newItem = item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!newItem || newItem->startTime != oldItem->startTime || newItem->cpuTime < oldItem->cpuTime || newItem->bytesRead < oldItem->bytesRead || newItem->bytesWritten < oldItem->bytesWritten) {
|
||||
ffStrbufDestroy(&oldItem->name);
|
||||
continue;
|
||||
}
|
||||
|
||||
FFTopProcessResult* item = FF_LIST_ADD(FFTopProcessResult, *result);
|
||||
item->pid = newItem->pid;
|
||||
item->memBytes = newItem->memBytes;
|
||||
item->bytesRead = (newItem->bytesRead - oldItem->bytesRead) * 1000u / (uint64_t) elapsed;
|
||||
item->bytesWritten = (newItem->bytesWritten - oldItem->bytesWritten) * 1000u / (uint64_t) elapsed;
|
||||
item->cpuPercent = (double) (newItem->cpuTime - oldItem->cpuTime) / elapsed * 100.0;
|
||||
item->startTime = newItem->startTime;
|
||||
ffStrbufInitMove(&item->name, &oldItem->name);
|
||||
}
|
||||
|
||||
// Reuse `second` as the baseline of the next call
|
||||
ffListDestroy(&first);
|
||||
ffListInitMove(&first, &second);
|
||||
startTick = ffTimeGetTick();
|
||||
}
|
||||
|
||||
const void* compare = options->sort == FF_TOP_TYPE_DISK_WRITE ? (void*) compareDiskWriteResults
|
||||
: options->sort == FF_TOP_TYPE_DISK_READ ? (void*) compareDiskReadResults
|
||||
: options->sort == FF_TOP_TYPE_MEMORY ? (void*) compareMemoryResults
|
||||
: (void*) compareCpuResults;
|
||||
ffListSort(result, sizeof(FFTopProcessResult), (void*) compare);
|
||||
if (result->length > options->nProcesses) {
|
||||
FFTopProcessResult* items = (FFTopProcessResult*) result->data;
|
||||
for (uint32_t i = options->nProcesses; i < result->length; ++i) {
|
||||
ffStrbufDestroy(&items[i].name);
|
||||
}
|
||||
result->length = options->nProcesses;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include "fastfetch.h"
|
||||
#include "modules/top/option.h"
|
||||
|
||||
typedef struct FFTopProcessResult {
|
||||
FFstrbuf name;
|
||||
double cpuPercent;
|
||||
uint64_t memBytes;
|
||||
uint64_t bytesRead;
|
||||
uint64_t bytesWritten;
|
||||
uint64_t startTime;
|
||||
uint32_t pid;
|
||||
} FFTopProcessResult;
|
||||
|
||||
typedef struct FFTopProcessSnapshot {
|
||||
FFstrbuf name; // comm
|
||||
uint64_t cpuTime; // user_time + system_time
|
||||
uint64_t memBytes; // RSS
|
||||
uint64_t bytesRead; // Physical storage read
|
||||
uint64_t bytesWritten; // Physical storage written
|
||||
uint64_t startTime; // Differs between platforms
|
||||
uint32_t pid;
|
||||
} FFTopProcessSnapshot;
|
||||
|
||||
const char* ffDetectTopProcesses(FFTopOptions* options, FFlist* result);
|
||||
const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes showTypes);
|
||||
@@ -0,0 +1,60 @@
|
||||
#include "top.h"
|
||||
#include "common/mallocHelper.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <libproc.h>
|
||||
|
||||
const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes) {
|
||||
int request[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL };
|
||||
size_t length;
|
||||
|
||||
if (sysctl(request, ARRAY_SIZE(request), nullptr, &length, nullptr, 0) != 0) {
|
||||
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_ALL, nullptr}) failed";
|
||||
}
|
||||
|
||||
// The process table may change between the two sysctl calls; retry with a larger buffer.
|
||||
length += length / 8 + sizeof(struct kinfo_proc);
|
||||
FF_AUTO_FREE struct kinfo_proc* processes = malloc(length);
|
||||
|
||||
if (sysctl(request, ARRAY_SIZE(request), processes, &length, nullptr, 0) != 0) {
|
||||
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_ALL, processes}) failed";
|
||||
}
|
||||
|
||||
uint32_t count = (uint32_t) (length / sizeof(struct kinfo_proc));
|
||||
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
const struct kinfo_proc* proc = &processes[i];
|
||||
if (proc->kp_proc.p_flag & P_SYSTEM) {
|
||||
continue;
|
||||
}
|
||||
pid_t pid = proc->kp_proc.p_pid;
|
||||
|
||||
struct rusage_info_v2 rusage;
|
||||
if (proc_pid_rusage(pid, RUSAGE_INFO_V2, (rusage_info_t*) &rusage) != 0) {
|
||||
continue; // The process may have exited
|
||||
}
|
||||
|
||||
FFTopProcessSnapshot* item = FF_LIST_ADD(FFTopProcessSnapshot, *snapshots);
|
||||
ffStrbufInitS(&item->name, proc->kp_proc.p_comm);
|
||||
item->pid = (uint32_t) pid;
|
||||
// Note: Do NOT use proc->kp_proc.p_pctcpu for CPU usage. p_pctcpu is a
|
||||
// decaying average (fixpt_t with FSCALE=2048) updated roughly once per
|
||||
// second by the kernel. It is heavily smoothed, lags short bursts, has
|
||||
// low resolution, and its multicore scaling (>100%) is inconsistent
|
||||
// across XNU versions. It also breaks the unified model where
|
||||
// FFTopProcessSnapshot.cpuTime is cumulative time and top.c computes
|
||||
// (new - old) / elapsed * 100 for all platforms. proc_pid_rusage with
|
||||
// RUSAGE_INFO_V2 provides precise cumulative ri_user_time +
|
||||
// ri_system_time in nanoseconds and is the modern recommended API on
|
||||
// Darwin, consistent with Linux/BSD differential sampling and accurate
|
||||
// for the short waitTime interval (e.g. 100ms).
|
||||
item->cpuTime = (rusage.ri_user_time + rusage.ri_system_time) / 1000000u; // ns -> ms
|
||||
item->memBytes = rusage.ri_resident_size;
|
||||
item->bytesRead = rusage.ri_diskio_bytesread;
|
||||
item->bytesWritten = rusage.ri_diskio_byteswritten;
|
||||
item->startTime = rusage.ri_proc_start_abstime;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
#include "top.h"
|
||||
#include "common/mallocHelper.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/user.h>
|
||||
|
||||
const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes) {
|
||||
int request[] = { CTL_KERN, KERN_PROC, KERN_PROC_PROC };
|
||||
size_t length;
|
||||
|
||||
if (sysctl(request, ARRAY_SIZE(request), nullptr, &length, nullptr, 0) != 0) {
|
||||
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_PROC}) failed";
|
||||
}
|
||||
|
||||
// The process table may change between the two sysctl calls; retry with a larger buffer.
|
||||
length += length / 8 + sizeof(struct kinfo_proc);
|
||||
FF_AUTO_FREE struct kinfo_proc* processes = malloc(length);
|
||||
if (sysctl(request, ARRAY_SIZE(request), processes, &length, nullptr, 0) != 0) {
|
||||
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_PROC}) failed";
|
||||
}
|
||||
uint32_t count = (uint32_t) (length / sizeof(struct kinfo_proc));
|
||||
|
||||
uint32_t pageSize = instance.state.platform.sysinfo.pageSize;
|
||||
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
const struct kinfo_proc* proc = &processes[i];
|
||||
|
||||
if (proc->ki_flag & P_KPROC) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t pid = (uint32_t) proc->ki_pid;
|
||||
|
||||
FFTopProcessSnapshot* item = FF_LIST_ADD(FFTopProcessSnapshot, *snapshots);
|
||||
|
||||
ffStrbufInitS(&item->name, proc->ki_comm);
|
||||
item->pid = pid;
|
||||
|
||||
uint64_t userMs = (uint64_t) proc->ki_rusage.ru_utime.tv_sec * 1000 +
|
||||
(uint64_t) proc->ki_rusage.ru_utime.tv_usec / 1000;
|
||||
uint64_t sysMs = (uint64_t) proc->ki_rusage.ru_stime.tv_sec * 1000 +
|
||||
(uint64_t) proc->ki_rusage.ru_stime.tv_usec / 1000;
|
||||
item->cpuTime = userMs + sysMs;
|
||||
|
||||
item->memBytes = (uint64_t) proc->ki_rssize * (uint64_t) pageSize;
|
||||
|
||||
item->startTime = (uint64_t) proc->ki_start.tv_sec * 1000 +
|
||||
(uint64_t) proc->ki_start.tv_usec / 1000;
|
||||
|
||||
item->bytesRead = (uint64_t) proc->ki_rusage.ru_inblock * DEV_BSIZE;
|
||||
item->bytesWritten = (uint64_t) proc->ki_rusage.ru_oublock * DEV_BSIZE;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
#include "top.h"
|
||||
#include "common/mallocHelper.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/user.h>
|
||||
|
||||
const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes) {
|
||||
int request[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL };
|
||||
size_t length;
|
||||
|
||||
if (sysctl(request, ARRAY_SIZE(request), nullptr, &length, nullptr, 0) != 0) {
|
||||
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_ALL, nullptr}) failed";
|
||||
}
|
||||
|
||||
// The process table may change between the two sysctl calls; retry with a larger buffer.
|
||||
length += length / 8 + sizeof(struct kinfo_proc);
|
||||
|
||||
FF_AUTO_FREE struct kinfo_proc* processes = malloc(length);
|
||||
if (sysctl(request, ARRAY_SIZE(request), processes, &length, nullptr, 0) != 0) {
|
||||
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_ALL, processes}) failed";
|
||||
}
|
||||
uint32_t count = (uint32_t) (length / sizeof(struct kinfo_proc));
|
||||
|
||||
uint32_t pageSize = instance.state.platform.sysinfo.pageSize;
|
||||
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
const struct kinfo_proc* proc = &processes[i];
|
||||
|
||||
// KERN_PROC_ALL won't return kernel threads
|
||||
if (proc->kp_stat == SZOMB) { // Ignore zombie processes
|
||||
continue;
|
||||
}
|
||||
|
||||
FFTopProcessSnapshot* item = FF_LIST_ADD(FFTopProcessSnapshot, *snapshots);
|
||||
|
||||
ffStrbufInitS(&item->name, proc->kp_comm);
|
||||
item->pid = (uint32_t) proc->kp_pid;
|
||||
|
||||
// kp_ru only accounts for exited lwps; the cpu times of live lwps are
|
||||
// kept in microseconds in kp_lwp.kl_uticks/kl_sticks (see calcru(9)).
|
||||
uint64_t exitedMs = (uint64_t) (proc->kp_ru.ru_utime.tv_sec + proc->kp_ru.ru_stime.tv_sec) * 1000 +
|
||||
(uint64_t) (proc->kp_ru.ru_utime.tv_usec + proc->kp_ru.ru_stime.tv_usec) / 1000;
|
||||
uint64_t liveMs = (proc->kp_lwp.kl_uticks + proc->kp_lwp.kl_sticks) / 1000;
|
||||
item->cpuTime = exitedMs + liveMs;
|
||||
|
||||
item->memBytes = (uint64_t) proc->kp_vm_rssize * pageSize;
|
||||
|
||||
item->startTime = (uint64_t) proc->kp_start.tv_sec * 1000 +
|
||||
(uint64_t) proc->kp_start.tv_usec / 1000;
|
||||
|
||||
item->bytesRead = (uint64_t) (proc->kp_ru.ru_inblock + proc->kp_lwp.kl_ru.ru_inblock) * DEV_BSIZE;
|
||||
item->bytesWritten = (uint64_t) (proc->kp_ru.ru_oublock + proc->kp_lwp.kl_ru.ru_oublock) * DEV_BSIZE;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
#include "top.h"
|
||||
|
||||
#include <hurd.h>
|
||||
#include <string.h>
|
||||
#include <ps.h>
|
||||
|
||||
const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes) {
|
||||
struct ps_context* context = nullptr;
|
||||
if (ps_context_create(getproc(), &context) != 0) {
|
||||
return "ps_context_create(getproc()) failed";
|
||||
}
|
||||
|
||||
const char* error = nullptr;
|
||||
struct proc_stat_list* list = nullptr;
|
||||
if (proc_stat_list_create(context, &list) != 0) {
|
||||
error = "proc_stat_list_create() failed";
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (proc_stat_list_add_all(list, nullptr, nullptr) != 0) {
|
||||
error = "proc_stat_list_add_all() failed";
|
||||
goto done;
|
||||
}
|
||||
|
||||
// Flags that cannot be fetched for some process are simply left unset;
|
||||
// each field must be checked before use.
|
||||
if (proc_stat_list_set_flags(list, PSTAT_ARGS | PSTAT_STATE | PSTAT_TASK_BASIC) != 0) {
|
||||
error = "proc_stat_list_set_flags() failed";
|
||||
goto done;
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < list->num_procs; ++i) {
|
||||
struct proc_stat* stat = list->proc_stats[i];
|
||||
|
||||
if (!proc_stat_has(stat, PSTAT_PID | PSTAT_STATE)) {
|
||||
continue;
|
||||
}
|
||||
if (proc_stat_state(stat) & PSTAT_STATE_P_ZOMBIE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t pid = (uint32_t) proc_stat_pid(stat);
|
||||
if (pid == 2) { // Kernel Task
|
||||
continue;
|
||||
}
|
||||
|
||||
FFTopProcessSnapshot* item = FF_LIST_ADD(FFTopProcessSnapshot, *snapshots);
|
||||
|
||||
// There is no kernel-side comm on the Hurd; use the first argument.
|
||||
ffStrbufInit(&item->name);
|
||||
if (proc_stat_has(stat, PSTAT_ARGS) && proc_stat_args(stat)) {
|
||||
size_t len = strnlen(proc_stat_args(stat), proc_stat_args_len(stat));
|
||||
ffStrbufAppendNS(&item->name, (uint32_t) len, proc_stat_args(stat));
|
||||
}
|
||||
if (item->name.length == 0) {
|
||||
ffStrbufAppendS(&item->name, "(unknown)");
|
||||
}
|
||||
|
||||
item->pid = pid;
|
||||
|
||||
if (proc_stat_has(stat, PSTAT_TASK_BASIC)) {
|
||||
const task_basic_info_t info = proc_stat_task_basic_info(stat);
|
||||
item->cpuTime = (uint64_t) info->user_time64.seconds * 1000 +
|
||||
(uint64_t) info->user_time64.nanoseconds / 1000000 +
|
||||
(uint64_t) info->system_time64.seconds * 1000 +
|
||||
(uint64_t) info->system_time64.nanoseconds / 1000000;
|
||||
item->memBytes = (uint64_t) info->resident_size;
|
||||
item->startTime = (uint64_t) info->creation_time64.seconds * 1000 +
|
||||
(uint64_t) info->creation_time64.nanoseconds / 1000000;
|
||||
}
|
||||
|
||||
// Storage io counters aren't exposed by kernel; leave them zeroed.
|
||||
item->bytesRead = 0;
|
||||
item->bytesWritten = 0;
|
||||
}
|
||||
|
||||
done:
|
||||
if (list) {
|
||||
proc_stat_list_free(list);
|
||||
}
|
||||
ps_context_free(context);
|
||||
return error;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
#include "top.h"
|
||||
|
||||
#include <OS.h>
|
||||
#include <string.h>
|
||||
|
||||
const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes) {
|
||||
int32 cookie = 0;
|
||||
team_info team;
|
||||
while (get_next_team_info(&cookie, &team) == B_OK) {
|
||||
if (team.team == B_SYSTEM_TEAM) {
|
||||
continue; // The kernel team
|
||||
}
|
||||
|
||||
FFTopProcessSnapshot* item = FF_LIST_ADD(FFTopProcessSnapshot, *snapshots);
|
||||
|
||||
ffStrbufInitS(&item->name, team.name);
|
||||
|
||||
item->pid = (uint32_t) team.team;
|
||||
|
||||
// User and kernel time of all threads of the team combined in microseconds.
|
||||
item->cpuTime = 0;
|
||||
team_usage_info usage;
|
||||
if (get_team_usage_info(team.team, B_TEAM_USAGE_SELF, &usage) == B_OK) {
|
||||
item->cpuTime = ((uint64_t) usage.user_time + (uint64_t) usage.kernel_time) / 1000;
|
||||
}
|
||||
|
||||
// Sum up the physically resident memory of all areas of the team;
|
||||
// ram_size is the amount of physical pages committed to an area,
|
||||
// whereas size is just its virtual reservation.
|
||||
uint64_t memBytes = 0;
|
||||
ssize_t areaCookie = 0;
|
||||
area_info area;
|
||||
while (get_next_area_info(team.team, &areaCookie, &area) == B_OK) {
|
||||
memBytes += area.ram_size;
|
||||
}
|
||||
item->memBytes = memBytes;
|
||||
|
||||
// start_time is microseconds since boot.
|
||||
item->startTime = (uint64_t) team.start_time;
|
||||
|
||||
// Storage io counters are not exposed by Haiku; leave them zeroed.
|
||||
item->bytesRead = 0;
|
||||
item->bytesWritten = 0;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
#include "top.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/io.h"
|
||||
#include "common/memrchr.h"
|
||||
#include "common/strutil.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#define PF_KTHREAD 0x00200000 // defined in kernel include/linux/sched.h
|
||||
|
||||
static bool parseStat(const char* buffer, size_t length, FFTopProcessSnapshot* result) {
|
||||
const char* open = strchr(buffer, '(');
|
||||
const char* close = (const char*) memrchr(buffer, ')', length);
|
||||
if (!open || !close || close <= open || close + 2 >= buffer + length) {
|
||||
FF_DEBUG("parseStat: malformed stat content");
|
||||
return false;
|
||||
}
|
||||
|
||||
ffStrbufSetNS(&result->name, (uint32_t) (close - open - 1), open + 1);
|
||||
|
||||
const char* cursor = close + 2; // skip ") "
|
||||
if (__builtin_expect(cursor >= buffer + length, false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
result->cpuTime = 0;
|
||||
|
||||
char* p;
|
||||
for (uint32_t field = 3; field <= 22 /*start time*/; ++field) {
|
||||
switch (field) {
|
||||
case 9: // flags
|
||||
if ((strtoul(cursor, &p, 10) & PF_KTHREAD)) {
|
||||
FF_DEBUG("parseStat: skipping kernel task %s", result->name.chars);
|
||||
return false;
|
||||
}
|
||||
cursor = p;
|
||||
break;
|
||||
|
||||
case 14: // user
|
||||
case 15: // system
|
||||
result->cpuTime += (uint64_t) strtoull(cursor, &p, 10);
|
||||
cursor = p;
|
||||
break;
|
||||
|
||||
case 22: // start time
|
||||
result->startTime = (uint64_t) strtoull(cursor, &p, 10);
|
||||
cursor = p;
|
||||
break;
|
||||
|
||||
default:
|
||||
while (*cursor != ' ' && __builtin_expect(*cursor != '\0', true)) {
|
||||
++cursor;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
++cursor;
|
||||
if (__builtin_expect(cursor >= buffer + length, false)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes showTypes) {
|
||||
const long ticks = sysconf(_SC_CLK_TCK);
|
||||
const long pageSize = instance.state.platform.sysinfo.pageSize;
|
||||
FF_DEBUG("Scanning /proc: clk_tck=%ld, pageSize=%ld", ticks, pageSize);
|
||||
if (ticks <= 0 || pageSize <= 0) {
|
||||
return "sysconf(_SC_CLK_TCK or _SC_PAGESIZE) failed";
|
||||
}
|
||||
|
||||
FF_AUTO_CLOSE_DIR DIR* dir = opendir("/proc");
|
||||
if (!dir) {
|
||||
FF_DEBUG("opendir(\"/proc\") failed: %s", strerror(errno));
|
||||
return "opendir(\"/proc\") failed";
|
||||
}
|
||||
|
||||
int procfd = dirfd(dir);
|
||||
|
||||
struct dirent* entry;
|
||||
char statBuffer[1024];
|
||||
char statmBuffer[256];
|
||||
while ((entry = readdir(dir))) {
|
||||
if (!entry->d_name[0] || !ffCharIsDigit(entry->d_name[0])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
unsigned long pid = strtoul(entry->d_name, nullptr, 10);
|
||||
if (pid == 0 || pid > UINT32_MAX) {
|
||||
FF_DEBUG("Skip invalid pid directory: %s", entry->d_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
FF_AUTO_CLOSE_FD int subfd = openat(procfd, entry->d_name, O_RDONLY | O_DIRECTORY);
|
||||
if (subfd < 0) {
|
||||
FF_DEBUG("openat(/proc/%s) failed: %s", entry->d_name, strerror(errno));
|
||||
continue;
|
||||
}
|
||||
|
||||
ssize_t statLength;
|
||||
if ((statLength = ffReadFileDataRelative(subfd, "stat", sizeof(statBuffer) - 1, statBuffer)) < 0) {
|
||||
FF_DEBUG("Failed to read /proc/%s/stat: %s", entry->d_name, strerror(errno));
|
||||
continue;
|
||||
}
|
||||
statBuffer[statLength] = '\0';
|
||||
|
||||
ssize_t statmLength = 0;
|
||||
if (showTypes & FF_TOP_TYPE_MEMORY) {
|
||||
if ((statmLength = ffReadFileDataRelative(subfd, "statm", sizeof(statmBuffer) - 1, statmBuffer)) < 0) {
|
||||
FF_DEBUG("Failed to read /proc/%s/statm: %s", entry->d_name, strerror(errno));
|
||||
statmLength = 0;
|
||||
}
|
||||
statmBuffer[statmLength] = '\0';
|
||||
}
|
||||
|
||||
char ioBuffer[512];
|
||||
ssize_t ioLength = 0;
|
||||
if (showTypes & FF_TOP_TYPE_DISK) {
|
||||
if ((ioLength = ffReadFileDataRelative(subfd, "io", sizeof(ioBuffer) - 1, ioBuffer)) < 0) {
|
||||
FF_DEBUG("Failed to read /proc/%s/io: %s", entry->d_name, strerror(errno));
|
||||
ioLength = 0;
|
||||
}
|
||||
ioBuffer[ioLength] = '\0';
|
||||
}
|
||||
|
||||
auto snapshot = FF_LIST_ADD(FFTopProcessSnapshot, *snapshots);
|
||||
ffStrbufInit(&snapshot->name);
|
||||
|
||||
if (!parseStat(statBuffer, (size_t) statLength, snapshot)) {
|
||||
ffStrbufDestroy(&snapshot->name);
|
||||
--snapshots->length;
|
||||
continue;
|
||||
}
|
||||
|
||||
uint64_t rssPages = 0;
|
||||
if (__builtin_expect((showTypes & FF_TOP_TYPE_MEMORY) && sscanf(statmBuffer, "%*u %" SCNu64, &rssPages) != 1, false)) {
|
||||
FF_DEBUG("Failed to parse statm of /proc/%lu", pid);
|
||||
ffStrbufDestroy(&snapshot->name);
|
||||
--snapshots->length;
|
||||
continue;
|
||||
}
|
||||
|
||||
snapshot->pid = (uint32_t) pid;
|
||||
snapshot->memBytes = rssPages * (uint64_t) pageSize;
|
||||
snapshot->bytesRead = 0;
|
||||
snapshot->bytesWritten = 0;
|
||||
if (ioLength > 0) {
|
||||
sscanf(ioBuffer, "rchar: %*u\nwchar: %*u\nsyscr: %*u\nsyscw: %*u\nread_bytes: %" SCNu64 "\nwrite_bytes: %" SCNu64, &snapshot->bytesRead, &snapshot->bytesWritten);
|
||||
}
|
||||
snapshot->cpuTime = snapshot->cpuTime * 1000u / (uint64_t) ticks;
|
||||
FF_DEBUG(
|
||||
"Captured process %u (%s): cpuTime=%" PRIu64 "ms, mem=%" PRIu64 "B, diskRead=%" PRIu64 "B, diskWrite=%" PRIu64 "B",
|
||||
snapshot->pid, snapshot->name.chars, snapshot->cpuTime, snapshot->memBytes, snapshot->bytesRead, snapshot->bytesWritten);
|
||||
}
|
||||
FF_DEBUG("Captured %u process snapshots in total", snapshots->length);
|
||||
return nullptr;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
#include "top.h"
|
||||
#include "common/mallocHelper.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <errno.h>
|
||||
|
||||
const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes) {
|
||||
int request[] = { CTL_KERN, KERN_PROC2, KERN_PROC_ALL, -1, sizeof(struct kinfo_proc2), INT_MAX };
|
||||
size_t length;
|
||||
|
||||
if (sysctl(request, ARRAY_SIZE(request), nullptr, &length, nullptr, 0) != 0) {
|
||||
return "sysctl({CTL_KERN, KERN_PROC2, KERN_PROC_ALL, nullptr}) failed";
|
||||
}
|
||||
|
||||
// The process table may change between the two sysctl calls; retry with a larger buffer.
|
||||
length += length / 8 + sizeof(struct kinfo_proc2);
|
||||
|
||||
FF_AUTO_FREE struct kinfo_proc2* processes = malloc(length);
|
||||
if (sysctl(request, ARRAY_SIZE(request), processes, &length, nullptr, 0) != 0) {
|
||||
return "sysctl({CTL_KERN, KERN_PROC2, KERN_PROC_ALL, processes}) failed";
|
||||
}
|
||||
uint32_t count = (uint32_t) (length / sizeof(struct kinfo_proc2));
|
||||
|
||||
uint32_t pageSize = instance.state.platform.sysinfo.pageSize;
|
||||
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
const struct kinfo_proc2* proc = &processes[i];
|
||||
|
||||
if ((proc->p_flag & P_SYSTEM) || !proc->p_uvalid) { // Kernel process or zombie
|
||||
continue;
|
||||
}
|
||||
|
||||
FFTopProcessSnapshot* item = FF_LIST_ADD(FFTopProcessSnapshot, *snapshots);
|
||||
|
||||
ffStrbufInitS(&item->name, proc->p_comm);
|
||||
item->pid = (uint32_t) proc->p_pid;
|
||||
|
||||
uint64_t userMs = (uint64_t) proc->p_uutime_sec * 1000 +
|
||||
(uint64_t) proc->p_uutime_usec / 1000;
|
||||
uint64_t sysMs = (uint64_t) proc->p_ustime_sec * 1000 +
|
||||
(uint64_t) proc->p_ustime_usec / 1000;
|
||||
item->cpuTime = userMs + sysMs;
|
||||
|
||||
item->memBytes = (uint64_t) proc->p_vm_rssize * (uint64_t) pageSize;
|
||||
|
||||
item->startTime = (uint64_t) proc->p_ustart_sec * 1000 +
|
||||
(uint64_t) proc->p_ustart_usec / 1000;
|
||||
|
||||
item->bytesRead = (uint64_t) proc->p_uru_inblock * DEV_BSIZE;
|
||||
item->bytesWritten = (uint64_t) proc->p_uru_oublock * DEV_BSIZE;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
#include "top.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/param.h> // DEV_BSIZE
|
||||
#include <sys/sysctl.h>
|
||||
#include <kvm.h>
|
||||
|
||||
const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes) {
|
||||
kvm_t* kd = kvm_open(nullptr, nullptr, nullptr, KVM_NO_FILES, nullptr);
|
||||
if (!kd) {
|
||||
return "kvm_open() failed";
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
// KERN_PROC_ALL returns all user-level processes, excluding kernel processes and threads
|
||||
const struct kinfo_proc* processes = kvm_getprocs(kd, KERN_PROC_ALL, 0, sizeof(struct kinfo_proc), &count);
|
||||
if (!processes) {
|
||||
kvm_close(kd);
|
||||
return "kvm_getprocs() failed";
|
||||
}
|
||||
|
||||
const uint32_t pageSize = instance.state.platform.sysinfo.pageSize;
|
||||
|
||||
for (int i = 0; i < count; ++i) {
|
||||
const struct kinfo_proc* proc = &processes[i];
|
||||
|
||||
if (!proc->p_uvalid) { // Zombie process; p_u* members are invalid
|
||||
continue;
|
||||
}
|
||||
|
||||
FFTopProcessSnapshot* item = FF_LIST_ADD(FFTopProcessSnapshot, *snapshots);
|
||||
ffStrbufInitS(&item->name, proc->p_comm);
|
||||
item->pid = (uint32_t) proc->p_pid;
|
||||
item->cpuTime = ((uint64_t) proc->p_uutime_sec * 1000 + (uint64_t) proc->p_uutime_usec / 1000) +
|
||||
((uint64_t) proc->p_ustime_sec * 1000 + (uint64_t) proc->p_ustime_usec / 1000);
|
||||
item->memBytes = (uint64_t) proc->p_vm_rssize * pageSize;
|
||||
item->startTime = (uint64_t) proc->p_ustart_sec * 1000 + (uint64_t) proc->p_ustart_usec / 1000;
|
||||
item->bytesRead = (uint64_t) proc->p_uru_inblock * DEV_BSIZE;
|
||||
item->bytesWritten = (uint64_t) proc->p_uru_oublock * DEV_BSIZE;
|
||||
}
|
||||
|
||||
kvm_close(kd);
|
||||
return nullptr;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
#include "top.h"
|
||||
|
||||
#include "common/io.h"
|
||||
#include "common/strutil.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <procfs.h>
|
||||
#include <sys/param.h> // DEV_BSIZE
|
||||
|
||||
const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes showTypes) {
|
||||
FF_AUTO_CLOSE_DIR DIR* dirp = opendir("/proc");
|
||||
if (!dirp) {
|
||||
return "opendir(\"/proc\") failed";
|
||||
}
|
||||
|
||||
int procfd = dirfd(dirp);
|
||||
|
||||
struct dirent* dp;
|
||||
while ((dp = readdir(dirp))) {
|
||||
if (!ffCharIsDigit(dp->d_name[0])) { // Skip "." and ".."
|
||||
continue;
|
||||
}
|
||||
|
||||
FF_AUTO_CLOSE_FD int subfd = openat(procfd, dp->d_name, O_RDONLY | O_DIRECTORY);
|
||||
if (subfd < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pstatus_t status;
|
||||
if (ffReadFileDataRelative(subfd, "status", sizeof(status), &status) != (ssize_t) sizeof(status) ||
|
||||
status.pr_flags & PR_ISSYS) {
|
||||
continue; // The process may have exited, no permission or is a kernel process
|
||||
}
|
||||
|
||||
// The data model of the returned structure depends on the data model of
|
||||
// the calling process, not of the observed process.
|
||||
psinfo_t psinfo;
|
||||
if (ffReadFileDataRelative(subfd, "psinfo", sizeof(psinfo), &psinfo) != (ssize_t) sizeof(psinfo)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// zombies
|
||||
if (psinfo.pr_lwp.pr_sname == 'Z') {
|
||||
continue;
|
||||
}
|
||||
|
||||
FFTopProcessSnapshot* item = FF_LIST_ADD(FFTopProcessSnapshot, *snapshots);
|
||||
|
||||
ffStrbufInitS(&item->name, psinfo.pr_fname);
|
||||
|
||||
item->pid = (uint32_t) psinfo.pr_pid;
|
||||
|
||||
item->cpuTime = (uint64_t) psinfo.pr_time.tv_sec * 1000 +
|
||||
(uint64_t) psinfo.pr_time.tv_nsec / 1000000;
|
||||
|
||||
item->memBytes = (uint64_t) psinfo.pr_rssize * 1024;
|
||||
|
||||
item->startTime = (uint64_t) psinfo.pr_start.tv_sec * 1000 +
|
||||
(uint64_t) psinfo.pr_start.tv_nsec / 1000000;
|
||||
|
||||
item->bytesRead = 0;
|
||||
item->bytesWritten = 0;
|
||||
if (showTypes & FF_TOP_TYPE_DISK) {
|
||||
prusage_t usage;
|
||||
if (ffReadFileDataRelative(subfd, "usage", sizeof(usage), &usage) == (ssize_t) sizeof(usage)) {
|
||||
item->bytesRead = (uint64_t) usage.pr_inblk * DEV_BSIZE;
|
||||
item->bytesWritten = (uint64_t) usage.pr_oublk * DEV_BSIZE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
#include "top.h"
|
||||
|
||||
#include "common/mallocHelper.h"
|
||||
#include "common/windows/unicode.h"
|
||||
|
||||
#include <ntstatus.h>
|
||||
#include <windows.h>
|
||||
#include <winternl.h>
|
||||
|
||||
const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes) {
|
||||
FF_AUTO_FREE SYSTEM_PROCESS_INFORMATION* pstart = nullptr;
|
||||
|
||||
// Multiple attempts in case processes change while
|
||||
// we are in the middle of querying them.
|
||||
ULONG size = 0;
|
||||
for (int attempts = 0;; ++attempts) {
|
||||
if (size) {
|
||||
pstart = (SYSTEM_PROCESS_INFORMATION*) realloc(pstart, size);
|
||||
assert(pstart);
|
||||
}
|
||||
NTSTATUS status = NtQuerySystemInformation(SystemProcessInformation, pstart, size, &size);
|
||||
if (NT_SUCCESS(status)) {
|
||||
break;
|
||||
} else if (status == STATUS_INFO_LENGTH_MISMATCH && attempts < 4) {
|
||||
size += sizeof(SYSTEM_PROCESS_INFORMATION) * 5;
|
||||
} else {
|
||||
return "NtQuerySystemInformation(SystemProcessInformation) failed";
|
||||
}
|
||||
}
|
||||
|
||||
for (SYSTEM_PROCESS_INFORMATION* info = pstart;; info = (SYSTEM_PROCESS_INFORMATION*) ((uint8_t*) info + info->NextEntryOffset)) {
|
||||
uintptr_t pidValue = (uintptr_t) info->UniqueProcessId;
|
||||
if (pidValue <= UINT32_MAX && info->CreateTime.QuadPart > 0) {
|
||||
FFTopProcessSnapshot* item = FF_LIST_ADD(FFTopProcessSnapshot, *snapshots);
|
||||
item->pid = (uint32_t) pidValue;
|
||||
item->startTime = (uint64_t) info->CreateTime.QuadPart;
|
||||
item->cpuTime = ((uint64_t) info->UserTime.QuadPart + (uint64_t) info->KernelTime.QuadPart) / 10000u;
|
||||
item->memBytes = (uint64_t) info->VirtualMemoryCounters.WorkingSetSize;
|
||||
item->bytesRead = (uint64_t) info->IoCounters.ReadTransferCount;
|
||||
item->bytesWritten = (uint64_t) info->IoCounters.WriteTransferCount;
|
||||
ffStrbufInitNWS(&item->name, info->ImageName.Length / sizeof(wchar_t), info->ImageName.Buffer);
|
||||
}
|
||||
if (info->NextEntryOffset == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@@ -18,7 +18,8 @@ struct FFWifiConnection {
|
||||
double rxRate;
|
||||
double txRate;
|
||||
uint16_t channel;
|
||||
uint16_t frequency; // MHz
|
||||
uint16_t channelWidth; // MHz
|
||||
uint16_t frequency; // MHz
|
||||
};
|
||||
|
||||
typedef struct FFWifiResult {
|
||||
@@ -60,3 +61,18 @@ static inline uint16_t ffWifiFreqToChannel(uint16_t frequency) {
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline uint16_t ffWifiChannelToFreq(uint16_t channel) {
|
||||
// Inverse of ffWifiFreqToChannel for the common 2.4 GHz and 5 GHz bands.
|
||||
// 4.9 GHz and 6 GHz channels overlap numerically, so they are not handled here.
|
||||
if (channel == 14) {
|
||||
return 2484;
|
||||
}
|
||||
if (channel >= 1 && channel <= 13) {
|
||||
return (uint16_t) (2407 + 5 * channel);
|
||||
}
|
||||
if (channel >= 36 && channel <= 177) { // 5 GHz: 5000 + 5*channel covers 36..177
|
||||
return (uint16_t) (5000 + 5 * channel);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ const char* ffDetectWifi(FFlist* result) {
|
||||
item->conn.rxRate = -DBL_MAX;
|
||||
item->conn.txRate = -DBL_MAX;
|
||||
item->conn.channel = 0;
|
||||
item->conn.channelWidth = 0;
|
||||
item->conn.frequency = 0;
|
||||
|
||||
ffStrbufAppendJsonVal(&item->inf.status, yyjson_obj_get(root, "supplicant_state"));
|
||||
|
||||
@@ -1,26 +1,28 @@
|
||||
#include "wifi.h"
|
||||
#include "common/processing.h"
|
||||
#include "common/strutil.h"
|
||||
#include "common/apple/cf_helpers.h"
|
||||
|
||||
#import <CoreWLAN/CoreWLAN.h>
|
||||
#import <IOKit/IOKitLib.h>
|
||||
#import <IOKit/kext/KextManager.h>
|
||||
|
||||
static inline double rssiToSignalQuality(int rssi)
|
||||
{
|
||||
return (double) (rssi >= -50 ? 100 : rssi <= -100 ? 0 : (rssi + 100) * 2);
|
||||
static inline double rssiToSignalQuality(int rssi) {
|
||||
return (double) (rssi >= -50 ? 100 : rssi <= -100 ? 0
|
||||
: (rssi + 100) * 2);
|
||||
}
|
||||
|
||||
@interface CWNetworkProfile()
|
||||
@property(readonly, retain, nullable) NSArray<NSDictionary *> *bssidList;
|
||||
@interface CWNetworkProfile ()
|
||||
@property (readonly, retain, nullable) NSArray<NSDictionary*>* bssidList;
|
||||
@end
|
||||
|
||||
const char* ffDetectWifi(FFlist* result)
|
||||
{
|
||||
const char* ffDetectWifi(FFlist* result) {
|
||||
NSArray<CWInterface*>* interfaces = CWWiFiClient.sharedWiFiClient.interfaces;
|
||||
if (!interfaces)
|
||||
if (!interfaces) {
|
||||
return "CWWiFiClient.sharedWiFiClient.interfaces is nil";
|
||||
}
|
||||
|
||||
for (CWInterface* inf in interfaces)
|
||||
{
|
||||
for (CWInterface* inf in interfaces) {
|
||||
FFWifiResult* item = FF_LIST_ADD(FFWifiResult, *result);
|
||||
ffStrbufInit(&item->inf.description);
|
||||
ffStrbufInit(&item->inf.status);
|
||||
@@ -33,37 +35,42 @@ const char* ffDetectWifi(FFlist* result)
|
||||
item->conn.rxRate = -DBL_MAX;
|
||||
item->conn.txRate = -DBL_MAX;
|
||||
item->conn.channel = 0;
|
||||
item->conn.channelWidth = 0;
|
||||
item->conn.frequency = 0;
|
||||
|
||||
ffStrbufAppendS(&item->inf.description, inf.interfaceName.UTF8String);
|
||||
ffStrbufSetS(&item->inf.description, inf.interfaceName.UTF8String);
|
||||
ffStrbufSetStatic(&item->inf.status, inf.powerOn ? "Power On" : "Power Off");
|
||||
if(!inf.powerOn)
|
||||
|
||||
if (!inf.powerOn) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ffStrbufSetStatic(&item->conn.status, inf.interfaceMode != kCWInterfaceModeNone ? "Active" : "Inactive");
|
||||
if(inf.interfaceMode == kCWInterfaceModeNone)
|
||||
if (inf.interfaceMode == kCWInterfaceModeNone) {
|
||||
continue;
|
||||
}
|
||||
|
||||
FF_STRBUF_AUTO_DESTROY ipconfig = ffStrbufCreate();
|
||||
|
||||
CWNetworkProfile* networkProfile = inf.configuration.networkProfiles.firstObject;
|
||||
|
||||
if (inf.ssid) // https://developer.apple.com/forums/thread/732431
|
||||
if (inf.ssid) { // https://developer.apple.com/forums/thread/732431
|
||||
ffStrbufAppendS(&item->conn.ssid, inf.ssid.UTF8String);
|
||||
else if (networkProfile.ssid)
|
||||
} else if (networkProfile.ssid) {
|
||||
ffStrbufSetStatic(&item->conn.ssid, inf.configuration.networkProfiles.firstObject.ssid.UTF8String);
|
||||
else
|
||||
} else {
|
||||
ffStrbufSetStatic(&item->conn.ssid, "<redacted>"); // https://developer.apple.com/forums/thread/732431
|
||||
}
|
||||
|
||||
if (inf.bssid)
|
||||
if (inf.bssid) {
|
||||
ffStrbufAppendS(&item->conn.bssid, inf.bssid.UTF8String);
|
||||
else if (networkProfile.bssidList)
|
||||
} else if (networkProfile.bssidList) {
|
||||
ffStrbufSetStatic(&item->conn.bssid, [networkProfile.bssidList.firstObject[@"BSSID"] UTF8String]);
|
||||
else
|
||||
} else {
|
||||
ffStrbufSetStatic(&item->conn.bssid, "<redacted>");
|
||||
}
|
||||
|
||||
switch(inf.activePHYMode)
|
||||
{
|
||||
switch (inf.activePHYMode) {
|
||||
case kCWPHYModeNone:
|
||||
ffStrbufSetStatic(&item->conn.protocol, "none");
|
||||
break;
|
||||
@@ -89,15 +96,15 @@ const char* ffDetectWifi(FFlist* result)
|
||||
ffStrbufSetStatic(&item->conn.protocol, "802.11be (Wi-Fi 7)");
|
||||
break;
|
||||
default:
|
||||
if (inf.activePHYMode < 8)
|
||||
if (inf.activePHYMode < 8) {
|
||||
ffStrbufAppendF(&item->conn.protocol, "Unknown (%ld)", inf.activePHYMode);
|
||||
}
|
||||
break;
|
||||
}
|
||||
item->conn.signalQuality = rssiToSignalQuality((int) inf.rssiValue);
|
||||
item->conn.txRate = inf.transmitRate;
|
||||
|
||||
switch(inf.security)
|
||||
{
|
||||
switch (inf.security) {
|
||||
case kCWSecurityNone:
|
||||
ffStrbufSetStatic(&item->conn.security, "Insecure");
|
||||
break;
|
||||
@@ -152,12 +159,33 @@ const char* ffDetectWifi(FFlist* result)
|
||||
}
|
||||
|
||||
item->conn.channel = (uint16_t) inf.wlanChannel.channelNumber;
|
||||
switch (inf.wlanChannel.channelBand)
|
||||
{
|
||||
case kCWChannelBand2GHz: item->conn.frequency = 2400; break;
|
||||
case kCWChannelBand5GHz: item->conn.frequency = 5400; break;
|
||||
case 3 /*kCWChannelBand6GHz*/: item->conn.frequency = 6400; break;
|
||||
default: item->conn.frequency = 0; break;
|
||||
switch (inf.wlanChannel.channelWidth) {
|
||||
case kCWChannelWidth20MHz: item->conn.channelWidth = 20; break;
|
||||
case kCWChannelWidth40MHz: item->conn.channelWidth = 40; break;
|
||||
case kCWChannelWidth80MHz: item->conn.channelWidth = 80; break;
|
||||
case kCWChannelWidth160MHz: item->conn.channelWidth = 160; break;
|
||||
default: item->conn.channelWidth = 0; break;
|
||||
}
|
||||
|
||||
FF_IOOBJECT_AUTO_RELEASE io_object_t service = IOServiceGetMatchingService(MACH_PORT_NULL, IOBSDNameMatching(MACH_PORT_NULL, 0, item->inf.description.chars));
|
||||
FF_CFTYPE_AUTO_RELEASE CFNumberRef frequency;
|
||||
// Note: IO80211ChannelFrequency is only available when the interface is connected
|
||||
while (!(frequency = IORegistryEntryCreateCFProperty(service, CFSTR("IO80211ChannelFrequency"), nullptr, kNilOptions))) {
|
||||
io_object_t parentService = IO_OBJECT_NULL;
|
||||
if (IORegistryEntryGetParentEntry(service, kIOServicePlane, &parentService) == KERN_SUCCESS) {
|
||||
IOObjectRelease(service);
|
||||
service = parentService;
|
||||
} else {
|
||||
IOObjectRelease(service);
|
||||
service = IO_OBJECT_NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (frequency) {
|
||||
int64_t value;
|
||||
if (ffCfNumGetInt64(frequency, &value) == nil) {
|
||||
item->conn.frequency = (uint16_t) value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ const char* ffDetectWifi(FFlist* result) {
|
||||
item->conn.rxRate = -DBL_MAX;
|
||||
item->conn.txRate = -DBL_MAX;
|
||||
item->conn.channel = 0;
|
||||
item->conn.channelWidth = 0;
|
||||
item->conn.frequency = 0;
|
||||
|
||||
char ssid[IEEE80211_NWID_LEN + 1] = {};
|
||||
@@ -80,6 +81,16 @@ const char* ffDetectWifi(FFlist* result) {
|
||||
item->conn.channel = curchan.ic_ieee;
|
||||
item->conn.frequency = curchan.ic_freq;
|
||||
|
||||
// Determine channel width from channel flags
|
||||
if (curchan.ic_flags & IEEE80211_CHAN_VHT160)
|
||||
item->conn.channelWidth = 160;
|
||||
else if (curchan.ic_flags & IEEE80211_CHAN_VHT80)
|
||||
item->conn.channelWidth = 80;
|
||||
else if (curchan.ic_flags & IEEE80211_CHAN_HT40)
|
||||
item->conn.channelWidth = 40;
|
||||
else
|
||||
item->conn.channelWidth = 20;
|
||||
|
||||
#ifdef IEEE80211_IS_CHAN_HE // for future use
|
||||
if (IEEE80211_IS_CHAN_HE(&curchan)) {
|
||||
ffStrbufSetStatic(&item->conn.protocol, "802.11ax (Wi-Fi 6)");
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
extern "C" {
|
||||
#include "wifi.h"
|
||||
}
|
||||
|
||||
#include <NetworkDevice.h>
|
||||
#include <NetworkInterface.h>
|
||||
#include <NetworkRoster.h>
|
||||
|
||||
static void initWifiResult(FFWifiResult* item, const char* name) {
|
||||
ffStrbufInitS(&item->inf.description, name);
|
||||
ffStrbufInit(&item->inf.status);
|
||||
ffStrbufInit(&item->conn.status);
|
||||
ffStrbufInit(&item->conn.ssid);
|
||||
ffStrbufInit(&item->conn.bssid);
|
||||
ffStrbufInit(&item->conn.protocol);
|
||||
ffStrbufInit(&item->conn.security);
|
||||
item->conn.signalQuality = -DBL_MAX;
|
||||
item->conn.rxRate = -DBL_MAX;
|
||||
item->conn.txRate = -DBL_MAX;
|
||||
item->conn.channel = 0;
|
||||
item->conn.channelWidth = 0;
|
||||
item->conn.frequency = 0;
|
||||
}
|
||||
|
||||
static void setWifiSecurity(FFstrbuf* security, const wireless_network& network) {
|
||||
switch (network.authentication_mode) {
|
||||
case B_NETWORK_AUTHENTICATION_NONE:
|
||||
if (network.flags & B_NETWORK_IS_ENCRYPTED) {
|
||||
ffStrbufSetStatic(security, "Encrypted");
|
||||
} else {
|
||||
ffStrbufSetStatic(security, "Insecure");
|
||||
}
|
||||
break;
|
||||
case B_NETWORK_AUTHENTICATION_WEP:
|
||||
ffStrbufSetStatic(security, "WEP");
|
||||
break;
|
||||
case B_NETWORK_AUTHENTICATION_WPA:
|
||||
ffStrbufSetStatic(security, "WPA");
|
||||
break;
|
||||
case B_NETWORK_AUTHENTICATION_WPA2:
|
||||
ffStrbufSetStatic(security, "WPA2");
|
||||
break;
|
||||
case B_NETWORK_AUTHENTICATION_EAP:
|
||||
ffStrbufSetStatic(security, "WPA Enterprise");
|
||||
break;
|
||||
default:
|
||||
ffStrbufSetF(security, "Unknown (%lu)", (unsigned long) network.authentication_mode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void setWifiBssid(FFstrbuf* bssid, const wireless_network& network) {
|
||||
const uint8* address = network.address.LinkLevelAddress();
|
||||
if (address == nullptr || network.address.LinkLevelAddressLength() < 6) {
|
||||
return;
|
||||
}
|
||||
|
||||
ffStrbufSetF(bssid, "%02X:%02X:%02X:%02X:%02X:%02X",
|
||||
address[0], address[1], address[2], address[3], address[4], address[5]);
|
||||
}
|
||||
|
||||
const char* ffDetectWifi(FFlist* result) {
|
||||
BNetworkRoster& roster = BNetworkRoster::Default();
|
||||
BNetworkInterface interface;
|
||||
uint32 cookie = 0;
|
||||
|
||||
while (roster.GetNextInterface(&cookie, interface) == B_OK) {
|
||||
if (!interface.Exists()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
BNetworkDevice device(interface.Name());
|
||||
if (!device.Exists() || !device.IsWireless()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
FFWifiResult* item = FF_LIST_ADD(FFWifiResult, *result);
|
||||
initWifiResult(item, interface.Name());
|
||||
|
||||
ffStrbufSetStatic(&item->inf.status, interface.HasLink() ? "Up" : "Down");
|
||||
if (!interface.HasLink() || !device.HasLink()) {
|
||||
ffStrbufSetStatic(&item->conn.status, "Not connected");
|
||||
continue;
|
||||
}
|
||||
|
||||
ffStrbufSetStatic(&item->conn.status, "Connected");
|
||||
|
||||
uint32 networkCookie = 0;
|
||||
wireless_network network = {};
|
||||
if (device.GetNextAssociatedNetwork(networkCookie, network) != B_OK) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ffStrbufSetNS(&item->conn.ssid, (uint32_t) strnlen(network.name, sizeof(network.name)), network.name);
|
||||
setWifiBssid(&item->conn.bssid, network);
|
||||
item->conn.signalQuality = network.signal_strength > 100 ? 100 : network.signal_strength;
|
||||
setWifiSecurity(&item->conn.security, network);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
@@ -212,8 +212,9 @@ static bool ffWifiNlInit(FFWifiNlContext* ctx) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static double ffWifiParseBitrateFromRateInfo(const struct nlattr* rateAttr, FFstrbuf* protocol) {
|
||||
static double ffWifiParseBitrateFromRateInfo(const struct nlattr* rateAttr, FFstrbuf* protocol, uint16_t* channelWidth) {
|
||||
double rate = -DBL_MAX;
|
||||
uint16_t width = 0;
|
||||
size_t remaining = ffWifiNlAttrPayload(rateAttr);
|
||||
|
||||
for (const struct nlattr* info = (const struct nlattr*) ffWifiNlAttrData(rateAttr);
|
||||
@@ -223,6 +224,40 @@ static double ffWifiParseBitrateFromRateInfo(const struct nlattr* rateAttr, FFst
|
||||
size_t payload = ffWifiNlAttrPayload(info);
|
||||
|
||||
switch (type) {
|
||||
case NL80211_RATE_INFO_40_MHZ_WIDTH:
|
||||
width = 40;
|
||||
break;
|
||||
case NL80211_RATE_INFO_80_MHZ_WIDTH:
|
||||
case NL80211_RATE_INFO_80P80_MHZ_WIDTH:
|
||||
width = 80;
|
||||
break;
|
||||
case NL80211_RATE_INFO_160_MHZ_WIDTH:
|
||||
width = 160;
|
||||
break;
|
||||
case 18 /* NL80211_RATE_INFO_320_MHZ_WIDTH */:
|
||||
width = 320;
|
||||
break;
|
||||
case NL80211_RATE_INFO_10_MHZ_WIDTH:
|
||||
width = 10;
|
||||
break;
|
||||
case NL80211_RATE_INFO_5_MHZ_WIDTH:
|
||||
width = 5;
|
||||
break;
|
||||
case 25 /* NL80211_RATE_INFO_1_MHZ_WIDTH */:
|
||||
width = 1;
|
||||
break;
|
||||
case 26 /* NL80211_RATE_INFO_2_MHZ_WIDTH */:
|
||||
width = 2;
|
||||
break;
|
||||
case 27 /* NL80211_RATE_INFO_4_MHZ_WIDTH */:
|
||||
width = 4;
|
||||
break;
|
||||
case 28 /* NL80211_RATE_INFO_8_MHZ_WIDTH */:
|
||||
width = 8;
|
||||
break;
|
||||
case 29 /* NL80211_RATE_INFO_16_MHZ_WIDTH */:
|
||||
width = 16;
|
||||
break;
|
||||
case 30 /* NL80211_RATE_INFO_UHR_MCS */:
|
||||
ffStrbufSetStatic(protocol, "802.11bn (Wi-Fi 8)");
|
||||
break;
|
||||
@@ -254,9 +289,126 @@ static double ffWifiParseBitrateFromRateInfo(const struct nlattr* rateAttr, FFst
|
||||
}
|
||||
}
|
||||
|
||||
if (rate != -DBL_MAX && *channelWidth == 0) {
|
||||
*channelWidth = width;
|
||||
}
|
||||
|
||||
return rate;
|
||||
}
|
||||
|
||||
static uint16_t ffWifiChannelWidthToMhz(uint32_t width) {
|
||||
switch (width) {
|
||||
case NL80211_CHAN_WIDTH_20_NOHT:
|
||||
case NL80211_CHAN_WIDTH_20:
|
||||
return 20;
|
||||
case NL80211_CHAN_WIDTH_40:
|
||||
return 40;
|
||||
case NL80211_CHAN_WIDTH_80:
|
||||
case NL80211_CHAN_WIDTH_80P80:
|
||||
return 80;
|
||||
case NL80211_CHAN_WIDTH_160:
|
||||
return 160;
|
||||
case NL80211_CHAN_WIDTH_5:
|
||||
return 5;
|
||||
case NL80211_CHAN_WIDTH_10:
|
||||
return 10;
|
||||
case NL80211_CHAN_WIDTH_1:
|
||||
return 1;
|
||||
case NL80211_CHAN_WIDTH_2:
|
||||
return 2;
|
||||
case NL80211_CHAN_WIDTH_4:
|
||||
return 4;
|
||||
case NL80211_CHAN_WIDTH_8:
|
||||
return 8;
|
||||
case NL80211_CHAN_WIDTH_16:
|
||||
return 16;
|
||||
case 13 /* NL80211_CHAN_WIDTH_320 */:
|
||||
return 320;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static bool ffWifiFetchInterfaceInfo(FFWifiNlContext* ctx, FFWifiResult* item, uint32_t ifIndex) {
|
||||
struct {
|
||||
struct nlmsghdr nlh;
|
||||
struct genlmsghdr genl;
|
||||
char attrs[32];
|
||||
} req = {
|
||||
.nlh = {
|
||||
.nlmsg_len = NLMSG_LENGTH(sizeof(struct genlmsghdr)),
|
||||
.nlmsg_type = ctx->nl80211FamilyId,
|
||||
.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK,
|
||||
.nlmsg_seq = ++ctx->seq,
|
||||
.nlmsg_pid = ctx->portId,
|
||||
},
|
||||
.genl = {
|
||||
.cmd = NL80211_CMD_GET_INTERFACE,
|
||||
.version = 0,
|
||||
},
|
||||
};
|
||||
|
||||
if (!ffWifiNlAppendAttr(&req.nlh, sizeof(req), NL80211_ATTR_IFINDEX, &ifIndex, sizeof(ifIndex))) {
|
||||
FF_DEBUG("Failed to build nl80211 interface request");
|
||||
return false;
|
||||
}
|
||||
|
||||
struct sockaddr_nl addr = {
|
||||
.nl_family = AF_NETLINK,
|
||||
};
|
||||
|
||||
ssize_t sent = sendto(ctx->sockFd, &req, req.nlh.nlmsg_len, 0, (struct sockaddr*) &addr, sizeof(addr));
|
||||
if (sent != (ssize_t) req.nlh.nlmsg_len) {
|
||||
FF_DEBUG("Failed to send nl80211 interface request");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t buffer[8192];
|
||||
while (true) {
|
||||
ssize_t received = recvfrom(ctx->sockFd, buffer, sizeof(buffer), 0, nullptr, nullptr);
|
||||
if (received < 0) {
|
||||
FF_DEBUG("Failed to receive nl80211 interface reply: %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const struct nlmsghdr* nlh = (const struct nlmsghdr*) buffer;
|
||||
NLMSG_OK(nlh, received);
|
||||
nlh = NLMSG_NEXT(nlh, received)) {
|
||||
if (nlh->nlmsg_seq != req.nlh.nlmsg_seq) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (nlh->nlmsg_type == NLMSG_ERROR) {
|
||||
const struct nlmsgerr* err = (const struct nlmsgerr*) NLMSG_DATA(nlh);
|
||||
if (err->error != 0) {
|
||||
FF_DEBUG("nl80211 interface request failed: %s", strerror(-err->error));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (nlh->nlmsg_type != ctx->nl80211FamilyId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const struct genlmsghdr* genl = (const struct genlmsghdr*) NLMSG_DATA(nlh);
|
||||
size_t attrRemaining = nlh->nlmsg_len - NLMSG_HDRLEN - GENL_HDRLEN;
|
||||
for (const struct nlattr* attr = (const struct nlattr*) ((const char*) genl + GENL_HDRLEN);
|
||||
ffWifiNlAttrOk(attr, attrRemaining);
|
||||
attr = ffWifiNlAttrNext(attr, &attrRemaining)) {
|
||||
if ((attr->nla_type & NLA_TYPE_MASK) != NL80211_ATTR_CHANNEL_WIDTH ||
|
||||
ffWifiNlAttrPayload(attr) < sizeof(uint32_t)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
item->conn.channelWidth = ffWifiChannelWidthToMhz(*(const uint32_t*) ffWifiNlAttrData(attr));
|
||||
return item->conn.channelWidth != 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ffWifiApplySecurityFlags(FFWifiResult* item, const FFWifiSecurityFlags* sec) {
|
||||
ffStrbufClear(&item->conn.security);
|
||||
|
||||
@@ -566,13 +718,13 @@ static void ffWifiParseStationInfo(const struct nlattr* staInfoAttr, FFWifiResul
|
||||
if (type == NL80211_STA_INFO_SIGNAL && payload >= sizeof(uint8_t) && item->conn.signalQuality == -DBL_MAX) {
|
||||
int rssi = (int8_t) *(const uint8_t*) ffWifiNlAttrData(attr);
|
||||
item->conn.signalQuality = rssiToSignalQuality(rssi);
|
||||
} else if (type == NL80211_STA_INFO_TX_BITRATE && item->conn.txRate == -DBL_MAX) {
|
||||
double tx = ffWifiParseBitrateFromRateInfo(attr, &item->conn.protocol);
|
||||
} else if (type == NL80211_STA_INFO_TX_BITRATE && (item->conn.txRate == -DBL_MAX || item->conn.channelWidth == 0)) {
|
||||
double tx = ffWifiParseBitrateFromRateInfo(attr, &item->conn.protocol, &item->conn.channelWidth);
|
||||
if (tx != -DBL_MAX) {
|
||||
item->conn.txRate = tx;
|
||||
}
|
||||
} else if (type == NL80211_STA_INFO_RX_BITRATE && item->conn.rxRate == -DBL_MAX) {
|
||||
double rx = ffWifiParseBitrateFromRateInfo(attr, &item->conn.protocol);
|
||||
} else if (type == NL80211_STA_INFO_RX_BITRATE && (item->conn.rxRate == -DBL_MAX || item->conn.channelWidth == 0)) {
|
||||
double rx = ffWifiParseBitrateFromRateInfo(attr, &item->conn.protocol, &item->conn.channelWidth);
|
||||
if (rx != -DBL_MAX) {
|
||||
item->conn.rxRate = rx;
|
||||
}
|
||||
@@ -680,6 +832,7 @@ static const char* detectWithNetlink(FFWifiNlContext* ctx, FFWifiResult* item, u
|
||||
if (ffWifiFetchScanInfo(ctx, item, ifIndex)) {
|
||||
FF_DEBUG("found associated BSS: %s", item->conn.ssid.chars);
|
||||
ffStrbufSetStatic(&item->conn.status, "connected");
|
||||
ffWifiFetchInterfaceInfo(ctx, item, ifIndex);
|
||||
ffWifiFetchStationInfo(ctx, item, ifIndex);
|
||||
if (!item->conn.protocol.length && item->conn.txRate != -DBL_MAX) {
|
||||
FF_DEBUG("nl80211 station info did not include MCS family fields");
|
||||
@@ -923,6 +1076,7 @@ const char* ffDetectWifi(FFlist* result) {
|
||||
item->conn.rxRate = -DBL_MAX;
|
||||
item->conn.txRate = -DBL_MAX;
|
||||
item->conn.channel = 0;
|
||||
item->conn.channelWidth = 0;
|
||||
item->conn.frequency = 0;
|
||||
|
||||
char operstate;
|
||||
|
||||
@@ -47,6 +47,7 @@ const char* ffDetectWifi(FFlist* result) {
|
||||
item->conn.rxRate = -DBL_MAX;
|
||||
item->conn.txRate = -DBL_MAX;
|
||||
item->conn.channel = 0;
|
||||
item->conn.channelWidth = 0;
|
||||
item->conn.frequency = 0;
|
||||
|
||||
char ssid[IEEE80211_NWID_LEN + 1] = {};
|
||||
@@ -91,6 +92,16 @@ const char* ffDetectWifi(FFlist* result) {
|
||||
item->conn.channel = ffWifiFreqToChannel(curchan.ic_freq);
|
||||
item->conn.frequency = curchan.ic_freq;
|
||||
|
||||
// Determine channel width from channel flags
|
||||
if (curchan.ic_flags & IEEE80211_CHAN_VHT160)
|
||||
item->conn.channelWidth = 160;
|
||||
else if (curchan.ic_flags & IEEE80211_CHAN_VHT80)
|
||||
item->conn.channelWidth = 80;
|
||||
else if (curchan.ic_flags & IEEE80211_CHAN_HT40)
|
||||
item->conn.channelWidth = 40;
|
||||
else
|
||||
item->conn.channelWidth = 20;
|
||||
|
||||
#ifdef IEEE80211_IS_CHAN_HE // for future use
|
||||
if (IEEE80211_IS_CHAN_HE(&curchan)) {
|
||||
ffStrbufSetStatic(&item->conn.protocol, "802.11ax (Wi-Fi 6)");
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "wifi.h"
|
||||
#include "common/io.h"
|
||||
#include "common/strutil.h"
|
||||
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
@@ -17,12 +16,20 @@ const char* ffDetectWifi(FFlist* result) {
|
||||
|
||||
FF_AUTO_CLOSE_FD int sock = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (sock < 0) {
|
||||
if_freenameindex(infs);
|
||||
return "socket() failed";
|
||||
}
|
||||
|
||||
for (struct if_nameindex* i = infs; !(i->if_index == 0 && i->if_name == nullptr); ++i) {
|
||||
if (!ffStrStartsWith(i->if_name, "iwm")) {
|
||||
continue;
|
||||
struct ieee80211_nodereq nr = {};
|
||||
strlcpy(nr.nr_ifname, i->if_name, sizeof(nr.nr_ifname));
|
||||
|
||||
int err = 0;
|
||||
if (ioctl(sock, SIOCG80211NODE, &nr) < 0) {
|
||||
err = errno;
|
||||
if (err == ENXIO || err == ENODEV || err == EINVAL || err == ENOTTY) {
|
||||
continue; // Not a Wi-Fi interface
|
||||
}
|
||||
}
|
||||
|
||||
FFWifiResult* item = FF_LIST_ADD(FFWifiResult, *result);
|
||||
@@ -37,11 +44,9 @@ const char* ffDetectWifi(FFlist* result) {
|
||||
item->conn.rxRate = -DBL_MAX;
|
||||
item->conn.txRate = -DBL_MAX;
|
||||
item->conn.channel = 0;
|
||||
item->conn.channelWidth = 0; // not exposed by ieee80211_nodereq
|
||||
item->conn.frequency = 0;
|
||||
|
||||
struct ieee80211_nodereq nr = {};
|
||||
strlcpy(nr.nr_ifname, i->if_name, sizeof(nr.nr_ifname));
|
||||
|
||||
struct ifreq ifr = {};
|
||||
strlcpy(ifr.ifr_name, i->if_name, sizeof(ifr.ifr_name));
|
||||
if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0) {
|
||||
@@ -50,31 +55,34 @@ const char* ffDetectWifi(FFlist* result) {
|
||||
ffStrbufSetStatic(&item->inf.status, ifr.ifr_flags & IFF_UP ? "Up" : "Down");
|
||||
}
|
||||
|
||||
if (ioctl(sock, SIOCG80211NODE, &nr) < 0) {
|
||||
ffStrbufSetStatic(&item->conn.status, "Not associated");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (nr.nr_nwid_len > 0) {
|
||||
ffStrbufSetStatic(&item->conn.status, "Associated");
|
||||
ffStrbufAppendNS(&item->conn.ssid, nr.nr_nwid_len, (char*) nr.nr_nwid);
|
||||
} else {
|
||||
if (err != 0 || nr.nr_nwid_len == 0) {
|
||||
ffStrbufSetStatic(&item->conn.status, "Not associated");
|
||||
continue;
|
||||
}
|
||||
|
||||
ffStrbufSetStatic(&item->conn.status, "Associated");
|
||||
ffStrbufAppendNS(&item->conn.ssid, nr.nr_nwid_len, (char*) nr.nr_nwid);
|
||||
ffStrbufSetF(&item->conn.bssid, "%02X:%02X:%02X:%02X:%02X:%02X", nr.nr_bssid[0], nr.nr_bssid[1], nr.nr_bssid[2], nr.nr_bssid[3], nr.nr_bssid[4], nr.nr_bssid[5]);
|
||||
|
||||
item->conn.channel = nr.nr_channel;
|
||||
if (item->conn.channel == 0) {
|
||||
struct ieee80211chanreq chreq = {};
|
||||
strlcpy(chreq.i_name, i->if_name, sizeof(chreq.i_name));
|
||||
if (ioctl(sock, SIOCG80211CHANNEL, &chreq) >= 0) {
|
||||
item->conn.channel = chreq.i_channel;
|
||||
}
|
||||
}
|
||||
item->conn.frequency = ffWifiChannelToFreq(item->conn.channel);
|
||||
|
||||
if (nr.nr_max_rssi) {
|
||||
item->conn.signalQuality = ((float) nr.nr_rssi / nr.nr_max_rssi) * 100.0;
|
||||
item->conn.signalQuality = ((double) nr.nr_rssi / (double) nr.nr_max_rssi) * 100.0;
|
||||
}
|
||||
|
||||
if (nr.nr_flags & IEEE80211_NODEREQ_HT) {
|
||||
ffStrbufSetStatic(&item->conn.protocol, "802.11n (Wi-Fi 4)");
|
||||
} else if (nr.nr_flags & IEEE80211_NODEREQ_VHT) {
|
||||
// VHT implies HT, so test VHT first
|
||||
if (nr.nr_flags & IEEE80211_NODEREQ_VHT) {
|
||||
ffStrbufSetStatic(&item->conn.protocol, "802.11ac (Wi-Fi 5)");
|
||||
} else if (nr.nr_flags & IEEE80211_NODEREQ_HT) {
|
||||
ffStrbufSetStatic(&item->conn.protocol, "802.11n (Wi-Fi 4)");
|
||||
} else if (nr.nr_chan_flags & IEEE80211_CHANINFO_5GHZ) {
|
||||
ffStrbufSetStatic(&item->conn.protocol, "802.11a");
|
||||
} else if (nr.nr_chan_flags & IEEE80211_CHANINFO_2GHZ) {
|
||||
@@ -85,15 +93,22 @@ const char* ffDetectWifi(FFlist* result) {
|
||||
strlcpy(wpa.i_name, i->if_name, sizeof(wpa.i_name));
|
||||
|
||||
if (ioctl(sock, SIOCG80211WPAPARMS, &wpa) >= 0 && wpa.i_enabled) {
|
||||
if (wpa.i_protos & IEEE80211_WPA_PROTO_WPA2) {
|
||||
if (wpa.i_akms & IEEE80211_WPA_AKM_SAE) {
|
||||
if (wpa.i_protos & IEEE80211_WPA_PROTO_WPA2) {
|
||||
ffStrbufSetStatic(&item->conn.security, "WPA2/WPA3");
|
||||
} else {
|
||||
ffStrbufSetStatic(&item->conn.security, "WPA3");
|
||||
}
|
||||
} else if (wpa.i_protos & IEEE80211_WPA_PROTO_WPA2) {
|
||||
ffStrbufSetStatic(&item->conn.security, "WPA2");
|
||||
} else if (wpa.i_protos & IEEE80211_WPA_PROTO_WPA1) {
|
||||
ffStrbufSetStatic(&item->conn.security, "WPA");
|
||||
} else {
|
||||
ffStrbufSetStatic(&item->conn.security, "Unknown");
|
||||
}
|
||||
} else {
|
||||
struct ieee80211_nwkey nwkey = {};
|
||||
strlcpy(nwkey.i_name, i->if_name, sizeof(nwkey.i_name));
|
||||
|
||||
if (ioctl(sock, SIOCG80211NWKEY, &nwkey) >= 0) {
|
||||
if (nwkey.i_wepon) {
|
||||
ffStrbufSetStatic(&item->conn.security, "WEP");
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
#include "wifi.h"
|
||||
#include "common/io.h"
|
||||
#include "common/mallocHelper.h"
|
||||
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <stropts.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <inet/wifi_ioctl.h>
|
||||
|
||||
static bool getWifiValue(int fd, uint32_t id, void* value, size_t valueSize, wldp_t* buffer) {
|
||||
memset(buffer, 0, MAX_BUF_LEN);
|
||||
buffer->wldp_type = NET_802_11;
|
||||
buffer->wldp_id = id;
|
||||
|
||||
struct strioctl request = {
|
||||
.ic_cmd = WLAN_GET_PARAM,
|
||||
.ic_timout = 0,
|
||||
.ic_len = MAX_BUF_LEN,
|
||||
.ic_dp = (char*) buffer,
|
||||
};
|
||||
|
||||
if (ioctl(fd, I_STR, &request) != 0 || buffer->wldp_result != WL_SUCCESS || buffer->wldp_length < WIFI_BUF_OFFSET + valueSize || buffer->wldp_length > MAX_BUF_LEN) {
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(value, buffer->wldp_buf, valueSize);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void setWifiProtocol(FFstrbuf* protocol, uint32_t subtype, bool htEnabled) {
|
||||
switch (subtype) {
|
||||
case WL_FHSS:
|
||||
ffStrbufSetStatic(protocol, "802.11 (FHSS)");
|
||||
break;
|
||||
case WL_DSSS:
|
||||
ffStrbufSetStatic(protocol, "802.11 (DSSS)");
|
||||
break;
|
||||
case WL_IRBASE:
|
||||
ffStrbufSetStatic(protocol, "802.11 (IR)");
|
||||
break;
|
||||
case WL_OFDM:
|
||||
ffStrbufSetStatic(protocol, htEnabled ? "802.11n (Wi-Fi 4)" : "802.11a");
|
||||
break;
|
||||
case WL_HRDS:
|
||||
ffStrbufSetStatic(protocol, "802.11b");
|
||||
break;
|
||||
case WL_ERP:
|
||||
ffStrbufSetStatic(protocol, htEnabled ? "802.11n (Wi-Fi 4)" : "802.11g");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void setWifiSecurity(FFstrbuf* security, uint32_t encryption, bool haveAuthMode, uint32_t authMode) {
|
||||
switch (encryption) {
|
||||
case WL_NOENCRYPTION:
|
||||
ffStrbufSetStatic(security, "Insecure");
|
||||
break;
|
||||
case WL_ENC_WEP:
|
||||
ffStrbufSetStatic(security, "WEP");
|
||||
break;
|
||||
case WL_ENC_WPA:
|
||||
ffStrbufSetStatic(security, haveAuthMode && authMode == WL_SHAREDKEY ? "WPA Shared Key" : "WPA");
|
||||
break;
|
||||
default:
|
||||
ffStrbufSetStatic(security, "Unknown");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const char* ffDetectWifi(FFlist* result) {
|
||||
// https://github.com/illumos/illumos-gate/blob/master/usr/src/cmd/cmd-inet/usr.sbin/wificonfig/wificonfig.c
|
||||
|
||||
FF_AUTO_CLOSE_DIR DIR* directory = opendir("/dev/wifi");
|
||||
if (!directory) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FF_AUTO_FREE void* bufferMemory = malloc(MAX_BUF_LEN);
|
||||
if (!bufferMemory) {
|
||||
return "malloc() failed";
|
||||
}
|
||||
wldp_t* buffer = (wldp_t*) bufferMemory;
|
||||
|
||||
struct dirent* entry;
|
||||
while ((entry = readdir(directory)) != nullptr) {
|
||||
size_t nameLength = strlen(entry->d_name);
|
||||
if (nameLength == 0 || entry->d_name[nameLength - 1] < '0' || entry->d_name[nameLength - 1] > '9') {
|
||||
continue;
|
||||
}
|
||||
|
||||
char devicePath[PATH_MAX];
|
||||
int pathLength = snprintf(devicePath, sizeof(devicePath), "/dev/wifi/%s", entry->d_name);
|
||||
if (pathLength < 0 || (size_t) pathLength >= sizeof(devicePath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
FF_AUTO_CLOSE_FD int fd = open(devicePath, O_RDWR | O_CLOEXEC);
|
||||
if (fd < 0) {
|
||||
continue;
|
||||
}
|
||||
if (!isastream(fd)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
FFWifiResult* item = FF_LIST_ADD(FFWifiResult, *result);
|
||||
ffStrbufInitS(&item->inf.description, entry->d_name);
|
||||
ffStrbufInit(&item->inf.status);
|
||||
ffStrbufInit(&item->conn.status);
|
||||
ffStrbufInit(&item->conn.ssid);
|
||||
ffStrbufInit(&item->conn.bssid);
|
||||
ffStrbufInit(&item->conn.protocol);
|
||||
ffStrbufInit(&item->conn.security);
|
||||
item->conn.signalQuality = -DBL_MAX;
|
||||
item->conn.rxRate = -DBL_MAX;
|
||||
item->conn.txRate = -DBL_MAX;
|
||||
item->conn.channel = 0;
|
||||
item->conn.channelWidth = 0;
|
||||
item->conn.frequency = 0;
|
||||
|
||||
wl_radio_t radio;
|
||||
if (getWifiValue(fd, WL_RADIO, &radio, sizeof(radio), buffer)) {
|
||||
ffStrbufSetStatic(&item->inf.status, radio ? "Up" : "Down");
|
||||
} else {
|
||||
ffStrbufSetStatic(&item->inf.status, "Unknown");
|
||||
}
|
||||
|
||||
wl_linkstatus_t linkStatus;
|
||||
if (!getWifiValue(fd, WL_LINKSTATUS, &linkStatus, sizeof(linkStatus), buffer)) {
|
||||
ffStrbufSetStatic(&item->conn.status, "Unknown");
|
||||
continue;
|
||||
}
|
||||
if (linkStatus != WL_CONNECTED) {
|
||||
ffStrbufSetStatic(&item->conn.status, "Not connected");
|
||||
continue;
|
||||
}
|
||||
ffStrbufSetStatic(&item->conn.status, "Connected");
|
||||
|
||||
wl_essid_t essid;
|
||||
if (getWifiValue(fd, WL_ESSID, &essid, sizeof(essid), buffer) && essid.wl_essid_length > 0 && essid.wl_essid_length < sizeof(essid.wl_essid_essid) && essid.wl_essid_length <= MAX_ESSID_LENGTH - 1) {
|
||||
ffStrbufSetNS(&item->conn.ssid, essid.wl_essid_length, essid.wl_essid_essid);
|
||||
}
|
||||
|
||||
wl_bssid_t bssid;
|
||||
if (getWifiValue(fd, WL_BSSID, bssid, sizeof(bssid), buffer)) {
|
||||
ffStrbufSetF(&item->conn.bssid, "%02X:%02X:%02X:%02X:%02X:%02X", (uint8_t) bssid[0], (uint8_t) bssid[1], (uint8_t) bssid[2], (uint8_t) bssid[3], (uint8_t) bssid[4], (uint8_t) bssid[5]);
|
||||
}
|
||||
|
||||
wl_rssi_t rssi;
|
||||
if (getWifiValue(fd, WL_RSSI, &rssi, sizeof(rssi), buffer)) {
|
||||
item->conn.signalQuality = rssi >= MAX_RSSI ? 100.0 : ((double) rssi / MAX_RSSI) * 100.0;
|
||||
}
|
||||
|
||||
wl_phy_conf_t phy;
|
||||
if (getWifiValue(fd, WL_PHY_CONFIG, &phy, sizeof(phy), buffer)) {
|
||||
uint32_t subtype = phy.wl_phy_dsss_conf.wl_dsss_subtype;
|
||||
switch (subtype) {
|
||||
case WL_FHSS:
|
||||
item->conn.channel = (uint16_t) phy.wl_phy_fhss_conf.wl_fhss_channel;
|
||||
setWifiProtocol(&item->conn.protocol, subtype, false);
|
||||
break;
|
||||
case WL_DSSS:
|
||||
item->conn.channel = (uint16_t) phy.wl_phy_dsss_conf.wl_dsss_channel;
|
||||
setWifiProtocol(&item->conn.protocol, subtype, false);
|
||||
break;
|
||||
case WL_OFDM:
|
||||
item->conn.frequency = (uint16_t) phy.wl_phy_ofdm_conf.wl_ofdm_frequency;
|
||||
item->conn.channel = ffWifiFreqToChannel(item->conn.frequency);
|
||||
setWifiProtocol(&item->conn.protocol, subtype, phy.wl_phy_ofdm_conf.wl_ofdm_ht_enabled);
|
||||
break;
|
||||
case WL_ERP:
|
||||
item->conn.channel = (uint16_t) phy.wl_phy_erp_conf.wl_erp_channel;
|
||||
setWifiProtocol(&item->conn.protocol, subtype, phy.wl_phy_erp_conf.wl_erp_ht_enabled);
|
||||
break;
|
||||
default:
|
||||
setWifiProtocol(&item->conn.protocol, subtype, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
wl_encryption_t encryption;
|
||||
wl_authmode_t authMode;
|
||||
bool haveEncryption = getWifiValue(fd, WL_ENCRYPTION, &encryption, sizeof(encryption), buffer);
|
||||
bool haveAuthMode = getWifiValue(fd, WL_AUTH_MODE, &authMode, sizeof(authMode), buffer);
|
||||
if (haveEncryption) {
|
||||
setWifiSecurity(&item->conn.security, encryption, haveAuthMode, authMode);
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
@@ -5,41 +5,59 @@
|
||||
#include <windows.h>
|
||||
#include <wlanapi.h>
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wswitch"
|
||||
|
||||
static void convertIfStateToString(WLAN_INTERFACE_STATE state, FFstrbuf* result) {
|
||||
switch (state) {
|
||||
case wlan_interface_state_not_ready:
|
||||
ffStrbufAppendS(result, "Not ready");
|
||||
ffStrbufSetStatic(result, "Not ready");
|
||||
break;
|
||||
case wlan_interface_state_connected:
|
||||
ffStrbufAppendS(result, "Connected");
|
||||
ffStrbufSetStatic(result, "Connected");
|
||||
break;
|
||||
case wlan_interface_state_ad_hoc_network_formed:
|
||||
ffStrbufAppendS(result, "Ad hoc network formed");
|
||||
ffStrbufSetStatic(result, "Ad hoc network formed");
|
||||
break;
|
||||
case wlan_interface_state_disconnecting:
|
||||
ffStrbufAppendS(result, "Disconnecting");
|
||||
ffStrbufSetStatic(result, "Disconnecting");
|
||||
break;
|
||||
case wlan_interface_state_disconnected:
|
||||
ffStrbufAppendS(result, "Disconnected");
|
||||
ffStrbufSetStatic(result, "Disconnected");
|
||||
break;
|
||||
case wlan_interface_state_associating:
|
||||
ffStrbufAppendS(result, "Associating");
|
||||
ffStrbufSetStatic(result, "Associating");
|
||||
break;
|
||||
case wlan_interface_state_discovering:
|
||||
ffStrbufAppendS(result, "Discovering");
|
||||
ffStrbufSetStatic(result, "Discovering");
|
||||
break;
|
||||
case wlan_interface_state_authenticating:
|
||||
ffStrbufAppendS(result, "Authenticating");
|
||||
ffStrbufSetStatic(result, "Authenticating");
|
||||
break;
|
||||
default:
|
||||
ffStrbufAppendS(result, "Unknown");
|
||||
ffStrbufSetStatic(result, "Unknown");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct _WLAN_REALTIME_CONNECTION_QUALITY_LINK_INFO {
|
||||
UCHAR ucLinkID;
|
||||
ULONG ulChannelCenterFrequencyMhz;
|
||||
ULONG ulBandwidth;
|
||||
LONG lRssi;
|
||||
WLAN_RATE_SET wlanRateSet;
|
||||
} WLAN_REALTIME_CONNECTION_QUALITY_LINK_INFO, *PWLAN_REALTIME_CONNECTION_QUALITY_LINK_INFO;
|
||||
|
||||
typedef struct _WLAN_REALTIME_CONNECTION_QUALITY {
|
||||
DOT11_PHY_TYPE dot11PhyType;
|
||||
ULONG ulLinkQuality;
|
||||
ULONG ulRxRate;
|
||||
ULONG ulTxRate;
|
||||
BOOL bIsMLOConnection;
|
||||
ULONG ulNumLinks;
|
||||
// Array of size ulNumLinks
|
||||
WLAN_REALTIME_CONNECTION_QUALITY_LINK_INFO linksInfo[];
|
||||
} WLAN_REALTIME_CONNECTION_QUALITY, *PWLAN_REALTIME_CONNECTION_QUALITY;
|
||||
|
||||
enum { wlan_intf_opcode_realtime_connection_quality = 19 };
|
||||
|
||||
const char* ffDetectWifi(FFlist* result) {
|
||||
FF_LIBRARY_LOAD_MESSAGE(wlanapi, "wlanapi" FF_LIBRARY_EXTENSION, 1)
|
||||
FF_LIBRARY_LOAD_SYMBOL_MESSAGE(wlanapi, WlanOpenHandle)
|
||||
@@ -54,7 +72,7 @@ const char* ffDetectWifi(FFlist* result) {
|
||||
WLAN_INTERFACE_INFO_LIST* ifList = nullptr;
|
||||
const char* error = nullptr;
|
||||
|
||||
if (ffWlanOpenHandle(2, nullptr, &curVersion, &hClient) != ERROR_SUCCESS) {
|
||||
if (ffWlanOpenHandle(2 /*maxClientVersion*/, nullptr, &curVersion, &hClient) != ERROR_SUCCESS) {
|
||||
error = "WlanOpenHandle() failed";
|
||||
goto exit;
|
||||
}
|
||||
@@ -79,6 +97,7 @@ const char* ffDetectWifi(FFlist* result) {
|
||||
item->conn.rxRate = -DBL_MAX;
|
||||
item->conn.txRate = -DBL_MAX;
|
||||
item->conn.channel = 0;
|
||||
item->conn.channelWidth = 0;
|
||||
item->conn.frequency = 0;
|
||||
|
||||
convertIfStateToString(ifInfo->isState, &item->inf.status);
|
||||
@@ -200,17 +219,44 @@ const char* ffDetectWifi(FFlist* result) {
|
||||
ffStrbufAppendS(&item->conn.security, "Insecure");
|
||||
}
|
||||
|
||||
WLAN_BSS_LIST* bssList = nullptr;
|
||||
if (ffWlanGetNetworkBssList(hClient,
|
||||
WLAN_REALTIME_CONNECTION_QUALITY* connectionQuality = nullptr;
|
||||
bufSize = 0;
|
||||
if (ffWlanQueryInterface(hClient,
|
||||
&ifInfo->InterfaceGuid,
|
||||
&connInfo->wlanAssociationAttributes.dot11Ssid,
|
||||
connInfo->wlanAssociationAttributes.dot11BssType,
|
||||
connInfo->wlanSecurityAttributes.bSecurityEnabled,
|
||||
wlan_intf_opcode_realtime_connection_quality,
|
||||
nullptr,
|
||||
&bssList) == ERROR_SUCCESS &&
|
||||
bssList->dwNumberOfItems > 0) {
|
||||
item->conn.frequency = (uint16_t) (bssList->wlanBssEntries[0].ulChCenterFrequency / 1000);
|
||||
ffWlanFreeMemory(bssList);
|
||||
&bufSize,
|
||||
(PVOID*) &connectionQuality,
|
||||
&opCode) == ERROR_SUCCESS) {
|
||||
const WLAN_REALTIME_CONNECTION_QUALITY_LINK_INFO* bestLink = nullptr;
|
||||
for (ULONG linkIndex = 0; linkIndex < connectionQuality->ulNumLinks; ++linkIndex) {
|
||||
const WLAN_REALTIME_CONNECTION_QUALITY_LINK_INFO* link = &connectionQuality->linksInfo[linkIndex];
|
||||
if (!bestLink || link->lRssi > bestLink->lRssi) {
|
||||
bestLink = link;
|
||||
}
|
||||
}
|
||||
if (bestLink) {
|
||||
item->conn.channelWidth = (uint16_t) bestLink->ulBandwidth;
|
||||
item->conn.frequency = (uint16_t) bestLink->ulChannelCenterFrequencyMhz;
|
||||
}
|
||||
}
|
||||
if (connectionQuality) {
|
||||
ffWlanFreeMemory(connectionQuality);
|
||||
}
|
||||
|
||||
if (item->conn.frequency == 0) {
|
||||
WLAN_BSS_LIST* bssList = nullptr;
|
||||
if (ffWlanGetNetworkBssList(hClient,
|
||||
&ifInfo->InterfaceGuid,
|
||||
&connInfo->wlanAssociationAttributes.dot11Ssid,
|
||||
connInfo->wlanAssociationAttributes.dot11BssType,
|
||||
connInfo->wlanSecurityAttributes.bSecurityEnabled,
|
||||
nullptr,
|
||||
&bssList) == ERROR_SUCCESS &&
|
||||
bssList->dwNumberOfItems > 0) {
|
||||
item->conn.frequency = (uint16_t) (bssList->wlanBssEntries[0].ulChCenterFrequency / 1000);
|
||||
ffWlanFreeMemory(bssList);
|
||||
}
|
||||
}
|
||||
|
||||
ffWlanFreeMemory(connInfo);
|
||||
@@ -238,5 +284,3 @@ exit:
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
@@ -11,16 +11,23 @@
|
||||
|
||||
const char* ffDetectWMPlugin(FFstrbuf* pluginName) {
|
||||
int request[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL };
|
||||
u_int requestLength = ARRAY_SIZE(request);
|
||||
size_t length;
|
||||
|
||||
size_t length = 0;
|
||||
FF_AUTO_FREE struct kinfo_proc* processes = ffSysctlGetData(request, requestLength, &length);
|
||||
if (processes == nullptr) {
|
||||
return "sysctl(CTL_KERN, KERN_PROC, KERN_PROC_ALL) failed";
|
||||
if (sysctl(request, ARRAY_SIZE(request), nullptr, &length, nullptr, 0) != 0) {
|
||||
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_ALL, nullptr}) failed";
|
||||
}
|
||||
assert(length % sizeof(struct kinfo_proc) == 0);
|
||||
|
||||
for (size_t i = 0; i < length / sizeof(struct kinfo_proc); i++) {
|
||||
// The process table may change between the two sysctl calls; retry with a larger buffer.
|
||||
length += length / 8 + sizeof(struct kinfo_proc);
|
||||
FF_AUTO_FREE struct kinfo_proc* processes = malloc(length);
|
||||
|
||||
if (sysctl(request, ARRAY_SIZE(request), processes, &length, nullptr, 0) != 0) {
|
||||
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_ALL, processes}) failed";
|
||||
}
|
||||
|
||||
uint32_t count = (uint32_t) (length / sizeof(struct kinfo_proc));
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
const struct kinfo_proc* proc = &processes[i];
|
||||
if (proc->kp_eproc.e_ppid != 1) {
|
||||
continue;
|
||||
|
||||
@@ -43,9 +43,8 @@ static const char* getHyprland(FFstrbuf* result) {
|
||||
FF_DEBUG("Detecting Hyprland version");
|
||||
FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
|
||||
|
||||
FF_DEBUG("Checking for " FASTFETCH_TARGET_DIR_USR "/include/hyprland/src/version.h"
|
||||
" file");
|
||||
if (ffReadFileBuffer(FASTFETCH_TARGET_DIR_USR "/include/hyprland/src/version.h", result)) {
|
||||
FF_DEBUG("Checking for " FF_PATH_PKG_BASE "/include/hyprland/src/version.h file");
|
||||
if (ffReadFileBuffer(FF_PATH_PKG_BASE "/include/hyprland/src/version.h", result)) {
|
||||
FF_DEBUG("Found version.h file, extracting version");
|
||||
if (ffStrbufSubstrAfterFirstS(result, "\n#define GIT_TAG ")) {
|
||||
ffStrbufSubstrAfterFirstC(result, '"');
|
||||
|
||||
@@ -633,6 +633,18 @@ static const FFlogo A[] = {
|
||||
.colorTitle = FF_COLOR_FG_CYAN,
|
||||
},
|
||||
#endif
|
||||
#ifdef FASTFETCH_DATATEXT_LOGO_ARTIX2
|
||||
// Artix2
|
||||
{
|
||||
.names = { "artix2" },
|
||||
.lines = FASTFETCH_DATATEXT_LOGO_ARTIX2,
|
||||
.colors = {
|
||||
FF_COLOR_FG_CYAN,
|
||||
},
|
||||
.colorKeys = FF_COLOR_FG_CYAN,
|
||||
.colorTitle = FF_COLOR_FG_CYAN,
|
||||
},
|
||||
#endif
|
||||
#ifdef FASTFETCH_DATATEXT_LOGO_ARTIX_SMALL
|
||||
// ArtixSmall
|
||||
{
|
||||
|
||||
+20
-20
@@ -1,20 +1,20 @@
|
||||
.:
|
||||
.==:
|
||||
.====:
|
||||
.======:
|
||||
.========:
|
||||
.==========:
|
||||
.============:
|
||||
.-===========:
|
||||
.:-========:
|
||||
.-:. .:======:
|
||||
.======:.. :-===.
|
||||
.============:.. .:=.
|
||||
.==================:..
|
||||
.=======================:
|
||||
.====================::. .
|
||||
================::. :-===
|
||||
============-:. .:========
|
||||
========-:. :-==========
|
||||
=====:. ..:-=====
|
||||
=::. ..::
|
||||
.:
|
||||
.==:
|
||||
.====:
|
||||
.======:
|
||||
.========:
|
||||
.==========:
|
||||
.============:
|
||||
.-===========:
|
||||
.:-========:
|
||||
.-:. .:======:
|
||||
.======:.. :-===.
|
||||
.============:.. .:=.
|
||||
.==================:..
|
||||
.=======================:
|
||||
.====================::. .
|
||||
================::. :-===
|
||||
============-:. .:========
|
||||
========-:. :-==========
|
||||
=====:. ..:-=====
|
||||
=::. ..::
|
||||
@@ -103,6 +103,19 @@ static const FFlogo M[] = {
|
||||
.colorTitle = FF_COLOR_FG_GREEN,
|
||||
},
|
||||
#endif
|
||||
#ifdef FASTFETCH_DATATEXT_LOGO_MACOS_OLD
|
||||
// MacOS_old
|
||||
{
|
||||
.names = { "macOS_old" },
|
||||
.type = FF_LOGO_LINE_TYPE_ALTER_BIT,
|
||||
.lines = FASTFETCH_DATATEXT_LOGO_MACOS_OLD,
|
||||
.colors = {
|
||||
FF_COLOR_FG_CYAN,
|
||||
FF_COLOR_FG_BLUE,
|
||||
FF_COLOR_FG_DEFAULT,
|
||||
},
|
||||
},
|
||||
#endif
|
||||
#ifdef FASTFETCH_DATATEXT_LOGO_MAINSAILOS
|
||||
// MainsailOS
|
||||
{
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
$3@@
|
||||
$3@@
|
||||
$2((((((((((((((((((((($3@@$1******************
|
||||
$2(((((((((((((((((((($3@@$1*******************
|
||||
$2(((((((((($3%%$2((((((($3@@$1********$3@@$1**********
|
||||
$2(((((((((($3%%$2(((((($3@@$1*********$3@@$1**********
|
||||
$2((((((((((((((((($3@@$1**********************
|
||||
$2(((((((((((((((($3@@$1***********************
|
||||
$2((((((((((((((($3@@$1************************
|
||||
$2(((((((((((((($3@@@@@@@@$1*******************
|
||||
$2(((((((((((((((((((($3@@$1*******************
|
||||
$2(((((((($3%%%%%%%%%%%@@@@@@@@@@@@@@$1********
|
||||
$2(((((((((((((((((((($3@@$1*******************
|
||||
$2((((((((((((((((((((($3@@$1******************
|
||||
$3@@
|
||||
@@
|
||||
@@
|
||||
@@ -37,6 +37,31 @@ static const FFlogo O[] = {
|
||||
},
|
||||
},
|
||||
#endif
|
||||
#ifdef FASTFETCH_DATATEXT_LOGO_OMARCHY
|
||||
// Omarchy
|
||||
{
|
||||
.names = { "Omarchy" },
|
||||
.lines = FASTFETCH_DATATEXT_LOGO_OMARCHY,
|
||||
.colors = {
|
||||
FF_COLOR_FG_256 "149",
|
||||
},
|
||||
.colorKeys = FF_COLOR_FG_256 "149",
|
||||
.colorTitle = FF_COLOR_FG_256 "111",
|
||||
},
|
||||
#endif
|
||||
#ifdef FASTFETCH_DATATEXT_LOGO_OMARCHY2
|
||||
// Omarchy2
|
||||
{
|
||||
.names = { "Omarchy2" },
|
||||
.lines = FASTFETCH_DATATEXT_LOGO_OMARCHY2,
|
||||
.type = FF_LOGO_LINE_TYPE_ALTER_BIT,
|
||||
.colors = {
|
||||
FF_COLOR_FG_256 "149",
|
||||
},
|
||||
.colorKeys = FF_COLOR_FG_256 "149",
|
||||
.colorTitle = FF_COLOR_FG_256 "111",
|
||||
},
|
||||
#endif
|
||||
#ifdef FASTFETCH_DATATEXT_LOGO_OMNIOS
|
||||
// OmniOS
|
||||
{
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
██████████████████████████████████
|
||||
██ ██ ██
|
||||
██ █████████████ ████ ██
|
||||
██ ██ ██ ██
|
||||
██ ██ ██ ██
|
||||
██ ██ ██ ██
|
||||
██ ██ ██ ██
|
||||
███████ ██ ██
|
||||
██ ██ ██ ██
|
||||
██ ██ ██ ██
|
||||
██ ██ ██ ██
|
||||
██ ██ ██ ██
|
||||
██ ████████████████████████ ██
|
||||
██ ██ ██
|
||||
██████████████████ █████████████
|
||||
@@ -0,0 +1,10 @@
|
||||
▄▄▄
|
||||
▄█████▄ ▄███████████▄ ▄███████ ▄███████ ▄███████ ▄█ █▄ ▄█ █▄
|
||||
███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███
|
||||
███ ███ ███ ███ ███ ███ ███ ███ ███ ███ █▀ ███ ███ ███ ███
|
||||
███ ███ ███ ███ ███ ▄███▄▄▄███ ▄███▄▄▄██▀ ███ ▄███▄▄▄███▄ ███▄▄▄███
|
||||
███ ███ ███ ███ ███ ▀███▀▀▀███ ▀███▀▀▀▀ ███ ▀▀███▀▀▀███ ▀▀▀▀▀▀███
|
||||
███ ███ ███ ███ ███ ███ ███ ██████████ ███ █▄ ███ ███ ▄██ ███
|
||||
███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███
|
||||
▀█████▀ ▀█ ███ █▀ ███ █▀ ███ ███ ███████▀ ███ █▀ ▀█████▀
|
||||
███ █▀
|
||||
@@ -11,9 +11,9 @@ static void printBattery(FFBatteryOptions* options, FFBatteryResult* result, uin
|
||||
FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate();
|
||||
if (options->moduleArgs.key.length == 0) {
|
||||
if (result->modelName.length > 0) {
|
||||
ffStrbufSetF(&key, "%s (%s)", FF_BATTERY_MODULE_NAME, result->modelName.chars);
|
||||
ffStrbufSetF(&key, "%s (%s)", FF_MODULE_GET_DISPLAY_NAME(Battery), result->modelName.chars);
|
||||
} else {
|
||||
ffStrbufSetS(&key, FF_BATTERY_MODULE_NAME);
|
||||
ffStrbufSetS(&key, FF_MODULE_GET_DISPLAY_NAME(Battery));
|
||||
}
|
||||
} else {
|
||||
ffStrbufClear(&key);
|
||||
@@ -170,11 +170,11 @@ bool ffPrintBattery(FFBatteryOptions* options) {
|
||||
const char* error = ffDetectBattery(options, &results);
|
||||
|
||||
if (error) {
|
||||
ffPrintError(FF_BATTERY_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error);
|
||||
ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Battery), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error);
|
||||
return false;
|
||||
}
|
||||
if (results.length == 0) {
|
||||
ffPrintError(FF_BATTERY_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", "No batteries found");
|
||||
ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Battery), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", "No batteries found");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -209,7 +209,7 @@ void ffParseBatteryJsonObject(FFBatteryOptions* options, yyjson_val* module) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ffPrintError(FF_BATTERY_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key));
|
||||
ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Battery), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,8 +297,30 @@ void ffDestroyBatteryOptions(FFBatteryOptions* options) {
|
||||
}
|
||||
|
||||
FFModuleBaseInfo ffBatteryModuleInfo = {
|
||||
.name = FF_BATTERY_MODULE_NAME,
|
||||
.name = "Battery",
|
||||
.description = "Print battery information",
|
||||
.displayName = {
|
||||
.en = "Battery",
|
||||
.ar = "البطارية",
|
||||
.cs = "Baterie",
|
||||
.de = "Akku",
|
||||
.es = "Batería",
|
||||
.fr = "Batterie",
|
||||
.gl = "Batería",
|
||||
.he = "סוללה",
|
||||
.id = "Baterai",
|
||||
.it = "Batteria",
|
||||
.ja = "バッテリー",
|
||||
.ko = "배터리",
|
||||
.pl = "Bateria",
|
||||
.pt = "Bateria",
|
||||
.ru = "Батарея",
|
||||
.tr = "Pil",
|
||||
.uk = "Батарея",
|
||||
.vi = "Pin",
|
||||
.zh_CN = "电池",
|
||||
.zh_TW = "電池",
|
||||
},
|
||||
.initOptions = (void*) ffInitBatteryOptions,
|
||||
.destroyOptions = (void*) ffDestroyBatteryOptions,
|
||||
.parseJsonObject = (void*) ffParseBatteryJsonObject,
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
#include "option.h"
|
||||
|
||||
#define FF_BATTERY_MODULE_NAME "Battery"
|
||||
|
||||
bool ffPrintBattery(FFBatteryOptions* options);
|
||||
void ffInitBatteryOptions(FFBatteryOptions* options);
|
||||
void ffDestroyBatteryOptions(FFBatteryOptions* options);
|
||||
|
||||
+27
-5
@@ -18,12 +18,12 @@ bool ffPrintBios(FFBiosOptions* options) {
|
||||
FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate();
|
||||
|
||||
if (error) {
|
||||
ffPrintError(FF_BIOS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error);
|
||||
ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Bios), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (bios.version.length == 0) {
|
||||
ffPrintError(FF_BIOS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "bios_version is not set.");
|
||||
ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Bios), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "bios_version is not set.");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ bool ffPrintBios(FFBiosOptions* options) {
|
||||
ffStrbufSetStatic(&bios.type, "Legacy");
|
||||
}
|
||||
|
||||
ffStrbufSetF(&key, FF_BIOS_MODULE_NAME " (%s)", bios.type.chars);
|
||||
ffStrbufSetF(&key, "%s (%s)", FF_MODULE_GET_DISPLAY_NAME(Bios), bios.type.chars);
|
||||
} else {
|
||||
ffStrbufClear(&key);
|
||||
FF_PARSE_FORMAT_STRING_CHECKED(&key, &options->moduleArgs.key, ((FFformatarg[]) {
|
||||
@@ -80,7 +80,7 @@ void ffParseBiosJsonObject(FFBiosOptions* options, yyjson_val* module) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ffPrintError(FF_BIOS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key));
|
||||
ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Bios), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,8 +130,30 @@ void ffDestroyBiosOptions(FFBiosOptions* options) {
|
||||
}
|
||||
|
||||
FFModuleBaseInfo ffBiosModuleInfo = {
|
||||
.name = FF_BIOS_MODULE_NAME,
|
||||
.name = "BIOS",
|
||||
.description = "Print first-stage bootloader information (name, version, release date, etc.)",
|
||||
.displayName = {
|
||||
.en = "BIOS",
|
||||
.ar = "BIOS",
|
||||
.cs = "BIOS",
|
||||
.de = "BIOS",
|
||||
.es = "BIOS",
|
||||
.fr = "BIOS",
|
||||
.gl = "BIOS",
|
||||
.he = "BIOS",
|
||||
.id = "BIOS",
|
||||
.it = "BIOS",
|
||||
.ja = "BIOS",
|
||||
.ko = "BIOS",
|
||||
.pl = "BIOS",
|
||||
.pt = "BIOS",
|
||||
.ru = "BIOS",
|
||||
.tr = "BIOS",
|
||||
.uk = "BIOS",
|
||||
.vi = "BIOS",
|
||||
.zh_CN = "BIOS",
|
||||
.zh_TW = "BIOS",
|
||||
},
|
||||
.initOptions = (void*) ffInitBiosOptions,
|
||||
.destroyOptions = (void*) ffDestroyBiosOptions,
|
||||
.parseJsonObject = (void*) ffParseBiosJsonObject,
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
#include "option.h"
|
||||
|
||||
#define FF_BIOS_MODULE_NAME "BIOS"
|
||||
|
||||
bool ffPrintBios(FFBiosOptions* options);
|
||||
void ffInitBiosOptions(FFBiosOptions* options);
|
||||
void ffDestroyBiosOptions(FFBiosOptions* options);
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
static void printDevice(FFBluetoothOptions* options, const FFBluetoothResult* device, uint8_t index) {
|
||||
FFPercentageTypeFlags percentType = options->percent.type == 0 ? instance.config.display.percentType : options->percent.type;
|
||||
if (options->moduleArgs.outputFormat.length == 0) {
|
||||
ffPrintLogoAndKey(FF_BLUETOOTH_MODULE_NAME, index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);
|
||||
ffPrintLogoAndKey(FF_MODULE_GET_DISPLAY_NAME(Bluetooth), index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);
|
||||
|
||||
FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
|
||||
bool showBatteryLevel = device->battery > 0 && device->battery <= 100;
|
||||
@@ -44,7 +44,7 @@ static void printDevice(FFBluetoothOptions* options, const FFBluetoothResult* de
|
||||
ffPercentAppendBar(&percentageBar, device->battery, options->percent, &options->moduleArgs);
|
||||
}
|
||||
|
||||
FF_PRINT_FORMAT_CHECKED(FF_BLUETOOTH_MODULE_NAME, index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) {
|
||||
FF_PRINT_FORMAT_CHECKED(FF_MODULE_GET_DISPLAY_NAME(Bluetooth), index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) {
|
||||
FF_ARG(device->name, "name"),
|
||||
FF_ARG(device->address, "address"),
|
||||
FF_ARG(device->type, "type"),
|
||||
@@ -60,12 +60,12 @@ bool ffPrintBluetooth(FFBluetoothOptions* options) {
|
||||
const char* error = ffDetectBluetooth(options, &devices);
|
||||
|
||||
if (error) {
|
||||
ffPrintError(FF_BLUETOOTH_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error);
|
||||
ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Bluetooth), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (devices.length == 0) {
|
||||
ffPrintError(FF_BLUETOOTH_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "No bluetooth devices found");
|
||||
ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Bluetooth), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "No bluetooth devices found");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ void ffParseBluetoothJsonObject(FFBluetoothOptions* options, yyjson_val* module)
|
||||
continue;
|
||||
}
|
||||
|
||||
ffPrintError(FF_BLUETOOTH_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key));
|
||||
ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Bluetooth), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,8 +151,30 @@ void ffDestroyBluetoothOptions(FFBluetoothOptions* options) {
|
||||
}
|
||||
|
||||
FFModuleBaseInfo ffBluetoothModuleInfo = {
|
||||
.name = FF_BLUETOOTH_MODULE_NAME,
|
||||
.name = "Bluetooth",
|
||||
.description = "List connected Bluetooth devices",
|
||||
.displayName = {
|
||||
.en = "Bluetooth",
|
||||
.ar = "بلوتوث",
|
||||
.cs = "Bluetooth",
|
||||
.de = "Bluetooth",
|
||||
.es = "Bluetooth",
|
||||
.fr = "Bluetooth",
|
||||
.gl = "Bluetooth",
|
||||
.he = "בלוטות'",
|
||||
.id = "Bluetooth",
|
||||
.it = "Bluetooth",
|
||||
.ja = "ブルートゥース",
|
||||
.ko = "블루투스",
|
||||
.pl = "Bluetooth",
|
||||
.pt = "Bluetooth",
|
||||
.ru = "Bluetooth",
|
||||
.tr = "Bluetooth",
|
||||
.uk = "Bluetooth",
|
||||
.vi = "Bluetooth",
|
||||
.zh_CN = "蓝牙设备",
|
||||
.zh_TW = "藍牙裝置",
|
||||
},
|
||||
.initOptions = (void*) ffInitBluetoothOptions,
|
||||
.destroyOptions = (void*) ffDestroyBluetoothOptions,
|
||||
.parseJsonObject = (void*) ffParseBluetoothJsonObject,
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
#include "option.h"
|
||||
|
||||
#define FF_BLUETOOTH_MODULE_NAME "Bluetooth"
|
||||
|
||||
bool ffPrintBluetooth(FFBluetoothOptions* options);
|
||||
void ffInitBluetoothOptions(FFBluetoothOptions* options);
|
||||
void ffDestroyBluetoothOptions(FFBluetoothOptions* options);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user