Github Issue #8543 - Devices widget: Label recently-connected devices as "NEW"

Github Issue #8543 - Devices widget: Label recently-connected devices as “NEW”

On Sep 22, 2023 Jamke writes:

Search for new devices in the devices widget can be easy and reliable.
Currently it is not easy and the chance of mistake, selection of wrong device, is high.
This can lead to a security leak in case the USB device is a USB-Ethernet connector or other network device connected to vault or any offline qube by mistake.
The most common user case:
A. User inserts USB device in USB slot
B. User opens devices widget
C. User manually search among devices (block or not) for the new just-inserted device.
This user case has 2 problems:
A. User has to manually search though the list reading everything
B. The chance of mistake, selection of wrong device, is high.

And also writes:

How about showing some label or icon “NEW” near device name in the list that was not show in the previous time widget was open, and was inserted in the last, let’s say, 10 minutes.
The device should stay NEW after being shown for the first time for some small time like a minute to prevent it from loosing NEW icon after opening the widget menu fast twice.
Also connecting device to any qube should instantly make the device not NEW.

And finally adds:

It should cover the most common case: user inserts device (block device or not) and tries to connect it somewhere. He will instantly find the connected device. And no spoofing is possible if the system can process events of connections and disconnections.
When I think about it, I do not know why isn’t it a case in all OSes yet.

Diagnosis and Analysis

User request could be easily implemented. Qubes Devices widget is a part of qubes-desktop-linux-manager repository. It is written in PyGTK. We can simply add a timestamp to new devices object and show a NEW! label markup in red color after the devices names for 10 minutes (or any other period).

Solutions

Solution 1 - Implement what user wants

This would improve overall security and functionality.

Here is the patch for this solution.
diff --git a/qui/devices/actionable_widgets.py b/qui/devices/actionable_widgets.py
index 34c69f0..45f151a 100644
--- a/qui/devices/actionable_widgets.py
+++ b/qui/devices/actionable_widgets.py
@@ -34,6 +34,7 @@ gi.require_version('Gtk', '3.0')  # isort:skip
 from gi.repository import Gtk, GdkPixbuf, GLib  # isort:skip
 
 from . import backend
+import tile
 
 
 def load_icon(icon_name: str, backup_name: str, size: int = 24):
@@ -401,7 +402,13 @@ class MainDeviceWidget(ActionableWidget, Gtk.Grid):
         self.device_icon.set_valign(Gtk.Align.CENTER)
 
         self.device_label = Gtk.Label(xalign=0)
-        self.device_label.set_markup(device.name)
+
+        label_markup = device.name
+        if hasattr(device, "_connection_timestamp"):
+            if int(time.monotonic() - device._connection_timestamp) < 120:
+                label_markup += ' <span foreground="red"><b>NEW!</b></span>'
+        self.device_label.set_markup(label_markup)
+
         if self.device.attachments:
             self.device_label.get_style_context().add_class("dev_attached")
 
diff --git a/qui/devices/device_widget.py b/qui/devices/device_widget.py
index 386b282..0a9556c 100644
--- a/qui/devices/device_widget.py
+++ b/qui/devices/device_widget.py
@@ -20,6 +20,7 @@
 from typing import Set, List, Dict
 import asyncio
 import sys
+import time
 
 import importlib.resources
 
@@ -130,6 +131,7 @@ class DevicesTray(Gtk.Application):
 
         for dev_name, dev in changed_devices.items():
             if dev_name not in self.devices:
