mirror of
https://github.com/fastfetch-cli/fastfetch.git
synced 2026-09-12 10:22:12 +02:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 37ec63d1bd |
@@ -1,405 +0,0 @@
|
||||
<!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>
|
||||
@@ -1,55 +0,0 @@
|
||||
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@faa0c6197e94aacf1c5956460152c8380d3560a5 # v1.5.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@faa0c6197e94aacf1c5956460152c8380d3560a5 # v1.5.0
|
||||
uses: cross-platform-actions/action@24ef01df165c76df1ed2b9f9e9212e78dc2fc963 # v1.4.0
|
||||
with:
|
||||
operating_system: freebsd
|
||||
version: '15.1'
|
||||
|
||||
@@ -18,10 +18,10 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Start VM
|
||||
uses: cross-platform-actions/action@faa0c6197e94aacf1c5956460152c8380d3560a5 # v1.5.0
|
||||
uses: cross-platform-actions/action@24ef01df165c76df1ed2b9f9e9212e78dc2fc963 # v1.4.0
|
||||
with:
|
||||
operating_system: haiku
|
||||
version: 'r1beta6'
|
||||
version: 'r1beta5'
|
||||
shell: bash
|
||||
cpu_count: 4
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: run VM
|
||||
uses: uraimo/run-on-arch-action@460cb8e6d9f726a588fc9b5e681c8a6cab09ae41 # v3.2.0
|
||||
uses: uraimo/run-on-arch-action@f9b26e3a1a408d5fd530d20c17b9f3f4428ff8d9 # v3.1.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 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
|
||||
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
|
||||
|
||||
- name: install linuxbrew packages
|
||||
run: |
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
|
||||
- name: Initialize CodeQL
|
||||
if: inputs.arch == 'amd64'
|
||||
uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8
|
||||
uses: github/codeql-action/init@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7
|
||||
with:
|
||||
languages: c
|
||||
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
|
||||
- name: perform CodeQL analysis
|
||||
if: inputs.arch == 'amd64'
|
||||
uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8
|
||||
uses: github/codeql-action/analyze@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7
|
||||
|
||||
- name: list features
|
||||
run: ./fastfetch --list-features
|
||||
@@ -73,29 +73,7 @@ jobs:
|
||||
run: time ./fastfetch -c presets/ci.jsonc --stat false
|
||||
|
||||
- name: run fastfetch --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
|
||||
run: time ./fastfetch -c presets/ci.jsonc --format json
|
||||
|
||||
- name: run flashfetch
|
||||
run: time ./flashfetch
|
||||
@@ -127,9 +105,3 @@ 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
|
||||
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
run: cmake --version
|
||||
|
||||
- name: configure project
|
||||
run: CC=gcc-13 CMAKE_PREFIX_PATH=/home/linuxbrew/.linuxbrew PKG_CONFIG_PATH=/home/linuxbrew/.linuxbrew/lib/pkgconfig:$PKG_CONFIG_PATH cmake -DCMAKE_C_FLAGS="-m32 -march=i686 -mtune=i686" -DCMAKE_SYSTEM_NAME=Linux -DCMAKE_SYSTEM_VERSION=5.10.0 -DCMAKE_SYSTEM_PROCESSOR=i686 -DCPACK_DEBIAN_PACKAGE_ARCHITECTURE=i386 -GNinja -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -DCMAKE_INSTALL_PREFIX=/usr .
|
||||
run: CC=gcc-13 CMAKE_PREFIX_PATH=/home/linuxbrew/.linuxbrew PKG_CONFIG_PATH=/home/linuxbrew/.linuxbrew/lib/pkgconfig:$PKG_CONFIG_PATH cmake -DCMAKE_C_FLAGS="-m32 -march=i686 -mtune=i686" -DCMAKE_SYSTEM_PROCESSOR_OVERRIDE=i686 -DCPACK_DEBIAN_PACKAGE_ARCHITECTURE=i386 -GNinja -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -DCMAKE_INSTALL_PREFIX=/usr .
|
||||
|
||||
- name: build project
|
||||
run: cmake --build . --target package --verbose -j4
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: run VM
|
||||
uses: uraimo/run-on-arch-action@460cb8e6d9f726a588fc9b5e681c8a6cab09ae41 # v3.2.0
|
||||
uses: uraimo/run-on-arch-action@f9b26e3a1a408d5fd530d20c17b9f3f4428ff8d9 # v3.1.0
|
||||
id: runcmd
|
||||
with:
|
||||
arch: ${{ inputs.arch }}
|
||||
|
||||
@@ -26,30 +26,13 @@ jobs:
|
||||
- name: uname -a
|
||||
run: uname -a
|
||||
|
||||
- name: install required packages with MacPorts # Homebrew dropped Intel support
|
||||
if: inputs.arch == 'amd64'
|
||||
run: |
|
||||
curl -fsSL https://github.com/macports/macports-base/releases/download/v2.12.6/MacPorts-2.12.6-15-Sequoia.pkg -o "$RUNNER_TEMP/MacPorts.pkg"
|
||||
sudo installer -pkg "$RUNNER_TEMP/MacPorts.pkg" -target /
|
||||
echo /opt/local/bin >> "$GITHUB_PATH"
|
||||
echo /opt/local/sbin >> "$GITHUB_PATH"
|
||||
export PATH=/opt/local/bin:/opt/local/sbin:$PATH
|
||||
sudo port selfupdate
|
||||
sudo port install vulkan-loader vulkan-headers MoltenVK ImageMagick7 chafa lua quickjs-ng jq
|
||||
|
||||
- name: install required packages with Homebrew
|
||||
if: inputs.arch == 'aarch64'
|
||||
- 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 jq
|
||||
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 brew install --overwrite vulkan-loader vulkan-headers molten-vk imagemagick chafa lua quickjs-ng
|
||||
|
||||
- name: configure project
|
||||
run: |
|
||||
if [ "${{ inputs.arch }}" = "amd64" ]; then
|
||||
export CMAKE_PREFIX_PATH=/opt/local
|
||||
export PKG_CONFIG_PATH=/opt/local/lib/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}
|
||||
fi
|
||||
cmake -DSET_TWEAK=Off -DBUILD_TESTS=On .
|
||||
run: cmake -DSET_TWEAK=Off -DBUILD_TESTS=On .
|
||||
|
||||
- name: build project
|
||||
run: cmake --build . --target package --verbose -j4
|
||||
@@ -58,32 +41,10 @@ jobs:
|
||||
run: ./fastfetch --list-features
|
||||
|
||||
- name: run fastfetch
|
||||
run: time ./fastfetch -c presets/ci.jsonc --structure-disabled vulkan:bluetooth:bluetoothradio --stat false
|
||||
run: time ./fastfetch -c presets/ci.jsonc --structure-disabled vulkan --stat false
|
||||
|
||||
- name: run fastfetch --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
|
||||
run: time ./fastfetch -c presets/ci.jsonc --structure-disabled vulkan --format json
|
||||
|
||||
- name: run flashfetch
|
||||
run: time ./flashfetch
|
||||
@@ -99,9 +60,3 @@ 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@faa0c6197e94aacf1c5956460152c8380d3560a5 # v1.5.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@faa0c6197e94aacf1c5956460152c8380d3560a5 # v1.5.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@faa0c6197e94aacf1c5956460152c8380d3560a5 # v1.5.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 }}-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
|
||||
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
|
||||
|
||||
- name: print msys version
|
||||
run: uname -a
|
||||
@@ -70,29 +70,7 @@ jobs:
|
||||
run: time ./fastfetch -c presets/ci.jsonc --stat false
|
||||
|
||||
- name: run fastfetch --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
|
||||
run: time ./fastfetch -c presets/ci.jsonc --format json
|
||||
|
||||
- name: run flashfetch
|
||||
run: time ./flashfetch
|
||||
@@ -138,9 +116,3 @@ 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
|
||||
|
||||
@@ -138,6 +138,7 @@ jobs:
|
||||
uses: ./.github/workflows/build-dragonfly-amd64.yml
|
||||
|
||||
haiku-amd64:
|
||||
if: false # Disabled because the Haiku build is currently broken
|
||||
needs: no-features-test
|
||||
name: Haiku-amd64
|
||||
permissions:
|
||||
@@ -175,17 +176,6 @@ 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
|
||||
@@ -203,7 +193,7 @@ jobs:
|
||||
- dragonfly-amd64
|
||||
- solaris-amd64
|
||||
- omnios-amd64
|
||||
- haiku-amd64
|
||||
# - haiku-amd64
|
||||
- windows-hosts
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
@@ -13,5 +13,3 @@ fastfetch.kdev4
|
||||
*.user.*
|
||||
*.swp
|
||||
*.log
|
||||
/*.zh.md
|
||||
/.*-ai/
|
||||
|
||||
+1
-23
@@ -1,22 +1,12 @@
|
||||
# Unreleased
|
||||
|
||||
Features:
|
||||
* Added CPU name and frequency detection support on SPARC. (CPU, Linux)
|
||||
|
||||
# 2.68.1
|
||||
|
||||
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.
|
||||
* Exposed in custom key format as `{module-name}`.
|
||||
* 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.
|
||||
@@ -25,22 +15,10 @@ Features:
|
||||
* 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)
|
||||
* Added new ARM SoCs (CPU, Linux / 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.
|
||||
* Attempted to fix potential issues on big-endian systems
|
||||
* Not tested due to lack of access to big-endian hardware.
|
||||
|
||||
Logos:
|
||||
* Added macOS_old
|
||||
* Added Omarchy, Omarchy2
|
||||
|
||||
# 2.67.1
|
||||
|
||||
@@ -3323,7 +3301,7 @@ Features:
|
||||
* Enhance `--percent-type` to allow hiding other texts (#387)
|
||||
* Add Wifi module support for Linux
|
||||
* Detect scaled resolutions (Windows, macOS)
|
||||
* Optimize font module printing (Windows)
|
||||
* Optimise font module printing (Windows)
|
||||
* Detect pacman package count inside MSYS2 environment (Windows)
|
||||
* Add Wifi / Battery module support for Android
|
||||
* Disk name support for Linux
|
||||
|
||||
+103
-61
@@ -1,7 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.21.0) # C_STANDARD C17 and C23
|
||||
cmake_minimum_required(VERSION 3.21.0) # C_STANDARD
|
||||
|
||||
project(fastfetch
|
||||
VERSION 2.68.1
|
||||
VERSION 2.67.1
|
||||
LANGUAGES C
|
||||
DESCRIPTION "Fast neofetch-like system information tool"
|
||||
HOMEPAGE_URL "https://github.com/fastfetch-cli/fastfetch"
|
||||
@@ -9,6 +9,9 @@ project(fastfetch
|
||||
|
||||
set(PROJECT_LICENSE "MIT license")
|
||||
|
||||
if(DEFINED CMAKE_SYSTEM_PROCESSOR_OVERRIDE) # Used by github actions for i686 build
|
||||
set(CMAKE_SYSTEM_PROCESSOR ${CMAKE_SYSTEM_PROCESSOR_OVERRIDE} CACHE INTERNAL "")
|
||||
endif()
|
||||
string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" CMAKE_SYSTEM_PROCESSOR)
|
||||
if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")
|
||||
set(CMAKE_SYSTEM_PROCESSOR "amd64")
|
||||
@@ -75,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 "ANDROID OR LINUX OR FreeBSD OR OpenBSD OR NetBSD" OFF)
|
||||
cmake_dependent_option(ENABLE_WAYLAND "Enable wayland-client" ON "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)
|
||||
@@ -125,23 +128,12 @@ if(APPLE)
|
||||
option(ENABLE_APPLE_MEMSIZE_USABLE "Use usable memory size as total memory size in Memory module, to match other systems" OFF)
|
||||
endif()
|
||||
|
||||
# A module is a directory `src/modules/<module>/` that contains `<module>.c`.
|
||||
# Globbing the sources rather than the directories skips empty directories and
|
||||
# other leftovers, and makes `CONFIGURE_DEPENDS` react to added or removed sources.
|
||||
file(GLOB FF_MODULE_SRCS CONFIGURE_DEPENDS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}/src/modules" "${CMAKE_CURRENT_SOURCE_DIR}/src/modules/*/*.c")
|
||||
set(FF_MODULE_DIRS "")
|
||||
foreach(FF_MODULE_SRC ${FF_MODULE_SRCS})
|
||||
get_filename_component(FF_MODULE_DIR "${FF_MODULE_SRC}" DIRECTORY)
|
||||
get_filename_component(FF_MODULE_NAME "${FF_MODULE_SRC}" NAME_WE)
|
||||
if("${FF_MODULE_DIR}" STREQUAL "${FF_MODULE_NAME}")
|
||||
list(APPEND FF_MODULE_DIRS "${FF_MODULE_DIR}")
|
||||
endif()
|
||||
endforeach()
|
||||
unset(FF_MODULE_SRCS)
|
||||
|
||||
file(GLOB FF_MODULE_DIRS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}/src/modules" "${CMAKE_CURRENT_SOURCE_DIR}/src/modules/*/")
|
||||
foreach(FF_MODULE_DIR ${FF_MODULE_DIRS})
|
||||
string(TOUPPER "${FF_MODULE_DIR}" FF_MODULE_UPPER)
|
||||
option(MODULE_DISABLE_${FF_MODULE_UPPER} "Disable module ${FF_MODULE_DIR}" OFF)
|
||||
if(IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/src/modules/${FF_MODULE_DIR}")
|
||||
string(TOUPPER "${FF_MODULE_DIR}" FF_MODULE_UPPER)
|
||||
option(MODULE_DISABLE_${FF_MODULE_UPPER} "Disable module ${FF_MODULE_DIR}" OFF)
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
set(BINARY_LINK_TYPE_OPTIONS dlopen dynamic static)
|
||||
@@ -492,7 +484,6 @@ 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
|
||||
@@ -504,18 +495,87 @@ set(LIBFASTFETCH_SRC
|
||||
src/logo/image/im7.c
|
||||
src/logo/image/image.c
|
||||
src/logo/logo.c
|
||||
src/modules/battery/battery.c
|
||||
src/modules/bios/bios.c
|
||||
src/modules/bluetooth/bluetooth.c
|
||||
src/modules/bluetoothradio/bluetoothradio.c
|
||||
src/modules/board/board.c
|
||||
src/modules/bootmgr/bootmgr.c
|
||||
src/modules/brightness/brightness.c
|
||||
src/modules/break/break.c
|
||||
src/modules/btrfs/btrfs.c
|
||||
src/modules/camera/camera.c
|
||||
src/modules/chassis/chassis.c
|
||||
src/modules/codec/codec.c
|
||||
src/modules/colors/colors.c
|
||||
src/modules/cpu/cpu.c
|
||||
src/modules/cpucache/cpucache.c
|
||||
src/modules/cpuusage/cpuusage.c
|
||||
src/modules/cursor/cursor.c
|
||||
src/modules/custom/custom.c
|
||||
src/modules/command/command.c
|
||||
src/modules/datetime/datetime.c
|
||||
src/modules/de/de.c
|
||||
src/modules/disk/disk.c
|
||||
src/modules/diskio/diskio.c
|
||||
src/modules/dns/dns.c
|
||||
src/modules/editor/editor.c
|
||||
src/modules/font/font.c
|
||||
src/modules/gpu/gpu.c
|
||||
src/modules/host/host.c
|
||||
src/modules/icons/icons.c
|
||||
src/modules/initsystem/initsystem.c
|
||||
src/modules/gamepad/gamepad.c
|
||||
src/modules/kernel/kernel.c
|
||||
src/modules/keyboard/keyboard.c
|
||||
src/modules/lm/lm.c
|
||||
src/modules/loadavg/loadavg.c
|
||||
src/modules/locale/locale.c
|
||||
src/modules/localip/localip.c
|
||||
src/modules/logo/logo.c
|
||||
src/modules/memory/memory.c
|
||||
src/modules/monitor/monitor.c
|
||||
src/modules/netio/netio.c
|
||||
src/modules/opencl/opencl.c
|
||||
src/modules/opengl/opengl.c
|
||||
src/modules/os/os.c
|
||||
src/modules/packages/packages.c
|
||||
src/modules/physicaldisk/physicaldisk.c
|
||||
src/modules/physicalmemory/physicalmemory.c
|
||||
src/modules/processes/processes.c
|
||||
src/modules/player/player.c
|
||||
src/modules/poweradapter/poweradapter.c
|
||||
src/modules/publicip/publicip.c
|
||||
src/modules/display/display.c
|
||||
src/modules/separator/separator.c
|
||||
src/modules/shell/shell.c
|
||||
src/modules/sound/sound.c
|
||||
src/modules/swap/swap.c
|
||||
src/modules/media/media.c
|
||||
src/modules/mouse/mouse.c
|
||||
src/modules/terminal/terminal.c
|
||||
src/modules/terminaltheme/terminaltheme.c
|
||||
src/modules/terminalfont/terminalfont.c
|
||||
src/modules/terminalsize/terminalsize.c
|
||||
src/modules/theme/theme.c
|
||||
src/modules/title/title.c
|
||||
src/modules/tpm/tpm.c
|
||||
src/modules/uptime/uptime.c
|
||||
src/modules/users/users.c
|
||||
src/modules/version/version.c
|
||||
src/modules/vulkan/vulkan.c
|
||||
src/modules/wallpaper/wallpaper.c
|
||||
src/modules/weather/weather.c
|
||||
src/modules/wifi/wifi.c
|
||||
src/modules/wm/wm.c
|
||||
src/modules/wmtheme/wmtheme.c
|
||||
src/modules/zpool/zpool.c
|
||||
src/modules/modules.c
|
||||
src/options/display.c
|
||||
src/options/logo.c
|
||||
src/options/general.c
|
||||
)
|
||||
|
||||
foreach(FF_MODULE_DIR ${FF_MODULE_DIRS})
|
||||
list(APPEND LIBFASTFETCH_SRC
|
||||
src/modules/${FF_MODULE_DIR}/${FF_MODULE_DIR}.c
|
||||
)
|
||||
endforeach()
|
||||
|
||||
if(LINUX)
|
||||
list(APPEND LIBFASTFETCH_SRC
|
||||
src/common/impl/dbus.c
|
||||
@@ -591,7 +651,6 @@ 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
|
||||
@@ -632,13 +691,6 @@ 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
|
||||
@@ -674,7 +726,6 @@ 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
|
||||
@@ -751,6 +802,7 @@ 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
|
||||
@@ -770,15 +822,11 @@ 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()
|
||||
@@ -856,7 +904,6 @@ 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
|
||||
@@ -917,7 +964,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_linux.c
|
||||
src/detection/lm/lm_nosupport.c
|
||||
src/detection/icons/icons_linux.c
|
||||
src/detection/initsystem/initsystem_linux.c
|
||||
src/detection/keyboard/keyboard_nosupport.c
|
||||
@@ -943,7 +990,6 @@ 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
|
||||
@@ -989,7 +1035,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_apple.m
|
||||
src/detection/lm/lm_nosupport.c
|
||||
src/detection/loadavg/loadavg_bsd.c
|
||||
src/detection/libc/libc_apple.c
|
||||
src/detection/locale/locale_linux.c
|
||||
@@ -1004,14 +1050,13 @@ elseif(APPLE)
|
||||
src/detection/packages/packages_apple.c
|
||||
src/detection/packages/packages_nix.c
|
||||
src/detection/poweradapter/poweradapter_apple.c
|
||||
src/detection/processes/processes_apple.c
|
||||
src/detection/processes/processes_bsd.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
|
||||
@@ -1083,7 +1128,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_windows.c
|
||||
src/detection/lm/lm_nosupport.c
|
||||
src/detection/loadavg/loadavg_nosupport.c
|
||||
src/detection/locale/locale_windows.c
|
||||
src/detection/localip/localip_windows.c
|
||||
@@ -1104,7 +1149,6 @@ 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
|
||||
@@ -1166,7 +1210,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_linux.c
|
||||
src/detection/lm/lm_nosupport.c
|
||||
src/detection/loadavg/loadavg_sunos.c
|
||||
src/detection/locale/locale_linux.c
|
||||
src/detection/localip/localip_linux.c
|
||||
@@ -1179,7 +1223,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_sunos.c
|
||||
src/detection/processes/processes_linux.c
|
||||
src/detection/gtk_qt/qt.c
|
||||
src/detection/sound/sound_sunos.c
|
||||
src/detection/swap/swap_sunos.c
|
||||
@@ -1187,7 +1231,6 @@ 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
|
||||
@@ -1258,7 +1301,6 @@ 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
|
||||
@@ -1335,7 +1377,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_gnu.c
|
||||
src/detection/processes/processes_linux.c
|
||||
src/detection/gtk_qt/qt.c
|
||||
src/detection/sound/sound_linux.c
|
||||
src/detection/swap/swap_linux.c
|
||||
@@ -1343,7 +1385,6 @@ 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
|
||||
@@ -1423,9 +1464,11 @@ add_library(libfastfetch OBJECT
|
||||
)
|
||||
|
||||
foreach(FF_MODULE_DIR ${FF_MODULE_DIRS})
|
||||
string(TOUPPER "${FF_MODULE_DIR}" FF_MODULE_UPPER)
|
||||
if(MODULE_DISABLE_${FF_MODULE_UPPER})
|
||||
target_compile_definitions(libfastfetch PUBLIC FF_MODULE_DISABLE_${FF_MODULE_UPPER}=1)
|
||||
if(IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/src/modules/${FF_MODULE_DIR}")
|
||||
string(TOUPPER "${FF_MODULE_DIR}" FF_MODULE_UPPER)
|
||||
if(MODULE_DISABLE_${FF_MODULE_UPPER})
|
||||
target_compile_definitions(libfastfetch PUBLIC FF_MODULE_DISABLE_${FF_MODULE_UPPER}=1)
|
||||
endif()
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
@@ -1458,11 +1501,10 @@ if(yyjson_FOUND)
|
||||
# `target_link_libraries(yyjson::yyjson)` sets rpath implicitly
|
||||
endif()
|
||||
|
||||
# Used for dlopen finding dylibs installed by package managers
|
||||
# `/opt/homebrew/lib`, `/usr/local/lib` and `/opt/local/lib` are not on the
|
||||
# dlopen search path by default
|
||||
# Used for dlopen finding dylibs installed by homebrew
|
||||
# `/opt/homebrew/lib` is not on in dlopen search path by default
|
||||
if(APPLE AND BINARY_LINK_TYPE STREQUAL "dlopen")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-rpath,/opt/homebrew/lib -Wl,-rpath,/usr/local/lib -Wl,-rpath,/opt/local/lib")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-rpath,/opt/homebrew/lib -Wl,-rpath,/usr/local/lib")
|
||||
endif()
|
||||
|
||||
if(ANDROID)
|
||||
@@ -1931,6 +1973,7 @@ elseif(FreeBSD)
|
||||
elseif(OpenBSD)
|
||||
target_link_libraries(libfastfetch
|
||||
PRIVATE "m"
|
||||
PRIVATE "kvm"
|
||||
PRIVATE "sndio"
|
||||
PRIVATE "util"
|
||||
)
|
||||
@@ -1952,7 +1995,6 @@ elseif(SunOS)
|
||||
elseif(GNU)
|
||||
target_link_libraries(libfastfetch
|
||||
PRIVATE "m"
|
||||
PRIVATE "ps"
|
||||
)
|
||||
elseif(ANDROID)
|
||||
target_link_libraries(libfastfetch
|
||||
|
||||
-797
@@ -1,797 +0,0 @@
|
||||
# Contributing to fastfetch
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
Disclaimer
|
||||
</summary>
|
||||
|
||||
This document is generated by AI and reviewed by humans. It is primarily intended for AI-assisted development, while remaining useful to human developers.
|
||||
</details>
|
||||
|
||||
Thank you for your interest in fastfetch. This document covers building, architecture, how to add a module or a logo, code style, commit conventions, and the pull request workflow.
|
||||
|
||||
fastfetch is a system information tool written in C23, supporting Linux, macOS, Windows, the BSDs, Solaris, Haiku and Android. The project places strong emphasis on **startup time** and on keeping **dependencies optional**; many design decisions only make sense under that constraint, and this document returns to it repeatedly.
|
||||
|
||||
---
|
||||
|
||||
## Table of contents
|
||||
|
||||
- [Contributor quick reference](#contributor-quick-reference)
|
||||
- [Getting started](#getting-started)
|
||||
- [Code map](#code-map)
|
||||
- [Core architecture: modules vs. detection](#core-architecture-modules-vs-detection)
|
||||
- [Module registration](#module-registration)
|
||||
- [Walkthrough: adding a module](#walkthrough-adding-a-module)
|
||||
- [Walkthrough: adding a logo](#walkthrough-adding-a-logo)
|
||||
- [Platform conventions](#platform-conventions)
|
||||
- [Code style](#code-style)
|
||||
- [Commit messages](#commit-messages)
|
||||
- [Changelog](#changelog)
|
||||
- [Tests](#tests)
|
||||
- [Pull requests](#pull-requests)
|
||||
- [Pitfalls](#pitfalls)
|
||||
|
||||
---
|
||||
|
||||
## Contributor quick reference
|
||||
|
||||
| Task | Start here | Verify with |
|
||||
|---|---|---|
|
||||
| Build and run fastfetch | `run.sh`, `CMakeLists.txt` | `./run.sh` |
|
||||
| Add a module | `src/modules/<name>/`, `src/detection/<name>/`, `src/modules/modules.c`, `CMakeLists.txt` | `cmake -B build && cmake --build build -j` |
|
||||
| Add a platform implementation | `src/detection/<name>/<name>_<platform>.c`, the matching platform block in `CMakeLists.txt` | Build on the target platform or CI |
|
||||
| Add an ASCII logo | `src/logo/ascii/<letter>/<name>.txt`, matching `<letter>.inc` | `./build/fastfetch --logo <name>` |
|
||||
| Change formatting or JSON output | `src/modules/<name>/<name>.c` | `./build/fastfetch -s <name> --format json` |
|
||||
| Change shared formatting or containers | `src/common/format.h`, `src/common/color.h`, `src/common/FFstrbuf.h` | Build and run the matching test |
|
||||
| Run the test suite | `tests/`, `build/` | `cd build && ctest --output-on-failure` |
|
||||
|
||||
For a typical code change, the shortest useful iteration is: configure, build the `fastfetch` target, run the affected module, then run the tests. If you add a directory or a generated input, re-run `cmake -B build` so that CMake refreshes its file globs.
|
||||
|
||||
---
|
||||
|
||||
## Getting started
|
||||
|
||||
### Building
|
||||
|
||||
The quickest way is `run.sh` in the repository root: it creates `build/`, configures, compiles and runs the binary.
|
||||
|
||||
```sh
|
||||
./run.sh # build and run
|
||||
./run.sh --format json # arguments are forwarded to fastfetch
|
||||
```
|
||||
|
||||
Manual build:
|
||||
|
||||
```sh
|
||||
cmake -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo
|
||||
cmake --build build --target fastfetch -j$(nproc)
|
||||
./build/fastfetch
|
||||
```
|
||||
|
||||
The default build type is `RelWithDebInfo`. LTO is enabled whenever `ENABLE_LTO=ON` **and** `CMAKE_BUILD_TYPE != Debug`, so the default configuration enables it and only `Debug` builds omit it. This is significant because LTO here is not merely an optimization: it is what removes the code of disabled modules. See [Pitfalls](#pitfalls).
|
||||
|
||||
### Common build options
|
||||
|
||||
| Option | Default | Notes |
|
||||
|---|---|---|
|
||||
| `CMAKE_BUILD_TYPE` | `RelWithDebInfo` | LTO is on for anything but `Debug` |
|
||||
| `BUILD_TESTS` | `OFF` | Builds the unit tests in `tests/` |
|
||||
| `BUILD_FLASHFETCH` | `ON` | Also builds the stripped-down `flashfetch` |
|
||||
| `BINARY_LINK_TYPE` | `dlopen` | `dlopen` / `dynamic` / `static` |
|
||||
| `ENABLE_ASAN` | `OFF` | Address Sanitizer |
|
||||
| `ENABLE_LTO` | `ON` | Link-time optimization |
|
||||
| `MODULE_DISABLE_<NAME>` | `OFF` | Disable a module, e.g. `MODULE_DISABLE_GPU=ON` |
|
||||
| `SET_TWEAK` | `ON` | Appends a tweak to the dev version; turned off for releases |
|
||||
|
||||
`BINARY_LINK_TYPE=dlopen` (the default) is a deliberate design choice: optional dependencies (Vulkan, Wayland, DBus, ImageMagick, chafa, …) are loaded at runtime, so a missing library never prevents startup. When adding a third-party dependency, use the dlopen helpers in `common/library.h` — do not link it directly.
|
||||
|
||||
For a minimal-dependency build, see `.github/workflows/build-no-features-test.yml`:
|
||||
|
||||
```sh
|
||||
cmake -DBUILD_TESTS=On -DENABLE_VULKAN=OFF -DENABLE_WAYLAND=OFF -DENABLE_X11=OFF \
|
||||
-DENABLE_DBUS=OFF -DENABLE_ZLIB=OFF ... .
|
||||
```
|
||||
|
||||
### Dependencies
|
||||
|
||||
Required: CMake ≥ 3.21 and a C23 compiler (GCC, Clang or MSVC). Everything else is optional.
|
||||
|
||||
Optional dependencies are auto-detected: libpci, libdrm, vulkan, wayland, xcb, xrandr, dbus, sqlite3, rpm, imagemagick{6,7}, chafa, zlib, egl, glx, opencl, freetype, pulse, ddcutil, elf, libzfs, and more.
|
||||
|
||||
---
|
||||
|
||||
## Code map
|
||||
|
||||
```
|
||||
src/
|
||||
├── fastfetch.c entry point: CLI parsing, module dispatch, main loop (36 KB)
|
||||
├── flashfetch.c entry point for the stripped-down build
|
||||
├── options/ global (non-module) config: general / display / logo
|
||||
├── modules/ module layer — module formatting and output
|
||||
├── detection/ detection layer — platform-specific data retrieval
|
||||
├── common/ infrastructure: FFstrbuf, FFlist, formatting, printing, networking
|
||||
├── logo/ 530 ASCII logos plus the image logo backends
|
||||
└── 3rdparty/ yyjson, widecharwidth, display-library
|
||||
```
|
||||
|
||||
Flow of control:
|
||||
|
||||
```
|
||||
fastfetch.c → options/ (global configuration)
|
||||
→ modules/ (76 modules, formatting and output)
|
||||
→ detection/ (one platform implementation each, raw data)
|
||||
→ common/ (infrastructure)
|
||||
```
|
||||
|
||||
The number of module directories does not match the number of detection directories, which is expected. The following relationships are structural rather than a fixed inventory:
|
||||
|
||||
- **13 modules have no detection directory** — they are either pure layout (`break`, `separator`, `colors`, `title`, `logo`) or reuse another module's detection result (`display`, `monitor`, `kernel`, `shell`, `terminal`, `player`, `custom`, `datetime`).
|
||||
- **4 detection directories serve modules with different names** — `displayserver` (→ `display`, `monitor`), `gtk_qt` (→ `theme`, `icons`, `font`, `cursor`), `terminalshell` (→ `terminal`, `shell`) and `libc`.
|
||||
|
||||
Therefore, do not assume that `modules/<x>/` always has a matching `detection/<x>/`.
|
||||
|
||||
### Layer boundaries
|
||||
|
||||
| Layer | Owns | Must not |
|
||||
|---|---|---|
|
||||
| `options/` | **Global** config (colors, logo, threading, timeouts) | hold per-module options |
|
||||
| `modules/` | formatting, printing, JSON serialization | read `/proc`, call Win32 APIs, parse sysfs |
|
||||
| `detection/` | cross-platform data retrieval, clean result structs | print anything, read display config |
|
||||
| `common/` | general-purpose utilities, no business logic | depend on a specific module |
|
||||
|
||||
A simple check: **no file under `detection/` should contain `printf` or read `instance.config`.** Symmetrically, no file under `modules/` should contain `#ifdef __linux__`.
|
||||
|
||||
---
|
||||
|
||||
## Core architecture: modules vs. detection
|
||||
|
||||
This is the most important convention in the codebase. Every feature consists of a fixed set of files; using CPU as the example:
|
||||
|
||||
```
|
||||
modules/cpu/option.h module option struct
|
||||
modules/cpu/cpu.c formatting and output
|
||||
detection/cpu/cpu.h platform-independent interface
|
||||
detection/cpu/cpu_linux.c ┐
|
||||
detection/cpu/cpu_apple.c │
|
||||
detection/cpu/cpu_windows.c ├─ platform implementations, one linked per build
|
||||
detection/cpu/cpu_bsd.c │
|
||||
detection/cpu/cpu_nosupport.c ┘
|
||||
```
|
||||
|
||||
The interface is deliberately minimal, typically consisting of only two declarations:
|
||||
|
||||
```c
|
||||
typedef struct FFCPUResult { ... } FFCPUResult; // result struct
|
||||
const char* ffDetectCPU(const FFCPUOptions* options, FFCPUResult* cpu);
|
||||
```
|
||||
|
||||
The `const char*` return value is an **error string**; `nullptr` means success. This pattern is used throughout `detection/` — please keep it consistent.
|
||||
|
||||
Helper functions are only exposed when they are genuinely shared between platform implementations (as in `cpu.h`: `ffCPUAppleCodeToName`, `ffCPUDetectByCpuid`).
|
||||
|
||||
**The benefit of this separation:** adding a platform never touches `modules/`; fixing output formatting never touches `detection/`.
|
||||
|
||||
---
|
||||
|
||||
## Module registration
|
||||
|
||||
### The descriptor
|
||||
|
||||
Every module exports one `FFModuleBaseInfo` (defined in `common/option.h:46`) — essentially a hand-written vtable:
|
||||
|
||||
```c
|
||||
typedef struct FFModuleBaseInfo {
|
||||
const char* name; // lookup key, e.g. "cpu"
|
||||
const char* description; // help text
|
||||
FFModuleDisplayName displayName; // module name in 20 languages
|
||||
|
||||
void (*initOptions)(void* options);
|
||||
void (*destroyOptions)(void* options);
|
||||
void (*parseJsonObject)(void* options, struct yyjson_val* module);
|
||||
bool (*printModule)(void* options);
|
||||
bool (*generateJsonResult)(void* options, yyjson_mut_doc*, yyjson_mut_val*);
|
||||
void (*generateJsonConfig)(void* options, yyjson_mut_doc*, yyjson_mut_val*);
|
||||
|
||||
FFModuleFormatArgList formatArgs; // format placeholder table
|
||||
const uint8_t defaultOrder; // sort weight
|
||||
} FFModuleBaseInfo;
|
||||
```
|
||||
|
||||
The source comment acknowledges that this is undefined behavior, since `void*` is not compatible with `FF*Options*`. It is a pragmatic compromise to obtain polymorphism in C; do not attempt to "fix" it.
|
||||
|
||||
### The registry: a first-letter hash bucket
|
||||
|
||||
`src/modules/modules.c` defines 26 `static FFModuleBaseInfo*` arrays (`A[]` through `Z[]`), each terminated by `nullptr`, collected into `ffModuleInfos[26]`:
|
||||
|
||||
```c
|
||||
FFModuleBaseInfo** modules = ffModuleInfos[toupper(name[0]) - 'A']; // O(1) bucket lookup
|
||||
for (; *modules; ++modules) { // linear scan in the bucket
|
||||
if (ffStrEqualsIgnCase(name, (*modules)->name)) { ... }
|
||||
}
|
||||
```
|
||||
|
||||
Because the registry is small, a single arithmetic bucket lookup followed by a few string comparisons is preferable to a general-purpose hash table. When adding a module, place its descriptor in the bucket matching the first letter of `.name`, and keep the existing `nullptr` terminator at the end.
|
||||
|
||||
### The calling convention: zero heap allocation
|
||||
|
||||
Every dispatch site (`jsonconfig.c:98`, `commandoption.c:189`) follows the same pattern:
|
||||
|
||||
```c
|
||||
alignas(uint64_t) uint8_t optionBuf[FF_OPTION_MAX_SIZE]; // 256 bytes, on the stack
|
||||
baseInfo->initOptions(optionBuf);
|
||||
baseInfo->parseJsonObject(optionBuf, jsonVal); // JSONC path only
|
||||
baseInfo->printModule(optionBuf); // or generateJsonResult
|
||||
baseInfo->destroyOptions(optionBuf);
|
||||
```
|
||||
|
||||
`FF_OPTION_MAX_SIZE = 1 << 8` (256 bytes). Every module's `option.h` must end with:
|
||||
|
||||
```c
|
||||
static_assert(sizeof(FFCPUOptions) <= FF_OPTION_MAX_SIZE, "FFCPUOptions size exceeds maximum allowed size");
|
||||
```
|
||||
|
||||
**In other words, no module option struct may exceed 256 bytes.** This is a hard constraint, enforced at compile time.
|
||||
|
||||
The three dispatch entry points:
|
||||
|
||||
| Entry point | Location | Used for |
|
||||
|---|---|---|
|
||||
| `parseModuleJsonObject` | `common/impl/jsonconfig.c:90` | the `modules[]` array in a JSONC config |
|
||||
| `parseStructureCommand` | `common/impl/commandoption.c:181` | the colon-separated structure string |
|
||||
|
||||
### Build-time module discovery
|
||||
|
||||
`CMakeLists.txt:134` globs `src/modules/*/*.c`, keeps only the entries whose file name matches their directory name, and generates a `MODULE_DISABLE_<UPPER>` option for each:
|
||||
|
||||
```cmake
|
||||
file(GLOB FF_MODULE_SRCS CONFIGURE_DEPENDS RELATIVE "..." ".../src/modules/*/*.c")
|
||||
set(FF_MODULE_DIRS "")
|
||||
foreach(FF_MODULE_SRC ${FF_MODULE_SRCS})
|
||||
get_filename_component(FF_MODULE_DIR "${FF_MODULE_SRC}" DIRECTORY)
|
||||
get_filename_component(FF_MODULE_NAME "${FF_MODULE_SRC}" NAME_WE)
|
||||
if("${FF_MODULE_DIR}" STREQUAL "${FF_MODULE_NAME}")
|
||||
list(APPEND FF_MODULE_DIRS "${FF_MODULE_DIR}")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
foreach(FF_MODULE_DIR ${FF_MODULE_DIRS})
|
||||
string(TOUPPER "${FF_MODULE_DIR}" FF_MODULE_UPPER)
|
||||
option(MODULE_DISABLE_${FF_MODULE_UPPER} "Disable module ${FF_MODULE_DIR}" OFF)
|
||||
endforeach()
|
||||
```
|
||||
|
||||
Because the glob matches sources rather than directories, a directory counts as a module only when `src/modules/<name>/<name>.c` exists. Empty directories — which git does not track, so they are easy to leave behind — and stray files are ignored instead of breaking the configure step with `Cannot find source file`.
|
||||
|
||||
### Module sources are discovered automatically
|
||||
|
||||
`FF_MODULE_DIRS` also drives the source list (`CMakeLists.txt:516`):
|
||||
|
||||
```cmake
|
||||
foreach(FF_MODULE_DIR ${FF_MODULE_DIRS})
|
||||
list(APPEND LIBFASTFETCH_SRC
|
||||
src/modules/${FF_MODULE_DIR}/${FF_MODULE_DIR}.c
|
||||
)
|
||||
endforeach()
|
||||
```
|
||||
|
||||
Consequences:
|
||||
|
||||
- `src/modules/<name>/<name>.c` is compiled as soon as the file exists — **there is no source list to edit for the module layer**.
|
||||
- The file name must match the directory name. A directory whose `.c` file is named differently, or has none at all, is silently not a module: a typo therefore shows up as a module missing from `fastfetch --list-modules`, not as a build error.
|
||||
- The glob uses `CONFIGURE_DEPENDS`, so with the Makefile and Ninja generators the build re-evaluates it and re-runs CMake when a module source is added or removed. With other generators, or to be safe, re-run `cmake -B build` explicitly.
|
||||
|
||||
Only the module layer is automated. Sources under `src/detection/` and `src/common/impl/` are **not** globbed and must still be listed by hand — see [step 5](#5-add-the-platform-sources-to-cmakeliststxt).
|
||||
|
||||
---
|
||||
|
||||
## Walkthrough: adding a module
|
||||
|
||||
Adding a `Foo` module, step by step.
|
||||
|
||||
### 1. Define the option struct
|
||||
|
||||
`src/modules/foo/option.h`:
|
||||
|
||||
```c
|
||||
#pragma once
|
||||
|
||||
#include "common/option.h"
|
||||
|
||||
typedef struct FFFooOptions {
|
||||
FFModuleArgs moduleArgs; // must be the first field
|
||||
bool showBar;
|
||||
} FFFooOptions;
|
||||
|
||||
static_assert(sizeof(FFFooOptions) <= FF_OPTION_MAX_SIZE, "FFFooOptions size exceeds maximum allowed size");
|
||||
```
|
||||
|
||||
`FFModuleArgs` must come first. It provides `key`, `format`, `outputColor`, `keyColor`, `keyIcon` and `keyWidth`; because it is the first field, `ffJsonConfigParseModuleArgs()` handles these generically, **so no parsing code is required**.
|
||||
|
||||
### 2. Implement detection
|
||||
|
||||
`src/detection/foo/foo.h`:
|
||||
|
||||
```c
|
||||
#pragma once
|
||||
#include "fastfetch.h"
|
||||
|
||||
typedef struct FFFooResult {
|
||||
FFstrbuf name;
|
||||
uint32_t count;
|
||||
} FFFooResult;
|
||||
|
||||
const char* ffDetectFoo(const FFFooOptions* options, FFFooResult* result);
|
||||
```
|
||||
|
||||
`src/detection/foo/foo_linux.c`:
|
||||
|
||||
```c
|
||||
#include "foo.h"
|
||||
|
||||
const char* ffDetectFoo(const FFFooOptions* options, FFFooResult* result) {
|
||||
// read /sys, /proc, sysfs, dbus, ...
|
||||
return nullptr; // success; return an error string on failure
|
||||
}
|
||||
```
|
||||
|
||||
**Write one file per target platform, plus a `foo_nosupport.c` fallback for the remaining platforms.** The repository currently contains 45 `*_nosupport.c` files serving this purpose.
|
||||
|
||||
### 3. Implement the module
|
||||
|
||||
`src/modules/foo/foo.c` — implement the six functions, then define the descriptor:
|
||||
|
||||
```c
|
||||
FFModuleBaseInfo ffFooModuleInfo = {
|
||||
.name = "Foo",
|
||||
.description = "Print foo information",
|
||||
.displayName = {
|
||||
.en = "Foo", .ar = "Foo", .cs = "Foo", .de = "Foo", .es = "Foo",
|
||||
.fr = "Foo", .gl = "Foo", .he = "Foo", .id = "Foo", .it = "Foo",
|
||||
.ja = "Foo", .ko = "Foo", .pl = "Foo", .pt = "Foo", .ru = "Foo",
|
||||
.tr = "Foo", .uk = "Foo", .vi = "Foo", .zh_CN = "Foo", .zh_TW = "Foo",
|
||||
},
|
||||
.initOptions = (void*) ffInitFooOptions,
|
||||
.destroyOptions = (void*) ffDestroyFooOptions,
|
||||
.parseJsonObject = (void*) ffParseFooJsonObject,
|
||||
.printModule = (void*) ffPrintFoo,
|
||||
.generateJsonResult = (void*) ffGenerateFooJsonResult,
|
||||
.generateJsonConfig = (void*) ffGenerateFooJsonConfig,
|
||||
.formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {
|
||||
{ "Name", "name" },
|
||||
{ "Count", "count" },
|
||||
})),
|
||||
.defaultOrder = 73,
|
||||
};
|
||||
```
|
||||
|
||||
Points to note:
|
||||
|
||||
- **`displayName`** is a literal block of all 20 language fields; there is no shortcut macro, so copy the layout from an existing module: **`en`, `ar`, `cs`, `de`, `es`, `fr`, `gl`, `he`, `id`, `it`, `ja`, `ko`, `pl`, `pt`, `ru`, `tr`, `uk`, `vi`, `zh_CN`, `zh_TW`**.
|
||||
**Fill in all 20.** The struct is read by byte offset (see [Pitfalls](#6-localization-uses-byte-offsets-not-enums)) and there is **no fallback** — a missing field means the key prints empty in that language.
|
||||
- **`formatArgs`** must be exhaustive. It drives the placeholder list printed by `fastfetch -h foo-format`; **any placeholder omitted here is neither listed nor usable by the user.**
|
||||
- The `moduleFormat` section in `doc/json_schema.json` is generated by `fastfetch -h format-json`. Its metadata comes from `FFModuleBaseInfo::formatArgs`. To keep the schema and module descriptors consistent, do not edit the `moduleFormat` section in `doc/json_schema.json` by hand; update the module metadata and regenerate it instead.
|
||||
- **`defaultOrder`**: use the current maximum plus 1. Search the existing descriptors for `.defaultOrder =` before choosing a value; do not copy a hard-coded value from this document. Leaving it out, or setting it to `0`, excludes the module from the interactive `--gen-config` picker. Only `logo`, `command` and `custom` rely on this behavior, because they require user arguments or are invoked directly by the display layer.
|
||||
|
||||
### 4. Register it
|
||||
|
||||
- Add `#include "modules/foo/foo.h"` to `src/modules/modules.h`
|
||||
- Insert into the `F[]` array in `src/modules/modules.c`:
|
||||
|
||||
```c
|
||||
#if !FF_MODULE_DISABLE_FOO
|
||||
&ffFooModuleInfo,
|
||||
#endif
|
||||
```
|
||||
|
||||
`FF_MODULE_DISABLE_FOO` is generated by CMake — you do not define it yourself.
|
||||
|
||||
### 5. Add the platform sources to `CMakeLists.txt`
|
||||
|
||||
The module layer is discovered by glob (see [Build-time module discovery](#module-sources-are-discovered-automatically)), **but `src/detection/**` and `src/common/impl/**` are not**. `CMakeLists.txt` lists them explicitly, inside one mutually exclusive chain of platform blocks:
|
||||
|
||||
| Block | Line | Covers |
|
||||
|---|---|---|
|
||||
| `if(LINUX)` | `CMakeLists.txt:522` | Linux |
|
||||
| `elseif(ANDROID)` | `CMakeLists.txt:608` | Android (Termux) |
|
||||
| `elseif(FreeBSD)` | `CMakeLists.txt:691` | FreeBSD, MidnightBSD, DragonFly |
|
||||
| `elseif(NetBSD)` | `CMakeLists.txt:788` | NetBSD |
|
||||
| `elseif(OpenBSD)` | `CMakeLists.txt:872` | OpenBSD |
|
||||
| `elseif(APPLE)` | `CMakeLists.txt:959` | macOS / iOS |
|
||||
| `elseif(WIN32)` | `CMakeLists.txt:1046` | Windows |
|
||||
| `elseif(SunOS)` | `CMakeLists.txt:1121` | Solaris / illumos |
|
||||
| `elseif(Haiku)` | `CMakeLists.txt:1204` | Haiku |
|
||||
| `elseif(GNU)` | `CMakeLists.txt:1282` | GNU/Hurd |
|
||||
|
||||
Add the platform implementation to every block whose platform it supports, and `foo_nosupport.c` to every remaining block, so that all ten platforms still link:
|
||||
|
||||
```cmake
|
||||
elseif(FreeBSD)
|
||||
list(APPEND LIBFASTFETCH_SRC
|
||||
...
|
||||
src/detection/foo/foo_bsd.c
|
||||
)
|
||||
```
|
||||
|
||||
Points to note:
|
||||
|
||||
- Exactly one block is compiled per build, so a file omitted from a block does not exist for that platform, and the link fails with an undefined reference to `ffDetectFoo()` — **on that platform only**. A missing entry therefore builds fine locally and fails in CI; this is why every block must be covered.
|
||||
- A platform may reuse another platform's implementation instead of a stub. `src/common/impl/networking_linux.c`, for example, is listed in nine of the ten blocks (all but `WIN32`). Check where the closest sibling module points before adding a new file.
|
||||
- `DragonFly` is handled by the inner `if(DragonFly)` sub-block inside the FreeBSD block (`CMakeLists.txt:773`); add the variant there, as `processes`, `top` and `wifi` do.
|
||||
- New helpers under `src/common/impl/` follow the same rule: they are not globbed, and each block that needs one must list it.
|
||||
- A few files are appended outside the platform chain because they depend on an option or on a specific feature — for example the proprietary GPU backends (`CMakeLists.txt:1379`) and `src/common/impl/wcwidth.c` (`CMakeLists.txt:1408`). Those are written by hand as well.
|
||||
|
||||
### 6. Modules that need warm-up
|
||||
|
||||
If your module needs a sampling interval (CPU usage) or a network round-trip (public IP, weather), also implement `ffPrepareFoo()` and register it in the switch inside `ffPrepareCommandOption()` in `common/impl/commandoption.c`, under the matching first-letter `case`. Six modules currently do this: CPUUsage, DiskIO, NetIO, PublicIP, Top and Weather.
|
||||
|
||||
### 7. Verify
|
||||
|
||||
```sh
|
||||
cmake -B build && cmake --build build -j
|
||||
./build/fastfetch -s foo --format json # JSON output
|
||||
./build/fastfetch -h foo-format # list formatArgs placeholders
|
||||
./build/fastfetch --gen-config # confirm it appears in the picker
|
||||
```
|
||||
|
||||
Note the `-format` suffix on the help flag: `fastfetch -h foo` is not supported; only `fastfetch -h foo-format` works.
|
||||
|
||||
If you added detection sources, re-check [step 5](#5-add-the-platform-sources-to-cmakeliststxt) before pushing: a platform block you missed compiles fine locally and fails only when that platform is built.
|
||||
|
||||
---
|
||||
|
||||
## Walkthrough: adding a logo
|
||||
|
||||
A new logo **must** have a corresponding "Logo Request" issue, linked from the PR with `Closes #1234`. Logo PRs without a linked issue are not accepted.
|
||||
|
||||
### 1. Add the ASCII file
|
||||
|
||||
```
|
||||
src/logo/ascii/<first-letter>/<distro>.txt
|
||||
```
|
||||
|
||||
For example `src/logo/ascii/d/distro.txt`. Directories are already split by first letter (`a/` … `z/`, plus `_/`).
|
||||
|
||||
The file is plain ASCII art with `$1` … `$9` as color placeholders:
|
||||
|
||||
```
|
||||
$3 ,-^-___
|
||||
$3 /\\\///
|
||||
$2refined.$1 /\\\\//
|
||||
```
|
||||
|
||||
- `$1` … `$9` map to palette slots 1–9; at most 9 are supported (`FASTFETCH_LOGO_MAX_COLORS = 9`)
|
||||
- `$$` is a literal `$`
|
||||
- Characters without a placeholder inherit the current color. Of the 530 existing logos, 239 use no placeholders at all (monochrome) and 291 do; **new logos should use placeholders**, as monochrome is a legacy style
|
||||
- Tabs are expanded to 4 spaces
|
||||
- Colors can be overridden with `--logo-color-1` … `--logo-color-9`
|
||||
|
||||
### 2. Register it in the `.inc`
|
||||
|
||||
CMake turns each `.txt` file into a `FASTFETCH_DATATEXT_LOGO_<UPPERCASE>` macro (`CMakeLists.txt:418`), but **the registry itself is maintained by hand**. Edit `src/logo/ascii/<first-letter>.inc`:
|
||||
|
||||
```c
|
||||
// src/logo/ascii/d.inc
|
||||
|
||||
#ifdef FASTFETCH_DATATEXT_LOGO_DISTRO
|
||||
// Distro
|
||||
{
|
||||
.names = { "Distro" }, // ID (preferred) or NAME from /etc/os-release, do NOT add both
|
||||
.lines = FASTFETCH_DATATEXT_LOGO_DISTRO,
|
||||
.colors = {
|
||||
FF_COLOR_FG_PRIMARY, // recommended for using as WHITE replacement, light-theme terminal friendly
|
||||
FF_COLOR_FG_BLUE, // preferred
|
||||
FF_COLOR_FG_256 "34",
|
||||
FF_COLOR_FG_RGB "0;255;0", // not recommended because of bad compatibility with raw TTY
|
||||
},
|
||||
},
|
||||
#endif
|
||||
```
|
||||
|
||||
- `names` is matched case-insensitively. Do not add names that differ only by case, because they can never be distinguished. Every name is also shown by `--list-logos`, so use user-friendly spelling such as an initial capital where appropriate.
|
||||
- For a new logo, use exactly one name unless there is a specific compatibility reason to add more. That name should first be the `ID` from `/etc/os-release`. If the ID cannot distinguish this logo from another logo for the same OS, use the `NAME` value instead. `logoGetBuiltinDetected` in `src/logo/logo.c` documents the detection order: `ID`, then `NAME`, then tokens from `ID_LIKE`, then the platform name fallback. Manual `logo-source` selection uses the name directly.
|
||||
- `colors` is positional — entry 0 is `$1`, entry 1 is `$2`, and so on
|
||||
- `colors[0]` also becomes the title color and `colors[1]` the key color, unless the user overrides them
|
||||
|
||||
If the same OS has multiple logo variants, mark the variant explicitly with `.type`:
|
||||
|
||||
```c
|
||||
.type = FF_LOGO_LINE_TYPE_SMALL_BIT,
|
||||
```
|
||||
|
||||
Use `FF_LOGO_LINE_TYPE_ALTER_BIT` for an alternate logo, `FF_LOGO_LINE_TYPE_SMALL_BIT` for a small logo, or combine the flags when both apply. This is also a lookup optimization: a logo marked `FF_LOGO_LINE_TYPE_SMALL_BIT` is considered only for `type = small`, while a logo marked `FF_LOGO_LINE_TYPE_ALTER_BIT` is never selected by automatic detection. Alternate logos are available only when explicitly requested through `-l <source>`.
|
||||
|
||||
### 3. Verify
|
||||
|
||||
```sh
|
||||
./build/fastfetch -l distro
|
||||
./build/fastfetch --list-logos | grep -i distro
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Platform conventions
|
||||
|
||||
The `detection/` layer picks its implementation by **filename suffix**; the choice is made at build time from `CMAKE_SYSTEM_NAME`:
|
||||
|
||||
| Suffix | Platform |
|
||||
|---|---|
|
||||
| `_linux.c` | Linux |
|
||||
| `_apple.c` / `_apple.m` | macOS / iOS (`.m` for Objective-C) |
|
||||
| `_windows.c` / `_windows.cpp` | Windows (`.cpp` for WinRT / WMI) |
|
||||
| `_bsd.c` | FreeBSD |
|
||||
| `_nbsd.c` | NetBSD |
|
||||
| `_obsd.c` | OpenBSD |
|
||||
| `_sunos.c` | Solaris / illumos |
|
||||
| `_haiku.c` / `.cpp` | Haiku |
|
||||
| `_android.c` | Android (Termux) |
|
||||
| `_gnu.c` | GNU/Hurd |
|
||||
| `_nosupport.c` | Empty fallback |
|
||||
|
||||
Current distribution of platform files in `detection/`:
|
||||
|
||||
| Suffix | Files | Suffix | Files |
|
||||
|---|---|---|---|
|
||||
| `_linux` | 58 | `_obsd` | 16 |
|
||||
| `_windows` | 47 | `_haiku` | 13 |
|
||||
| `_apple` | 46 | `_android` | 12 |
|
||||
| `_nosupport` | 45 | `_gnu` | 3 |
|
||||
| `_bsd` | 29 | | |
|
||||
| `_sunos` | 19 | | |
|
||||
| `_nbsd` | 17 | | |
|
||||
|
||||
(These are repository inventory figures, counting `.c`, `.m`, `.cpp`, and `.h`; they may change as platforms and modules are added.)
|
||||
|
||||
**Do not accumulate `#ifdef __linux__` blocks in a single file.** When adding platform support, copy the closest existing implementation and change the suffix.
|
||||
|
||||
`common/` follows the same convention: `common/impl/` contains `io_unix.c` / `io_windows.c`, `netif_linux.c` / `netif_apple.c` / `netif_bsd.c` and similar files, with `common/apple/`, `common/windows/` and `common/haiku/` holding platform-specific helpers.
|
||||
|
||||
When several platform suffixes could apply, use the most specific implementation supported by the build system (for example, `_nbsd.c` instead of the generic `_bsd.c` on NetBSD). Keep the generic file as the fallback for platforms that share its conventions.
|
||||
|
||||
---
|
||||
|
||||
## Code style
|
||||
|
||||
### Formatting
|
||||
|
||||
The project uses clang-format with `BasedOnStyle: LLVM` plus these key overrides:
|
||||
|
||||
```yaml
|
||||
IndentWidth: 4
|
||||
UseTab: Never
|
||||
ColumnLimit: 0 # never wrap
|
||||
InsertBraces: true # braces even on single-statement ifs
|
||||
PointerAlignment: Left # char* p, not char *p
|
||||
SortIncludes: Never # include order is managed by hand
|
||||
AlignAfterOpenBracket: DontAlign
|
||||
BinPackParameters: false # all params on one line, or one per line
|
||||
```
|
||||
|
||||
Before committing:
|
||||
|
||||
```sh
|
||||
clang-format -i src/modules/foo/*.c src/modules/foo/*.h
|
||||
```
|
||||
|
||||
`src/3rdparty/**`, `build/**` and `src/logo/builtin.c` are listed in `.clang-format-ignore` — **do not reformat them.**
|
||||
|
||||
`.editorconfig` specifies LF line endings, a 4-space indent, a final newline, and trailing whitespace trimmed (except in Markdown).
|
||||
|
||||
### Naming
|
||||
|
||||
| Kind | Convention | Example |
|
||||
|---|---|---|
|
||||
| Functions | `ff` + PascalCase | `ffDetectCPU`, `ffPrintCPU` |
|
||||
| Types | `FF` + PascalCase | `FFCPUResult`, `FFModuleBaseInfo` |
|
||||
| Variables / fields | camelCase | `coresPhysical`, `keyWidth` |
|
||||
| Macros | SCREAMING_SNAKE_CASE | `FF_OPTION_MAX_SIZE` |
|
||||
| Module options | `FF<Name>Options` | `FFCPUOptions` |
|
||||
| Detection results | `FF<Name>Result` | `FFCPUResult` |
|
||||
|
||||
### Spelling
|
||||
|
||||
CI runs codespell (`.codespellrc`). Known false positives are listed in `ignore-words-list` (`iterm`, `compiletime`, and various non-English distro words). Add new words there rather than changing the code.
|
||||
|
||||
### Compiler warnings
|
||||
|
||||
The build enables `-Wall -Wextra -Wconversion` plus several `-Werror`s:
|
||||
|
||||
```
|
||||
-Werror=uninitialized -Werror=return-type -Werror=vla
|
||||
-Werror=incompatible-pointer-types -Werror=implicit-function-declaration -Werror=int-conversion
|
||||
```
|
||||
|
||||
**`-Wconversion` is strict**: every implicit narrowing conversion needs an explicit cast.
|
||||
|
||||
---
|
||||
|
||||
## Commit messages
|
||||
|
||||
Format:
|
||||
|
||||
```
|
||||
<Scope>[ (Platform)]: <third-person singular verb> <object>
|
||||
```
|
||||
|
||||
Scope is the module or subsystem name; Platform is optional. Use the third-person singular present tense, capitalize the first letter, no trailing period.
|
||||
|
||||
Real examples from recent history:
|
||||
|
||||
```
|
||||
Processes (Haiku): honors `options->countKprocs`
|
||||
WM (macOS): improves reliability of WM plugin detection
|
||||
Memory (Windows): prefers `NQSI`
|
||||
Top (Linux): improves performance of `stat` parsing
|
||||
Logo (Builtin): adds omarchy
|
||||
Global: introduces global macro `FF_PATH_PKG_BASE` to replace `_PATH_LOCALBASE`
|
||||
LM (OpenBSD): adds support
|
||||
CI: disables fail on alert
|
||||
Doc: updates README
|
||||
Presets: moves `top` to the bottom of running modules [ci skip]
|
||||
```
|
||||
|
||||
Common verbs: `adds`, `removes`, `fixes`, `improves`, `updates`, `corrects`, `prefers`, `honors`, `disables`, `enables`, `introduces`, `detects`, `reports`, `skips`, `uses`.
|
||||
|
||||
Scopes in use: `Top`, `Processes`, `Memory`, `Logo (Builtin)`, `CI`, `Doc`, `Presets`, `Global`, plus individual module names.
|
||||
|
||||
Documentation-only changes use a `[ci skip]` suffix.
|
||||
|
||||
---
|
||||
|
||||
## Changelog
|
||||
|
||||
User-visible changes go into `CHANGELOG.md`, under the topmost version heading:
|
||||
|
||||
```markdown
|
||||
# Unreleased
|
||||
|
||||
Changes:
|
||||
* The DE / WM / LM modules now reports the full name ...
|
||||
|
||||
Features:
|
||||
* Added Top module to print processes with the highest CPU, memory or disk I/O usage. (Top)
|
||||
* Improved Wi-Fi module
|
||||
* Added Wifi channel width detection, exposed via `{channel-width}` in custom format.
|
||||
* Improved Wifi channel frequency accuracy on Windows, macOS.
|
||||
* Added Battery detection support on SunOS. (Battery, SunOS)
|
||||
|
||||
Bugfixes:
|
||||
* Fixed I/O rate calculation precision in DiskIO and NetIO. (DiskIO / NetIO)
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- Three sections: `Changes:` (behavior changes), `Features:`, `Bugfixes:`
|
||||
- Past tense: `Added` / `Fixed` / `Improved`
|
||||
- End each entry with its scope: `(Module, Platform)` or `(ModuleA / ModuleB)`
|
||||
- Sub-bullets are indented 4 spaces
|
||||
|
||||
---
|
||||
|
||||
## Tests
|
||||
|
||||
Tests are standalone executables in `tests/` using a `VERIFY` macro that exits non-zero on failure:
|
||||
|
||||
```c
|
||||
#define VERIFY(expression) \
|
||||
if (!(expression)) testFailed(&strbuf, #expression, __LINE__)
|
||||
```
|
||||
|
||||
Current tests: `strbuf.c`, `list.c`, `format.c`, `color.c`, `duration.c`, `strutil.c`.
|
||||
|
||||
```sh
|
||||
cmake -B build -DBUILD_TESTS=On
|
||||
cmake --build build
|
||||
cd build && ctest --output-on-failure
|
||||
```
|
||||
|
||||
Coverage focuses on the core data structures and the formatting engine in `common/`. The `detection/` layer has no automated tests, because it depends on the state of a running system; it is instead covered by the CI matrix — 20 workflows under `.github/workflows/` spanning Linux (including musl, loong64, armv7l, i686), macOS, Windows, FreeBSD, NetBSD, OpenBSD, DragonFly, Solaris, OmniOS and Haiku, plus spellcheck and benchmark jobs.
|
||||
|
||||
**If you modify `common/FFstrbuf.h`, `common/format.h` or `common/color.h`, extend the corresponding test.**
|
||||
|
||||
---
|
||||
|
||||
## Pull requests
|
||||
|
||||
1. **Open an issue first** (feature request / bug report / logo request) to confirm that the change is wanted before investing effort. Templates are in `.github/ISSUE_TEMPLATE/`.
|
||||
2. Branch off `dev` — **`dev` is the main development branch**, not `master`.
|
||||
3. Follow the [commit message convention](#commit-messages).
|
||||
4. Update `CHANGELOG.md` for user-visible changes.
|
||||
5. Open the PR against `dev` and fill in `.github/pull_request_template.md`:
|
||||
- Summary
|
||||
- Related issue (**required for new logos**; otherwise the PR is not accepted)
|
||||
- Changes
|
||||
- Screenshots (required for visual changes)
|
||||
- Checklist: confirm you tested locally
|
||||
|
||||
### Pre-submit checklist
|
||||
|
||||
```sh
|
||||
clang-format -i <changed files> # format
|
||||
codespell # spelling
|
||||
cmake -B build -DBUILD_TESTS=On && cmake --build build -j
|
||||
cd build && ctest --output-on-failure # tests
|
||||
./build/fastfetch --format json # verify JSON output is well-formed
|
||||
./build/fastfetch -c presets/all.jsonc --stat false # smoke-test every module
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pitfalls
|
||||
|
||||
The following points are easy to misinterpret when reading this codebase. Most of them follow from the startup-time objective described above.
|
||||
|
||||
### 1. Option structs must not exceed 256 bytes
|
||||
|
||||
`FF_OPTION_MAX_SIZE = 1 << 8`. Exceeding it is caught at compile time by `static_assert`. Do not attempt to increase the value: it determines the stack cost of every module invocation.
|
||||
|
||||
### 2. `FF_MODULE_DISABLE_*` controls registration, not compilation
|
||||
|
||||
```c
|
||||
// verbatim from the top of modules/modules.c:
|
||||
// FF_MODULE_DISABLE_<module> only controls if the module is registered,
|
||||
// the module code itself is still compiled.
|
||||
// We rely `LTO` to remove the unused code (only enabled in Release mode)
|
||||
```
|
||||
|
||||
**Disabling modules in a Debug build does not shrink the binary.** LTO is enabled whenever `CMAKE_BUILD_TYPE != Debug`, and the default `RelWithDebInfo` already satisfies that — so measure size with `RelWithDebInfo` or `Release`, never with `Debug`. (The "Release mode" wording in the comment above is imprecise.)
|
||||
|
||||
### 3. Only two things are discovered by glob
|
||||
|
||||
The module sources (`CMakeLists.txt:134`) and the logo `.txt` files (`CMakeLists.txt:429`). Both use `CONFIGURE_DEPENDS`, so with the Makefile and Ninja generators the build re-checks the glob and re-runs CMake when the result changes; other generators (Visual Studio and Xcode in particular) do not track it as reliably. Re-run `cmake -B build` after adding a module source or a logo file rather than relying on that behavior.
|
||||
|
||||
Everything else — `src/detection/**` and `src/common/impl/**`, and any source outside the platform chain — must be listed in `CMakeLists.txt` by hand; see [step 5](#5-add-the-platform-sources-to-cmakeliststxt).
|
||||
|
||||
### 4. CLI module options are removed
|
||||
|
||||
Per-module command-line flags such as `--cpu-temp` are **no longer supported**. The only job of `ffParseModuleOptions` now is to translate the flag into a JSON key, then `exit(477)` with a pointer to the config file:
|
||||
|
||||
```
|
||||
Error: Unsupported module option: --cpu-temp
|
||||
Support of module options has been removed. Please add the flag to the JSON config instead.
|
||||
Example (demonstration only): `{ "modules": [ { "type": "cpu", "temp": true } ] }`
|
||||
```
|
||||
|
||||
**JSONC is the first-class configuration interface; the command line is not.** When adding module options, implement `parseJsonObject` only — do not add a CLI branch.
|
||||
|
||||
### 5. `defaultOrder = 0` hides a module from `--gen-config`
|
||||
|
||||
`collectModuleInfos` (`genconfig.c:186`) skips any module whose `defaultOrder` is `0`. C zero-initializes the field, so **omitting it is the same as setting it to 0**. Only `logo`, `command` and `custom` rely on this intentionally.
|
||||
|
||||
`defaultOrder` affects only the ordering in the interactive `--gen-config` picker; it has no effect on the runtime output order.
|
||||
|
||||
### 6. Localization uses byte offsets, not enums
|
||||
|
||||
`instance.config.display.keyLanguage` does not hold a language enum — it holds a **byte offset** such as `offsetof(FFModuleDisplayName, zh_CN)`. The macro `FF_MODULE_GET_DISPLAY_NAME` (`common/option.h:123`) does plain pointer arithmetic with it:
|
||||
|
||||
```c
|
||||
#define FF_MODULE_GET_DISPLAY_NAME(moduleName) \
|
||||
(*(const char**) ((uint8_t*) &ff ## moduleName ## ModuleInfo.displayName + instance.config.display.keyLanguage))
|
||||
```
|
||||
|
||||
Consequence: **the field order of `FFModuleDisplayName` must never change** — reordering the fields silently corrupts the output for every language.
|
||||
|
||||
### 7. The `multithreading` option has limited effect
|
||||
|
||||
The global `multithreading` option currently takes effect in exactly one place: `common/impl/networking_linux.c:339`. Modules are still printed sequentially. Modules that need concurrency go through the `ffPrepare*` warm-up hooks instead, which start sampling or fire off requests before the print loop begins.
|
||||
|
||||
This may change in the future. The main blocking issue is that there are dependencies between different modules.
|
||||
|
||||
### 8. Do not print or read configuration inside `detection/`
|
||||
|
||||
The `detection/` layer must stay pure: read system state, fill a struct, return an error string. Any `printf` or any read of `instance.config` there is a design error.
|
||||
|
||||
Use `FF_DEBUG` (`src/common/debug.h`) for logging.
|
||||
|
||||
### 9. `src/logo/builtin.c` and `3rdparty/` are formatting-exempt
|
||||
|
||||
The former is a large generated and hand-maintained data table; the latter is upstream code. Both are excluded via `.clang-format-ignore`; do not reformat either.
|
||||
|
||||
---
|
||||
|
||||
## Reference
|
||||
|
||||
- Configuration guide: <https://github.com/fastfetch-cli/fastfetch/wiki/Configuration>
|
||||
- JSON schema: `doc/json_schema.json`
|
||||
- Example presets: `presets/` (`all.jsonc`, `neofetch.jsonc`, `ci.jsonc`, …)
|
||||
- Man page source: `doc/fastfetch.1.in`
|
||||
- Code generation scripts: `scripts/` (`gen-pciids.py`, `gen-amdgpuids.py`, `gen-man.py`)
|
||||
@@ -1,6 +1,5 @@
|
||||
# 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)
|
||||
|
||||
Vendored
-12
@@ -1,15 +1,3 @@
|
||||
fastfetch (2.68.1~#UBUNTU_CODENAME#) #UBUNTU_CODENAME#; urgency=medium
|
||||
|
||||
* Update to 2.68.1
|
||||
|
||||
-- Carter Li <zhangsongcui@live.cn> Wed, 02 Sep 2026 10:47:51 +0800
|
||||
|
||||
fastfetch (2.68.0~#UBUNTU_CODENAME#) #UBUNTU_CODENAME#; urgency=medium
|
||||
|
||||
* Update to 2.68.0
|
||||
|
||||
-- Carter Li <zhangsongcui@live.cn> Tue, 01 Sep 2026 16:34:36 +0800
|
||||
|
||||
fastfetch (2.67.0~#UBUNTU_CODENAME#) #UBUNTU_CODENAME#; urgency=medium
|
||||
|
||||
* Update to 2.67.0
|
||||
|
||||
+4
-112
@@ -398,7 +398,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"lmFormat": {
|
||||
"description": "Output format for the `LM` module. See Wiki for formatting syntax\n 1. {service}: LM service / process name\n 2. {pretty-name}: LM pretty name\n 3. {version}: LM version",
|
||||
"description": "Output format for the `LM` module. See Wiki for formatting syntax\n 1. {service}: LM service\n 2. {type}: LM type\n 3. {version}: LM version",
|
||||
"type": "string"
|
||||
},
|
||||
"loadavgFormat": {
|
||||
@@ -446,7 +446,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"packagesFormat": {
|
||||
"description": "Output format for the `Packages` module. See Wiki for formatting syntax\n 1. {am-system}: Number of am-system packages\n 2. {am-user}: Number of am-user (aka appman) packages\n 3. {appimage}: Number of appimage packages\n 4. {apk}: Number of apk packages\n 5. {brew}: Number of brew packages\n 6. {brew-cask}: Number of brew-cask packages\n 7. {cards}: Number of cards packages\n 8. {choco}: Number of choco packages\n 9. {crux}: Number of crux packages\n 10. {dpkg}: Number of dpkg packages\n 11. {emerge}: Number of emerge packages\n 12. {eopkg}: Number of eopkg packages\n 13. {flatpak-system}: Number of flatpak-system app packages\n 14. {flatpak-user}: Number of flatpak-user app packages\n 15. {guix-home}: Number of guix-home packages\n 16. {guix-system}: Number of guix-system packages\n 17. {guix-user}: Number of guix-user packages\n 18. {hpkg-system}: Number of hpkg-system packages\n 19. {hpkg-user}: Number of hpkg-user packages\n 20. {install-release}: Number of install-release packages\n 21. {kiss}: Number of kiss packages\n 22. {linglong}: Number of linglong packages\n 23. {lpkg}: Number of lpkg packages\n 24. {lpkgbuild}: Number of lpkgbuild packages\n 25. {macports}: Number of macports packages\n 26. {mport}: Number of mport packages\n 27. {moss}: Number of moss packages\n 28. {nix-default}: Number of nix-default packages\n 29. {nix-system}: Number of nix-system packages\n 30. {nix-user}: Number of nix-user packages\n 31. {opkg}: Number of opkg packages\n 32. {pacman}: Number of pacman packages\n 33. {pacman-branch}: Pacman branch on manjaro\n 34. {pacstall}: Number of pacstall packages\n 35. {paludis}: Number of paludis packages\n 36. {pisi}: Number of pisi packages\n 37. {pkg}: Number of pkg packages\n 38. {pkgsrc}: Number of pkgsrc packages\n 39. {pkgtool}: Number of pkgtool packages\n 40. {porg}: Number of porg packages\n 41. {rpm}: Number of rpm packages\n 42. {scoop-global}: Number of scoop-global packages\n 43. {scoop-user}: Number of scoop-user packages\n 44. {snap}: Number of snap packages\n 45. {soar}: Number of soar packages\n 46. {sorcery}: Number of sorcery packages\n 47. {winget}: Number of winget packages\n 48. {xbps}: Number of xbps packages\n 49. {brew-all}: Total number of all brew packages\n 50. {flatpak-all}: Total number of all flatpak app packages\n 51. {guix-all}: Total number of all guix packages\n 52. {hpkg-all}: Total number of all hpkg packages\n 53. {nix-all}: Total number of all nix packages\n 54. {all}: Number of all packages",
|
||||
"description": "Output format for the `Packages` module. See Wiki for formatting syntax\n 1. {am-system}: Number of am-system packages\n 2. {am-user}: Number of am-user (aka appman) packages\n 3. {appimage}: Number of appimage packages\n 4. {apk}: Number of apk packages\n 5. {brew}: Number of brew packages\n 6. {brew-cask}: Number of brew-cask packages\n 7. {cards}: Number of cards packages\n 8. {choco}: Number of choco packages\n 9. {dpkg}: Number of dpkg packages\n 10. {emerge}: Number of emerge packages\n 11. {eopkg}: Number of eopkg packages\n 12. {flatpak-system}: Number of flatpak-system app packages\n 13. {flatpak-user}: Number of flatpak-user app packages\n 14. {guix-home}: Number of guix-home packages\n 15. {guix-system}: Number of guix-system packages\n 16. {guix-user}: Number of guix-user packages\n 17. {hpkg-system}: Number of hpkg-system packages\n 18. {hpkg-user}: Number of hpkg-user packages\n 19. {install-release}: Number of install-release packages\n 20. {kiss}: Number of kiss packages\n 21. {linglong}: Number of linglong packages\n 22. {lpkg}: Number of lpkg packages\n 23. {lpkgbuild}: Number of lpkgbuild packages\n 24. {macports}: Number of macports packages\n 25. {mport}: Number of mport packages\n 26. {moss}: Number of moss packages\n 27. {nix-default}: Number of nix-default packages\n 28. {nix-system}: Number of nix-system packages\n 29. {nix-user}: Number of nix-user packages\n 30. {opkg}: Number of opkg packages\n 31. {pacman}: Number of pacman packages\n 32. {pacman-branch}: Pacman branch on manjaro\n 33. {pacstall}: Number of pacstall packages\n 34. {paludis}: Number of paludis packages\n 35. {pisi}: Number of pisi packages\n 36. {pkg}: Number of pkg packages\n 37. {pkgsrc}: Number of pkgsrc packages\n 38. {pkgtool}: Number of pkgtool packages\n 39. {porg}: Number of porg packages\n 40. {rpm}: Number of rpm packages\n 41. {scoop-global}: Number of scoop-global packages\n 42. {scoop-user}: Number of scoop-user packages\n 43. {snap}: Number of snap packages\n 44. {soar}: Number of soar packages\n 45. {sorcery}: Number of sorcery packages\n 46. {winget}: Number of winget packages\n 47. {xbps}: Number of xbps packages\n 48. {brew-all}: Total number of all brew packages\n 49. {flatpak-all}: Total number of all flatpak app packages\n 50. {guix-all}: Total number of all guix packages\n 51. {hpkg-all}: Total number of all hpkg packages\n 52. {nix-all}: Total number of all nix packages\n 53. {all}: Number of all packages",
|
||||
"type": "string"
|
||||
},
|
||||
"physicaldiskFormat": {
|
||||
@@ -505,10 +505,6 @@
|
||||
"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. {pid}: Process ID\n 3. {cpu}: CPU usage\n 4. {mem}: Memory usage (RSS) in bytes\n 5. {disk-read}: Disk read bytes per second (0 if unsupported)\n 6. {disk-write}: Disk write bytes per second (0 if unsupported)\n 7. {threads}: Number of threads\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"
|
||||
@@ -546,7 +542,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. {channel-width}: Connection channel width in MHz\n 14. {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. {band}: Connection channel band in GHz",
|
||||
"type": "string"
|
||||
},
|
||||
"wmthemeFormat": {
|
||||
@@ -1596,7 +1592,6 @@
|
||||
"poweradapter",
|
||||
"processes",
|
||||
"publicip",
|
||||
"top",
|
||||
"separator",
|
||||
"shell",
|
||||
"sound",
|
||||
@@ -3763,7 +3758,6 @@
|
||||
"apk",
|
||||
"brew",
|
||||
"choco",
|
||||
"crux",
|
||||
"dpkg",
|
||||
"emerge",
|
||||
"eopkg",
|
||||
@@ -3985,12 +3979,7 @@
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "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
|
||||
"description": "Print the number of running processes"
|
||||
},
|
||||
"key": {
|
||||
"$ref": "#/$defs/key"
|
||||
@@ -4435,103 +4424,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Top",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "top",
|
||||
"description": "Print processes with the highest CPU, memory, disk I/O usage, latest start time or thread count"
|
||||
},
|
||||
"sort": {
|
||||
"type": "string",
|
||||
"description": "Sort processes by CPU, memory, disk IO usage, start time or thread count",
|
||||
"enum": [
|
||||
"cpu",
|
||||
"memory",
|
||||
"disk-read",
|
||||
"disk-write",
|
||||
"start-time",
|
||||
"threads"
|
||||
],
|
||||
"default": "cpu"
|
||||
},
|
||||
"showTypes": {
|
||||
"description": "Specify which resource usages are detected and displayed.\nIf memory or threads 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",
|
||||
"threads"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"cpu",
|
||||
"memory",
|
||||
"disk",
|
||||
"threads"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"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",
|
||||
|
||||
+4
-5
@@ -99,17 +99,16 @@
|
||||
"gamepad",
|
||||
"mouse",
|
||||
"keyboard",
|
||||
"top",
|
||||
{
|
||||
"type": "weather",
|
||||
"timeout": 1000
|
||||
},
|
||||
"netio",
|
||||
"diskio",
|
||||
{
|
||||
"type": "physicaldisk",
|
||||
"temp": true
|
||||
},
|
||||
{
|
||||
"type": "weather",
|
||||
"timeout": 1000
|
||||
},
|
||||
"tpm",
|
||||
"version",
|
||||
"break",
|
||||
|
||||
+6
-7
@@ -94,24 +94,23 @@
|
||||
"opengl",
|
||||
"opencl",
|
||||
"users",
|
||||
"bluetooth",
|
||||
"bluetoothradio",
|
||||
// "bluetooth", // doesn't work on macOS because it requires bluetooth permissions
|
||||
// "bluetoothradio",
|
||||
"sound",
|
||||
"camera",
|
||||
"gamepad",
|
||||
"mouse",
|
||||
"keyboard",
|
||||
"top",
|
||||
{
|
||||
"type": "weather",
|
||||
"timeout": 1000
|
||||
},
|
||||
"netio",
|
||||
"diskio",
|
||||
{
|
||||
"type": "physicaldisk",
|
||||
"temp": true
|
||||
},
|
||||
{
|
||||
"type": "weather",
|
||||
"timeout": 1000
|
||||
},
|
||||
"tpm",
|
||||
"version",
|
||||
"logo",
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
},
|
||||
"modules": [
|
||||
{
|
||||
// draw borders first to make colors of left and right border consistent
|
||||
// draw borders first to make colors of left and right border consistant
|
||||
"key": " user",
|
||||
"type": "title",
|
||||
"format": "{user-name}",
|
||||
|
||||
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"home": "https://github.com/ibireme/yyjson",
|
||||
"license": "MIT ( embed in source )",
|
||||
"version": "0.13.0",
|
||||
"version": "0.12.0",
|
||||
"author": "ibireme"
|
||||
}
|
||||
|
||||
Vendored
+285
-700
File diff suppressed because it is too large
Load Diff
Vendored
+674
-911
File diff suppressed because it is too large
Load Diff
@@ -1,17 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#if __BIG_ENDIAN__
|
||||
#define FF_READ_LE(x) _Generic((x), \
|
||||
uint16_t: __builtin_bswap16(x), \
|
||||
uint32_t: __builtin_bswap32(x), \
|
||||
uint64_t: __builtin_bswap64(x))
|
||||
#define FF_READ_BE(x) (x)
|
||||
#else
|
||||
#define FF_READ_LE(x) (x)
|
||||
#define FF_READ_BE(x) _Generic((x), \
|
||||
uint16_t: __builtin_bswap16(x), \
|
||||
uint32_t: __builtin_bswap32(x), \
|
||||
uint64_t: __builtin_bswap64(x))
|
||||
#endif
|
||||
@@ -3,8 +3,7 @@
|
||||
#include "common/arrutil.h"
|
||||
#include "common/strutil.h"
|
||||
#include "common/io.h"
|
||||
#include "common/path.h"
|
||||
#include "common/mallocHelper.h"
|
||||
#include "fastfetch_config.h"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <pwd.h>
|
||||
@@ -20,6 +19,7 @@
|
||||
#elif defined(__OpenBSD__)
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <kvm.h>
|
||||
#include "common/path.h"
|
||||
#elif defined(__HAIKU__)
|
||||
#include <image.h>
|
||||
@@ -68,14 +68,13 @@ static void getExePath(FFPlatform* platform) {
|
||||
// Current implementation uses argv[0], which can be easily spoofed.
|
||||
// See #2195
|
||||
size_t exePathLen = 0;
|
||||
{
|
||||
char argvBuf[ARG_MAX];
|
||||
size_t argvSize = sizeof(argvBuf);
|
||||
int argvMib[] = { CTL_KERN, KERN_PROC_ARGS, (pid_t) platform->pid, KERN_PROC_ARGV };
|
||||
if (sysctl(argvMib, ARRAY_SIZE(argvMib), argvBuf, &argvSize, nullptr, 0) == 0) {
|
||||
// The buffer is filled with an array of char pointers followed by the strings themselves
|
||||
char** argv = (char**) argvBuf;
|
||||
if (argv[0] && (char*) argv[0] >= argvBuf && (char*) argv[0] < argvBuf + argvSize) {
|
||||
kvm_t* kd = kvm_openfiles(nullptr, nullptr, nullptr, KVM_NO_FILES, nullptr);
|
||||
if (kd) {
|
||||
int kpCount;
|
||||
struct kinfo_proc* kp = kvm_getprocs(kd, KERN_PROC_PID, (pid_t) platform->pid, sizeof(*kp), &kpCount);
|
||||
if (kp && kpCount == 1) {
|
||||
char** argv = kvm_getargv(kd, kp, 0);
|
||||
if (argv && argv[0]) {
|
||||
char* arg0 = argv[0];
|
||||
if (arg0[0]) {
|
||||
if (strchr(arg0, '/') != nullptr) // likely a path (absolute or relative)
|
||||
@@ -98,38 +97,25 @@ static void getExePath(FFPlatform* platform) {
|
||||
if (exePathLen > 0) {
|
||||
struct stat st;
|
||||
if (stat(exePath, &st) == 0 && S_ISREG(st.st_mode)) {
|
||||
// Replicate kvm_getfiles()'s live path: {CTL_KERN, KERN_FILE, KERN_FILE_BYPID, pid, esize, count}
|
||||
int fileMib[6] = { CTL_KERN, KERN_FILE, KERN_FILE_BYPID, (pid_t) platform->pid, (int) sizeof(struct kinfo_file), 0 };
|
||||
size_t fileSize = 0;
|
||||
if (sysctl(fileMib, ARRAY_SIZE(fileMib), nullptr, &fileSize, nullptr, 0) == 0) {
|
||||
fileSize += fileSize / 8; // add ~10%
|
||||
FF_AUTO_FREE struct kinfo_file* kf = (struct kinfo_file*) malloc(fileSize);
|
||||
if (kf) {
|
||||
int rv;
|
||||
do {
|
||||
fileMib[5] = (int) (fileSize / sizeof(struct kinfo_file));
|
||||
rv = sysctl(fileMib, ARRAY_SIZE(fileMib), kf, &fileSize, nullptr, 0);
|
||||
} while (rv == -1 && errno == ENOMEM);
|
||||
|
||||
if (rv == 0) {
|
||||
int cntp = (int) (fileSize / sizeof(struct kinfo_file));
|
||||
int i;
|
||||
for (i = 0; i < cntp; i++) {
|
||||
if (kf[i].fd_fd == KERN_FILE_TEXT) {
|
||||
// KERN_FILE_TEXT is the executable file, not a shared library, and should be unique in the list.
|
||||
if (st.st_dev != (dev_t) kf[i].va_fsid || st.st_ino != (ino_t) kf[i].va_fileid) {
|
||||
i = -1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i < 0) {
|
||||
exePathLen = 0;
|
||||
int cntp;
|
||||
struct kinfo_file* kf = kvm_getfiles(kd, KERN_FILE_BYPID, (pid_t) platform->pid, sizeof(*kf), &cntp);
|
||||
if (kf) {
|
||||
int i;
|
||||
for (i = 0; i < cntp; i++) {
|
||||
if (kf[i].fd_fd == KERN_FILE_TEXT) {
|
||||
// KERN_FILE_TEXT is the executable file, not a shared library, and should be unique in the list.
|
||||
if (st.st_dev != (dev_t) kf[i].va_fsid || st.st_ino != (ino_t) kf[i].va_fileid) {
|
||||
i = -1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// If we can't get the list of open files, we can't verify that the file is actually the executable
|
||||
// Assume it is
|
||||
}
|
||||
if (i < 0) {
|
||||
exePathLen = 0;
|
||||
}
|
||||
} else {
|
||||
// If we can't get the list of open files, we can't verify that the file is actually the executable
|
||||
// Assume it is
|
||||
}
|
||||
} else {
|
||||
exePathLen = 0;
|
||||
@@ -138,6 +124,7 @@ static void getExePath(FFPlatform* platform) {
|
||||
}
|
||||
}
|
||||
}
|
||||
kvm_close(kd);
|
||||
}
|
||||
#elif defined(__sun)
|
||||
ssize_t exePathLen = readlink("/proc/self/path/a.out", exePath, sizeof(exePath) - 1);
|
||||
@@ -251,8 +238,8 @@ static void getDataDirs(FFPlatform* platform) {
|
||||
|
||||
ffPlatformPathAddHome(&platform->dataDirs, platform, "");
|
||||
platformPathAddEnv(&platform->dataDirs, "XDG_DATA_DIRS");
|
||||
#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
|
||||
ffPlatformPathAddAbsolute(&platform->dataDirs, FF_PATH_PKG_BASE "/share/");
|
||||
#ifdef _PATH_LOCALBASE
|
||||
ffPlatformPathAddAbsolute(&platform->dataDirs, _PATH_LOCALBASE "/share/");
|
||||
#endif
|
||||
ffPlatformPathAddAbsolute(&platform->dataDirs, FASTFETCH_TARGET_DIR_USR "/local/share/");
|
||||
ffPlatformPathAddAbsolute(&platform->dataDirs, FASTFETCH_TARGET_DIR_USR "/share/");
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "common/base64.h"
|
||||
#include "common/endian.h"
|
||||
|
||||
// https://github.com/kostya/benchmarks/blob/master/base64/test-nolib.c#L145
|
||||
void ffBase64EncodeRaw(uint32_t size, const char* str, uint32_t* out_size, char* output) {
|
||||
@@ -8,8 +7,12 @@ void ffBase64EncodeRaw(uint32_t size, const char* str, uint32_t* out_size, char*
|
||||
const char* ends = str + (size - size % 3);
|
||||
while (str != ends) {
|
||||
uint32_t n = *(uint32_t*) str;
|
||||
// The 3 input bytes must be laid out big-endian (str[0] in the most significant position).
|
||||
n = FF_READ_BE(n);
|
||||
#if !__BIG_ENDIAN__
|
||||
// The 3 input bytes must be laid out big-endian (str[0] in the most
|
||||
// significant position). On little-endian hosts swap; on big-endian
|
||||
// hosts the word is already in the right order.
|
||||
n = __builtin_bswap32(n);
|
||||
#endif
|
||||
*out++ = chars[(n >> 26) & 63];
|
||||
*out++ = chars[(n >> 20) & 63];
|
||||
*out++ = chars[(n >> 14) & 63];
|
||||
|
||||
@@ -70,15 +70,15 @@ void ffPrepareCommandOption(FFdata* data) {
|
||||
#define FF_IF_MODULE_MATCH(moduleNameConstant) if (ffStrEqualsIgnCase(moduleType, moduleNameConstant) && !ffStrbufSeparatedContainIgnCaseS(&data->structureDisabled, moduleNameConstant, ':'))
|
||||
|
||||
switch (moduleType[0]) {
|
||||
#if !FF_MODULE_DISABLE_CPUUSAGE
|
||||
#if !FF_MODULE_DISABLE_CPUUSAGE
|
||||
case 'C':
|
||||
case 'c':
|
||||
FF_IF_MODULE_MATCH(ffCPUUsageModuleInfo.name)
|
||||
ffPrepareCPUUsage();
|
||||
break;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if !FF_MODULE_DISABLE_DISKIO
|
||||
#if !FF_MODULE_DISABLE_DISKIO
|
||||
case 'D':
|
||||
case 'd':
|
||||
FF_IF_MODULE_MATCH(ffDiskIOModuleInfo.name) {
|
||||
@@ -87,9 +87,9 @@ void ffPrepareCommandOption(FFdata* data) {
|
||||
ffPrepareDiskIO(&options);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if !FF_MODULE_DISABLE_NETIO
|
||||
#if !FF_MODULE_DISABLE_NETIO
|
||||
case 'N':
|
||||
case 'n':
|
||||
FF_IF_MODULE_MATCH(ffNetIOModuleInfo.name) {
|
||||
@@ -98,9 +98,9 @@ void ffPrepareCommandOption(FFdata* data) {
|
||||
ffPrepareNetIO(&options);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if !FF_MODULE_DISABLE_PUBLICIP
|
||||
#if !FF_MODULE_DISABLE_PUBLICIP
|
||||
case 'P':
|
||||
case 'p':
|
||||
FF_IF_MODULE_MATCH(ffPublicIPModuleInfo.name) {
|
||||
@@ -109,20 +109,9 @@ void ffPrepareCommandOption(FFdata* data) {
|
||||
ffPreparePublicIp(&options);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#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
|
||||
#if !FF_MODULE_DISABLE_WEATHER
|
||||
case 'W':
|
||||
case 'w':
|
||||
FF_IF_MODULE_MATCH(ffWeatherModuleInfo.name) {
|
||||
@@ -131,7 +120,7 @@ void ffPrepareCommandOption(FFdata* data) {
|
||||
ffPrepareWeather(&options);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
#undef FF_IF_MODULE_MATCH
|
||||
@@ -178,34 +167,6 @@ static void genJsonResult(FFdata* data, FFModuleBaseInfo* baseInfo, void* option
|
||||
}
|
||||
}
|
||||
|
||||
static yyjson_val* findStructureModuleConfig(FFdata* data, const char* moduleType) {
|
||||
if (data->configDoc == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
yyjson_val* root = yyjson_doc_get_root(data->configDoc);
|
||||
if (root == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
yyjson_val* modules = yyjson_obj_get(root, "modules");
|
||||
if (!yyjson_is_arr(modules)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
yyjson_val* item;
|
||||
size_t idx, max;
|
||||
yyjson_arr_foreach (modules, idx, max, item) {
|
||||
if (yyjson_is_obj(item)) {
|
||||
const char* type = yyjson_get_str(yyjson_obj_get(item, "type"));
|
||||
if (type != nullptr && ffStrEqualsIgnCase(type, moduleType)) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static bool parseStructureCommand(
|
||||
FFdata* data,
|
||||
const char* line,
|
||||
@@ -219,13 +180,7 @@ static bool parseStructureCommand(
|
||||
if (data->resultDoc != nullptr) {
|
||||
fn(data, baseInfo, optionBuf);
|
||||
} else {
|
||||
yyjson_val* configModule = findStructureModuleConfig(data, baseInfo->name);
|
||||
if (configModule != nullptr) {
|
||||
baseInfo->parseJsonObject(optionBuf, configModule);
|
||||
baseInfo->printModule(optionBuf);
|
||||
} else {
|
||||
baseInfo->printModule(optionBuf);
|
||||
}
|
||||
baseInfo->printModule(optionBuf);
|
||||
}
|
||||
baseInfo->destroyOptions(optionBuf);
|
||||
return true;
|
||||
|
||||
@@ -996,7 +996,7 @@ bool ffGenConfigInteractive(FFdata* data) {
|
||||
success = true;
|
||||
}
|
||||
} else {
|
||||
fputs("\nConfig generation canceled.\n", stderr);
|
||||
fputs("\nConfig generation cancelled.\n", stderr);
|
||||
}
|
||||
|
||||
ffListDestroy(&ui.items);
|
||||
|
||||
@@ -195,21 +195,6 @@ 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': {
|
||||
|
||||
@@ -47,7 +47,6 @@ void ffPrintLogoAndKey(const char* moduleName, uint8_t moduleIndex, const FFModu
|
||||
FF_PARSE_FORMAT_STRING_CHECKED(&key, &moduleArgs->key, ((FFformatarg[]) {
|
||||
FF_ARG(moduleIndex, "index"),
|
||||
FF_ARG(moduleArgs->keyIcon, "icon"),
|
||||
FF_ARG(moduleName, "module-name"),
|
||||
}));
|
||||
ffStrbufWriteTo(&key, stdout);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#elif defined(__OpenBSD__)
|
||||
#include <sys/param.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <kvm.h>
|
||||
#elif defined(__NetBSD__)
|
||||
#include <sys/types.h>
|
||||
#include <sys/sysctl.h>
|
||||
@@ -422,13 +423,12 @@ void ffProcessGetInfoLinux(pid_t pid, FFstrbuf* processName, FFstrbuf* exe, cons
|
||||
|
||||
#elif defined(__OpenBSD__)
|
||||
|
||||
char argvBuf[ARG_MAX];
|
||||
size_t argvSize = sizeof(argvBuf);
|
||||
int argvMib[] = { CTL_KERN, KERN_PROC_ARGS, pid, KERN_PROC_ARGV };
|
||||
if (sysctl(argvMib, ARRAY_SIZE(argvMib), argvBuf, &argvSize, nullptr, 0) == 0) {
|
||||
// The buffer is filled with an array of char pointers followed by the strings themselves
|
||||
char** argv = (char**) argvBuf;
|
||||
if (argv[0] && (char*) argv[0] >= argvBuf && (char*) argv[0] < argvBuf + argvSize) {
|
||||
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_PID, pid, sizeof(struct kinfo_proc), &count);
|
||||
if (proc) {
|
||||
char** argv = kvm_getargv(kd, proc, 0);
|
||||
if (argv) {
|
||||
const char* arg0 = argv[0];
|
||||
if (arg0[0] == '-') {
|
||||
arg0++;
|
||||
@@ -436,6 +436,7 @@ void ffProcessGetInfoLinux(pid_t pid, FFstrbuf* processName, FFstrbuf* exe, cons
|
||||
ffStrbufSetS(exe, arg0);
|
||||
}
|
||||
}
|
||||
kvm_close(kd);
|
||||
|
||||
#elif defined(__HAIKU__)
|
||||
|
||||
@@ -632,19 +633,21 @@ const char* ffProcessGetBasicInfoLinux(pid_t pid, FFstrbuf* name, pid_t* ppid, i
|
||||
|
||||
#elif defined(__OpenBSD__)
|
||||
|
||||
struct kinfo_proc proc;
|
||||
size_t size = sizeof(proc);
|
||||
int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, pid, (int) sizeof(struct kinfo_proc), 1 };
|
||||
if (sysctl(mib, ARRAY_SIZE(mib), &proc, &size, nullptr, 0) == 0) {
|
||||
ffStrbufSetS(name, proc.p_comm);
|
||||
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_PID, pid, sizeof(struct kinfo_proc), &count);
|
||||
if (proc) {
|
||||
ffStrbufSetS(name, proc->p_comm);
|
||||
if (ppid) {
|
||||
*ppid = proc.p_ppid;
|
||||
*ppid = proc->p_ppid;
|
||||
}
|
||||
if (tty) {
|
||||
*tty = (int) proc.p_tdev;
|
||||
*tty = (int) proc->p_tdev;
|
||||
}
|
||||
} else {
|
||||
return "sysctl(KERN_PROC_PID) failed";
|
||||
}
|
||||
kvm_close(kd);
|
||||
if (!proc) {
|
||||
return "kvm_getprocs() failed";
|
||||
}
|
||||
|
||||
#elif defined(__HAIKU__)
|
||||
|
||||
@@ -450,10 +450,12 @@ bool ffSettingsGetSQLite3String(const char* dbPath, const char* query, FFstrbuf*
|
||||
return true;
|
||||
}
|
||||
#else // FF_HAVE_SQLITE3
|
||||
int ffSettingsGetSQLite3Int(const char*, const char*) {
|
||||
int ffSettingsGetSQLite3Int(const char* dbPath, const char* query) {
|
||||
FF_UNUSED(dbPath, query)
|
||||
return 0;
|
||||
}
|
||||
bool ffSettingsGetSQLite3String(const char*, const char*, FFstrbuf*) {
|
||||
bool ffSettingsGetSQLite3String(const char* dbPath, const char* query, FFstrbuf* result) {
|
||||
FF_UNUSED(dbPath, query, result)
|
||||
return false;
|
||||
}
|
||||
#endif // FF_HAVE_SQLITE3
|
||||
@@ -507,8 +509,8 @@ typedef struct E_Config {
|
||||
Eina_List* font_defaults;
|
||||
} E_Config; // Must be the same name as the top level struct in e.cfg
|
||||
|
||||
#define FF_EET_EINA_FILE_DATA_DESCRIPTOR_CLASS_SET(klass, type) \
|
||||
(ffeet_eina_file_data_descriptor_class_set(klass, sizeof(*(klass)), #type, sizeof(type)))
|
||||
#define FF_EET_EINA_FILE_DATA_DESCRIPTOR_CLASS_SET(clas, type) \
|
||||
(ffeet_eina_file_data_descriptor_class_set(clas, sizeof(*(clas)), #type, sizeof(type)))
|
||||
#define FF_EET_DATA_DESCRIPTOR_ADD_BASIC(edd, struct_type, member, type) \
|
||||
do { \
|
||||
struct_type ___ett; \
|
||||
|
||||
+16
-17
@@ -1,4 +1,3 @@
|
||||
#include "common/endian.h"
|
||||
#include "common/smbios.h"
|
||||
#include "common/io.h"
|
||||
#include "common/mallocHelper.h"
|
||||
@@ -82,9 +81,9 @@ static bool parseSmbiosTable(const uint8_t* data, uint32_t length) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (FF_READ_LE(header->Handle) >= 0xFF00) {
|
||||
if (header->Handle >= 0xFF00) {
|
||||
FF_DEBUG("Invalid SMBIOS structure handle 0x%04x at offset 0x%lx",
|
||||
FF_READ_LE(header->Handle),
|
||||
header->Handle,
|
||||
(unsigned long) ((const uint8_t*) header - data));
|
||||
break;
|
||||
}
|
||||
@@ -104,25 +103,25 @@ static bool parseSmbiosTable(const uint8_t* data, uint32_t length) {
|
||||
smbiosTable[header->Type] = header;
|
||||
FF_DEBUG("Found SMBIOS structure type %u, handle 0x%04X, length %u",
|
||||
header->Type,
|
||||
FF_READ_LE(header->Handle),
|
||||
header->Handle,
|
||||
header->Length);
|
||||
structureCount++;
|
||||
} else {
|
||||
FF_DEBUG("Duplicate SMBIOS structure type %u, handle 0x%04X, length %u",
|
||||
header->Type,
|
||||
FF_READ_LE(header->Handle),
|
||||
header->Handle,
|
||||
header->Length);
|
||||
}
|
||||
} else if (header->Type == FF_SMBIOS_TYPE_END_OF_TABLE) {
|
||||
FF_DEBUG("Reached SMBIOS end of type %u, handle 0x%04X, length %u",
|
||||
header->Type,
|
||||
FF_READ_LE(header->Handle),
|
||||
header->Handle,
|
||||
header->Length);
|
||||
break;
|
||||
} else {
|
||||
FF_DEBUG("Found custom SMBIOS structure type %u, handle 0x%04X, length %u; ignoring",
|
||||
header->Type,
|
||||
FF_READ_LE(header->Handle),
|
||||
header->Handle,
|
||||
header->Length);
|
||||
}
|
||||
}
|
||||
@@ -309,8 +308,8 @@ static bool fillTableBufferFallback(FFstrbuf* buffer) {
|
||||
sizeof(p->Smbios30));
|
||||
return false;
|
||||
}
|
||||
tableLength = FF_READ_LE(p->Smbios30.StructureTableMaximumSize);
|
||||
tableAddress = (off_t) FF_READ_LE(p->Smbios30.StructureTableAddress);
|
||||
tableLength = p->Smbios30.StructureTableMaximumSize;
|
||||
tableAddress = (off_t) p->Smbios30.StructureTableAddress;
|
||||
FF_DEBUG("SMBIOS 3.0: tableLength=0x%x, tableAddress=0x%lx, version=%u.%u.%u",
|
||||
tableLength,
|
||||
(unsigned long) tableAddress,
|
||||
@@ -326,8 +325,8 @@ static bool fillTableBufferFallback(FFstrbuf* buffer) {
|
||||
sizeof(p->Smbios20));
|
||||
return false;
|
||||
}
|
||||
tableLength = FF_READ_LE(p->Smbios20.StructureTableLength);
|
||||
tableAddress = (off_t) FF_READ_LE(p->Smbios20.StructureTableAddress);
|
||||
tableLength = p->Smbios20.StructureTableLength;
|
||||
tableAddress = (off_t) p->Smbios20.StructureTableAddress;
|
||||
FF_DEBUG("SMBIOS 2.0: tableLength=0x%x, tableAddress=0x%lx, version=%u.%u",
|
||||
tableLength,
|
||||
(unsigned long) tableAddress,
|
||||
@@ -370,9 +369,9 @@ static bool detectSmbiosTableLength(const uint8_t* data, uint32_t bufferLength,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (FF_READ_LE(header->Handle) >= 0xFF00) {
|
||||
if (header->Handle >= 0xFF00) {
|
||||
FF_DEBUG("Invalid SMBIOS structure handle 0x%04x at offset 0x%lx",
|
||||
FF_READ_LE(header->Handle),
|
||||
header->Handle,
|
||||
(unsigned long) (p - data));
|
||||
return false;
|
||||
}
|
||||
@@ -615,8 +614,8 @@ static bool fillTableBufferPlatform(FFstrbuf* buffer) {
|
||||
sizeof(entryPoint.Smbios20));
|
||||
return false;
|
||||
}
|
||||
tableLength = FF_READ_LE(entryPoint.Smbios20.StructureTableLength);
|
||||
tableAddress = (off_t) FF_READ_LE(entryPoint.Smbios20.StructureTableAddress);
|
||||
tableLength = entryPoint.Smbios20.StructureTableLength;
|
||||
tableAddress = (off_t) entryPoint.Smbios20.StructureTableAddress;
|
||||
FF_DEBUG("SMBIOS 2.0: tableLength=0x%x, tableAddress=0x%lx, version=%u.%u",
|
||||
tableLength,
|
||||
(unsigned long) tableAddress,
|
||||
@@ -630,8 +629,8 @@ static bool fillTableBufferPlatform(FFstrbuf* buffer) {
|
||||
sizeof(entryPoint.Smbios30));
|
||||
return false;
|
||||
}
|
||||
tableLength = FF_READ_LE(entryPoint.Smbios30.StructureTableMaximumSize);
|
||||
tableAddress = (off_t) FF_READ_LE(entryPoint.Smbios30.StructureTableAddress);
|
||||
tableLength = entryPoint.Smbios30.StructureTableMaximumSize;
|
||||
tableAddress = (off_t) entryPoint.Smbios30.StructureTableAddress;
|
||||
FF_DEBUG("SMBIOS 3.0: tableLength=0x%x, tableAddress=0x%lx, version=%u.%u.%u",
|
||||
tableLength,
|
||||
(unsigned long) tableAddress,
|
||||
|
||||
+23
-48
@@ -22,41 +22,21 @@ const char* ffSysctlGetString(int mib1, int mib2, FFstrbuf* result) {
|
||||
}
|
||||
|
||||
int ffSysctlGetInt(int mib1, int mib2, int defaultValue) {
|
||||
alignas(int) uint8_t result[sizeof(int)];
|
||||
int result;
|
||||
size_t neededLength = sizeof(result);
|
||||
if (sysctl((int[]) { mib1, mib2 }, 2, &result, &neededLength, nullptr, 0) != 0) {
|
||||
return defaultValue;
|
||||
}
|
||||
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;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int64_t ffSysctlGetInt64(int mib1, int mib2, int64_t defaultValue) {
|
||||
alignas(int64_t) uint8_t result[sizeof(int64_t)];
|
||||
int64_t result;
|
||||
size_t neededLength = sizeof(result);
|
||||
if (sysctl((int[]) { mib1, mib2 }, 2, &result, &neededLength, nullptr, 0) != 0) {
|
||||
return defaultValue;
|
||||
}
|
||||
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;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
#else
|
||||
const char* ffSysctlGetString(const char* propName, FFstrbuf* result) {
|
||||
@@ -79,40 +59,35 @@ const char* ffSysctlGetString(const char* propName, FFstrbuf* result) {
|
||||
}
|
||||
|
||||
int ffSysctlGetInt(const char* propName, int defaultValue) {
|
||||
alignas(int) uint8_t result[sizeof(int)];
|
||||
int result;
|
||||
size_t neededLength = sizeof(result);
|
||||
if (sysctlbyname(propName, &result, &neededLength, nullptr, 0) != 0) {
|
||||
return defaultValue;
|
||||
}
|
||||
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;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int64_t ffSysctlGetInt64(const char* propName, int64_t defaultValue) {
|
||||
alignas(int64_t) uint8_t result[sizeof(int64_t)];
|
||||
int64_t result;
|
||||
size_t neededLength = sizeof(result);
|
||||
if (sysctlbyname(propName, &result, &neededLength, nullptr, 0) != 0) {
|
||||
return defaultValue;
|
||||
}
|
||||
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;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
#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;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#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) {
|
||||
@@ -20,18 +19,3 @@ 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,3 +14,4 @@ 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);
|
||||
|
||||
+3
-79
@@ -11,10 +11,7 @@ enum {
|
||||
SystemFirmwareTableInformation = 76,
|
||||
SystemBootEnvironmentInformation = 90,
|
||||
SystemLogicalProcessorAndGroupInformation = 107,
|
||||
SystemBasicPerformanceInformation = 123,
|
||||
SystemSecureBootInformation = 146,
|
||||
SystemBasicProcessInformation = 252,
|
||||
SystemHandleCountInformation = 253,
|
||||
};
|
||||
|
||||
typedef struct _PROCESSOR_POWER_INFORMATION {
|
||||
@@ -201,27 +198,6 @@ 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;
|
||||
|
||||
typedef struct _SYSTEM_BASICPROCESS_INFORMATION {
|
||||
ULONG NextEntryOffset;
|
||||
HANDLE UniqueProcessId;
|
||||
HANDLE InheritedFromUniqueProcessId;
|
||||
ULONG64 SequenceNumber;
|
||||
UNICODE_STRING ImageName;
|
||||
} SYSTEM_BASICPROCESS_INFORMATION, *PSYSTEM_BASICPROCESS_INFORMATION;
|
||||
|
||||
typedef struct _SYSTEM_HANDLECOUNT_INFORMATION {
|
||||
ULONG ProcessCount;
|
||||
ULONG ThreadCount;
|
||||
ULONG HandleCount;
|
||||
} SYSTEM_HANDLECOUNT_INFORMATION, *PSYSTEM_HANDLECOUNT_INFORMATION;
|
||||
|
||||
NTSYSAPI NTSTATUS NTAPI NtDelayExecution(_In_ BOOLEAN Alertable, _In_ PLARGE_INTEGER DelayInterval);
|
||||
|
||||
/**
|
||||
@@ -623,54 +599,6 @@ 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;
|
||||
|
||||
@@ -707,19 +635,15 @@ static inline uint64_t ffKSystemTimeToUInt64(const volatile KSYSTEM_TIME* pTime)
|
||||
|
||||
static inline bool ffIsWindows10OrGreater() {
|
||||
#if FF_WIN81_COMPAT
|
||||
return SharedUserData->NtBuildNumber >= 10240;
|
||||
return SharedUserData->NtMajorVersion >= 10;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline bool ffIsWindows11OrGreater() {
|
||||
return SharedUserData->NtBuildNumber >= 22000;
|
||||
}
|
||||
|
||||
static inline bool ffIsSystemBasicProcessInfoAvailable() { // Includes HandleCountInformation, which was added together
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntquerysysteminformation#systembasicprocessinformation
|
||||
return SharedUserData->NtBuildNumber >= 26200; // MSDN says it was added in 26100.4770; ntdoc says 25H2
|
||||
return SharedUserData->NtMajorVersion > 10 ||
|
||||
(SharedUserData->NtMajorVersion == 10 && SharedUserData->NtBuildNumber >= 22000);
|
||||
}
|
||||
|
||||
NTSYSAPI NTSTATUS NTAPI NtOpenProcessToken(
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#define INITGUID
|
||||
|
||||
#include "battery.h"
|
||||
#include "common/endian.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/mallocHelper.h"
|
||||
@@ -128,24 +127,29 @@ static void detectStaticData(FFlist* entries, FFlist* results) {
|
||||
FFBatteryWmiEntry* entry = getBatteryEntry(entries, results, data->Tag);
|
||||
|
||||
FF_DEBUG("chemistry: %.4s", (const char*) &data->Chemistry);
|
||||
#if __BIG_ENDIAN__
|
||||
#define htobe32(x) (x) // Should not happen on Windows, but just in case
|
||||
#else
|
||||
#define htobe32(x) __builtin_bswap32(x)
|
||||
#endif
|
||||
switch (data->Chemistry) {
|
||||
case FF_READ_BE((uint32_t) 'PbAc'):
|
||||
case htobe32('PbAc'):
|
||||
ffStrbufSetStatic(&entry->result->technology, "Lead Acid");
|
||||
break;
|
||||
case FF_READ_BE((uint32_t) 'LION'):
|
||||
case FF_READ_BE((uint32_t) 'Li-I'):
|
||||
case htobe32('LION'):
|
||||
case htobe32('Li-I'):
|
||||
ffStrbufSetStatic(&entry->result->technology, "Lithium Ion");
|
||||
break;
|
||||
case FF_READ_BE((uint32_t) 'NiCd'):
|
||||
case htobe32('NiCd'):
|
||||
ffStrbufSetStatic(&entry->result->technology, "Nickel Cadmium");
|
||||
break;
|
||||
case FF_READ_BE((uint32_t) 'NiMH'):
|
||||
case htobe32('NiMH'):
|
||||
ffStrbufSetStatic(&entry->result->technology, "Nickel Metal Hydride");
|
||||
break;
|
||||
case FF_READ_BE((uint32_t) 'NiZn'):
|
||||
case htobe32('NiZn'):
|
||||
ffStrbufSetStatic(&entry->result->technology, "Nickel Zinc");
|
||||
break;
|
||||
case FF_READ_BE((uint32_t) 'RAM\0'):
|
||||
case htobe32('RAM\0'):
|
||||
ffStrbufSetStatic(&entry->result->technology, "Rechargeable Alkaline-Manganese");
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "chassis.h"
|
||||
#include "common/endian.h"
|
||||
#include "common/io.h"
|
||||
#include "common/smbios.h"
|
||||
|
||||
@@ -22,7 +21,7 @@ const char* ffDetectChassis(FFChassisResult* result) {
|
||||
uint32_t chassisType = 0;
|
||||
if (ffReadFileData("/sys/firmware/devicetree/base/smbios/smbios/chassis/chassis-type", sizeof(chassisType), &chassisType)) // big endian
|
||||
{
|
||||
chassisType = FF_READ_BE(chassisType);
|
||||
chassisType = __builtin_bswap32(chassisType);
|
||||
const char* typeStr = ffChassisTypeToString(chassisType);
|
||||
if (typeStr) {
|
||||
ffStrbufSetStatic(&result->type, typeStr);
|
||||
|
||||
@@ -62,10 +62,6 @@ const char* ffCPUAppleCodeToName(uint32_t code) {
|
||||
return "Apple M4 Pro";
|
||||
case 6041:
|
||||
return "Apple M4 Max";
|
||||
case 8142:
|
||||
return "Apple M5";
|
||||
case 6050:
|
||||
return "Apple M5 Pro / Max";
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -214,15 +214,9 @@ static void detectQualcomm(FFCPUResult* cpu) {
|
||||
const char* name = nullptr;
|
||||
|
||||
switch (code) {
|
||||
case 8975:
|
||||
name = "8 Elite Extreme Gen 6";
|
||||
break;
|
||||
case 8950:
|
||||
name = "8 Elite Gen 6";
|
||||
break;
|
||||
case 8845:
|
||||
name = "8 Gen 5";
|
||||
break;
|
||||
break; // ?
|
||||
case 8850:
|
||||
name = "8 Elite Gen 5";
|
||||
break;
|
||||
@@ -244,9 +238,6 @@ static void detectQualcomm(FFCPUResult* cpu) {
|
||||
case 8475:
|
||||
name = "8+ Gen 1";
|
||||
break;
|
||||
case 8425:
|
||||
name = "8+ Gen 1 4G";
|
||||
break;
|
||||
case 8450:
|
||||
name = "8 Gen 1";
|
||||
break;
|
||||
@@ -280,18 +271,12 @@ static void detectQualcomm(FFCPUResult* cpu) {
|
||||
case 6475:
|
||||
name = "6 Gen 3";
|
||||
break;
|
||||
case 6225:
|
||||
name = "6s 4G Gen 2";
|
||||
break;
|
||||
case 6115:
|
||||
name = "6s 4G Gen 1";
|
||||
name = "6s Gen 1";
|
||||
break;
|
||||
case 6450:
|
||||
name = "6 Gen 1";
|
||||
break;
|
||||
case 4850:
|
||||
name = "4 Gen 5";
|
||||
break;
|
||||
case 4635:
|
||||
name = "4s Gen 2";
|
||||
break;
|
||||
@@ -320,9 +305,6 @@ static void detectMediaTek(FFCPUResult* cpu) {
|
||||
|
||||
switch (code) // The SOC code of MTK Dimensity series is full of mess
|
||||
{
|
||||
case 6995:
|
||||
name = "9600";
|
||||
break;
|
||||
case 6993:
|
||||
name = "9500";
|
||||
break;
|
||||
@@ -375,9 +357,6 @@ static void detectExynos(FFCPUResult* cpu) {
|
||||
const char* name = nullptr;
|
||||
|
||||
switch (code) {
|
||||
case 9975:
|
||||
name = "2700";
|
||||
break;
|
||||
case 9965:
|
||||
name = "2600";
|
||||
break;
|
||||
@@ -395,9 +374,6 @@ static void detectExynos(FFCPUResult* cpu) {
|
||||
name = "2100";
|
||||
break;
|
||||
|
||||
case 8865:
|
||||
name = "1680";
|
||||
break;
|
||||
case 8855:
|
||||
name = "1580";
|
||||
break;
|
||||
@@ -619,9 +595,6 @@ static const char* parseCpuInfo(
|
||||
(cpu->name.length == 0 && ffParsePropLine(line, "cpu :", &cpu->name)) ||
|
||||
#elif __sh__
|
||||
(cpu->name.length == 0 && ffParsePropLine(line, "cpu type :", &cpu->name)) ||
|
||||
#elif __sparc__ || __sparc
|
||||
(cpu->name.length == 0 && ffParsePropLine(line, "cpu :", &cpu->name)) ||
|
||||
(cpuMHz->length == 0 && ffParsePropLine(line, "Cpu0ClkTck :", cpuMHz)) ||
|
||||
#else
|
||||
(cpu->name.length == 0 && ffParsePropLine(line, "model name :", &cpu->name)) ||
|
||||
(cpu->name.length == 0 && ffParsePropLine(line, "model :", &cpu->name)) ||
|
||||
@@ -715,7 +688,7 @@ static bool detectFrequency(FFCPUResult* cpu, const FFCPUOptions* options) {
|
||||
while ((p = memmem(p, cpuinfo->length - (uint32_t) (p - cpuinfo->chars), "\nphysical id\t:", strlen("\nphysical id\t:")))) {
|
||||
p += strlen("\nphysical id\t:");
|
||||
char* pend;
|
||||
unsigned long id = strtoul(p, &pend, 10);
|
||||
unsigned long long id = strtoul(p, &pend, 10);
|
||||
if (__builtin_expect(id > 64, false)) { // Do 129-socket boards exist?
|
||||
high |= 1ULL << (id - 64);
|
||||
} else {
|
||||
@@ -840,46 +813,6 @@ static const char* detectPhysicalCores(FFCPUResult* cpu) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
[[maybe_unused]] static bool detectSnapdragonX(FFstrbuf* name, const char* model) {
|
||||
// SoC models are enumerated in the form of `x<gen>[e|p]<part><rev>`, e.g. `x1e80100`,
|
||||
// which is marketed as `X1E-80-100`, where `e` stands for Elite and `p` for Plus.
|
||||
// https://en.wikipedia.org/wiki/List_of_Qualcomm_Snapdragon_systems_on_chips#Snapdragon_X_series
|
||||
const char* prefix;
|
||||
uint32_t prefixLen;
|
||||
|
||||
if (ffStrStartsWith(model, "x1e")) {
|
||||
prefix = "Qualcomm Snapdragon X Elite";
|
||||
prefixLen = 3;
|
||||
} else if (ffStrStartsWith(model, "x1p")) {
|
||||
prefix = "Qualcomm Snapdragon X Plus";
|
||||
prefixLen = 3;
|
||||
} else if (ffStrStartsWith(model, "x2e")) {
|
||||
// Only X2E-94-100 and X2E-96-100 are branded as Extreme
|
||||
prefix = model[3] == '9' ? "Qualcomm Snapdragon X2 Elite Extreme" : "Qualcomm Snapdragon X2 Elite";
|
||||
prefixLen = 3;
|
||||
} else if (ffStrStartsWith(model, "x2p")) {
|
||||
prefix = "Qualcomm Snapdragon X2 Plus";
|
||||
prefixLen = 3;
|
||||
} else if (ffStrStartsWith(model, "x1")) {
|
||||
prefix = "Qualcomm Snapdragon X";
|
||||
prefixLen = 2;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t length = (uint32_t) strlen(model);
|
||||
bool splitCode = length - prefixLen == 5;
|
||||
|
||||
ffStrbufSetF(name, "%s X", prefix);
|
||||
for (uint32_t i = 1; i < length; ++i) {
|
||||
if (i == prefixLen || (splitCode && i == length - 3)) {
|
||||
ffStrbufAppendC(name, '-');
|
||||
}
|
||||
ffStrbufAppendC(name, (char) toupper(model[i]));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
[[maybe_unused]] static void parseIsa(FFstrbuf* cpuIsa) {
|
||||
// Always use the last part of the ISA string. Ref: #590 #1204
|
||||
ffStrbufSubstrAfterLastC(cpuIsa, ' ');
|
||||
@@ -981,19 +914,22 @@ static const char* detectPhysicalCores(FFCPUResult* cpu) {
|
||||
}
|
||||
#endif
|
||||
else if (ffStrEquals(vendor, "qcom")) {
|
||||
// https://elixir.bootlin.com/linux/latest/source/arch/arm64/boot/dts/qcom
|
||||
if (!detectSnapdragonX(&cpu->name, model)) {
|
||||
if (ffStrStartsWith(model, "sc")) {
|
||||
const char* code = model + 2;
|
||||
uint32_t deviceId = (uint32_t) strtoul(code, nullptr, 10);
|
||||
ffStrbufSetStatic(&cpu->name, ffCPUQualcommCodeToName(deviceId));
|
||||
if (!cpu->name.length) {
|
||||
ffStrbufAppendS(&cpu->name, "Qualcomm Snapdragon SC");
|
||||
ffStrbufAppendS(&cpu->name, code);
|
||||
}
|
||||
} else {
|
||||
ffStrbufSetS(&cpu->name, model);
|
||||
// https://elixir.bootlin.com/linux/v6.11/source/arch/arm64/boot/dts/qcom
|
||||
if (ffStrStartsWith(model, "x")) {
|
||||
ffStrbufSetS(&cpu->name, "Qualcomm Snapdragon X Elite ");
|
||||
for (const char* p = model + 1; *p; ++p) {
|
||||
ffStrbufAppendC(&cpu->name, (char) toupper(*p));
|
||||
}
|
||||
} else if (ffStrStartsWith(model, "sc")) {
|
||||
const char* code = model + 2;
|
||||
uint32_t deviceId = (uint32_t) strtoul(code, nullptr, 10);
|
||||
ffStrbufSetStatic(&cpu->name, ffCPUQualcommCodeToName(deviceId));
|
||||
if (!cpu->name.length) {
|
||||
ffStrbufAppendS(&cpu->name, "Qualcomm Snapdragon SC");
|
||||
ffStrbufAppendS(&cpu->name, code);
|
||||
}
|
||||
} else {
|
||||
ffStrbufSetS(&cpu->name, model);
|
||||
}
|
||||
|
||||
ffStrbufSetStatic(&cpu->vendor, "Qualcomm");
|
||||
@@ -1104,10 +1040,6 @@ static const char* detectPhysicalCores(FFCPUResult* cpu) {
|
||||
if (cpu->name.length) {
|
||||
ffStrbufPrependS(&cpu->name, "Machine ");
|
||||
}
|
||||
#elif __sparc__ || __sparc
|
||||
// Cpu0ClkTck is in Hz, printed as "%016lx" by sparc64 and "%ld" by sparc32. A 32-bit userland can run
|
||||
// on a 64-bit kernel, so take the base from the width of the value rather than from our own bitness.
|
||||
cpu->frequencyBase = (uint32_t) (strtoull(cpuMHz.chars, nullptr, cpuMHz.length == 16 ? 16 : 10) / 1000000);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "cpu.h"
|
||||
#include "common/endian.h"
|
||||
#include "common/windows/registry.h"
|
||||
#include "common/windows/nt.h"
|
||||
#include "common/mallocHelper.h"
|
||||
@@ -206,7 +205,7 @@ static const char* detectMaxSpeedBySmbios(FFCPUResult* cpu) {
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t speed = FF_READ_LE(data->MaxSpeed);
|
||||
uint32_t speed = data->MaxSpeed;
|
||||
// Sometimes SMBIOS reports invalid value. We assume that max speed is small than 2x of base
|
||||
if (speed < cpu->frequencyBase || speed > cpu->frequencyBase * 2) {
|
||||
return "Possible invalid CPU max speed in SMBIOS data. See #800";
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "cpucache.h"
|
||||
#include "common/endian.h"
|
||||
#include "common/smbios.h"
|
||||
#include "common/strutil.h"
|
||||
|
||||
@@ -44,26 +43,24 @@ const char* ffDetectCPUCache(FFCPUCacheResult* result) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint16_t cacheConfiguration = FF_READ_LE(data->CacheConfiguration);
|
||||
uint16_t installedSize = FF_READ_LE(data->InstalledSize);
|
||||
bool enabled = !!(cacheConfiguration & (1 << 7));
|
||||
bool enabled = !!(data->CacheConfiguration & (1 << 7));
|
||||
if (!enabled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t size = installedSize;
|
||||
uint32_t size = data->InstalledSize;
|
||||
if (size == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (installedSize != 0xFFFF) {
|
||||
if (data->InstalledSize != 0xFFFF) {
|
||||
size *= (size >> 15 ? 64 : 1) * 1024u;
|
||||
} else if (data->Header.Length > offsetof(FFSmbiosCacheInfo, InstalledCacheSize2)) {
|
||||
size = FF_READ_LE(data->InstalledCacheSize2);
|
||||
size = data->InstalledCacheSize2;
|
||||
size *= (size >> 31 ? 64 : 1) * 1024u;
|
||||
}
|
||||
|
||||
uint32_t level = (cacheConfiguration & 0b111u) + 1;
|
||||
uint32_t level = (data->CacheConfiguration & 0b111u) + 1;
|
||||
|
||||
FFCPUCacheType type;
|
||||
switch (data->SystemCacheType) {
|
||||
|
||||
@@ -11,12 +11,29 @@
|
||||
#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) {
|
||||
ffParsePropFile(FF_PATH_PKG_BASE "/share/wayland-sessions/plasma.desktop", "X-KDE-PluginInfo-Version =", result);
|
||||
#ifdef _PATH_LOCALBASE
|
||||
ffParsePropFile(_PATH_LOCALBASE "/share/wayland-sessions/plasma.desktop", "X-KDE-PluginInfo-Version =", result);
|
||||
if (result->length == 0) {
|
||||
ffParsePropFile(FF_PATH_PKG_BASE "/share/xsessions/plasmax11.desktop", "X-KDE-PluginInfo-Version =", result);
|
||||
ffParsePropFile(_PATH_LOCALBASE "/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);
|
||||
|
||||
@@ -174,11 +174,6 @@ 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, ' ');
|
||||
@@ -192,23 +187,13 @@ static bool detectDE(FFDisplayServerResult* ds) {
|
||||
}
|
||||
|
||||
void ffConnectDisplayServerImpl(FFDisplayServerResult* ds) {
|
||||
// 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;
|
||||
}
|
||||
const char* error = ffdsConnectXcbRandr(ds);
|
||||
if (error) {
|
||||
error = ffdsConnectXrandr(ds);
|
||||
}
|
||||
if (!error) {
|
||||
ffdsDetectWMDE(ds);
|
||||
return;
|
||||
}
|
||||
|
||||
// https://source.android.com/docs/core/graphics/surfaceflinger-windowmanager
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <libgen.h>
|
||||
|
||||
#if __FreeBSD__
|
||||
#include <sys/sysctl.h>
|
||||
@@ -16,6 +15,7 @@
|
||||
#elif __OpenBSD__
|
||||
#include <sys/param.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <kvm.h>
|
||||
#elif __sun
|
||||
#include <procfs.h>
|
||||
#elif __NetBSD__
|
||||
@@ -310,33 +310,25 @@ static const char* getFromProcesses(FFDisplayServerResult* result) {
|
||||
}
|
||||
}
|
||||
#elif __OpenBSD__
|
||||
int request[] = { CTL_KERN, KERN_PROC, KERN_PROC_UID, (int) userId, (int) sizeof(struct kinfo_proc), 0 };
|
||||
size_t length = 0;
|
||||
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, (int) userId, sizeof(*proc), &count);
|
||||
if (proc) {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
if (result->dePrettyName.length == 0) {
|
||||
applyPrettyNameIfDE(result, proc[i].p_comm);
|
||||
}
|
||||
|
||||
if (sysctl(request, ARRAY_SIZE(request), nullptr, &length, nullptr, 0) != 0) {
|
||||
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_UID}, nullptr) failed";
|
||||
}
|
||||
if (result->wmPrettyName.length == 0) {
|
||||
applyNameIfWM(result, proc[i].p_comm);
|
||||
}
|
||||
|
||||
FF_AUTO_FREE struct kinfo_proc* procs = (struct kinfo_proc*) malloc(length);
|
||||
request[5] = (int) (length / sizeof(struct kinfo_proc)); // count must be non-zero for data fetch
|
||||
if (sysctl(request, ARRAY_SIZE(request), procs, &length, nullptr, 0) != 0) {
|
||||
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_UID}, procs) failed";
|
||||
}
|
||||
|
||||
int count = (int) (length / sizeof(struct kinfo_proc));
|
||||
for (int i = 0; i < count; ++i) {
|
||||
if (result->dePrettyName.length == 0) {
|
||||
applyPrettyNameIfDE(result, procs[i].p_comm);
|
||||
}
|
||||
|
||||
if (result->wmPrettyName.length == 0) {
|
||||
applyNameIfWM(result, procs[i].p_comm);
|
||||
}
|
||||
|
||||
if (result->dePrettyName.length > 0 && result->wmPrettyName.length > 0) {
|
||||
break;
|
||||
if (result->dePrettyName.length > 0 && result->wmPrettyName.length > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
kvm_close(kd);
|
||||
#elif __sun
|
||||
FF_AUTO_CLOSE_DIR DIR* procdir = opendir("/proc");
|
||||
if (procdir == nullptr) {
|
||||
@@ -384,60 +376,48 @@ static const char* getFromProcesses(FFDisplayServerResult* result) {
|
||||
}
|
||||
|
||||
FF_STRBUF_AUTO_DESTROY procPath = ffStrbufCreateA(64);
|
||||
int procfd = dirfd(procdir);
|
||||
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)
|
||||
|
||||
struct dirent* dirent;
|
||||
while ((dirent = readdir(procdir)) != nullptr) {
|
||||
// Match only folders starting with a number (the pid folders)
|
||||
if ((dirent->d_type != DT_DIR && dirent->d_type != DT_UNKNOWN) || !ffCharIsDigit(dirent->d_name[0])) {
|
||||
if (dirent->d_type != DT_DIR || !ffCharIsDigit(dirent->d_name[0])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ffStrbufSetS(&procPath, dirent->d_name);
|
||||
ffStrbufAppendS(&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");
|
||||
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) {
|
||||
ffReadFileBuffer(procPath.chars, &loginuid);
|
||||
if (ffStrbufToUInt(&loginuid, (uint64_t) -1) != userId) {
|
||||
ffStrbufSubstrBefore(&procPath, procPathLength);
|
||||
continue;
|
||||
}
|
||||
|
||||
ffStrbufSubstrBefore(&procPath, procFolderPathLength);
|
||||
|
||||
// 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;
|
||||
}
|
||||
// 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);
|
||||
|
||||
if (result->dePrettyName.length == 0) {
|
||||
applyPrettyNameIfDE(result, processName);
|
||||
applyPrettyNameIfDE(result, processName.chars);
|
||||
}
|
||||
|
||||
if (result->wmPrettyName.length == 0) {
|
||||
applyNameIfWM(result, processName);
|
||||
applyNameIfWM(result, processName.chars);
|
||||
}
|
||||
|
||||
if (result->dePrettyName.length > 0 && result->wmPrettyName.length > 0) {
|
||||
|
||||
@@ -331,7 +331,7 @@ static bool xcbRandrHandleMonitors(XcbRandrData* data, xcb_screen_t* screen) {
|
||||
}
|
||||
|
||||
static void xcbRandrHandleScreen(XcbRandrData* data, xcb_screen_t* screen) {
|
||||
// With all the initialization done, start the detection
|
||||
// With all the initialisation done, start the detection
|
||||
if (xcbRandrHandleMonitors(data, screen)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -426,7 +426,7 @@ static const char* detectPci(const FFGPUOptions* options, FFlist* gpus, FFstrbuf
|
||||
ffStrbufAppendS(deviceDir, "/revision");
|
||||
if (ffReadFileBuffer(deviceDir->chars, buffer)) {
|
||||
char* pend;
|
||||
uint32_t revision = (uint32_t) strtoul(buffer->chars, &pend, 16);
|
||||
uint64_t revision = strtoul(buffer->chars, &pend, 16);
|
||||
if (pend != buffer->chars) {
|
||||
ffGPUQueryAmdGpuName((uint16_t) deviceId, (uint8_t) revision, gpu);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,21 @@
|
||||
#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
|
||||
@@ -35,9 +46,9 @@ static const FFstrbuf* loadPciIds() {
|
||||
}
|
||||
}
|
||||
#elif __OpenBSD__ || __FreeBSD__ || __NetBSD__
|
||||
ffReadFileBuffer(FF_PATH_PKG_BASE "/share/hwdata/pci.ids", &pciids);
|
||||
ffReadFileBuffer(_PATH_LOCALBASE "/share/hwdata/pci.ids", &pciids);
|
||||
if (pciids.length == 0) {
|
||||
ffReadFileBuffer(FF_PATH_PKG_BASE "/share/pciids/pci.ids", &pciids);
|
||||
ffReadFileBuffer(_PATH_LOCALBASE "/share/pciids/pci.ids", &pciids);
|
||||
}
|
||||
#elif __sun
|
||||
ffReadFileBuffer(FASTFETCH_TARGET_DIR_ROOT "/usr/share/hwdata/pci.ids", &pciids);
|
||||
|
||||
@@ -19,7 +19,7 @@ static int walkDevTree(di_node_t node, [[maybe_unused]] di_minor_t minor, FFlist
|
||||
gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET;
|
||||
gpu->type = FF_GPU_TYPE_UNKNOWN;
|
||||
gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET;
|
||||
gpu->deviceId = (uint64_t) strtoull(di_bus_addr(node), nullptr, 16);
|
||||
gpu->deviceId = strtoul(di_bus_addr(node), nullptr, 16);
|
||||
gpu->frequency = FF_GPU_FREQUENCY_UNSET;
|
||||
gpu->pcieSpeed = FF_GPU_PCIE_SPEED_UNSET;
|
||||
|
||||
|
||||
@@ -111,7 +111,7 @@ static void detectGTKFromSettings(FFGTKResult* result) {
|
||||
} 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 synthesizes the schema default for keys
|
||||
// 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;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "host.h"
|
||||
#include "common/endian.h"
|
||||
#include "common/smbios.h"
|
||||
|
||||
typedef struct [[gnu::packed]] FFSmbiosSystemInfo {
|
||||
@@ -53,14 +52,7 @@ const char* ffDetectHost(FFHostResult* host) {
|
||||
|
||||
static_assert(offsetof(FFSmbiosSystemInfo, UUID) == 0x08, "FFSmbiosSystemInfo.UUID offset is wrong");
|
||||
if (data->Header.Length > offsetof(FFSmbiosSystemInfo, UUID)) {
|
||||
ffStrbufSetF(&host->uuid, "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X",
|
||||
FF_READ_LE(data->UUID.TimeLow),
|
||||
FF_READ_LE(data->UUID.TimeMid),
|
||||
FF_READ_LE(data->UUID.TimeHighAndVersion),
|
||||
data->UUID.ClockSeqHiAndReserved,
|
||||
data->UUID.ClockSeqLow,
|
||||
data->UUID.Node[0], data->UUID.Node[1], data->UUID.Node[2],
|
||||
data->UUID.Node[3], data->UUID.Node[4], data->UUID.Node[5]);
|
||||
ffStrbufSetF(&host->uuid, "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X", data->UUID.TimeLow, data->UUID.TimeMid, data->UUID.TimeHighAndVersion, data->UUID.ClockSeqHiAndReserved, data->UUID.ClockSeqLow, data->UUID.Node[0], data->UUID.Node[1], data->UUID.Node[2], data->UUID.Node[3], data->UUID.Node[4], data->UUID.Node[5]);
|
||||
}
|
||||
|
||||
static_assert(offsetof(FFSmbiosSystemInfo, SKUNumber) == 0x19, "FFSmbiosSystemInfo.SKUNumber offset is wrong");
|
||||
|
||||
@@ -7,65 +7,32 @@
|
||||
#include <winternl.h>
|
||||
#include <wchar.h>
|
||||
|
||||
static bool fillResult(FFInitSystemResult* result, uint32_t ppid, uint32_t pid, uint16_t len, PCWSTR name) {
|
||||
if (ppid != 4 /* System */ || len <= 4 || _wcsnicmp(name + len - 4, L".exe", 4) != 0) { // smss.exe
|
||||
return false;
|
||||
}
|
||||
result->pid = pid;
|
||||
// We have no permission to open the process for querying the full information
|
||||
wchar_t exePath[MAX_PATH];
|
||||
_snwprintf(exePath, ARRAY_SIZE(exePath), L"%ls\\system32\\%.*ls", (const wchar_t*) SharedUserData->NtSystemRoot, len, name);
|
||||
ffGetFileVersion(exePath, NULL, &result->version);
|
||||
ffStrbufSetWS(&result->exe, exePath);
|
||||
ffStrbufSetNWS(&result->name, len - 4, name);
|
||||
return true;
|
||||
}
|
||||
|
||||
const char* ffDetectInitSystem(FFInitSystemResult* result) {
|
||||
if (ffIsSystemBasicProcessInfoAvailable()) {
|
||||
// SYSTEM_BASICPROCESS_INFORMATION entries are much smaller than SYSTEM_PROCESS_INFORMATION ones,
|
||||
// so a modest buffer should be enough to contain all processes
|
||||
SYSTEM_BASICPROCESS_INFORMATION buffer[1024];
|
||||
NTSTATUS status = NtQuerySystemInformation(SystemBasicProcessInformation, buffer, sizeof(buffer), NULL);
|
||||
if (status != STATUS_INFO_LENGTH_MISMATCH && !NT_SUCCESS(status)) {
|
||||
goto fallback;
|
||||
}
|
||||
for (auto ptr = buffer; ;ptr = (PSYSTEM_BASICPROCESS_INFORMATION) ((uint8_t*) ptr + ptr->NextEntryOffset)) {
|
||||
assert(ptr >= buffer && (uint8_t*) ptr < (uint8_t*) buffer + sizeof(buffer));
|
||||
if (fillResult(result, (uint32_t)(uintptr_t) ptr->InheritedFromUniqueProcessId,
|
||||
(uint32_t)(uintptr_t) ptr->UniqueProcessId,
|
||||
ptr->ImageName.Length / sizeof(*ptr->ImageName.Buffer),
|
||||
ptr->ImageName.Buffer)) {
|
||||
return nullptr;
|
||||
}
|
||||
// The last process in the list always has a NextEntryOffset of 0, even if the buffer was truncated.
|
||||
if (!ptr->NextEntryOffset) {
|
||||
return "Could not find init system process";
|
||||
}
|
||||
}
|
||||
// We only need to find the first user process, so 1024 entries should be enough
|
||||
SYSTEM_PROCESS_INFORMATION buffer[1024] = {};
|
||||
ULONG size = sizeof(buffer);
|
||||
NTSTATUS status = NtQuerySystemInformation(SystemProcessInformation, buffer, size, &size);
|
||||
if (status != STATUS_INFO_LENGTH_MISMATCH && !NT_SUCCESS(status)) {
|
||||
return "NtQuerySystemInformation(SystemProcessInformation) failed";
|
||||
}
|
||||
|
||||
fallback:
|
||||
{
|
||||
// We only need to find the first user process, so 1024 entries should be enough
|
||||
SYSTEM_PROCESS_INFORMATION buffer[1024] = {};
|
||||
ULONG size = sizeof(buffer);
|
||||
NTSTATUS status = NtQuerySystemInformation(SystemProcessInformation, buffer, size, &size);
|
||||
if (status != STATUS_INFO_LENGTH_MISMATCH && !NT_SUCCESS(status)) {
|
||||
return "NtQuerySystemInformation(SystemProcessInformation) failed";
|
||||
for (SYSTEM_PROCESS_INFORMATION* ptr = buffer; ; ptr = (SYSTEM_PROCESS_INFORMATION*) ((uint8_t*) ptr + ptr->NextEntryOffset)) {
|
||||
assert(ptr >= buffer && (uint8_t*) ptr < (uint8_t*) buffer + sizeof(buffer));
|
||||
uint16_t len = ptr->ImageName.Length / sizeof(*ptr->ImageName.Buffer);
|
||||
if (ptr->InheritedFromUniqueProcessId == (HANDLE)(uintptr_t) 4 /* System */ &&
|
||||
len > 4 && _wcsnicmp(ptr->ImageName.Buffer + len - 4, L".exe", 4) == 0) { // smss.exe
|
||||
result->pid = (uint32_t)(uintptr_t) ptr->UniqueProcessId;
|
||||
// We have no permission to open the process for querying the full information
|
||||
wchar_t exePath[MAX_PATH];
|
||||
_snwprintf(exePath, ARRAY_SIZE(exePath), L"%ls\\system32\\%.*ls", (const wchar_t*) SharedUserData->NtSystemRoot, len, ptr->ImageName.Buffer);
|
||||
ffGetFileVersion(exePath, NULL, &result->version);
|
||||
ffStrbufSetWS(&result->exe, exePath);
|
||||
ffStrbufSetNWS(&result->name, len - 4, ptr->ImageName.Buffer);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (auto ptr = buffer; ; ptr = (SYSTEM_PROCESS_INFORMATION*) ((uint8_t*) ptr + ptr->NextEntryOffset)) {
|
||||
assert(ptr >= buffer && (uint8_t*) ptr < (uint8_t*) buffer + sizeof(buffer));
|
||||
uint16_t len = ptr->ImageName.Length / sizeof(*ptr->ImageName.Buffer);
|
||||
if (fillResult(result, (uint32_t)(uintptr_t) ptr->InheritedFromUniqueProcessId,
|
||||
(uint32_t)(uintptr_t) ptr->UniqueProcessId, len, ptr->ImageName.Buffer)) {
|
||||
return nullptr;
|
||||
}
|
||||
// The last process in the list always has a NextEntryOffset of 0, even if the buffer was truncated.
|
||||
if (!ptr->NextEntryOffset) {
|
||||
return "Could not find init system process";
|
||||
}
|
||||
// The last process in the list always has a NextEntryOffset of 0, even if the buffer was truncated.
|
||||
if (!ptr->NextEntryOffset) {
|
||||
return "Could not find init system process";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
typedef struct FFLMResult {
|
||||
FFstrbuf service;
|
||||
FFstrbuf prettyName;
|
||||
FFstrbuf type;
|
||||
FFstrbuf version;
|
||||
} FFLMResult;
|
||||
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
#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;
|
||||
}
|
||||
+26
-368
@@ -1,34 +1,18 @@
|
||||
#include "lm.h"
|
||||
#include "common/io.h"
|
||||
#include "common/mallocHelper.h"
|
||||
#include "common/properties.h"
|
||||
#include "common/dbus.h"
|
||||
#include "common/processing.h"
|
||||
#include "common/strutil.h"
|
||||
|
||||
#ifdef FF_HAVE_DBUS
|
||||
#include "common/dbus.h"
|
||||
#endif
|
||||
#include "detection/displayserver/displayserver.h"
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#if __FreeBSD__
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/user.h>
|
||||
#elif __OpenBSD__
|
||||
#include <sys/param.h>
|
||||
#include <sys/sysctl.h>
|
||||
#elif __sun
|
||||
#include <procfs.h>
|
||||
#elif __NetBSD__
|
||||
#include <sys/types.h>
|
||||
#include <sys/sysctl.h>
|
||||
#endif
|
||||
#define FF_SYSTEMD_SESSIONS_PATH "/run/systemd/sessions/"
|
||||
#define FF_SYSTEMD_USERS_PATH "/run/systemd/users/"
|
||||
|
||||
static const char* getGdmVersion(FFstrbuf* version) {
|
||||
const char* error = ffProcessAppendStdOut(version, (char* const[]) { "gdm3", "--version", nullptr });
|
||||
const char* error = ffProcessAppendStdOut(version, (char* const[]) { "gdm", "--version", nullptr });
|
||||
if (error || version->length == 0) {
|
||||
error = ffProcessAppendStdOut(version, (char* const[]) { "gdm", "--version", nullptr });
|
||||
error = ffProcessAppendStdOut(version, (char* const[]) { "gdm3", "--version", nullptr });
|
||||
if (error || version->length == 0) {
|
||||
return "Failed to get GDM version";
|
||||
}
|
||||
@@ -53,8 +37,6 @@ static const char* getSshdVersion(FFstrbuf* version) {
|
||||
|
||||
#ifdef FF_HAVE_ZLIB
|
||||
#include "common/library.h"
|
||||
#include "common/path.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <zlib.h>
|
||||
|
||||
@@ -67,7 +49,7 @@ static const char* getSddmVersion(FFstrbuf* version) {
|
||||
FF_LIBRARY_LOAD_SYMBOL_MESSAGE(zlib, gzrewind)
|
||||
FF_LIBRARY_LOAD_SYMBOL_MESSAGE(zlib, gzclose)
|
||||
|
||||
gzFile file = ffgzopen(FF_PATH_PKG_BASE "/share/man/man1/sddm.1.gz", "rb");
|
||||
gzFile file = ffgzopen(FASTFETCH_TARGET_DIR_USR "/share/man/man1/sddm.1.gz", "rb");
|
||||
if (file == Z_NULL) {
|
||||
return "ffgzopen(\"/usr/share/man/man1/sddm.1.gz\", \"rb\") failed";
|
||||
}
|
||||
@@ -130,368 +112,35 @@ static const char* getLightdmVersion(FFstrbuf* version) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#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) {
|
||||
const char* ffDetectLM(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
|
||||
ffStrbufAppendS(&path, FF_SYSTEMD_USERS_PATH);
|
||||
ffStrbufAppendUInt(&path, instance.state.platform.uid);
|
||||
ffStrbufSetF(&path, FF_SYSTEMD_USERS_PATH "%d", 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 (!ffParsePropFile(path.chars, "DISPLAY=", &sessionId)) {
|
||||
if (!ffParsePropFileValues(path.chars, 1, (FFpropquery[]) {
|
||||
{ "DISPLAY=", &sessionId },
|
||||
})) {
|
||||
return "Failed to get $XDG_SESSION_ID";
|
||||
}
|
||||
}
|
||||
|
||||
ffStrbufClear(&path);
|
||||
ffStrbufAppendS(&path, FF_SYSTEMD_SESSIONS_PATH);
|
||||
ffStrbufSetS(&path, FF_SYSTEMD_SESSIONS_PATH);
|
||||
ffStrbufAppend(&path, &sessionId);
|
||||
|
||||
// WARNING: This is private data. Do not parse
|
||||
if (!ffParsePropFile(path.chars, "SERVICE=", &result->service)) {
|
||||
if (!ffParsePropFileValues(path.chars, 2, (FFpropquery[]) {
|
||||
{ "SERVICE=", &result->service },
|
||||
{ "TYPE=", &result->type },
|
||||
})) {
|
||||
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__
|
||||
int request[] = { CTL_KERN, KERN_PROC, KERN_PROC_UID, 0, (int) sizeof(struct kinfo_proc), 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);
|
||||
request[5] = (int) (length / sizeof(struct kinfo_proc)); // count must be non-zero for data fetch
|
||||
if (sysctl(request, ARRAY_SIZE(request), procs, &length, nullptr, 0) != 0) {
|
||||
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_UID}, procs) failed";
|
||||
}
|
||||
|
||||
int count = (int) (length / sizeof(struct kinfo_proc));
|
||||
for (int i = 0; i < count; ++i) {
|
||||
const char* lm = testLms(procs[i].p_comm);
|
||||
if (lm) {
|
||||
ffStrbufSetStatic(&result->service, lm);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#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);
|
||||
@@ -506,5 +155,14 @@ 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;
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
#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,26 +1,19 @@
|
||||
#include "memory.h"
|
||||
|
||||
#include "common/windows/nt.h"
|
||||
#include <windows.h>
|
||||
|
||||
const char* ffDetectMemory(FFMemoryResult* ram) {
|
||||
// Note: GlobalMemoryStatusEx() internally uses SystemMemoryUsageInformation in Win 10
|
||||
|
||||
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
|
||||
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";
|
||||
}
|
||||
#else
|
||||
SharedUserData->NumberOfPhysicalPages;
|
||||
#endif
|
||||
|
||||
uint64_t pageSize = instance.state.platform.sysinfo.pageSize;
|
||||
ram->bytesTotal = phyPages * pageSize;
|
||||
ram->bytesUsed = (phyPages - sbpi.AvailablePages) * pageSize;
|
||||
ram->bytesTotal = statex.ullTotalPhys;
|
||||
ram->bytesUsed = statex.ullTotalPhys - statex.ullAvailPhys;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ typedef struct FFPackagesResult {
|
||||
uint32_t brewCask;
|
||||
uint32_t cards;
|
||||
uint32_t choco;
|
||||
uint32_t crux;
|
||||
uint32_t dpkg;
|
||||
uint32_t emerge;
|
||||
uint32_t eopkg;
|
||||
|
||||
@@ -487,9 +487,6 @@ static void getPackageCounts(FFstrbuf* baseDir, FFPackagesResult* packageCounts,
|
||||
if (FF_PACKAGES_IS_ENABLED(options, APK)) {
|
||||
packageCounts->apk += getNumStrings(baseDir, "/lib/apk/db/installed", "C:Q", "apk");
|
||||
}
|
||||
if (FF_PACKAGES_IS_ENABLED(options, CRUX)) {
|
||||
packageCounts->crux += getNumStrings(baseDir, "/var/lib/pkg/db", "\n\n", "crux");
|
||||
}
|
||||
if (FF_PACKAGES_IS_ENABLED(options, DPKG)) {
|
||||
packageCounts->dpkg += getNumStrings(baseDir, "/var/lib/dpkg/status", "Status: install ok installed", "dpkg");
|
||||
}
|
||||
|
||||
@@ -122,7 +122,6 @@ 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
|
||||
|
||||
@@ -34,7 +34,7 @@ static void parsePhysicalDisk(int dfd, const char* devName, FFPhysicalDiskOption
|
||||
ssize_t fileSize = ffReadFileDataRelative(dfd, "size", ARRAY_SIZE(blkSize) - 1, blkSize);
|
||||
if (fileSize > 0) {
|
||||
blkSize[fileSize] = 0;
|
||||
size = (uint64_t) strtoull(blkSize, nullptr, 10) * 512;
|
||||
size = (uint64_t) strtoul(blkSize, nullptr, 10) * 512;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ static void appendDevice(
|
||||
if (size)
|
||||
{
|
||||
char* unit = nullptr;
|
||||
device->size = (uint64_t) strtoull(size.UTF8String, &unit, 10);
|
||||
device->size = strtoul(size.UTF8String, &unit, 10);
|
||||
if (*unit == ' ') ++unit;
|
||||
|
||||
switch (*unit)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "physicalmemory.h"
|
||||
#include "common/endian.h"
|
||||
#include "common/smbios.h"
|
||||
|
||||
// 7.18
|
||||
@@ -83,8 +82,7 @@ const char* ffDetectPhysicalMemory(FFPhysicalMemoryOptions* options, FFlist* res
|
||||
}
|
||||
|
||||
const char* strings = (const char*) data + data->Header.Length;
|
||||
uint16_t size = FF_READ_LE(data->Size);
|
||||
bool installed = size != 0;
|
||||
bool installed = data->Size != 0;
|
||||
|
||||
if (!installed && !options->showEmptySlots) {
|
||||
continue;
|
||||
@@ -103,21 +101,19 @@ const char* ffDetectPhysicalMemory(FFPhysicalMemoryOptions* options, FFlist* res
|
||||
device->installed = installed;
|
||||
device->ecc = false;
|
||||
|
||||
uint16_t totalWidth = FF_READ_LE(data->TotalWidth);
|
||||
uint16_t dataWidth = FF_READ_LE(data->DataWidth);
|
||||
if (installed && totalWidth != 0xFFFF && dataWidth != 0xFFFF) {
|
||||
device->ecc = totalWidth > dataWidth;
|
||||
if (installed && data->TotalWidth != 0xFFFF && data->DataWidth != 0xFFFF) {
|
||||
device->ecc = data->TotalWidth > data->DataWidth;
|
||||
}
|
||||
|
||||
if (installed && size != 0xFFFF) {
|
||||
if (size == 0x7FFF) {
|
||||
device->size = (FF_READ_LE(data->ExtendedSize) & ~(1ULL << 31)) * 1024ULL * 1024ULL;
|
||||
} else if (size & (1 << 15)) {
|
||||
if (installed && data->Size != 0xFFFF) {
|
||||
if (data->Size == 0x7FFF) {
|
||||
device->size = (data->ExtendedSize & ~(1ULL << 31)) * 1024ULL * 1024ULL;
|
||||
} else if (data->Size & (1 << 15)) {
|
||||
// in kB
|
||||
device->size = (size & ~(1ULL << 15)) * 1024ULL;
|
||||
device->size = (data->Size & ~(1ULL << 15)) * 1024ULL;
|
||||
} else {
|
||||
// in MB
|
||||
device->size = size * 1024ULL * 1024ULL;
|
||||
device->size = data->Size * 1024ULL * 1024ULL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,9 +206,8 @@ const char* ffDetectPhysicalMemory(FFPhysicalMemoryOptions* options, FFlist* res
|
||||
|
||||
if (installed && data->Header.Length > offsetof(FFSmbiosMemoryDevice, Speed)) // 2.3+
|
||||
{
|
||||
uint16_t speed = FF_READ_LE(data->Speed);
|
||||
if (speed) {
|
||||
device->maxSpeed = speed == 0xFFFF ? FF_READ_LE(data->ExtendedSpeed) : speed;
|
||||
if (data->Speed) {
|
||||
device->maxSpeed = data->Speed == 0xFFFF ? data->ExtendedSpeed : data->Speed;
|
||||
}
|
||||
|
||||
ffStrbufSetStatic(&device->vendor, ffSmbiosLocateString(strings, data->Manufacturer));
|
||||
@@ -228,11 +223,10 @@ const char* ffDetectPhysicalMemory(FFPhysicalMemoryOptions* options, FFlist* res
|
||||
|
||||
if (installed && data->Header.Length > offsetof(FFSmbiosMemoryDevice, ConfiguredMemorySpeed)) // 2.7+
|
||||
{
|
||||
uint16_t configuredSpeed = FF_READ_LE(data->ConfiguredMemorySpeed);
|
||||
if (configuredSpeed) {
|
||||
device->runningSpeed = configuredSpeed == 0xFFFF
|
||||
? FF_READ_LE(data->ExtendedConfiguredSpeed)
|
||||
: configuredSpeed;
|
||||
if (data->ConfiguredMemorySpeed) {
|
||||
device->runningSpeed = data->ConfiguredMemorySpeed == 0xFFFF
|
||||
? data->ExtendedConfiguredSpeed
|
||||
: data->ConfiguredMemorySpeed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "fastfetch.h"
|
||||
#include "modules/processes/option.h"
|
||||
|
||||
typedef struct FFProcessesResult
|
||||
{
|
||||
uint32_t processes;
|
||||
uint32_t threads;
|
||||
} FFProcessesResult;
|
||||
|
||||
const char* ffDetectProcesses(const FFProcessesOptions* options, FFProcessesResult* result);
|
||||
const char* ffDetectProcesses(uint32_t* result);
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
#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)) {
|
||||
// NOTE: This fails for system processes
|
||||
result->threads += (uint32_t) taskInfo.pti_threadnum;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1,35 +1,23 @@
|
||||
#include "processes.h"
|
||||
#include "common/mallocHelper.h"
|
||||
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/user.h>
|
||||
#ifdef __FreeBSD__
|
||||
#include <sys/types.h>
|
||||
#include <sys/user.h>
|
||||
#endif
|
||||
|
||||
const char* ffDetectProcesses(const FFProcessesOptions* options, FFProcessesResult* result) {
|
||||
#ifndef KERN_PROC_PROC
|
||||
#define KERN_PROC_PROC KERN_PROC_ALL // Apple
|
||||
#endif
|
||||
|
||||
const char* ffDetectProcesses(uint32_t* 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";
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
*result = (uint32_t) (length / sizeof(struct kinfo_proc));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
#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;
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
#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;
|
||||
}
|
||||
|
||||
++result->processes;
|
||||
if (proc_stat_has(stat, PSTAT_NUM_THREADS)) {
|
||||
result->threads += proc_stat_num_threads(stat);
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
if (list) {
|
||||
proc_stat_list_free(list);
|
||||
}
|
||||
ps_context_free(context);
|
||||
return error;
|
||||
}
|
||||
@@ -2,22 +2,13 @@
|
||||
|
||||
#include <OS.h>
|
||||
|
||||
const char* ffDetectProcesses(const FFProcessesOptions* options, FFProcessesResult* result) {
|
||||
const char* ffDetectProcesses(uint32_t* result) {
|
||||
system_info info;
|
||||
if (get_system_info(&info) != B_OK) {
|
||||
return "Error getting system info";
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
*result = info.used_teams;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -1,73 +1,27 @@
|
||||
#include "processes.h"
|
||||
|
||||
#include "common/io.h"
|
||||
#include "common/memrchr.h"
|
||||
#include "common/strutil.h"
|
||||
|
||||
#define PF_KTHREAD 0x00200000 // defined in kernel include/linux/sched.h
|
||||
|
||||
const char* ffDetectProcesses(const FFProcessesOptions* options, FFProcessesResult* result) {
|
||||
const char* ffDetectProcesses(uint32_t* result) {
|
||||
FF_AUTO_CLOSE_DIR DIR* dir = opendir("/proc");
|
||||
if (dir == nullptr) {
|
||||
return "opendir(\"/proc\") failed";
|
||||
}
|
||||
|
||||
int procfd = dirfd(dir);
|
||||
uint32_t num = 0;
|
||||
|
||||
struct dirent* entry;
|
||||
while ((entry = readdir(dir)) != nullptr) {
|
||||
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");
|
||||
|
||||
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:;
|
||||
if (
|
||||
#ifdef _DIRENT_HAVE_D_TYPE
|
||||
(entry->d_type == DT_DIR || entry->d_type == DT_UNKNOWN) &&
|
||||
#endif
|
||||
ffCharIsDigit(entry->d_name[0]))
|
||||
++num;
|
||||
}
|
||||
|
||||
*result = num;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -1,37 +1,15 @@
|
||||
#include "processes.h"
|
||||
#include "common/mallocHelper.h"
|
||||
|
||||
#include <sys/sysctl.h>
|
||||
|
||||
const char* ffDetectProcesses(const FFProcessesOptions* options, FFProcessesResult* result) {
|
||||
int request[] = { CTL_KERN, KERN_PROC2, KERN_PROC_ALL, -1, sizeof(struct kinfo_proc2), INT_MAX };
|
||||
const char* ffDetectProcesses(uint32_t* result) {
|
||||
int request[] = { CTL_KERN, KERN_PROC2, KERN_PROC_ALL, -1, sizeof(struct kinfo_proc2), 0 };
|
||||
size_t length = 0;
|
||||
|
||||
if (sysctl(request, ARRAY_SIZE(request), nullptr, &length, nullptr, 0) != 0) {
|
||||
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));
|
||||
}
|
||||
return "sysctl({CTL_KERN, KERN_PROC2, KERN_PROC_ALL}) failed";
|
||||
}
|
||||
|
||||
*result = (uint32_t) (length / sizeof(struct kinfo_proc2));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "processes.h"
|
||||
|
||||
const char* ffDetectProcesses([[maybe_unused]] const FFProcessesOptions* options, [[maybe_unused]] FFProcessesResult* result) {
|
||||
const char* ffDetectProcesses(uint32_t* result) {
|
||||
return "Not supported on this platform";
|
||||
}
|
||||
|
||||
@@ -1,33 +1,15 @@
|
||||
#include "processes.h"
|
||||
|
||||
#include "common/mallocHelper.h"
|
||||
|
||||
#include <sys/param.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <kvm.h>
|
||||
|
||||
const char* ffDetectProcesses(const FFProcessesOptions* options, FFProcessesResult* result) {
|
||||
int request[] = { CTL_KERN, KERN_PROC, (options->countKprocs ? KERN_PROC_KTHREAD : KERN_PROC_ALL) | KERN_PROC_SHOW_THREADS, 0, (int) sizeof(struct kinfo_proc), 0 };
|
||||
size_t length = 0;
|
||||
|
||||
if (sysctl(request, ARRAY_SIZE(request), nullptr, &length, nullptr, 0) != 0) {
|
||||
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_ALL}, nullptr) failed";
|
||||
const char* ffDetectProcesses(uint32_t* 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) {
|
||||
return "kvm_getprocs() failed";
|
||||
}
|
||||
|
||||
FF_AUTO_FREE struct kinfo_proc* procs = (struct kinfo_proc*) malloc(length);
|
||||
request[5] = (int) (length / sizeof(struct kinfo_proc)); // count must be non-zero for data fetch
|
||||
if (sysctl(request, ARRAY_SIZE(request), procs, &length, nullptr, 0) != 0) {
|
||||
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_ALL}, procs) failed";
|
||||
}
|
||||
|
||||
int count = (int) (length / sizeof(struct kinfo_proc));
|
||||
for (int i = 0; i < count; ++i) {
|
||||
const struct kinfo_proc* proc = &procs[i];
|
||||
|
||||
++result->threads;
|
||||
if (proc->p_tid == -1) {
|
||||
++result->processes;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
#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;
|
||||
}
|
||||
@@ -1,23 +1,10 @@
|
||||
#include "processes.h"
|
||||
#include "common/mallocHelper.h"
|
||||
#include "common/windows/nt.h"
|
||||
|
||||
#include <ntstatus.h>
|
||||
#include <winternl.h>
|
||||
|
||||
const char* ffDetectProcesses(const FFProcessesOptions* options, FFProcessesResult* result) {
|
||||
if (options->countKprocs && ffIsSystemBasicProcessInfoAvailable()) {
|
||||
// SystemHandleCountInformation reports the total process/thread counts directly,
|
||||
// so we don't need to walk the whole process table.
|
||||
SYSTEM_HANDLECOUNT_INFORMATION info = {}; // Seems that kernel only fills the lower 32 bits of the counts, leave the upper 32 bits untouched.
|
||||
if (NT_SUCCESS(NtQuerySystemInformation(SystemHandleCountInformation, &info, sizeof(info), NULL))) {
|
||||
result->processes = info.ProcessCount;
|
||||
result->threads = info.ThreadCount;
|
||||
return nullptr;
|
||||
}
|
||||
// Otherwise fall back to walking the process table
|
||||
}
|
||||
|
||||
const char* ffDetectProcesses(uint32_t* result) {
|
||||
FF_AUTO_FREE SYSTEM_PROCESS_INFORMATION* pstart = nullptr;
|
||||
|
||||
// Multiple attempts in case processes change while
|
||||
@@ -38,14 +25,9 @@ const char* ffDetectProcesses(const FFProcessesOptions* options, FFProcessesResu
|
||||
}
|
||||
}
|
||||
|
||||
for (SYSTEM_PROCESS_INFORMATION* ptr = pstart; ; ptr = (SYSTEM_PROCESS_INFORMATION*) ((uint8_t*) ptr + ptr->NextEntryOffset)) {
|
||||
if (options->countKprocs || (uintptr_t) ptr->InheritedFromUniqueProcessId > 0) {
|
||||
++result->processes;
|
||||
result->threads += ptr->NumberOfThreads;
|
||||
}
|
||||
if (ptr->NextEntryOffset == 0) {
|
||||
break;
|
||||
}
|
||||
*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;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
#include "sound.h"
|
||||
|
||||
const char* ffDetectSound([[maybe_unused]] FFlist* devices /* List of FFSoundDevice */) {
|
||||
return "Not supported on this platform";
|
||||
}
|
||||
@@ -19,11 +19,11 @@ static const char* detectByProcMeminfo(FFlist* result) {
|
||||
|
||||
char* token = nullptr;
|
||||
if ((token = strstr(buf, "SwapTotal:")) != nullptr) {
|
||||
swapTotal = (uint64_t) strtoull(token + strlen("SwapTotal:"), nullptr, 10);
|
||||
swapTotal = strtoul(token + strlen("SwapTotal:"), nullptr, 10);
|
||||
}
|
||||
|
||||
if ((token = strstr(buf, "SwapFree:")) != nullptr) {
|
||||
swapFree = (uint64_t) strtoull(token + strlen("SwapFree:"), nullptr, 10);
|
||||
swapFree = strtoul(token + strlen("SwapFree:"), nullptr, 10);
|
||||
}
|
||||
|
||||
FFSwapResult* swap = FF_LIST_ADD(FFSwapResult, *result);
|
||||
|
||||
@@ -16,8 +16,7 @@
|
||||
const char* detectTermux(FFTerminalFontResult* terminalFont) {
|
||||
FF_STRBUF_AUTO_DESTROY fontSize = ffStrbufCreate();
|
||||
// SharedPreferences XML: <string name="fontsize">14</string>, in px
|
||||
ffParsePropFile(FF_TERMUX_PREF_PATH, "<string name=\"fontsize\">", &fontSize);
|
||||
if (fontSize.length > 0) {
|
||||
if (ffParsePropFile(FF_TERMUX_PREF_PATH, "<string name=\"fontsize\">", &fontSize)) {
|
||||
ffStrbufAppendS(&fontSize, "px");
|
||||
}
|
||||
|
||||
|
||||
@@ -8,8 +8,17 @@
|
||||
|
||||
#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>
|
||||
@@ -185,9 +194,6 @@ 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 })
|
||||
@@ -654,7 +660,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(FF_PATH_PKG_BASE "/share/kitty/kitty/constants.py", ARRAY_SIZE(buffer) - 1, buffer)
|
||||
ffReadFileData(_PATH_LOCALBASE "/share/kitty/kitty/constants.py", ARRAY_SIZE(buffer) - 1, buffer)
|
||||
#endif
|
||||
) {
|
||||
// Starts from version 0.17.0
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
#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);
|
||||
}
|
||||
|
||||
static int compareStartTimeResults(const FFTopProcessResult* a, const FFTopProcessResult* b) {
|
||||
if (a->startTime < b->startTime) return 1;
|
||||
if (a->startTime > b->startTime) return -1;
|
||||
return (int) (a->pid - b->pid);
|
||||
}
|
||||
|
||||
static int compareThreadsResults(const FFTopProcessResult* a, const FFTopProcessResult* b) {
|
||||
if (a->threads < b->threads) return 1;
|
||||
if (a->threads > b->threads) 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 and thread count are 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;
|
||||
item->threads = snap->threads;
|
||||
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;
|
||||
item->threads = newItem->threads;
|
||||
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
|
||||
: options->sort == FF_TOP_TYPE_START_TIME ? (void*) compareStartTimeResults
|
||||
: options->sort == FF_TOP_TYPE_THREADS ? (void*) compareThreadsResults
|
||||
: (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;
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
#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 threads;
|
||||
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 threads;
|
||||
uint32_t pid;
|
||||
} FFTopProcessSnapshot;
|
||||
|
||||
const char* ffDetectTopProcesses(FFTopOptions* options, FFlist* result);
|
||||
const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes showTypes);
|
||||
@@ -1,54 +0,0 @@
|
||||
#include "top.h"
|
||||
#include "common/mallocHelper.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <libproc.h>
|
||||
|
||||
const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes showTypes) {
|
||||
int npids = proc_listallpids(nullptr, 0);
|
||||
if (npids <= 0) {
|
||||
return "proc_listallpids(nullptr, 0) failed";
|
||||
}
|
||||
FF_AUTO_FREE pid_t* pids = malloc((uint32_t) (npids + npids / 8 + 1) * sizeof(pid_t));
|
||||
npids = proc_listallpids(pids, npids);
|
||||
if (npids <= 0) {
|
||||
return "proc_listallpids(pids, bufferSize) failed";
|
||||
}
|
||||
|
||||
uint32_t count = (uint32_t) npids;
|
||||
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
pid_t pid = pids[i];
|
||||
|
||||
struct proc_taskallinfo proc;
|
||||
if (proc_pidinfo(pid, PROC_PIDTASKALLINFO, 0, &proc, sizeof(proc)) != sizeof(proc)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (proc.pbsd.pbi_flags & PROC_FLAG_SYSTEM) {
|
||||
continue;
|
||||
}
|
||||
|
||||
FFTopProcessSnapshot* item = FF_LIST_ADD(FFTopProcessSnapshot, *snapshots);
|
||||
ffStrbufInitS(&item->name, proc.pbsd.pbi_name);
|
||||
if (item->name.length == 0) {
|
||||
ffStrbufInitS(&item->name, proc.pbsd.pbi_comm);
|
||||
}
|
||||
item->pid = (uint32_t) pid;
|
||||
item->cpuTime = (proc.ptinfo.pti_total_user + proc.ptinfo.pti_total_system) / 1000000u; // ns -> ms
|
||||
item->memBytes = proc.ptinfo.pti_resident_size;
|
||||
item->startTime = proc.pbsd.pbi_start_tvsec * 1000u + proc.pbsd.pbi_start_tvusec / 1000u; // convert to ms
|
||||
item->threads = (uint32_t) proc.ptinfo.pti_threadnum;
|
||||
|
||||
if (showTypes & FF_TOP_TYPE_DISK) {
|
||||
struct rusage_info_v2 rusage;
|
||||
if (proc_pid_rusage(pid, RUSAGE_INFO_V2, (rusage_info_t*) &rusage) == 0) {
|
||||
item->bytesRead = rusage.ri_diskio_bytesread;
|
||||
item->bytesWritten = rusage.ri_diskio_byteswritten;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
#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 showTypes) {
|
||||
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;
|
||||
item->threads = (uint32_t) proc->ki_numthreads;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
#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 showTypes) {
|
||||
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;
|
||||
item->threads = (uint32_t) proc->kp_nthreads;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
#include "top.h"
|
||||
|
||||
#include <hurd.h>
|
||||
#include <string.h>
|
||||
#include <ps.h>
|
||||
|
||||
const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes showTypes) {
|
||||
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 | ((showTypes & FF_TOP_TYPE_THREADS) ? PSTAT_NUM_THREADS : 0)) != 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;
|
||||
item->cpuTime = 0;
|
||||
item->memBytes = 0;
|
||||
item->startTime = 0;
|
||||
item->threads = 0;
|
||||
|
||||
if (showTypes & FF_TOP_TYPE_THREADS) {
|
||||
if (proc_stat_has(stat, PSTAT_NUM_THREADS)) {
|
||||
item->threads = (uint32_t) proc_stat_num_threads(stat);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
#include "top.h"
|
||||
|
||||
#include <OS.h>
|
||||
#include <string.h>
|
||||
|
||||
const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes showTypes) {
|
||||
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;
|
||||
item->threads = (uint32_t) team.thread_count;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1,166 +0,0 @@
|
||||
#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;
|
||||
}
|
||||
|
||||
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 20: // number of threads
|
||||
result->threads = (uint32_t) strtoul(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);
|
||||
snapshot->threads = 0;
|
||||
snapshot->cpuTime = 0;
|
||||
snapshot->startTime = 0;
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
#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 showTypes) {
|
||||
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;
|
||||
item->threads = 0;
|
||||
if (showTypes & FF_TOP_TYPE_THREADS) {
|
||||
int lwpRequest[] = { CTL_KERN, KERN_LWP, proc->p_pid, sizeof(struct kinfo_lwp), 0 };
|
||||
size_t lwpLength = 0;
|
||||
if (sysctl(lwpRequest, ARRAY_SIZE(lwpRequest), nullptr, &lwpLength, nullptr, 0) == 0) {
|
||||
item->threads = (uint32_t) (lwpLength / sizeof(struct kinfo_lwp));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
#include "top.h"
|
||||
|
||||
#include "common/mallocHelper.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/param.h> // DEV_BSIZE
|
||||
#include <sys/sysctl.h>
|
||||
|
||||
const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes showTypes) {
|
||||
int request[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0, (int) sizeof(struct kinfo_proc), 0 };
|
||||
size_t length = 0;
|
||||
|
||||
if (sysctl(request, ARRAY_SIZE(request), nullptr, &length, nullptr, 0) != 0) {
|
||||
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_ALL}, nullptr) failed";
|
||||
}
|
||||
|
||||
FF_AUTO_FREE struct kinfo_proc* processes = (struct kinfo_proc*) malloc(length);
|
||||
request[5] = (int) (length / sizeof(struct kinfo_proc)); // count must be non-zero for data fetch
|
||||
if (sysctl(request, ARRAY_SIZE(request), processes, &length, nullptr, 0) != 0) {
|
||||
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_ALL}, processes) failed";
|
||||
}
|
||||
|
||||
int count = (int) (length / sizeof(struct kinfo_proc));
|
||||
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;
|
||||
item->threads = 0;
|
||||
}
|
||||
|
||||
if (showTypes & FF_TOP_TYPE_THREADS) {
|
||||
int threadRequest[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL | KERN_PROC_SHOW_THREADS, 0, (int) sizeof(struct kinfo_proc), 0 };
|
||||
size_t threadLength = 0;
|
||||
if (sysctl(threadRequest, ARRAY_SIZE(threadRequest), nullptr, &threadLength, nullptr, 0) == 0 && threadLength > 0) {
|
||||
FF_AUTO_FREE struct kinfo_proc* threads = (struct kinfo_proc*) malloc(threadLength);
|
||||
threadRequest[5] = (int) (threadLength / sizeof(struct kinfo_proc)); // count must be non-zero for data fetch
|
||||
if (sysctl(threadRequest, ARRAY_SIZE(threadRequest), threads, &threadLength, nullptr, 0) == 0) {
|
||||
int threadCount = (int) (threadLength / sizeof(struct kinfo_proc));
|
||||
for (uint32_t i = 0; i < snapshots->length; ++i) {
|
||||
FFTopProcessSnapshot* item = FF_LIST_GET(FFTopProcessSnapshot, *snapshots, i);
|
||||
for (int j = 0; j < threadCount; ++j) {
|
||||
if (threads[j].p_pid == (pid_t) item->pid && threads[j].p_tid != -1) {
|
||||
++item->threads;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
#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;
|
||||
item->threads = (uint32_t) psinfo.pr_nlwp;
|
||||
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;
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
#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 && (uintptr_t) info->InheritedFromUniqueProcessId > 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;
|
||||
item->threads = info->NumberOfThreads;
|
||||
ffStrbufInitNWS(&item->name, info->ImageName.Length / sizeof(wchar_t), info->ImageName.Buffer);
|
||||
}
|
||||
if (info->NextEntryOffset == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@@ -5,125 +5,79 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <AppKit/AppKit.h>
|
||||
|
||||
const char* detectFromPlist(FFstrbuf* result) {
|
||||
// For Sonoma and later (macOS 14.0+)
|
||||
// https://github.com/JohnCoates/Aerial/issues/1332
|
||||
NSError* error;
|
||||
NSString* fileName = [NSString stringWithFormat:@"file://%s/Library/Application Support/com.apple.wallpaper/Store/Index.plist", instance.state.platform.homeDir.chars];
|
||||
NSDictionary* dict = [NSDictionary dictionaryWithContentsOfURL:[NSURL URLWithString:fileName]
|
||||
error:&error];
|
||||
|
||||
if (error) {
|
||||
return "Failed to read wallpaper plist file";
|
||||
const char* ffDetectWallpaper(FFstrbuf* result)
|
||||
{
|
||||
{
|
||||
// Reliable for user-picked static images.
|
||||
NSURL* url = [NSWorkspace.sharedWorkspace desktopImageURLForScreen:NSScreen.mainScreen];
|
||||
if (url.fileURL && ![url.path isEqualToString:@"/System/Library/CoreServices/DefaultDesktop.heic"] /* dynamic wallpapers */)
|
||||
{
|
||||
ffStrbufSetS(result, url.path.UTF8String);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
NSArray* choices = [dict valueForKeyPath:@"SystemDefault.Desktop.Content.Choices"];
|
||||
if (choices.count > 0) {
|
||||
NSDictionary* choice = choices[0];
|
||||
|
||||
NSArray* files = choice[@"Files"];
|
||||
if (files.count > 0) {
|
||||
NSString* file = files[0][@"relative"];
|
||||
ffStrbufSetS(result, [NSURL URLWithString:file].path.UTF8String);
|
||||
}
|
||||
|
||||
if (result->length == 0) {
|
||||
NSData* configData = choice[@"Configuration"];
|
||||
if (configData && configData.length > 0) {
|
||||
NSError* plistError = nil;
|
||||
NSDictionary* configPlist = [NSPropertyListSerialization propertyListWithData:configData
|
||||
options:NSPropertyListImmutable
|
||||
format:NULL
|
||||
error:&plistError];
|
||||
|
||||
if (!plistError && [configPlist isKindOfClass:NSDictionary.class]) {
|
||||
NSDictionary* urlDict = configPlist[@"url"];
|
||||
if ([urlDict isKindOfClass:NSDictionary.class]) {
|
||||
NSString* relativeUrlString = urlDict[@"relative"];
|
||||
if ([relativeUrlString isKindOfClass:[NSString class]]) {
|
||||
NSURL* fileUrl = [NSURL URLWithString:relativeUrlString];
|
||||
if (fileUrl.fileURL) {
|
||||
ffStrbufSetS(result, fileUrl.path.UTF8String);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result->length == 0) {
|
||||
NSString* provider = choice[@"Provider"];
|
||||
if ([provider isKindOfClass:NSString.class]) {
|
||||
NSString* builtinPrefix = @"com.apple.wallpaper.choice.";
|
||||
if ([provider hasPrefix:builtinPrefix]) {
|
||||
provider = [provider substringFromIndex:builtinPrefix.length];
|
||||
}
|
||||
|
||||
// macOS internal wallpapers
|
||||
if ([provider isEqualToString:@"aerials"]) { // Most builtin aerial wallpapers are private
|
||||
ffStrbufSetStatic(result, "Built-in aerial photography");
|
||||
} else if ([provider isEqualToString:@"default"]) {
|
||||
ffStrbufSetStatic(result, "macOS Default Wallpaper");
|
||||
} else {
|
||||
ffStrbufSetF(result, "Built-in %s wallpaper", provider.UTF8String);
|
||||
{
|
||||
// For Sonoma
|
||||
// https://github.com/JohnCoates/Aerial/issues/1332
|
||||
NSError* error;
|
||||
NSString* fileName = [NSString stringWithFormat:@"file://%s/Library/Application Support/com.apple.wallpaper/Store/Index.plist", instance.state.platform.homeDir.chars];
|
||||
NSDictionary* dict = [NSDictionary dictionaryWithContentsOfURL:[NSURL URLWithString:fileName]
|
||||
error:&error];
|
||||
if (!error)
|
||||
{
|
||||
NSArray* choices = [dict valueForKeyPath:@"SystemDefault.Desktop.Content.Choices"];
|
||||
if (choices.count > 0)
|
||||
{
|
||||
NSDictionary* choice = choices[0];
|
||||
NSArray* files = choice[@"Files"];
|
||||
if (files.count > 0)
|
||||
{
|
||||
NSString* file = files[0][@"relative"];
|
||||
ffStrbufAppendS(result, [NSURL URLWithString:file].path.UTF8String);
|
||||
}
|
||||
else
|
||||
{
|
||||
NSString* provider = choice[@"Provider"];
|
||||
NSString* builtinPrefix = @"com.apple.wallpaper.choice.";
|
||||
if ([provider hasPrefix:builtinPrefix])
|
||||
provider = [provider substringFromIndex:builtinPrefix.length];
|
||||
if ([provider isEqualToString:@"sonoma"])
|
||||
ffStrbufSetStatic(result, "macOS Sonoma");
|
||||
else if ([provider isEqualToString:@"aerials"]) // Most builtin aerial wallpapers are private
|
||||
ffStrbufSetStatic(result, "Built-in aerial photography");
|
||||
else
|
||||
ffStrbufAppendF(result, "Built-in %s wallpaper", provider.UTF8String);
|
||||
}
|
||||
}
|
||||
if (result->length > 0)
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
if (result->length == 0) {
|
||||
return "Failed to detect wallpaper from plist";
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#ifdef FF_HAVE_SQLITE3
|
||||
const char* detectFromSQLite(FFstrbuf* result) {
|
||||
// For Ventura
|
||||
// https://stackoverflow.com/questions/301215/getting-desktop-background-on-mac
|
||||
FF_STRBUF_AUTO_DESTROY path = ffStrbufCreateCopy(&instance.state.platform.homeDir);
|
||||
ffStrbufAppendS(&path, "Library/Application Support/Dock/desktoppicture.db");
|
||||
if (ffSettingsGetSQLite3String(path.chars,
|
||||
#ifdef FF_HAVE_SQLITE3
|
||||
|
||||
{
|
||||
// For Ventura
|
||||
// https://stackoverflow.com/questions/301215/getting-desktop-background-on-mac
|
||||
FF_STRBUF_AUTO_DESTROY path = ffStrbufCreateCopy(&instance.state.platform.homeDir);
|
||||
ffStrbufAppendS(&path, "Library/Application Support/Dock/desktoppicture.db");
|
||||
if (ffSettingsGetSQLite3String(path.chars,
|
||||
"SELECT value\n"
|
||||
"FROM preferences\n"
|
||||
"JOIN data ON preferences.data_id=data.ROWID\n"
|
||||
"JOIN pictures ON preferences.picture_id=pictures.ROWID\n"
|
||||
"JOIN displays ON pictures.display_id=displays.ROWID\n"
|
||||
"JOIN spaces ON pictures.space_id=spaces.ROWID\n"
|
||||
"WHERE display_id=1 AND space_id=1 AND key=1",
|
||||
result)) {
|
||||
"WHERE display_id=1 AND space_id=1 AND key=1", result)
|
||||
)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
if (ffOsascript("tell application \"Finder\" to get POSIX path of (get desktop picture as alias)", result))
|
||||
return nullptr;
|
||||
}
|
||||
return "Failed to detect wallpaper from SQLite database";
|
||||
}
|
||||
#endif
|
||||
|
||||
const char* detectFromNSWorkspace(FFstrbuf* result) {
|
||||
// Reliable for user-picked static images.
|
||||
NSScreen* mainScreen = NSScreen.mainScreen;
|
||||
if (!mainScreen) {
|
||||
return "Failed to detect wallpaper from NSWorkspace: No main screen found";
|
||||
}
|
||||
|
||||
NSURL* url = [NSWorkspace.sharedWorkspace desktopImageURLForScreen:mainScreen];
|
||||
if (url.fileURL) {
|
||||
ffStrbufSetS(result, url.path.UTF8String);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return "Failed to detect wallpaper from NSWorkspace";
|
||||
}
|
||||
|
||||
const char* ffDetectWallpaper(FFstrbuf* result) {
|
||||
const char* error;
|
||||
|
||||
if (@available(macOS 14.0, *)) {
|
||||
error = detectFromPlist(result);
|
||||
} else {
|
||||
#ifdef FF_HAVE_SQLITE3
|
||||
error = detectFromSQLite(result);
|
||||
#else
|
||||
error = detectFromNSWorkspace(result);
|
||||
#endif
|
||||
}
|
||||
return error;
|
||||
|
||||
return "All detection methods failed";
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "wifi.h"
|
||||
#include "common/endian.h"
|
||||
#include "common/io.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/strutil.h"
|
||||
@@ -12,11 +11,12 @@
|
||||
#include <linux/wireless.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <linux/genetlink.h>
|
||||
#include <linux/nl80211.h>
|
||||
#if !__BIG_ENDIAN__
|
||||
#include <linux/genetlink.h>
|
||||
#include <linux/nl80211.h>
|
||||
|
||||
// Silence warning of `NLA_HDRLEN` and `NLA_ALIGN`
|
||||
#pragma GCC diagnostic ignored "-Wsign-conversion"
|
||||
// Silence warning of `NLA_HDRLEN` and `NLA_ALIGN`
|
||||
#pragma GCC diagnostic ignored "-Wsign-conversion"
|
||||
|
||||
typedef struct FFWifiNlContext {
|
||||
int sockFd;
|
||||
@@ -308,19 +308,19 @@ static uint16_t ffWifiChannelWidthToMhz(uint32_t width) {
|
||||
return 80;
|
||||
case NL80211_CHAN_WIDTH_160:
|
||||
return 160;
|
||||
case 6 /* NL80211_CHAN_WIDTH_5 */:
|
||||
case NL80211_CHAN_WIDTH_5:
|
||||
return 5;
|
||||
case 7 /* NL80211_CHAN_WIDTH_10 */:
|
||||
case NL80211_CHAN_WIDTH_10:
|
||||
return 10;
|
||||
case 8 /* NL80211_CHAN_WIDTH_1 */:
|
||||
case NL80211_CHAN_WIDTH_1:
|
||||
return 1;
|
||||
case 9 /* NL80211_CHAN_WIDTH_2 */:
|
||||
case NL80211_CHAN_WIDTH_2:
|
||||
return 2;
|
||||
case 10 /* NL80211_CHAN_WIDTH_4 */:
|
||||
case NL80211_CHAN_WIDTH_4:
|
||||
return 4;
|
||||
case 11 /* NL80211_CHAN_WIDTH_8 */:
|
||||
case NL80211_CHAN_WIDTH_8:
|
||||
return 8;
|
||||
case 12 /* NL80211_CHAN_WIDTH_16 */:
|
||||
case NL80211_CHAN_WIDTH_16:
|
||||
return 16;
|
||||
case 13 /* NL80211_CHAN_WIDTH_320 */:
|
||||
return 320;
|
||||
@@ -459,7 +459,7 @@ static void ffWifiParseRsnIe(const uint8_t* ie, size_t len, FFWifiSecurityFlags*
|
||||
if (pos + 2 > len) { // pairwise cipher suite count field length
|
||||
return;
|
||||
}
|
||||
uint16_t pairwiseCount = FF_READ_LE(*(const uint16_t*) (ie + pos));
|
||||
uint16_t pairwiseCount = *(uint16_t*) (ie + pos);
|
||||
pos += 2; // skip pairwise cipher suite count field
|
||||
|
||||
size_t pairwiseLen = (size_t) pairwiseCount * 4; // each suite selector is 4 bytes
|
||||
@@ -471,7 +471,7 @@ static void ffWifiParseRsnIe(const uint8_t* ie, size_t len, FFWifiSecurityFlags*
|
||||
if (pos + 2 > len) { // AKM suite count field length
|
||||
return;
|
||||
}
|
||||
uint16_t akmCount = FF_READ_LE(*(const uint16_t*) (ie + pos));
|
||||
uint16_t akmCount = *(uint16_t*) (ie + pos);
|
||||
pos += 2; // skip AKM suite count field
|
||||
|
||||
for (uint16_t i = 0; i < akmCount && pos + 4 <= len; ++i, pos += 4) { // each AKM suite selector is 4 bytes
|
||||
@@ -528,13 +528,13 @@ static void ffWifiParseWpaVendorIe(const uint8_t* ie, size_t len, FFWifiSecurity
|
||||
if (pos + 2 > len) { // unicast cipher suite count field length
|
||||
return;
|
||||
}
|
||||
uint16_t pairwiseCount = FF_READ_LE(*(const uint16_t*) (ie + pos));
|
||||
uint16_t pairwiseCount = *(uint16_t*) (ie + pos);
|
||||
pos += 2 + (size_t) pairwiseCount * 4; // count field(2) + N unicast suite selectors(4 each)
|
||||
|
||||
if (pos + 2 > len) { // AKM suite count field length
|
||||
return;
|
||||
}
|
||||
uint16_t akmCount = FF_READ_LE(*(const uint16_t*) (ie + pos));
|
||||
uint16_t akmCount = *(uint16_t*) (ie + pos);
|
||||
pos += 2; // skip AKM suite count field
|
||||
|
||||
for (uint16_t i = 0; i < akmCount && pos + 4 <= len; ++i, pos += 4) { // each AKM suite selector is 4 bytes
|
||||
@@ -845,6 +845,8 @@ static const char* detectWithNetlink(FFWifiNlContext* ctx, FFWifiResult* item, u
|
||||
FF_DEBUG("Netlink wifi detection completed");
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
typedef struct FFWifiIcContext {
|
||||
int sockFd;
|
||||
} FFWifiIcContext;
|
||||
@@ -1047,7 +1049,9 @@ const char* ffDetectWifi(FFlist* result) {
|
||||
return "if_nameindex() failed";
|
||||
}
|
||||
|
||||
#if !__BIG_ENDIAN__
|
||||
FFWifiNlContext nl = { .sockFd = -1 };
|
||||
#endif
|
||||
FFWifiIcContext ic = { .sockFd = -1 };
|
||||
|
||||
FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
|
||||
@@ -1086,7 +1090,9 @@ const char* ffDetectWifi(FFlist* result) {
|
||||
if (operstate == 'u') {
|
||||
ffStrbufSetStatic(&item->inf.status, "up");
|
||||
|
||||
#if !__BIG_ENDIAN__
|
||||
detectWithNetlink(&nl, item, i->if_index);
|
||||
#endif
|
||||
detectWithIoctl(&ic, item, i->if_name);
|
||||
} else {
|
||||
ffStrbufSetStatic(&item->conn.status, "disconnected");
|
||||
@@ -1110,9 +1116,11 @@ const char* ffDetectWifi(FFlist* result) {
|
||||
}
|
||||
|
||||
if_freenameindex(infs);
|
||||
#if !__BIG_ENDIAN__
|
||||
if (nl.sockFd >= 0) {
|
||||
close(nl.sockFd);
|
||||
}
|
||||
#endif
|
||||
if (ic.sockFd >= 0) {
|
||||
close(ic.sockFd);
|
||||
}
|
||||
|
||||
@@ -11,23 +11,16 @@
|
||||
|
||||
const char* ffDetectWMPlugin(FFstrbuf* pluginName) {
|
||||
int request[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL };
|
||||
size_t length;
|
||||
u_int requestLength = ARRAY_SIZE(request);
|
||||
|
||||
if (sysctl(request, ARRAY_SIZE(request), nullptr, &length, nullptr, 0) != 0) {
|
||||
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_ALL, nullptr}) failed";
|
||||
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";
|
||||
}
|
||||
assert(length % sizeof(struct kinfo_proc) == 0);
|
||||
|
||||
// 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++) {
|
||||
for (size_t i = 0; i < length / sizeof(struct kinfo_proc); i++) {
|
||||
const struct kinfo_proc* proc = &processes[i];
|
||||
if (proc->kp_eproc.e_ppid != 1) {
|
||||
continue;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user