diff --git a/plugins/sysinfo/meson.build b/plugins/sysinfo/meson.build index a2e4ce68..420d28c4 100644 --- a/plugins/sysinfo/meson.build +++ b/plugins/sysinfo/meson.build @@ -6,50 +6,66 @@ sysinfo_sources = [ sysinfo_deps = [ libgio_dep, zoitechat_plugin_dep, - common_sysinfo_deps, ] sysinfo_includes = [] sysinfo_cargs = ['-DHAVE_CONFIG_H'] system = host_machine.system() -if system == 'linux' or system == 'gnu' or system.startswith('gnu/') or system == 'darwin' or system == 'freebsd' - sysinfo_includes += 'shared' - sysinfo_sources += [ - 'shared/df.c' - ] - - if system == 'linux' or system == 'gnu' or system.startswith('gnu/') or system == 'freebsd' - libpci = dependency('libpci', required: false, method: 'pkg-config') - if libpci.found() - sysinfo_deps += libpci - sysinfo_cargs += '-DHAVE_LIBPCI' - sysinfo_sources += 'unix/pci.c' - - picidsdir = libpci.get_pkgconfig_variable('idsdir') - pciids = join_paths(picidsdir, 'pci.ids') - sysinfo_cargs += '-DPCIIDS_FILE="@0@"'.format(pciids) - endif - if get_option('gtk-frontend') - sysinfo_deps += dependency('gtk+-3.0', version: '>= 3.22') - endif - - sysinfo_includes += 'unix' - sysinfo_sources += [ - 'unix/backend.c', - 'unix/match.c', - 'unix/parse.c', - ] - elif system == 'darwin' - add_languages('objc') - sysinfo_sources += 'osx/backend.m' +if system == 'linux' or system == 'gnu' or system.startswith('gnu/') + if get_option('gtk-frontend') + sysinfo_deps += dependency('gtk+-3.0', version: '>= 3.22') endif -elif system == 'windows' + libpci = dependency('libpci', required: false, method: 'pkg-config') + if libpci.found() + sysinfo_deps += libpci + sysinfo_cargs += '-DHAVE_LIBPCI' + endif + + sysinfo_includes += 'unix' + sysinfo_sources += 'unix/modern-backend.c' + +elif system == 'freebsd' + sysinfo_includes += ['shared', 'unix'] sysinfo_sources += [ - 'win32/backend.c', - '../../src/common/sysinfo/win32/backend.c' + 'shared/df.c', + 'unix/backend.c', + 'unix/match.c', + 'unix/parse.c', ] + + libpci = dependency('libpci', required: false, method: 'pkg-config') + if libpci.found() + sysinfo_deps += libpci + sysinfo_cargs += '-DHAVE_LIBPCI' + sysinfo_sources += 'unix/pci.c' + + picidsdir = libpci.get_pkgconfig_variable('idsdir') + pciids = join_paths(picidsdir, 'pci.ids') + sysinfo_cargs += '-DPCIIDS_FILE="@0@"'.format(pciids) + endif + + if get_option('gtk-frontend') + sysinfo_deps += dependency('gtk+-3.0', version: '>= 3.22') + endif + +elif system == 'darwin' + sysinfo_includes += 'shared' + sysinfo_sources += 'shared/df.c' + add_languages('objc') + sysinfo_sources += 'osx/backend.m' + +elif system == 'windows' + cc = meson.get_compiler('c') + sysinfo_deps += [ + cc.find_library('advapi32', required: true), + cc.find_library('dxgi', required: true), + cc.find_library('dxguid', required: true), + cc.find_library('iphlpapi', required: true), + cc.find_library('winmm', required: true), + ] + sysinfo_sources += 'win32/backend.c' else error('sysinfo: Unknown system?') endif diff --git a/plugins/sysinfo/sysinfo.c b/plugins/sysinfo/sysinfo.c index 21c50bbb..fa46a5c8 100644 --- a/plugins/sysinfo/sysinfo.c +++ b/plugins/sysinfo/sysinfo.c @@ -40,8 +40,8 @@ static zoitechat_plugin *ph; static char name[] = "Sysinfo"; static char desc[] = "Display info about your hardware and OS"; -static char version[] = "1.0"; -static char sysinfo_help[] = "SysInfo Usage:\n /SYSINFO [-e|-o] [CLIENT|UI|OS|CPU|RAM|STORAGE|GPU|CHIPSET|SOUND|ETHERNET|UPTIME], print various details about your system or print a summary without arguments\n /SYSINFO SET \n"; +static char version[] = "2.0"; +static char sysinfo_help[] = "SysInfo Usage:\n /SYSINFO [-e|-o] [CLIENT|UI|OS|CPU|RAM|MEMORY|STORAGE|DISK|GPU|VGA|CHIPSET|SOUND|NETWORK|ETHERNET|UPTIME], print various details about your system or print a summary without arguments\n /SYSINFO SET \n"; typedef struct { @@ -68,11 +68,15 @@ static hwinfo hwinfos[] = { {"os", "OS", sysinfo_backend_get_os}, {"cpu", "CPU", sysinfo_backend_get_cpu}, {"memory", "Memory", sysinfo_backend_get_memory}, +#if defined(__linux__) + {"storage", "Storage", sysinfo_backend_get_disk, FALSE}, +#else {"storage", "Storage", sysinfo_backend_get_disk, TRUE}, +#endif {"gpu", "GPU", sysinfo_backend_get_gpu}, - {"chipset", "CHIPSET", sysinfo_backend_get_chipset, TRUE}, + {"chipset", "Chipset", sysinfo_backend_get_chipset, TRUE}, {"sound", "Sound", sysinfo_backend_get_sound, TRUE}, - {"ethernet", "Ethernet", sysinfo_backend_get_network, TRUE}, + {"ethernet", "Network", sysinfo_backend_get_network, TRUE}, {"uptime", "Uptime", sysinfo_backend_get_uptime}, {NULL, NULL}, }; @@ -115,14 +119,30 @@ print_summary (gboolean announce) g_free (output); } +static const char * +normalize_info_name (const char *info) +{ + if (!g_ascii_strcasecmp (info, "ram")) + return "memory"; + if (!g_ascii_strcasecmp (info, "disk")) + return "storage"; + if (!g_ascii_strcasecmp (info, "vga")) + return "gpu"; + if (!g_ascii_strcasecmp (info, "network")) + return "ethernet"; + + return info; +} + static void print_info (char *info, gboolean announce) { + const char *normalized = normalize_info_name (info); int i; for (i = 0; hwinfos[i].name != NULL; i++) { - if (!g_ascii_strcasecmp (info, hwinfos[i].name)) + if (!g_ascii_strcasecmp (normalized, hwinfos[i].name)) { char *str = hwinfos[i].callback(); if (str) @@ -183,12 +203,17 @@ sysinfo_set_pref (char *key, char *value) } else if (g_str_has_prefix (key, "hide_")) { + const char *normalized = normalize_info_name (key + 5); int i; + for (i = 0; hwinfos[i].name != NULL; i++) { - if (!strcmp (key + 5, hwinfos[i].name)) + if (!strcmp (normalized, hwinfos[i].name)) { - sysinfo_set_pref_real (key, value, hwinfos[i].def); + char canonical[32]; + + g_snprintf (canonical, sizeof (canonical), "hide_%s", hwinfos[i].name); + sysinfo_set_pref_real (canonical, value, hwinfos[i].def); return; } } diff --git a/plugins/sysinfo/unix/modern-backend.c b/plugins/sysinfo/unix/modern-backend.c new file mode 100644 index 00000000..56425036 --- /dev/null +++ b/plugins/sysinfo/unix/modern-backend.c @@ -0,0 +1,1089 @@ +/* + * Modern Linux SysInfo backend for ZoiteChat. + * + * Uses native kernel/sysfs interfaces and caches static hardware data. + */ + +#include +#include +#include +#include +#include +#include + +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#if defined(USE_GTK_FRONTEND) +#include +#endif + +#ifdef HAVE_LIBPCI +#include +#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; +} diff --git a/plugins/sysinfo/win32/backend.c b/plugins/sysinfo/win32/backend.c index 19d60da5..823775ac 100644 --- a/plugins/sysinfo/win32/backend.c +++ b/plugins/sysinfo/win32/backend.c @@ -1,28 +1,33 @@ -/* ZoiteChat - * Copyright (c) 2011-2012 Berke Viktor. +/* + * Modern Windows SysInfo backend for ZoiteChat. * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. + * Avoids WMI in the plugin hot path and uses native Win32/DXGI APIs. */ -#include +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0601 +#elif _WIN32_WINNT < 0x0601 +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x0601 +#endif + #include -#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma comment(lib, "advapi32.lib") +#pragma comment(lib, "dxgi.lib") +#pragma comment(lib, "dxguid.lib") +#pragma comment(lib, "iphlpapi.lib") +#pragma comment(lib, "winmm.lib") +#endif +#include +#include +#include #include @@ -30,63 +35,543 @@ #include "config.h" #endif -#include "../../../src/common/sysinfo/sysinfo.h" - #include "../format.h" +#include "../sysinfo-backend.h" -static char *get_memory_info (void); +#define SYSINFO_SEP " \xC2\xB7 " -char * -sysinfo_backend_get_sound (void) +typedef struct { - return NULL; -} + char *os; + char *cpu; + char *gpu; + char *sound; +} WinStaticSnapshot; -char * -sysinfo_backend_get_network (void) +static WinStaticSnapshot snapshot; +static gsize os_initialized = 0; +static gsize cpu_initialized = 0; +static gsize gpu_initialized = 0; +static gsize sound_initialized = 0; + +static char *wide_to_utf8 (const WCHAR *value); +static char *read_registry_string (HKEY root, const WCHAR *subkey, const WCHAR *value_name); +static gboolean read_registry_dword (HKEY root, const WCHAR *subkey, const WCHAR *value_name, DWORD *value); +static const char *get_native_arch (void); +static DWORD get_windows_build_number (void); +static char *build_os_string (void); +static DWORD count_physical_cores (void); +static char *build_cpu_string (void); +static char *build_gpu_string (void); +static char *build_sound_string (void); +static void ensure_os_snapshot (void); +static void ensure_cpu_snapshot (void); +static void ensure_gpu_snapshot (void); +static void ensure_sound_snapshot (void); +static char *format_usage (guint64 total, guint64 available); +static char *join_ptr_array (GPtrArray *items, const char *separator); +static void ptr_array_add_unique (GPtrArray *items, const char *value); +static gboolean adapter_looks_virtual (const char *name); + +static char * +wide_to_utf8 (const WCHAR *value) { - return NULL; -} - -char * -sysinfo_backend_get_uptime (void) -{ - return sysinfo_format_uptime (GetTickCount64 () / 1000); -} - -char * -sysinfo_backend_get_disk (void) -{ - guint64 hdd_capacity; - guint64 hdd_free_space; - - sysinfo_get_hdd_info (&hdd_capacity, &hdd_free_space); - - if (hdd_capacity != 0) + if (!value || value[0] == L'\0') { - return sysinfo_format_disk(hdd_capacity, hdd_free_space); + return NULL; } - return NULL; + return g_utf16_to_utf8 ((const gunichar2 *) value, -1, NULL, NULL, NULL); +} + +static char * +read_registry_string (HKEY root, const WCHAR *subkey, const WCHAR *value_name) +{ + HKEY key; + DWORD type = 0; + DWORD size = 0; + WCHAR *buffer; + char *result = NULL; + LONG status; + + status = RegOpenKeyExW (root, subkey, 0, KEY_READ | KEY_WOW64_64KEY, &key); + if (status != ERROR_SUCCESS) + { + return NULL; + } + + status = RegQueryValueExW (key, value_name, NULL, &type, NULL, &size); + if (status != ERROR_SUCCESS || (type != REG_SZ && type != REG_EXPAND_SZ) || size < sizeof (WCHAR)) + { + RegCloseKey (key); + return NULL; + } + + buffer = g_malloc0 ((gsize) size + sizeof (WCHAR)); + status = RegQueryValueExW (key, value_name, NULL, &type, (LPBYTE) buffer, &size); + if (status == ERROR_SUCCESS) + { + result = wide_to_utf8 (buffer); + if (result) + { + g_strstrip (result); + } + } + + g_free (buffer); + RegCloseKey (key); + return result; +} + +static gboolean +read_registry_dword (HKEY root, const WCHAR *subkey, const WCHAR *value_name, DWORD *value) +{ + HKEY key; + DWORD type = 0; + DWORD size = sizeof (*value); + LONG status; + + status = RegOpenKeyExW (root, subkey, 0, KEY_READ | KEY_WOW64_64KEY, &key); + if (status != ERROR_SUCCESS) + { + return FALSE; + } + + status = RegQueryValueExW (key, value_name, NULL, &type, (LPBYTE) value, &size); + RegCloseKey (key); + return status == ERROR_SUCCESS && type == REG_DWORD; +} + +static const char * +get_native_arch (void) +{ + SYSTEM_INFO info; + + GetNativeSystemInfo (&info); + switch (info.wProcessorArchitecture) + { + case PROCESSOR_ARCHITECTURE_AMD64: + return "x64"; + case PROCESSOR_ARCHITECTURE_ARM64: + return "ARM64"; + case PROCESSOR_ARCHITECTURE_INTEL: + return "x86"; + default: + return "unknown-arch"; + } +} + +static DWORD +get_windows_build_number (void) +{ + typedef LONG (WINAPI *RtlGetVersionFn) (PRTL_OSVERSIONINFOW); + HMODULE ntdll; + RtlGetVersionFn rtl_get_version; + RTL_OSVERSIONINFOW version; + DWORD build = 0; + + ntdll = GetModuleHandleW (L"ntdll.dll"); + if (ntdll) + { + rtl_get_version = (RtlGetVersionFn) GetProcAddress (ntdll, "RtlGetVersion"); + if (rtl_get_version) + { + ZeroMemory (&version, sizeof (version)); + version.dwOSVersionInfoSize = sizeof (version); + if (rtl_get_version (&version) == 0) + { + build = version.dwBuildNumber; + } + } + } + + return build; +} + +static char * +build_os_string (void) +{ + static const WCHAR current_version_key[] = L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"; + char *product; + char *display_version; + char *registry_build; + char *fixed_product = NULL; + char *result; + DWORD ubr = 0; + DWORD build; + guint64 parsed_build = 0; + + product = read_registry_string (HKEY_LOCAL_MACHINE, current_version_key, L"ProductName"); + display_version = read_registry_string (HKEY_LOCAL_MACHINE, current_version_key, L"DisplayVersion"); + registry_build = read_registry_string (HKEY_LOCAL_MACHINE, current_version_key, L"CurrentBuildNumber"); + read_registry_dword (HKEY_LOCAL_MACHINE, current_version_key, L"UBR", &ubr); + + build = get_windows_build_number (); + if (registry_build) + { + parsed_build = g_ascii_strtoull (registry_build, NULL, 10); + if (parsed_build != 0) + { + build = (DWORD) parsed_build; + } + } + + if (!product) + { + product = g_strdup (build >= 22000 ? "Windows 11" : "Windows"); + } + else if (build >= 22000 && g_str_has_prefix (product, "Windows 10")) + { + fixed_product = g_strdup_printf ("Windows 11%s", product + strlen ("Windows 10")); + g_free (product); + product = fixed_product; + } + + if (display_version && build != 0) + { + result = g_strdup_printf ("%s %s" SYSINFO_SEP "build %lu.%lu" SYSINFO_SEP "%s", + product, display_version, (unsigned long) build, (unsigned long) ubr, get_native_arch ()); + } + else if (build != 0) + { + result = g_strdup_printf ("%s" SYSINFO_SEP "build %lu.%lu" SYSINFO_SEP "%s", + product, (unsigned long) build, (unsigned long) ubr, get_native_arch ()); + } + else + { + result = g_strdup_printf ("%s" SYSINFO_SEP "%s", product, get_native_arch ()); + } + + g_free (product); + g_free (display_version); + g_free (registry_build); + return result; +} + +static DWORD +count_physical_cores (void) +{ + DWORD length = 0; + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX buffer; + BYTE *cursor; + BYTE *end; + DWORD cores = 0; + + GetLogicalProcessorInformationEx (RelationProcessorCore, NULL, &length); + if (GetLastError () != ERROR_INSUFFICIENT_BUFFER || length == 0) + { + return 0; + } + + buffer = g_malloc0 (length); + if (!GetLogicalProcessorInformationEx (RelationProcessorCore, buffer, &length)) + { + g_free (buffer); + return 0; + } + + cursor = (BYTE *) buffer; + end = cursor + length; + while (cursor < end) + { + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX info = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX) cursor; + + if (info->Size == 0) + { + break; + } + if (info->Relationship == RelationProcessorCore) + { + cores++; + } + cursor += info->Size; + } + + g_free (buffer); + return cores; +} + +static char * +build_cpu_string (void) +{ + static const WCHAR cpu_key[] = L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0"; + char *name; + DWORD cores; + DWORD threads; + SYSTEM_INFO info; + char *result; + + name = read_registry_string (HKEY_LOCAL_MACHINE, cpu_key, L"ProcessorNameString"); + if (!name) + { + name = g_strdup ("Unknown CPU"); + } + + cores = count_physical_cores (); + threads = GetActiveProcessorCount (ALL_PROCESSOR_GROUPS); + if (threads == 0) + { + GetNativeSystemInfo (&info); + threads = info.dwNumberOfProcessors; + } + if (cores == 0) + { + cores = threads; + } + + result = g_strdup_printf ("%s" SYSINFO_SEP "%luC/%luT", + name, (unsigned long) cores, (unsigned long) threads); + g_free (name); + return result; +} + +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++) + { + if (i != 0) + { + g_string_append (joined, separator); + } + g_string_append (joined, g_ptr_array_index (items, i)); + } + 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++) + { + if (g_ascii_strcasecmp (g_ptr_array_index (items, i), value) == 0) + { + return; + } + } + g_ptr_array_add (items, g_strdup (value)); +} + +static char * +build_gpu_string (void) +{ + IDXGIFactory1 *factory = NULL; + GPtrArray *adapters; + UINT index; + HRESULT hr; + char *result; + + hr = CreateDXGIFactory1 (&IID_IDXGIFactory1, (void **) &factory); + if (FAILED (hr) || !factory) + { + return NULL; + } + + adapters = g_ptr_array_new_with_free_func (g_free); + for (index = 0; ; index++) + { + IDXGIAdapter1 *adapter = NULL; + DXGI_ADAPTER_DESC1 desc; + char *name; + char *label; + + hr = factory->lpVtbl->EnumAdapters1 (factory, index, &adapter); + if (hr == DXGI_ERROR_NOT_FOUND) + { + break; + } + if (FAILED (hr) || !adapter) + { + continue; + } + + ZeroMemory (&desc, sizeof (desc)); + hr = adapter->lpVtbl->GetDesc1 (adapter, &desc); + adapter->lpVtbl->Release (adapter); + if (FAILED (hr) || (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)) + { + continue; + } + + name = wide_to_utf8 (desc.Description); + if (!name) + { + continue; + } + g_strstrip (name); + + if ((guint64) desc.DedicatedVideoMemory >= (512ULL * 1024ULL * 1024ULL)) + { + char *vram = g_format_size_full ((guint64) desc.DedicatedVideoMemory, G_FORMAT_SIZE_IEC_UNITS); + label = g_strdup_printf ("%s (%s VRAM)", name, vram); + g_free (vram); + } + else + { + label = g_strdup (name); + } + + ptr_array_add_unique (adapters, label); + g_free (label); + g_free (name); + } + + factory->lpVtbl->Release (factory); + result = join_ptr_array (adapters, ", "); + g_ptr_array_free (adapters, TRUE); + return result; +} + +static char * +build_sound_string (void) +{ + UINT count; + UINT i; + GPtrArray *devices; + char *result; + + count = waveOutGetNumDevs (); + if (count == 0) + { + return NULL; + } + + devices = g_ptr_array_new_with_free_func (g_free); + for (i = 0; i < count; i++) + { + WAVEOUTCAPSW caps; + char *name; + + ZeroMemory (&caps, sizeof (caps)); + if (waveOutGetDevCapsW ((UINT_PTR) i, &caps, sizeof (caps)) != MMSYSERR_NOERROR) + { + continue; + } + + name = wide_to_utf8 (caps.szPname); + if (name) + { + g_strstrip (name); + ptr_array_add_unique (devices, name); + g_free (name); + } + } + + result = join_ptr_array (devices, ", "); + g_ptr_array_free (devices, TRUE); + 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_gpu_snapshot (void) +{ + if (g_once_init_enter (&gpu_initialized)) + { + snapshot.gpu = build_gpu_string (); + g_once_init_leave (&gpu_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) { - return sysinfo_get_cpu (); -} - -char * -sysinfo_backend_get_memory (void) -{ - /* Memory information is always loaded dynamically since it includes the current amount of free memory */ - return get_memory_info (); + ensure_cpu_snapshot (); + return g_strdup (snapshot.cpu); } char * sysinfo_backend_get_gpu (void) { - return sysinfo_get_gpu (); + ensure_gpu_snapshot (); + return g_strdup (snapshot.gpu); +} + +char * +sysinfo_backend_get_sound (void) +{ + ensure_sound_snapshot (); + return g_strdup (snapshot.sound); } char * @@ -96,43 +581,178 @@ sysinfo_backend_get_chipset (void) } char * -sysinfo_backend_get_os (void) +sysinfo_backend_get_memory (void) { - return sysinfo_get_os (); -} + MEMORYSTATUSEX memory; -static char *get_memory_info (void) -{ - MEMORYSTATUSEX meminfo = { 0 }; - meminfo.dwLength = sizeof (meminfo); - - if (!GlobalMemoryStatusEx (&meminfo)) + ZeroMemory (&memory, sizeof (memory)); + memory.dwLength = sizeof (memory); + if (!GlobalMemoryStatusEx (&memory)) { return NULL; } - return sysinfo_format_memory (meminfo.ullTotalPhys, meminfo.ullAvailPhys); + return format_usage ((guint64) memory.ullTotalPhys, (guint64) memory.ullAvailPhys); } -static const char *sysinfo_detect_toolkit(void) +char * +sysinfo_backend_get_disk (void) { -#if defined(USE_GTK_FRONTEND) - return "GTK3"; -#else - return NULL; -#endif + WCHAR windows_dir[MAX_PATH]; + WCHAR volume_path[MAX_PATH]; + ULARGE_INTEGER available; + ULARGE_INTEGER total; + ULARGE_INTEGER total_free; + char *usage; + char *volume_utf8; + char *result; + + if (GetWindowsDirectoryW (windows_dir, G_N_ELEMENTS (windows_dir)) == 0) + { + return NULL; + } + if (!GetVolumePathNameW (windows_dir, volume_path, G_N_ELEMENTS (volume_path))) + { + return NULL; + } + if (!GetDiskFreeSpaceExW (volume_path, &available, &total, &total_free)) + { + return NULL; + } + + usage = format_usage ((guint64) total.QuadPart, (guint64) available.QuadPart); + if (!usage) + { + return NULL; + } + + volume_utf8 = wide_to_utf8 (volume_path); + if (!volume_utf8) + { + return usage; + } + g_strchomp (volume_utf8); + result = g_strdup_printf ("%s" SYSINFO_SEP "%s", usage, volume_utf8); + g_free (volume_utf8); + g_free (usage); + return result; +} + +char * +sysinfo_backend_get_uptime (void) +{ + return sysinfo_format_uptime ((gint64) (GetTickCount64 () / 1000ULL)); +} + +static gboolean +adapter_looks_virtual (const char *name) +{ + char *lower; + gboolean result; + + if (!name) + { + return FALSE; + } + + lower = g_ascii_strdown (name, -1); + result = strstr (lower, "virtual") != NULL || + strstr (lower, "hyper-v") != NULL || + strstr (lower, "vmware") != NULL || + strstr (lower, "virtualbox") != NULL || + strstr (lower, "tap-windows") != NULL || + strstr (lower, "wireguard") != NULL || + strstr (lower, "vpn") != NULL; + g_free (lower); + return result; +} + +char * +sysinfo_backend_get_network (void) +{ + ULONG size = 16 * 1024; + ULONG status; + IP_ADAPTER_ADDRESSES *addresses; + IP_ADAPTER_ADDRESSES *adapter; + GPtrArray *physical; + GPtrArray *fallback; + GPtrArray *selected; + char *result; + + addresses = g_malloc0 (size); + status = GetAdaptersAddresses (AF_UNSPEC, + GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER, + NULL, addresses, &size); + if (status == ERROR_BUFFER_OVERFLOW) + { + g_free (addresses); + addresses = g_malloc0 (size); + status = GetAdaptersAddresses (AF_UNSPEC, + GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER, + NULL, addresses, &size); + } + if (status != NO_ERROR) + { + g_free (addresses); + return NULL; + } + + physical = g_ptr_array_new_with_free_func (g_free); + fallback = g_ptr_array_new_with_free_func (g_free); + for (adapter = addresses; adapter != NULL; adapter = adapter->Next) + { + char *description; + char *friendly; + const char *name; + const char *type; + char *label; + + if (adapter->OperStatus != IfOperStatusUp || adapter->PhysicalAddressLength == 0) + { + continue; + } + if (adapter->IfType != IF_TYPE_ETHERNET_CSMACD && adapter->IfType != IF_TYPE_IEEE80211) + { + continue; + } + + description = wide_to_utf8 (adapter->Description); + friendly = wide_to_utf8 (adapter->FriendlyName); + name = description && description[0] ? description : friendly; + if (!name) + { + g_free (description); + g_free (friendly); + continue; + } + + type = adapter->IfType == IF_TYPE_IEEE80211 ? "Wi-Fi" : "Ethernet"; + label = g_strdup_printf ("%s: %s", type, name); + ptr_array_add_unique (fallback, label); + if (!adapter_looks_virtual (name)) + { + ptr_array_add_unique (physical, label); + } + + g_free (label); + g_free (description); + g_free (friendly); + } + g_free (addresses); + + selected = physical->len != 0 ? physical : fallback; + result = join_ptr_array (selected, ", "); + g_ptr_array_free (physical, TRUE); + g_ptr_array_free (fallback, TRUE); + return result; } char * sysinfo_backend_get_ui (void) { - const char *toolkit = sysinfo_detect_toolkit(); - - /* On Windows we don't have X11/Wayland. Keep it simple. */ - if (toolkit) - { - return g_strdup_printf ("Windows / %s", toolkit); - } - +#if defined(USE_GTK_FRONTEND) + return g_strdup ("Windows / GTK3"); +#else return g_strdup ("Windows"); +#endif }