+                dev._connection_timestamp = time.monotonic()
                 self.devices[dev_name] = dev
                 self.emit_notification(
                     _("Device available"),

And here is the screenshot:

Solution 2 - Do not touch the Devices Widget

Leave the widget as it is. It is already bloated.

Solution 3 - Make it configurable

Allow a special host feature for NEW label timeout. 0 means disable. But default should be X seconds.

Collecting feedback

So if you have read this post till this point, what is your opinion? I personally prefer 120 seconds. 10 minutes is too much.

4 Likes

@kenosen

I have some improvements to the patch. I have not tested it yet. I appreciate if you could test it and see if you like it. Then amend your pull request to apply these:

Updated patch
diff --git a/qui/devices/actionable_widgets.py b/qui/devices/actionable_widgets.py
index 34c69f0..9e96c5b 100644
--- a/qui/devices/actionable_widgets.py
+++ b/qui/devices/actionable_widgets.py
@@ -34,6 +34,7 @@ gi.require_version('Gtk', '3.0')  # isort:skip
 from gi.repository import Gtk, GdkPixbuf, GLib  # isort:skip
 
 from . import backend
+import time
 
 
 def load_icon(icon_name: str, backup_name: str, size: int = 24):
@@ -393,6 +394,11 @@ class MainDeviceWidget(ActionableWidget, Gtk.Grid):
         self.device = device
         self.variant = variant
 
+        # Add NEW! label for new devices for 10 minutes on 1st view
+        self._new_device_label_timout = 10 * 60
+        # Reduce NEW! label timeout to 2 minutes after 1st view
+        self._new_device_label_afterview = 2 * 60
+
         self.get_style_context().add_class('main_device_item')
 
         # the part that is common to all devices
@@ -401,7 +407,17 @@ class MainDeviceWidget(ActionableWidget, Gtk.Grid):
         self.device_icon.set_valign(Gtk.Align.CENTER)
 
         self.device_label = Gtk.Label(xalign=0)
-        self.device_label.set_markup(device.name)
+
+        markup = device.name
+        if hasattr(device, "_connection_timestamp"):
+            delta_sec = int(time.monotonic() - device._connection_timestamp)
+            if delta_sec < self._new_device_label_timeout:
+                markup += ' <span foreground="red"><b>NEW!</b></span>'
+                if delta_sec > self._new_device_label_afterview:
+                    device._connection_timestamp = time.monotonic() - \
+                            self._new_device_label_afterview
+        self.device_label.set_markup(markup)
+
         if self.device.attachments:
             self.device_label.get_style_context().add_class("dev_attached")
 
diff --git a/qui/devices/backend.py b/qui/devices/backend.py
index 1dfaacd..8c73854 100644
--- a/qui/devices/backend.py
+++ b/qui/devices/backend.py
@@ -100,6 +100,8 @@ class Device:
         self._dev: qubesadmin.devices.DeviceInfo = dev
         self.__hash = hash(dev)
         self._port: str = ''
+        # Monotonic connection timestamp only for new devices
+        self._connection_timestamp: float = None
 
         self._dev_name: str = getattr(dev, 'description', 'unknown')
         if dev.devclass == 'block' and 'size' in dev.data:
diff --git a/qui/devices/device_widget.py b/qui/devices/device_widget.py
index 386b282..0a9556c 100644
--- a/qui/devices/device_widget.py
+++ b/qui/devices/device_widget.py
@@ -20,6 +20,7 @@
 from typing import Set, List, Dict
 import asyncio
 import sys
+import time
 
 import importlib.resources
 
@@ -130,6 +131,7 @@ class DevicesTray(Gtk.Application):
 
         for dev_name, dev in changed_devices.items():
             if dev_name not in self.devices:
+                dev._connection_timestamp = time.monotonic()
                 self.devices[dev_name] = dev
                 self.emit_notification(
                     _("Device available"),

The changes to all files should be in a single PR. There should be this line in PR body:

fixes: https://github.com/QubesOS/qubes-issues/issues/8543

After git add *; git commit --amend, the PR message should look like this:

And then you could git push --force

So the PR is properly linked with the PR and would close it if the PR is accepted.

Many thanks @alimirjamali, for both the guidance and the inspiration!

2 Likes

Hi, sorry I didn’t take the time to read this before but I have two remarks:

  • first, I don’t know how screen readers work with Qubes OS, or if the HTML markup is parsed in something else but I believe it is most of the time better to use <strong> to carry information about something important
  • about the red color, is that appropriate in the Qubes OS context? It’s also a label, and it’s suggested that it could be associated with untrusted… Given the current use of bold text, wouldn’t it be enough to remove the red color?
1 Like

Very good. Just for your next commit, study this guide:

It is a very simple. You generate a pair of GPG keys. And upload the public one to a keyserver website. After than, next time you git commit, there will be a popup asking you to enter your GPG password and your commit will be signed with those keys.

The other issue is the CI/CD bot complaining about pylint syntax checking (Python Naming conventions and Coding standards). But it is not your fault in this case. Reading the reports of CI/CD bot is always recommended.

GTK Markup is very similar to HTML markup but is different. They do not use WebKit or Gecko. They use something different which is called Pango which does not have the strong attribute:

I believe yes. A new USB stick connected to PC should be treated as an un-trusted device. Given the fact that it is shown only for 2 minutes (after 1st view), I hope it is not that much painful.

Hmm, I had followed the guide and those mentioned within. I thought I had uploaded my keys both to the keyserver and Github. I’ll check again. Thanks for flagging.

1 Like

In somehow related issue, I and Marmarta have been testing many different tricks to properly apply CSS to GUI Updater labels. It turned out that the CSS classes for GTK labels do not cascade. We had to go through lot’s of different tricks and and workarounds. Some were rejected by Marek because of technical reasons.

Finally we ended up with a proper solution but it took a lot of time.

Thank you for addressing my proposal! I was not expecting it to be implemented anytime soon, nice surprise.

About timeout choosing problem, and your opinion that 10 minutes can be too big. I kind of agree and have another idea.

How about having small timeout like 1 minute - show NEW red label and after 1 minute just show time if was connected for the last time, something like 3 min ago, or 5 hours ago and so on? The color should be more neutral obviously, like gray.
The devices that were available from the boot can be marked since boot or have nothing.
The gray color will make it look not dragging attention too much and everything would be informative and great.

1 Like

Update on the color: Marmarta advised it to be Blue. So blue it is (for the time).

1 Like

The current patch does the following:

Showing the blue NEW! label for up to 10 minutes for the 1st view. Then it is reduced to 2 minutes timeout.

The devices that were available from the boot does not show any NEW! label.

1 Like

A agree with blue or dark blue (like Navy color, easy to read on light gray). Red is kind of disturbing a bit.

I would recommend adding dark yellow (or also blue, or blue but more transparent, close to gray) too, for range range not new and not too old.
E,g.:
0-1 minutes - Blue label “NEW”
1-10 minutes - Dark yellow “6 min ago”
10+ minutes - Dark gray “2 hours ago”.
Since boot - Gray “since boot” or nothing.

@marmarta does it sound reasonable?

Noting we’ve removed the exclamation point so as not to strike fear into the user.

1 Like

Just a note we’re working within the style guide; colors available here:

2 Likes

I believe we could wait to see how it turns out on light and dark themes. Then we will can further improve it. And we can collect more feedback from other users. Overall I believe it was a very good idea.

2 Likes

I am sure, that what is already done by you, will improve the user experience a lot. Thank you.

1 Like

Actually @kenosen is doing most PR and communication in this case. I am helping a little bit time to time.