[gnome-boxes/wip/new-properties-dialog] WIP




commit 2f94186a4b9a9e80655fb58ee546968aa77819f9
Author: Felipe Borges <felipeborges gnome org>
Date:   Wed Jan 13 11:02:12 2021 +0100

    WIP

 data/gnome-boxes.gresource.xml            |   4 +
 data/ui/preferences/devices-page.ui       |  67 ++++++
 data/ui/preferences/preferences-dialog.ui |  26 ++
 data/ui/preferences/resources-page.ui     | 158 +++++++++++++
 data/ui/preferences/snapshots-page.ui     |  80 +++++++
 data/ui/properties-shared-folder-row.ui   |   2 +-
 data/ui/properties-window.ui              |   8 +-
 data/ui/shared-folders.ui                 |   7 +-
 data/ui/snapshots-widget.ui               |  68 ++++++
 src/app-window.vala                       |   9 +-
 src/meson.build                           |   5 +
 src/preferences/devices-page.vala         |  12 +
 src/preferences/preferences-dialog.vala   |  45 ++++
 src/preferences/preferences-page.vala     |  58 +++++
 src/preferences/resources-page.vala       | 378 ++++++++++++++++++++++++++++++
 src/preferences/snapshots-page.vala       | 153 ++++++++++++
 src/properties-window.vala                |  14 +-
 src/shared-folders.vala                   |   8 +-
 src/snapshot-list-row.vala                |  47 +---
 src/spice-display.vala                    |   7 -
 20 files changed, 1089 insertions(+), 67 deletions(-)
---
diff --git a/data/gnome-boxes.gresource.xml b/data/gnome-boxes.gresource.xml
index 66528948..69fc28bf 100644
--- a/data/gnome-boxes.gresource.xml
+++ b/data/gnome-boxes.gresource.xml
@@ -24,6 +24,10 @@
     <file preprocess="xml-stripblanks">ui/list-view.ui</file>
     <file preprocess="xml-stripblanks">ui/list-view-row.ui</file>
     <file preprocess="xml-stripblanks">ui/notification.ui</file>
+    <file preprocess="xml-stripblanks">ui/preferences/preferences-dialog.ui</file>
+    <file preprocess="xml-stripblanks">ui/preferences/devices-page.ui</file>
+    <file preprocess="xml-stripblanks">ui/preferences/resources-page.ui</file>
+    <file preprocess="xml-stripblanks">ui/preferences/snapshots-page.ui</file>
     <file preprocess="xml-stripblanks">ui/properties-shared-folder-row.ui</file>
     <file preprocess="xml-stripblanks">ui/properties-page-widget.ui</file>
     <file preprocess="xml-stripblanks">ui/properties-toolbar.ui</file>
