summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleksander Morgado <aleksander@aleksander.es>2017-03-03 14:44:46 +0100
committerAleksander Morgado <aleksander@aleksander.es>2017-05-30 11:29:08 +0200
commit7750b8aee3b5bfa18b328769ae111108d071784a (patch)
treedb6d53eeba541b89a03bb7caccfe6b81d3032e56
parentdb0e81c69fdfd1da7bb798c4034329eb24a830bc (diff)
downloadlibqmi-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
-rw-r--r--.gitignore7
-rw-r--r--configure.ac121
-rw-r--r--src/Makefile.am4
-rw-r--r--src/qmi-radio-monitor/Makefile.am124
-rw-r--r--src/qmi-radio-monitor/data/Makefile.am24
-rw-r--r--src/qmi-radio-monitor/data/org.freedesktop.QmiRadioMonitor.pkexec.policy.in23
-rw-r--r--src/qmi-radio-monitor/data/qmi-radio-monitor.desktop11
-rw-r--r--src/qmi-radio-monitor/data/qmi-radio-monitor.pngbin0 -> 33986 bytes
-rw-r--r--src/qmi-radio-monitor/qrm-app.c599
-rw-r--r--src/qmi-radio-monitor/qrm-app.h52
-rw-r--r--src/qmi-radio-monitor/qrm-color-icon.c276
-rw-r--r--src/qmi-radio-monitor/qrm-color-icon.h61
-rw-r--r--src/qmi-radio-monitor/qrm-device.c1497
-rw-r--r--src/qmi-radio-monitor/qrm-device.h163
-rw-r--r--src/qmi-radio-monitor/qrm-error.h29
-rw-r--r--src/qmi-radio-monitor/qrm-graph.c823
-rw-r--r--src/qmi-radio-monitor/qrm-graph.h79
-rw-r--r--src/qmi-radio-monitor/qrm-main.c40
-rw-r--r--src/qmi-radio-monitor/qrm-power-tab.c397
-rw-r--r--src/qmi-radio-monitor/qrm-power-tab.h52
-rw-r--r--src/qmi-radio-monitor/qrm-power-tab.ui930
-rw-r--r--src/qmi-radio-monitor/qrm-signal-tab.c505
-rw-r--r--src/qmi-radio-monitor/qrm-signal-tab.h52
-rw-r--r--src/qmi-radio-monitor/qrm-signal-tab.ui990
-rw-r--r--src/qmi-radio-monitor/qrm-window.c628
-rw-r--r--src/qmi-radio-monitor/qrm-window.h51
-rw-r--r--src/qmi-radio-monitor/qrm-window.ui366
-rw-r--r--src/qmi-radio-monitor/qrm.gresource.xml8
28 files changed, 7864 insertions, 48 deletions
diff --git a/.gitignore b/.gitignore
index 5534d6b2..d4ea39da 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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
new file mode 100644
index 00000000..5634c02a
--- /dev/null
+++ b/src/qmi-radio-monitor/data/qmi-radio-monitor.png
Binary files differ
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, &lte_rssi, &lte_rsrq, &lte_rsrp, &lte_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,
+ &current_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>