mirror of
https://github.com/ZoiteChat/zoitechat.git
synced 2026-09-03 21:43:10 +00:00
1090 lines
21 KiB
C
1090 lines
21 KiB
C
/*
|
|
* Modern Linux SysInfo backend for ZoiteChat.
|
|
*
|
|
* Uses native kernel/sysfs interfaces and caches static hardware data.
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/statvfs.h>
|
|
#include <sys/utsname.h>
|
|
#include <unistd.h>
|
|
|
|
#include <glib.h>
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#if defined(USE_GTK_FRONTEND)
|
|
#include <gdk/gdk.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_LIBPCI
|
|
#include <pci/pci.h>
|
|
#endif
|
|
|
|
#include "../format.h"
|
|
#include "../sysinfo-backend.h"
|
|
|
|
#define SYSINFO_SEP " \xC2\xB7 "
|
|
|
|
typedef struct
|
|
{
|
|
char *os;
|
|
char *cpu;
|
|
char *gpu;
|
|
char *chipset;
|
|
char *sound;
|
|
GHashTable *pci_names;
|
|
} LinuxStaticSnapshot;
|
|
|
|
static LinuxStaticSnapshot snapshot;
|
|
static gsize os_initialized = 0;
|
|
static gsize cpu_initialized = 0;
|
|
static gsize pci_initialized = 0;
|
|
static gsize sound_initialized = 0;
|
|
|
|
static char *read_trimmed_file (const char *path);
|
|
static gboolean read_hex_file (const char *path, guint *value);
|
|
static char *join_ptr_array (GPtrArray *items, const char *separator);
|
|
static void ptr_array_add_unique (GPtrArray *items, const char *value);
|
|
static char *read_os_pretty_name (void);
|
|
static char *read_cpu_model (void);
|
|
static guint count_physical_cores (void);
|
|
static char *build_cpu_string (void);
|
|
static char *build_sound_string (void);
|
|
static void scan_pci_devices (void);
|
|
static void ensure_os_snapshot (void);
|
|
static void ensure_cpu_snapshot (void);
|
|
static void ensure_pci_snapshot (void);
|
|
static void ensure_sound_snapshot (void);
|
|
static char *format_usage (guint64 total, guint64 available);
|
|
static char *read_driver_name (const char *interface_path);
|
|
|
|
static char *
|
|
read_trimmed_file (const char *path)
|
|
{
|
|
char *contents = NULL;
|
|
gsize length = 0;
|
|
|
|
if (!g_file_get_contents (path, &contents, &length, NULL))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
g_strstrip (contents);
|
|
if (contents[0] == '\0')
|
|
{
|
|
g_free (contents);
|
|
return NULL;
|
|
}
|
|
|
|
return contents;
|
|
}
|
|
|
|
static gboolean
|
|
read_hex_file (const char *path, guint *value)
|
|
{
|
|
char *contents;
|
|
char *end = NULL;
|
|
guint64 parsed;
|
|
|
|
contents = read_trimmed_file (path);
|
|
if (!contents)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
errno = 0;
|
|
parsed = g_ascii_strtoull (contents, &end, 16);
|
|
if (errno != 0 || end == contents)
|
|
{
|
|
g_free (contents);
|
|
return FALSE;
|
|
}
|
|
|
|
*value = (guint) parsed;
|
|
g_free (contents);
|
|
return TRUE;
|
|
}
|
|
|
|
static char *
|
|
join_ptr_array (GPtrArray *items, const char *separator)
|
|
{
|
|
GString *joined;
|
|
guint i;
|
|
|
|
if (!items || items->len == 0)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
joined = g_string_new (NULL);
|
|
for (i = 0; i < items->len; i++)
|
|
{
|
|
const char *item = g_ptr_array_index (items, i);
|
|
|
|
if (i != 0)
|
|
{
|
|
g_string_append (joined, separator);
|
|
}
|
|
g_string_append (joined, item);
|
|
}
|
|
|
|
return g_string_free (joined, FALSE);
|
|
}
|
|
|
|
static void
|
|
ptr_array_add_unique (GPtrArray *items, const char *value)
|
|
{
|
|
guint i;
|
|
|
|
if (!value || value[0] == '\0')
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < items->len; i++)
|
|
{
|
|
const char *existing = g_ptr_array_index (items, i);
|
|
if (g_strcmp0 (existing, value) == 0)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
g_ptr_array_add (items, g_strdup (value));
|
|
}
|
|
|
|
static char *
|
|
unquote_os_release_value (const char *value)
|
|
{
|
|
char *copy;
|
|
gsize len;
|
|
|
|
if (!value)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
copy = g_strdup (value);
|
|
g_strstrip (copy);
|
|
len = strlen (copy);
|
|
|
|
if (len >= 2 &&
|
|
((copy[0] == '"' && copy[len - 1] == '"') ||
|
|
(copy[0] == '\'' && copy[len - 1] == '\'')))
|
|
{
|
|
copy[len - 1] = '\0';
|
|
memmove (copy, copy + 1, len - 1);
|
|
}
|
|
|
|
return copy;
|
|
}
|
|
|
|
static char *
|
|
read_pretty_name_from_os_release (const char *path)
|
|
{
|
|
char *contents = NULL;
|
|
char **lines;
|
|
char *result = NULL;
|
|
guint i;
|
|
|
|
if (!g_file_get_contents (path, &contents, NULL, NULL))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
lines = g_strsplit (contents, "\n", -1);
|
|
for (i = 0; lines[i] != NULL; i++)
|
|
{
|
|
if (g_str_has_prefix (lines[i], "PRETTY_NAME="))
|
|
{
|
|
result = unquote_os_release_value (lines[i] + strlen ("PRETTY_NAME="));
|
|
break;
|
|
}
|
|
}
|
|
|
|
g_strfreev (lines);
|
|
g_free (contents);
|
|
return result;
|
|
}
|
|
|
|
static char *
|
|
read_os_pretty_name (void)
|
|
{
|
|
char *pretty = NULL;
|
|
|
|
/* Flatpak exposes the host os-release here. Prefer it over the runtime. */
|
|
if (g_file_test ("/run/host/etc/os-release", G_FILE_TEST_IS_REGULAR))
|
|
{
|
|
pretty = read_pretty_name_from_os_release ("/run/host/etc/os-release");
|
|
}
|
|
|
|
if (!pretty)
|
|
{
|
|
pretty = g_get_os_info (G_OS_INFO_KEY_PRETTY_NAME);
|
|
}
|
|
|
|
if (!pretty)
|
|
{
|
|
pretty = read_pretty_name_from_os_release ("/etc/os-release");
|
|
}
|
|
|
|
return pretty ? pretty : g_strdup ("Linux");
|
|
}
|
|
|
|
static char *
|
|
read_cpu_model (void)
|
|
{
|
|
FILE *fp;
|
|
char line[1024];
|
|
static const char *keys[] = {
|
|
"model name",
|
|
"Hardware",
|
|
"Processor",
|
|
"cpu model",
|
|
"Model Name",
|
|
NULL
|
|
};
|
|
|
|
fp = fopen ("/proc/cpuinfo", "r");
|
|
if (fp)
|
|
{
|
|
while (fgets (line, sizeof (line), fp) != NULL)
|
|
{
|
|
char *colon = strchr (line, ':');
|
|
guint i;
|
|
|
|
if (!colon)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
*colon = '\0';
|
|
g_strstrip (line);
|
|
for (i = 0; keys[i] != NULL; i++)
|
|
{
|
|
if (g_ascii_strcasecmp (line, keys[i]) == 0)
|
|
{
|
|
char *value = g_strdup (colon + 1);
|
|
char *endptr = NULL;
|
|
g_strstrip (value);
|
|
|
|
/*
|
|
* On x86 /proc/cpuinfo starts with `processor : 0`.
|
|
* `Processor` is also a useful model key on some ARM systems,
|
|
* so only reject it when the value is purely a CPU index.
|
|
*/
|
|
if (g_ascii_strcasecmp (keys[i], "Processor") == 0 && value[0] != '\0')
|
|
{
|
|
g_ascii_strtoull (value, &endptr, 10);
|
|
if (endptr && endptr != value && *endptr == '\0')
|
|
{
|
|
g_free (value);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (value[0] != '\0')
|
|
{
|
|
fclose (fp);
|
|
return value;
|
|
}
|
|
g_free (value);
|
|
}
|
|
}
|
|
}
|
|
fclose (fp);
|
|
}
|
|
|
|
if (g_file_test ("/sys/firmware/devicetree/base/model", G_FILE_TEST_IS_REGULAR))
|
|
{
|
|
char *model = read_trimmed_file ("/sys/firmware/devicetree/base/model");
|
|
if (model)
|
|
{
|
|
return model;
|
|
}
|
|
}
|
|
|
|
return g_strdup ("Unknown CPU");
|
|
}
|
|
|
|
static guint
|
|
count_physical_cores (void)
|
|
{
|
|
GDir *dir;
|
|
const char *entry;
|
|
GHashTable *cores;
|
|
guint count;
|
|
|
|
dir = g_dir_open ("/sys/devices/system/cpu", 0, NULL);
|
|
if (!dir)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
cores = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
|
|
while ((entry = g_dir_read_name (dir)) != NULL)
|
|
{
|
|
char *package_path;
|
|
char *core_path;
|
|
char *package_id;
|
|
char *core_id;
|
|
char *key;
|
|
|
|
if (!g_str_has_prefix (entry, "cpu") || !g_ascii_isdigit (entry[3]))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
package_path = g_build_filename ("/sys/devices/system/cpu", entry, "topology", "physical_package_id", NULL);
|
|
core_path = g_build_filename ("/sys/devices/system/cpu", entry, "topology", "core_id", NULL);
|
|
package_id = read_trimmed_file (package_path);
|
|
core_id = read_trimmed_file (core_path);
|
|
g_free (package_path);
|
|
g_free (core_path);
|
|
|
|
if (!package_id || !core_id)
|
|
{
|
|
g_free (package_id);
|
|
g_free (core_id);
|
|
continue;
|
|
}
|
|
|
|
key = g_strdup_printf ("%s:%s", package_id, core_id);
|
|
g_hash_table_add (cores, key);
|
|
g_free (package_id);
|
|
g_free (core_id);
|
|
}
|
|
|
|
count = g_hash_table_size (cores);
|
|
g_hash_table_destroy (cores);
|
|
g_dir_close (dir);
|
|
return count;
|
|
}
|
|
|
|
static char *
|
|
build_cpu_string (void)
|
|
{
|
|
char *model;
|
|
long logical_long;
|
|
guint logical;
|
|
guint physical;
|
|
|
|
model = read_cpu_model ();
|
|
logical_long = sysconf (_SC_NPROCESSORS_ONLN);
|
|
logical = logical_long > 0 ? (guint) logical_long : 0;
|
|
physical = count_physical_cores ();
|
|
|
|
if (physical == 0)
|
|
{
|
|
physical = logical;
|
|
}
|
|
|
|
if (physical != 0 && logical != 0)
|
|
{
|
|
char *result = g_strdup_printf ("%s" SYSINFO_SEP "%uC/%uT", model, physical, logical);
|
|
g_free (model);
|
|
return result;
|
|
}
|
|
|
|
return model;
|
|
}
|
|
|
|
|
|
static char *
|
|
build_sound_string (void)
|
|
{
|
|
FILE *fp;
|
|
char line[1024];
|
|
GPtrArray *cards;
|
|
char *result;
|
|
|
|
fp = fopen ("/proc/asound/cards", "r");
|
|
if (!fp)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
cards = g_ptr_array_new_with_free_func (g_free);
|
|
while (fgets (line, sizeof (line), fp) != NULL)
|
|
{
|
|
char *start = line;
|
|
char *separator;
|
|
char *name;
|
|
|
|
while (*start && g_ascii_isspace (*start))
|
|
{
|
|
start++;
|
|
}
|
|
if (!g_ascii_isdigit (*start))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
separator = strstr (start, " - ");
|
|
if (!separator)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
name = g_strdup (separator + 3);
|
|
g_strstrip (name);
|
|
ptr_array_add_unique (cards, name);
|
|
g_free (name);
|
|
}
|
|
fclose (fp);
|
|
|
|
result = join_ptr_array (cards, ", ");
|
|
g_ptr_array_free (cards, TRUE);
|
|
return result;
|
|
}
|
|
|
|
#ifdef HAVE_LIBPCI
|
|
static char *
|
|
resolve_pci_name (struct pci_access *pacc, guint vendor, guint device)
|
|
{
|
|
char name[1024];
|
|
char *resolved;
|
|
|
|
resolved = pci_lookup_name (pacc, name, sizeof (name),
|
|
PCI_LOOKUP_VENDOR | PCI_LOOKUP_DEVICE,
|
|
vendor, device);
|
|
if (resolved && resolved[0] != '\0')
|
|
{
|
|
return g_strdup (resolved);
|
|
}
|
|
|
|
return g_strdup_printf ("%04x:%04x", vendor, device);
|
|
}
|
|
#endif
|
|
|
|
static char *
|
|
clean_gpu_name (const char *raw_name)
|
|
{
|
|
static const char *vendor_prefixes[] = {
|
|
"NVIDIA Corporation ",
|
|
"Advanced Micro Devices, Inc. [AMD/ATI] ",
|
|
"Advanced Micro Devices, Inc. ",
|
|
"Intel Corporation ",
|
|
NULL
|
|
};
|
|
char *name;
|
|
char *open;
|
|
char *close;
|
|
guint i;
|
|
|
|
if (!raw_name || raw_name[0] == '\0')
|
|
return NULL;
|
|
|
|
name = g_strdup (raw_name);
|
|
g_strstrip (name);
|
|
|
|
/* Prefer the friendly product name at the end of pci.ids entries. */
|
|
close = strrchr (name, ']');
|
|
open = strrchr (name, '[');
|
|
if (open && close && close > open + 1 && close[1] == '\0')
|
|
{
|
|
char *clean;
|
|
|
|
*close = '\0';
|
|
clean = g_strdup (open + 1);
|
|
g_strstrip (clean);
|
|
if (clean[0] != '\0')
|
|
{
|
|
g_free (name);
|
|
return clean;
|
|
}
|
|
g_free (clean);
|
|
}
|
|
|
|
/* Fall back to removing common PCI vendor prefixes. */
|
|
for (i = 0; vendor_prefixes[i] != NULL; i++)
|
|
{
|
|
if (g_str_has_prefix (name, vendor_prefixes[i]))
|
|
{
|
|
char *clean = g_strdup (name + strlen (vendor_prefixes[i]));
|
|
g_strstrip (clean);
|
|
if (clean[0] != '\0')
|
|
{
|
|
g_free (name);
|
|
return clean;
|
|
}
|
|
g_free (clean);
|
|
}
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
static void
|
|
scan_pci_devices (void)
|
|
{
|
|
GDir *dir;
|
|
const char *entry;
|
|
GPtrArray *gpus;
|
|
GPtrArray *chipsets;
|
|
#ifdef HAVE_LIBPCI
|
|
struct pci_access *pacc = NULL;
|
|
#endif
|
|
|
|
snapshot.pci_names = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
|
|
gpus = g_ptr_array_new_with_free_func (g_free);
|
|
chipsets = g_ptr_array_new_with_free_func (g_free);
|
|
|
|
#ifdef HAVE_LIBPCI
|
|
pacc = pci_alloc ();
|
|
if (pacc)
|
|
{
|
|
pci_init (pacc);
|
|
}
|
|
#endif
|
|
|
|
dir = g_dir_open ("/sys/bus/pci/devices", 0, NULL);
|
|
if (dir)
|
|
{
|
|
while ((entry = g_dir_read_name (dir)) != NULL)
|
|
{
|
|
char *class_path;
|
|
char *vendor_path;
|
|
char *device_path;
|
|
char *name;
|
|
char *key;
|
|
guint class_code;
|
|
guint vendor;
|
|
guint device;
|
|
guint base_class;
|
|
guint class_subclass;
|
|
|
|
class_path = g_build_filename ("/sys/bus/pci/devices", entry, "class", NULL);
|
|
vendor_path = g_build_filename ("/sys/bus/pci/devices", entry, "vendor", NULL);
|
|
device_path = g_build_filename ("/sys/bus/pci/devices", entry, "device", NULL);
|
|
|
|
if (!read_hex_file (class_path, &class_code) ||
|
|
!read_hex_file (vendor_path, &vendor) ||
|
|
!read_hex_file (device_path, &device))
|
|
{
|
|
g_free (class_path);
|
|
g_free (vendor_path);
|
|
g_free (device_path);
|
|
continue;
|
|
}
|
|
|
|
g_free (class_path);
|
|
g_free (vendor_path);
|
|
g_free (device_path);
|
|
|
|
#ifdef HAVE_LIBPCI
|
|
if (pacc)
|
|
{
|
|
name = resolve_pci_name (pacc, vendor, device);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
name = g_strdup_printf ("%04x:%04x", vendor, device);
|
|
}
|
|
|
|
key = g_strdup_printf ("%04x:%04x", vendor, device);
|
|
if (!g_hash_table_contains (snapshot.pci_names, key))
|
|
{
|
|
g_hash_table_insert (snapshot.pci_names, key, g_strdup (name));
|
|
}
|
|
else
|
|
{
|
|
g_free (key);
|
|
}
|
|
|
|
base_class = (class_code >> 16) & 0xff;
|
|
class_subclass = (class_code >> 8) & 0xffff;
|
|
|
|
if (base_class == 0x03)
|
|
{
|
|
char *gpu_name = clean_gpu_name (name);
|
|
if (gpu_name)
|
|
{
|
|
ptr_array_add_unique (gpus, gpu_name);
|
|
g_free (gpu_name);
|
|
}
|
|
}
|
|
else if (class_subclass == 0x0600)
|
|
{
|
|
ptr_array_add_unique (chipsets, name);
|
|
}
|
|
|
|
g_free (name);
|
|
}
|
|
g_dir_close (dir);
|
|
}
|
|
|
|
#ifdef HAVE_LIBPCI
|
|
if (pacc)
|
|
{
|
|
pci_cleanup (pacc);
|
|
}
|
|
#endif
|
|
|
|
snapshot.gpu = join_ptr_array (gpus, ", ");
|
|
snapshot.chipset = join_ptr_array (chipsets, ", ");
|
|
g_ptr_array_free (gpus, TRUE);
|
|
g_ptr_array_free (chipsets, TRUE);
|
|
}
|
|
|
|
static char *
|
|
build_os_string (void)
|
|
{
|
|
char *pretty;
|
|
struct utsname uts;
|
|
char *result;
|
|
|
|
pretty = read_os_pretty_name ();
|
|
if (uname (&uts) != 0)
|
|
{
|
|
return pretty;
|
|
}
|
|
|
|
result = g_strdup_printf ("%s" SYSINFO_SEP "%s %s" SYSINFO_SEP "%s",
|
|
pretty, uts.sysname, uts.release, uts.machine);
|
|
g_free (pretty);
|
|
return result;
|
|
}
|
|
|
|
static void
|
|
ensure_os_snapshot (void)
|
|
{
|
|
if (g_once_init_enter (&os_initialized))
|
|
{
|
|
snapshot.os = build_os_string ();
|
|
g_once_init_leave (&os_initialized, 1);
|
|
}
|
|
}
|
|
|
|
static void
|
|
ensure_cpu_snapshot (void)
|
|
{
|
|
if (g_once_init_enter (&cpu_initialized))
|
|
{
|
|
snapshot.cpu = build_cpu_string ();
|
|
g_once_init_leave (&cpu_initialized, 1);
|
|
}
|
|
}
|
|
|
|
static void
|
|
ensure_pci_snapshot (void)
|
|
{
|
|
if (g_once_init_enter (&pci_initialized))
|
|
{
|
|
scan_pci_devices ();
|
|
g_once_init_leave (&pci_initialized, 1);
|
|
}
|
|
}
|
|
|
|
static void
|
|
ensure_sound_snapshot (void)
|
|
{
|
|
if (g_once_init_enter (&sound_initialized))
|
|
{
|
|
snapshot.sound = build_sound_string ();
|
|
g_once_init_leave (&sound_initialized, 1);
|
|
}
|
|
}
|
|
|
|
static char *
|
|
format_usage (guint64 total, guint64 available)
|
|
{
|
|
guint64 used;
|
|
char *used_fmt;
|
|
char *total_fmt;
|
|
char *result;
|
|
double percent;
|
|
|
|
if (total == 0)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
if (available > total)
|
|
{
|
|
available = total;
|
|
}
|
|
used = total - available;
|
|
percent = ((double) used / (double) total) * 100.0;
|
|
|
|
used_fmt = g_format_size_full (used, G_FORMAT_SIZE_IEC_UNITS);
|
|
total_fmt = g_format_size_full (total, G_FORMAT_SIZE_IEC_UNITS);
|
|
result = g_strdup_printf ("%s / %s used (%.0f%%)", used_fmt, total_fmt, percent);
|
|
g_free (used_fmt);
|
|
g_free (total_fmt);
|
|
return result;
|
|
}
|
|
|
|
char *
|
|
sysinfo_backend_get_os (void)
|
|
{
|
|
ensure_os_snapshot ();
|
|
return g_strdup (snapshot.os);
|
|
}
|
|
|
|
char *
|
|
sysinfo_backend_get_cpu (void)
|
|
{
|
|
ensure_cpu_snapshot ();
|
|
return g_strdup (snapshot.cpu);
|
|
}
|
|
|
|
char *
|
|
sysinfo_backend_get_gpu (void)
|
|
{
|
|
ensure_pci_snapshot ();
|
|
return g_strdup (snapshot.gpu);
|
|
}
|
|
|
|
char *
|
|
sysinfo_backend_get_chipset (void)
|
|
{
|
|
ensure_pci_snapshot ();
|
|
return g_strdup (snapshot.chipset);
|
|
}
|
|
|
|
char *
|
|
sysinfo_backend_get_sound (void)
|
|
{
|
|
ensure_sound_snapshot ();
|
|
return g_strdup (snapshot.sound);
|
|
}
|
|
|
|
char *
|
|
sysinfo_backend_get_memory (void)
|
|
{
|
|
FILE *fp;
|
|
char line[512];
|
|
guint64 mem_total_kib = 0;
|
|
guint64 mem_available_kib = 0;
|
|
guint64 swap_total_kib = 0;
|
|
guint64 swap_free_kib = 0;
|
|
char *memory;
|
|
|
|
fp = fopen ("/proc/meminfo", "r");
|
|
if (!fp)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
while (fgets (line, sizeof (line), fp) != NULL)
|
|
{
|
|
if (g_str_has_prefix (line, "MemTotal:"))
|
|
{
|
|
mem_total_kib = g_ascii_strtoull (line + strlen ("MemTotal:"), NULL, 10);
|
|
}
|
|
else if (g_str_has_prefix (line, "MemAvailable:"))
|
|
{
|
|
mem_available_kib = g_ascii_strtoull (line + strlen ("MemAvailable:"), NULL, 10);
|
|
}
|
|
else if (g_str_has_prefix (line, "SwapTotal:"))
|
|
{
|
|
swap_total_kib = g_ascii_strtoull (line + strlen ("SwapTotal:"), NULL, 10);
|
|
}
|
|
else if (g_str_has_prefix (line, "SwapFree:"))
|
|
{
|
|
swap_free_kib = g_ascii_strtoull (line + strlen ("SwapFree:"), NULL, 10);
|
|
}
|
|
}
|
|
fclose (fp);
|
|
|
|
if (mem_total_kib == 0)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
memory = format_usage (mem_total_kib * 1024, mem_available_kib * 1024);
|
|
if (memory && swap_total_kib != 0)
|
|
{
|
|
char *swap = format_usage (swap_total_kib * 1024, swap_free_kib * 1024);
|
|
if (swap)
|
|
{
|
|
char *result = g_strdup_printf ("%s" SYSINFO_SEP "Swap: %s", memory, swap);
|
|
g_free (memory);
|
|
g_free (swap);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
return memory;
|
|
}
|
|
|
|
char *
|
|
sysinfo_backend_get_disk (void)
|
|
{
|
|
const char *path;
|
|
struct statvfs fs;
|
|
guint64 block_size;
|
|
guint64 total;
|
|
guint64 available;
|
|
char *usage;
|
|
|
|
path = g_get_home_dir ();
|
|
if (!path || path[0] == '\0')
|
|
{
|
|
path = "/";
|
|
}
|
|
|
|
if (statvfs (path, &fs) != 0)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
block_size = fs.f_frsize != 0 ? (guint64) fs.f_frsize : (guint64) fs.f_bsize;
|
|
total = (guint64) fs.f_blocks * block_size;
|
|
available = (guint64) fs.f_bavail * block_size;
|
|
usage = format_usage (total, available);
|
|
return usage;
|
|
}
|
|
|
|
char *
|
|
sysinfo_backend_get_uptime (void)
|
|
{
|
|
char *contents;
|
|
char *end = NULL;
|
|
double uptime;
|
|
|
|
contents = read_trimmed_file ("/proc/uptime");
|
|
if (!contents)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
uptime = g_ascii_strtod (contents, &end);
|
|
if (end == contents || uptime <= 0.0)
|
|
{
|
|
g_free (contents);
|
|
return NULL;
|
|
}
|
|
|
|
g_free (contents);
|
|
return sysinfo_format_uptime ((gint64) uptime);
|
|
}
|
|
|
|
static char *
|
|
read_driver_name (const char *interface_path)
|
|
{
|
|
char *driver_path;
|
|
char *target;
|
|
char *name;
|
|
|
|
driver_path = g_build_filename (interface_path, "device", "driver", NULL);
|
|
target = g_file_read_link (driver_path, NULL);
|
|
g_free (driver_path);
|
|
if (!target)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
name = g_path_get_basename (target);
|
|
g_free (target);
|
|
return name;
|
|
}
|
|
|
|
char *
|
|
sysinfo_backend_get_network (void)
|
|
{
|
|
GDir *dir;
|
|
const char *entry;
|
|
GPtrArray *adapters;
|
|
char *result;
|
|
|
|
ensure_pci_snapshot ();
|
|
dir = g_dir_open ("/sys/class/net", 0, NULL);
|
|
if (!dir)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
adapters = g_ptr_array_new_with_free_func (g_free);
|
|
while ((entry = g_dir_read_name (dir)) != NULL)
|
|
{
|
|
char *interface_path;
|
|
char *device_path;
|
|
char *operstate_path;
|
|
char *operstate;
|
|
char *wireless_path;
|
|
char *vendor_path;
|
|
char *device_id_path;
|
|
char *driver;
|
|
const char *hardware_name = NULL;
|
|
char *key = NULL;
|
|
char *label;
|
|
guint vendor;
|
|
guint device;
|
|
gboolean wireless;
|
|
|
|
if (strcmp (entry, "lo") == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
interface_path = g_build_filename ("/sys/class/net", entry, NULL);
|
|
device_path = g_build_filename (interface_path, "device", NULL);
|
|
if (!g_file_test (device_path, G_FILE_TEST_EXISTS))
|
|
{
|
|
g_free (device_path);
|
|
g_free (interface_path);
|
|
continue;
|
|
}
|
|
|
|
operstate_path = g_build_filename (interface_path, "operstate", NULL);
|
|
operstate = read_trimmed_file (operstate_path);
|
|
g_free (operstate_path);
|
|
if (!operstate || g_ascii_strcasecmp (operstate, "up") != 0)
|
|
{
|
|
g_free (operstate);
|
|
g_free (device_path);
|
|
g_free (interface_path);
|
|
continue;
|
|
}
|
|
g_free (operstate);
|
|
|
|
wireless_path = g_build_filename (interface_path, "wireless", NULL);
|
|
wireless = g_file_test (wireless_path, G_FILE_TEST_IS_DIR);
|
|
g_free (wireless_path);
|
|
|
|
vendor_path = g_build_filename (device_path, "vendor", NULL);
|
|
device_id_path = g_build_filename (device_path, "device", NULL);
|
|
if (read_hex_file (vendor_path, &vendor) && read_hex_file (device_id_path, &device))
|
|
{
|
|
key = g_strdup_printf ("%04x:%04x", vendor, device);
|
|
hardware_name = g_hash_table_lookup (snapshot.pci_names, key);
|
|
}
|
|
g_free (vendor_path);
|
|
g_free (device_id_path);
|
|
|
|
driver = read_driver_name (interface_path);
|
|
if (hardware_name)
|
|
{
|
|
label = g_strdup_printf ("%s: %s (%s)", wireless ? "Wi-Fi" : "Ethernet", hardware_name, entry);
|
|
}
|
|
else if (driver)
|
|
{
|
|
label = g_strdup_printf ("%s: %s (%s)", wireless ? "Wi-Fi" : "Ethernet", driver, entry);
|
|
}
|
|
else
|
|
{
|
|
label = g_strdup_printf ("%s: %s", wireless ? "Wi-Fi" : "Ethernet", entry);
|
|
}
|
|
|
|
ptr_array_add_unique (adapters, label);
|
|
g_free (label);
|
|
g_free (driver);
|
|
g_free (key);
|
|
g_free (device_path);
|
|
g_free (interface_path);
|
|
}
|
|
g_dir_close (dir);
|
|
|
|
result = join_ptr_array (adapters, ", ");
|
|
g_ptr_array_free (adapters, TRUE);
|
|
return result;
|
|
}
|
|
|
|
static const char *
|
|
sysinfo_detect_toolkit (void)
|
|
{
|
|
#if defined(USE_GTK_FRONTEND)
|
|
return "GTK3";
|
|
#else
|
|
return NULL;
|
|
#endif
|
|
}
|
|
|
|
static const char *
|
|
sysinfo_detect_display_backend (void)
|
|
{
|
|
const char *backend = NULL;
|
|
const char *gdk_backend = g_getenv ("GDK_BACKEND");
|
|
const char *session = g_getenv ("XDG_SESSION_TYPE");
|
|
const gboolean session_wayland = session && g_ascii_strcasecmp (session, "wayland") == 0;
|
|
|
|
#if defined(USE_GTK_FRONTEND)
|
|
{
|
|
GdkDisplay *display = gdk_display_get_default ();
|
|
if (display)
|
|
{
|
|
const char *type_name = G_OBJECT_TYPE_NAME (display);
|
|
if (type_name)
|
|
{
|
|
if (g_strrstr (type_name, "Wayland"))
|
|
{
|
|
backend = "Wayland";
|
|
}
|
|
else if (g_strrstr (type_name, "X11"))
|
|
{
|
|
backend = "X11";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (!backend && gdk_backend)
|
|
{
|
|
if (g_strrstr (gdk_backend, "wayland"))
|
|
{
|
|
backend = "Wayland";
|
|
}
|
|
else if (g_strrstr (gdk_backend, "x11"))
|
|
{
|
|
backend = "X11";
|
|
}
|
|
}
|
|
|
|
if (!backend)
|
|
{
|
|
const gboolean has_wayland = g_getenv ("WAYLAND_DISPLAY") != NULL;
|
|
const gboolean has_x11 = g_getenv ("DISPLAY") != NULL;
|
|
|
|
if (has_wayland && !has_x11)
|
|
{
|
|
backend = "Wayland";
|
|
}
|
|
else if (has_x11 && !has_wayland)
|
|
{
|
|
backend = "X11";
|
|
}
|
|
else if (session_wayland)
|
|
{
|
|
backend = "Wayland";
|
|
}
|
|
}
|
|
|
|
if (backend && g_strcmp0 (backend, "X11") == 0 && session_wayland)
|
|
{
|
|
return "XWayland";
|
|
}
|
|
|
|
return backend;
|
|
}
|
|
|
|
char *
|
|
sysinfo_backend_get_ui (void)
|
|
{
|
|
const char *toolkit = sysinfo_detect_toolkit ();
|
|
const char *display = sysinfo_detect_display_backend ();
|
|
|
|
if (toolkit && display)
|
|
{
|
|
return g_strdup_printf ("%s / %s", toolkit, display);
|
|
}
|
|
if (toolkit)
|
|
{
|
|
return g_strdup (toolkit);
|
|
}
|
|
if (display)
|
|
{
|
|
return g_strdup (display);
|
|
}
|
|
|
|
return NULL;
|
|
}
|