diff --git a/data/ui/preferences/devices-page.ui b/data/ui/preferences/devices-page.ui
new file mode 100644
index 00000000..5380f1d0
--- /dev/null
+++ b/data/ui/preferences/devices-page.ui
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="BoxesPreferencesDevicesPage" parent="HdyPreferencesPage">
+    <property name="visible">True</property>
+    <property name="icon-name">media-removable-symbolic</property>
+    <property name="title" translatable="yes">Devices &amp; Shares</property>
+
+    <child>
+      <object class="HdyPreferencesGroup" id="usb_devices_group">
+        <property name="visible">True</property>
+        <property name="title" translatable="yes">Devices</property>
+
+        <child>
+          <object class="HdyActionRow">
+            <property name="visible">True</property>
+            <property name="title" translatable="yes">No CD/DVD Image</property>
+
+            <child>
+              <object class="GtkButton">
+                <property name="visible">True</property>
+                <property name="label" translatable="yes">Select</property>
+                <property name="valign">center</property>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+
+    <child>
+      <object class="HdyPreferencesGroup">
+        <property name="visible">True</property>
+        <property name="title" translatable="yes">Folder Shares</property>
+
+        <child>
+          <object class="BoxesSharedFoldersWidget" id="shared_folders_widget">
+            <property name="visible">True</property>
+          </object>
+        </child>
+
+        <!--<child>
+          <object class="HdyPreferencesRow">
+            <property name="visible">True</property>
+
+            <child>
+              <object class="GtkButton">
+                <property name="visible">True</property>
+                <property name="valign">center</property>
+                <property name="halign">center</property>
+                <style>
+                  <class name="flat"/>
+                </style>
+
+                <child>
+                  <object class="GtkImage">
+                    <property name="visible">True</property>
+                    <property name="icon-name">list-add-symbolic</property>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child> -->
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/data/ui/preferences/preferences-dialog.ui b/data/ui/preferences/preferences-dialog.ui
new file mode 100644
index 00000000..6785352a
--- /dev/null
+++ b/data/ui/preferences/preferences-dialog.ui
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="BoxesPreferencesDialog" parent="HdyPreferencesWindow">
+    <property name="role">boxes-preferences</property>
+    <property name="can_focus">False</property>
+    <property name="resizable">True</property>
+    <property name="modal">True</property>
+    <property name="type-hint">dialog</property>
+    <property name="width-request">724</property>
+    <property name="height-request">468</property>
+    <signal name="key-press-event" after="yes" handler="on_key_pressed"/>
+
+    <child>
+      <object class="BoxesPreferencesResourcesPage" id="resources_page"/>
+    </child>
+
+    <child>
+      <object class="BoxesPreferencesDevicesPage" id="devices_page"/>
+    </child>
+
+    <child>
+      <object class="BoxesPreferencesSnapshotsPage" id="snapshots_page"/>
+    </child>
+
+  </template>
+</interface>
diff --git a/data/ui/preferences/resources-page.ui b/data/ui/preferences/resources-page.ui
new file mode 100644
index 00000000..ce3402b8
--- /dev/null
+++ b/data/ui/preferences/resources-page.ui
@@ -0,0 +1,158 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="BoxesPreferencesResourcesPage" parent="HdyPreferencesPage">
+    <property name="visible">True</property>
+    <property name="icon-name">network-cellular-signal-excellent-symbolic</property>
+    <property name="title" translatable="yes">Resources</property>
+
+    <child>
+      <object class="HdyPreferencesGroup" id="box_stats">
+        <property name="visible">True</property>
+
+        <child>
+          <object class="HdyActionRow">
+            <property name="visible">True</property>
+            <property name="title" translatable="yes">Box Name</property>
+
+            <child>
+              <object class="GtkEntry" id="box_name_entry">
+                <property name="visible">True</property>
+                <property name="hexpand">True</property>
+                <property name="valign">center</property>
+              </object>
+            </child>
+          </object>
+        </child>
+
+        <child>
+          <object class="HdyActionRow">
+            <property name="visible">True</property>
+            <property name="title" translatable="yes">3D Acceleration</property>
+            <property name="activatable_widget">accel_3d_toggle</property>
+
+            <child>
+              <object class="GtkSwitch" id="accel_3d_toggle">
+                <property name="visible">True</property>
+                <property name="valign">center</property>
+              </object>
+            </child>
+          </object>
+        </child>
+
+        <child>
+          <object class="GtkBox">
+            <property name="visible">True</property>
+            <property name="margin-top">18</property>
+
+            <child>
+              <object class="GtkButton">
+                <property name="visible">True</property>
+                <property name="label" translatable="yes">Troubleshooting Log</property>
+              </object>
+            </child>
+
+            <child>
+              <object class="GtkBox">
+                <property name="visible">True</property>
+                <property name="spacing">10</property>
+
+                <child>
+                  <object class="GtkButton" id="restart_button">
+                    <property name="visible">True</property>
+                    <property name="label" translatable="yes">Restart</property>
+                    <signal name="clicked" handler="on_restart_button_clicked"/>
+                    <style>
+                      <class name="suggested-action"/>
+                    </style>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkButton" id="force_shutdown_button">
+                    <property name="visible">True</property>
+                    <property name="label" translatable="yes">Force Shutdown</property>
+                    <signal name="clicked" handler="on_force_shutdown_button_clicked"/>
+                    <style>
+                      <class name="destructive-action"/>
+                    </style>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="pack-type">end</property>
+              </packing>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+
+    <child>
+      <object class="HdyPreferencesGroup" id="box_resources">
+        <property name="visible">True</property>
+        <property name="title" translatable="yes">Resources</property>
+
+        <child>
+          <object class="HdyActionRow">
+            <property name="visible">True</property>
+            <property name="title" translatable="yes">Memory</property>
+
+            <child>
+              <object class="GtkBox" id="memory_widget">
+                <property name="visible">True</property>
+              </object>
+            </child>
+          </object>
+        </child>
+
+        <child>
+          <object class="HdyActionRow">
+            <property name="visible">True</property>
+            <property name="title" translatable="yes">Storage</property>
+
+            <child>
+              <object class="GtkBox" id="storage_widget">
+                <property name="visible">True</property>
+              </object>
+            </child>
+          </object>
+        </child>
+
+        <child>
+          <object class="HdyActionRow">
+            <property name="visible">True</property>
+            <property name="title" translatable="yes">CPUs</property>
+
+            <child>
+              <object class="GtkSpinButton" id="cpus_spinbutton">
+                <property name="visible">True</property>
+                <property name="valign">center</property>
+              </object>
+            </child>
+          </object>
+        </child>
+
+        <child>
+          <object class="HdyActionRow">
+            <property name="visible">True</property>
+            <property name="title" translatable="yes">Allow running in background</property>
+
+            <child>
+              <object class="GtkSwitch" id="run_in_bg_switch">
+                <property name="visible">True</property>
+                <property name="valign">center</property>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+
+  <object class="GtkSizeGroup">
+    <property name="mode">horizontal</property>
+    <widgets>
+      <widget name="restart_button"/>
+      <widget name="force_shutdown_button"/>
+    </widgets>
+  </object>
+</interface>
diff --git a/data/ui/preferences/snapshots-page.ui b/data/ui/preferences/snapshots-page.ui
new file mode 100644
index 00000000..1fec905d
--- /dev/null
+++ b/data/ui/preferences/snapshots-page.ui
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="BoxesPreferencesSnapshotsPage" parent="BoxesPreferencesPage">
+    <property name="visible">True</property>
+    <property name="icon-name">rotation-allowed-symbolic</property>
+    <property name="title" translatable="yes">Snapshots</property>
+
+    <child>
+      <object class="HdyPreferencesGroup">
+        <property name="visible">True</property>
+
+        <child>
+          <object class="GtkBox">
+            <property name="visible">True</property>
+            <property name="orientation">vertical</property>
+            <property name="margin-top">20</property>
+            <property name="margin-bottom">20</property>
+            <style>
+              <class name="frame"/>
+            </style>
+            <child>
+              <object class="GtkStack" id="stack">
+                <property name="visible">True</property>
+
+                <child>
+                  <object class="GtkListBox" id="list_page">
+                    <property name="visible">True</property>
+                    <property name="selection-mode">none</property>
+                    <signal name="add" handler="update_stack"/>
+                    <signal name="remove" handler="update_stack"/>
+                  </object>
+                </child>
+
+                <child>
+                  <object class="HdyStatusPage" id="empty_page">
+                    <property name="visible">True</property>
+                    <property name="hexpand">True</property>
+                    <property name="vexpand">True</property>
+                    <property name="icon-name">empty-boxes</property>
+                    <property name="title" translatable="yes">No snapshots created yet.</property>
+                    <property name="description" translatable="yes">Create one using the button 
below.</property>
+                  </object>
+                </child>
+
+                <child>
+                  <object class="GtkBox" id="activity_page">
+                    <property name="visible">True</property>
+
+                    <child>
+                      <object class="GtkSpinner">
+                        <property name="visible">True</property>
+                        <property name="active">True</property>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+
+            <child>
+              <object class="GtkButton">
+                <property name="visible">True</property>
+                <signal name="clicked" handler="create_snapshot"/>
+                <style>
+                  <class name="flat"/>
+                </style>
+                <child>
+                  <object class="GtkImage">
+                    <property name="visible">True</property>
+                    <property name="icon-name">list-add-symbolic</property>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/data/ui/properties-shared-folder-row.ui b/data/ui/properties-shared-folder-row.ui
index b703084d..c4e9da00 100644
--- a/data/ui/properties-shared-folder-row.ui
+++ b/data/ui/properties-shared-folder-row.ui
@@ -2,7 +2,7 @@
 <!-- Generated with glade 3.20.0 -->
 <interface>
   <requires lib="gtk+" version="3.19"/>
