/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- * * Copyright (C) 2022-2024 Matthias Klumpp * * Licensed under the GNU Lesser General Public License Version 2.1 * * This library is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 2.1 of the license, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . */ /** * SECTION:as-system-info * @short_description: Read information about the current OS and device. * @include: appstream.h * * This class reads information about the current operating system and device * that AppStream is running on. It can also be used by GUI toolkits to set * data that we can not automatically determine in a toolkit-independent way, * such as screen dimensions. * * AppStream uses this information to verify component relations * (as set in requires/recommends/supports etc. tags). * * See also: #AsComponent */ #include "config.h" #include "as-system-info-private.h" #include #include #ifdef G_OS_WIN32 #include #else #include #endif #include #include #if defined(__linux__) #include #elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__) #include #include #elif defined(__GNU__) #include #include #include #endif #ifdef HAVE_SYSTEMD #include #include #endif #include "as-utils-private.h" #define MB_IN_BYTES (1024 * 1024) typedef struct { gchar *os_id; gchar *os_cid; gchar *os_name; gchar *os_version; gchar *os_homepage; gchar *kernel_name; gchar *kernel_version; gulong memory_total; GPtrArray *modaliases; GHashTable *modalias_to_sysfs; gboolean inputs_scanned; guint input_controls; guint tested_input_controls; gboolean has_gui; gulong display_length_shortest; gulong display_length_longest; #ifdef HAVE_SYSTEMD sd_hwdb *hwdb; #endif } AsSystemInfoPrivate; G_DEFINE_TYPE_WITH_PRIVATE (AsSystemInfo, as_system_info, G_TYPE_OBJECT) #define GET_PRIVATE(o) (as_system_info_get_instance_private (o)) /** * as_system_info_error_quark: * * Return value: An error quark. * * Since: 0.16.0 **/ G_DEFINE_QUARK (as-system-info-error-quark, as_system_info_error) /** * as_chassis_kind_to_string: * @kind: the #AsChassisKind. * * Converts the enumerated value to a text representation. * * Returns: string version of @kind * * Since: 1.0.0 **/ const gchar * as_chassis_kind_to_string (AsChassisKind kind) { if (kind == AS_CHASSIS_KIND_DESKTOP) return "desktop"; if (kind == AS_CHASSIS_KIND_LAPTOP) return "laptop"; if (kind == AS_CHASSIS_KIND_SERVER) return "server"; if (kind == AS_CHASSIS_KIND_TABLET) return "tablet"; if (kind == AS_CHASSIS_KIND_HANDSET) return "handset"; return "unknown"; } /** * as_chassis_kind_from_string: * @kind_str: the string. * * Converts the text representation to an enumerated value. * * Returns: a #AsChassisKind or %AS_CHASSIS_KIND_UNKNOWN for unknown * * Since: 1.0.0 **/ AsChassisKind as_chassis_kind_from_string (const gchar *kind_str) { if (as_str_equal0 (kind_str, "desktop")) return AS_CHASSIS_KIND_DESKTOP; if (as_str_equal0 (kind_str, "laptop")) return AS_CHASSIS_KIND_LAPTOP; if (as_str_equal0 (kind_str, "server")) return AS_CHASSIS_KIND_SERVER; if (as_str_equal0 (kind_str, "tablet")) return AS_CHASSIS_KIND_TABLET; if (as_str_equal0 (kind_str, "handset")) return AS_CHASSIS_KIND_HANDSET; return AS_CHASSIS_KIND_UNKNOWN; } static void as_system_info_init (AsSystemInfo *sysinfo) { AsSystemInfoPrivate *priv = GET_PRIVATE (sysinfo); priv->modaliases = g_ptr_array_new (); priv->modalias_to_sysfs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); /* we assume a GUI is present by default */ priv->has_gui = TRUE; } static void as_system_info_finalize (GObject *object) { AsSystemInfo *sysinfo = AS_SYSTEM_INFO (object); AsSystemInfoPrivate *priv = GET_PRIVATE (sysinfo); g_free (priv->os_id); g_free (priv->os_cid); g_free (priv->os_name); g_free (priv->os_version); g_free (priv->os_homepage); g_free (priv->kernel_name); g_free (priv->kernel_version); g_ptr_array_unref (priv->modaliases); g_hash_table_unref (priv->modalias_to_sysfs); #ifdef HAVE_SYSTEMD if (priv->hwdb != NULL) sd_hwdb_unref (priv->hwdb); #endif G_OBJECT_CLASS (as_system_info_parent_class)->finalize (object); } static void as_system_info_class_init (AsSystemInfoClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = as_system_info_finalize; } /** * as_system_info_load_os_release: */ void as_system_info_load_os_release (AsSystemInfo *sysinfo, const gchar *os_release_fname) { AsSystemInfoPrivate *priv = GET_PRIVATE (sysinfo); g_autoptr(GFile) f = NULL; g_autoptr(GError) error = NULL; gchar *line; /* skip if we already loaded data */ if (priv->os_id != NULL) return; if (os_release_fname == NULL) { if (g_file_test ("/etc/os-release", G_FILE_TEST_EXISTS)) os_release_fname = "/etc/os-release"; else os_release_fname = "/usr/lib/os-release"; } /* get details about the distribution we are running on */ f = g_file_new_for_path (os_release_fname); if (g_file_query_exists (f, NULL)) { g_autoptr(GDataInputStream) dis = NULL; g_autoptr(GFileInputStream) fis = NULL; fis = g_file_read (f, NULL, &error); if (error != NULL) { g_warning ("Unable to read file '%s': %s", os_release_fname, error->message); return; } dis = g_data_input_stream_new ((GInputStream *) fis); while ((line = g_data_input_stream_read_line (dis, NULL, NULL, &error)) != NULL) { g_auto(GStrv) data = NULL; g_autofree gchar *dvalue = NULL; if (error != NULL) { g_warning ("Unable to read line in file '%s': %s", os_release_fname, error->message); g_free (line); return; } data = g_strsplit (line, "=", 2); if (g_strv_length (data) != 2) { g_free (line); continue; } dvalue = g_strdup (data[1]); if (g_str_has_prefix (dvalue, "\"")) { gchar *tmp; tmp = g_strndup (dvalue + 1, strlen (dvalue) - 2); g_free (dvalue); dvalue = tmp; } if (g_strcmp0 (data[0], "ID") == 0) { g_free (priv->os_id); priv->os_id = g_steal_pointer (&dvalue); } else if (g_strcmp0 (data[0], "NAME") == 0) { g_free (priv->os_name); priv->os_name = g_steal_pointer (&dvalue); } else if (g_strcmp0 (data[0], "VERSION_ID") == 0) { g_free (priv->os_version); priv->os_version = g_steal_pointer (&dvalue); } else if (g_strcmp0 (data[0], "HOME_URL") == 0) { g_free (priv->os_homepage); priv->os_homepage = g_steal_pointer (&dvalue); } g_free (line); } } } /** * as_system_info_get_os_id: * @sysinfo: a #AsSystemInfo instance. * * Get the ID of the current operating system. * * Returns: the current OS ID. */ const gchar * as_system_info_get_os_id (AsSystemInfo *sysinfo) { AsSystemInfoPrivate *priv = GET_PRIVATE (sysinfo); as_system_info_load_os_release (sysinfo, NULL); return priv->os_id; } /** * as_system_info_get_os_cid: * @sysinfo: a #AsSystemInfo instance. * * Get the AppStream component ID of the current operating system. * * Returns: the component ID of the current OS. */ const gchar * as_system_info_get_os_cid (AsSystemInfo *sysinfo) { AsSystemInfoPrivate *priv = GET_PRIVATE (sysinfo); as_system_info_load_os_release (sysinfo, NULL); if (priv->os_cid != NULL) return priv->os_cid; if (priv->os_homepage == NULL) { priv->os_cid = g_strdup (priv->os_id); return priv->os_cid; } priv->os_cid = as_utils_dns_to_rdns (priv->os_homepage, priv->os_id); if (priv->os_cid == NULL) return priv->os_id; return priv->os_cid; } /** * as_system_info_get_os_name: * @sysinfo: a #AsSystemInfo instance. * * Get the humen-readable name of the current operating system. * * Returns: the name of the current OS. */ const gchar * as_system_info_get_os_name (AsSystemInfo *sysinfo) { AsSystemInfoPrivate *priv = GET_PRIVATE (sysinfo); as_system_info_load_os_release (sysinfo, NULL); return priv->os_name; } /** * as_system_info_get_os_version: * @sysinfo: a #AsSystemInfo instance. * * Get the version string of the current operating system. * * Returns: the version of the current OS. */ const gchar * as_system_info_get_os_version (AsSystemInfo *sysinfo) { AsSystemInfoPrivate *priv = GET_PRIVATE (sysinfo); as_system_info_load_os_release (sysinfo, NULL); return priv->os_version; } /** * as_system_info_get_os_homepage: * @sysinfo: a #AsSystemInfo instance. * * Get the homepage URL of the current operating system. * * Returns: the homepage of the current OS. */ const gchar * as_system_info_get_os_homepage (AsSystemInfo *sysinfo) { AsSystemInfoPrivate *priv = GET_PRIVATE (sysinfo); as_system_info_load_os_release (sysinfo, NULL); return priv->os_homepage; } /** * as_system_info_read_kernel_details: */ static void as_system_info_read_kernel_details (AsSystemInfo *sysinfo) { #ifdef G_OS_WIN32 g_warning ("Reading Windows version is not supported"); #else AsSystemInfoPrivate *priv = GET_PRIVATE (sysinfo); struct utsname utsbuf; gchar *tmp; if (priv->kernel_name != NULL) return; if (uname (&utsbuf) != 0) { g_warning ("Unable to read kernel information via uname: %s", g_strerror (errno)); return; } g_free (priv->kernel_name); priv->kernel_name = g_strdup (utsbuf.sysname); tmp = g_strrstr (utsbuf.release, "-"); if (tmp != NULL) tmp[0] = '\0'; g_free (priv->kernel_version); priv->kernel_version = g_strdup (utsbuf.release); #endif } /** * as_system_info_get_kernel_name: * @sysinfo: a #AsSystemInfo instance. * * Get the name of the current kernel, e.g. "Linux" * * Returns: the current OS kernel name */ const gchar * as_system_info_get_kernel_name (AsSystemInfo *sysinfo) { AsSystemInfoPrivate *priv = GET_PRIVATE (sysinfo); as_system_info_read_kernel_details (sysinfo); return priv->kernel_name; } /** * as_system_info_get_kernel_version: * @sysinfo: a #AsSystemInfo instance. * * Get the version of the current kernel, e.g. "6.2.0-2" * * Returns: the current kernel version */ const gchar * as_system_info_get_kernel_version (AsSystemInfo *sysinfo) { AsSystemInfoPrivate *priv = GET_PRIVATE (sysinfo); as_system_info_read_kernel_details (sysinfo); return priv->kernel_version; } /** * as_system_info_set_kernel: * @sysinfo: a #AsSystemInfo instance. * @name: the kernel name. * @version: the kernel version. * * Override the kernel data. * * Internal API. */ void as_system_info_set_kernel (AsSystemInfo *sysinfo, const gchar *name, const gchar *version) { AsSystemInfoPrivate *priv = GET_PRIVATE (sysinfo); as_assign_string_safe (priv->kernel_name, name); as_assign_string_safe (priv->kernel_version, version); } /** * as_get_physical_memory_total: */ static gulong as_get_physical_memory_total (void) { #if defined(__linux__) struct sysinfo si = { 0 }; sysinfo (&si); if (si.mem_unit > 0) return (si.totalram * si.mem_unit) / MB_IN_BYTES; return 0; #elif defined(__FreeBSD__) || defined(__NetBSD__) unsigned long physmem; size_t len = sizeof (physmem); int mib[2] = { CTL_HW, HW_PHYSMEM }; if (sysctl (mib, 2, &physmem, &len, NULL, 0) == -1) { g_warning ("Unable to determine physical memory size: %s", g_strerror (errno)); return 0; } return physmem / MB_IN_BYTES; #elif defined(__APPLE__) unsigned long physmem; size_t len = sizeof (physmem); /* macOS needs HW_MEMSIZE, as HW_PHYSMEM contrary to its name will not give * the physical memory size but available memory size */ int mib[2] = { CTL_HW, HW_MEMSIZE }; if (sysctl (mib, 2, &physmem, &len, NULL, 0) == -1) { g_warning ("Unable to determine physical memory size: %s", g_strerror (errno)); return 0; } return physmem / MB_IN_BYTES; #elif defined(G_OS_WIN32) MEMORYSTATUSEX statex; statex.dwLength = sizeof (statex); GlobalMemoryStatusEx (&statex); return statex.ullTotalPhys / (1024 * 1024); #elif defined(__GNU__) host_basic_info_data_t hbi; mach_msg_type_number_t cnt = HOST_BASIC_INFO_COUNT; int err = host_info (mach_host_self (), HOST_BASIC_INFO, (host_info_t) &hbi, &cnt); if (err != 0) { g_warning ("Unable to determine physical memory size: %s", g_strerror (err)); return 0; } return hbi.memory_size / MB_IN_BYTES; #else #error "Implementation of as_get_physical_memory_total() missing for this OS." #endif } /** * as_system_info_get_memory_total: * @sysinfo: a #AsSystemInfo instance. * * Get the current total amount of physical memory in MiB. * * Returns: the current total amount of usable memory in MiB */ gulong as_system_info_get_memory_total (AsSystemInfo *sysinfo) { AsSystemInfoPrivate *priv = GET_PRIVATE (sysinfo); if (priv->memory_total == 0) priv->memory_total = as_get_physical_memory_total (); return priv->memory_total; } /** * as_system_info_set_memory_total: * @sysinfo: a #AsSystemInfo instance. * @size_mib: the size to set. * * Override the memory size. * * Internal API. */ void as_system_info_set_memory_total (AsSystemInfo *sysinfo, gulong size_mib) { AsSystemInfoPrivate *priv = GET_PRIVATE (sysinfo); priv->memory_total = size_mib; } #if defined(__linux__) /** * as_system_info_populate_modaliases_map_cb: */ static gboolean as_system_info_populate_modaliases_map_cb (AsSystemInfo *sysinfo, const gchar *root_path) { AsSystemInfoPrivate *priv = GET_PRIVATE (sysinfo); DIR *dir; struct dirent *ent; if ((dir = opendir (root_path)) != NULL) { while ((ent = readdir (dir)) != NULL) { if (ent->d_type == DT_LNK) continue; if (ent->d_type == DT_DIR) { g_autofree gchar *subdir_path = g_build_filename (root_path, ent->d_name, NULL); if (!as_str_equal0 (ent->d_name, ".") && !as_str_equal0 (ent->d_name, "..")) as_system_info_populate_modaliases_map_cb (sysinfo, subdir_path); } else { if (as_str_equal0 (ent->d_name, "modalias")) { gchar *contents = NULL; g_autoptr(GError) read_error = NULL; g_autofree gchar *path = g_build_filename (root_path, ent->d_name, NULL); if (!g_file_get_contents (path, &contents, NULL, &read_error)) { g_warning ( "Error while reading modalias file %s: %s", path, read_error->message); closedir (dir); return FALSE; } contents = as_strstripnl (contents); g_hash_table_insert (priv->modalias_to_sysfs, contents, g_path_get_dirname (path)); } } } closedir (dir); } else { g_warning ("Error while searching for modalias entries in %s: %s", root_path, g_strerror (errno)); } return TRUE; } #endif /** * as_system_info_populate_modaliases: */ static void as_system_info_populate_modaliases (AsSystemInfo *sysinfo) { #if defined(__linux__) AsSystemInfoPrivate *priv = GET_PRIVATE (sysinfo); GHashTableIter ht_iter; gpointer ht_key; /* we never want to run this multiple times */ if (priv->modaliases->len != 0) return; as_system_info_populate_modaliases_map_cb (sysinfo, "/sys/devices"); g_hash_table_iter_init (&ht_iter, priv->modalias_to_sysfs); while (g_hash_table_iter_next (&ht_iter, &ht_key, NULL)) g_ptr_array_add (priv->modaliases, ht_key); #endif } /** * as_system_info_get_modaliases: * @sysinfo: a #AsSystemInfo instance. * * Get a list of modaliases for all the hardware on this system that has them. * * Returns: (transfer none) (element-type utf8): a list of modaliases on the system. */ GPtrArray * as_system_info_get_modaliases (AsSystemInfo *sysinfo) { AsSystemInfoPrivate *priv = GET_PRIVATE (sysinfo); as_system_info_populate_modaliases (sysinfo); return priv->modaliases; } /** * as_system_info_modalias_to_syspath: * @sysinfo: a #AsSystemInfo instance. * @modalias: the modalias value to resolve. * * Receive a path in /sys for the devices with the given modalias. * * Returns: the syspath, or %NULL if none was found. */ const gchar * as_system_info_modalias_to_syspath (AsSystemInfo *sysinfo, const gchar *modalias) { AsSystemInfoPrivate *priv = GET_PRIVATE (sysinfo); as_system_info_populate_modaliases (sysinfo); return g_hash_table_lookup (priv->modalias_to_sysfs, modalias); } /** * as_system_info_has_device_matching_modalias: * @sysinfo: a #AsSystemInfo instance. * @modalias_glob: the modalias value to to look for, may contain wildcards. * * Check if there is a device on this system that matches the given modalias glob. * * Returns: %TRUE if a matching device was found. */ gboolean as_system_info_has_device_matching_modalias (AsSystemInfo *sysinfo, const gchar *modalias_glob) { AsSystemInfoPrivate *priv = GET_PRIVATE (sysinfo); as_system_info_populate_modaliases (sysinfo); for (guint i = 0; i < priv->modaliases->len; i++) { const gchar *modalias = (const gchar *) g_ptr_array_index (priv->modaliases, i); if (g_strcmp0 (modalias, modalias_glob) == 0) return TRUE; if (g_pattern_match_simple (modalias_glob, modalias)) return TRUE; } return FALSE; } /** * as_system_info_get_device_name_from_syspath: */ static gchar * as_system_info_get_device_name_from_syspath (AsSystemInfo *sysinfo, const gchar *syspath, const gchar *modalias, gboolean allow_fallback, GError **error) { #ifdef HAVE_SYSTEMD __attribute__((cleanup (sd_device_unrefp))) sd_device *device = NULL; const gchar *key, *value; gint r; const gchar *device_vendor = NULL; const gchar *device_model = NULL; const gchar *usb_class = NULL; const gchar *driver = NULL; gchar *result = NULL; r = sd_device_new_from_syspath (&device, syspath); if (r < 0) { g_set_error (error, AS_SYSTEM_INFO_ERROR, AS_SYSTEM_INFO_ERROR_FAILED, "Failure to read device information for %s: %s", syspath, g_strerror (errno)); return NULL; } for (key = sd_device_get_property_first (device, &value); key; key = sd_device_get_property_next (device, &value)) { if (g_strstr_len (key, -1, "_VENDOR") != NULL) { if (g_strstr_len (key, -1, "VENDOR_ID") == NULL && !g_str_has_suffix (key, "_ENC")) device_vendor = value; } else if (g_strstr_len (key, -1, "_MODEL") != NULL) { if (g_strstr_len (key, -1, "MODEL_ID") == NULL && !g_str_has_suffix (key, "_ENC")) device_model = value; } else if (as_str_equal0 (key, "DRIVER")) driver = value; else if (g_strstr_len (key, -1, "_USB_CLASS")) usb_class = value; } if (device_vendor != NULL) { if (device_model != NULL) result = g_strdup_printf ("%s - %s", device_vendor, device_model); else if (usb_class != NULL) result = g_strdup_printf ("%s - %s", device_vendor, usb_class); } if (result == NULL) { if (allow_fallback) { if (driver != NULL) result = g_strdup (driver); else result = g_strdup (modalias); } else { g_set_error (error, AS_SYSTEM_INFO_ERROR, AS_SYSTEM_INFO_ERROR_NOT_FOUND, "Unable to find good human-readable description for device %s", modalias); } } return result; #else g_set_error_literal ( error, AS_SYSTEM_INFO_ERROR, AS_SYSTEM_INFO_ERROR_FAILED, "Unable to determine device name: AppStream was built without systemd-udevd support."); return NULL; #endif } /** * as_system_info_get_device_name_from_hwdb: */ static gchar * as_system_info_get_device_name_from_hwdb (AsSystemInfo *sysinfo, const gchar *modalias_glob, gboolean allow_fallback, GError **error) { #ifdef HAVE_SYSTEMD AsSystemInfoPrivate *priv = GET_PRIVATE (sysinfo); gint ret = 0; const gchar *device_vendor = NULL; const gchar *device_model = NULL; if (priv->hwdb == NULL) { ret = sd_hwdb_new (&priv->hwdb); if (ret < 0) { g_set_error (error, AS_SYSTEM_INFO_ERROR, AS_SYSTEM_INFO_ERROR_FAILED, "Unable to open hardware database: %s", g_strerror (ret)); return NULL; } } sd_hwdb_get (priv->hwdb, modalias_glob, "ID_VENDOR_FROM_DATABASE", &device_vendor); sd_hwdb_get (priv->hwdb, modalias_glob, "ID_MODEL_FROM_DATABASE", &device_model); if (device_vendor != NULL && device_model != NULL) return g_strdup_printf ("%s - %s", device_vendor, device_model); if (allow_fallback) return g_strdup (modalias_glob); g_set_error (error, AS_SYSTEM_INFO_ERROR, AS_SYSTEM_INFO_ERROR_NOT_FOUND, "Unable to find good human-readable description for device %s", modalias_glob); return NULL; #else g_set_error_literal ( error, AS_SYSTEM_INFO_ERROR, AS_SYSTEM_INFO_ERROR_FAILED, "Unable to determine device name: AppStream was built without systemd-hwdb support."); return NULL; #endif } /** * as_system_info_get_device_name_for_modalias: * @sysinfo: a #AsSystemInfo instance. * @modalias: the modalias value to resolve (may contain wildcards). * @allow_fallback: fall back to low-quality data if no better information is available * @error: a #GError * * Return a human readable device name for the given modalias. * Will return the modalias again if no device name could be found, * and returns %NULL on error. * If @allow_fallback is set to %FALSE, this function will return %NULL and error * %AS_SYSTEM_INFO_ERROR_NOT_FOUND in case no suitable description could be found. * * Returns: a human-readable device name, or %NULL on error. */ gchar * as_system_info_get_device_name_for_modalias (AsSystemInfo *sysinfo, const gchar *modalias, gboolean allow_fallback, GError **error) { AsSystemInfoPrivate *priv = GET_PRIVATE (sysinfo); const gchar *syspath; syspath = g_hash_table_lookup (priv->modalias_to_sysfs, modalias); if (syspath == NULL) return as_system_info_get_device_name_from_hwdb (sysinfo, modalias, allow_fallback, error); else return as_system_info_get_device_name_from_syspath (sysinfo, syspath, modalias, allow_fallback, error); } /** * as_system_info_has_device_with_property: */ static AsCheckResult as_system_info_has_device_with_property (AsSystemInfo *sysinfo, const gchar *prop_key, const gchar *prop_value, GError **error) { #ifdef HAVE_SYSTEMD __attribute__((cleanup (sd_device_enumerator_unrefp))) sd_device_enumerator *e = NULL; gint r; r = sd_device_enumerator_new (&e); if (r < 0) { g_set_error (error, AS_SYSTEM_INFO_ERROR, AS_SYSTEM_INFO_ERROR_FAILED, "Unable to enumerate devices: %s", g_strerror (r)); return AS_CHECK_RESULT_ERROR; } r = sd_device_enumerator_allow_uninitialized (e); if (r < 0) { g_set_error (error, AS_SYSTEM_INFO_ERROR, AS_SYSTEM_INFO_ERROR_FAILED, "Unable to enumerate uninitialized devices: %s", g_strerror (r)); return AS_CHECK_RESULT_ERROR; } r = sd_device_enumerator_add_match_property (e, prop_key, prop_value); if (r < 0) { g_set_error (error, AS_SYSTEM_INFO_ERROR, AS_SYSTEM_INFO_ERROR_FAILED, "Unable to enumerate devices by property: %s", g_strerror (r)); return AS_CHECK_RESULT_ERROR; } return sd_device_enumerator_get_device_first (e) != NULL ? AS_CHECK_RESULT_TRUE : AS_CHECK_RESULT_FALSE; #else g_set_error_literal ( error, AS_SYSTEM_INFO_ERROR, AS_SYSTEM_INFO_ERROR_FAILED, "Unable to look for input device: AppStream was built without systemd-udevd support."); return AS_CHECK_RESULT_ERROR; #endif } /** * as_system_info_mark_input_control_status: * * Mark an input control as set to a specific value. */ static void as_system_info_mark_input_control_status (AsSystemInfo *sysinfo, AsControlKind kind, gboolean found) { AsSystemInfoPrivate *priv = GET_PRIVATE (sysinfo); as_flags_add (priv->tested_input_controls, (1 << kind)); if (found) as_flags_add (priv->input_controls, (1 << kind)); } /** * as_system_info_find_input_controls: */ static gboolean as_system_info_find_input_controls (AsSystemInfo *sysinfo, GError **error) { AsSystemInfoPrivate *priv = GET_PRIVATE (sysinfo); AsCheckResult res; /* skip scan if we have already tried it once */ if (priv->inputs_scanned) return TRUE; /* console input is always present, unless the API user explicitly forbids it */ as_system_info_mark_input_control_status (sysinfo, AS_CONTROL_KIND_CONSOLE, TRUE); priv->inputs_scanned = TRUE; /* autodetect all inputs we can */ res = as_system_info_has_device_with_property (sysinfo, "ID_INPUT_KEYBOARD", "1", error); if (res == AS_CHECK_RESULT_ERROR) return FALSE; as_system_info_mark_input_control_status (sysinfo, AS_CONTROL_KIND_KEYBOARD, res == AS_CHECK_RESULT_TRUE); res = as_system_info_has_device_with_property (sysinfo, "ID_INPUT_MOUSE", "1", error); if (res == AS_CHECK_RESULT_ERROR) return FALSE; as_system_info_mark_input_control_status (sysinfo, AS_CONTROL_KIND_POINTING, res == AS_CHECK_RESULT_TRUE); if (res != AS_CHECK_RESULT_TRUE) { res = as_system_info_has_device_with_property (sysinfo, "ID_INPUT_TOUCHPAD", "1", error); if (res == AS_CHECK_RESULT_ERROR) return FALSE; as_system_info_mark_input_control_status (sysinfo, AS_CONTROL_KIND_POINTING, res == AS_CHECK_RESULT_TRUE); } res = as_system_info_has_device_with_property (sysinfo, "ID_INPUT_JOYSTICK", "1", error); if (res == AS_CHECK_RESULT_ERROR) return FALSE; as_system_info_mark_input_control_status (sysinfo, AS_CONTROL_KIND_GAMEPAD, res == AS_CHECK_RESULT_TRUE); res = as_system_info_has_device_with_property (sysinfo, "ID_INPUT_TABLET", "1", error); if (res == AS_CHECK_RESULT_ERROR) return FALSE; as_system_info_mark_input_control_status (sysinfo, AS_CONTROL_KIND_TABLET, res == AS_CHECK_RESULT_TRUE); res = as_system_info_has_device_with_property (sysinfo, "ID_INPUT_TOUCHSCREEN", "1", error); if (res == AS_CHECK_RESULT_ERROR) return FALSE; as_system_info_mark_input_control_status (sysinfo, AS_CONTROL_KIND_TOUCH, res == AS_CHECK_RESULT_TRUE); return TRUE; } /** * as_system_info_has_input_control: * @sysinfo: a #AsSystemInfo instance. * @kind: the #AsControlKind to test for. * @error: a #GError * * Test if the current system has a specific user input control method. * Returns %AS_CHECK_RESULT_UNKNOWN if we could not test for an input control method, * %AS_CHECK_RESULT_ERROR on error and %AS_CHECK_RESULT_FALSE if the control was not found. * * Returns: %AS_CHECK_RESULT_TRUE if control was found */ AsCheckResult as_system_info_has_input_control (AsSystemInfo *sysinfo, AsControlKind kind, GError **error) { AsSystemInfoPrivate *priv = GET_PRIVATE (sysinfo); g_return_val_if_fail (kind < AS_CONTROL_KIND_LAST, AS_CHECK_RESULT_UNKNOWN); g_return_val_if_fail (kind != AS_CONTROL_KIND_UNKNOWN, AS_CHECK_RESULT_UNKNOWN); if (!as_system_info_find_input_controls (sysinfo, error)) return AS_CHECK_RESULT_ERROR; /* if we tried to autodetect and haven't found a device, return FALSE, * but if we didn't even try to autodetect an input control, return UNKNOWN */ if (as_flags_contains (priv->input_controls, (1 << kind))) return AS_CHECK_RESULT_TRUE; else if (!as_flags_contains (priv->tested_input_controls, (1 << kind))) return AS_CHECK_RESULT_UNKNOWN; return AS_CHECK_RESULT_FALSE; } /** * as_system_info_set_input_control: * @sysinfo: a #AsSystemInfo instance. * @kind: the #AsControlKind to set. * @found: %TRUE if the control should be marked as found. * * Explicitly mark a user input control as present or not present on this system. */ void as_system_info_set_input_control (AsSystemInfo *sysinfo, AsControlKind kind, gboolean found) { g_return_if_fail (kind < AS_CONTROL_KIND_LAST); g_return_if_fail (kind != AS_CONTROL_KIND_UNKNOWN); as_system_info_find_input_controls (sysinfo, NULL); as_system_info_mark_input_control_status (sysinfo, kind, found); } /** * as_system_info_get_gui_available: * @sysinfo: a #AsSystemInfo instance. * * Check whether graphical applications can be displayed via X11 or Wayland. * * Returns: %TRUE if graphical applications can be displayed. */ gboolean as_system_info_get_gui_available (AsSystemInfo *sysinfo) { AsSystemInfoPrivate *priv = GET_PRIVATE (sysinfo); return priv->has_gui; } /** * as_system_info_set_gui_available: * @sysinfo: a #AsSystemInfo instance. * @available: %TRUE if GUI is available. * * Set whether this system has a GUI / desktop environment available. */ void as_system_info_set_gui_available (AsSystemInfo *sysinfo, gboolean available) { AsSystemInfoPrivate *priv = GET_PRIVATE (sysinfo); priv->has_gui = available; } /** * as_system_info_get_display_length: * @sysinfo: a #AsSystemInfo instance. * @side: the #AsDisplaySideKind to select. * * Get the current display length for the given side kind. * If the display size is unknown, this function will return 0. * * Returns: the display size in logical pixels. */ gulong as_system_info_get_display_length (AsSystemInfo *sysinfo, AsDisplaySideKind side) { AsSystemInfoPrivate *priv = GET_PRIVATE (sysinfo); g_return_val_if_fail (side < AS_DISPLAY_SIDE_KIND_LAST, 0); g_return_val_if_fail (side != AS_DISPLAY_SIDE_KIND_UNKNOWN, 0); if (side == AS_DISPLAY_SIDE_KIND_LONGEST) return priv->display_length_longest; return priv->display_length_shortest; } /** * as_system_info_set_display_length: * @sysinfo: a #AsSystemInfo instance. * @side: the #AsDisplaySideKind to select. * @value_dip: the length value in device-independt pixels. * * Set the current display length for the given side kind. * The size needs to be in device-independent pixels, see the * AppStream documentation for more information: * https://www.freedesktop.org/software/appstream/docs/chap-Metadata.html#tag-relations-display_length */ void as_system_info_set_display_length (AsSystemInfo *sysinfo, AsDisplaySideKind side, gulong value_dip) { AsSystemInfoPrivate *priv = GET_PRIVATE (sysinfo); g_return_if_fail (side < AS_DISPLAY_SIDE_KIND_LAST); g_return_if_fail (side != AS_DISPLAY_SIDE_KIND_UNKNOWN); if (side == AS_DISPLAY_SIDE_KIND_LONGEST) priv->display_length_longest = value_dip; priv->display_length_shortest = value_dip; } /** * as_system_info_new: * * Creates a new #AsSystemInfo. * * Returns: (transfer full): a #AsSystemInfo * * Since: 0.10 **/ AsSystemInfo * as_system_info_new (void) { AsSystemInfo *sysinfo; sysinfo = g_object_new (AS_TYPE_SYSTEM_INFO, NULL); return AS_SYSTEM_INFO (sysinfo); } /** * as_system_info_new_template_for_chassis: * @chassis: the #AsChassisKind to generate a template for * @error: a #GError * * Creates a new #AsSystemInfo that can be used as a mock system * for the given chassis type, reading (mostly) no information * from the current system. * * This function will try to generate a device most representative of * the given chassis class. This can be useful when testing compatibility * for other devices on a different device that is not of the same chassis * type as the one to be tested. * * Returns: (transfer full): an #AsSystemInfo template * * Since: 1.0.0 **/ AsSystemInfo * as_system_info_new_template_for_chassis (AsChassisKind chassis, GError **error) { AsSystemInfoPrivate *priv = NULL; AsSystemInfo *sysinfo = as_system_info_new (); priv = GET_PRIVATE (sysinfo); /* the template sysinfo is not supposed to scan the curent system's devices */ priv->inputs_scanned = TRUE; /* we assume all input controls have been tested */ priv->tested_input_controls = G_MAXUINT; priv->input_controls = 0; /* just assume 8GiB memory by default */ as_system_info_set_memory_total (sysinfo, 8192); /* assume X11/Wayland is available by default */ as_system_info_set_gui_available (sysinfo, TRUE); /* set default for the individual chassis types */ if (chassis == AS_CHASSIS_KIND_DESKTOP || chassis == AS_CHASSIS_KIND_LAPTOP) { as_system_info_set_display_length (sysinfo, AS_DISPLAY_SIDE_KIND_SHORTEST, 800); as_system_info_set_display_length (sysinfo, AS_DISPLAY_SIDE_KIND_LONGEST, 1280); as_system_info_mark_input_control_status (sysinfo, AS_CONTROL_KIND_POINTING, TRUE); as_system_info_mark_input_control_status (sysinfo, AS_CONTROL_KIND_KEYBOARD, TRUE); as_system_info_mark_input_control_status (sysinfo, AS_CONTROL_KIND_CONSOLE, TRUE); return sysinfo; } if (chassis == AS_CHASSIS_KIND_SERVER) { as_system_info_mark_input_control_status (sysinfo, AS_CONTROL_KIND_KEYBOARD, TRUE); as_system_info_mark_input_control_status (sysinfo, AS_CONTROL_KIND_CONSOLE, TRUE); as_system_info_set_gui_available (sysinfo, FALSE); return sysinfo; } if (chassis == AS_CHASSIS_KIND_TABLET) { as_system_info_set_memory_total (sysinfo, 4096); as_system_info_set_display_length (sysinfo, AS_DISPLAY_SIDE_KIND_SHORTEST, 600); as_system_info_set_display_length (sysinfo, AS_DISPLAY_SIDE_KIND_LONGEST, 1024); as_system_info_mark_input_control_status (sysinfo, AS_CONTROL_KIND_TOUCH, TRUE); return sysinfo; } if (chassis == AS_CHASSIS_KIND_HANDSET) { as_system_info_set_memory_total (sysinfo, 4096); as_system_info_set_display_length (sysinfo, AS_DISPLAY_SIDE_KIND_SHORTEST, 320); as_system_info_set_display_length (sysinfo, AS_DISPLAY_SIDE_KIND_LONGEST, 480); as_system_info_mark_input_control_status (sysinfo, AS_CONTROL_KIND_TOUCH, TRUE); return sysinfo; } g_set_error (error, AS_SYSTEM_INFO_ERROR, AS_SYSTEM_INFO_ERROR_FAILED, "Unable to generate system info template for chassis type: %s", as_chassis_kind_to_string (chassis)); g_object_unref (sysinfo); return NULL; } /** * as_get_current_distro_component_id: * * Returns the component-ID of the current distribution based on contents * of the `/etc/os-release` file. * This function is a shorthand for %as_distro_details_get_cid */ gchar * as_get_current_distro_component_id (void) { g_autoptr(AsSystemInfo) sysinfo = as_system_info_new (); return g_strdup (as_system_info_get_os_cid (sysinfo)); }