diff options
author | Aleksander Morgado <aleksander@aleksander.es> | 2017-03-03 14:44:46 +0100 |
---|---|---|
committer | Aleksander Morgado <aleksander@aleksander.es> | 2017-05-30 11:29:08 +0200 |
commit | 7750b8aee3b5bfa18b328769ae111108d071784a (patch) | |
tree | db6d53eeba541b89a03bb7caccfe6b81d3032e56 | |
parent | db0e81c69fdfd1da7bb798c4034329eb24a830bc (diff) | |
download | libqmi-aleksander/qmi-radio-monitor.tar.gz |
qmi-radio-monitor: imported from the 'mobile radio monitor' projectaleksander/qmi-radio-monitor
The QMI radio monitor is now GPLv2+.
https://sigquit.wordpress.com/2013/09/17/mobile-radio-monitor/
https://bitbucket.org/aleksander0m/mobile-radio-monitor
28 files changed, 7864 insertions, 48 deletions
@@ -55,6 +55,13 @@ Makefile.in /src/qmi-firmware-update/qmi-firmware-update +/src/qmi-radio-monitor/data/org.freedesktop.QmiRadioMonitor.pkexec.policy +/src/qmi-radio-monitor/qmi-radio-monitor +/src/qmi-radio-monitor/qrm-enum-types.* +/src/qmi-radio-monitor/qrm-error-quarks.* +/src/qmi-radio-monitor/qrm-error-types.* +/src/qmi-radio-monitor/qrm-resources.* + /build-aux/qmi-codegen/*.pyc /docs/reference/libqmi-glib/version.xml diff --git a/configure.ac b/configure.ac index 162d5ae5..1523a3c4 100644 --- a/configure.ac +++ b/configure.ac @@ -67,63 +67,87 @@ AC_SUBST(QMI_GLIB_LT_AGE) dnl Required dependency versions GLIB_VERSION=2.36 GUDEV_VERSION=147 +GTK_VERSION=3.10 -dnl GLib, GIO... -PKG_CHECK_MODULES(GLIB, - glib-2.0 >= $GLIB_VERSION - gobject-2.0 - gio-2.0 - gio-unix-2.0) +PKG_CHECK_MODULES(GLIB, [glib-2.0 >= $GLIB_VERSION gobject-2.0 gio-2.0 gio-unix-2.0]) AC_SUBST(GLIB_CFLAGS) AC_SUBST(GLIB_LIBS) GLIB_MKENUMS=`$PKG_CONFIG --variable=glib_mkenums glib-2.0` AC_SUBST(GLIB_MKENUMS) +AC_PATH_PROG(GLIB_COMPILE_RESOURCES, [glib-compile-resources]) +AC_SUBST(GLIB_COMPILE_RESOURCES) + +PKG_CHECK_MODULES(GUDEV, [gudev-1.0 >= $GUDEV_VERSION], [have_gudev=yes], [have_gudev=no]) +AC_SUBST(GUDEV_CFLAGS) +AC_SUBST(GUDEV_LIBS) + +PKG_CHECK_MODULES(GTK, [gtk+-3.0 >= $GTK_VERSION], [have_gtk=yes], [have_gtk=no]) +AC_SUBST(GTK_CFLAGS) +AC_SUBST(GTK_LIBS) + +dnl udev support is optional for qmi-firmware-update and mandatory for qmi-radio-monitor, enabled by default +AC_ARG_WITH(udev, + AS_HELP_STRING([--without-udev], + [Build without udev support]), + [with_udev=no], + [with_udev=yes]) +if test "$with_udev" = "yes"; then + if test "$have_gudev" = "no"; then + AC_MSG_ERROR([Couldn't find gudev >= $GUDEV_VERSION. Install it, or otherwise configure using --without-udev to disable udev support.]) + else + AC_DEFINE(WITH_UDEV, 1, [Define if you want udev support]) + fi +fi + dnl qmi-firmware-update is optional, enabled by default AC_ARG_ENABLE([firmware-update], - AS_HELP_STRING([--enable-firmware-update], - [enable compilation of `qmi-firmware-update' [default=yes]]), - [build_firmware_update=$enableval], + AS_HELP_STRING([--disable-firmware-update], + [disable compilation of `qmi-firmware-update']), + [build_firmware_update=no], [build_firmware_update=yes]) -AM_CONDITIONAL([BUILD_FIRMWARE_UPDATE], [test "x$build_firmware_update" = "xyes"]) - -dnl udev support is optional, enabled by default -AC_ARG_WITH(udev, AS_HELP_STRING([--without-udev], [Build without udev support]), [], [with_udev=yes]) -case $with_udev in - yes) - if test "x$build_firmware_update" = "xyes"; then - PKG_CHECK_MODULES(GUDEV, [gudev-1.0 >= $GUDEV_VERSION], [have_gudev=yes],[have_gudev=no]) - if test "x$have_gudev" = "xno"; then - AC_MSG_ERROR([Couldn't find gudev >= $GUDEV_VERSION. Install it, or otherwise configure using --without-udev to disable udev support.]) - else - AC_DEFINE(WITH_UDEV, 1, [Define if you want udev support]) - AC_SUBST(GUDEV_CFLAGS) - AC_SUBST(GUDEV_LIBS) - fi - else - with_udev="n/a" - fi - ;; - *) - with_udev=no - ;; -esac +if test "$build_firmware_update" = "yes"; then + if test "$with_udev" != "yes"; then + firmware_update_details="without udev" + fi +fi +AM_CONDITIONAL([BUILD_FIRMWARE_UPDATE], [test "$build_firmware_update" = "yes"]) dnl runtime MM check is optional, enabled by default -AC_ARG_ENABLE(mm-runtime-check, AS_HELP_STRING([--disable-mm-runtime-check], [Build without ModemManager runtime check]), [], [enable_mm_runtime_check=yes]) -case $enable_mm_runtime_check in - yes) - if test "x$build_firmware_update" = "xyes"; then - AC_DEFINE(MM_RUNTIME_CHECK_ENABLED, 1, [Define if you want ModemManager runtime check]) - else - enable_mm_runtime_check="n/a" +AC_ARG_ENABLE(mm-runtime-check, + AS_HELP_STRING([--disable-mm-runtime-check], + [Build without ModemManager runtime check]), + [enable_mm_runtime_check=no], + [enable_mm_runtime_check=yes]) +if test "$build_firmware_update" = "yes"; then + if test "$enable_mm_runtime_check" = "yes"; then + AC_DEFINE(MM_RUNTIME_CHECK_ENABLED, 1, [Define if you want ModemManager runtime check]) + else + if test -n "$firmware_update_details"; then + firmware_update_details="${firmware_update_details}, " fi - ;; - *) - enable_mm_runtime_check=no - ;; -esac + firmware_update_details="${firmware_update_details}without MM runtime check" + fi +fi + +dnl cleanup firmware update details information to show up later +if test -n "$firmware_update_details"; then + firmware_update_details=" (${firmware_update_details})" +fi + +dnl qmi-radio-monitor is optional, enabled by default +AC_ARG_ENABLE([radio-monitor], + AS_HELP_STRING([--disable-radio-monitor], + [disable compilation of `qmi-radio-monitor']), + [build_radio_monitor=no], + [build_radio_monitor=yes]) +if test "$build_radio_monitor" = "yes"; then + if test "$with_udev" != "yes"; then + AC_MSG_ERROR([Cannot build `qmi-radio-monitor' without udev support. Enable it, or otherwise configure using --disable-radio-monitor to skip building `qmi-radio-monitor'.]) + fi +fi +AM_CONDITIONAL([BUILD_RADIO_MONITOR], [test "$build_radio_monitor" = "yes"]) dnl Documentation GTK_DOC_CHECK(1.0) @@ -199,6 +223,8 @@ AC_CONFIG_FILES([Makefile src/qmi-proxy/Makefile src/qmi-firmware-update/Makefile src/qmi-firmware-update/test/Makefile + src/qmi-radio-monitor/Makefile + src/qmi-radio-monitor/data/Makefile utils/Makefile docs/Makefile docs/reference/Makefile @@ -213,7 +239,7 @@ fi AC_OUTPUT echo " - libqmi (libqmi-glib, qmicli) $VERSION + libqmi $VERSION ============================================== compiler: ${CC} @@ -227,7 +253,6 @@ echo " Built items: libqmi-glib: yes qmicli: yes - qmi-firmware-update: ${build_firmware_update} - with udev: ${with_udev} - with MM runtime check: ${enable_mm_runtime_check} + qmi-firmware-update: ${build_firmware_update}${firmware_update_details} + qmi-radio-monitor: ${build_radio_monitor} " diff --git a/src/Makefile.am b/src/Makefile.am index 1ef5d357..2011017b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -4,3 +4,7 @@ SUBDIRS = libqmi-glib qmicli qmi-proxy if BUILD_FIRMWARE_UPDATE SUBDIRS += qmi-firmware-update endif + +if BUILD_RADIO_MONITOR +SUBDIRS += qmi-radio-monitor +endif diff --git a/src/qmi-radio-monitor/Makefile.am b/src/qmi-radio-monitor/Makefile.am new file mode 100644 index 00000000..81c31669 --- /dev/null +++ b/src/qmi-radio-monitor/Makefile.am @@ -0,0 +1,124 @@ + +SUBDIRS = data . + +################################################################################ +# Program + +bin_PROGRAMS = qmi-radio-monitor + +qmi_radio_monitor_SOURCES = \ + qrm-resources.h qrm-resources.c \ + qrm-error.h qrm-error-types.h qrm-error-types.c qrm-error-quarks.c \ + qrm-enum-types.h qrm-enum-types.c \ + qrm-color-icon.h qrm-color-icon.c \ + qrm-graph.h qrm-graph.c \ + qrm-device.h qrm-device.c \ + qrm-signal-tab.h qrm-signal-tab.c \ + qrm-power-tab.h qrm-power-tab.c \ + qrm-window.h qrm-window.c \ + qrm-app.h qrm-app.c \ + qrm-main.c \ + $(NULL) + +qmi_radio_monitor_CPPFLAGS = \ + -I$(top_srcdir) \ + -I$(top_srcdir)/src/libqmi-glib \ + -I$(top_builddir)/src/libqmi-glib/generated \ + $(GTK_CFLAGS) \ + $(GUDEV_CFLAGS) \ + $(NULL) + +qmi_radio_monitor_LDADD = \ + $(top_builddir)/src/libqmi-glib/libqmi-glib.la \ + $(GTK_LIBS) \ + $(GUDEV_LIBS) \ + -lm \ + $(NULL) + +################################################################################ +# Error types + +qrm-error-types.h: qrm-error.h $(top_srcdir)/build-aux/templates/qmi-error-types-template.h + $(AM_V_GEN) $(GLIB_MKENUMS) \ + --fhead "#ifndef __QRM_ERROR_TYPES_H__\n#define __QRM_ERROR_TYPES_H__\n" \ + --template $(top_srcdir)/build-aux/templates/qmi-error-types-template.h \ + --ftail "#endif /* __QRM_ERROR_TYPES_H__ */\n" \ + qrm-error.h > $@ + +qrm-error-types.c: Makefile.am qrm-error.h $(top_srcdir)/build-aux/templates/qmi-error-types-template.c qrm-error-types.h + $(AM_V_GEN) $(GLIB_MKENUMS) \ + --fhead "#include \"qrm-error.h\"\n#include \"qrm-error-types.h\"\n" \ + --template $(top_srcdir)/build-aux/templates/qmi-error-types-template.c \ + qrm-error.h > $@ + +qrm-error-quarks.c: Makefile.am qrm-error.h qrm-error-types.h $(top_srcdir)/build-aux/templates/qmi-error-quarks-template.c + $(AM_V_GEN) $(GLIB_MKENUMS) \ + --fhead "#include \"qrm-error.h\"\n#include \"qrm-error-types.h\"\n" \ + --template $(top_srcdir)/build-aux/templates/qmi-error-quarks-template.c \ + qrm-error.h > $@ + +################################################################################ +# Enum types + +ENUMS = \ + $(srcdir)/qrm-device.h \ + $(srcdir)/qrm-graph.h + +qrm-enum-types.h: Makefile.am $(ENUMS) $(top_srcdir)/build-aux/templates/qmi-enum-types-template.h + $(AM_V_GEN) $(GLIB_MKENUMS) \ + --fhead "#include \"qrm-device.h\"\n#include \"qrm-graph.h\"\n#ifndef __QRM_ENUM_TYPES_H__\n#define __QRM_ENUM_TYPES_H__\n" \ + --template $(top_srcdir)/build-aux/templates/qmi-enum-types-template.h \ + --ftail "#endif /* __QRM_ENUM_TYPES_H__ */\n" \ + $(ENUMS) > $@ + +qrm-enum-types.c: Makefile.am $(top_srcdir)/build-aux/templates/qmi-enum-types-template.c qrm-enum-types.h + $(AM_V_GEN) $(GLIB_MKENUMS) \ + --fhead "#include \"qrm-enum-types.h\"" \ + --template $(top_srcdir)/build-aux/templates/qmi-enum-types-template.c \ + $(ENUMS) > $@ + +################################################################################ +# Resource files + +resource_file_dependencies = \ + $(shell $(GLIB_COMPILE_RESOURCES) \ + --sourcedir=$(srcdir) \ + --generate-dependencies \ + $(srcdir)/qrm.gresource.xml) + +qrm-resources.c: qrm.gresource.xml $(resource_file_dependencies) + $(AM_V_GEN) $(GLIB_COMPILE_RESOURCES) \ + --target=$@ \ + --sourcedir=$(srcdir) \ + --generate-source \ + --c-name qrm \ + $(srcdir)/qrm.gresource.xml + +qrm-resources.h: qrm.gresource.xml $(resource_file_dependencies) + $(AM_V_GEN) $(GLIB_COMPILE_RESOURCES) \ + --target=$@ \ + --sourcedir=$(srcdir) \ + --generate-header \ + --c-name qrm \ + $(srcdir)/qrm.gresource.xml + +################################################################################ + +BUILT_SOURCES = \ + qrm-resources.h qrm-resources.c \ + qrm-error-types.h qrm-error-types.c qrm-error-quarks.c \ + qrm-enum-types.h qrm-enum-types.c \ + $(NULL) + +CLEANFILES = \ + qrm-resources.h qrm-resources.c \ + qrm-error-types.h qrm-error-types.c \ + qrm-enum-types.h qrm-enum-types.c \ + $(NULL) + +EXTRA_DIST = \ + qrm-window.ui \ + qrm-signal-tab.ui \ + qrm-power-tab.ui \ + qrm.gresource.xml \ + $(NULL) diff --git a/src/qmi-radio-monitor/data/Makefile.am b/src/qmi-radio-monitor/data/Makefile.am new file mode 100644 index 00000000..d5b27046 --- /dev/null +++ b/src/qmi-radio-monitor/data/Makefile.am @@ -0,0 +1,24 @@ + +EXTRA_DIST = + +# Desktop file +desktopdir = $(datadir)/applications +desktop_DATA = qmi-radio-monitor.desktop +EXTRA_DIST += qmi-radio-monitor.desktop + +# Application icon +# NOTE: yes, this is gnome-system-monitor's icon with other colors, yes. +# All credits go to jimmac, who I believe is the one who draw it +icon256dir = $(datadir)/icons/hicolor/256x256/apps +icon256_DATA = qmi-radio-monitor.png +EXTRA_DIST += qmi-radio-monitor.png + +# Polkit policy +edit = @sed -e 's|@bindir[@]|$(bindir)|g' +org.freedesktop.QmiRadioMonitor.pkexec.policy: org.freedesktop.QmiRadioMonitor.pkexec.policy.in + $(edit) $< >$@ + +polkit_policydir = $(datadir)/polkit-1/actions +polkit_policy_DATA = org.freedesktop.QmiRadioMonitor.pkexec.policy +DISTCLEANFILES = org.freedesktop.QmiRadioMonitor.pkexec.policy +EXTRA_DIST += org.freedesktop.QmiRadioMonitor.pkexec.policy.in diff --git a/src/qmi-radio-monitor/data/org.freedesktop.QmiRadioMonitor.pkexec.policy.in b/src/qmi-radio-monitor/data/org.freedesktop.QmiRadioMonitor.pkexec.policy.in new file mode 100644 index 00000000..0e838632 --- /dev/null +++ b/src/qmi-radio-monitor/data/org.freedesktop.QmiRadioMonitor.pkexec.policy.in @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE policyconfig PUBLIC + "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN" + "http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd"> +<policyconfig> + + <vendor>Aleksander Morgado</vendor> + <vendor_url>https://www.freedesktop.org/wiki/Software/libqmi/</vendor_url> + + <action id="es.aleksander.MobileRadioMonitor.pkexec.run-mobile-radio-monitor"> + <description>Run the QMI Radio Monitor</description> + <message>Authentication is required to run the QMI Radio Monitor (user=$(user), program=$(program), command_line=$(command_line))</message> + <icon_name>qmi-radio-monitor</icon_name> + <defaults> + <allow_any>no</allow_any> + <allow_inactive>no</allow_inactive> + <allow_active>auth_self_keep</allow_active> + </defaults> + <annotate key="org.freedesktop.policykit.exec.path">@bindir@/qmi-radio-monitor</annotate> + <annotate key="org.freedesktop.policykit.exec.allow_gui">yes</annotate> + </action> + +</policyconfig> diff --git a/src/qmi-radio-monitor/data/qmi-radio-monitor.desktop b/src/qmi-radio-monitor/data/qmi-radio-monitor.desktop new file mode 100644 index 00000000..f89653ce --- /dev/null +++ b/src/qmi-radio-monitor/data/qmi-radio-monitor.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Name=QMI Radio Monitor +Comment=A monitor for mobile radio environment parameters +Comment[de]=Ein Monitor zur Anzeige von mobilen Breitband Parametern +TryExec=pkexec mobile-radio-monitor +Exec=pkexec mobile-radio-monitor +Icon=mobile-radio-monitor +Terminal=false +Type=Application +StartupNotify=true +Categories=System;Monitor;
\ No newline at end of file diff --git a/src/qmi-radio-monitor/data/qmi-radio-monitor.png b/src/qmi-radio-monitor/data/qmi-radio-monitor.png Binary files differnew file mode 100644 index 00000000..5634c02a --- /dev/null +++ b/src/qmi-radio-monitor/data/qmi-radio-monitor.png diff --git a/src/qmi-radio-monitor/qrm-app.c b/src/qmi-radio-monitor/qrm-app.c new file mode 100644 index 00000000..b4aece13 --- /dev/null +++ b/src/qmi-radio-monitor/qrm-app.c @@ -0,0 +1,599 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * Copyright (C) 2015-2017 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <config.h> +#include <gudev/gudev.h> + +#include "qrm-app.h" +#include "qrm-window.h" +#include "qrm-device.h" + +G_DEFINE_TYPE (QrmApp, qrm_app, GTK_TYPE_APPLICATION) + +enum { + SIGNAL_DEVICE_DETECTION, + SIGNAL_INITIAL_SCAN_DONE, + SIGNAL_DEVICE_ADDED, + SIGNAL_DEVICE_REMOVED, + SIGNAL_LAST +}; + +static guint signals [SIGNAL_LAST] = { 0 }; + +struct _QrmAppPrivate { + /* The UDev client */ + GUdevClient *udev_client; + guint initial_scan_id; + gboolean initial_scan_done; + + /* List of devices being added */ + GList *pending_devices; + + /* List of QrmDevices */ + GList *devices; + + /* Shutdown inner loop */ + GMainLoop *shutdown_loop; +}; + +/******************************************************************************/ + +gboolean +qrm_app_is_initial_scan_done (QrmApp *self) +{ + g_return_val_if_fail (QRM_IS_APP (self), FALSE); + + return self->priv->initial_scan_done; +} + +GList * +qrm_app_peek_devices (QrmApp *self) +{ + g_return_val_if_fail (QRM_IS_APP (self), NULL); + + return self->priv->devices; +} + +/******************************************************************************/ + +typedef struct { + gchar *device_name; + GCancellable *cancellable; +} PendingDeviceInfo; + +static PendingDeviceInfo * +pending_device_info_find (QrmApp *self, + const gchar *device_name) +{ + GList *l; + + for (l = self->priv->pending_devices; l; l = g_list_next (l)) { + if (g_str_equal (((PendingDeviceInfo *)l->data)->device_name, device_name)) + return (PendingDeviceInfo *)l->data; + } + return NULL; +} + +static void +pending_device_info_cancel (QrmApp *self, + const gchar *device_name) +{ + PendingDeviceInfo *info; + + info = pending_device_info_find (self, device_name); + if (info) + g_cancellable_cancel (info->cancellable); +} + +static void +pending_device_info_remove (QrmApp *self, + const gchar *device_name) +{ + PendingDeviceInfo *info; + + info = pending_device_info_find (self, device_name); + if (!info) + return; + + self->priv->pending_devices = g_list_remove (self->priv->pending_devices, info); + g_object_unref (info->cancellable); + g_free (info->device_name); + g_slice_free (PendingDeviceInfo, info); + + if (!self->priv->pending_devices) + g_signal_emit (self, signals[SIGNAL_DEVICE_DETECTION], 0, FALSE); +} + +static void +pending_device_info_add (QrmApp *self, + const gchar *device_name, + GCancellable *cancellable) +{ + PendingDeviceInfo *info; + + info = g_slice_new0 (PendingDeviceInfo); + info->device_name = g_strdup (device_name); + info->cancellable = g_object_ref (cancellable); + + g_signal_emit (self, signals[SIGNAL_DEVICE_DETECTION], 0, TRUE); + self->priv->pending_devices = g_list_append (self->priv->pending_devices, info); +} + +/******************************************************************************/ + +static void shutdown_loop_check_completed (QrmApp *self); + +typedef struct { + QrmApp *self; + GFile *file; + GCancellable *cancellable; + gchar *device_name; +} PortAddedContext; + +static void +port_added_context_free (PortAddedContext *ctx) +{ + g_free (ctx->device_name); + g_object_unref (ctx->cancellable); + g_object_unref (ctx->file); + g_object_unref (ctx->self); + g_slice_free (PortAddedContext, ctx); +} + +static void +device_new_ready (GObject *source, + GAsyncResult *res, + PortAddedContext *ctx) +{ + GError *error = NULL; + QrmDevice *device; + + device = qrm_device_new_finish (res, &error); + if (!device) { + g_warning ("Cannot create QRM device: %s", error->message); + g_error_free (error); + } else if (g_cancellable_is_cancelled (ctx->cancellable)) { + g_warning ("QRM device creation cancelled"); + g_object_unref (device); + } else { + /* Add device */ + g_signal_emit (ctx->self, signals[SIGNAL_DEVICE_ADDED], 0, device); + ctx->self->priv->devices = g_list_append (ctx->self->priv->devices, device); + } + + pending_device_info_remove (ctx->self, ctx->device_name); + + /* If this was the last pending device, we're done */ + if (!ctx->self->priv->initial_scan_done && !ctx->self->priv->pending_devices) { + ctx->self->priv->initial_scan_done = TRUE; + g_signal_emit (ctx->self, signals[SIGNAL_INITIAL_SCAN_DONE], 0); + } + + /* If we got the last pending device info done, and no more devices available, + * complete shutdown if we are in the middle of a shutdown process */ + shutdown_loop_check_completed (ctx->self); + + port_added_context_free (ctx); +} + +static gboolean +filter_usb_device (GUdevDevice *device) +{ + const gchar *subsystem; + const gchar *name; + const gchar *driver; + GUdevDevice *parent = NULL; + gboolean filtered = TRUE; + + /* Subsystems: 'usb', 'usbmisc' or 'net' */ + subsystem = g_udev_device_get_subsystem (device); + if (!subsystem || !g_str_has_prefix (subsystem, "usb")) + goto out; + + /* Names: if 'usb' or 'usbmisc' only 'cdc-wdm' prefixed names allowed */ + name = g_udev_device_get_name (device); + if (!name || !g_str_has_prefix (name, "cdc-wdm")) + goto out; + + /* Drivers: 'qmi_wwan' only */ + driver = g_udev_device_get_driver (device); + if (!driver) { + parent = g_udev_device_get_parent (device); + if (parent) + driver = g_udev_device_get_driver (parent); + } + if (!driver || !g_str_equal (driver, "qmi_wwan")) + goto out; + + /* Not filtered! */ + filtered = FALSE; + + out: + if (parent) + g_object_unref (parent); + return filtered; +} + +static void +port_added (QrmApp *self, + GUdevDevice *udev_device) +{ + PortAddedContext *ctx; + gchar *path; + + /* Filter */ + if (filter_usb_device (udev_device)) + return; + + path = g_strdup_printf ("/dev/%s", g_udev_device_get_name (udev_device)); + g_debug ("QMI device file available: %s", path); + + ctx = g_slice_new (PortAddedContext); + ctx->self = g_object_ref (self); + ctx->device_name = g_strdup (g_udev_device_get_name (udev_device)); + ctx->file = g_file_new_for_path (path); + ctx->cancellable = g_cancellable_new (); + pending_device_info_add (self, ctx->device_name, ctx->cancellable); + + qrm_device_new (ctx->file, + ctx->cancellable, + (GAsyncReadyCallback) device_new_ready, + ctx); + + g_free (path); +} + +static void +port_removed (QrmApp *self, + GUdevDevice *udev_device) +{ + GList *l; + + /* Remove from device file list */ + for (l = self->priv->devices; l; l = g_list_next (l)) { + QrmDevice *device = QRM_DEVICE (l->data); + + if (g_str_equal (qrm_device_get_name (device), g_udev_device_get_name (udev_device))) { + g_debug ("QMI device file unavailable: /dev/%s", g_udev_device_get_name (udev_device)); + self->priv->devices = g_list_delete_link (self->priv->devices, l); + g_signal_emit (self, signals[SIGNAL_DEVICE_REMOVED], 0, device); + g_object_unref (device); + return; + } + } + + /* In case we were still adding it... */ + pending_device_info_cancel (self, g_udev_device_get_name (udev_device)); +} + +static void +uevent_cb (GUdevClient *client, + const gchar *action, + GUdevDevice *device, + QrmApp *self) +{ + /* Port added */ + if (g_str_equal (action, "add") || + g_str_equal (action, "move") || + g_str_equal (action, "change")) { + port_added (self, device); + return; + } + + /* Port removed */ + if (g_str_equal (action, "remove")) { + port_removed (self, device); + return; + } + + /* Ignore other actions */ +} + +static gboolean +initial_scan_cb (QrmApp *self) +{ + GList *devices, *iter; + + self->priv->initial_scan_id = 0; + + g_debug ("Scanning usb subsystems..."); + + devices = g_udev_client_query_by_subsystem (self->priv->udev_client, "usb"); + for (iter = devices; iter; iter = g_list_next (iter)) { + port_added (self, G_UDEV_DEVICE (iter->data)); + g_object_unref (G_OBJECT (iter->data)); + } + g_list_free (devices); + + devices = g_udev_client_query_by_subsystem (self->priv->udev_client, "usbmisc"); + for (iter = devices; iter; iter = g_list_next (iter)) { + port_added (self, G_UDEV_DEVICE (iter->data)); + g_object_unref (G_OBJECT (iter->data)); + } + g_list_free (devices); + + /* If no pending devices, we're done */ + if (!self->priv->initial_scan_done && !self->priv->pending_devices) { + self->priv->initial_scan_done = TRUE; + g_signal_emit (self, signals[SIGNAL_INITIAL_SCAN_DONE], 0); + } + + return FALSE; +} + +/******************************************************************************/ +/* Main application window management */ + +static GtkWidget * +peek_main_window (QrmApp *self) +{ + GList *l; + + /* Remove all windows registered in the application */ + l = gtk_application_get_windows (GTK_APPLICATION (self)); + g_assert_cmpuint (g_list_length (l), <=, 1); + + return (l ? GTK_WIDGET (l->data) : NULL); +} + +static void +ensure_main_window (QrmApp *self) +{ + GtkWidget *window; + + if (peek_main_window (self)) + return; + + window = qrm_window_new (self); + gtk_application_add_window (GTK_APPLICATION (self), GTK_WINDOW (window)); + gtk_window_maximize (GTK_WINDOW (window)); + gtk_widget_show (window); +} + +void +qrm_app_quit (QrmApp *self) +{ + g_action_group_activate_action (G_ACTION_GROUP (self), "quit", NULL); +} + +/******************************************************************************/ +/* Application actions setup */ + +static void +about_cb (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + QrmApp *self = QRM_APP (user_data); + const gchar *authors[] = { + "Aleksander Morgado <aleksander@aleksander.es>", + NULL + }; + + gtk_show_about_dialog (GTK_WINDOW (peek_main_window (self)), + "name", "QMI Radio Monitor", + "version", PACKAGE_VERSION, + "comments", "A monitor for mobile radio environment parameters", + "copyright", "Copyright \xc2\xa9 2013-2017 Aleksander Morgado", + "logo-icon-name", "qmi-radio-monitor", + "authors", authors, + "license", "GPLv2+", + "wrap-license", TRUE, + NULL); +} + +static void +quit_cb (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + QrmApp *self = QRM_APP (user_data); + GList *l; + + /* Remove all windows registered in the application */ + while ((l = gtk_application_get_windows (GTK_APPLICATION (self)))) + gtk_application_remove_window (GTK_APPLICATION (self), GTK_WINDOW (l->data)); +} + +static GActionEntry app_entries[] = { + { "about", about_cb, NULL, NULL, NULL }, + { "quit", quit_cb, NULL, NULL, NULL }, +}; + +/******************************************************************************/ + +static void +activate (GApplication *application) +{ + ensure_main_window (QRM_APP (application)); +} + +/******************************************************************************/ + +static void +startup (GApplication *application) +{ + QrmApp *self = QRM_APP (application); + + /* Chain up parent's startup */ + G_APPLICATION_CLASS (qrm_app_parent_class)->startup (application); + + /* Setup actions */ + g_action_map_add_action_entries (G_ACTION_MAP (self), + app_entries, G_N_ELEMENTS (app_entries), + self); +} + +/******************************************************************************/ + +static void +shutdown_loop_check_completed (QrmApp *self) +{ + if (!self->priv->shutdown_loop) + return; + + /* Whenever the last device has been closed, quit the inner loop */ + if (!self->priv->devices && !self->priv->pending_devices) + g_main_loop_quit (self->priv->shutdown_loop); +} + +static void +device_close_ready (QrmDevice *device, + GAsyncResult *res, + QrmApp *self) +{ + qrm_device_close_finish (device, res, NULL); + + /* Remove from app list once closed */ + self->priv->devices = g_list_remove (self->priv->devices, device); + g_object_unref (device); + + shutdown_loop_check_completed (self); +} + +static void +shutdown (GApplication *application) +{ + QrmApp *self = QRM_APP (application); + GList *l; + + self->priv->shutdown_loop = g_main_loop_new (NULL, FALSE); + + for (l = self->priv->pending_devices; l; l = g_list_next (l)) + pending_device_info_cancel (self, ((PendingDeviceInfo *)l->data)->device_name); + + for (l = self->priv->devices; l; l = g_list_next (l)) + qrm_device_close (QRM_DEVICE (l->data), + NULL, + (GAsyncReadyCallback) device_close_ready, + self); + + g_main_loop_run (self->priv->shutdown_loop); + g_main_loop_unref (self->priv->shutdown_loop); + self->priv->shutdown_loop = NULL; + + /* Chain up parent's shutdown */ + G_APPLICATION_CLASS (qrm_app_parent_class)->shutdown (application); +} + +/******************************************************************************/ + +QrmApp * +qrm_app_new (void) +{ + return g_object_new (QRM_TYPE_APP, + "application-id", "org.freedesktop.QmiRadioMonitor", + "flags", G_APPLICATION_FLAGS_NONE, + NULL); +} + +static void +qrm_app_init (QrmApp *self) +{ + const gchar *subsys[] = { "usb", "usbmisc", NULL }; + + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, QRM_TYPE_APP, QrmAppPrivate); + + g_set_application_name ("QMI Radio Monitor"); + gtk_window_set_default_icon_name ("qmi-radio-monitor"); + + /* Setup UDev client */ + self->priv->udev_client = g_udev_client_new (subsys); + g_signal_connect (self->priv->udev_client, "uevent", G_CALLBACK (uevent_cb), self); + + /* Setup initial scan */ + self->priv->initial_scan_id = g_idle_add ((GSourceFunc) initial_scan_cb, self); +} + +static void +dispose (GObject *object) +{ + QrmApp *self = QRM_APP (object); + + if (self->priv->initial_scan_id != 0) { + g_source_remove (self->priv->initial_scan_id); + self->priv->initial_scan_id = 0; + } + + g_assert (self->priv->pending_devices == NULL); + + g_list_free_full (self->priv->devices, g_object_unref); + self->priv->devices = NULL; + + g_clear_object (&self->priv->udev_client); + + G_OBJECT_CLASS (qrm_app_parent_class)->dispose (object); +} + +static void +qrm_app_class_init (QrmAppClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GApplicationClass *application_class = G_APPLICATION_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (QrmAppPrivate)); + + application_class->startup = startup; + application_class->activate = activate; + application_class->shutdown = shutdown; + object_class->dispose = dispose; + + signals[SIGNAL_DEVICE_DETECTION] = + g_signal_new ("device-detection", + G_OBJECT_CLASS_TYPE (G_OBJECT_CLASS (klass)), + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + G_TYPE_BOOLEAN); + + signals[SIGNAL_DEVICE_ADDED] = + g_signal_new ("device-added", + G_OBJECT_CLASS_TYPE (G_OBJECT_CLASS (klass)), + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + QRM_TYPE_DEVICE); + + signals[SIGNAL_DEVICE_REMOVED] = + g_signal_new ("device-removed", + G_OBJECT_CLASS_TYPE (G_OBJECT_CLASS (klass)), + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + QRM_TYPE_DEVICE); + + signals[SIGNAL_INITIAL_SCAN_DONE] = + g_signal_new ("initial-scan-done", + G_OBJECT_CLASS_TYPE (G_OBJECT_CLASS (klass)), + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 0); +} diff --git a/src/qmi-radio-monitor/qrm-app.h b/src/qmi-radio-monitor/qrm-app.h new file mode 100644 index 00000000..df69da0d --- /dev/null +++ b/src/qmi-radio-monitor/qrm-app.h @@ -0,0 +1,52 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * Copyright (C) 2013-2017 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef QRM_APP_H +#define QRM_APP_H + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define QRM_TYPE_APP (qrm_app_get_type ()) +#define QRM_APP(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), QRM_TYPE_APP, QrmApp)) +#define QRM_APP_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), QRM_TYPE_APP, QrmAppClass)) +#define QRM_IS_APP(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), QRM_TYPE_APP)) +#define QRM_IS_APP_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), QRM_TYPE_APP)) +#define QRM_APP_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), QRM_TYPE_APP, QrmAppClass)) + +typedef struct _QrmApp QrmApp; +typedef struct _QrmAppClass QrmAppClass; +typedef struct _QrmAppPrivate QrmAppPrivate; + +struct _QrmApp { + GtkApplication parent_instance; + QrmAppPrivate *priv; +}; + +struct _QrmAppClass { + GtkApplicationClass parent_class; +}; + +GType qrm_app_get_type (void) G_GNUC_CONST; + +QrmApp *qrm_app_new (void); +void qrm_app_quit (QrmApp *self); +gboolean qrm_app_is_initial_scan_done (QrmApp *self); +GList *qrm_app_peek_devices (QrmApp *self); + +G_END_DECLS + +#endif /* QRM_APP_H */ diff --git a/src/qmi-radio-monitor/qrm-color-icon.c b/src/qmi-radio-monitor/qrm-color-icon.c new file mode 100644 index 00000000..1556c732 --- /dev/null +++ b/src/qmi-radio-monitor/qrm-color-icon.c @@ -0,0 +1,276 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details: + * + * Copyright (C) 2013-2017 Aleksander Morgado <aleksander@aleksander.es> + * + * Based on the Gnome system monitor colour pickers + * Copyright (C) 2007 Karl Lattimer <karl@qdh.org.uk> + */ + +#include <config.h> + +#include <gtk/gtk.h> +#include <cairo.h> + +#include "qrm-color-icon.h" + +G_DEFINE_TYPE (QrmColorIcon, qrm_color_icon, GTK_TYPE_DRAWING_AREA) + +#define MIN_WIDTH 30 +#define MIN_HEIGHT 15 + +enum +{ + PROP_0, + PROP_COLOR, +}; + +struct _QrmColorIconPrivate { + GdkRGBA color; + cairo_surface_t *image_buffer; +}; + +static void +render (QrmColorIcon *self) +{ + cairo_t *cr; + gint width, height; + + cr = gdk_cairo_create (gtk_widget_get_window (GTK_WIDGET (self))); + + gdk_cairo_set_source_rgba (cr, &(self->priv->color)); + width = gdk_window_get_width (gtk_widget_get_window (GTK_WIDGET (self))); + height = gdk_window_get_height (gtk_widget_get_window (GTK_WIDGET (self))); + + cairo_paint (cr); + cairo_set_line_width (cr, 1); + cairo_set_source_rgba (cr, 0, 0, 0, 0.5); + cairo_rectangle (cr, 0.5, 0.5, width - 1, height - 1); + cairo_stroke (cr); + cairo_set_line_width (cr, 1); + cairo_set_source_rgba (cr, 1, 1, 1, 0.4); + cairo_rectangle (cr, 1.5, 1.5, width - 3, height - 3); + cairo_stroke (cr); + cairo_destroy (cr); +} + +/******************************************************************************/ + +void +qrm_color_icon_set_color (QrmColorIcon *self, + guint8 color_red, + guint8 color_green, + guint8 color_blue) +{ + GdkRGBA color; + + color.red = ((gdouble)color_red) / 255.0; + color.green = ((gdouble)color_green) / 255.0; + color.blue = ((gdouble)color_blue) / 255.0; + color.alpha = 1.0; + + g_object_set (self, + "color", &color, + NULL); +} + +/******************************************************************************/ + +GtkWidget * +qrm_color_icon_new (const GdkRGBA *color) +{ + return g_object_new (QRM_TYPE_COLOR_ICON, + "color", color, + NULL); +} + +static gint +draw (GtkWidget *widget, + cairo_t *cr) +{ + render (QRM_COLOR_ICON (widget)); + + return FALSE; +} + +static void +realize (GtkWidget *widget) +{ + GTK_WIDGET_CLASS (qrm_color_icon_parent_class)->realize (widget); + + render (QRM_COLOR_ICON (widget)); +} + +static void +get_preferred_width (GtkWidget *widget, + gint *minimum, + gint *natural) +{ + g_return_if_fail (widget != NULL || minimum != NULL || natural != NULL); + g_return_if_fail (QRM_IS_COLOR_ICON (widget)); + + *minimum = MIN_WIDTH; + *natural = MIN_WIDTH; +} + +static void +get_preferred_height (GtkWidget *widget, + gint *minimum, + gint *natural) +{ + g_return_if_fail (widget != NULL || minimum != NULL || natural != NULL); + g_return_if_fail (QRM_IS_COLOR_ICON (widget)); + + *minimum = MIN_HEIGHT; + *natural = MIN_HEIGHT; +} + +static void +size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + g_return_if_fail (widget != NULL || allocation != NULL); + g_return_if_fail (QRM_IS_COLOR_ICON (widget)); + + gtk_widget_set_allocation (widget, allocation); + + if (gtk_widget_get_realized (widget)) { + gdk_window_move_resize (gtk_widget_get_window (widget), + allocation->x, + allocation->y, + allocation->width, + allocation->height); + } +} + +static void +unrealize (GtkWidget *widget) +{ + GTK_WIDGET_CLASS (qrm_color_icon_parent_class)->unrealize (widget); +} + +static void +style_set (GtkWidget *widget, + GtkStyle *previous_style) +{ + GTK_WIDGET_CLASS (qrm_color_icon_parent_class)->style_set (widget, previous_style); +} + +static void +set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + QrmColorIcon *self = QRM_COLOR_ICON (object); + + switch (param_id) + { + case PROP_COLOR: { + const GdkRGBA *color; + + color = g_value_get_boxed (value); + self->priv->color.red = color ? color->red : 0.0; + self->priv->color.green = color ? color->green : 0.0; + self->priv->color.blue = color ? color->blue : 0.0; + self->priv->color.alpha = color ? color->alpha : 1.0; + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + QrmColorIcon *self = QRM_COLOR_ICON (object); + + switch (param_id) { + case PROP_COLOR: { + GdkRGBA color; + + color.red = self->priv->color.red; + color.green = self->priv->color.green; + color.blue = self->priv->color.blue; + color.alpha = self->priv->color.alpha; + g_value_set_boxed (value, &color); + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +finalize (GObject *object) +{ + QrmColorIcon *self = QRM_COLOR_ICON (object); + + cairo_surface_destroy (self->priv->image_buffer); + self->priv->image_buffer = NULL; + + G_OBJECT_CLASS (qrm_color_icon_parent_class)->finalize (object); +} + +static void +qrm_color_icon_init (QrmColorIcon *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + QRM_TYPE_COLOR_ICON, + QrmColorIconPrivate); + + self->priv->color.red = 0; + self->priv->color.green = 0; + self->priv->color.blue = 0; + self->priv->color.alpha = 1.0; + self->priv->image_buffer = NULL; + + g_signal_connect (self, + "draw", + G_CALLBACK (draw), + NULL); +} + +static void +qrm_color_icon_class_init (QrmColorIconClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + g_type_class_add_private (gobject_class, sizeof (QrmColorIconPrivate)); + + gobject_class->get_property = get_property; + gobject_class->set_property = set_property; + gobject_class->finalize = finalize; + + widget_class->get_preferred_width = get_preferred_width; + widget_class->get_preferred_height = get_preferred_height; + widget_class->size_allocate = size_allocate; + widget_class->realize = realize; + widget_class->unrealize = unrealize; + widget_class->style_set = style_set; + + g_object_class_install_property (gobject_class, + PROP_COLOR, + g_param_spec_boxed ("color", + "Current Color", + "The selected color", + GDK_TYPE_RGBA, + G_PARAM_READWRITE)); +} diff --git a/src/qmi-radio-monitor/qrm-color-icon.h b/src/qmi-radio-monitor/qrm-color-icon.h new file mode 100644 index 00000000..47fcaaee --- /dev/null +++ b/src/qmi-radio-monitor/qrm-color-icon.h @@ -0,0 +1,61 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details: + * + * Copyright (C) 2013-2017 Aleksander Morgado <aleksander@aleksander.es> + * + * Based on the Gnome system monitor colour pickers + * Copyright (C) 2007 Karl Lattimer <karl@qdh.org.uk> + */ + +#ifndef QRM_COLOR_ICON_H +#define QRM_COLOR_ICON_H + +#include <glib.h> +#include <gdk/gdk.h> +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define QRM_TYPE_COLOR_ICON (qrm_color_icon_get_type ()) +#define QRM_COLOR_ICON(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), QRM_TYPE_COLOR_ICON, QrmColorIcon)) +#define QRM_COLOR_ICON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), QRM_TYPE_COLOR_ICON, QrmColorIconClass)) +#define QRM_IS_COLOR_ICON(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), QRM_TYPE_COLOR_ICON)) +#define QRM_IS_COLOR_ICON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), QRM_TYPE_COLOR_ICON)) +#define QRM_COLOR_ICON_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), QRM_TYPE_COLOR_ICON, QrmColorIconClass)) + +typedef struct _QrmColorIcon QrmColorIcon; +typedef struct _QrmColorIconClass QrmColorIconClass; +typedef struct _QrmColorIconPrivate QrmColorIconPrivate; + +struct _QrmColorIcon +{ + GtkDrawingArea widget; + QrmColorIconPrivate *priv; +}; + +struct _QrmColorIconClass +{ + GtkDrawingAreaClass parent_class; +}; + +GType qrm_color_icon_get_type (void) G_GNUC_CONST; + +GtkWidget *qrm_color_icon_new (const GdkRGBA *color); + +void qrm_color_icon_set_color (QrmColorIcon *self, + guint8 color_red, + guint8 color_green, + guint8 color_blue); + +G_END_DECLS +#endif /* QRM_COLOR_ICON_H */ diff --git a/src/qmi-radio-monitor/qrm-device.c b/src/qmi-radio-monitor/qrm-device.c new file mode 100644 index 00000000..57373c9f --- /dev/null +++ b/src/qmi-radio-monitor/qrm-device.c @@ -0,0 +1,1497 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * Copyright (C) 2013-2017 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include "qrm-device.h" +#include "qrm-error.h" +#include "qrm-error-types.h" +#include "qrm-enum-types.h" + +static void async_initable_iface_init (GAsyncInitableIface *iface); + +G_DEFINE_TYPE_EXTENDED (QrmDevice, qrm_device, G_TYPE_OBJECT, 0, + G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, async_initable_iface_init)) + +enum { + PROP_0, + PROP_FILE, + PROP_QMI_DEVICE, + PROP_STATUS, + PROP_LAST +}; + +static GParamSpec *properties[PROP_LAST]; + +enum { + SIGNAL_ACT_UPDATED, + SIGNAL_RSSI_UPDATED, + SIGNAL_ECIO_UPDATED, + SIGNAL_SINR_LEVEL_UPDATED, + SIGNAL_IO_UPDATED, + SIGNAL_RSRQ_UPDATED, + SIGNAL_RSRP_UPDATED, + SIGNAL_SNR_UPDATED, + SIGNAL_RX0_UPDATED, + SIGNAL_RX1_UPDATED, + SIGNAL_TX_UPDATED, + SIGNAL_LAST +}; + +static guint signals[SIGNAL_LAST] = { 0 }; + +struct _QrmDevicePrivate { + /* QMI device */ + GFile *file; + QmiDevice *qmi_device; + + /* Device IDs */ + gchar *name; + gchar *manufacturer; + gchar *model; + gchar *revision; + + /* Device status */ + QrmDeviceStatus status; + gint pin_attempts_left; + + /* Clients */ + QmiClient *dms; + QmiClient *nas; + + /* Info updates handling */ + guint info_updated_id; +}; + +/*****************************************************************************/ +/* Reload power info */ + +typedef struct { + QrmDevice *self; + QrmDeviceAct act; + guint current_i; + + gboolean has_gsm; + gdouble gsm_rx0; + gdouble gsm_rx1; + gdouble gsm_tx; + + gboolean has_umts; + gdouble umts_rx0; + gdouble umts_rx1; + gdouble umts_tx; + + gboolean has_lte; + gdouble lte_rx0; + gdouble lte_rx1; + gdouble lte_tx; + + gboolean has_cdma; + gdouble cdma_rx0; + gdouble cdma_rx1; + gdouble cdma_tx; + + gboolean has_evdo; + gdouble evdo_rx0; + gdouble evdo_rx1; + gdouble evdo_tx; +} ReloadPowerInfoContext; + +static void +reload_power_info_context_complete (ReloadPowerInfoContext *ctx) +{ + g_signal_emit (ctx->self, + signals[SIGNAL_RX0_UPDATED], + 0, + ctx->has_gsm ? ctx->gsm_rx0 : -G_MAXDOUBLE, + ctx->has_umts ? ctx->umts_rx0 : -G_MAXDOUBLE, + ctx->has_lte ? ctx->lte_rx0 : -G_MAXDOUBLE, + ctx->has_cdma ? ctx->cdma_rx0 : -G_MAXDOUBLE, + ctx->has_evdo ? ctx->evdo_rx0 : -G_MAXDOUBLE); + + g_signal_emit (ctx->self, + signals[SIGNAL_RX1_UPDATED], + 0, + ctx->has_gsm ? ctx->gsm_rx1 : -G_MAXDOUBLE, + ctx->has_umts ? ctx->umts_rx1 : -G_MAXDOUBLE, + ctx->has_lte ? ctx->lte_rx1 : -G_MAXDOUBLE, + ctx->has_cdma ? ctx->cdma_rx1 : -G_MAXDOUBLE, + ctx->has_evdo ? ctx->evdo_rx1 : -G_MAXDOUBLE); + + g_signal_emit (ctx->self, + signals[SIGNAL_TX_UPDATED], + 0, + ctx->has_gsm ? ctx->gsm_tx : -G_MAXDOUBLE, + ctx->has_umts ? ctx->umts_tx : -G_MAXDOUBLE, + ctx->has_lte ? ctx->lte_tx : -G_MAXDOUBLE, + ctx->has_cdma ? ctx->cdma_tx : -G_MAXDOUBLE, + ctx->has_evdo ? ctx->evdo_tx : -G_MAXDOUBLE); + + g_object_unref (ctx->self); + g_slice_free (ReloadPowerInfoContext, ctx); +} + +static void reload_power_info_context_step (ReloadPowerInfoContext *ctx); + +static void +qmi_client_nas_get_tx_rx_info_ready (QmiClientNas *client, + GAsyncResult *res, + ReloadPowerInfoContext *ctx) +{ + QmiMessageNasGetTxRxInfoOutput *output; + GError *error = NULL; + + output = qmi_client_nas_get_tx_rx_info_finish (client, res, &error); + if (!output || !qmi_message_nas_get_tx_rx_info_output_get_result (output, &error)) { + g_debug ("Error loading tx/rx info: %s", error->message); + g_error_free (error); + } else { + gboolean rx0_tuned = FALSE; + gint32 rx0 = 0; + gboolean rx1_tuned = FALSE; + gint32 rx1 = 0; + gboolean in_traffic = FALSE; + gint32 tx = 0; + + qmi_message_nas_get_tx_rx_info_output_get_rx_chain_0_info (output, &rx0_tuned, &rx0, NULL, NULL, NULL, NULL, NULL); + qmi_message_nas_get_tx_rx_info_output_get_rx_chain_1_info (output, &rx1_tuned, &rx1, NULL, NULL, NULL, NULL, NULL); + qmi_message_nas_get_tx_rx_info_output_get_tx_info (output, &in_traffic, &tx, NULL); + + switch (1 << ctx->current_i) { + case QRM_DEVICE_ACT_GSM: + ctx->has_gsm = TRUE; + ctx->gsm_rx0 = rx0_tuned ? (0.1 * ((gdouble)rx0)) : -G_MAXDOUBLE; + ctx->gsm_rx1 = rx1_tuned ? (0.1 * ((gdouble)rx1)) : -G_MAXDOUBLE; + ctx->gsm_tx = in_traffic ? (0.1 * ((gdouble)tx)) : -G_MAXDOUBLE; + break; + case QRM_DEVICE_ACT_UMTS: + ctx->has_umts = TRUE; + ctx->umts_rx0 = rx0_tuned ? (0.1 * ((gdouble)rx0)) : -G_MAXDOUBLE; + ctx->umts_rx1 = rx1_tuned ? (0.1 * ((gdouble)rx1)) : -G_MAXDOUBLE; + ctx->umts_tx = in_traffic ? (0.1 * ((gdouble)tx)) : -G_MAXDOUBLE; + break; + case QRM_DEVICE_ACT_LTE: + ctx->has_lte = TRUE; + ctx->lte_rx0 = rx0_tuned ? (0.1 * ((gdouble)rx0)) : -G_MAXDOUBLE; + ctx->lte_rx1 = rx1_tuned ? (0.1 * ((gdouble)rx1)) : -G_MAXDOUBLE; + ctx->lte_tx = in_traffic ? (0.1 * ((gdouble)tx)) : -G_MAXDOUBLE; + break; + case QRM_DEVICE_ACT_CDMA: + ctx->has_cdma = TRUE; + ctx->cdma_rx0 = rx0_tuned ? (0.1 * ((gdouble)rx0)) : -G_MAXDOUBLE; + ctx->cdma_rx1 = rx1_tuned ? (0.1 * ((gdouble)rx1)) : -G_MAXDOUBLE; + ctx->cdma_tx = in_traffic ? (0.1 * ((gdouble)tx)) : -G_MAXDOUBLE; + break; + case QRM_DEVICE_ACT_EVDO: + ctx->has_evdo = TRUE; + ctx->evdo_rx0 = rx0_tuned ? (0.1 * ((gdouble)rx0)) : -G_MAXDOUBLE; + ctx->evdo_rx1 = rx1_tuned ? (0.1 * ((gdouble)rx1)) : -G_MAXDOUBLE; + ctx->evdo_tx = in_traffic ? (0.1 * ((gdouble)tx)) : -G_MAXDOUBLE; + break; + default: + g_assert_not_reached (); + } + } + + if (output) + qmi_message_nas_get_tx_rx_info_output_unref (output); + + /* Go on */ + ctx->current_i++; + reload_power_info_context_step (ctx); +} + +static void +reload_power_info_context_step (ReloadPowerInfoContext *ctx) +{ + guint i; + + for (i = ctx->current_i; i < sizeof (QrmDeviceAct) * 8; i++) { + guint32 current = 1 << i; + + /* Found the next one to query! */ + if (ctx->act & current) { + QmiMessageNasGetTxRxInfoInput *input; + QmiNasRadioInterface nas_iface; + + switch (current) { + case QRM_DEVICE_ACT_GSM: + nas_iface = QMI_NAS_RADIO_INTERFACE_GSM; + break; + case QRM_DEVICE_ACT_UMTS: + nas_iface = QMI_NAS_RADIO_INTERFACE_UMTS; + break; + case QRM_DEVICE_ACT_LTE: + nas_iface = QMI_NAS_RADIO_INTERFACE_LTE; + break; + case QRM_DEVICE_ACT_CDMA: + nas_iface = QMI_NAS_RADIO_INTERFACE_CDMA_1X; + break; + case QRM_DEVICE_ACT_EVDO: + nas_iface = QMI_NAS_RADIO_INTERFACE_CDMA_1XEVDO; + break; + default: + g_assert_not_reached (); + } + + ctx->current_i = i; + + input = qmi_message_nas_get_tx_rx_info_input_new (); + qmi_message_nas_get_tx_rx_info_input_set_radio_interface (input, nas_iface, NULL); + qmi_client_nas_get_tx_rx_info (QMI_CLIENT_NAS (ctx->self->priv->nas), + input, + 1, + NULL, + (GAsyncReadyCallback)qmi_client_nas_get_tx_rx_info_ready, + ctx); + qmi_message_nas_get_tx_rx_info_input_unref (input); + return; + } + } + + /* All done */ + reload_power_info_context_complete (ctx); +} + +static void +reload_power_info (QrmDevice *self, + QrmDeviceAct act) +{ + ReloadPowerInfoContext *ctx; + + ctx = g_slice_new0 (ReloadPowerInfoContext); + ctx->self = g_object_ref (self); + ctx->act = act; + ctx->current_i = 0; + + reload_power_info_context_step (ctx); +} + +/*****************************************************************************/ +/* Reload signal info */ + +static gdouble +get_db_from_sinr_level (QmiNasEvdoSinrLevel level) +{ + switch (level) { + case QMI_NAS_EVDO_SINR_LEVEL_0: return -9.0; + case QMI_NAS_EVDO_SINR_LEVEL_1: return -6; + case QMI_NAS_EVDO_SINR_LEVEL_2: return -4.5; + case QMI_NAS_EVDO_SINR_LEVEL_3: return -3; + case QMI_NAS_EVDO_SINR_LEVEL_4: return -2; + case QMI_NAS_EVDO_SINR_LEVEL_5: return 1; + case QMI_NAS_EVDO_SINR_LEVEL_6: return 3; + case QMI_NAS_EVDO_SINR_LEVEL_7: return 6; + case QMI_NAS_EVDO_SINR_LEVEL_8: return +9; + default: + g_warning ("Invalid SINR level '%u'", level); + return -G_MAXDOUBLE; + } +} + +static void +qmi_client_nas_get_signal_info_ready (QmiClientNas *client, + GAsyncResult *res, + QrmDevice *self) +{ + QmiMessageNasGetSignalInfoOutput *output; + GError *error = NULL; + QrmDeviceAct act = 0; + + output = qmi_client_nas_get_signal_info_finish (client, res, &error); + if (!output || !qmi_message_nas_get_signal_info_output_get_result (output, &error)) { + g_debug ("Error loading signal info: %s", error->message); + g_error_free (error); + } else { + gint8 gsm_rssi = -125; + gint8 umts_rssi = -125; + gint8 lte_rssi = -125; + gint8 cdma_rssi = -125; + gint8 evdo_rssi = -125; + gint16 umts_ecio = -1; + gint16 cdma_ecio = -1; + gint16 evdo_ecio = -1; + QmiNasEvdoSinrLevel evdo_sinr_level = QMI_NAS_EVDO_SINR_LEVEL_0; + gint32 evdo_io = -125; + gint8 lte_rsrq = -125; + gint16 lte_rsrp = -125; + gint16 lte_snr = -125; + gboolean has_gsm; + gboolean has_umts; + gboolean has_lte; + gboolean has_cdma; + gboolean has_evdo; + + /* Get signal info */ + has_gsm = qmi_message_nas_get_signal_info_output_get_gsm_signal_strength (output, &gsm_rssi, NULL); + has_umts = qmi_message_nas_get_signal_info_output_get_wcdma_signal_strength (output, &umts_rssi, &umts_ecio, NULL); + has_lte = qmi_message_nas_get_signal_info_output_get_lte_signal_strength (output, <e_rssi, <e_rsrq, <e_rsrp, <e_snr, NULL); + has_cdma = qmi_message_nas_get_signal_info_output_get_cdma_signal_strength (output, &cdma_rssi, &cdma_ecio, NULL); + has_evdo = qmi_message_nas_get_signal_info_output_get_hdr_signal_strength (output, &evdo_rssi, &evdo_ecio, &evdo_sinr_level, &evdo_io, NULL); + + if (has_gsm) + act |= QRM_DEVICE_ACT_GSM; + if (has_umts) + act |= QRM_DEVICE_ACT_UMTS; + if (has_lte) + act |= QRM_DEVICE_ACT_LTE; + if (has_cdma) + act |= QRM_DEVICE_ACT_CDMA; + if (has_evdo) + act |= QRM_DEVICE_ACT_EVDO; + + g_signal_emit (self, + signals[SIGNAL_ACT_UPDATED], + 0, + act); + + g_signal_emit (self, + signals[SIGNAL_RSSI_UPDATED], + 0, + has_gsm ? (gdouble)gsm_rssi : -G_MAXDOUBLE, + has_umts ? (gdouble)umts_rssi : -G_MAXDOUBLE, + has_lte ? (gdouble)lte_rssi : -G_MAXDOUBLE, + has_cdma ? (gdouble)cdma_rssi : -G_MAXDOUBLE, + has_evdo ? (gdouble)evdo_rssi : -G_MAXDOUBLE); + + g_signal_emit (self, + signals[SIGNAL_ECIO_UPDATED], + 0, + has_umts ? (-0.5)*((gdouble)umts_ecio) : -G_MAXDOUBLE, + has_cdma ? (-0.5)*((gdouble)cdma_ecio) : -G_MAXDOUBLE, + has_evdo ? (-0.5)*((gdouble)evdo_ecio) : -G_MAXDOUBLE); + + g_signal_emit (self, + signals[SIGNAL_SINR_LEVEL_UPDATED], + 0, + has_evdo ? get_db_from_sinr_level (evdo_sinr_level) : -G_MAXDOUBLE); + + g_signal_emit (self, + signals[SIGNAL_IO_UPDATED], + 0, + has_evdo ? ((gdouble)evdo_io) : -G_MAXDOUBLE); + + g_signal_emit (self, + signals[SIGNAL_RSRQ_UPDATED], + 0, + has_lte ? ((gdouble)lte_rsrq) : -G_MAXDOUBLE); + + g_signal_emit (self, + signals[SIGNAL_RSRP_UPDATED], + 0, + has_lte ? ((gdouble)lte_rsrp) : -G_MAXDOUBLE); + + g_signal_emit (self, + signals[SIGNAL_SNR_UPDATED], + 0, + has_lte ? ((gdouble)lte_snr) : -G_MAXDOUBLE); + } + + if (output) + qmi_message_nas_get_signal_info_output_unref (output); + + /* Now reload power info */ + reload_power_info (self, act); + + g_object_unref (self); +} + +static gboolean +info_reload_cb (QrmDevice *self) +{ + /* First, signal info */ + qmi_client_nas_get_signal_info (QMI_CLIENT_NAS (self->priv->nas), + NULL, + 10, + NULL, + (GAsyncReadyCallback)qmi_client_nas_get_signal_info_ready, + g_object_ref (self)); + return TRUE; +} + +/*****************************************************************************/ +/* Reload status */ + +static gboolean +reload_status_finish (QrmDevice *self, + GAsyncResult *res, + GError **error) +{ + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); +} + +static void +qmi_client_dms_get_pin_status_ready (QmiClientDms *dms, + GAsyncResult *res, + GSimpleAsyncResult *simple) +{ + QmiDmsUimPinStatus current_status; + guint8 pin1_status_verify_retries_left; + QmiMessageDmsUimGetPinStatusOutput *output; + GError *error = NULL; + QrmDevice *self; + + self = QRM_DEVICE (g_async_result_get_source_object (G_ASYNC_RESULT (simple))); + output = qmi_client_dms_uim_get_pin_status_finish (dms, res, &error); + if (!output) { + g_prefix_error (&error, "QMI operation failed: "); + g_simple_async_result_take_error (simple, error); + } else if (!qmi_message_dms_uim_get_pin_status_output_get_result (output, &error)) { + /* QMI error internal when checking PIN status likely means NO SIM */ + if (g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_INTERNAL)) { + self->priv->status = QRM_DEVICE_STATUS_SIM_ERROR; + g_simple_async_result_set_op_res_gboolean (simple, TRUE); + g_error_free (error); + } else if (g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_INCORRECT_PIN)) { + /* Stupid modem... retry until we get a proper result */ + qmi_client_dms_uim_get_pin_status ( + QMI_CLIENT_DMS (dms), + NULL, + 5, + NULL, + (GAsyncReadyCallback) qmi_client_dms_get_pin_status_ready, + simple); + qmi_message_dms_uim_get_pin_status_output_unref (output); + g_object_unref (self); + g_error_free (error); + return; + } else { + g_prefix_error (&error, "couldn't get PIN status: "); + g_simple_async_result_take_error (simple, error); + } + } else if (!qmi_message_dms_uim_get_pin_status_output_get_pin1_status ( + output, + ¤t_status, + &pin1_status_verify_retries_left, /* verify_retries_left */ + NULL, /* unblock_retries_left */ + &error)) { + g_prefix_error (&error, "couldn't get PIN1 status: "); + g_simple_async_result_take_error (simple, error); + } else { + switch (current_status) { + case QMI_DMS_UIM_PIN_STATUS_CHANGED: + case QMI_DMS_UIM_PIN_STATUS_UNBLOCKED: + case QMI_DMS_UIM_PIN_STATUS_DISABLED: + case QMI_DMS_UIM_PIN_STATUS_ENABLED_VERIFIED: + self->priv->pin_attempts_left = -1; + self->priv->status = QRM_DEVICE_STATUS_READY; + break; + + case QMI_DMS_UIM_PIN_STATUS_BLOCKED: + self->priv->pin_attempts_left = 0; + self->priv->status = QRM_DEVICE_STATUS_SIM_PUK_LOCKED; + break; + + case QMI_DMS_UIM_PIN_STATUS_PERMANENTLY_BLOCKED: + self->priv->pin_attempts_left = -1; + self->priv->status = QRM_DEVICE_STATUS_SIM_ERROR; + break; + + case QMI_DMS_UIM_PIN_STATUS_NOT_INITIALIZED: + case QMI_DMS_UIM_PIN_STATUS_ENABLED_NOT_VERIFIED: + self->priv->pin_attempts_left = (gint)pin1_status_verify_retries_left; + self->priv->status = QRM_DEVICE_STATUS_SIM_PIN_LOCKED; + break; + + default: + /* Unknown SIM error */ + self->priv->pin_attempts_left = -1; + self->priv->status = QRM_DEVICE_STATUS_SIM_ERROR; + break; + } + + g_simple_async_result_set_op_res_gboolean (simple, TRUE); + } + + if (output) + qmi_message_dms_uim_get_pin_status_output_unref (output); + g_object_unref (self); + + /* Notify about the internal property change */ + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_STATUS]); + + g_simple_async_result_complete (simple); + g_object_unref (simple); +} + +static void +reload_status (QrmDevice *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_return_if_fail (QMI_IS_CLIENT_DMS (self->priv->dms)); + + qmi_client_dms_uim_get_pin_status ( + QMI_CLIENT_DMS (self->priv->dms), + NULL, + 5, + cancellable, + (GAsyncReadyCallback) qmi_client_dms_get_pin_status_ready, + g_simple_async_result_new ( + G_OBJECT (self), + callback, + user_data, + reload_status)); +} + +/*****************************************************************************/ + +gboolean +qrm_device_unlock_finish (QrmDevice *self, + GAsyncResult *res, + GError **error) +{ + if (self->priv->status != QRM_DEVICE_STATUS_READY) { + g_set_error (error, + QRM_CORE_ERROR, + QRM_CORE_ERROR_FAILED, + "SIM still locked after unlock attempt"); + return FALSE; + } + + return TRUE; +} + +static void +after_pin_reload_status_ready (QrmDevice *self, + GAsyncResult *res, + GSimpleAsyncResult *simple) +{ + reload_status_finish (self, res, NULL); + + g_simple_async_result_set_op_res_gboolean (simple, TRUE); + g_simple_async_result_complete (simple); + g_object_unref (simple); +} + +static void +qmi_dms_uim_verify_pin_ready (QmiClientDms *client, + GAsyncResult *res, + GSimpleAsyncResult *simple) +{ + QmiMessageDmsUimVerifyPinOutput *output = NULL; + QrmDevice *self; + + self = QRM_DEVICE (g_async_result_get_source_object (G_ASYNC_RESULT (simple))); + + output = qmi_client_dms_uim_verify_pin_finish (client, res, NULL); + if (output) + qmi_message_dms_uim_verify_pin_output_unref (output); + + /* Ignore errors here; just reload status */ + + reload_status (self, + NULL, + (GAsyncReadyCallback)after_pin_reload_status_ready, + simple); + + g_object_unref (self); +} + +void +qrm_device_unlock (QrmDevice *self, + const gchar *pin, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *simple; + QmiMessageDmsUimVerifyPinInput *input; + + simple = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + qrm_device_unlock); + + + if (self->priv->status != QRM_DEVICE_STATUS_SIM_PIN_LOCKED) { + g_simple_async_result_set_error (simple, + QRM_CORE_ERROR, + QRM_CORE_ERROR_FAILED, + "Wrong state: PIN unlocking not required"); + g_simple_async_result_complete_in_idle (simple); + g_object_unref (simple); + return; + } + + input = qmi_message_dms_uim_verify_pin_input_new (); + qmi_message_dms_uim_verify_pin_input_set_info ( + input, + QMI_DMS_UIM_PIN_ID_PIN, + pin, + NULL); + qmi_client_dms_uim_verify_pin (QMI_CLIENT_DMS (self->priv->dms), + input, + 5, + NULL, + (GAsyncReadyCallback)qmi_dms_uim_verify_pin_ready, + simple); + qmi_message_dms_uim_verify_pin_input_unref (input); +} + +/*****************************************************************************/ +/* Stop NAS service monitoring */ + +gboolean +qrm_device_stop_nas_finish (QrmDevice *self, + GAsyncResult *res, + GError **error) +{ + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); +} + +static void +qmi_device_release_client_nas_ready (QmiDevice *device, + GAsyncResult *res, + GSimpleAsyncResult *simple) +{ + GError *error = NULL; + QrmDevice *self; + + self = QRM_DEVICE (g_async_result_get_source_object (G_ASYNC_RESULT (simple))); + + g_clear_object (&self->priv->nas); + + if (!qmi_device_release_client_finish (device, res, &error)) { + g_prefix_error (&error, "Cannot release NAS client: "); + g_simple_async_result_take_error (simple, error); + } else { + g_simple_async_result_set_op_res_gboolean (simple, TRUE); + } + + g_simple_async_result_complete (simple); + g_object_unref (simple); + g_object_unref (self); +} + +void +qrm_device_stop_nas (QrmDevice *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *simple; + + simple = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + qrm_device_stop_nas); + + /* If not started, error */ + if (!self->priv->nas) { + g_simple_async_result_set_error (simple, + QRM_CORE_ERROR, + QRM_CORE_ERROR_FAILED, + "NAS service already stopped"); + g_simple_async_result_complete_in_idle (simple); + g_object_unref (simple); + return; + } + + g_source_remove (self->priv->info_updated_id); + self->priv->info_updated_id = 0; + + qmi_device_release_client (self->priv->qmi_device, + self->priv->nas, + QMI_DEVICE_RELEASE_CLIENT_FLAGS_RELEASE_CID, + 5, + NULL, + (GAsyncReadyCallback) qmi_device_release_client_nas_ready, + simple); +} + +/*****************************************************************************/ +/* Start NAS service monitoring */ + +gboolean +qrm_device_start_nas_finish (QrmDevice *self, + GAsyncResult *res, + GError **error) +{ + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); +} + +static void +qmi_device_allocate_client_nas_ready (QmiDevice *device, + GAsyncResult *res, + GSimpleAsyncResult *simple) +{ + GError *error = NULL; + QrmDevice *self; + + self = QRM_DEVICE (g_async_result_get_source_object (G_ASYNC_RESULT (simple))); + + self->priv->nas = qmi_device_allocate_client_finish (device, res, &error); + if (!self->priv->nas) { + g_prefix_error (&error, "Cannot allocate NAS client: "); + g_simple_async_result_take_error (simple, error); + } else { + /* Start info polling */ + self->priv->info_updated_id = g_timeout_add_seconds (1, + (GSourceFunc) info_reload_cb, + self); + info_reload_cb (self); + g_simple_async_result_set_op_res_gboolean (simple, TRUE); + } + + g_simple_async_result_complete (simple); + g_object_unref (simple); + g_object_unref (self); +} + +void +qrm_device_start_nas (QrmDevice *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *simple; + + simple = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + qrm_device_start_nas); + + /* If already started, error */ + if (self->priv->nas) { + g_simple_async_result_set_error (simple, + QRM_CORE_ERROR, + QRM_CORE_ERROR_FAILED, + "NAS service already started"); + g_simple_async_result_complete_in_idle (simple); + g_object_unref (simple); + return; + } + + qmi_device_allocate_client (self->priv->qmi_device, + QMI_SERVICE_NAS, + QMI_CID_NONE, + 5, + NULL, + (GAsyncReadyCallback) qmi_device_allocate_client_nas_ready, + simple); +} + +/*****************************************************************************/ + +gboolean +qrm_device_close_finish (QrmDevice *self, + GAsyncResult *res, + GError **error) +{ + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); +} + +static void device_close_step (QrmDevice *self, + GSimpleAsyncResult *simple); +static void +close_release_dms (QmiDevice *device, + GAsyncResult *res, + GSimpleAsyncResult *simple) +{ + QrmDevice *self; + + self = QRM_DEVICE (g_async_result_get_source_object (G_ASYNC_RESULT (simple))); + + /* ignore errors */ + qmi_device_release_client_finish (device, res, NULL); + g_clear_object (&self->priv->dms); + + device_close_step (self, simple); + + g_object_unref (self); +} + +static void +close_stop_nas (QrmDevice *self, + GAsyncResult *res, + GSimpleAsyncResult *simple) +{ + /* ignore errors */ + qrm_device_stop_nas_finish (self, res, NULL); + g_assert (self->priv->nas == NULL); + + device_close_step (self, simple); +} + +static void +device_close_step (QrmDevice *self, + GSimpleAsyncResult *simple) +{ + /* Stop NAS and release client */ + if (self->priv->nas) { + qrm_device_stop_nas (self, + (GAsyncReadyCallback) close_stop_nas, + simple); + return; + } + + /* Release DMS client */ + if (self->priv->dms) { + qmi_device_release_client (self->priv->qmi_device, + self->priv->dms, + QMI_DEVICE_RELEASE_CLIENT_FLAGS_RELEASE_CID, + 5, + NULL, + (GAsyncReadyCallback) close_release_dms, + simple); + return; + } + + g_simple_async_result_set_op_res_gboolean (simple, TRUE); + g_simple_async_result_complete_in_idle (simple); + g_object_unref (simple); +} + +void +qrm_device_close (QrmDevice *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *simple; + + simple = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + qrm_device_close); + + device_close_step (self, simple); +} + +/*****************************************************************************/ + +QmiDevice * +qrm_device_peek_qmi_device (QrmDevice *self) +{ + g_return_val_if_fail (QRM_IS_DEVICE (self), NULL); + + return self->priv->qmi_device; +} + +const gchar * +qrm_device_get_manufacturer (QrmDevice *self) +{ + g_return_val_if_fail (QRM_IS_DEVICE (self), NULL); + + return self->priv->manufacturer; +} + +const gchar * +qrm_device_get_model (QrmDevice *self) +{ + g_return_val_if_fail (QRM_IS_DEVICE (self), NULL); + + return self->priv->model; +} + +const gchar * +qrm_device_get_revision (QrmDevice *self) +{ + g_return_val_if_fail (QRM_IS_DEVICE (self), NULL); + + return self->priv->revision; +} + +const gchar * +qrm_device_get_name (QrmDevice *self) +{ + g_return_val_if_fail (QRM_IS_DEVICE (self), NULL); + + return self->priv->name; +} + +QrmDeviceStatus +qrm_device_get_status (QrmDevice *self) +{ + g_return_val_if_fail (QRM_IS_DEVICE (self), QRM_DEVICE_STATUS_UNKNOWN); + + return self->priv->status; +} + +gint +qrm_device_get_pin_attempts_left (QrmDevice *self) +{ + g_return_val_if_fail (QRM_IS_DEVICE (self), -1); + + return self->priv->pin_attempts_left; +} + +/*****************************************************************************/ +/* New QRM device */ + +QrmDevice * +qrm_device_new_finish (GAsyncResult *res, + GError **error) +{ + GObject *ret; + GObject *source_object; + + source_object = g_async_result_get_source_object (res); + ret = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object), res, error); + g_object_unref (source_object); + + return (ret ? QRM_DEVICE (ret) : NULL); +} + +void +qrm_device_new (GFile *file, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_async_initable_new_async (QRM_TYPE_DEVICE, + G_PRIORITY_DEFAULT, + cancellable, + callback, + user_data, + "file", file, + NULL); +} + +/*****************************************************************************/ +/* Async init */ + +typedef struct { + QrmDevice *self; + GSimpleAsyncResult *result; + GCancellable *cancellable; +} InitContext; + +static void +init_context_complete_and_free (InitContext *ctx) +{ + g_simple_async_result_complete_in_idle (ctx->result); + if (ctx->cancellable) + g_object_unref (ctx->cancellable); + g_object_unref (ctx->result); + g_object_unref (ctx->self); + g_slice_free (InitContext, ctx); +} + +static gboolean +initable_init_finish (GAsyncInitable *initable, + GAsyncResult *result, + GError **error) +{ + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error); +} + +static void +init_reload_status_ready (QrmDevice *self, + GAsyncResult *res, + InitContext *ctx) +{ + GError *error = NULL; + + if (!reload_status_finish (self, res, &error)) { + g_prefix_error (&error, "Cannot reload modem status: "); + g_simple_async_result_take_error (ctx->result, error); + } else + g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); + + init_context_complete_and_free (ctx); +} + +static void +qmi_client_dms_get_revision_ready (QmiClientDms *dms, + GAsyncResult *res, + InitContext *ctx) +{ + QmiMessageDmsGetRevisionOutput *output; + GError *error = NULL; + const gchar *str; + + output = qmi_client_dms_get_revision_finish (dms, res, &error); + if (!output || + !qmi_message_dms_get_revision_output_get_result (output, &error) || + !qmi_message_dms_get_revision_output_get_revision (output, &str, &error)) { + g_prefix_error (&error, "Cannot get revision: "); + g_simple_async_result_take_error (ctx->result, error); + init_context_complete_and_free (ctx); + } else { + ctx->self->priv->revision = g_strdup (str); + reload_status (ctx->self, + ctx->cancellable, + (GAsyncReadyCallback) init_reload_status_ready, + ctx); + } + + if (output) + qmi_message_dms_get_revision_output_unref (output); +} + +static void +qmi_client_dms_get_model_ready (QmiClientDms *dms, + GAsyncResult *res, + InitContext *ctx) +{ + QmiMessageDmsGetModelOutput *output; + GError *error = NULL; + const gchar *str; + + output = qmi_client_dms_get_model_finish (dms, res, &error); + if (!output || + !qmi_message_dms_get_model_output_get_result (output, &error) || + !qmi_message_dms_get_model_output_get_model (output, &str, &error)) { + g_prefix_error (&error, "Cannot get model: "); + g_simple_async_result_take_error (ctx->result, error); + init_context_complete_and_free (ctx); + } else { + ctx->self->priv->model = g_strdup (str); + qmi_client_dms_get_revision (QMI_CLIENT_DMS (ctx->self->priv->dms), + NULL, + 5, + ctx->cancellable, + (GAsyncReadyCallback) qmi_client_dms_get_revision_ready, + ctx); + } + + if (output) + qmi_message_dms_get_model_output_unref (output); +} + +static void +qmi_client_dms_get_manufacturer_ready (QmiClientDms *dms, + GAsyncResult *res, + InitContext *ctx) +{ + QmiMessageDmsGetManufacturerOutput *output; + GError *error = NULL; + const gchar *str; + + output = qmi_client_dms_get_manufacturer_finish (dms, res, &error); + if (!output || + !qmi_message_dms_get_manufacturer_output_get_result (output, &error) || + !qmi_message_dms_get_manufacturer_output_get_manufacturer (output, &str, &error)) { + g_prefix_error (&error, "Cannot get manufacturer: "); + g_simple_async_result_take_error (ctx->result, error); + init_context_complete_and_free (ctx); + } else { + ctx->self->priv->manufacturer = g_strdup (str); + qmi_client_dms_get_model (QMI_CLIENT_DMS (ctx->self->priv->dms), + NULL, + 5, + ctx->cancellable, + (GAsyncReadyCallback) qmi_client_dms_get_model_ready, + ctx); + } + + if (output) + qmi_message_dms_get_manufacturer_output_unref (output); +} + +static void +qmi_device_allocate_client_ready (QmiDevice *device, + GAsyncResult *res, + InitContext *ctx) +{ + GError *error = NULL; + + ctx->self->priv->dms = qmi_device_allocate_client_finish (device, res, &error); + if (!ctx->self->priv->dms) { + g_prefix_error (&error, "Cannot allocate DMS client: "); + g_simple_async_result_take_error (ctx->result, error); + init_context_complete_and_free (ctx); + return; + } + + g_debug ("DMS client at '%s' correctly allocated", qmi_device_get_path_display (device)); + + qmi_client_dms_get_manufacturer (QMI_CLIENT_DMS (ctx->self->priv->dms), + NULL, + 5, + ctx->cancellable, + (GAsyncReadyCallback) qmi_client_dms_get_manufacturer_ready, + ctx); +} + +static void +qmi_device_open_ready (QmiDevice *device, + GAsyncResult *res, + InitContext *ctx) +{ + GError *error = NULL; + + if (!qmi_device_open_finish (device, res, &error)) { + g_prefix_error (&error, "Cannot open QMI device file: "); + g_simple_async_result_take_error (ctx->result, error); + init_context_complete_and_free (ctx); + return; + } + + g_debug ("QMI device at '%s' correctly opened", qmi_device_get_path_display (device)); + + qmi_device_allocate_client (device, + QMI_SERVICE_DMS, + QMI_CID_NONE, + 5, + ctx->cancellable, + (GAsyncReadyCallback) qmi_device_allocate_client_ready, + ctx); +} + +static void +qmi_device_new_ready (GObject *source, + GAsyncResult *res, + InitContext *ctx) +{ + GError *error = NULL; + + ctx->self->priv->qmi_device = qmi_device_new_finish (res, &error); + if (!ctx->self->priv->qmi_device) { + g_prefix_error (&error, "Cannot access QMI device file: "); + g_simple_async_result_take_error (ctx->result, error); + init_context_complete_and_free (ctx); + return; + } + + g_debug ("QMI device at '%s' correctly created", + qmi_device_get_path_display (ctx->self->priv->qmi_device)); + + qmi_device_open (ctx->self->priv->qmi_device, + (QMI_DEVICE_OPEN_FLAGS_PROXY | + QMI_DEVICE_OPEN_FLAGS_VERSION_INFO), + 5, + ctx->cancellable, + (GAsyncReadyCallback) qmi_device_open_ready, + ctx); +} + + +static void +initable_init_async (GAsyncInitable *initable, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + InitContext *ctx; + + ctx = g_slice_new0 (InitContext); + ctx->self = g_object_ref (initable); + if (cancellable) + ctx->cancellable = g_object_ref (cancellable); + ctx->result = g_simple_async_result_new (G_OBJECT (initable), + callback, + user_data, + initable_init_async); + + /* We need a proper file to initialize */ + if (!ctx->self->priv->file) { + g_simple_async_result_set_error (ctx->result, + QRM_CORE_ERROR, + QRM_CORE_ERROR_INVALID_ARGS, + "Cannot initialize QRM device: No file given"); + init_context_complete_and_free (ctx); + return; + } + + /* Create QMI device */ + qmi_device_new (ctx->self->priv->file, + ctx->cancellable, + (GAsyncReadyCallback) qmi_device_new_ready, + ctx); +} + +/*****************************************************************************/ + +static void +set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + QrmDevice *self = QRM_DEVICE (object); + + switch (prop_id) { + case PROP_FILE: + g_assert (self->priv->file == NULL); + g_assert (self->priv->qmi_device == NULL); + self->priv->file = g_value_dup_object (value); + if (self->priv->file) + self->priv->name = g_file_get_basename (self->priv->file); + break; + case PROP_QMI_DEVICE: + case PROP_STATUS: + g_assert_not_reached (); + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + QrmDevice *self = QRM_DEVICE (object); + + switch (prop_id) { + case PROP_FILE: + g_value_set_object (value, self->priv->file); + break; + case PROP_QMI_DEVICE: + g_value_set_object (value, self->priv->qmi_device); + break; + case PROP_STATUS: + g_value_set_enum (value, self->priv->status); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +qrm_device_init (QrmDevice *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, QRM_TYPE_DEVICE, QrmDevicePrivate); + self->priv->pin_attempts_left = -1; /* i.e., N/A */ +} + +static void +dispose (GObject *object) +{ + QrmDevice *self = QRM_DEVICE (object); + + if (self->priv->dms) { + qmi_device_release_client (self->priv->qmi_device, + self->priv->dms, + QMI_DEVICE_RELEASE_CLIENT_FLAGS_RELEASE_CID, + 5, + NULL, + NULL, + NULL); + g_clear_object (&self->priv->dms); + } + + if (self->priv->nas) { + g_source_remove (self->priv->info_updated_id); + self->priv->info_updated_id = 0; + + qmi_device_release_client (self->priv->qmi_device, + self->priv->nas, + QMI_DEVICE_RELEASE_CLIENT_FLAGS_RELEASE_CID, + 5, + NULL, + NULL, + NULL); + g_clear_object (&self->priv->nas); + } + + g_clear_object (&self->priv->file); + if (self->priv->qmi_device) + qmi_device_close (self->priv->qmi_device, NULL); + g_clear_object (&self->priv->qmi_device); + + G_OBJECT_CLASS (qrm_device_parent_class)->dispose (object); +} + +static void +finalize (GObject *object) +{ + QrmDevice *self = QRM_DEVICE (object); + + g_free (self->priv->name); + g_free (self->priv->manufacturer); + g_free (self->priv->model); + g_free (self->priv->revision); + + G_OBJECT_CLASS (qrm_device_parent_class)->finalize (object); +} + +static void +async_initable_iface_init (GAsyncInitableIface *iface) +{ + iface->init_async = initable_init_async; + iface->init_finish = initable_init_finish; +} + +static void +qrm_device_class_init (QrmDeviceClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (QrmDevicePrivate)); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + object_class->dispose = dispose; + + properties[PROP_FILE] = + g_param_spec_object ("file", + "File", + "File to the underlying QMI port", + G_TYPE_FILE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + g_object_class_install_property (object_class, PROP_FILE, properties[PROP_FILE]); + + properties[PROP_QMI_DEVICE] = + g_param_spec_object ("qmi-device", + "QMI Device", + "QMI Device to the underlying QMI port", + QMI_TYPE_DEVICE, + G_PARAM_READABLE); + g_object_class_install_property (object_class, PROP_QMI_DEVICE, properties[PROP_QMI_DEVICE]); + + properties[PROP_STATUS] = + g_param_spec_enum ("status", + "Status", + "Status of the modem", + QRM_TYPE_DEVICE_STATUS, + QRM_DEVICE_STATUS_UNKNOWN, + G_PARAM_READABLE); + g_object_class_install_property (object_class, PROP_STATUS, properties[PROP_STATUS]); + + signals[SIGNAL_ACT_UPDATED] = + g_signal_new ("act-updated", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (QrmDeviceClass, act_updated), + NULL, NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, + 1, + QRM_TYPE_DEVICE_ACT); + + + signals[SIGNAL_RSSI_UPDATED] = + g_signal_new ("rssi-updated", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (QrmDeviceClass, rssi_updated), + NULL, NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, + 5, + G_TYPE_DOUBLE, + G_TYPE_DOUBLE, + G_TYPE_DOUBLE, + G_TYPE_DOUBLE, + G_TYPE_DOUBLE); + + signals[SIGNAL_ECIO_UPDATED] = + g_signal_new ("ecio-updated", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (QrmDeviceClass, ecio_updated), + NULL, NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, + 3, + G_TYPE_DOUBLE, + G_TYPE_DOUBLE, + G_TYPE_DOUBLE); + + signals[SIGNAL_SINR_LEVEL_UPDATED] = + g_signal_new ("sinr-level-updated", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (QrmDeviceClass, sinr_level_updated), + NULL, NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, + 1, + G_TYPE_DOUBLE); + + signals[SIGNAL_IO_UPDATED] = + g_signal_new ("io-updated", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (QrmDeviceClass, io_updated), + NULL, NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, + 1, + G_TYPE_DOUBLE); + + signals[SIGNAL_RSRQ_UPDATED] = + g_signal_new ("rsrq-updated", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (QrmDeviceClass, rsrq_updated), + NULL, NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, + 1, + G_TYPE_DOUBLE); + + signals[SIGNAL_RSRP_UPDATED] = + g_signal_new ("rsrp-updated", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (QrmDeviceClass, rsrp_updated), + NULL, NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, + 1, + G_TYPE_DOUBLE); + + signals[SIGNAL_SNR_UPDATED] = + g_signal_new ("snr-updated", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (QrmDeviceClass, snr_updated), + NULL, NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, + 1, + G_TYPE_DOUBLE); + + signals[SIGNAL_RX0_UPDATED] = + g_signal_new ("rx0-updated", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (QrmDeviceClass, rx0_updated), + NULL, NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, + 5, + G_TYPE_DOUBLE, + G_TYPE_DOUBLE, + G_TYPE_DOUBLE, + G_TYPE_DOUBLE, + G_TYPE_DOUBLE); + + signals[SIGNAL_RX1_UPDATED] = + g_signal_new ("rx1-updated", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (QrmDeviceClass, rx1_updated), + NULL, NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, + 5, + G_TYPE_DOUBLE, + G_TYPE_DOUBLE, + G_TYPE_DOUBLE, + G_TYPE_DOUBLE, + G_TYPE_DOUBLE); + + signals[SIGNAL_TX_UPDATED] = + g_signal_new ("tx-updated", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (QrmDeviceClass, tx_updated), + NULL, NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, + 5, + G_TYPE_DOUBLE, + G_TYPE_DOUBLE, + G_TYPE_DOUBLE, + G_TYPE_DOUBLE, + G_TYPE_DOUBLE); +} diff --git a/src/qmi-radio-monitor/qrm-device.h b/src/qmi-radio-monitor/qrm-device.h new file mode 100644 index 00000000..4ac6b99b --- /dev/null +++ b/src/qmi-radio-monitor/qrm-device.h @@ -0,0 +1,163 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * Copyright (C) 2013-2017 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef QRM_DEVICE_H +#define QRM_DEVICE_H + +#include <gtk/gtk.h> +#include <libqmi-glib.h> + +G_BEGIN_DECLS + +#define QRM_TYPE_DEVICE (qrm_device_get_type ()) +#define QRM_DEVICE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), QRM_TYPE_DEVICE, QrmDevice)) +#define QRM_DEVICE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), QRM_TYPE_DEVICE, QrmDeviceClass)) +#define QRM_IS_DEVICE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), QRM_TYPE_DEVICE)) +#define QRM_IS_DEVICE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), QRM_TYPE_DEVICE)) +#define QRM_DEVICE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), QRM_TYPE_DEVICE, QrmDeviceClass)) + +typedef enum { + QRM_DEVICE_STATUS_UNKNOWN, + QRM_DEVICE_STATUS_READY, + QRM_DEVICE_STATUS_SIM_PIN_LOCKED, + QRM_DEVICE_STATUS_SIM_PUK_LOCKED, + QRM_DEVICE_STATUS_SIM_ERROR, +} QrmDeviceStatus; + +typedef enum { + QRM_DEVICE_ACT_GSM = 1 << 0, + QRM_DEVICE_ACT_UMTS = 1 << 1, + QRM_DEVICE_ACT_LTE = 1 << 2, + QRM_DEVICE_ACT_CDMA = 1 << 3, + QRM_DEVICE_ACT_EVDO = 1 << 4, +} QrmDeviceAct; + +typedef struct _QrmDevice QrmDevice; +typedef struct _QrmDeviceClass QrmDeviceClass; +typedef struct _QrmDevicePrivate QrmDevicePrivate; + +struct _QrmDevice { + GObject parent_instance; + QrmDevicePrivate *priv; +}; + +struct _QrmDeviceClass { + GObjectClass parent_class; + + /* Signals */ + + void (*act_updated) (QrmDevice *device, + QrmDeviceAct act); + + void (*rssi_updated) (QrmDevice *device, + gdouble gsm_rssi, + gdouble umts_rssi, + gdouble lte_rssi, + gdouble cdma_rssi, + gdouble evdo_rssi); + + void (*ecio_updated) (QrmDevice *device, + gdouble umts_ecio, + gdouble cdma_ecio, + gdouble evdo_ecio); + + void (*sinr_level_updated) (QrmDevice *device, + gdouble evdo_sinr_level); + + void (*io_updated) (QrmDevice *device, + gdouble evdo_io); + + void (*rsrq_updated) (QrmDevice *device, + gdouble lte_rsrq); + + void (*rsrp_updated) (QrmDevice *device, + gdouble lte_rsrp); + + void (*snr_updated) (QrmDevice *device, + gdouble lte_snr); + + void (*rx0_updated) (QrmDevice *device, + gdouble gsm_rx0, + gdouble umts_rx0, + gdouble lte_rx0, + gdouble cdma_rx0, + gdouble evdo_rx0); + + void (*rx1_updated) (QrmDevice *device, + gdouble gsm_rx1, + gdouble umts_rx1, + gdouble lte_rx1, + gdouble cdma_rx1, + gdouble evdo_rx1); + + void (*tx_updated) (QrmDevice *device, + gdouble gsm_tx, + gdouble umts_tx, + gdouble lte_tx, + gdouble cdma_tx, + gdouble evdo_tx); +}; + +GType qrm_device_get_type (void) G_GNUC_CONST; + +void qrm_device_new (GFile *file, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +QrmDevice *qrm_device_new_finish (GAsyncResult *res, + GError **error); + +const gchar *qrm_device_get_name (QrmDevice *self); +const gchar *qrm_device_get_manufacturer (QrmDevice *self); +const gchar *qrm_device_get_model (QrmDevice *self); +const gchar *qrm_device_get_revision (QrmDevice *self); +QrmDeviceStatus qrm_device_get_status (QrmDevice *self); +gint qrm_device_get_pin_attempts_left (QrmDevice *self); + +QmiDevice *qrm_device_peek_qmi_device (QrmDevice *self); + +void qrm_device_unlock (QrmDevice *self, + const gchar *pin, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean qrm_device_unlock_finish (QrmDevice *self, + GAsyncResult *res, + GError **error); + +void qrm_device_start_nas (QrmDevice *self, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean qrm_device_start_nas_finish (QrmDevice *self, + GAsyncResult *res, + GError **error); + +void qrm_device_stop_nas (QrmDevice *self, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean qrm_device_stop_nas_finish (QrmDevice *self, + GAsyncResult *res, + GError **error); + +void qrm_device_close (QrmDevice *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean qrm_device_close_finish (QrmDevice *self, + GAsyncResult *res, + GError **error); + +G_END_DECLS + +#endif /* QRM_DEVICE_H */ diff --git a/src/qmi-radio-monitor/qrm-error.h b/src/qmi-radio-monitor/qrm-error.h new file mode 100644 index 00000000..793269ff --- /dev/null +++ b/src/qmi-radio-monitor/qrm-error.h @@ -0,0 +1,29 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * Copyright (C) 2013-2017 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef QRM_ERROR_H +#define QRM_ERROR_H + +/* Prefixes for errors registered in DBus */ +#define QRM_DBUS_ERROR_PREFIX "org.freedesktop.qrm.Error" +#define QRM_CORE_ERROR_DBUS_PREFIX QRM_DBUS_ERROR_PREFIX ".Core" + +typedef enum { /*< underscore_name=qrm_core_error >*/ + QRM_CORE_ERROR_FAILED = 0, /*< nick=Failed >*/ + QRM_CORE_ERROR_INVALID_ARGS = 1, /*< nick=InvalidArgs >*/ + QRM_CORE_ERROR_UNSUPPORTED = 2 /*< nick=Unsupported >*/ +} QrmCoreError; + +#endif /* QRM_ERROR_H */ diff --git a/src/qmi-radio-monitor/qrm-graph.c b/src/qmi-radio-monitor/qrm-graph.c new file mode 100644 index 00000000..27456180 --- /dev/null +++ b/src/qmi-radio-monitor/qrm-graph.c @@ -0,0 +1,823 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details: + * + * Copyright (C) 2013-2017 Aleksander Morgado <aleksander@aleksander.es> + * + * Graph drawing based on gnome-system-monitor's LoadGraph implementation. + */ + + +#include "qrm-graph.h" +#include "qrm-enum-types.h" +#include "qrm-color-icon.h" + +#include <math.h> + +/* Number of points to show in the graph */ +#define NUM_POINTS 61 + +/* Number of horizontal separators in the graph */ +#define N_HORIZONTAL_SEPARATORS 6 + +/* Width of the graph frame */ +#define FRAME_WIDTH 2 + +/* Indentation of the actual graph within the drawing area */ +#define INDENT 24.0 + +/* Font size */ +#define FONTSIZE 8.0 + +/* Right margin; to account for the space needed to display the + * horizontal line label */ +#define RIGHT_LABEL_MARGIN (8 * FONTSIZE) + +/* Bottom label vertical margin */ +#define BOTTOM_LABEL_MARGIN 15 + +G_DEFINE_TYPE (QrmGraph, qrm_graph, GTK_TYPE_BOX) + +enum { + PROP_0, + PROP_N_SERIES, + PROP_Y_MIN, + PROP_Y_MAX, + PROP_Y_UNITS, + PROP_Y_N_SEPARATORS, + PROP_TITLE, + PROP_LEGEND_POSITION, + PROP_LAST +}; + +static GParamSpec *properties[PROP_LAST]; + +typedef struct { + gchar *text; + GdkRGBA color; + gdouble data[NUM_POINTS]; + GtkWidget *box; + GtkWidget *box_icon; + GtkWidget *box_label; + GtkWidget *box_value; +} Series; + +struct _QrmGraphPrivate { + /* Properties */ + guint n_series; + gdouble y_min; + gdouble y_max; + gchar *y_units; + guint y_n_separators; + gchar *title; + QrmGraphLegendPosition legend_position; + + /* The series data block */ + Series *series; + + /* The current step index */ + guint step_index; + + /* Graph title label */ + GtkWidget *title_label; + + /* The drawing area */ + GtkWidget *drawing_area; + + /* Legend box */ + GtkWidget *legend_box; + + /* Actual width and height available for drawing in the widget, + * changes whenever the widget allocation size changes */ + guint draw_width; + guint draw_height; + + /* The background surface */ + cairo_pattern_t *background; + + /* Useful coordinates updated whenever background is changed */ + gdouble plot_area_offset_x0; + gdouble plot_area_offset_y0; + gdouble plot_area_width; + gdouble plot_area_height; +}; + +/*****************************************************************************/ +/* Series management */ + +static void +free_series (QrmGraph *self) +{ + guint i; + + if (!self->priv->series) + return; + + for (i = 0; i < self->priv->n_series; i++) + g_free (self->priv->series[i].text); + g_free (self->priv->series); + self->priv->series = NULL; +} + +void +qrm_graph_clear_series (QrmGraph *self, + guint series_index) +{ + guint i; + + for (i = 0; i < NUM_POINTS; i++) + self->priv->series[series_index].data[i] = -G_MAXDOUBLE; + + g_free (self->priv->series[series_index].text); + self->priv->series[series_index].text = NULL; + + if (self->priv->series[series_index].box) { + if (self->priv->legend_box) + gtk_container_remove (GTK_CONTAINER (self->priv->legend_box), + self->priv->series[series_index].box); + self->priv->series[series_index].box = NULL; + self->priv->series[series_index].box_icon = NULL; + self->priv->series[series_index].box_label = NULL; + self->priv->series[series_index].box_value = NULL; + } +} + +static void +allocate_series (QrmGraph *self) +{ + guint i; + + if (!self->priv->n_series) + return; + + self->priv->series = g_new0 (Series, self->priv->n_series); + for (i = 0; i < self->priv->n_series; i++) + qrm_graph_clear_series (self, i); +} + +void +qrm_graph_setup_series (QrmGraph *self, + guint series_index, + const gchar *label, + guint8 color_red, + guint8 color_green, + guint8 color_blue) +{ + g_assert_cmpuint (series_index, <, self->priv->n_series); + + qrm_graph_clear_series (self, series_index); + + self->priv->series[series_index].text = g_strdup (label); + self->priv->series[series_index].color.red = ((gdouble)color_red) / 255.0; + self->priv->series[series_index].color.green = ((gdouble)color_green) / 255.0; + self->priv->series[series_index].color.blue = ((gdouble)color_blue) / 255.0; + self->priv->series[series_index].color.alpha = 1.0; + + self->priv->series[series_index].box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 8); + if (self->priv->legend_box) + gtk_box_pack_start (GTK_BOX (self->priv->legend_box), self->priv->series[series_index].box, TRUE, TRUE, 0); + gtk_widget_show (self->priv->series[series_index].box); + + self->priv->series[series_index].box_icon = qrm_color_icon_new (&self->priv->series[series_index].color); + gtk_box_pack_start (GTK_BOX (self->priv->series[series_index].box), self->priv->series[series_index].box_icon, FALSE, TRUE, 0); + gtk_widget_show (self->priv->series[series_index].box_icon); + + self->priv->series[series_index].box_label = gtk_label_new (label); + gtk_box_pack_start (GTK_BOX (self->priv->series[series_index].box), self->priv->series[series_index].box_label, FALSE, TRUE, 0); + gtk_widget_show (self->priv->series[series_index].box_label); + + self->priv->series[series_index].box_value = gtk_label_new ("N/A"); + gtk_box_pack_start (GTK_BOX (self->priv->series[series_index].box), self->priv->series[series_index].box_value, FALSE, TRUE, 0); + gtk_widget_show (self->priv->series[series_index].box_value); +} + +/*****************************************************************************/ +/* Adding new values to the series */ + +void +qrm_graph_step_init (QrmGraph *self) +{ + guint i; + + for (i = 0; i < self->priv->n_series; i++) + self->priv->series[i].data[self->priv->step_index] = -G_MAXDOUBLE; +} + +void +qrm_graph_step_set_value (QrmGraph *self, + guint series_index, + gdouble value, + GtkLabel *additional_label) +{ + g_assert_cmpuint (series_index, <, self->priv->n_series); + g_assert_cmpuint (self->priv->step_index, <, NUM_POINTS); + + self->priv->series[series_index].data[self->priv->step_index] = + CLAMP (value, self->priv->y_min, self->priv->y_max); + + if (value < self->priv->y_min || + value > self->priv->y_max) { + gtk_label_set_text (GTK_LABEL (self->priv->series[series_index].box_value), "N/A"); + if (additional_label) + gtk_label_set_text (additional_label, "N/A"); + } else { + gchar *str; + + str = g_strdup_printf ("%.2lf %s", + self->priv->series[series_index].data[self->priv->step_index], + self->priv->y_units); + gtk_label_set_text (GTK_LABEL (self->priv->series[series_index].box_value), str); + if (additional_label) + gtk_label_set_text (additional_label, str); + g_free (str); + } +} + +void +qrm_graph_step_finish (QrmGraph *self) +{ + /* Update step */ + self->priv->step_index++; + if (self->priv->step_index == NUM_POINTS) + self->priv->step_index = 0; + + g_assert_cmpuint (self->priv->step_index, <, NUM_POINTS); + + /* Repaint */ + gtk_widget_queue_draw (self->priv->drawing_area); +} + +/*****************************************************************************/ +/* Graph background management */ + +static void +graph_background_clear (QrmGraph *self) +{ + if (!self->priv->background) + return; + + cairo_pattern_destroy (self->priv->background); + self->priv->background = NULL; +} + +static void +graph_background_pattern_create (QrmGraph *self) +{ + GtkAllocation allocation; + cairo_t *cr; + GtkStyleContext *context; + GdkRGBA fg; + PangoLayout *layout; + PangoFontDescription *font_desc; + gdouble dash[2] = { 1.0, 2.0 }; + guint i; + guint vertical_separator_relative_height; + cairo_surface_t *background; + + /* Create surface ad cairo context */ + gtk_widget_get_allocation (self->priv->drawing_area, &allocation); + background = gdk_window_create_similar_surface (gtk_widget_get_window (self->priv->drawing_area), + CAIRO_CONTENT_COLOR_ALPHA, + allocation.width, + allocation.height); + cr = cairo_create (background); + + /* Setup foreground color from style context */ + context = gtk_widget_get_style_context (self->priv->drawing_area); + gtk_style_context_get_color (context, + GTK_STATE_FLAG_NORMAL, + &fg); + + /* Setup pango */ + cairo_paint_with_alpha (cr, 0.0); + layout = pango_cairo_create_layout (cr); + gtk_style_context_get (context, + GTK_STATE_FLAG_NORMAL, + GTK_STYLE_PROPERTY_FONT, &font_desc, + NULL); + pango_font_description_set_size (font_desc, FONTSIZE * PANGO_SCALE); + pango_layout_set_font_description (layout, font_desc); + pango_font_description_free (font_desc); + + /* Compute actual plotting area (i.e. area where the lines will be drawn). + * The plot area height is computed based on the number of separators and the + * fixed size of each separator. */ + vertical_separator_relative_height = (guint)(((gdouble)(self->priv->draw_height - BOTTOM_LABEL_MARGIN)) / + ((gdouble)self->priv->y_n_separators)); + self->priv->plot_area_height = vertical_separator_relative_height * self->priv->y_n_separators; + self->priv->plot_area_width = self->priv->draw_width - INDENT - RIGHT_LABEL_MARGIN; + self->priv->plot_area_offset_x0 = FRAME_WIDTH + INDENT + 1; + self->priv->plot_area_offset_y0 = FRAME_WIDTH + self->priv->plot_area_height - 1; + + /* Draw background rectangle */ + cairo_translate (cr, FRAME_WIDTH, FRAME_WIDTH); + cairo_set_source_rgb (cr, 1.0, 1.0, 1.0); + cairo_rectangle (cr, + INDENT, 0, + self->priv->plot_area_width, self->priv->plot_area_height); + cairo_fill (cr); + + /* Draw horizontal lines (vertical separators) */ + cairo_set_line_width (cr, 1.0); + cairo_set_dash (cr, dash, 2, 0); + for (i = 0; i <= self->priv->y_n_separators; ++i) { + PangoRectangle extents; + gdouble y; + gchar *caption; + + if (i == 0) + y = 0.5 + FONTSIZE / 2.0; + else if (i == self->priv->y_n_separators) + y = i * vertical_separator_relative_height + 0.5; + else + y = i * vertical_separator_relative_height + FONTSIZE / 2.0; + + /* Draw line */ + cairo_set_source_rgba (cr, 0, 0, 0, 0.75); + cairo_move_to (cr, + INDENT, + i * vertical_separator_relative_height + 0.5); + cairo_line_to (cr, + self->priv->draw_width - RIGHT_LABEL_MARGIN + 0.5 + 4, + i * vertical_separator_relative_height + 0.5); + + /* Draw caption */ + gdk_cairo_set_source_rgba (cr, &fg); + caption = g_strdup_printf ("%d %s", + (gint)(self->priv->y_max - i * ((self->priv->y_max - self->priv->y_min) / self->priv->y_n_separators)), + self->priv->y_units); + pango_layout_set_alignment (layout, PANGO_ALIGN_LEFT); + pango_layout_set_text (layout, caption, -1); + pango_layout_get_extents (layout, NULL, &extents); + cairo_move_to (cr, + self->priv->draw_width - INDENT - 30, + y - 1.0 * extents.height / PANGO_SCALE / 2); + pango_cairo_show_layout (cr, layout); + g_free (caption); + } + cairo_stroke (cr); + + /* Draw vertical lines (horizontal separators) */ + cairo_set_dash (cr, dash, 2, 1.5); + for (i = 0; i <= N_HORIZONTAL_SEPARATORS; i++) { + PangoRectangle extents; + double x; + gchar *caption; + + /* Draw line */ + x = i * (self->priv->draw_width - RIGHT_LABEL_MARGIN - INDENT) / N_HORIZONTAL_SEPARATORS; + cairo_set_source_rgba (cr, 0, 0, 0, 0.75); + cairo_move_to (cr, (ceil (x) + 0.5) + INDENT, 0.5); + cairo_line_to (cr, (ceil (x) + 0.5) + INDENT, self->priv->plot_area_height + 4.5); + cairo_stroke (cr); + + /* Draw caption */ + caption = g_strdup_printf (i == 0 ? "%u seconds" : "%u", i * 10); + pango_layout_set_text (layout, caption, -1); + pango_layout_get_extents (layout, NULL, &extents); + cairo_move_to (cr, + (ceil (x) + 0.5 + INDENT) - (1.0 * extents.width / PANGO_SCALE / 2), + self->priv->draw_height - 1.0 * extents.height / PANGO_SCALE); + gdk_cairo_set_source_rgba (cr, &fg); + pango_cairo_show_layout (cr, layout); + g_free (caption); + } + cairo_stroke (cr); + + cairo_destroy (cr); + g_object_unref (layout); + + self->priv->background = cairo_pattern_create_for_surface (background); + cairo_surface_destroy (background); +} + +/*****************************************************************************/ +/* Reconfigure draw */ + +static gboolean +graph_configure (GtkWidget *widget, + GdkEventConfigure *event, + QrmGraph *self) +{ + GtkAllocation allocation; + + gtk_widget_get_allocation (widget, &allocation); + self->priv->draw_width = allocation.width - 2 * FRAME_WIDTH; + self->priv->draw_height = allocation.height - 2 * FRAME_WIDTH; + + /* repaint */ + graph_background_clear (self); + gtk_widget_queue_draw (self->priv->drawing_area); + + return TRUE; +} + +/*****************************************************************************/ +/* Graph draw */ + +static gboolean +graph_draw (GtkWidget *widget, + cairo_t *context, + QrmGraph *self) +{ + GdkWindow *window; + cairo_t *cr; + guint i; + gdouble x_ratio; + gdouble y_ratio; + guint current_step_index; + + window = gtk_widget_get_window (self->priv->drawing_area); + + if (!self->priv->background) + graph_background_pattern_create (self); + + cr = gdk_cairo_create (window); + cairo_set_source (cr, self->priv->background); + cairo_rectangle (cr, + 0, 0, + self->priv->draw_width, self->priv->draw_height); + cairo_fill (cr); + cairo_set_line_width (cr, 1); + cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); + cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND); + cairo_rectangle (cr, + self->priv->plot_area_offset_x0, + self->priv->plot_area_offset_y0 - self->priv->plot_area_height, + self->priv->plot_area_width, + self->priv->plot_area_height); + cairo_clip (cr); + + /* Compute ratios to convert from values to pixels */ + x_ratio = ((gdouble)self->priv->plot_area_width) / ((gdouble)NUM_POINTS - 1); + y_ratio = ((gdouble)self->priv->plot_area_height) / ((gdouble)(self->priv->y_max - self->priv->y_min)); + + /* Mark which is the current point */ + if (self->priv->step_index == 0) + current_step_index = NUM_POINTS - 1; + else + current_step_index = self->priv->step_index - 1; + + /* Print series */ + for (i = 0; i < self->priv->n_series; i++) { + guint j; + guint seconds; + gdouble x1; + gdouble y1; + gdouble x2; + gdouble y2; + gdouble x3; + gdouble y3; + + /* Set series color */ + gdk_cairo_set_source_rgba (cr, &(self->priv->series[i].color)); + + /* Convert the current (seconds,value) to the corresponding amount of pixels */ + x3 = 0; + y3 = (self->priv->series[i].data[current_step_index] - self->priv->y_min) * y_ratio; + + cairo_move_to (cr, + self->priv->plot_area_offset_x0 - x3, + self->priv->plot_area_offset_y0 - y3); + + seconds = 0; + j = current_step_index; + do { + + + seconds++; + if (j == 0) + j = NUM_POINTS - 1; + else + j--; + + if (self->priv->series[i].data[j] < self->priv->y_min) + continue; + + /* Convert the previous (seconds,value) to the corresponding amount of pixels */ + x3 = seconds * x_ratio; + y3 = (self->priv->series[i].data[j] - self->priv->y_min) * y_ratio; + + /* Additional control points for the bezier spline */ + x1 = ((gdouble)seconds - 1.0f + 0.5f) * x_ratio; + y1 = (self->priv->series[i].data[j == (NUM_POINTS - 1) ? 0 : j + 1] - self->priv->y_min) * y_ratio; + x2 = ((gdouble)seconds - 0.5f) * x_ratio; + y2 = (self->priv->series[i].data[j] - self->priv->y_min) * y_ratio; + + cairo_curve_to (cr, + self->priv->plot_area_offset_x0 + x1, + self->priv->plot_area_offset_y0 - y1, + self->priv->plot_area_offset_x0 + x2, + self->priv->plot_area_offset_y0 - y2, + self->priv->plot_area_offset_x0 + x3, + self->priv->plot_area_offset_y0 - y3); + + } while (j != self->priv->step_index); + + cairo_stroke (cr); + } + + cairo_destroy (cr); + + return TRUE; +} + +/*****************************************************************************/ +/* Update graph title */ + +static void +update_graph_title (QrmGraph *self) +{ + gchar *str; + + if (!self->priv->title_label) + return; + + str = (self->priv->title ? + g_strdup_printf ("<span weight=\"bold\">%s</span>", self->priv->title) : + g_strdup ("")); + gtk_label_set_markup (GTK_LABEL (self->priv->title_label), str); + g_free (str); +} + +/*****************************************************************************/ +/* New QRM graph */ + +GtkWidget * +qrm_graph_new (void) +{ + return GTK_WIDGET (g_object_new (QRM_TYPE_GRAPH, NULL)); +} + +/*****************************************************************************/ + +static void +qrm_graph_init (QrmGraph *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, QRM_TYPE_GRAPH, QrmGraphPrivate); + + /* Defaults */ + self->priv->n_series = 0; + self->priv->y_min = 0.0; + self->priv->y_max = 100.0; + self->priv->y_units = g_strdup ("%"); + self->priv->y_n_separators = 5; + self->priv->legend_position = QRM_GRAPH_LEGEND_POSITION_BOTTOM; +} + +static void +constructed (GObject *object) +{ + QrmGraph *self = QRM_GRAPH (object); + + /* Additional property defaults */ + g_object_set (object, + "orientation", GTK_ORIENTATION_VERTICAL, + "spacing", 6, + "margin-left", 8, + "margin-right", 8, + "margin-top", 8, + "margin-bottom", 8, + NULL); + + /* Setup title */ + self->priv->title_label = gtk_label_new (""); + gtk_widget_set_halign (self->priv->title_label, GTK_ALIGN_START); + gtk_widget_show (self->priv->title_label); + update_graph_title (self); + + /* Setup drawing area */ + self->priv->drawing_area = gtk_drawing_area_new (); + gtk_widget_set_events (self->priv->drawing_area, GDK_EXPOSURE_MASK); + g_signal_connect (G_OBJECT (self->priv->drawing_area), + "draw", + G_CALLBACK (graph_draw), + self); + g_signal_connect (G_OBJECT (self->priv->drawing_area), + "configure-event", + G_CALLBACK (graph_configure), + self); +#if GTK_CHECK_VERSION(3,12,0) + gtk_widget_set_margin_start (self->priv->drawing_area, 8); +#else + gtk_widget_set_margin_left (self->priv->drawing_area, 8); +#endif + + gtk_widget_set_margin_top (self->priv->drawing_area, 4); + gtk_widget_set_margin_bottom (self->priv->drawing_area, 4); + + gtk_widget_show (self->priv->drawing_area); + + if (self->priv->legend_position == QRM_GRAPH_LEGEND_POSITION_NONE) { + /* No legend, just title and graph */ + gtk_box_pack_start (GTK_BOX (self), self->priv->title_label, FALSE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (self), self->priv->drawing_area, TRUE, TRUE, 0); + } else { + /* Legend box */ + self->priv->legend_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 8); + gtk_widget_set_hexpand (self->priv->legend_box, TRUE); +#if GTK_CHECK_VERSION(3,12,0) + gtk_widget_set_margin_start (self->priv->legend_box, 16); +#else + gtk_widget_set_margin_left (self->priv->legend_box, 16); +#endif + gtk_widget_set_margin_top (self->priv->legend_box, 4); + gtk_widget_set_margin_bottom (self->priv->legend_box, 4); + gtk_widget_show (self->priv->legend_box); + + if (self->priv->legend_position == QRM_GRAPH_LEGEND_POSITION_BOTTOM) { + gtk_box_pack_start (GTK_BOX (self), self->priv->title_label, FALSE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (self), self->priv->drawing_area, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (self), self->priv->legend_box, FALSE, TRUE, 0); + } else { + GtkWidget *titlebox; + + /* Setup titlebox */ + titlebox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 20); + gtk_box_pack_start (GTK_BOX (titlebox), self->priv->title_label, FALSE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (titlebox), self->priv->legend_box, FALSE, TRUE, 0); + gtk_widget_show (titlebox); + + gtk_box_pack_start (GTK_BOX (self), titlebox, FALSE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (self), self->priv->drawing_area, TRUE, TRUE, 0); + } + } + + G_OBJECT_CLASS (qrm_graph_parent_class)->constructed (object); +} + +static void +set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + QrmGraph *self = QRM_GRAPH (object); + + switch (prop_id) { + case PROP_N_SERIES: + free_series (self); + self->priv->n_series = g_value_get_uint (value); + allocate_series (self); + break; + case PROP_Y_MIN: + self->priv->y_min = g_value_get_double (value); + break; + case PROP_Y_MAX: + self->priv->y_max = g_value_get_double (value); + break; + case PROP_Y_UNITS: + g_free (self->priv->y_units); + self->priv->y_units = g_value_dup_string (value); + break; + case PROP_Y_N_SEPARATORS: + self->priv->y_n_separators = g_value_get_uint (value); + break; + case PROP_TITLE: + g_free (self->priv->title); + self->priv->title = g_value_dup_string (value); + update_graph_title (self); + break; + case PROP_LEGEND_POSITION: + self->priv->legend_position = g_value_get_enum (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + QrmGraph *self = QRM_GRAPH (object); + + switch (prop_id) { + case PROP_N_SERIES: + g_value_set_uint (value, self->priv->n_series); + break; + case PROP_Y_MIN: + g_value_set_double (value, self->priv->y_min); + break; + case PROP_Y_MAX: + g_value_set_double (value, self->priv->y_max); + break; + case PROP_Y_UNITS: + g_value_set_string (value, self->priv->y_units); + break; + case PROP_Y_N_SEPARATORS: + g_value_set_uint (value, self->priv->y_n_separators); + break; + case PROP_TITLE: + g_value_set_string (value, self->priv->title); + break; + case PROP_LEGEND_POSITION: + g_value_set_enum (value, self->priv->legend_position); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +finalize (GObject *object) +{ + QrmGraph *self = QRM_GRAPH (object); + + if (self->priv->series) + free_series (self); + g_free (self->priv->y_units); + g_free (self->priv->title); + + G_OBJECT_CLASS (qrm_graph_parent_class)->finalize (object); +} + +static void +qrm_graph_class_init (QrmGraphClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (QrmGraphPrivate)); + + object_class->constructed = constructed; + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + properties[PROP_N_SERIES] = + g_param_spec_uint ("n-series", + "Number of series", + "Number of series to handle in the graph", + 0, + G_MAXUINT, + 0, + G_PARAM_READWRITE); + g_object_class_install_property (object_class, PROP_N_SERIES, properties[PROP_N_SERIES]); + + properties[PROP_Y_MIN] = + g_param_spec_double ("y-min", + "Minumum Y value", + "Minimum value in the Y axis", + -G_MAXDOUBLE, + G_MAXDOUBLE, + 0.0, + G_PARAM_READWRITE); + g_object_class_install_property (object_class, PROP_Y_MIN, properties[PROP_Y_MIN]); + + properties[PROP_Y_MAX] = + g_param_spec_double ("y-max", + "Maximum Y value", + "Maximum value in the Y axis", + -G_MAXDOUBLE, + G_MAXDOUBLE, + 100.0, + G_PARAM_READWRITE); + g_object_class_install_property (object_class, PROP_Y_MAX, properties[PROP_Y_MAX]); + + properties[PROP_Y_UNITS] = + g_param_spec_string ("y-units", + "Y units", + "Units in the Y axis", + "%", + G_PARAM_READWRITE); + g_object_class_install_property (object_class, PROP_Y_UNITS, properties[PROP_Y_UNITS]); + + properties[PROP_Y_N_SEPARATORS] = + g_param_spec_uint ("y-n-separators", + "Number of vertical separators", + "Number of horizontal lines splitting the graph in the Y axis", + 1, + G_MAXUINT, + 5, + G_PARAM_READWRITE); + g_object_class_install_property (object_class, PROP_Y_N_SEPARATORS, properties[PROP_Y_N_SEPARATORS]); + + properties[PROP_TITLE] = + g_param_spec_string ("title", + "Title", + "Graph title", + "", + G_PARAM_READWRITE); + g_object_class_install_property (object_class, PROP_TITLE, properties[PROP_TITLE]); + + properties[PROP_LEGEND_POSITION] = + g_param_spec_enum ("legend-position", + "Legend position", + "Whether the legend should be displayed, and where", + QRM_TYPE_GRAPH_LEGEND_POSITION, + QRM_GRAPH_LEGEND_POSITION_BOTTOM, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + g_object_class_install_property (object_class, PROP_LEGEND_POSITION, properties[PROP_LEGEND_POSITION]); +} diff --git a/src/qmi-radio-monitor/qrm-graph.h b/src/qmi-radio-monitor/qrm-graph.h new file mode 100644 index 00000000..4cf01b5a --- /dev/null +++ b/src/qmi-radio-monitor/qrm-graph.h @@ -0,0 +1,79 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details: + * + * Copyright (C) 2013-2017 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef QRM_GRAPH_H +#define QRM_GRAPH_H + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define QRM_TYPE_GRAPH (qrm_graph_get_type ()) +#define QRM_GRAPH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), QRM_TYPE_GRAPH, QrmGraph)) +#define QRM_GRAPH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), QRM_TYPE_GRAPH, QrmGraphClass)) +#define QRM_IS_GRAPH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), QRM_TYPE_GRAPH)) +#define QRM_IS_GRAPH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), QRM_TYPE_GRAPH)) +#define QRM_GRAPH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), QRM_TYPE_GRAPH, QrmGraphClass)) + +typedef struct _QrmGraph QrmGraph; +typedef struct _QrmGraphClass QrmGraphClass; +typedef struct _QrmGraphPrivate QrmGraphPrivate; + +typedef enum { + QRM_GRAPH_LEGEND_POSITION_NONE = 0, + QRM_GRAPH_LEGEND_POSITION_TOP = 1, + QRM_GRAPH_LEGEND_POSITION_BOTTOM = 2, +} QrmGraphLegendPosition; + +/** + * QrmGraph: + * + * The #QrmGraph structure contains private data and should only be accessed + * using the provided API. + */ +struct _QrmGraph { + /*< private >*/ + GtkBox parent; + QrmGraphPrivate *priv; +}; + +struct _QrmGraphClass { + /*< private >*/ + GtkBoxClass parent; +}; + +GType qrm_graph_get_type (void); + +GtkWidget *qrm_graph_new (void); + +void qrm_graph_setup_series (QrmGraph *self, + guint series_index, + const gchar *label, + guint8 color_red, + guint8 color_green, + guint8 color_blue); +void qrm_graph_clear_series (QrmGraph *self, + guint series_index); + +void qrm_graph_step_init (QrmGraph *self); +void qrm_graph_step_set_value (QrmGraph *self, + guint series_index, + gdouble value, + GtkLabel *additional_label); +void qrm_graph_step_finish (QrmGraph *self); + +G_END_DECLS + +#endif /* QRM_GRAPH_H */ diff --git a/src/qmi-radio-monitor/qrm-main.c b/src/qmi-radio-monitor/qrm-main.c new file mode 100644 index 00000000..73cadae1 --- /dev/null +++ b/src/qmi-radio-monitor/qrm-main.c @@ -0,0 +1,40 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * Copyright (C) 2013-2017 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <config.h> +#include <locale.h> +#include <gtk/gtk.h> + +#include "qrm-app.h" + +gint +main (gint argc, gchar **argv) +{ + QrmApp *app; + gint status; + + setlocale (LC_ALL, ""); + + app = qrm_app_new (); + + /* Set it as the default application */ + g_application_set_default (G_APPLICATION (app)); + + /* And run the GtkApplication */ + status = g_application_run (G_APPLICATION (app), argc, argv); + g_object_unref (app); + + return status; +} diff --git a/src/qmi-radio-monitor/qrm-power-tab.c b/src/qmi-radio-monitor/qrm-power-tab.c new file mode 100644 index 00000000..17635572 --- /dev/null +++ b/src/qmi-radio-monitor/qrm-power-tab.c @@ -0,0 +1,397 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * Copyright (C) 2013-2017 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <config.h> +#include <gtk/gtk.h> + +#include "qrm-power-tab.h" +#include "qrm-graph.h" +#include "qrm-color-icon.h" + +struct _QrmPowerTabPrivate { + QrmDevice *current; + + guint act_updated_id; + + GtkWidget *legend_gsm_box; + GtkWidget *legend_gsm_icon; + GtkWidget *legend_gsm_rx0_value_label; + GtkWidget *legend_gsm_rx1_value_label; + GtkWidget *legend_gsm_tx_value_label; + + GtkWidget *legend_umts_box; + GtkWidget *legend_umts_icon; + GtkWidget *legend_umts_rx0_value_label; + GtkWidget *legend_umts_rx1_value_label; + GtkWidget *legend_umts_tx_value_label; + + GtkWidget *legend_lte_box; + GtkWidget *legend_lte_icon; + GtkWidget *legend_lte_rx0_value_label; + GtkWidget *legend_lte_rx1_value_label; + GtkWidget *legend_lte_tx_value_label; + + GtkWidget *legend_cdma_box; + GtkWidget *legend_cdma_icon; + GtkWidget *legend_cdma_rx0_value_label; + GtkWidget *legend_cdma_rx1_value_label; + GtkWidget *legend_cdma_tx_value_label; + + GtkWidget *legend_evdo_box; + GtkWidget *legend_evdo_icon; + GtkWidget *legend_evdo_rx0_value_label; + GtkWidget *legend_evdo_rx1_value_label; + GtkWidget *legend_evdo_tx_value_label; + + GtkWidget *rx0_graph; + GtkWidget *rx0_graph_frame; + guint rx0_graph_updated_id; + + GtkWidget *rx1_graph; + GtkWidget *rx1_graph_frame; + guint rx1_graph_updated_id; + + GtkWidget *tx_graph; + GtkWidget *tx_graph_frame; + guint tx_graph_updated_id; +}; + +G_DEFINE_TYPE_WITH_PRIVATE (QrmPowerTab, qrm_power_tab, GTK_TYPE_BOX) + +/******************************************************************************/ + +static void +act_updated (QrmDevice *device, + QrmDeviceAct act, + QrmPowerTab *self) +{ + gtk_widget_set_sensitive (self->priv->rx0_graph_frame, act != 0); + gtk_widget_set_sensitive (self->priv->rx1_graph_frame, act != 0); + gtk_widget_set_sensitive (self->priv->tx_graph_frame, act != 0); + + gtk_widget_set_sensitive (self->priv->legend_gsm_box, + (act & QRM_DEVICE_ACT_GSM)); + gtk_widget_set_sensitive (self->priv->legend_umts_box, + (act & QRM_DEVICE_ACT_UMTS)); + gtk_widget_set_sensitive (self->priv->legend_lte_box, + (act & QRM_DEVICE_ACT_LTE)); + gtk_widget_set_sensitive (self->priv->legend_cdma_box, + (act & QRM_DEVICE_ACT_CDMA)); + gtk_widget_set_sensitive (self->priv->legend_evdo_box, + (act & QRM_DEVICE_ACT_EVDO)); +} + +typedef enum { + SERIES_RX0_GSM = 0, + SERIES_RX0_UMTS = 1, + SERIES_RX0_LTE = 2, + SERIES_RX0_CDMA = 3, + SERIES_RX0_EVDO = 4, +} SeriesRx0; + +static void +rx0_updated (QrmDevice *device, + gdouble gsm_rx0, + gdouble umts_rx0, + gdouble lte_rx0, + gdouble cdma_rx0, + gdouble evdo_rx0, + QrmPowerTab *self) +{ + qrm_graph_step_init (QRM_GRAPH (self->priv->rx0_graph)); + qrm_graph_step_set_value (QRM_GRAPH (self->priv->rx0_graph), + SERIES_RX0_GSM, + gsm_rx0, + GTK_LABEL (self->priv->legend_gsm_rx0_value_label)); + qrm_graph_step_set_value (QRM_GRAPH (self->priv->rx0_graph), + SERIES_RX0_UMTS, + umts_rx0, + GTK_LABEL (self->priv->legend_umts_rx0_value_label)); + qrm_graph_step_set_value (QRM_GRAPH (self->priv->rx0_graph), + SERIES_RX0_LTE, + lte_rx0, + GTK_LABEL (self->priv->legend_lte_rx0_value_label)); + qrm_graph_step_set_value (QRM_GRAPH (self->priv->rx0_graph), + SERIES_RX0_CDMA, + cdma_rx0, + GTK_LABEL (self->priv->legend_cdma_rx0_value_label)); + qrm_graph_step_set_value (QRM_GRAPH (self->priv->rx0_graph), + SERIES_RX0_EVDO, + evdo_rx0, + GTK_LABEL (self->priv->legend_cdma_rx0_value_label)); + qrm_graph_step_finish (QRM_GRAPH (self->priv->rx0_graph)); +} + +typedef enum { + SERIES_RX1_GSM = 0, + SERIES_RX1_UMTS = 1, + SERIES_RX1_LTE = 2, + SERIES_RX1_CDMA = 3, + SERIES_RX1_EVDO = 4, +} SeriesRx1; + +static void +rx1_updated (QrmDevice *device, + gdouble gsm_rx1, + gdouble umts_rx1, + gdouble lte_rx1, + gdouble cdma_rx1, + gdouble evdo_rx1, + QrmPowerTab *self) +{ + qrm_graph_step_init (QRM_GRAPH (self->priv->rx1_graph)); + qrm_graph_step_set_value (QRM_GRAPH (self->priv->rx1_graph), + SERIES_RX1_GSM, + gsm_rx1, + GTK_LABEL (self->priv->legend_gsm_rx1_value_label)); + qrm_graph_step_set_value (QRM_GRAPH (self->priv->rx1_graph), + SERIES_RX1_UMTS, + umts_rx1, + GTK_LABEL (self->priv->legend_umts_rx1_value_label)); + qrm_graph_step_set_value (QRM_GRAPH (self->priv->rx1_graph), + SERIES_RX1_LTE, + lte_rx1, + GTK_LABEL (self->priv->legend_lte_rx1_value_label)); + qrm_graph_step_set_value (QRM_GRAPH (self->priv->rx1_graph), + SERIES_RX1_CDMA, + cdma_rx1, + GTK_LABEL (self->priv->legend_cdma_rx1_value_label)); + qrm_graph_step_set_value (QRM_GRAPH (self->priv->rx1_graph), + SERIES_RX1_EVDO, + evdo_rx1, + GTK_LABEL (self->priv->legend_cdma_rx1_value_label)); + qrm_graph_step_finish (QRM_GRAPH (self->priv->rx1_graph)); +} + +typedef enum { + SERIES_TX_GSM = 0, + SERIES_TX_UMTS = 1, + SERIES_TX_LTE = 2, + SERIES_TX_CDMA = 3, + SERIES_TX_EVDO = 4, +} SeriesTx; + +static void +tx_updated (QrmDevice *device, + gdouble gsm_tx, + gdouble umts_tx, + gdouble lte_tx, + gdouble cdma_tx, + gdouble evdo_tx, + QrmPowerTab *self) +{ + qrm_graph_step_init (QRM_GRAPH (self->priv->tx_graph)); + qrm_graph_step_set_value (QRM_GRAPH (self->priv->tx_graph), + SERIES_TX_GSM, + gsm_tx, + GTK_LABEL (self->priv->legend_gsm_tx_value_label)); + qrm_graph_step_set_value (QRM_GRAPH (self->priv->tx_graph), + SERIES_TX_UMTS, + umts_tx, + GTK_LABEL (self->priv->legend_umts_tx_value_label)); + qrm_graph_step_set_value (QRM_GRAPH (self->priv->tx_graph), + SERIES_TX_LTE, + lte_tx, + GTK_LABEL (self->priv->legend_lte_tx_value_label)); + qrm_graph_step_set_value (QRM_GRAPH (self->priv->tx_graph), + SERIES_TX_CDMA, + cdma_tx, + GTK_LABEL (self->priv->legend_cdma_tx_value_label)); + qrm_graph_step_set_value (QRM_GRAPH (self->priv->tx_graph), + SERIES_TX_EVDO, + evdo_tx, + GTK_LABEL (self->priv->legend_cdma_tx_value_label)); + qrm_graph_step_finish (QRM_GRAPH (self->priv->tx_graph)); +} + +void +qrm_power_tab_change_current_device (QrmPowerTab *self, + QrmDevice *new_device) +{ + if (self->priv->current) { + /* If same device, nothing else needed */ + if (new_device && + (self->priv->current == new_device || + g_str_equal (qrm_device_get_name (self->priv->current), qrm_device_get_name (new_device)))) + return; + + /* Changing current device, cleanup */ + if (self->priv->act_updated_id) { + g_signal_handler_disconnect (self->priv->current, self->priv->act_updated_id); + self->priv->act_updated_id = 0; + } + + if (self->priv->rx0_graph_updated_id) { + g_signal_handler_disconnect (self->priv->current, self->priv->rx0_graph_updated_id); + self->priv->rx0_graph_updated_id = 0; + } + + if (self->priv->rx1_graph_updated_id) { + g_signal_handler_disconnect (self->priv->current, self->priv->rx1_graph_updated_id); + self->priv->rx1_graph_updated_id = 0; + } + + if (self->priv->tx_graph_updated_id) { + g_signal_handler_disconnect (self->priv->current, self->priv->tx_graph_updated_id); + self->priv->tx_graph_updated_id = 0; + } + + g_clear_object (&self->priv->current); + + /* Clear graphs */ + + qrm_graph_clear_series (QRM_GRAPH (self->priv->rx0_graph), SERIES_RX0_GSM); + qrm_graph_clear_series (QRM_GRAPH (self->priv->rx0_graph), SERIES_RX0_UMTS); + qrm_graph_clear_series (QRM_GRAPH (self->priv->rx0_graph), SERIES_RX0_LTE); + qrm_graph_clear_series (QRM_GRAPH (self->priv->rx0_graph), SERIES_RX0_CDMA); + qrm_graph_clear_series (QRM_GRAPH (self->priv->rx0_graph), SERIES_RX0_EVDO); + + qrm_graph_clear_series (QRM_GRAPH (self->priv->rx1_graph), SERIES_RX1_GSM); + qrm_graph_clear_series (QRM_GRAPH (self->priv->rx1_graph), SERIES_RX1_UMTS); + qrm_graph_clear_series (QRM_GRAPH (self->priv->rx1_graph), SERIES_RX1_LTE); + qrm_graph_clear_series (QRM_GRAPH (self->priv->rx1_graph), SERIES_RX1_CDMA); + qrm_graph_clear_series (QRM_GRAPH (self->priv->rx1_graph), SERIES_RX1_EVDO); + + qrm_graph_clear_series (QRM_GRAPH (self->priv->tx_graph), SERIES_TX_GSM); + qrm_graph_clear_series (QRM_GRAPH (self->priv->tx_graph), SERIES_TX_UMTS); + qrm_graph_clear_series (QRM_GRAPH (self->priv->tx_graph), SERIES_TX_LTE); + qrm_graph_clear_series (QRM_GRAPH (self->priv->tx_graph), SERIES_TX_CDMA); + qrm_graph_clear_series (QRM_GRAPH (self->priv->tx_graph), SERIES_TX_EVDO); + } + + if (new_device) { + /* Keep a ref to current device */ + self->priv->current = g_object_ref (new_device); + self->priv->act_updated_id = g_signal_connect (new_device, + "act-updated", + G_CALLBACK (act_updated), + self); + self->priv->rx0_graph_updated_id = g_signal_connect (new_device, + "rx0-updated", + G_CALLBACK (rx0_updated), + self); + self->priv->rx1_graph_updated_id = g_signal_connect (new_device, + "rx1-updated", + G_CALLBACK (rx1_updated), + self); + self->priv->tx_graph_updated_id = g_signal_connect (new_device, + "tx-updated", + G_CALLBACK (tx_updated), + self); + } +} + +/******************************************************************************/ + +static void +qrm_power_tab_init (QrmPowerTab *self) +{ + self->priv = qrm_power_tab_get_instance_private (self); + + /* Ensure we register the QrmGraph and QrmColorIcon before initiating + * the template */ + g_warn_if_fail (qrm_color_icon_get_type ()); + g_warn_if_fail (qrm_graph_get_type ()); + + gtk_widget_init_template (GTK_WIDGET (self)); + +#define GSM_RGB 76, 153, 0 /* green */ +#define UMTS_RGB 204, 0, 0 /* red */ +#define LTE_RGB 0, 76, 153 /* blue */ +#define CDMA_RGB 153, 153, 0 /* yellow */ +#define EVDO_RGB 153, 0, 153 /* purple */ + + /* Main legend box */ + qrm_color_icon_set_color (QRM_COLOR_ICON (self->priv->legend_gsm_icon), GSM_RGB); + qrm_color_icon_set_color (QRM_COLOR_ICON (self->priv->legend_umts_icon), UMTS_RGB); + qrm_color_icon_set_color (QRM_COLOR_ICON (self->priv->legend_lte_icon), LTE_RGB); + qrm_color_icon_set_color (QRM_COLOR_ICON (self->priv->legend_cdma_icon), CDMA_RGB); + qrm_color_icon_set_color (QRM_COLOR_ICON (self->priv->legend_evdo_icon), EVDO_RGB); + + /* RX0 graph */ + qrm_graph_setup_series (QRM_GRAPH (self->priv->rx0_graph), SERIES_RX0_GSM, "GSM", GSM_RGB); + qrm_graph_setup_series (QRM_GRAPH (self->priv->rx0_graph), SERIES_RX0_UMTS, "UMTS", UMTS_RGB); + qrm_graph_setup_series (QRM_GRAPH (self->priv->rx0_graph), SERIES_RX0_LTE, "LTE", LTE_RGB); + qrm_graph_setup_series (QRM_GRAPH (self->priv->rx0_graph), SERIES_RX0_CDMA, "CDMA", CDMA_RGB); + qrm_graph_setup_series (QRM_GRAPH (self->priv->rx0_graph), SERIES_RX0_EVDO, "EVDO", EVDO_RGB); + + /* RX1 graph */ + qrm_graph_setup_series (QRM_GRAPH (self->priv->rx1_graph), SERIES_RX1_GSM, "GSM", GSM_RGB); + qrm_graph_setup_series (QRM_GRAPH (self->priv->rx1_graph), SERIES_RX1_UMTS, "UMTS", UMTS_RGB); + qrm_graph_setup_series (QRM_GRAPH (self->priv->rx1_graph), SERIES_RX1_LTE, "LTE", LTE_RGB); + qrm_graph_setup_series (QRM_GRAPH (self->priv->rx1_graph), SERIES_RX1_CDMA, "CDMA", CDMA_RGB); + qrm_graph_setup_series (QRM_GRAPH (self->priv->rx1_graph), SERIES_RX1_EVDO, "EVDO", EVDO_RGB); + + /* TX graph */ + qrm_graph_setup_series (QRM_GRAPH (self->priv->tx_graph), SERIES_TX_GSM, "GSM", GSM_RGB); + qrm_graph_setup_series (QRM_GRAPH (self->priv->tx_graph), SERIES_TX_UMTS, "UMTS", UMTS_RGB); + qrm_graph_setup_series (QRM_GRAPH (self->priv->tx_graph), SERIES_TX_LTE, "LTE", LTE_RGB); + qrm_graph_setup_series (QRM_GRAPH (self->priv->tx_graph), SERIES_TX_CDMA, "CDMA", CDMA_RGB); + qrm_graph_setup_series (QRM_GRAPH (self->priv->tx_graph), SERIES_TX_EVDO, "EVDO", EVDO_RGB); +} + +static void +dispose (GObject *object) +{ + QrmPowerTab *self = QRM_POWER_TAB (object); + + g_clear_object (&self->priv->current); + + G_OBJECT_CLASS (qrm_power_tab_parent_class)->dispose (object); +} + +static void +qrm_power_tab_class_init (QrmPowerTabClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = dispose; + + /* Bind class to template */ + gtk_widget_class_set_template_from_resource (widget_class, "/org/freedesktop/qrm/qrm-power-tab.ui"); + gtk_widget_class_bind_template_child_private (widget_class, QrmPowerTab, rx0_graph); + gtk_widget_class_bind_template_child_private (widget_class, QrmPowerTab, rx1_graph); + gtk_widget_class_bind_template_child_private (widget_class, QrmPowerTab, tx_graph); + gtk_widget_class_bind_template_child_private (widget_class, QrmPowerTab, rx0_graph_frame); + gtk_widget_class_bind_template_child_private (widget_class, QrmPowerTab, rx1_graph_frame); + gtk_widget_class_bind_template_child_private (widget_class, QrmPowerTab, tx_graph_frame); + gtk_widget_class_bind_template_child_private (widget_class, QrmPowerTab, legend_gsm_box); + gtk_widget_class_bind_template_child_private (widget_class, QrmPowerTab, legend_umts_box); + gtk_widget_class_bind_template_child_private (widget_class, QrmPowerTab, legend_lte_box); + gtk_widget_class_bind_template_child_private (widget_class, QrmPowerTab, legend_cdma_box); + gtk_widget_class_bind_template_child_private (widget_class, QrmPowerTab, legend_evdo_box); + gtk_widget_class_bind_template_child_private (widget_class, QrmPowerTab, legend_gsm_icon); + gtk_widget_class_bind_template_child_private (widget_class, QrmPowerTab, legend_umts_icon); + gtk_widget_class_bind_template_child_private (widget_class, QrmPowerTab, legend_lte_icon); + gtk_widget_class_bind_template_child_private (widget_class, QrmPowerTab, legend_cdma_icon); + gtk_widget_class_bind_template_child_private (widget_class, QrmPowerTab, legend_evdo_icon); + gtk_widget_class_bind_template_child_private (widget_class, QrmPowerTab, legend_gsm_rx0_value_label); + gtk_widget_class_bind_template_child_private (widget_class, QrmPowerTab, legend_umts_rx0_value_label); + gtk_widget_class_bind_template_child_private (widget_class, QrmPowerTab, legend_lte_rx0_value_label); + gtk_widget_class_bind_template_child_private (widget_class, QrmPowerTab, legend_cdma_rx0_value_label); + gtk_widget_class_bind_template_child_private (widget_class, QrmPowerTab, legend_evdo_rx0_value_label); + gtk_widget_class_bind_template_child_private (widget_class, QrmPowerTab, legend_gsm_rx1_value_label); + gtk_widget_class_bind_template_child_private (widget_class, QrmPowerTab, legend_umts_rx1_value_label); + gtk_widget_class_bind_template_child_private (widget_class, QrmPowerTab, legend_lte_rx1_value_label); + gtk_widget_class_bind_template_child_private (widget_class, QrmPowerTab, legend_cdma_rx1_value_label); + gtk_widget_class_bind_template_child_private (widget_class, QrmPowerTab, legend_evdo_rx1_value_label); + gtk_widget_class_bind_template_child_private (widget_class, QrmPowerTab, legend_gsm_tx_value_label); + gtk_widget_class_bind_template_child_private (widget_class, QrmPowerTab, legend_umts_tx_value_label); + gtk_widget_class_bind_template_child_private (widget_class, QrmPowerTab, legend_lte_tx_value_label); + gtk_widget_class_bind_template_child_private (widget_class, QrmPowerTab, legend_cdma_tx_value_label); + gtk_widget_class_bind_template_child_private (widget_class, QrmPowerTab, legend_evdo_tx_value_label); + +} diff --git a/src/qmi-radio-monitor/qrm-power-tab.h b/src/qmi-radio-monitor/qrm-power-tab.h new file mode 100644 index 00000000..991695a1 --- /dev/null +++ b/src/qmi-radio-monitor/qrm-power-tab.h @@ -0,0 +1,52 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * Copyright (C) 2013-2017 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef QRM_POWER_TAB_H +#define QRM_POWER_TAB_H + +#include <gtk/gtk.h> + +#include "qrm-device.h" + +G_BEGIN_DECLS + +#define QRM_TYPE_POWER_TAB (qrm_power_tab_get_type ()) +#define QRM_POWER_TAB(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), QRM_TYPE_POWER_TAB, QrmPowerTab)) +#define QRM_POWER_TAB_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), QRM_TYPE_POWER_TAB, QrmPowerTabClass)) +#define QRM_IS_POWER_TAB(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), QRM_TYPE_POWER_TAB)) +#define QRM_IS_POWER_TAB_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), QRM_TYPE_POWER_TAB)) +#define QRM_POWER_TAB_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), QRM_TYPE_POWER_TAB, QrmPowerTabClass)) + +typedef struct _QrmPowerTab QrmPowerTab; +typedef struct _QrmPowerTabClass QrmPowerTabClass; +typedef struct _QrmPowerTabPrivate QrmPowerTabPrivate; + +struct _QrmPowerTab { + GtkBox parent_instance; + QrmPowerTabPrivate *priv; +}; + +struct _QrmPowerTabClass { + GtkBoxClass parent_class; +}; + +GType qrm_power_tab_get_type (void) G_GNUC_CONST; + +void qrm_power_tab_change_current_device (QrmPowerTab *self, + QrmDevice *new_device); + +G_END_DECLS + +#endif /* QRM_POWER_TAB_H */ diff --git a/src/qmi-radio-monitor/qrm-power-tab.ui b/src/qmi-radio-monitor/qrm-power-tab.ui new file mode 100644 index 00000000..3eab159a --- /dev/null +++ b/src/qmi-radio-monitor/qrm-power-tab.ui @@ -0,0 +1,930 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <!-- interface-requires gtk+ 3.10 --> + <template class="QrmPowerTab" parent="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">horizontal</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <child> + <object class="GtkBox" id="legend_box"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <property name="orientation">vertical</property> + <property name="vexpand">true</property> + <property name="valign">center</property> + <child> + <object class="GtkBox" id="legend_gsm_box"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox" id="legend_gsm_title_box"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">horizontal</property> + <child> + <object class="QrmColorIcon" id="legend_gsm_icon"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + <property name="padding">4</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_gsm_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">GSM</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + <property name="padding">4</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + <property name="padding">4</property> + </packing> + </child> + <child> + <object class="GtkGrid" id="legend_gsm_grid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="column-homogeneous">True</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <property name="row-spacing">5</property> + <property name="column-spacing">5</property> + <child> + <object class="GtkLabel" id="legend_gsm_rx0_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">Rx (chain 0):</property> + <property name="halign">end</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_gsm_rx0_value_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">N/A</property> + <property name="halign">start</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_gsm_rx1_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">Rx (chain 1):</property> + <property name="halign">end</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_gsm_rx1_value_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">N/A</property> + <property name="halign">start</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_gsm_tx_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">Tx:</property> + <property name="halign">end</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_gsm_tx_value_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">N/A</property> + <property name="halign">start</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + <property name="padding">4</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + <property name="padding">4</property> + </packing> + </child> + <child> + <object class="GtkBox" id="legend_umts_box"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox" id="legend_umts_title_box"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">horizontal</property> + <child> + <object class="QrmColorIcon" id="legend_umts_icon"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + <property name="padding">4</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_umts_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">UMTS</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + <property name="padding">4</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + <property name="padding">4</property> + </packing> + </child> + <child> + <object class="GtkGrid" id="legend_umts_grid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="column-homogeneous">True</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <property name="row-spacing">5</property> + <property name="column-spacing">5</property> + <child> + <object class="GtkLabel" id="legend_umts_rx0_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">Rx (chain 0):</property> + <property name="halign">end</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_umts_rx0_value_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">N/A</property> + <property name="halign">start</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_umts_rx1_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">Rx (chain 1):</property> + <property name="halign">end</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_umts_rx1_value_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">N/A</property> + <property name="halign">start</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_umts_tx_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">Tx:</property> + <property name="halign">end</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_umts_tx_value_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">N/A</property> + <property name="halign">start</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + <property name="padding">4</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + <property name="padding">4</property> + </packing> + </child> + <child> + <object class="GtkBox" id="legend_lte_box"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox" id="legend_lte_title_box"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">horizontal</property> + <child> + <object class="QrmColorIcon" id="legend_lte_icon"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + <property name="padding">4</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_lte_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">LTE</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + <property name="padding">4</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + <property name="padding">4</property> + </packing> + </child> + <child> + <object class="GtkGrid" id="legend_lte_grid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="column-homogeneous">True</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <property name="row-spacing">5</property> + <property name="column-spacing">5</property> + <child> + <object class="GtkLabel" id="legend_lte_rx0_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">Rx (chain 0):</property> + <property name="halign">end</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_lte_rx0_value_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">N/A</property> + <property name="halign">start</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_lte_rx1_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">Rx (chain 1):</property> + <property name="halign">end</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_lte_rx1_value_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">N/A</property> + <property name="halign">start</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_lte_tx_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">Tx:</property> + <property name="halign">end</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_lte_tx_value_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">N/A</property> + <property name="halign">start</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + <property name="padding">4</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + <property name="padding">4</property> + </packing> + </child> + <child> + <object class="GtkBox" id="legend_cdma_box"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox" id="legend_cdma_title_box"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">horizontal</property> + <child> + <object class="QrmColorIcon" id="legend_cdma_icon"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + <property name="padding">4</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_cdma_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">CDMA</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + <property name="padding">4</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + <property name="padding">4</property> + </packing> + </child> + <child> + <object class="GtkGrid" id="legend_cdma_grid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="column-homogeneous">True</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <property name="row-spacing">5</property> + <property name="column-spacing">5</property> + <child> + <object class="GtkLabel" id="legend_cdma_rx0_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">Rx (chain 0):</property> + <property name="halign">end</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_cdma_rx0_value_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">N/A</property> + <property name="halign">start</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_cdma_rx1_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">Rx (chain 1):</property> + <property name="halign">end</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_cdma_rx1_value_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">N/A</property> + <property name="halign">start</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_cdma_tx_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">Tx:</property> + <property name="halign">end</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_cdma_tx_value_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">N/A</property> + <property name="halign">start</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + <property name="padding">4</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">3</property> + <property name="padding">4</property> + </packing> + </child> + <child> + <object class="GtkBox" id="legend_evdo_box"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox" id="legend_evdo_title_box"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">horizontal</property> + <child> + <object class="QrmColorIcon" id="legend_evdo_icon"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + <property name="padding">4</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_evdo_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">EVDO</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + <property name="padding">4</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + <property name="padding">4</property> + </packing> + </child> + <child> + <object class="GtkGrid" id="legend_evdo_grid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="column-homogeneous">True</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <property name="row-spacing">5</property> + <property name="column-spacing">5</property> + <child> + <object class="GtkLabel" id="legend_evdo_rx0_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">Rx (chain 0):</property> + <property name="halign">end</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_evdo_rx0_value_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">N/A</property> + <property name="halign">start</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_evdo_rx1_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">Rx (chain 1):</property> + <property name="halign">end</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_evdo_rx1_value_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">N/A</property> + <property name="halign">start</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_evdo_tx_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">Tx:</property> + <property name="halign">end</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_evdo_tx_value_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">N/A</property> + <property name="halign">start</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + <property name="padding">4</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">4</property> + <property name="padding">4</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + <property name="padding">0</property> + </packing> + </child> + <child> + <object class="GtkGrid" id="grid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="column-homogeneous">True</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="row-spacing">5</property> + <property name="column-spacing">5</property> + <child> + <object class="GtkFrame" id="rx0_graph_frame"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can_focus">False</property> + <property name="shadow_type">in</property> + <child> + <object class="QrmGraph" id="rx0_graph"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="title">Rx (chain 0)</property> + <property name="y-max">-49.0</property> + <property name="y-min">-113.0</property> + <property name="y-n-separators">4</property> + <property name="y-units">dBm</property> + <property name="n-series">5</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="legend-position">none</property> + </object> + </child> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkFrame" id="rx1_graph_frame"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can_focus">False</property> + <property name="shadow_type">in</property> + <child> + <object class="QrmGraph" id="rx1_graph"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="title">Rx (chain 1)</property> + <property name="y-max">-49</property> + <property name="y-min">-113</property> + <property name="y-n-separators">4</property> + <property name="y-units">dBm</property> + <property name="n-series">5</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="legend-position">none</property> + </object> + </child> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkFrame" id="tx_graph_frame"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can_focus">False</property> + <property name="shadow_type">in</property> + <child> + <object class="QrmGraph" id="tx_graph"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="title">Tx</property> + <property name="y-max">0.0</property> + <property name="y-min">-10.0</property> + <property name="y-n-separators">4</property> + <property name="y-units">dBm</property> + <property name="n-series">5</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="legend-position">none</property> + </object> + </child> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">3</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + <property name="padding">0</property> + </packing> + </child> + </template> +</interface> diff --git a/src/qmi-radio-monitor/qrm-signal-tab.c b/src/qmi-radio-monitor/qrm-signal-tab.c new file mode 100644 index 00000000..a42d7458 --- /dev/null +++ b/src/qmi-radio-monitor/qrm-signal-tab.c @@ -0,0 +1,505 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * Copyright (C) 2013-2017 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <config.h> +#include <gtk/gtk.h> + +#include "qrm-signal-tab.h" +#include "qrm-graph.h" +#include "qrm-color-icon.h" + +struct _QrmSignalTabPrivate { + QrmDevice *current; + + guint act_updated_id; + + GtkWidget *legend_gsm_box; + GtkWidget *legend_gsm_icon; + GtkWidget *legend_gsm_rssi_value_label; + + GtkWidget *legend_umts_box; + GtkWidget *legend_umts_icon; + GtkWidget *legend_umts_rssi_value_label; + GtkWidget *legend_umts_ecio_value_label; + + GtkWidget *legend_lte_box; + GtkWidget *legend_lte_icon; + GtkWidget *legend_lte_rssi_value_label; + GtkWidget *legend_lte_rsrq_value_label; + GtkWidget *legend_lte_rsrp_value_label; + GtkWidget *legend_lte_snr_value_label; + + GtkWidget *legend_cdma_box; + GtkWidget *legend_cdma_icon; + GtkWidget *legend_cdma_rssi_value_label; + GtkWidget *legend_cdma_ecio_value_label; + + GtkWidget *legend_evdo_box; + GtkWidget *legend_evdo_icon; + GtkWidget *legend_evdo_rssi_value_label; + GtkWidget *legend_evdo_ecio_value_label; + GtkWidget *legend_evdo_sinr_level_value_label; + GtkWidget *legend_evdo_io_value_label; + + GtkWidget *rssi_graph; + GtkWidget *rssi_graph_frame; + guint rssi_graph_updated_id; + + GtkWidget *ecio_graph; + GtkWidget *ecio_graph_frame; + guint ecio_graph_updated_id; + + GtkWidget *sinr_level_graph; + GtkWidget *sinr_level_graph_frame; + guint sinr_level_graph_updated_id; + + GtkWidget *io_graph; + GtkWidget *io_graph_frame; + guint io_graph_updated_id; + + GtkWidget *rsrq_graph; + GtkWidget *rsrq_graph_frame; + guint rsrq_graph_updated_id; + + GtkWidget *rsrp_graph; + GtkWidget *rsrp_graph_frame; + guint rsrp_graph_updated_id; + + GtkWidget *snr_graph; + GtkWidget *snr_graph_frame; + guint snr_graph_updated_id; +}; + +G_DEFINE_TYPE_WITH_PRIVATE (QrmSignalTab, qrm_signal_tab, GTK_TYPE_BOX) + +/******************************************************************************/ + +static void +act_updated (QrmDevice *device, + QrmDeviceAct act, + QrmSignalTab *self) +{ + gtk_widget_set_sensitive (self->priv->rssi_graph_frame, act != 0); + gtk_widget_set_sensitive (self->priv->ecio_graph_frame, + (act & (QRM_DEVICE_ACT_UMTS | + QRM_DEVICE_ACT_CDMA | + QRM_DEVICE_ACT_EVDO))); + gtk_widget_set_sensitive (self->priv->sinr_level_graph_frame, + (act & (QRM_DEVICE_ACT_EVDO))); + gtk_widget_set_sensitive (self->priv->io_graph_frame, + (act & (QRM_DEVICE_ACT_EVDO))); + gtk_widget_set_sensitive (self->priv->rsrq_graph_frame, + (act & (QRM_DEVICE_ACT_LTE))); + gtk_widget_set_sensitive (self->priv->rsrp_graph_frame, + (act & (QRM_DEVICE_ACT_LTE))); + gtk_widget_set_sensitive (self->priv->snr_graph_frame, + (act & (QRM_DEVICE_ACT_LTE))); + + gtk_widget_set_sensitive (self->priv->legend_gsm_box, + (act & QRM_DEVICE_ACT_GSM)); + gtk_widget_set_sensitive (self->priv->legend_umts_box, + (act & QRM_DEVICE_ACT_UMTS)); + gtk_widget_set_sensitive (self->priv->legend_lte_box, + (act & QRM_DEVICE_ACT_LTE)); + gtk_widget_set_sensitive (self->priv->legend_cdma_box, + (act & QRM_DEVICE_ACT_CDMA)); + gtk_widget_set_sensitive (self->priv->legend_evdo_box, + (act & QRM_DEVICE_ACT_EVDO)); +} + +typedef enum { + SERIES_RSSI_GSM = 0, + SERIES_RSSI_UMTS = 1, + SERIES_RSSI_LTE = 2, + SERIES_RSSI_CDMA = 3, + SERIES_RSSI_EVDO = 4, +} SeriesRssi; + +static void +rssi_updated (QrmDevice *device, + gdouble gsm_rssi, + gdouble umts_rssi, + gdouble lte_rssi, + gdouble cdma_rssi, + gdouble evdo_rssi, + QrmSignalTab *self) +{ + qrm_graph_step_init (QRM_GRAPH (self->priv->rssi_graph)); + qrm_graph_step_set_value (QRM_GRAPH (self->priv->rssi_graph), + SERIES_RSSI_GSM, + gsm_rssi, + GTK_LABEL (self->priv->legend_gsm_rssi_value_label)); + qrm_graph_step_set_value (QRM_GRAPH (self->priv->rssi_graph), + SERIES_RSSI_UMTS, + umts_rssi, + GTK_LABEL (self->priv->legend_umts_rssi_value_label)); + qrm_graph_step_set_value (QRM_GRAPH (self->priv->rssi_graph), + SERIES_RSSI_LTE, + lte_rssi, + GTK_LABEL (self->priv->legend_lte_rssi_value_label)); + qrm_graph_step_set_value (QRM_GRAPH (self->priv->rssi_graph), + SERIES_RSSI_CDMA, + cdma_rssi, + GTK_LABEL (self->priv->legend_cdma_rssi_value_label)); + qrm_graph_step_set_value (QRM_GRAPH (self->priv->rssi_graph), + SERIES_RSSI_EVDO, + evdo_rssi, + GTK_LABEL (self->priv->legend_cdma_rssi_value_label)); + qrm_graph_step_finish (QRM_GRAPH (self->priv->rssi_graph)); +} + +typedef enum { + SERIES_ECIO_UMTS = 0, + SERIES_ECIO_CDMA = 1, + SERIES_ECIO_EVDO = 2, +} SeriesEcio; + +static void +ecio_updated (QrmDevice *device, + gdouble umts_ecio, + gdouble cdma_ecio, + gdouble evdo_ecio, + QrmSignalTab *self) +{ + qrm_graph_step_init (QRM_GRAPH (self->priv->ecio_graph)); + qrm_graph_step_set_value (QRM_GRAPH (self->priv->ecio_graph), + SERIES_ECIO_UMTS, + umts_ecio, + GTK_LABEL (self->priv->legend_umts_ecio_value_label)); + qrm_graph_step_set_value (QRM_GRAPH (self->priv->ecio_graph), + SERIES_ECIO_CDMA, + cdma_ecio, + GTK_LABEL (self->priv->legend_cdma_ecio_value_label)); + qrm_graph_step_set_value (QRM_GRAPH (self->priv->ecio_graph), + SERIES_ECIO_EVDO, + evdo_ecio, + GTK_LABEL (self->priv->legend_evdo_ecio_value_label)); + qrm_graph_step_finish (QRM_GRAPH (self->priv->ecio_graph)); +} + +typedef enum { + SERIES_SINR_LEVEL_EVDO = 0, +} SeriesSinrLevel; + +static void +sinr_level_updated (QrmDevice *device, + gdouble evdo_sinr_level, + QrmSignalTab *self) +{ + qrm_graph_step_init (QRM_GRAPH (self->priv->sinr_level_graph)); + qrm_graph_step_set_value (QRM_GRAPH (self->priv->sinr_level_graph), + SERIES_SINR_LEVEL_EVDO, + evdo_sinr_level, + GTK_LABEL (self->priv->legend_evdo_sinr_level_value_label)); + qrm_graph_step_finish (QRM_GRAPH (self->priv->sinr_level_graph)); +} + +typedef enum { + SERIES_IO_EVDO = 0, +} SeriesIo; + +static void +io_updated (QrmDevice *device, + gdouble evdo_io, + QrmSignalTab *self) +{ + qrm_graph_step_init (QRM_GRAPH (self->priv->io_graph)); + qrm_graph_step_set_value (QRM_GRAPH (self->priv->io_graph), + SERIES_IO_EVDO, + evdo_io, + GTK_LABEL (self->priv->legend_evdo_io_value_label)); + qrm_graph_step_finish (QRM_GRAPH (self->priv->io_graph)); +} + +typedef enum { + SERIES_RSRQ_LTE = 0, +} SeriesRsrqLte; + +static void +rsrq_updated (QrmDevice *device, + gdouble lte_rsrq, + QrmSignalTab *self) +{ + qrm_graph_step_init (QRM_GRAPH (self->priv->rsrq_graph)); + qrm_graph_step_set_value (QRM_GRAPH (self->priv->rsrq_graph), + SERIES_RSRQ_LTE, + lte_rsrq, + GTK_LABEL (self->priv->legend_lte_rsrq_value_label)); + qrm_graph_step_finish (QRM_GRAPH (self->priv->rsrq_graph)); +} + +typedef enum { + SERIES_RSRP_LTE = 0, +} SeriesRsrpLte; + +static void +rsrp_updated (QrmDevice *device, + gdouble lte_rsrp, + QrmSignalTab *self) +{ + qrm_graph_step_init (QRM_GRAPH (self->priv->rsrp_graph)); + qrm_graph_step_set_value (QRM_GRAPH (self->priv->rsrp_graph), + SERIES_RSRP_LTE, + lte_rsrp, + GTK_LABEL (self->priv->legend_lte_rsrp_value_label)); + qrm_graph_step_finish (QRM_GRAPH (self->priv->rsrp_graph)); +} + +typedef enum { + SERIES_SNR_LTE = 0, +} SeriesSnrLte; + +static void +snr_updated (QrmDevice *device, + gdouble lte_snr, + QrmSignalTab *self) +{ + qrm_graph_step_init (QRM_GRAPH (self->priv->snr_graph)); + qrm_graph_step_set_value (QRM_GRAPH (self->priv->snr_graph), + SERIES_SNR_LTE, + lte_snr, + GTK_LABEL (self->priv->legend_lte_snr_value_label)); + qrm_graph_step_finish (QRM_GRAPH (self->priv->snr_graph)); +} + +void +qrm_signal_tab_change_current_device (QrmSignalTab *self, + QrmDevice *new_device) +{ + if (self->priv->current) { + /* If same device, nothing else needed */ + if (new_device && + (self->priv->current == new_device || + g_str_equal (qrm_device_get_name (self->priv->current), qrm_device_get_name (new_device)))) + return; + + /* Changing current device, cleanup */ + if (self->priv->act_updated_id) { + g_signal_handler_disconnect (self->priv->current, self->priv->act_updated_id); + self->priv->act_updated_id = 0; + } + + if (self->priv->rssi_graph_updated_id) { + g_signal_handler_disconnect (self->priv->current, self->priv->rssi_graph_updated_id); + self->priv->rssi_graph_updated_id = 0; + } + + if (self->priv->ecio_graph_updated_id) { + g_signal_handler_disconnect (self->priv->current, self->priv->ecio_graph_updated_id); + self->priv->ecio_graph_updated_id = 0; + } + + if (self->priv->sinr_level_graph_updated_id) { + g_signal_handler_disconnect (self->priv->current, self->priv->sinr_level_graph_updated_id); + self->priv->sinr_level_graph_updated_id = 0; + } + + if (self->priv->io_graph_updated_id) { + g_signal_handler_disconnect (self->priv->current, self->priv->io_graph_updated_id); + self->priv->io_graph_updated_id = 0; + } + + if (self->priv->rsrq_graph_updated_id) { + g_signal_handler_disconnect (self->priv->current, self->priv->rsrq_graph_updated_id); + self->priv->rsrq_graph_updated_id = 0; + } + + if (self->priv->rsrp_graph_updated_id) { + g_signal_handler_disconnect (self->priv->current, self->priv->rsrp_graph_updated_id); + self->priv->rsrp_graph_updated_id = 0; + } + + if (self->priv->snr_graph_updated_id) { + g_signal_handler_disconnect (self->priv->current, self->priv->snr_graph_updated_id); + self->priv->snr_graph_updated_id = 0; + } + + g_clear_object (&self->priv->current); + + /* Clear graphs */ + + qrm_graph_clear_series (QRM_GRAPH (self->priv->rssi_graph), SERIES_RSSI_GSM); + qrm_graph_clear_series (QRM_GRAPH (self->priv->rssi_graph), SERIES_RSSI_UMTS); + qrm_graph_clear_series (QRM_GRAPH (self->priv->rssi_graph), SERIES_RSSI_LTE); + qrm_graph_clear_series (QRM_GRAPH (self->priv->rssi_graph), SERIES_RSSI_CDMA); + qrm_graph_clear_series (QRM_GRAPH (self->priv->rssi_graph), SERIES_RSSI_EVDO); + + qrm_graph_clear_series (QRM_GRAPH (self->priv->ecio_graph), SERIES_ECIO_UMTS); + qrm_graph_clear_series (QRM_GRAPH (self->priv->ecio_graph), SERIES_ECIO_CDMA); + qrm_graph_clear_series (QRM_GRAPH (self->priv->ecio_graph), SERIES_ECIO_EVDO); + + qrm_graph_clear_series (QRM_GRAPH (self->priv->sinr_level_graph), SERIES_SINR_LEVEL_EVDO); + + qrm_graph_clear_series (QRM_GRAPH (self->priv->io_graph), SERIES_IO_EVDO); + + qrm_graph_clear_series (QRM_GRAPH (self->priv->rsrq_graph), SERIES_RSRQ_LTE); + + qrm_graph_clear_series (QRM_GRAPH (self->priv->rsrp_graph), SERIES_RSRP_LTE); + + qrm_graph_clear_series (QRM_GRAPH (self->priv->snr_graph), SERIES_SNR_LTE); + } + + if (new_device) { + /* Keep a ref to current device */ + self->priv->current = g_object_ref (new_device); + self->priv->act_updated_id = g_signal_connect (new_device, + "act-updated", + G_CALLBACK (act_updated), + self); + self->priv->rssi_graph_updated_id = g_signal_connect (new_device, + "rssi-updated", + G_CALLBACK (rssi_updated), + self); + self->priv->ecio_graph_updated_id = g_signal_connect (new_device, + "ecio-updated", + G_CALLBACK (ecio_updated), + self); + self->priv->sinr_level_graph_updated_id = g_signal_connect (new_device, + "sinr-level-updated", + G_CALLBACK (sinr_level_updated), + self); + self->priv->io_graph_updated_id = g_signal_connect (new_device, + "io-updated", + G_CALLBACK (io_updated), + self); + self->priv->rsrq_graph_updated_id = g_signal_connect (new_device, + "rsrq-updated", + G_CALLBACK (rsrq_updated), + self); + self->priv->rsrp_graph_updated_id = g_signal_connect (new_device, + "rsrp-updated", + G_CALLBACK (rsrp_updated), + self); + self->priv->snr_graph_updated_id = g_signal_connect (new_device, + "snr-updated", + G_CALLBACK (snr_updated), + self); + } +} + +/******************************************************************************/ + +static void +qrm_signal_tab_init (QrmSignalTab *self) +{ + self->priv = qrm_signal_tab_get_instance_private (self); + + /* Ensure we register the QrmGraph and QrmColorIcon before initiating + * the template */ + g_warn_if_fail (qrm_color_icon_get_type ()); + g_warn_if_fail (qrm_graph_get_type ()); + + gtk_widget_init_template (GTK_WIDGET (self)); + +#define GSM_RGB 76, 153, 0 /* green */ +#define UMTS_RGB 204, 0, 0 /* red */ +#define LTE_RGB 0, 76, 153 /* blue */ +#define CDMA_RGB 153, 153, 0 /* yellow */ +#define EVDO_RGB 153, 0, 153 /* purple */ + + /* Main legend box */ + qrm_color_icon_set_color (QRM_COLOR_ICON (self->priv->legend_gsm_icon), GSM_RGB); + qrm_color_icon_set_color (QRM_COLOR_ICON (self->priv->legend_umts_icon), UMTS_RGB); + qrm_color_icon_set_color (QRM_COLOR_ICON (self->priv->legend_lte_icon), LTE_RGB); + qrm_color_icon_set_color (QRM_COLOR_ICON (self->priv->legend_cdma_icon), CDMA_RGB); + qrm_color_icon_set_color (QRM_COLOR_ICON (self->priv->legend_evdo_icon), EVDO_RGB); + + /* RSSI graph */ + qrm_graph_setup_series (QRM_GRAPH (self->priv->rssi_graph), SERIES_RSSI_GSM, "GSM", GSM_RGB); + qrm_graph_setup_series (QRM_GRAPH (self->priv->rssi_graph), SERIES_RSSI_UMTS, "UMTS", UMTS_RGB); + qrm_graph_setup_series (QRM_GRAPH (self->priv->rssi_graph), SERIES_RSSI_LTE, "LTE", LTE_RGB); + qrm_graph_setup_series (QRM_GRAPH (self->priv->rssi_graph), SERIES_RSSI_CDMA, "CDMA", CDMA_RGB); + qrm_graph_setup_series (QRM_GRAPH (self->priv->rssi_graph), SERIES_RSSI_EVDO, "EVDO", EVDO_RGB); + + /* ECIO graph */ + qrm_graph_setup_series (QRM_GRAPH (self->priv->ecio_graph), SERIES_ECIO_UMTS, "UMTS", UMTS_RGB); + qrm_graph_setup_series (QRM_GRAPH (self->priv->ecio_graph), SERIES_ECIO_CDMA, "CDMA", CDMA_RGB); + qrm_graph_setup_series (QRM_GRAPH (self->priv->ecio_graph), SERIES_ECIO_EVDO, "EVDO", EVDO_RGB); + + /* SINR level graph */ + qrm_graph_setup_series (QRM_GRAPH (self->priv->sinr_level_graph), SERIES_SINR_LEVEL_EVDO, "EVDO", EVDO_RGB); + + /* Io graph */ + qrm_graph_setup_series (QRM_GRAPH (self->priv->io_graph), SERIES_IO_EVDO, "EVDO", EVDO_RGB); + + /* RSRQ graph */ + qrm_graph_setup_series (QRM_GRAPH (self->priv->rsrq_graph), SERIES_RSRQ_LTE, "LTE", LTE_RGB); + + /* RSRP graph */ + qrm_graph_setup_series (QRM_GRAPH (self->priv->rsrp_graph), SERIES_RSRP_LTE, "LTE", LTE_RGB); + + /* SNR graph */ + qrm_graph_setup_series (QRM_GRAPH (self->priv->snr_graph), SERIES_SNR_LTE, "LTE", LTE_RGB); +} + +static void +dispose (GObject *object) +{ + QrmSignalTab *self = QRM_SIGNAL_TAB (object); + + g_clear_object (&self->priv->current); + + G_OBJECT_CLASS (qrm_signal_tab_parent_class)->dispose (object); +} + +static void +qrm_signal_tab_class_init (QrmSignalTabClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = dispose; + + /* Bind class to template */ + gtk_widget_class_set_template_from_resource (widget_class, "/org/freedesktop/qrm/qrm-signal-tab.ui"); + gtk_widget_class_bind_template_child_private (widget_class, QrmSignalTab, rssi_graph); + gtk_widget_class_bind_template_child_private (widget_class, QrmSignalTab, ecio_graph); + gtk_widget_class_bind_template_child_private (widget_class, QrmSignalTab, sinr_level_graph); + gtk_widget_class_bind_template_child_private (widget_class, QrmSignalTab, io_graph); + gtk_widget_class_bind_template_child_private (widget_class, QrmSignalTab, rsrq_graph); + gtk_widget_class_bind_template_child_private (widget_class, QrmSignalTab, rsrp_graph); + gtk_widget_class_bind_template_child_private (widget_class, QrmSignalTab, snr_graph); + gtk_widget_class_bind_template_child_private (widget_class, QrmSignalTab, rssi_graph_frame); + gtk_widget_class_bind_template_child_private (widget_class, QrmSignalTab, ecio_graph_frame); + gtk_widget_class_bind_template_child_private (widget_class, QrmSignalTab, sinr_level_graph_frame); + gtk_widget_class_bind_template_child_private (widget_class, QrmSignalTab, io_graph_frame); + gtk_widget_class_bind_template_child_private (widget_class, QrmSignalTab, rsrq_graph_frame); + gtk_widget_class_bind_template_child_private (widget_class, QrmSignalTab, rsrp_graph_frame); + gtk_widget_class_bind_template_child_private (widget_class, QrmSignalTab, snr_graph_frame); + gtk_widget_class_bind_template_child_private (widget_class, QrmSignalTab, legend_gsm_box); + gtk_widget_class_bind_template_child_private (widget_class, QrmSignalTab, legend_umts_box); + gtk_widget_class_bind_template_child_private (widget_class, QrmSignalTab, legend_lte_box); + gtk_widget_class_bind_template_child_private (widget_class, QrmSignalTab, legend_cdma_box); + gtk_widget_class_bind_template_child_private (widget_class, QrmSignalTab, legend_evdo_box); + gtk_widget_class_bind_template_child_private (widget_class, QrmSignalTab, legend_gsm_icon); + gtk_widget_class_bind_template_child_private (widget_class, QrmSignalTab, legend_umts_icon); + gtk_widget_class_bind_template_child_private (widget_class, QrmSignalTab, legend_lte_icon); + gtk_widget_class_bind_template_child_private (widget_class, QrmSignalTab, legend_cdma_icon); + gtk_widget_class_bind_template_child_private (widget_class, QrmSignalTab, legend_evdo_icon); + gtk_widget_class_bind_template_child_private (widget_class, QrmSignalTab, legend_gsm_rssi_value_label); + gtk_widget_class_bind_template_child_private (widget_class, QrmSignalTab, legend_umts_rssi_value_label); + gtk_widget_class_bind_template_child_private (widget_class, QrmSignalTab, legend_lte_rssi_value_label); + gtk_widget_class_bind_template_child_private (widget_class, QrmSignalTab, legend_cdma_rssi_value_label); + gtk_widget_class_bind_template_child_private (widget_class, QrmSignalTab, legend_evdo_rssi_value_label); + gtk_widget_class_bind_template_child_private (widget_class, QrmSignalTab, legend_umts_ecio_value_label); + gtk_widget_class_bind_template_child_private (widget_class, QrmSignalTab, legend_cdma_ecio_value_label); + gtk_widget_class_bind_template_child_private (widget_class, QrmSignalTab, legend_evdo_ecio_value_label); + gtk_widget_class_bind_template_child_private (widget_class, QrmSignalTab, legend_evdo_sinr_level_value_label); + gtk_widget_class_bind_template_child_private (widget_class, QrmSignalTab, legend_evdo_io_value_label); + gtk_widget_class_bind_template_child_private (widget_class, QrmSignalTab, legend_lte_rsrq_value_label); + gtk_widget_class_bind_template_child_private (widget_class, QrmSignalTab, legend_lte_rsrp_value_label); + gtk_widget_class_bind_template_child_private (widget_class, QrmSignalTab, legend_lte_snr_value_label); + +} diff --git a/src/qmi-radio-monitor/qrm-signal-tab.h b/src/qmi-radio-monitor/qrm-signal-tab.h new file mode 100644 index 00000000..fc851abf --- /dev/null +++ b/src/qmi-radio-monitor/qrm-signal-tab.h @@ -0,0 +1,52 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * Copyright (C) 2013-2017 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef QRM_SIGNAL_TAB_H +#define QRM_SIGNAL_TAB_H + +#include <gtk/gtk.h> + +#include "qrm-device.h" + +G_BEGIN_DECLS + +#define QRM_TYPE_SIGNAL_TAB (qrm_signal_tab_get_type ()) +#define QRM_SIGNAL_TAB(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), QRM_TYPE_SIGNAL_TAB, QrmSignalTab)) +#define QRM_SIGNAL_TAB_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), QRM_TYPE_SIGNAL_TAB, QrmSignalTabClass)) +#define QRM_IS_SIGNAL_TAB(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), QRM_TYPE_SIGNAL_TAB)) +#define QRM_IS_SIGNAL_TAB_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), QRM_TYPE_SIGNAL_TAB)) +#define QRM_SIGNAL_TAB_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), QRM_TYPE_SIGNAL_TAB, QrmSignalTabClass)) + +typedef struct _QrmSignalTab QrmSignalTab; +typedef struct _QrmSignalTabClass QrmSignalTabClass; +typedef struct _QrmSignalTabPrivate QrmSignalTabPrivate; + +struct _QrmSignalTab { + GtkBox parent_instance; + QrmSignalTabPrivate *priv; +}; + +struct _QrmSignalTabClass { + GtkBoxClass parent_class; +}; + +GType qrm_signal_tab_get_type (void) G_GNUC_CONST; + +void qrm_signal_tab_change_current_device (QrmSignalTab *self, + QrmDevice *new_device); + +G_END_DECLS + +#endif /* QRM_SIGNAL_TAB_H */ diff --git a/src/qmi-radio-monitor/qrm-signal-tab.ui b/src/qmi-radio-monitor/qrm-signal-tab.ui new file mode 100644 index 00000000..d717fe7c --- /dev/null +++ b/src/qmi-radio-monitor/qrm-signal-tab.ui @@ -0,0 +1,990 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <!-- interface-requires gtk+ 3.10 --> + <template class="QrmSignalTab" parent="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">horizontal</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <child> + <object class="GtkBox" id="legend_box"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <property name="orientation">vertical</property> + <property name="vexpand">true</property> + <property name="valign">center</property> + <child> + <object class="GtkBox" id="legend_gsm_box"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox" id="legend_gsm_title_box"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">horizontal</property> + <child> + <object class="QrmColorIcon" id="legend_gsm_icon"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + <property name="padding">4</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_gsm_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">GSM</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + <property name="padding">4</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + <property name="padding">4</property> + </packing> + </child> + <child> + <object class="GtkGrid" id="legend_gsm_grid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="column-homogeneous">True</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <property name="row-spacing">5</property> + <property name="column-spacing">5</property> + <child> + <object class="GtkLabel" id="legend_gsm_rssi_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">RSSI:</property> + <property name="halign">end</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_gsm_rssi_value_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">N/A</property> + <property name="halign">start</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + <property name="padding">4</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + <property name="padding">4</property> + </packing> + </child> + <child> + <object class="GtkBox" id="legend_umts_box"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox" id="legend_umts_title_box"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">horizontal</property> + <child> + <object class="QrmColorIcon" id="legend_umts_icon"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + <property name="padding">4</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_umts_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">UMTS</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + <property name="padding">4</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + <property name="padding">4</property> + </packing> + </child> + <child> + <object class="GtkGrid" id="legend_umts_grid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="column-homogeneous">True</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <property name="row-spacing">5</property> + <property name="column-spacing">5</property> + <child> + <object class="GtkLabel" id="legend_umts_rssi_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">RSSI:</property> + <property name="halign">end</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_umts_rssi_value_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">N/A</property> + <property name="halign">start</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_umts_ecio_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">Ec/Io:</property> + <property name="halign">end</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_umts_ecio_value_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">N/A</property> + <property name="halign">start</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + <property name="padding">4</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + <property name="padding">4</property> + </packing> + </child> + <child> + <object class="GtkBox" id="legend_lte_box"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox" id="legend_lte_title_box"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">horizontal</property> + <child> + <object class="QrmColorIcon" id="legend_lte_icon"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + <property name="padding">4</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_lte_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">LTE</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + <property name="padding">4</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + <property name="padding">4</property> + </packing> + </child> + <child> + <object class="GtkGrid" id="legend_lte_grid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="column-homogeneous">True</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <property name="row-spacing">5</property> + <property name="column-spacing">5</property> + <child> + <object class="GtkLabel" id="legend_lte_rssi_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">RSSI:</property> + <property name="halign">end</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_lte_rssi_value_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">N/A</property> + <property name="halign">start</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_lte_rsrq_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">RSRQ:</property> + <property name="halign">end</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_lte_rsrq_value_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">N/A</property> + <property name="halign">start</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_lte_rsrp_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">RSRP:</property> + <property name="halign">end</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_lte_rsrp_value_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">N/A</property> + <property name="halign">start</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_lte_snr_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">S/N:</property> + <property name="halign">end</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">3</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_lte_snr_value_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">N/A</property> + <property name="halign">start</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">3</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + <property name="padding">4</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + <property name="padding">4</property> + </packing> + </child> + <child> + <object class="GtkBox" id="legend_cdma_box"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox" id="legend_cdma_title_box"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">horizontal</property> + <child> + <object class="QrmColorIcon" id="legend_cdma_icon"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + <property name="padding">4</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_cdma_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">CDMA</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + <property name="padding">4</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + <property name="padding">4</property> + </packing> + </child> + <child> + <object class="GtkGrid" id="legend_cdma_grid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="column-homogeneous">True</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <property name="row-spacing">5</property> + <property name="column-spacing">5</property> + <child> + <object class="GtkLabel" id="legend_cdma_rssi_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">RSSI:</property> + <property name="halign">end</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_cdma_rssi_value_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">N/A</property> + <property name="halign">start</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_cdma_ecio_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">Ec/Io:</property> + <property name="halign">end</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_cdma_ecio_value_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">N/A</property> + <property name="halign">start</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + <property name="padding">4</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">3</property> + <property name="padding">4</property> + </packing> + </child> + <child> + <object class="GtkBox" id="legend_evdo_box"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox" id="legend_evdo_title_box"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">horizontal</property> + <child> + <object class="QrmColorIcon" id="legend_evdo_icon"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + <property name="padding">4</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_evdo_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">EVDO</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + <property name="padding">4</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + <property name="padding">4</property> + </packing> + </child> + <child> + <object class="GtkGrid" id="legend_evdo_grid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="column-homogeneous">True</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <property name="row-spacing">5</property> + <property name="column-spacing">5</property> + <child> + <object class="GtkLabel" id="legend_evdo_rssi_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">RSSI:</property> + <property name="halign">end</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_evdo_rssi_value_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">N/A</property> + <property name="halign">start</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_evdo_ecio_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">Ec/Io:</property> + <property name="halign">end</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_evdo_ecio_value_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">N/A</property> + <property name="halign">start</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_evdo_sinr_level_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">SINR:</property> + <property name="halign">end</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_evdo_sinr_level_value_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">N/A</property> + <property name="halign">start</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_evdo_io_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">Io:</property> + <property name="halign">end</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">3</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="legend_evdo_io_value_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">N/A</property> + <property name="halign">start</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">3</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + <property name="padding">4</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">4</property> + <property name="padding">4</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + <property name="padding">0</property> + </packing> + </child> + <child> + <object class="GtkGrid" id="grid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="column-homogeneous">True</property> + <property name="margin-left">5</property> + <property name="margin-right">5</property> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="row-spacing">5</property> + <property name="column-spacing">5</property> + <child> + <object class="GtkFrame" id="rssi_graph_frame"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can_focus">False</property> + <property name="shadow_type">in</property> + <child> + <object class="QrmGraph" id="rssi_graph"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="title">RSSI</property> + <property name="y-max">-49.0</property> + <property name="y-min">-113.0</property> + <property name="y-n-separators">4</property> + <property name="y-units">dBm</property> + <property name="n-series">5</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="legend-position">none</property> + </object> + </child> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">2</property> + </packing> + </child> + <child> + <object class="GtkFrame" id="ecio_graph_frame"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can_focus">False</property> + <property name="shadow_type">in</property> + <child> + <object class="QrmGraph" id="ecio_graph"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="title">Ec/Io</property> + <property name="y-max">0.0</property> + <property name="y-min">-50.0</property> + <property name="y-n-separators">4</property> + <property name="y-units">dBm</property> + <property name="n-series">5</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="legend-position">none</property> + </object> + </child> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkFrame" id="rsrq_graph_frame"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can_focus">False</property> + <property name="shadow_type">in</property> + <child> + <object class="QrmGraph" id="rsrq_graph"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="title">RSRQ</property> + <property name="y-max">-3</property> + <property name="y-min">-20</property> + <property name="y-n-separators">4</property> + <property name="y-units">dB</property> + <property name="n-series">1</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="legend-position">none</property> + </object> + </child> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkFrame" id="rsrp_graph_frame"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can_focus">False</property> + <property name="shadow_type">in</property> + <child> + <object class="QrmGraph" id="rsrp_graph"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="title">RSRP</property> + <property name="y-max">-44</property> + <property name="y-min">-140</property> + <property name="y-n-separators">4</property> + <property name="y-units">dBm</property> + <property name="n-series">1</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="legend-position">none</property> + </object> + </child> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkFrame" id="snr_graph_frame"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can_focus">False</property> + <property name="shadow_type">in</property> + <child> + <object class="QrmGraph" id="snr_graph"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="title">S/N</property> + <property name="y-max">50</property> + <property name="y-min">-50</property> + <property name="y-n-separators">4</property> + <property name="y-units">dBm</property> + <property name="n-series">1</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="legend-position">none</property> + </object> + </child> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkFrame" id="sinr_level_graph_frame"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can_focus">False</property> + <property name="shadow_type">in</property> + <child> + <object class="QrmGraph" id="sinr_level_graph"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="title">SINR</property> + <property name="y-max">9.0</property> + <property name="y-min">-9.0</property> + <property name="y-n-separators">4</property> + <property name="y-units">dB</property> + <property name="n-series">1</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="legend-position">none</property> + </object> + </child> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">3</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkFrame" id="io_graph_frame"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can_focus">False</property> + <property name="shadow_type">in</property> + <child> + <object class="QrmGraph" id="io_graph"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="title">Io</property> + <property name="y-max">0</property> + <property name="y-min">-106</property> + <property name="y-n-separators">4</property> + <property name="y-units">dBm</property> + <property name="n-series">1</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="legend-position">none</property> + </object> + </child> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">3</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + <property name="padding">0</property> + </packing> + </child> + </template> +</interface> diff --git a/src/qmi-radio-monitor/qrm-window.c b/src/qmi-radio-monitor/qrm-window.c new file mode 100644 index 00000000..98f488c6 --- /dev/null +++ b/src/qmi-radio-monitor/qrm-window.c @@ -0,0 +1,628 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * Copyright (C) 2013-2017 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <config.h> +#include <gtk/gtk.h> +#include <libqmi-glib.h> + +#include "qrm-window.h" +#include "qrm-device.h" +#include "qrm-signal-tab.h" +#include "qrm-power-tab.h" + +#define NOTEBOOK_TAB_DEVICE_LIST 0 +#define NOTEBOOK_TAB_GRAPHS 1 + +struct _QrmWindowPrivate { + /* Current device */ + QrmDevice *current; + + /* Header bar */ + GtkWidget *header_bar; + GtkWidget *back_button; + GtkWidget *graph_stack_switcher; + + /* Notebook */ + GtkWidget *notebook; + + /* Device list tab */ + GtkWidget *device_list_frame; + GtkWidget *device_list_box; + GtkWidget *device_list_label; + GtkWidget *device_list_spinner_box; + GtkWidget *pin_entry_frame; + GtkWidget *pin_entry; + GtkWidget *attempts_label; + GtkWidget *pin_check_spinner_box; + + /* Graphs */ + GtkWidget *signal_box; + GtkWidget *power_box; + + guint initial_scan_done_id; + guint device_detection_id; + guint device_added_id; + guint device_removed_id; +}; + +G_DEFINE_TYPE_WITH_PRIVATE (QrmWindow, qrm_window, GTK_TYPE_APPLICATION_WINDOW) + +/******************************************************************************/ + +static void +change_current_device (QrmWindow *self, + QrmDevice *new_device) +{ + /* If same device, nothing else needed */ + if (new_device && + self->priv->current && + (self->priv->current == new_device || + g_str_equal (qrm_device_get_name (self->priv->current), qrm_device_get_name (new_device)))) + return; + + g_clear_object (&self->priv->current); + + qrm_signal_tab_change_current_device (QRM_SIGNAL_TAB (self->priv->signal_box), new_device); + qrm_power_tab_change_current_device (QRM_POWER_TAB (self->priv->power_box), new_device); + + if (new_device) + /* Keep a ref to current device */ + self->priv->current = g_object_ref (new_device); +} + +/******************************************************************************/ + +static void +go_back_cb (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + QrmWindow *self = QRM_WINDOW (user_data); + + gtk_header_bar_set_title (GTK_HEADER_BAR (self->priv->header_bar), "QMI Radio Monitor"); + gtk_widget_set_sensitive (self->priv->back_button, FALSE); + gtk_widget_hide (self->priv->graph_stack_switcher); + gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), NOTEBOOK_TAB_DEVICE_LIST); +} + +static void +go_graphs_tab_cb (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + QrmWindow *self = QRM_WINDOW (user_data); + + if (self->priv->current) { + gchar *title; + + title = g_strdup_printf ("QMI Radio Monitor - %s", qrm_device_get_model (self->priv->current)); + gtk_header_bar_set_title (GTK_HEADER_BAR (self->priv->header_bar), title); + g_free (title); + } + + gtk_widget_set_sensitive (self->priv->back_button, TRUE); + gtk_widget_show (self->priv->graph_stack_switcher); + gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), NOTEBOOK_TAB_GRAPHS); +} + +static GActionEntry win_entries[] = { + /* go */ + { "go-back", go_back_cb, NULL, "false", NULL }, + { "go-graphs-tab", go_graphs_tab_cb, NULL, "false", NULL }, +}; + +/******************************************************************************/ + +static void +window_open_device (QrmWindow *self, + QrmDevice *device) +{ + /* Setup device to use */ + change_current_device (self, device); + + /* Start NAS monitoring */ + qrm_device_start_nas (device, NULL, NULL); + + /* Go to the signal tab */ + g_action_group_activate_action (G_ACTION_GROUP (self), "go-graphs-tab", NULL); +} + +/******************************************************************************/ + +#define DEVICE_TAG "device-tag" + +static void +device_list_update_header (GtkListBoxRow *row, + GtkListBoxRow *before, + gpointer user_data) +{ + GtkWidget *current; + + if (!before) + return; + + current = gtk_list_box_row_get_header (row); + if (!current) { + current = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL); + gtk_widget_show (current); + gtk_list_box_row_set_header (row, current); + } +} + +static gint +device_list_sort (GtkListBoxRow *row1, + GtkListBoxRow *row2, + gpointer user_data) +{ + QrmDevice *device1; + QrmDevice *device2; + + device1 = QRM_DEVICE (g_object_get_data (G_OBJECT (row1), DEVICE_TAG)); + device2 = QRM_DEVICE (g_object_get_data (G_OBJECT (row2), DEVICE_TAG)); + + return g_strcmp0 (qrm_device_get_name (device1), qrm_device_get_name (device2)); +} + +static void +show_pin_request (QrmWindow *self, + QrmDevice *device) +{ + gint attempts; + gchar *text; + + attempts = qrm_device_get_pin_attempts_left (device); + if (attempts >= 0) + text = g_strdup_printf ("%d attempts left", attempts); + else + text = g_strdup_printf ("unknown attempts left"); + + gtk_label_set_text (GTK_LABEL (self->priv->attempts_label), text); + gtk_entry_set_text (GTK_ENTRY (self->priv->pin_entry), ""); + gtk_widget_grab_focus (self->priv->pin_entry); + gtk_widget_show (self->priv->attempts_label); + gtk_widget_hide (self->priv->pin_check_spinner_box); + gtk_widget_show (self->priv->pin_entry_frame); + + g_free (text); +} + +static void +reset_pin_request (QrmWindow *self) +{ + gtk_label_set_text (GTK_LABEL (self->priv->attempts_label), ""); + gtk_entry_set_text (GTK_ENTRY (self->priv->pin_entry), ""); + gtk_widget_show (self->priv->attempts_label); + gtk_widget_hide (self->priv->pin_check_spinner_box); + gtk_widget_hide (self->priv->pin_entry_frame); +} + +static void +ongoing_pin_request (QrmWindow *self) +{ + gtk_widget_show (self->priv->pin_check_spinner_box); + gtk_widget_hide (self->priv->attempts_label); +} + +static void +device_unlock_ready (QrmDevice *device, + GAsyncResult *res, + QrmWindow *self) +{ + /* Reset PIN unlocking widgets */ + reset_pin_request (self); + + if (!qrm_device_unlock_finish (device, res, NULL)) { + /* Retry */ + show_pin_request (self, device); + return; + } + + /* Hide PIN unlocking widgets and go on */ + gtk_widget_hide (self->priv->pin_entry_frame); + window_open_device (self, device); +} + +static void +pin_entry_activated_cb (QrmWindow *self, + GtkEntry *pin_entry) +{ + GtkListBoxRow *row; + QrmDevice *device; + + row = gtk_list_box_get_selected_row (GTK_LIST_BOX (self->priv->device_list_box)); + if (!row) + return; + + device = QRM_DEVICE (g_object_get_data (G_OBJECT (row), DEVICE_TAG)); + if (!device) + return; + + ongoing_pin_request (self); + + qrm_device_unlock (device, + gtk_entry_get_text (pin_entry), + (GAsyncReadyCallback)device_unlock_ready, + self); +} + +static void +device_list_activate_row (QrmWindow *self, + GtkListBoxRow *row) +{ + QrmDevice *device; + + device = QRM_DEVICE (g_object_get_data (G_OBJECT (row), DEVICE_TAG)); + + if (qrm_device_get_status (device) != QRM_DEVICE_STATUS_SIM_PIN_LOCKED) { + window_open_device (self, device); + return; + } + + show_pin_request (self, device); +} + +static void +update_row_sensitivity (QrmDevice *device, + GParamSpec *unused, + GtkWidget *row) +{ + switch (qrm_device_get_status (device)) { + case QRM_DEVICE_STATUS_UNKNOWN: + case QRM_DEVICE_STATUS_SIM_PUK_LOCKED: + case QRM_DEVICE_STATUS_SIM_ERROR: + gtk_widget_set_sensitive (row, FALSE); + break; + case QRM_DEVICE_STATUS_READY: + case QRM_DEVICE_STATUS_SIM_PIN_LOCKED: + gtk_widget_set_sensitive (row, TRUE); + break; + default: + g_assert_not_reached (); + } +} + +static void +update_modem_status_label_text (QrmDevice *device, + GParamSpec *unused, + GtkWidget *status) +{ + switch (qrm_device_get_status (device)) { + case QRM_DEVICE_STATUS_UNKNOWN: + gtk_label_set_text (GTK_LABEL (status), "Unknown status"); + break; + case QRM_DEVICE_STATUS_READY: + gtk_label_set_text (GTK_LABEL (status), "Ready"); + break; + case QRM_DEVICE_STATUS_SIM_PIN_LOCKED: + gtk_label_set_text (GTK_LABEL (status), "PIN required"); + break; + case QRM_DEVICE_STATUS_SIM_PUK_LOCKED: + gtk_label_set_text (GTK_LABEL (status), "PUK required"); + break; + case QRM_DEVICE_STATUS_SIM_ERROR: + gtk_label_set_text (GTK_LABEL (status), "SIM error"); + break; + default: + g_assert_not_reached (); + } +} + +static void +device_added_cb (QrmApp *application, + QrmDevice *device, + QrmWindow *self) +{ + GtkWidget *row; + GtkWidget *box; + GtkWidget *button_label; + gchar *button_label_markup; + GtkWidget *status; + + row = gtk_list_box_row_new (); + box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 50); + gtk_container_add (GTK_CONTAINER (row), box); + gtk_widget_set_hexpand (box, TRUE); + g_object_set_data_full (G_OBJECT (row), + DEVICE_TAG, + g_object_ref (device), + g_object_unref); + gtk_container_add (GTK_CONTAINER (self->priv->device_list_box), row); + + button_label_markup = g_strdup_printf ("[%s]\n\t<span weight=\"bold\">%s</span>\n\t<span style=\"italic\">%s</span>", + qrm_device_get_name (device), + qrm_device_get_model (device), + qrm_device_get_manufacturer (device)); + button_label = gtk_label_new (NULL); + gtk_label_set_markup (GTK_LABEL (button_label), button_label_markup); + gtk_widget_set_valign (GTK_WIDGET (button_label), 0.5f); + +#if GTK_CHECK_VERSION(3,12,0) + gtk_widget_set_margin_start (button_label, 20); + gtk_widget_set_margin_end (button_label, 20); +#else + gtk_widget_set_margin_left (button_label, 20); + gtk_widget_set_margin_right (button_label, 20); +#endif + gtk_widget_set_margin_top (button_label, 12); + gtk_widget_set_margin_bottom (button_label, 12); + gtk_widget_set_halign (button_label, GTK_ALIGN_START); + gtk_widget_set_valign (button_label, GTK_ALIGN_CENTER); + gtk_widget_set_hexpand (button_label, TRUE); + gtk_box_pack_start (GTK_BOX (box), button_label, TRUE, TRUE, 0); + + status = gtk_label_new (NULL); +#if GTK_CHECK_VERSION(3,12,0) + gtk_widget_set_margin_start (status, 20); + gtk_widget_set_margin_end (status, 20); +#else + gtk_widget_set_margin_left (status, 20); + gtk_widget_set_margin_right (status, 20); +#endif + + gtk_widget_set_halign (status, GTK_ALIGN_END); + gtk_widget_set_valign (status, GTK_ALIGN_CENTER); + gtk_box_pack_end (GTK_BOX (box), status, FALSE, FALSE, 0); + + g_signal_connect (device, + "notify::status", + G_CALLBACK (update_row_sensitivity), + row); + update_row_sensitivity (device, NULL, row); + + g_signal_connect (device, + "notify::status", + G_CALLBACK (update_modem_status_label_text), + status); + update_modem_status_label_text (device, NULL, status); + + gtk_widget_show_all (row); + + gtk_widget_hide (self->priv->device_list_label); + gtk_widget_show (self->priv->device_list_frame); +} + +static void +device_removed_cb (QrmApp *application, + QrmDevice *device, + QrmWindow *self) +{ + GList *children, *l; + guint valid = 0; + gboolean removed = FALSE; + + children = gtk_container_get_children (GTK_CONTAINER (self->priv->device_list_box)); + for (l = children; l; l = g_list_next (l)) { + gpointer ldevice; + + ldevice = g_object_get_data (G_OBJECT (l->data), DEVICE_TAG); + if (!ldevice) + continue; + + if (device == ldevice || + g_str_equal (qrm_device_get_name (QRM_DEVICE (ldevice)), + qrm_device_get_name (device))) { + gtk_container_remove (GTK_CONTAINER (self->priv->device_list_box), GTK_WIDGET (l->data)); + removed = TRUE; + } else + valid++; + } + + /* If this was the last item; show label and hide list */ + if (removed && !valid) { + gtk_widget_hide (self->priv->device_list_frame); + gtk_widget_show (self->priv->device_list_label); + } + + /* If we were monitoring this device already, stop it and back to the device + * list tab */ + if (self->priv->current && + (device == self->priv->current || + g_str_equal (qrm_device_get_name (self->priv->current), + qrm_device_get_name (device)))) { + change_current_device (self, NULL); + g_action_group_activate_action (G_ACTION_GROUP (self), "go-back", NULL); + } + + g_list_free (children); +} + +static void +populate_device_list (QrmWindow *self) +{ + QrmApp *application = NULL; + GList *l; + + g_object_get (self, + "application", &application, + NULL); + + self->priv->device_added_id = + g_signal_connect (application, + "device-added", + G_CALLBACK (device_added_cb), + self); + self->priv->device_removed_id = + g_signal_connect (application, + "device-removed", + G_CALLBACK (device_removed_cb), + self); + + for (l = qrm_app_peek_devices (application); l; l = g_list_next (l)) + device_added_cb (application, QRM_DEVICE (l->data), self); + + g_object_unref (application); +} + +static void +initial_scan_done_cb (QrmApp *application, + QrmWindow *self) +{ + populate_device_list (self); +} + +static void +device_detection_cb (QrmApp *application, + gboolean detection, + QrmWindow *self) +{ + if (detection) + gtk_widget_show (self->priv->device_list_spinner_box); + else + gtk_widget_hide (self->priv->device_list_spinner_box); +} + +static void +setup_device_list_updates (QrmWindow *self) +{ + QrmApp *application = NULL; + + g_object_get (self, + "application", &application, + NULL); + + self->priv->device_detection_id = + g_signal_connect (application, + "device-detection", + G_CALLBACK (device_detection_cb), + self); + + if (qrm_app_is_initial_scan_done (application)) + populate_device_list (self); + else + self->priv->initial_scan_done_id = + g_signal_connect (application, + "initial-scan-done", + G_CALLBACK (initial_scan_done_cb), + self); + + g_object_unref (application); +} + +/******************************************************************************/ + +GtkWidget * +qrm_window_new (QrmApp *application) +{ + QrmWindow *self; + + self = g_object_new (QRM_TYPE_WINDOW, + "application", application, + NULL); + + setup_device_list_updates (self); + + return GTK_WIDGET (self); +} + +static void +qrm_window_init (QrmWindow *self) +{ + self->priv = qrm_window_get_instance_private (self); + + /* Ensure we register the QrmSignalTab and QrmPowerTab before initiating + * the template */ + g_warn_if_fail (qrm_signal_tab_get_type ()); + g_warn_if_fail (qrm_power_tab_get_type ()); + + gtk_widget_init_template (GTK_WIDGET (self)); + + g_action_map_add_action_entries (G_ACTION_MAP (self), + win_entries, G_N_ELEMENTS (win_entries), + self); + + g_signal_connect_swapped (self->priv->device_list_box, + "row-activated", + G_CALLBACK (device_list_activate_row), + self); + + g_signal_connect_swapped (self->priv->pin_entry, + "activate", + G_CALLBACK (pin_entry_activated_cb), + self); + + gtk_list_box_set_header_func (GTK_LIST_BOX (self->priv->device_list_box), + device_list_update_header, + NULL, NULL); + + gtk_list_box_set_sort_func (GTK_LIST_BOX (self->priv->device_list_box), + device_list_sort, + NULL, NULL); + + gtk_widget_show (self->priv->device_list_label); + gtk_widget_hide (self->priv->device_list_frame); +} + +static void +dispose (GObject *object) +{ + QrmWindow *self = QRM_WINDOW (object); + QrmApp *application = NULL; + + /* Remove signal handlers */ + g_object_get (self, + "application", &application, + NULL); + if (application) { + if (self->priv->initial_scan_done_id) { + g_signal_handler_disconnect (application, + self->priv->initial_scan_done_id); + self->priv->initial_scan_done_id = 0; + } + if (self->priv->device_detection_id) { + g_signal_handler_disconnect (application, + self->priv->device_detection_id); + self->priv->device_detection_id = 0; + } + if (self->priv->device_added_id) { + g_signal_handler_disconnect (application, + self->priv->device_added_id); + self->priv->device_added_id = 0; + } + if (self->priv->device_removed_id) { + g_signal_handler_disconnect (application, + self->priv->device_removed_id); + self->priv->device_removed_id = 0; + } + g_object_unref (application); + } + + G_OBJECT_CLASS (qrm_window_parent_class)->dispose (object); +} + +static void +qrm_window_class_init (QrmWindowClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = dispose; + + /* Bind class to template */ + gtk_widget_class_set_template_from_resource (widget_class, "/org/freedesktop/qrm/qrm-window.ui"); + gtk_widget_class_bind_template_child_private (widget_class, QrmWindow, notebook); + gtk_widget_class_bind_template_child_private (widget_class, QrmWindow, header_bar); + gtk_widget_class_bind_template_child_private (widget_class, QrmWindow, back_button); + gtk_widget_class_bind_template_child_private (widget_class, QrmWindow, graph_stack_switcher); + gtk_widget_class_bind_template_child_private (widget_class, QrmWindow, device_list_frame); + gtk_widget_class_bind_template_child_private (widget_class, QrmWindow, device_list_box); + gtk_widget_class_bind_template_child_private (widget_class, QrmWindow, device_list_label); + gtk_widget_class_bind_template_child_private (widget_class, QrmWindow, device_list_spinner_box); + gtk_widget_class_bind_template_child_private (widget_class, QrmWindow, pin_entry_frame); + gtk_widget_class_bind_template_child_private (widget_class, QrmWindow, pin_entry); + gtk_widget_class_bind_template_child_private (widget_class, QrmWindow, attempts_label); + gtk_widget_class_bind_template_child_private (widget_class, QrmWindow, pin_check_spinner_box); + gtk_widget_class_bind_template_child_private (widget_class, QrmWindow, signal_box); + gtk_widget_class_bind_template_child_private (widget_class, QrmWindow, power_box); +} diff --git a/src/qmi-radio-monitor/qrm-window.h b/src/qmi-radio-monitor/qrm-window.h new file mode 100644 index 00000000..f0831524 --- /dev/null +++ b/src/qmi-radio-monitor/qrm-window.h @@ -0,0 +1,51 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * Copyright (C) 2013-2017 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef QRM_WINDOW_H +#define QRM_WINDOW_H + +#include <gtk/gtk.h> + +#include "qrm-app.h" + +G_BEGIN_DECLS + +#define QRM_TYPE_WINDOW (qrm_window_get_type ()) +#define QRM_WINDOW(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), QRM_TYPE_WINDOW, QrmWindow)) +#define QRM_WINDOW_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), QRM_TYPE_WINDOW, QrmWindowClass)) +#define QRM_IS_WINDOW(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), QRM_TYPE_WINDOW)) +#define QRM_IS_WINDOW_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), QRM_TYPE_WINDOW)) +#define QRM_WINDOW_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), QRM_TYPE_WINDOW, QrmWindowClass)) + +typedef struct _QrmWindow QrmWindow; +typedef struct _QrmWindowClass QrmWindowClass; +typedef struct _QrmWindowPrivate QrmWindowPrivate; + +struct _QrmWindow { + GtkApplicationWindow parent_instance; + QrmWindowPrivate *priv; +}; + +struct _QrmWindowClass { + GtkApplicationWindowClass parent_class; +}; + +GType qrm_window_get_type(void) G_GNUC_CONST; + +GtkWidget *qrm_window_new (QrmApp *application); + +G_END_DECLS + +#endif /* QRM_WINDOW_H */ diff --git a/src/qmi-radio-monitor/qrm-window.ui b/src/qmi-radio-monitor/qrm-window.ui new file mode 100644 index 00000000..326cb45f --- /dev/null +++ b/src/qmi-radio-monitor/qrm-window.ui @@ -0,0 +1,366 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <!-- interface-requires gtk+ 3.10 --> + <menu id="gear_menu"> + <section> + <item> + <attribute name="label" translatable="yes">_About QMI Radio Monitor</attribute> + <attribute name="action">app.about</attribute> + </item> + </section> + </menu> + <template class="QrmWindow" parent="GtkApplicationWindow"> + <property name="can_focus">False</property> + <property name="has_focus">False</property> + <property name="is_focus">False</property> + <property name="icon_name">mobile-radio-monitor</property> + <property name="window_position">center</property> + <property name="default_width">800</property> + <property name="default_height">600</property> + <child type="titlebar"> + <object class="GtkHeaderBar" id="header_bar"> + <property name="title">QMI Radio Monitor</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="vexpand">False</property> + <property name="show-close-button">True</property> + <child> + <object class="GtkBox" id="back_box"> + <property name="visible">True</property> + <property name="valign">center</property> + <property name="can_focus">False</property> + <property name="orientation">horizontal</property> + <style> + <class name="linked"/> + </style> + <child> + <object class="GtkButton" id="back_button"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="valign">center</property> + <property name="can_focus">False</property> + <property name="tooltip_text" translatable="yes">Back</property> + <property name="action_name">win.go-back</property> + <style> + <class name="image-button"/> + </style> + <child> + <object class="GtkImage" id="back_button_image"> + <property name="visible">True</property> + <property name="icon_size">1</property> + <property name="icon_name">go-previous-symbolic</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="pack_type">start</property> + </packing> + </child> + <child> + <object class="GtkStackSwitcher" id="graph_stack_switcher"> + <property name="visible">False</property> + <property name="valign">center</property> + <property name="stack">graphs_stack</property> + </object> + <packing> + <property name="pack_type">start</property> + </packing> + </child> + <child> + <object class="GtkMenuButton" id="gear_menu_button"> + <property name="visible">True</property> + <property name="valign">center</property> + <property name="can_focus">False</property> + <property name="action_name">win.gear-menu</property> + <property name="menu_model">gear_menu</property> + <style> + <class name="image-button"/> + </style> + <child> + <object class="GtkImage" id="gear_image"> + <property name="visible">True</property> + <property name="icon_size">1</property> + <property name="icon_name">emblem-system-symbolic</property> + </object> + </child> + </object> + <packing> + <property name="pack_type">end</property> + </packing> + </child> + </object> + </child> + <child> + <object class="GtkNotebook" id="notebook"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="show_tabs">False</property> + <child> + <object class="GtkBox" id="device_list"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">center</property> + <property name="valign">center</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel" id="device_list_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">No available QMI devices</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkFrame" id="device_list_frame"> + <property name="visible">False</property> + <property name="can_focus">False</property> + <property name="shadow_type">in</property> + <child> + <object class="GtkListBox" id="device_list_box"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">center</property> + <property name="valign">center</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkBox" id="device_list_spinner_box"> + <property name="visible">False</property> + <property name="can_focus">False</property> + <property name="halign">start</property> + <property name="valign">center</property> + <property name="orientation">horizontal</property> + <child> + <object class="GtkSpinner" id="device_list_spinner"> + <property name="active">True</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + <property name="padding">8</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="device_list_spinner_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">Detecting new device...</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + <property name="padding">8</property> + </packing> + </child> + <child> + <object class="GtkFrame" id="pin_entry_frame"> + <property name="visible">False</property> + <property name="can_focus">False</property> + <property name="shadow_type">in</property> + <child> + <object class="GtkBox" id="pin_entry_hbox"> + <property name="visible">True</property> + <property name="valign">center</property> + <property name="hexpand">True</property> + <child> + <object class="GtkLabel" id="pin_entry_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0.5</property> + <property name="valign">0.5</property> + <property name="margin-left">20</property> + <property name="margin-right">10</property> + <property name="margin-top">6</property> + <property name="margin-bottom">6</property> + <property name="hexpand">True</property> + <property name="label" translatable="yes">Enter PIN code:</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="pin_entry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="max_length">10</property> + <property name="visibility">False</property> + <property name="invisible_char">●</property> + <property name="activates_default">True</property> + <property name="width_chars">10</property> + <property name="invisible_char_set">True</property> + <property name="input_purpose">password</property> + <property name="xalign">0.5</property> + <property name="valign">0.5</property> + <property name="margin-left">10</property> + <property name="margin-right">10</property> + <property name="margin-top">6</property> + <property name="margin-bottom">6</property> + <property name="hexpand">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="attempts_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">3 attempts left</property> + <property name="xalign">0.0</property> + <property name="valign">0.5</property> + <property name="margin-left">10</property> + <property name="margin-right">20</property> + <property name="margin-top">6</property> + <property name="margin-bottom">6</property> + <property name="hexpand">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkBox" id="pin_check_spinner_box"> + <property name="visible">False</property> + <property name="can_focus">False</property> + <property name="halign">0.5</property> + <property name="valign">0.5</property> + <property name="orientation">horizontal</property> + <property name="hexpand">True</property> + <child> + <object class="GtkSpinner" id="pin_check_spinner"> + <property name="active">True</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + <property name="padding">8</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="pin_check_spinner_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">Checking...</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + <property name="padding">8</property> + </packing> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">3</property> + <property name="padding">20</property> + </packing> + </child> + </object> + </child> + <child type="tab"> + <object class="GtkLabel" id="device_list_tab"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">Device List</property> + </object> + <packing> + <property name="position">0</property> + <property name="tab_fill">False</property> + </packing> + </child> + <child> + <object class="GtkStack" id="graphs_stack"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="transition-type">slide-right</property> + <property name="transition-duration">500</property> + <child> + <object class="QrmSignalTab" id="signal_box" /> + <packing> + <property name="position">0</property> + <property name="name">signal_box</property> + <property name="title">Signal</property> + </packing> + </child> + <child> + <object class="QrmPowerTab" id="power_box" /> + <packing> + <property name="name">power_box</property> + <property name="title">Power</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + <child type="tab"> + <object class="GtkLabel" id="graphs-tab"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">Graphs</property> + </object> + <packing> + <property name="position">1</property> + <property name="tab_fill">False</property> + </packing> + </child> + </object> + </child> + </template> +</interface> diff --git a/src/qmi-radio-monitor/qrm.gresource.xml b/src/qmi-radio-monitor/qrm.gresource.xml new file mode 100644 index 00000000..2158c456 --- /dev/null +++ b/src/qmi-radio-monitor/qrm.gresource.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<gresources> + <gresource prefix="/org/freedesktop/qrm"> + <file preprocess="xml-stripblanks">qrm-window.ui</file> + <file preprocess="xml-stripblanks">qrm-signal-tab.ui</file> + <file preprocess="xml-stripblanks">qrm-power-tab.ui</file> + </gresource> +</gresources> |