-  <template class="BoxesSharedFolderRow" parent="GtkListBoxRow">
+  <template class="BoxesSharedFolderRow" parent="HdyPreferencesRow">
     <property name="visible">True</property>
 
     <child>
diff --git a/data/ui/properties-window.ui b/data/ui/properties-window.ui
index 8a8e63d3..1bd6053f 100644
--- a/data/ui/properties-window.ui
+++ b/data/ui/properties-window.ui
@@ -21,7 +21,7 @@
         |-> topbar = new Boxes.PropertiesToolbar (); // as titlebar
   -->
 
-  <template class="BoxesPropertiesWindow" parent="GtkWindow">
+  <template class="BoxesPropertiesWindow" parent="HdyPreferencesWindow">
     <property name="can_focus">False</property>
     <property name="resizable">True</property>
     <property name="modal">True</property>
@@ -95,12 +95,6 @@
 
       </object>
     </child>
-
-    <child type="titlebar">
-      <object class="BoxesPropertiesToolbar" id="topbar">
-        <property name="visible">True</property>
-      </object>
-    </child>
   </template>
 
   <object class="GtkFileFilter" id="supported_files_filter">
diff --git a/data/ui/shared-folders.ui b/data/ui/shared-folders.ui
index 25431a8a..e3734645 100644
--- a/data/ui/shared-folders.ui
+++ b/data/ui/shared-folders.ui
@@ -24,15 +24,14 @@
             <property name="visible">True</property>
             <property name="halign">center</property>
             <signal name="clicked" handler="on_add_button_clicked"/>
+            <style>
+              <class name="flat"/>
+            </style>
 
             <child>
               <object class="GtkImage">
                 <property name="visible">True</property>
                 <property name="icon-name">list-add-symbolic</property>
-                <property name="icon-size">button</property>
-                <style>
-                  <class name="flat"/>
-                </style>
               </object>
             </child>
           </object>
diff --git a/data/ui/snapshots-widget.ui b/data/ui/snapshots-widget.ui
new file mode 100644
index 00000000..6a0b89f1
--- /dev/null
+++ b/data/ui/snapshots-widget.ui
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="BoxesSnapshotsWidget" parent="GtkStack">
+    <property name="visible">True</property>
+
+    <child>
+      <object class="GtkBox" id="list_page">
+        <property name="visible">True</property>
+        <property name="margin">20</property>
+        <property name="orientation">vertical</property>
+        <signal name="add" handler="update_snapshot_stack_page"/>
+        <signal name="remove" handler="update_snapshot_stack_page"/>
+
+        <child>
+          <object class="GtkListBox" id="listbox">
+            <property name="visible">True</property>
+            <property name="selection-mode">none</property>
+
+            <style>
+              <class name="frame"/>
+            </style>
+          </object>
+        </child>
+
+        <child>
+          <object class="GtkButton">
+            <property name="visible">True</property>
+            <signal name="clicked" handler="create_snapshot"/>
+
+            <child>
+              <object class="GtkImage">
+                <property name="visible">True</property>
+                <property name="icon-name">list-add-symbolic</property>
+                <style>
+                  <class name="flat"/>
+                </style>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+
+    <child>
+      <object class="HdyStatusPage" id="empty_page">
+        <property name="visible">True</property>
+        <property name="hexpand">True</property>
+        <property name="vexpand">True</property>
+        <property name="icon-name">empty-boxes</property>
+        <property name="title" translatable="yes">No snapshots created yet.</property>
+        <property name="description" translatable="yes">Create one using the button below.</property>
+      </object>
+    </child>
+
+    <child>
+      <object class="GtkBox" id="activity_page">
+        <property name="visible">True</property>
+
+        <child>
+          <object class="GtkSpinner">
+            <property name="visible">True</property>
+            <property name="active">True</property>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/src/app-window.vala b/src/app-window.vala
index 348ce22c..88ae4947 100644
--- a/src/app-window.vala
+++ b/src/app-window.vala
@@ -296,7 +296,12 @@ public void show_welcome_tutorial () {
     }
 
     public void show_properties () {
-        if (current_item != null) {
+        if (current_item == null)
+            return;
+
+        new Boxes.PreferencesDialog (this, current_item as Machine).present ();
+
+        /*if (current_item != null) {
             if (ui_state == UIState.COLLECTION && selection_mode)
                 selection_mode = false;
             set_state (UIState.PROPERTIES);
@@ -314,7 +319,7 @@ public void show_properties () {
             current_item = item;
             set_state (UIState.PROPERTIES);
             break;
-        }
+        }*/
     }
 
     public void show_send_file () {
diff --git a/src/meson.build b/src/meson.build
index aa45b773..9e54ee24 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -79,6 +79,11 @@ vala_sources = [
   'notificationbar.vala',
   'os-database.vala',
   'portals.vala',
+  'preferences/preferences-dialog.vala',
+  'preferences/preferences-page.vala',
+  'preferences/devices-page.vala',
+  'preferences/resources-page.vala',
+  'preferences/snapshots-page.vala',
   'properties.vala',
   'properties-window.vala',
   'properties-page-widget.vala',
diff --git a/src/preferences/devices-page.vala b/src/preferences/devices-page.vala
new file mode 100644
index 00000000..32fe8cfd
--- /dev/null
+++ b/src/preferences/devices-page.vala
@@ -0,0 +1,12 @@
+// This file is part of GNOME Boxes. License: LGPLv2+
+using Gtk;
+
+[GtkTemplate (ui = "/org/gnome/Boxes/ui/preferences/devices-page.ui")]
+private class Boxes.PreferencesDevicesPage: Hdy.PreferencesPage {
+    [GtkChild]
+    private Boxes.SharedFoldersWidget shared_folders_widget;
+
+    public void setup (LibvirtMachine machine) {
+        shared_folders_widget.setup (machine.domain.get_uuid ());
+    }
+}
diff --git a/src/preferences/preferences-dialog.vala b/src/preferences/preferences-dialog.vala
new file mode 100644
index 00000000..6f71a5a8
--- /dev/null
+++ b/src/preferences/preferences-dialog.vala
@@ -0,0 +1,45 @@
+// This file is part of GNOME Boxes. License: LGPLv2+
+using Gtk;
+
+[GtkTemplate (ui = "/org/gnome/Boxes/ui/preferences/preferences-dialog.ui")]
+private class Boxes.PreferencesDialog: Hdy.PreferencesWindow {
+    private unowned AppWindow app_window;
+
+    [GtkChild]
+    private PreferencesResourcesPage resources_page;
+    [GtkChild]
+    private PreferencesDevicesPage devices_page;
+    [GtkChild]
+    private PreferencesSnapshotsPage snapshots_page;
+
+    public PreferencesDialog (AppWindow app_window, Machine machine) {
+        set_transient_for (app_window);
+        this.app_window = app_window;
+
+        resources_page.setup (machine as LibvirtMachine);
+        devices_page.setup (machine as LibvirtMachine);
+        snapshots_page.setup (machine as LibvirtMachine);
+    }
+
+    [GtkCallback]
+    private bool on_key_pressed (Widget widget, Gdk.EventKey event) {
+        var default_modifiers = Gtk.accelerator_get_default_mod_mask ();
+        var direction = get_direction ();
+
+        if (((direction == Gtk.TextDirection.LTR && // LTR
+              event.keyval == Gdk.Key.Left) ||      // ALT + Left -> back
+             (direction == Gtk.TextDirection.RTL && // RTL
+              event.keyval == Gdk.Key.Right)) &&    // ALT + Right -> back
+            (event.state & default_modifiers) == Gdk.ModifierType.MOD1_MASK) {
+            //topbar.click_back_button ();
+
+            return true;
+        } else if (event.keyval == Gdk.Key.Escape) { // ESC -> back
+            //revert_state ();
+
+            return true;
+        }
+
+        return false;
+    }
+}
diff --git a/src/preferences/preferences-page.vala b/src/preferences/preferences-page.vala
new file mode 100644
index 00000000..c5409370
--- /dev/null
+++ b/src/preferences/preferences-page.vala
@@ -0,0 +1,58 @@
+// This file is part of GNOME Boxes. License: LGPLv2+
+using Gtk;
+
+private class Boxes.PreferencesNotification: Gtk.InfoBar {
+    private Notification.DismissFunc? dismiss_func;
+
+    public PreferencesNotification (string message,
+                                    string action_label,
+                                    owned Notification.OKFunc ok_func,
+                                    owned Notification.DismissFunc? ignore_func = null) {
+        dismiss_func = ignore_func;
+
+        var label = new Gtk.Label (message) {
+            hexpand = true,
+            halign = Gtk.Align.START
+        };
+        var button = new Gtk.Button.with_label (action_label);
+        button.clicked.connect (() => {
+            if (ok_func != null)
+                ok_func ();
+
+            destroy ();
+        });
+
+        get_content_area ().add (label);
+        get_content_area ().add (button);
+    }
+
+    public void dismiss () {
+        if (dismiss_func != null)
+            dismiss_func ();
+    }
+}
+
+private class Boxes.PreferencesPage: Hdy.PreferencesPage {
+    protected PreferencesNotification active_notification = null;
+
+    protected void display_notification (string message,
+                                         string action_label,
+                                         owned Notification.OKFunc       ok_func,
+                                         owned Notification.DismissFunc? ignore_func = null) {
+        if (active_notification != null) {
+            active_notification.dismiss ();
+        }
+
+        active_notification = new PreferencesNotification (message, action_label, ok_func, ignore_func);
+
+        var box = get_parent ().get_parent () as Gtk.Container;
+        box.add_with_properties (active_notification, "position", 0, null);
+
+        active_notification.show_all ();
+    }
+
+    ~PreferencesPage () {
+        if (active_notification != null)
+            active_notification.dismiss ();
+    }
+}
diff --git a/src/preferences/resources-page.vala b/src/preferences/resources-page.vala
new file mode 100644
index 00000000..bbe26f46
--- /dev/null
+++ b/src/preferences/resources-page.vala
@@ -0,0 +1,378 @@
+// This file is part of GNOME Boxes. License: LGPLv2+
+using Gtk;
+
+[GtkTemplate (ui = "/org/gnome/Boxes/ui/preferences/resources-page.ui")]
+private class Boxes.PreferencesResourcesPage: Hdy.PreferencesPage {
+    [GtkChild]
+    private Gtk.Entry box_name_entry;
+    [GtkChild]
+    private Gtk.Switch accel_3d_toggle;
+
+    [GtkChild]
+    private Gtk.Button restart_button;
+    [GtkChild]
+    private Gtk.Button force_shutdown_button;
+
+    [GtkChild]
+    private Gtk.Box memory_widget;
+    [GtkChild]
+    private Gtk.Box storage_widget;
+    [GtkChild]
+    private Gtk.SpinButton cpus_spinbutton;
+    [GtkChild]
+    private Gtk.Switch run_in_bg_switch;
+
+    private LibvirtMachine machine;
+
+    public void setup (LibvirtMachine machine) {
+        this.machine = machine;
+
+        box_name_entry.set_text (machine.name);
+        box_name_entry.bind_property ("text", machine, "name", BindingFlags.BIDIRECTIONAL);
+
+        accel_3d_toggle.set_active (machine.acceleration_3d);
+        accel_3d_toggle.bind_property ("active", machine, "acceleration-3d", BindingFlags.BIDIRECTIONAL);
+
+#if FLATPAK
+        if (accel_3d_toggle.get_active ())
+            on_run_in_bg ();
+#endif
+
+        // Action buttons
+        restart_button.sensitive = force_shutdown_button.sensitive = machine.is_running;
+        machine.notify["state"].connect (on_machine_state_changed);
+
+        var host_topology = machine.connection.get_node_info ();
+        add_ram_property ();
+        add_storage_property ();
+
+        /* CPU property */
+        var vcpus = machine.domain_config.get_vcpus ();
+        var max_vcpus = host_topology.cores * host_topology.sockets * host_topology.threads;
+        cpus_spinbutton.set_range (1, max_vcpus);
+        cpus_spinbutton.set_value (vcpus);
+
+        run_in_bg_switch.set_active (machine.run_in_bg);
+        //run_in_bg_switch.bind_property ("active", machine, "run-in-bg", BindingFlags.BIDIRECTIONAL);
+
+        run_in_bg_switch.notify["active"].connect (() => {
+            var box = get_parent ().get_parent () as Gtk.Container;
+            var infobar = new Gtk.InfoBar () {
+                valign = Gtk.Align.START
+            }; 
+            infobar.add (new Gtk.Label ("TEST LABEL"));
+            //box.pack_end (infobar, false, false, 0);
+            box.add_with_properties (infobar, "position", 0, null);
+            infobar.show_all ();
+        });
+
+    }
+
+    private async void on_run_in_bg () {
+        if (!machine.run_in_bg)
+            return;
+
+        yield Portals.get_default ().request_to_run_in_background (
+        (response, results) => {
+            if (response == 0) {
+                debug ("User authorized Boxes to run in background");
+
+                return;
+            }
+
+            try {
+                machine.run_in_bg = false;
+
+                var msg = _("Boxes is not authorized to run in the background");
+                machine.window.notificationbar.display_for_action (msg,
+                                                                   _("Manage permissions"),
+                                                                   open_permission_settings);
+            } catch (GLib.Error error) {
+                warning ("Failed to reset VM's run-in-bg setting: %s", error.message);
+            }
+        });
+    }
+
+    private void on_machine_state_changed () {
+        restart_button.sensitive = machine.is_running;
+        force_shutdown_button.sensitive = machine.is_running;
+    }
+
+    [GtkCallback]
+    private void on_restart_button_clicked () {
+        machine.restart ();
+    }
+
+    [GtkCallback]
+    private void on_force_shutdown_button_clicked () {
+        machine.force_shutdown ();
+    }
+
+    private void add_ram_property () {
+        try {
+            var host_topology = machine.connection.get_node_info ();
+
+            var min_ram = 64 * Osinfo.MEBIBYTES;
+            var max_ram = host_topology.memory * Osinfo.KIBIBYTES;
+            var size_ram = machine.domain_config.memory * Osinfo.KIBIBYTES;
+
+            var ram_property = new SizeProperty ("ram", size_ram, min_ram, max_ram, 0, min_ram, 
GLib.FormatSizeFlags.IEC_UNITS);
+            memory_widget.add (ram_property.extra_widget);
+            ram_property.extra_widget.visible = true;
+
+            if ((VMConfigurator.is_install_config (machine.domain_config) ||
+                 VMConfigurator.is_live_config (machine.domain_config)) &&
+                 machine.state != Machine.MachineState.FORCE_STOPPED)
+                ram_property.sensitive = false;
+            else
+                ram_property.changed.connect ((property, value) => {
+                    property.deferred_change = () => {
+                        var ram = (value + Osinfo.KIBIBYTES - 1) / Osinfo.KIBIBYTES;
+                        try {
+                            var config = machine.domain.get_config (GVir.DomainXMLFlags.INACTIVE);
+                            config.memory = ram;
+                            if (config.get_class ().find_property ("current-memory") != null)
+                                config.set ("current-memory", ram);
+                            machine.domain.set_config (config);
+                            debug ("RAM changed to %llu KiB", ram);
+                        } catch (GLib.Error error) {
+                            warning ("Failed to change RAM of box '%s' to %llu KiB: %s",
+                                     machine.domain.get_name (), ram, error.message);
+                        }
+
+                        update_ram_property (property);
+
+                        return false;
+                    };
+                });
+
+            machine.notify["state"].connect (() => {
+                if (!machine.is_on)
+                    ram_property.reboot_required = false;
+            });
+
+            update_ram_property (ram_property);
+        } catch (GLib.Error error) {
+            warning ("Failed to add RAM property: %s", error.message);
+        }
+    }
+
+    private void update_ram_property (Boxes.Property property) {
+        try {
+            var config = machine.domain.get_config (GVir.DomainXMLFlags.INACTIVE);
+
+            // we use KiB unit, convert to MiB
+            var actual = machine.domain_config.memory / 1024;
+            var pending = config.memory / 1024;
+
+            debug ("RAM actual: %llu, pending: %llu", actual, pending);
+            // somehow, there are rounded errors, so let's forget about 1Mb diff
+            property.reboot_required = (actual - pending) > 1; // no need for abs()
+        } catch (GLib.Error e) {}
+    }
+
+    private const uint64 MEGABYTES = 1000 * 1000;
+    private void add_storage_property () {
+        if (machine.importing || machine.storage_volume == null)
+            return;
+
+        try {
+            var volume_info = machine.storage_volume.get_info ();
+            var pool = get_storage_pool (machine.connection);
+            var pool_info = pool.get_info ();
+            var min_storage = get_minimum_disk_size ();
+            var max_storage = volume_info.allocation + pool_info.available;
+
+            if (min_storage >= max_storage) {
+                var label = new Gtk.Label ("");
+                var capacity = format_size (volume_info.capacity, FormatSizeFlags.DEFAULT);
+                var allocation = format_size (volume_info.allocation, FormatSizeFlags.DEFAULT);
+                var label_text = _("Maximum Disk Space");
+                var allocation_text = _("%s used").printf (allocation);
+                var markup = ("<span color=\"grey\">%s</span>\t\t %s <span 
color=\"grey\">(%s)</span>").printf (label_text, capacity, allocation_text);
+                label.set_markup (markup);
+                label.halign = Gtk.Align.START;
+
+                storage_widget.add (label);
+
+                var infobar = new Gtk.InfoBar ();
+                infobar.message_type = Gtk.MessageType.WARNING;
+
+                var content = infobar.get_content_area ();
+
+                var image = new Gtk.Image ();
+                image.icon_name = "dialog-warning";
+                image.icon_size = 3;
+                content.add (image);
+
+                var msg = _("There is not enough space on your machine to increase the maximum disk size.");
+                label = new Gtk.Label (msg);
+                content.add (label);
+
+                //add_property (ref list, null, infobar);
+                storage_widget.add (infobar);
+            }
+
+            var property = new SizeProperty ("storage",
+                                             volume_info.capacity,
+                                             min_storage,
+                                             max_storage,
+                                             volume_info.allocation,
+                                             256 * MEGABYTES,
+                                             GLib.FormatSizeFlags.DEFAULT);
+
+            // Disable 'save on timeout' all together since that could lead us to very bad user experience:
+            // You accidently increase the capacity to too high value and if you are not quick enough to 
change
+            // it again, you'll not be able to correct this ever as we don't support shrinking of volumes.
+            property.defer_interval = 0;
+            if ((VMConfigurator.is_install_config (machine.domain_config) ||
+                 VMConfigurator.is_live_config (machine.domain_config)) &&
+                machine.window.ui_state != Boxes.UIState.WIZARD &&
+                machine.state != Machine.MachineState.FORCE_STOPPED)
+                property.sensitive = false;
+            else
+                property.changed.connect (on_storage_changed);
+
+            storage_widget.add (property.extra_widget);
+            storage_widget.show_all ();
+        } catch (GLib.Error error) {
+            warning ("Failed to get information on volume '%s' or it's parent pool: %s",
+                     machine.storage_volume.get_name (),
+                     error.message);
+        }
+    }
+
+    private async void change_storage_size (Boxes.Property property, uint64 value) {
+        if (machine.storage_volume == null)
+            return;
+
+        List<GVir.DomainSnapshot> snapshots;
+        try {
+            snapshots = yield get_snapshots (null);
+        } catch (GLib.Error e) {
+            warning ("Error fetching snapshots for %s: %s", machine.name, e.message);
+            snapshots = new List<GVir.DomainSnapshot> ();
+        }
+
+        var num_snapshots = snapshots.length ();
+        if (num_snapshots != 0) {
+            // qemu-img doesn't support resizing disk image with snapshots:
+            // https://bugs.launchpad.net/qemu/+bug/1563931
+            var msg = ngettext ("Storage resize requires deleting associated snapshot.",
+                                "Storage resize requires deleting %llu associated snapshots.",
+                                num_snapshots).printf (num_snapshots);
+
+            Notification.OKFunc undo = () => {
+                debug ("Storage resize of '%s' cancelled by user.", machine.name);
+            };
+
+            Notification.DismissFunc really_resize = () => {
+                debug ("User did not cancel storage resize of '%s'. Deleting all snapshots..", machine.name);
+                force_change_storage_size.begin (property, value);
+            };
+
+            machine.window.notificationbar.display_for_action (msg, _("_Undo"), (owned) undo, (owned) 
really_resize);
+
+            return;
+        }
+
+        try {
+            if (machine.is_running) {
+                var disk = machine.get_domain_disk ();
+                if (disk == null)
+                    return;
+
+                var size = (value + Osinfo.KIBIBYTES - 1) / Osinfo.KIBIBYTES;
+                disk.resize (size, 0);
+
+                var pool = get_storage_pool (machine.connection);
+                try {
+                  yield pool.refresh_async (null);
+                  machine.update_domain_config ();
+                  debug ("Storage changed to %llu KiB", size);
+                } catch (GLib.Error error) {
+                  warning ("Failed to change storage capacity of volume '%s' to %llu KiB: %s",
+                           machine.storage_volume.get_name (),
+                           size,
+                           error.message);
+                }
+            } else {
+                resize_storage_volume (value);
+                debug ("Storage changed to %llu", value);
+            }
+        } catch (GLib.Error error) {
+            warning ("Failed to change storage capacity of volume '%s' to %llu: %s",
+                    machine.storage_volume.get_name (),
+                    value,
+                    error.message);
+        }
+    }
+
+    private async void force_change_storage_size (Boxes.Property property, uint64 value) {
+        try {
+            var snapshots = yield get_snapshots (null);
+
+            foreach (var snapshot in snapshots) {
+                yield snapshot.delete_async (0, null);
+            }
+
+            change_storage_size.begin (property, value);
+        } catch (GLib.Error e) {
+            warning ("Error while deleting snapshots: %s", e.message);
+        }
+    }
+
+    private uint64 get_minimum_disk_size () throws GLib.Error {
+        var volume_info = machine.storage_volume.get_info ();
+        if (machine.vm_creator == null) {
+            // Since we disable the properties during install going on we don't need to check for
+            // previous_ui_state here to be WIZARD.
+            return volume_info.capacity;
+        }
+
+        Osinfo.Resources minimum_resources = null;
+
+        if (machine.vm_creator.install_media.os != null) {
+            var os = machine.vm_creator.install_media.os;
+            var architecture = machine.domain_config.get_os ().get_arch ();
+            minimum_resources = OSDatabase.get_minimum_resources_for_os (os, architecture);
+        }
+
+        if (minimum_resources != null && minimum_resources.storage != -1) {
+            return minimum_resources.storage;
+        } else {
+            minimum_resources = OSDatabase.get_minimum_resources ();
+            return uint64.min (volume_info.capacity, minimum_resources.storage);
+        }
+    }
+
+    private void resize_storage_volume (uint64 size) throws GLib.Error {
+        var volume_info = machine.storage_volume.get_info ();
+        if (machine.vm_creator != null && size < volume_info.capacity) {
+            // New VM Customization
+            var config = machine.storage_volume.get_config (GVir.DomainXMLFlags.NONE);
+            config.set_capacity (size);
+            machine.storage_volume.delete (0);
+
+            var pool = get_storage_pool (machine.connection);
+            machine.storage_volume = pool.create_volume (config);
+        } else {
+            machine.storage_volume.resize (size, GVir.StorageVolResizeFlags.NONE);
+        }
+    }
+
+    private void on_storage_changed (Boxes.Property property, uint64 value) {
+        // Ensure that we don't end-up changing storage like a 1000 times a second while user moves the 
slider..
+        property.deferred_change = () => {
+            change_storage_size.begin (property, value);
+
+            return false;
+        };
+    }
+
+    public async List<GVir.DomainSnapshot>? get_snapshots (GLib.Cancellable? cancellable) throws GLib.Error {
+        yield machine.domain.fetch_snapshots_async (GVir.DomainSnapshotListFlags.ALL, cancellable);
+
+        return machine.domain.get_snapshots ();
+    }
+}
diff --git a/src/preferences/snapshots-page.vala b/src/preferences/snapshots-page.vala
new file mode 100644
index 00000000..9dd24225
--- /dev/null
+++ b/src/preferences/snapshots-page.vala
@@ -0,0 +1,153 @@
+// This file is part of GNOME Boxes. License: LGPLv2+
+using Gtk;
+
+[GtkTemplate (ui = "/org/gnome/Boxes/ui/preferences/snapshots-page.ui")]
+private class Boxes.PreferencesSnapshotsPage: Boxes.PreferencesPage {
+    private LibvirtMachine machine;
+
+    [GtkChild]
+    private Gtk.Stack stack;
+    [GtkChild]
+    private Gtk.ListBox list_page;
+    [GtkChild]
+    private Hdy.StatusPage empty_page;
+    [GtkChild]
+    private Gtk.Box activity_page;
+
+    private string? activity {
+        set {
+            if (value == null) {
+                stack.visible_child = list_page;
+            } else {
+                stack.visible_child = activity_page;
+            }
+        }
+    }
+
+    public void setup (LibvirtMachine machine) {
+        this.machine = machine;
+
+        list_page.set_sort_func (config_sort_func);
+
+        fetch_snapshots.begin ();
+    }
+
+    private async void fetch_snapshots () {
+        try {
+            var snapshots = yield machine.properties.get_snapshots (null);
+
+            foreach (var snapshot in snapshots) {
+                list_page.add (create_snapshot_row (snapshot));
+            }
+        } catch (GLib.Error e) {
+            warning ("Could not fetch snapshots: %s", e.message);
+        }
+
+        update_stack ();
+    }
+
+    private SnapshotListRow create_snapshot_row (GVir.DomainSnapshot snapshot) {
+        var row = new SnapshotListRow (snapshot, machine);
+        row.notify["activity-message"].connect (row_activity_changed);
+        row.removed.connect (() => {
+            row.visible = false;
+
+            string snapshot_identifier = row.snapshot.get_name ();
+            try {
+                var config = row.snapshot.get_config (0);
+                snapshot_identifier = config.get_description ();
+            } catch (GLib.Error e) {
+                warning ("Could not get configuration of snapshot %s: %s",
+                          row.snapshot.get_name (),
+                          e.message);
+            }
+            var message = _("Snapshot “%s” deleted.").printf (snapshot_identifier);
+
+            Notification.OKFunc undo = () => {
+                row.visible = true;
+
+                active_notification = null;
+            };
+
+            Notification.DismissFunc really_remove = () => {
+                row.really_remove ();
+            };
+
+            display_notification (message, _("Undo"), (owned) undo, (owned) really_remove);
+        });
+
+        return row;
+    }
+
+    [GtkCallback]
+    private async void create_snapshot () {
+        if (machine.state == Machine.MachineState.RUNNING)
+            this.activity = _("Creating new snapshot…");
+
+        try {
+            var new_snapshot = yield machine.create_snapshot ();
+            list_page.add (create_snapshot_row (new_snapshot));
+        } catch (GLib.Error e) {
+            var msg = _("Failed to create snapshot of %s").printf (machine.name);
+            machine.window.notificationbar.display_error (msg);
+            warning (e.message);
+        }
+        this.activity = null;
+    }
+
+    private void remove_snapshot (SnapshotListRow row) {
+        row.visible = false;
+
+        string snapshot_identifier = row.snapshot.get_name ();
+        try {
+            var config = row.snapshot.get_config (0);
+            snapshot_identifier = config.get_description ();
+        } catch (GLib.Error e) {
+            warning ("Could not get configuration of snapshot %s: %s",
+                      row.snapshot.get_name (),
+                      e.message);
+        }
+        var message = _("Snapshot “%s” deleted.").printf (snapshot_identifier);
+
+        Notification.OKFunc undo = () => {
+            row.visible = true;
+        };
+
+        Notification.DismissFunc really_remove = () => {
+            row.really_remove ();
+        };
+
+        display_notification (message, _("Undo"), (owned) undo, (owned) really_remove);
+    }
+
+    private void row_activity_changed (GLib.Object source, GLib.ParamSpec param_spec) {
+        var row = source as SnapshotListRow;
+        this.activity = row.activity_message;
+    }
+
+    [GtkCallback]
+    private void update_stack () {
+        var num_snapshots = list_page.get_children ().length ();
+        if (num_snapshots > 0)
+            stack.visible_child = list_page;
+        else
+            stack.visible_child = empty_page;
+    }
+
+    private int config_sort_func (Gtk.ListBoxRow row1, Gtk.ListBoxRow row2) {
+        try {
+            var snapshot_row1 = row1 as SnapshotListRow;
+            var snapshot_row2 = row2 as SnapshotListRow;
+
+            var conf1  = snapshot_row1.snapshot.get_config (0);
+            var conf2  = snapshot_row2.snapshot.get_config (0);
+            if (conf1.get_creation_time () < conf2.get_creation_time ())
+                return -1;
+            else
+                return 1;
+        } catch (GLib.Error e) {
+            warning ("Failed to fetch snapshot config: %s", e.message);
+            return 0;
+        }
+    }
+}
diff --git a/src/properties-window.vala b/src/properties-window.vala
index 1a16b148..ff9971ac 100644
--- a/src/properties-window.vala
+++ b/src/properties-window.vala
@@ -11,7 +11,7 @@
 public delegate void Boxes.FileChosenFunc (string path);
 
 [GtkTemplate (ui = "/org/gnome/Boxes/ui/properties-window.ui")]
-private class Boxes.PropertiesWindow: Gtk.Window, Boxes.UI {
+private class Boxes.PropertiesWindow: Hdy.PreferencesWindow, Boxes.UI {
     public const string[] page_names = { "main", "troubleshoot_log", "file_chooser", "config_editor" };
 
     public UIState previous_ui_state { get; protected set; }
@@ -24,7 +24,7 @@
             _page = value;
 
             view.visible_child_name = page_names[value];
-            topbar.page = value;
+            //topbar.page = value;
         }
     }
 
@@ -39,8 +39,6 @@
 
     public Gtk.FileChooserNative file_chooser;
     [GtkChild]
-    public PropertiesToolbar topbar;
-    [GtkChild]
     public Notificationbar notificationbar;
 
     private unowned AppWindow app_window;
@@ -49,7 +47,7 @@ public PropertiesWindow (AppWindow app_window) {
         this.app_window = app_window;
 
         properties.setup_ui (app_window, this);
-        topbar.setup_ui (app_window, this);
+        //topbar.setup_ui (app_window, this);
 
         set_transient_for (app_window);
 
@@ -70,9 +68,9 @@ public void show_troubleshoot_log (string log) {
 
     public void show_editor_view (LibvirtMachine machine) {
         page = PropsWindowPage.TEXT_EDITOR;
-        config_editor.setup (machine, topbar.apply_config_button);
+        //config_editor.setup (machine, topbar.apply_config_button);
 
-        topbar.config_editor.set_title (machine.name);
+        //topbar.config_editor.set_title (machine.name);
     }
 
     public void show_file_chooser (owned FileChosenFunc file_chosen_func) {
@@ -119,7 +117,7 @@ private bool on_key_pressed (Widget widget, Gdk.EventKey event) {
              (direction == Gtk.TextDirection.RTL && // RTL
               event.keyval == Gdk.Key.Right)) &&    // ALT + Right -> back
             (event.state & default_modifiers) == Gdk.ModifierType.MOD1_MASK) {
-            topbar.click_back_button ();
+            //topbar.click_back_button ();
 
             return true;
         } else if (event.keyval == Gdk.Key.Escape) { // ESC -> back
diff --git a/src/shared-folders.vala b/src/shared-folders.vala
index 0157025e..8c105bff 100644
--- a/src/shared-folders.vala
+++ b/src/shared-folders.vala
@@ -181,7 +181,7 @@ private static string get_shared_folder_real_path (SharedFolder folder) {
 }
 
 [GtkTemplate (ui = "/org/gnome/Boxes/ui/properties-shared-folder-row.ui")]
-private class Boxes.SharedFolderRow : Gtk.ListBoxRow {
+private class Boxes.SharedFolderRow : Hdy.PreferencesRow {
     public signal void removed (SharedFolder folder);
     [GtkChild]
     private Gtk.Label folder_path_label;
@@ -211,17 +211,17 @@ private void on_delete_button_clicked () {
 
     private GLib.ListStore list_model;
 
-    private Boxes.SharedFolderPopover popover;
+    private Boxes.SharedFolderPopover popover = new SharedFolderPopover ();
     [GtkChild]
     private Gtk.ListBox listbox;
 
-    public SharedFoldersWidget (string machine_uuid) {
+    public void setup (string machine_uuid) {
         this.machine_uuid = machine_uuid;
 
         list_model = manager.get_folders (machine_uuid);
         listbox.bind_model (list_model, create_shared_folder_row);
 
-        popover = new SharedFolderPopover ();
+        //popover = new SharedFolderPopover ();
         popover.saved.connect (on_popover_saved);
     }
 
diff --git a/src/snapshot-list-row.vala b/src/snapshot-list-row.vala
index 5a758661..adca6638 100644
--- a/src/snapshot-list-row.vala
+++ b/src/snapshot-list-row.vala
@@ -23,6 +23,8 @@
     private Boxes.LibvirtMachine machine;
     private unowned Gtk.Container? parent_container = null;
 
+    public signal void removed ();
+
     private const GLib.ActionEntry[] action_entries = {
         {"revert-to", revert_to_activated},
         {"rename",    rename_activated},
@@ -160,40 +162,17 @@ private void revert_to_activated (GLib.SimpleAction action, GLib.Variant? v) {
 
 
     private void delete_activated (GLib.SimpleAction action, GLib.Variant? v) {
-        string snapshot_identifier = snapshot.get_name ();
-        try {
-            var config = snapshot.get_config (0);
-            snapshot_identifier = config.get_description ();
-        } catch (GLib.Error e) {
-            warning ("Could not get configuration of snapshot %s: %s",
-                      snapshot.get_name (),
-                      e.message);
-        }
-        var message = _("Snapshot “%s” deleted.").printf (snapshot_identifier);
-        parent_container = (Gtk.Container) this.get_parent ();
-        var row = this;
-        parent_container.remove (this);
-
-        Notification.OKFunc undo = () => {
-            parent_container.add (this);
-            row = null;
-        };
-
-        Notification.DismissFunc really_remove = () => {
-            this.snapshot.delete_async.begin (0, null, (obj, res) =>{
-                try {
-                    this.snapshot.delete_async.end (res);
-                    parent_container.queue_draw ();
-                } catch (GLib.Error e) {
-                    warning ("Error while deleting snapshot %s: %s", snapshot.get_name (), e.message);
-                }
-            });
-            row = null;
-        };
-        machine.window.notificationbar.display_for_action (message,
-                                                           _("_Undo"),
-                                                           (owned) undo,
-                                                           (owned) really_remove);
+        removed ();
+    }
+
+    public void really_remove () {
+        snapshot.delete_async.begin (0, null, (obj, res) => {
+            try {
+               snapshot.delete_async.end (res);
+            } catch (GLib.Error e) {
+                warning ("Error while deleting snapshot %s: %s", snapshot.get_name (), e.message)    ;
+            }
+        });
     }
 
     private void rename_activated (GLib.SimpleAction action, GLib.Variant? v) {
diff --git a/src/spice-display.vala b/src/spice-display.vala
index 0d2feee7..39b5b1ed 100644
--- a/src/spice-display.vala
+++ b/src/spice-display.vala
@@ -414,9 +414,6 @@ private void on_new_file_transfer (Spice.MainChannel main_channel, Object transf
             if (webdav_channel == null || !webdav_channel.port_opened)
                 break;
 
-            var frame = create_shared_folders_frame ();
-            add_property (ref list, _("Folder Shares"), new Gtk.Label (""), frame);
-
             break;
         }
 
@@ -525,10 +522,6 @@ public override void send_keys (uint[] keyvals) {
         return frame;
     }
 
-    private Gtk.Frame create_shared_folders_frame () {
-        return new SharedFoldersWidget (machine.config.uuid);
-    }
-
     private bool is_usb_kbd_or_mouse (uint8 class, uint8 subclass, uint8 protocol) {
         var ret = false;
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]