diff --git a/Makefile b/Makefile
index 0453301..2aefdea 100644
--- a/Makefile
+++ b/Makefile
@@ -1,21 +1,26 @@
 BRANCH    ?= $(shell git rev-parse --abbrev-ref HEAD)
 BUILDDATE ?= $(shell date -u +%FT%T%z)
+BUILDTS   ?= $(shell date -u +%s)
 REVISION  ?= $(shell git rev-parse HEAD)
-VERSION_DEV := 0.3.8-dev$(shell date +%Y%m%d%H%M)
-VERSION := 0.3.7
+VERSION_DEV := 0.3.9-dev$(shell date +%Y%m%d%H%M)
+VERSION := 0.3.8
+
+PROMETHEUS_TAG := github.com/prometheus/common/version
+KVM_PKG_NAME := github.com/jetkvm/kvm
 
 GO_LDFLAGS := \
   -s -w \
-  -X github.com/prometheus/common/version.Branch=$(BRANCH) \
-  -X github.com/prometheus/common/version.BuildDate=$(BUILDDATE) \
-  -X github.com/prometheus/common/version.Revision=$(REVISION)
+  -X $(PROMETHEUS_TAG).Branch=$(BRANCH) \
+  -X $(PROMETHEUS_TAG).BuildDate=$(BUILDDATE) \
+  -X $(PROMETHEUS_TAG).Revision=$(REVISION) \
+  -X $(KVM_PKG_NAME).builtTimestamp=$(BUILDTS)
 
 hash_resource:
 	@shasum -a 256 resource/jetkvm_native | cut -d ' ' -f 1 > resource/jetkvm_native.sha256
 
 build_dev: hash_resource
 	@echo "Building..."
-	GOOS=linux GOARCH=arm GOARM=7 go build -ldflags="$(GO_LDFLAGS) -X kvm.builtAppVersion=$(VERSION_DEV)" -o bin/jetkvm_app cmd/main.go
+	GOOS=linux GOARCH=arm GOARM=7 go build -ldflags="$(GO_LDFLAGS) -X $(KVM_PKG_NAME).builtAppVersion=$(VERSION_DEV)" -o bin/jetkvm_app cmd/main.go
 
 frontend:
 	cd ui && npm ci && npm run build:device
@@ -28,7 +33,7 @@ dev_release: frontend build_dev
 
 build_release: frontend hash_resource
 	@echo "Building release..."
-	GOOS=linux GOARCH=arm GOARM=7 go build -ldflags="$(GO_LDFLAGS) -X kvm.builtAppVersion=$(VERSION)" -o bin/jetkvm_app cmd/main.go
+	GOOS=linux GOARCH=arm GOARM=7 go build -ldflags="$(GO_LDFLAGS) -X $(KVM_PKG_NAME).builtAppVersion=$(VERSION)" -o bin/jetkvm_app cmd/main.go
 
 release:
 	@if rclone lsf r2://jetkvm-update/app/$(VERSION)/ | grep -q "jetkvm_app"; then \
diff --git a/internal/usbgadget/config.go b/internal/usbgadget/config.go
index 5f08733..5cc3ed2 100644
--- a/internal/usbgadget/config.go
+++ b/internal/usbgadget/config.go
@@ -128,6 +128,15 @@ func (u *UsbGadget) GetConfigPath(itemKey string) (string, error) {
 	return joinPath(u.kvmGadgetPath, item.configPath), nil
 }
 
+// GetPath returns the path to the item.
+func (u *UsbGadget) GetPath(itemKey string) (string, error) {
+	item, ok := u.configMap[itemKey]
+	if !ok {
+		return "", fmt.Errorf("config item %s not found", itemKey)
+	}
+	return joinPath(u.kvmGadgetPath, item.path), nil
+}
+
 func mountConfigFS() error {
 	_, err := os.Stat(gadgetPath)
 	// TODO: check if it's mounted properly
diff --git a/jsonrpc.go b/jsonrpc.go
index 298a810..64935e1 100644
--- a/jsonrpc.go
+++ b/jsonrpc.go
@@ -799,6 +799,7 @@ var rpcHandlers = map[string]RPCHandler{
 	"getCloudState":          {Func: rpcGetCloudState},
 	"keyboardReport":         {Func: rpcKeyboardReport, Params: []string{"modifier", "keys"}},
 	"absMouseReport":         {Func: rpcAbsMouseReport, Params: []string{"x", "y", "buttons"}},
+	"relMouseReport":         {Func: rpcRelMouseReport, Params: []string{"dx", "dy", "buttons"}},
 	"wheelReport":            {Func: rpcWheelReport, Params: []string{"wheelY"}},
 	"getVideoState":          {Func: rpcGetVideoState},
 	"getUSBState":            {Func: rpcGetUSBState},
diff --git a/main.go b/main.go
index 0673389..6a55595 100644
--- a/main.go
+++ b/main.go
@@ -35,6 +35,8 @@ func Main() {
 	StartNativeCtrlSocketServer()
 	StartNativeVideoSocketServer()
 
+	initPrometheus()
+
 	go func() {
 		err = ExtractAndRunNativeBin()
 		if err != nil {
@@ -67,6 +69,9 @@ func Main() {
 	}()
 	//go RunFuseServer()
 	go RunWebServer()
+	if config.TLSMode != "" {
+		go RunWebSecureServer()
+	}
 	// If the cloud token isn't set, the client won't be started by default.
 	// However, if the user adopts the device via the web interface, handleCloudRegister will start the client.
 	if config.CloudToken != "" {
diff --git a/prometheus.go b/prometheus.go
new file mode 100644
index 0000000..8ebf259
--- /dev/null
+++ b/prometheus.go
@@ -0,0 +1,17 @@
+package kvm
+
+import (
+	"net/http"
+
+	"github.com/prometheus/client_golang/prometheus"
+	versioncollector "github.com/prometheus/client_golang/prometheus/collectors/version"
+	"github.com/prometheus/common/version"
+)
+
+var promHandler http.Handler
+
+func initPrometheus() {
+	// A Prometheus metrics endpoint.
+	version.Version = builtAppVersion
+	prometheus.MustRegister(versioncollector.NewCollector("jetkvm"))
+}
diff --git a/ui/src/components/InfoBar.tsx b/ui/src/components/InfoBar.tsx
index be94043..7c002f1 100644
--- a/ui/src/components/InfoBar.tsx
+++ b/ui/src/components/InfoBar.tsx
@@ -14,6 +14,7 @@ export default function InfoBar() {
   const activeModifiers = useHidStore(state => state.activeModifiers);
   const mouseX = useMouseStore(state => state.mouseX);
   const mouseY = useMouseStore(state => state.mouseY);
+  const mouseMove = useMouseStore(state => state.mouseMove);
 
   const videoClientSize = useVideoStore(
     state => `${Math.round(state.clientWidth)}x${Math.round(state.clientHeight)}`,
@@ -62,7 +63,7 @@ export default function InfoBar() {
               </div>
             ) : null}
 
-            {settings.debugMode ? (
+            {(settings.debugMode && settings.mouseMode == "absolute") ? (
               <div className="flex w-[118px] items-center gap-x-1">
                 <span className="text-xs font-semibold">Pointer:</span>
                 <span className="text-xs">
@@ -71,6 +72,17 @@ export default function InfoBar() {
               </div>
             ) : null}
 
+            {(settings.debugMode && settings.mouseMode == "relative") ? (
+              <div className="flex w-[118px] items-center gap-x-1">
+                <span className="text-xs font-semibold">Last Move:</span>
+                <span className="text-xs">
+                  {mouseMove ?
+                    `${mouseMove.x},${mouseMove.y} ${mouseMove.buttons ? `(${mouseMove.buttons})` : ""}` :
+                    "N/A"}
+                </span>
+              </div>
+            ) : null}
+
             {settings.debugMode && (
               <div className="flex w-[156px] items-center gap-x-1">
                 <span className="text-xs font-semibold">USB State:</span>
diff --git a/ui/src/components/USBConfigDialog.tsx b/ui/src/components/USBConfigDialog.tsx
deleted file mode 100644
index db8b677..0000000
--- a/ui/src/components/USBConfigDialog.tsx
+++ /dev/null
@@ -1,117 +0,0 @@
-import { Button } from "@components/Button";
-import { InputFieldWithLabel } from "./InputField";
-import { UsbConfigState } from "@/hooks/stores";
-import { useEffect, useCallback, useState } from "react";
-import { useJsonRpc } from "../hooks/useJsonRpc";
-import { USBConfig } from "./UsbConfigSetting";
-
-export default function UpdateUsbConfigModal({
-  onSetUsbConfig,
-  onRestoreToDefault,
-}: {
-  onSetUsbConfig: (usbConfig: USBConfig) => void;
-  onRestoreToDefault: () => void;
-}) {
-  const [usbConfigState, setUsbConfigState] = useState<USBConfig>({
-    vendor_id: "",
-    product_id: "",
-    serial_number: "",
-    manufacturer: "",
-    product: "",
-  });
-
-  const [send] = useJsonRpc();
-
-  const syncUsbConfig = useCallback(() => {
-    send("getUsbConfig", {}, resp => {
-      if ("error" in resp) {
-        console.error("Failed to load USB Config:", resp.error);
-      } else {
-        setUsbConfigState(resp.result as UsbConfigState);
-      }
-    });
-  }, [send, setUsbConfigState]);
-
-  // Load stored usb config from the backend
-  useEffect(() => {
-    syncUsbConfig();
-  }, [syncUsbConfig]);
-
-  const handleUsbVendorIdChange = (value: string) => {
-    setUsbConfigState({ ...usbConfigState, vendor_id: value });
-  };
-
-  const handleUsbProductIdChange = (value: string) => {
-    setUsbConfigState({ ...usbConfigState, product_id: value });
-  };
-
-  const handleUsbSerialChange = (value: string) => {
-    setUsbConfigState({ ...usbConfigState, serial_number: value });
-  };
-
-  const handleUsbManufacturer = (value: string) => {
-    setUsbConfigState({ ...usbConfigState, manufacturer: value });
-  };
-
-  const handleUsbProduct = (value: string) => {
-    setUsbConfigState({ ...usbConfigState, product: value });
-  };
-
-  return (
-    <div className="space-y-6">
-      <div className="grid grid-cols-2 gap-4">
-        <InputFieldWithLabel
-          required
-          label="Vendor ID"
-          placeholder="Enter Vendor ID"
-          pattern="^0[xX][\da-fA-F]{4}$"
-          defaultValue={usbConfigState?.vendor_id}
-          onChange={e => handleUsbVendorIdChange(e.target.value)}
-        />
-        <InputFieldWithLabel
-          required
-          label="Product ID"
-          placeholder="Enter Product ID"
-          pattern="^0[xX][\da-fA-F]{4}$"
-          defaultValue={usbConfigState?.product_id}
-          onChange={e => handleUsbProductIdChange(e.target.value)}
-        />
-        <InputFieldWithLabel
-          required
-          label="Serial Number"
-          placeholder="Enter Serial Number"
-          defaultValue={usbConfigState?.serial_number}
-          onChange={e => handleUsbSerialChange(e.target.value)}
-        />
-        <InputFieldWithLabel
-          required
-          label="Manufacturer"
-          placeholder="Enter Manufacturer"
-          defaultValue={usbConfigState?.manufacturer}
-          onChange={e => handleUsbManufacturer(e.target.value)}
-        />
-        <InputFieldWithLabel
-          required
-          label="Product Name"
-          placeholder="Enter Product Name"
-          defaultValue={usbConfigState?.product}
-          onChange={e => handleUsbProduct(e.target.value)}
-        />
-      </div>
-      <div className="flex gap-x-2">
-        <Button
-          size="SM"
-          theme="primary"
-          text="Update USB Config"
-          onClick={() => onSetUsbConfig(usbConfigState)}
-        />
-        <Button
-          size="SM"
-          theme="light"
-          text="Restore to Default"
-          onClick={onRestoreToDefault}
-        />
-      </div>
-    </div>
-  );
-}
diff --git a/ui/src/components/UsbDeviceSetting.tsx b/ui/src/components/UsbDeviceSetting.tsx
index 1c8812c..07125e6 100644
--- a/ui/src/components/UsbDeviceSetting.tsx
+++ b/ui/src/components/UsbDeviceSetting.tsx
@@ -5,7 +5,10 @@ import { useJsonRpc } from "../hooks/useJsonRpc";
 import notifications from "../notifications";
 import { SettingsItem } from "../routes/devices.$id.settings";
 import Checkbox from "./Checkbox";
-
+import { Button } from "./Button";
+import { SelectMenuBasic } from "./SelectMenuBasic";
+import { SettingsSectionHeader } from "./SettingsSectionHeader";
+import Fieldset from "./Fieldset";
 export interface USBConfig {
   vendor_id: string;
   product_id: string;
@@ -26,12 +29,43 @@ const defaultUsbDeviceConfig: UsbDeviceConfig = {
   absolute_mouse: true,
   relative_mouse: true,
   mass_storage: true,
-}
+};
+
+const usbPresets = [
+  {
+    label: "Keyboard, Mouse and Mass Storage",
+    value: "default",
+    config: {
+      keyboard: true,
+      absolute_mouse: true,
+      relative_mouse: true,
+      mass_storage: true,
+    },
+  },
+  {
+    label: "Keyboard Only",
+    value: "keyboard_only",
+    config: {
+      keyboard: true,
+      absolute_mouse: false,
+      relative_mouse: false,
+      mass_storage: false,
+    },
+  },
+  {
+    label: "Custom",
+    value: "custom",
+  },
+];
 
 export function UsbDeviceSetting() {
   const [send] = useJsonRpc();
+  const [loading, setLoading] = useState(false);
+
+  const [usbDeviceConfig, setUsbDeviceConfig] =
+    useState<UsbDeviceConfig>(defaultUsbDeviceConfig);
+  const [selectedPreset, setSelectedPreset] = useState<string>("default");
 
-  const [usbDeviceConfig, setUsbDeviceConfig] = useState<UsbDeviceConfig>(defaultUsbDeviceConfig);
   const syncUsbDeviceConfig = useCallback(() => {
     send("getUsbDevices", {}, resp => {
       if ("error" in resp) {
@@ -40,90 +74,167 @@ export function UsbDeviceSetting() {
           `Failed to load USB devices: ${resp.error.data || "Unknown error"}`,
         );
       } else {
-        console.log("syncUsbDeviceConfig#getUsbDevices result:", resp.result);
         const usbConfigState = resp.result as UsbDeviceConfig;
         setUsbDeviceConfig(usbConfigState);
+
+        // Set the appropriate preset based on current config
+        const matchingPreset = usbPresets.find(
+          preset =>
+            preset.value !== "custom" &&
+            preset.config &&
+            Object.keys(preset.config).length === Object.keys(usbConfigState).length &&
+            Object.keys(preset.config).every(key => {
+              const configKey = key as keyof typeof preset.config;
+              return preset.config[configKey] === usbConfigState[configKey];
+            }),
+        );
+
+        setSelectedPreset(matchingPreset ? matchingPreset.value : "custom");
       }
     });
   }, [send]);
 
   const handleUsbConfigChange = useCallback(
     (devices: UsbDeviceConfig) => {
-      send("setUsbDevices", { devices }, resp => {
+      setLoading(true);
+      send("setUsbDevices", { devices }, async resp => {
         if ("error" in resp) {
           notifications.error(
             `Failed to set usb devices: ${resp.error.data || "Unknown error"}`,
           );
+          setLoading(false);
           return;
         }
-        notifications.success(
-          `USB Devices updated`
-        );
+
+        // We need some time to ensure the USB devices are updated
+        await new Promise(resolve => setTimeout(resolve, 2000));
+        setLoading(false);
         syncUsbDeviceConfig();
+        notifications.success(`USB Devices updated`);
       });
     },
     [send, syncUsbDeviceConfig],
   );
 
-  const onUsbConfigItemChange = useCallback((key: keyof UsbDeviceConfig) => (e: React.ChangeEvent<HTMLInputElement>) => {
-    setUsbDeviceConfig((val) => {
-      val[key] = e.target.checked;
-      handleUsbConfigChange(val);
-      return val;
-    });
-  }, [handleUsbConfigChange]);
+  const onUsbConfigItemChange = useCallback(
+    (key: keyof UsbDeviceConfig) => (e: React.ChangeEvent<HTMLInputElement>) => {
+      setUsbDeviceConfig(prev => ({
+        ...prev,
+        [key]: e.target.checked,
+      }));
+    },
+    [],
+  );
+
+  const handlePresetChange = useCallback(
+    async (e: React.ChangeEvent<HTMLSelectElement>) => {
+      const newPreset = e.target.value;
+      setSelectedPreset(newPreset);
+
+      if (newPreset !== "custom") {
+        const presetConfig = usbPresets.find(
+          preset => preset.value === newPreset,
+        )?.config;
+
+        if (presetConfig) {
+          handleUsbConfigChange(presetConfig);
+        }
+      }
+    },
+    [handleUsbConfigChange],
+  );
 
   useEffect(() => {
     syncUsbDeviceConfig();
   }, [syncUsbDeviceConfig]);
 
   return (
-    <>
+    <Fieldset disabled={loading} className="space-y-4">
       <div className="h-[1px] w-full bg-slate-800/10 dark:bg-slate-300/20" />
-      <div className="space-y-4">
-        <SettingsItem
-          title="Enable Keyboard"
-          description="Enable Keyboard"
-        >
-          <Checkbox
-            checked={usbDeviceConfig.keyboard}
-            onChange={onUsbConfigItemChange("keyboard")}
-          />
-        </SettingsItem>
-      </div>
-      <div className="space-y-4">
-        <SettingsItem
-          title="Enable Absolute Mouse (Pointer)"
-          description="Enable Absolute Mouse (Pointer)"
-        >
-          <Checkbox
-            checked={usbDeviceConfig.absolute_mouse}
-            onChange={onUsbConfigItemChange("absolute_mouse")}
-          />
-        </SettingsItem>
-      </div>
-      <div className="space-y-4">
-        <SettingsItem
-          title="Enable Relative Mouse"
-          description="Enable Relative Mouse"
-        >
-          <Checkbox
-            checked={usbDeviceConfig.relative_mouse}
-            onChange={onUsbConfigItemChange("relative_mouse")}
-          />
-        </SettingsItem>
-      </div>
-      <div className="space-y-4">
-        <SettingsItem
-          title="Enable USB Mass Storage"
-          description="Sometimes it might need to be disabled to prevent issues with certain devices"
-        >
-          <Checkbox
-            checked={usbDeviceConfig.mass_storage}
-            onChange={onUsbConfigItemChange("mass_storage")}
-          />
-        </SettingsItem>
-      </div>
-    </>
+
+      <SettingsSectionHeader
+        title="USB Device"
+        description="USB devices to emulate on the target computer"
+      />
+
+      <SettingsItem
+        loading={loading}
+        title="Classes"
+        description="USB device classes in the composite device"
+      >
+        <SelectMenuBasic
+          size="SM"
+          label=""
+          className="max-w-[292px]"
+          value={selectedPreset}
+          fullWidth
+          onChange={handlePresetChange}
+          options={usbPresets}
+        />
+      </SettingsItem>
+
+      {selectedPreset === "custom" && (
+        <div className="ml-2 border-l border-slate-800/10 pl-4 dark:border-slate-300/20 ">
+          <div className="space-y-4">
+            <div className="space-y-4">
+              <SettingsItem title="Enable Keyboard" description="Enable Keyboard">
+                <Checkbox
+                  checked={usbDeviceConfig.keyboard}
+                  onChange={onUsbConfigItemChange("keyboard")}
+                />
+              </SettingsItem>
+            </div>
+            <div className="space-y-4">
+              <SettingsItem
+                title="Enable Absolute Mouse (Pointer)"
+                description="Enable Absolute Mouse (Pointer)"
+              >
+                <Checkbox
+                  checked={usbDeviceConfig.absolute_mouse}
+                  onChange={onUsbConfigItemChange("absolute_mouse")}
+                />
+              </SettingsItem>
+            </div>
+            <div className="space-y-4">
+              <SettingsItem
+                title="Enable Relative Mouse"
+                description="Enable Relative Mouse"
+              >
+                <Checkbox
+                  checked={usbDeviceConfig.relative_mouse}
+                  onChange={onUsbConfigItemChange("relative_mouse")}
+                />
+              </SettingsItem>
+            </div>
+            <div className="space-y-4">
+              <SettingsItem
+                title="Enable USB Mass Storage"
+                description="Sometimes it might need to be disabled to prevent issues with certain devices"
+              >
+                <Checkbox
+                  checked={usbDeviceConfig.mass_storage}
+                  onChange={onUsbConfigItemChange("mass_storage")}
+                />
+              </SettingsItem>
+            </div>
+          </div>
+          <div className="mt-6 flex gap-x-2">
+            <Button
+              size="SM"
+              loading={loading}
+              theme="primary"
+              text="Update USB Classes"
+              onClick={() => handleUsbConfigChange(usbDeviceConfig)}
+            />
+            <Button
+              size="SM"
+              theme="light"
+              text="Restore to Default"
+              onClick={() => handleUsbConfigChange(defaultUsbDeviceConfig)}
+            />
+          </div>
+        </div>
+      )}
+    </Fieldset>
   );
 }
diff --git a/ui/src/components/UsbConfigSetting.tsx b/ui/src/components/UsbInfoSetting.tsx
similarity index 51%
rename from ui/src/components/UsbConfigSetting.tsx
rename to ui/src/components/UsbInfoSetting.tsx
index 1e8ab03..4ac93ff 100644
--- a/ui/src/components/UsbConfigSetting.tsx
+++ b/ui/src/components/UsbInfoSetting.tsx
@@ -1,6 +1,8 @@
 import { useMemo } from "react";
 
 import { useCallback } from "react";
+import { Button } from "@components/Button";
+import { InputFieldWithLabel } from "./InputField";
 
 import { useEffect, useState } from "react";
 import { UsbConfigState } from "../hooks/stores";
@@ -8,7 +10,7 @@ import { useJsonRpc } from "../hooks/useJsonRpc";
 import notifications from "../notifications";
 import { SettingsItem } from "../routes/devices.$id.settings";
 import { SelectMenuBasic } from "./SelectMenuBasic";
-import USBConfigDialog from "./USBConfigDialog";
+import Fieldset from "./Fieldset";
 
 const generatedSerialNumber = [generateNumber(1, 9), generateHex(7, 7), 0, 1].join("&");
 
@@ -51,8 +53,9 @@ const usbConfigs = [
 
 type UsbConfigMap = Record<string, USBConfig>;
 
-export function UsbConfigSetting() {
+export function UsbInfoSetting() {
   const [send] = useJsonRpc();
+  const [loading, setLoading] = useState(false);
 
   const [usbConfigProduct, setUsbConfigProduct] = useState("");
   const [deviceId, setDeviceId] = useState("");
@@ -110,17 +113,23 @@ export function UsbConfigSetting() {
 
   const handleUsbConfigChange = useCallback(
     (usbConfig: USBConfig) => {
-      send("setUsbConfig", { usbConfig }, resp => {
+      setLoading(true);
+      send("setUsbConfig", { usbConfig }, async resp => {
         if ("error" in resp) {
           notifications.error(
             `Failed to set usb config: ${resp.error.data || "Unknown error"}`,
           );
+          setLoading(false);
           return;
         }
-        // setUsbConfigProduct(usbConfig.product);
+
+        // We need some time to ensure the USB devices are updated
+        await new Promise(resolve => setTimeout(resolve, 2000));
+        setLoading(false);
         notifications.success(
           `USB Config set to ${usbConfig.manufacturer} ${usbConfig.product}`,
         );
+
         syncUsbConfigProduct();
       });
     },
@@ -141,18 +150,18 @@ export function UsbConfigSetting() {
   }, [send, syncUsbConfigProduct]);
 
   return (
-    <>
-      <div className="h-[1px] w-full bg-slate-800/10 dark:bg-slate-300/20" />
-
+    <Fieldset disabled={loading} className="space-y-4">
       <SettingsItem
-        title="USB Device Emulation"
-        description="Set a Preconfigured USB Device"
+        loading={loading}
+        title="Identifiers"
+        description="USB device identifiers exposed to the target computer"
       >
         <SelectMenuBasic
           size="SM"
           label=""
           className="max-w-[192px]"
           value={usbConfigProduct}
+          fullWidth
           onChange={e => {
             if (e.target.value === "custom") {
               setUsbConfigProduct(e.target.value);
@@ -165,13 +174,130 @@ export function UsbConfigSetting() {
         />
       </SettingsItem>
       {usbConfigProduct === "custom" && (
-        <USBConfigDialog
-          onSetUsbConfig={usbConfig => handleUsbConfigChange(usbConfig)}
-          onRestoreToDefault={() =>
-            handleUsbConfigChange(usbConfigData[usbConfigs[0].value])
-          }
-        />
+        <div className="ml-2 space-y-4 border-l border-slate-800/10 pl-4 dark:border-slate-300/20 ">
+          <USBConfigDialog
+            loading={loading}
+            onSetUsbConfig={usbConfig => handleUsbConfigChange(usbConfig)}
+            onRestoreToDefault={() =>
+              handleUsbConfigChange(usbConfigData[usbConfigs[0].value])
+            }
+          />
+        </div>
       )}
-    </>
+    </Fieldset>
+  );
+}
+
+function USBConfigDialog({
+  loading,
+  onSetUsbConfig,
+  onRestoreToDefault,
+}: {
+  loading: boolean;
+  onSetUsbConfig: (usbConfig: USBConfig) => void;
+  onRestoreToDefault: () => void;
+}) {
+  const [usbConfigState, setUsbConfigState] = useState<USBConfig>({
+    vendor_id: "",
+    product_id: "",
+    serial_number: "",
+    manufacturer: "",
+    product: "",
+  });
+
+  const [send] = useJsonRpc();
+
+  const syncUsbConfig = useCallback(() => {
+    send("getUsbConfig", {}, resp => {
+      if ("error" in resp) {
+        console.error("Failed to load USB Config:", resp.error);
+      } else {
+        setUsbConfigState(resp.result as UsbConfigState);
+      }
+    });
+  }, [send, setUsbConfigState]);
+
+  // Load stored usb config from the backend
+  useEffect(() => {
+    syncUsbConfig();
+  }, [syncUsbConfig]);
+
+  const handleUsbVendorIdChange = (value: string) => {
+    setUsbConfigState({ ...usbConfigState, vendor_id: value });
+  };
+
+  const handleUsbProductIdChange = (value: string) => {
+    setUsbConfigState({ ...usbConfigState, product_id: value });
+  };
+
+  const handleUsbSerialChange = (value: string) => {
+    setUsbConfigState({ ...usbConfigState, serial_number: value });
+  };
+
+  const handleUsbManufacturer = (value: string) => {
+    setUsbConfigState({ ...usbConfigState, manufacturer: value });
+  };
+
+  const handleUsbProduct = (value: string) => {
+    setUsbConfigState({ ...usbConfigState, product: value });
+  };
+
+  return (
+    <div className="">
+      <div className="grid grid-cols-2 gap-4">
+        <InputFieldWithLabel
+          required
+          label="Vendor ID"
+          placeholder="Enter Vendor ID"
+          pattern="^0[xX][\da-fA-F]{4}$"
+          defaultValue={usbConfigState?.vendor_id}
+          onChange={e => handleUsbVendorIdChange(e.target.value)}
+        />
+        <InputFieldWithLabel
+          required
+          label="Product ID"
+          placeholder="Enter Product ID"
+          pattern="^0[xX][\da-fA-F]{4}$"
+          defaultValue={usbConfigState?.product_id}
+          onChange={e => handleUsbProductIdChange(e.target.value)}
+        />
+        <InputFieldWithLabel
+          required
+          label="Serial Number"
+          placeholder="Enter Serial Number"
+          defaultValue={usbConfigState?.serial_number}
+          onChange={e => handleUsbSerialChange(e.target.value)}
+        />
+        <InputFieldWithLabel
+          required
+          label="Manufacturer"
+          placeholder="Enter Manufacturer"
+          defaultValue={usbConfigState?.manufacturer}
+          onChange={e => handleUsbManufacturer(e.target.value)}
+        />
+        <InputFieldWithLabel
+          required
+          label="Product Name"
+          placeholder="Enter Product Name"
+          defaultValue={usbConfigState?.product}
+          onChange={e => handleUsbProduct(e.target.value)}
+        />
+      </div>
+      <div className="mt-6 flex gap-x-2">
+        <Button
+          loading={loading}
+          size="SM"
+          theme="primary"
+          text="Update USB Identifiers"
+          onClick={() => onSetUsbConfig(usbConfigState)}
+        />
+        <Button
+          size="SM"
+          theme="light"
+          text="Restore to Default"
+          onClick={onRestoreToDefault}
+        />
+      </div>
+    </div>
   );
 }
diff --git a/ui/src/components/WebRTCVideo.tsx b/ui/src/components/WebRTCVideo.tsx
index 1587d29..de36e37 100644
--- a/ui/src/components/WebRTCVideo.tsx
+++ b/ui/src/components/WebRTCVideo.tsx
@@ -29,6 +29,7 @@ export default function WebRTCVideo() {
   const settings = useSettingsStore();
   const { sendKeyboardEvent, resetKeyboardState } = useKeyboard();
   const setMousePosition = useMouseStore(state => state.setMousePosition);
+  const setMouseMove = useMouseStore(state => state.setMouseMove);
   const {
     setClientSize: setVideoClientSize,
     setSize: setVideoSize,
@@ -93,19 +94,44 @@ export default function WebRTCVideo() {
   );
 
   // Mouse-related
-  const sendMouseMovement = useCallback(
+  const calcDelta = (pos: number) => (Math.abs(pos) < 10 ? pos * 2 : pos);
+  const sendRelMouseMovement = useCallback(
     (x: number, y: number, buttons: number) => {
-      send("absMouseReport", { x, y, buttons });
+      if (settings.mouseMode !== "relative") return;
+      // if we ignore the event, double-click will not work
+      // if (x === 0 && y === 0 && buttons === 0) return;
+      send("relMouseReport", { dx: calcDelta(x), dy: calcDelta(y), buttons });
+      setMouseMove({ x, y, buttons });
+    },
+    [send, setMouseMove, settings.mouseMode],
+  );
 
+  const relMouseMoveHandler = useCallback(
+    (e: MouseEvent) => {
+      if (settings.mouseMode !== "relative") return;
+
+      // Send mouse movement
+      const { buttons } = e;
+      sendRelMouseMovement(e.movementX, e.movementY, buttons);
+    },
+    [sendRelMouseMovement, settings.mouseMode],
+  );
+
+  const sendAbsMouseMovement = useCallback(
+    (x: number, y: number, buttons: number) => {
+      if (settings.mouseMode !== "absolute") return;
+      send("absMouseReport", { x, y, buttons });
       // We set that for the debug info bar
       setMousePosition(x, y);
     },
-    [send, setMousePosition],
+    [send, setMousePosition, settings.mouseMode],
   );
 
-  const mouseMoveHandler = useCallback(
+  const absMouseMoveHandler = useCallback(
     (e: MouseEvent) => {
       if (!videoClientWidth || !videoClientHeight) return;
+      if (settings.mouseMode !== "absolute") return;
+
       // Get the aspect ratios of the video element and the video stream
       const videoElementAspectRatio = videoClientWidth / videoClientHeight;
       const videoStreamAspectRatio = videoWidth / videoHeight;
@@ -140,9 +166,16 @@ export default function WebRTCVideo() {
 
       // Send mouse movement
       const { buttons } = e;
-      sendMouseMovement(x, y, buttons);
+      sendAbsMouseMovement(x, y, buttons);
     },
-    [sendMouseMovement, videoClientHeight, videoClientWidth, videoWidth, videoHeight],
+    [
+      sendAbsMouseMovement,
+      videoClientHeight,
+      videoClientWidth,
+      videoWidth,
+      videoHeight,
+      settings.mouseMode,
+    ],
   );
 
   const trackpadSensitivity = useDeviceSettingsStore(state => state.trackpadSensitivity);
@@ -193,8 +226,8 @@ export default function WebRTCVideo() {
   );
 
   const resetMousePosition = useCallback(() => {
-    sendMouseMovement(0, 0, 0);
-  }, [sendMouseMovement]);
+    sendAbsMouseMovement(0, 0, 0);
+  }, [sendAbsMouseMovement]);
 
   // Keyboard-related
   const handleModifierKeys = useCallback(
@@ -329,28 +362,6 @@ export default function WebRTCVideo() {
     ],
   );
 
-  // Effect hooks
-  useEffect(
-    function setupKeyboardEvents() {
-      const abortController = new AbortController();
-      const signal = abortController.signal;
-
-      document.addEventListener("keydown", keyDownHandler, { signal });
-      document.addEventListener("keyup", keyUpHandler, { signal });
-
-      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
-      // @ts-expect-error
-      window.clearKeys = () => sendKeyboardEvent([], []);
-      window.addEventListener("blur", resetKeyboardState, { signal });
-      document.addEventListener("visibilitychange", resetKeyboardState, { signal });
-
-      return () => {
-        abortController.abort();
-      };
-    },
-    [keyDownHandler, keyUpHandler, resetKeyboardState, sendKeyboardEvent],
-  );
-
   const videoKeyUpHandler = useCallback((e: KeyboardEvent) => {
     // In fullscreen mode in chrome & safari, the space key is used to pause/play the video
     // there is no way to prevent this, so we need to simply force play the video when it's paused.
@@ -363,46 +374,6 @@ export default function WebRTCVideo() {
     }
   }, []);
 
-  useEffect(
-    function setupVideoEventListeners() {
-      let videoElmRefValue = null;
-      if (!videoElm.current) return;
-      videoElmRefValue = videoElm.current;
-      const abortController = new AbortController();
-      const signal = abortController.signal;
-
-      videoElmRefValue.addEventListener("mousemove", mouseMoveHandler, { signal });
-      videoElmRefValue.addEventListener("pointerdown", mouseMoveHandler, { signal });
-      videoElmRefValue.addEventListener("pointerup", mouseMoveHandler, { signal });
-      videoElmRefValue.addEventListener("keyup", videoKeyUpHandler, { signal });
-      videoElmRefValue.addEventListener("wheel", mouseWheelHandler, {
-        signal,
-        passive: true,
-      });
-      videoElmRefValue.addEventListener(
-        "contextmenu",
-        (e: MouseEvent) => e.preventDefault(),
-        { signal },
-      );
-      videoElmRefValue.addEventListener("playing", onVideoPlaying, { signal });
-
-      const local = resetMousePosition;
-      window.addEventListener("blur", local, { signal });
-      document.addEventListener("visibilitychange", local, { signal });
-
-      return () => {
-        if (videoElmRefValue) abortController.abort();
-      };
-    },
-    [
-      mouseMoveHandler,
-      resetMousePosition,
-      onVideoPlaying,
-      mouseWheelHandler,
-      videoKeyUpHandler,
-    ],
-  );
-
   useEffect(
     function updateVideoStream() {
       if (!mediaStream) return;
@@ -425,6 +396,120 @@ export default function WebRTCVideo() {
     ],
   );
 
+  // Setup Keyboard Events
+  useEffect(
+    function setupKeyboardEvents() {
+      const abortController = new AbortController();
+      const signal = abortController.signal;
+
+      document.addEventListener("keydown", keyDownHandler, { signal });
+      document.addEventListener("keyup", keyUpHandler, { signal });
+
+      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+      // @ts-expect-error
+      window.clearKeys = () => sendKeyboardEvent([], []);
+      window.addEventListener("blur", resetKeyboardState, { signal });
+      document.addEventListener("visibilitychange", resetKeyboardState, { signal });
+
+      return () => {
+        abortController.abort();
+      };
+    },
+    [keyDownHandler, keyUpHandler, resetKeyboardState, sendKeyboardEvent],
+  );
+
+  useEffect(
+    function setupVideoEventListeners() {
+      const videoElmRefValue = videoElm.current;
+      if (!videoElmRefValue) return;
+
+      const abortController = new AbortController();
+      const signal = abortController.signal;
+
+      // To prevent the video from being paused when the user presses a space in fullscreen mode
+      videoElmRefValue.addEventListener("keyup", videoKeyUpHandler, { signal });
+
+      // We need to know when the video is playing to update state and video size
+      videoElmRefValue.addEventListener("playing", onVideoPlaying, { signal });
+
+      return () => {
+        abortController.abort();
+      };
+    },
+    [
+      absMouseMoveHandler,
+      resetMousePosition,
+      onVideoPlaying,
+      mouseWheelHandler,
+      videoKeyUpHandler,
+    ],
+  );
+
+  // Setup Absolute Mouse Events
+  useEffect(
+    function setAbsoluteMouseModeEventListeners() {
+      const videoElmRefValue = videoElm.current;
+      if (!videoElmRefValue) return;
+
+      if (settings.mouseMode !== "absolute") return;
+
+      const abortController = new AbortController();
+      const signal = abortController.signal;
+
+      videoElmRefValue.addEventListener("mousemove", absMouseMoveHandler, { signal });
+      videoElmRefValue.addEventListener("pointerdown", absMouseMoveHandler, { signal });
+      videoElmRefValue.addEventListener("pointerup", absMouseMoveHandler, { signal });
+      videoElmRefValue.addEventListener("wheel", mouseWheelHandler, {
+        signal,
+        passive: true,
+      });
+
+      // Reset the mouse position when the window is blurred or the document is hidden
+      const local = resetMousePosition;
+      window.addEventListener("blur", local, { signal });
+      document.addEventListener("visibilitychange", local, { signal });
+
+      return () => {
+        abortController.abort();
+      };
+    },
+    [absMouseMoveHandler, mouseWheelHandler, resetMousePosition, settings.mouseMode],
+  );
+
+  // Setup Relative Mouse Events
+  const containerRef = useRef<HTMLDivElement>(null);
+  useEffect(
+    function setupRelativeMouseEventListeners() {
+      if (settings.mouseMode !== "relative") return;
+
+      const abortController = new AbortController();
+      const signal = abortController.signal;
+
+      // We bind to the larger container in relative mode because of delta between the acceleration of the local
+      // mouse and the mouse movement of the remote mouse. This simply makes it a bit less painful to use.
+      // When we get Pointer Lock support, we can remove this.
+      const containerElm = containerRef.current;
+      if (!containerElm) return;
+
+      containerElm.addEventListener("mousemove", relMouseMoveHandler, { signal });
+      containerElm.addEventListener("pointerdown", relMouseMoveHandler, { signal });
+      containerElm.addEventListener("pointerup", relMouseMoveHandler, { signal });
+
+      containerElm.addEventListener("wheel", mouseWheelHandler, {
+        signal,
+        passive: true,
+      });
+
+      const preventContextMenu = (e: MouseEvent) => e.preventDefault();
+      containerElm.addEventListener("contextmenu", preventContextMenu, { signal });
+
+      return () => {
+        abortController.abort();
+      };
+    },
+    [settings.mouseMode, relMouseMoveHandler, mouseWheelHandler],
+  );
+
   return (
     <div className="grid h-full w-full grid-rows-layout">
       <div className="min-h-[39.5px]">
@@ -439,7 +524,12 @@ export default function WebRTCVideo() {
         </fieldset>
       </div>
 
-      <div className="h-full overflow-hidden">
+      <div
+        ref={containerRef}
+        className={cx("h-full overflow-hidden", {
+          "cursor-none": settings.mouseMode === "relative" && settings.isCursorHidden,
+        })}
+      >
         <div className="relative h-full">
           <div
             className={cx(
@@ -468,7 +558,9 @@ export default function WebRTCVideo() {
                         className={cx(
                           "outline-50 max-h-full max-w-full object-contain transition-all duration-1000",
                           {
-                            "cursor-none": settings.isCursorHidden,
+                            "cursor-none":
+                              settings.mouseMode === "absolute" &&
+                              settings.isCursorHidden,
                             "opacity-0": isLoading || isConnectionError || hdmiError,
                             "animate-slideUpFade border border-slate-800/30 opacity-0 shadow dark:border-slate-300/20":
                               isPlaying,
diff --git a/ui/src/hooks/stores.ts b/ui/src/hooks/stores.ts
index ac8ad7d..f30c28c 100644
--- a/ui/src/hooks/stores.ts
+++ b/ui/src/hooks/stores.ts
@@ -197,15 +197,23 @@ export const useRTCStore = create<RTCState>(set => ({
   setTerminalChannel: channel => set({ terminalChannel: channel }),
 }));
 
+interface MouseMove {
+  x: number;
+  y: number;
+  buttons: number;
+}
 interface MouseState {
   mouseX: number;
   mouseY: number;
+  mouseMove?: MouseMove;
+  setMouseMove: (move?: MouseMove) => void;
   setMousePosition: (x: number, y: number) => void;
 }
 
 export const useMouseStore = create<MouseState>(set => ({
   mouseX: 0,
   mouseY: 0,
+  setMouseMove: (move?: MouseMove) => set({ mouseMove: move }),
   setMousePosition: (x, y) => set({ mouseX: x, mouseY: y }),
 }));
 
@@ -543,12 +551,12 @@ export interface UpdateState {
   setOtaState: (state: UpdateState["otaState"]) => void;
   setUpdateDialogHasBeenMinimized: (hasBeenMinimized: boolean) => void;
   modalView:
-    | "loading"
-    | "updating"
-    | "upToDate"
-    | "updateAvailable"
-    | "updateCompleted"
-    | "error";
+  | "loading"
+  | "updating"
+  | "upToDate"
+  | "updateAvailable"
+  | "updateCompleted"
+  | "error";
   setModalView: (view: UpdateState["modalView"]) => void;
   setUpdateErrorMessage: (errorMessage: string) => void;
   updateErrorMessage: string | null;
@@ -612,12 +620,12 @@ export const useUsbConfigModalStore = create<UsbConfigModalState>(set => ({
 
 interface LocalAuthModalState {
   modalView:
-    | "createPassword"
-    | "deletePassword"
-    | "updatePassword"
-    | "creationSuccess"
-    | "deleteSuccess"
-    | "updateSuccess";
+  | "createPassword"
+  | "deletePassword"
+  | "updatePassword"
+  | "creationSuccess"
+  | "deleteSuccess"
+  | "updateSuccess";
   setModalView: (view: LocalAuthModalState["modalView"]) => void;
 }
 
diff --git a/ui/src/routes/devices.$id.mount.tsx b/ui/src/routes/devices.$id.mount.tsx
index 1a0ace8..42be090 100644
--- a/ui/src/routes/devices.$id.mount.tsx
+++ b/ui/src/routes/devices.$id.mount.tsx
@@ -100,8 +100,6 @@ export function Dialog({ onClose }: { onClose: () => void }) {
         .finally(() => {
           setMountInProgress(false);
         });
-
-      navigate("..");
     });
   }
 
@@ -115,7 +113,7 @@ export function Dialog({ onClose }: { onClose: () => void }) {
       clearMountMediaState();
       syncRemoteVirtualMediaState()
         .then(() => {
-          false;
+          navigate("..");
         })
         .catch(err => {
           triggerError(err instanceof Error ? err.message : String(err));
@@ -839,7 +837,11 @@ function DeviceFileView({
                   onDelete={() => {
                     const selectedFile = onStorageFiles.find(f => f.name === file.name);
                     if (!selectedFile) return;
-                    if (window.confirm("Are you sure you want to delete " + selectedFile.name + "?")) {
+                    if (
+                      window.confirm(
+                        "Are you sure you want to delete " + selectedFile.name + "?",
+                      )
+                    ) {
                       handleDeleteFile(selectedFile);
                     }
                   }}
diff --git a/ui/src/routes/devices.$id.settings.hardware.tsx b/ui/src/routes/devices.$id.settings.hardware.tsx
index 3a60466..d9d3919 100644
--- a/ui/src/routes/devices.$id.settings.hardware.tsx
+++ b/ui/src/routes/devices.$id.settings.hardware.tsx
@@ -6,7 +6,7 @@ import { useJsonRpc } from "@/hooks/useJsonRpc";
 
 import notifications from "../notifications";
 import { SelectMenuBasic } from "@components/SelectMenuBasic";
-import { UsbConfigSetting } from "../components/UsbConfigSetting";
+import { UsbInfoSetting } from "../components/UsbInfoSetting";
 import { UsbDeviceSetting } from "@components/UsbDeviceSetting";
 import { FeatureFlag } from "../components/FeatureFlag";
 
@@ -131,11 +131,11 @@ export default function SettingsHardwareRoute() {
       </div>
 
       <FeatureFlag minAppVersion="0.3.8">
-        <UsbConfigSetting />
+        <UsbDeviceSetting />
       </FeatureFlag>
 
       <FeatureFlag minAppVersion="0.3.8">
-        <UsbDeviceSetting />
+        <UsbInfoSetting />
       </FeatureFlag>
     </div>
   );
diff --git a/ui/src/routes/devices.$id.settings.mouse.tsx b/ui/src/routes/devices.$id.settings.mouse.tsx
index c8c351a..1d3a6cd 100644
--- a/ui/src/routes/devices.$id.settings.mouse.tsx
+++ b/ui/src/routes/devices.$id.settings.mouse.tsx
@@ -1,23 +1,27 @@
-import { SettingsPageHeader } from "@components/SettingsPageheader";
-import { SettingsItem } from "./devices.$id.settings";
-import { Checkbox } from "@/components/Checkbox";
-import { GridCard } from "@/components/Card";
+import MouseIcon from "@/assets/mouse-icon.svg";
 import PointingFinger from "@/assets/pointing-finger.svg";
-import { CheckCircleIcon } from "@heroicons/react/16/solid";
+import { GridCard } from "@/components/Card";
+import { Checkbox } from "@/components/Checkbox";
 import { useDeviceSettingsStore, useSettingsStore } from "@/hooks/stores";
-import notifications from "@/notifications";
-import { useCallback, useEffect, useState } from "react";
 import { useJsonRpc } from "@/hooks/useJsonRpc";
-import { cx } from "../cva.config";
+import notifications from "@/notifications";
+import { SettingsPageHeader } from "@components/SettingsPageheader";
+import { CheckCircleIcon } from "@heroicons/react/16/solid";
+import { useCallback, useEffect, useState } from "react";
+import { FeatureFlag } from "../components/FeatureFlag";
 import { SelectMenuBasic } from "../components/SelectMenuBasic";
 import { useFeatureFlag } from "../hooks/useFeatureFlag";
-import { FeatureFlag } from "../components/FeatureFlag";
+import { SettingsItem } from "./devices.$id.settings";
 
 type ScrollSensitivity = "low" | "default" | "high";
 
 export default function SettingsKeyboardMouseRoute() {
   const hideCursor = useSettingsStore(state => state.isCursorHidden);
   const setHideCursor = useSettingsStore(state => state.setCursorVisibility);
+
+  const mouseMode = useSettingsStore(state => state.mouseMode);
+  const setMouseMode = useSettingsStore(state => state.setMouseMode);
+
   const scrollSensitivity = useDeviceSettingsStore(state => state.scrollSensitivity);
   const setScrollSensitivity = useDeviceSettingsStore(
     state => state.setScrollSensitivity,
@@ -122,19 +126,19 @@ export default function SettingsKeyboardMouseRoute() {
         </SettingsItem>
         <div className="space-y-4">
           <SettingsItem title="Modes" description="Choose the mouse input mode" />
-          <div className="flex flex-col items-center gap-4 md:flex-row">
+          <div className="flex items-center gap-4">
             <button
-              className="group block w-full grow"
-              onClick={() => console.log("Absolute mouse mode clicked")}
+              className="block group grow"
+              onClick={() => { setMouseMode("absolute"); }}
             >
               <GridCard>
-                <div className="group flex items-center gap-x-4 px-4 py-3">
+                <div className="flex items-center px-4 py-3 group gap-x-4">
                   <img
                     className="w-6 shrink-0 dark:invert"
                     src={PointingFinger}
                     alt="Finger touching a screen"
                   />
-                  <div className="flex grow items-center justify-between">
+                  <div className="flex items-center justify-between grow">
                     <div className="text-left">
                       <h3 className="text-sm font-semibold text-black dark:text-white">
                         Absolute
@@ -143,41 +147,32 @@ export default function SettingsKeyboardMouseRoute() {
                         Most convenient
                       </p>
                     </div>
-                    <CheckCircleIcon
-                      className={cx(
-                        "h-4 w-4 text-blue-700 transition-opacity duration-300 dark:text-blue-500",
-                      )}
-                    />
+                    {mouseMode === "absolute" && (
+                      <CheckCircleIcon className="w-4 h-4 text-blue-700 dark:text-blue-500" />
+                    )}
                   </div>
                 </div>
               </GridCard>
             </button>
             <button
-              className="group block w-full grow cursor-not-allowed opacity-50"
-              disabled
+              className="block group grow"
+              onClick={() => { setMouseMode("relative"); }}
             >
               <GridCard>
-                <div className="group flex items-center gap-x-4 px-4 py-3">
-                  <img
-                    className="w-6 shrink-0 dark:invert"
-                    src={PointingFinger}
-                    alt="Finger touching a screen"
-                  />
-                  <div className="flex grow items-center justify-between">
+                <div className="flex items-center px-4 py-3 gap-x-4">
+                  <img className="w-6 shrink-0 dark:invert" src={MouseIcon} alt="Mouse icon" />
+                  <div className="flex items-center justify-between grow">
                     <div className="text-left">
                       <h3 className="text-sm font-semibold text-black dark:text-white">
                         Relative
                       </h3>
                       <p className="text-xs leading-none text-slate-800 dark:text-slate-300">
-                        Most Compatible
+                        Most Compatible (Beta)
                       </p>
                     </div>
-                    <CheckCircleIcon
-                      className={cx(
-                        "hidden",
-                        "h-4 w-4 text-blue-700 transition-opacity duration-300 dark:text-blue-500",
-                      )}
-                    />
+                    {mouseMode === "relative" && (
+                      <CheckCircleIcon className="w-4 h-4 text-blue-700 dark:text-blue-500" />
+                    )}
                   </div>
                 </div>
               </GridCard>
diff --git a/ui/src/routes/devices.$id.settings.tsx b/ui/src/routes/devices.$id.settings.tsx
index 6084afb..1a8de03 100644
--- a/ui/src/routes/devices.$id.settings.tsx
+++ b/ui/src/routes/devices.$id.settings.tsx
@@ -16,6 +16,7 @@ import { cx } from "../cva.config";
 import { useUiStore } from "../hooks/stores";
 import useKeyboard from "../hooks/useKeyboard";
 import { useResizeObserver } from "../hooks/useResizeObserver";
+import LoadingSpinner from "../components/LoadingSpinner";
 
 /* TODO: Migrate to using URLs instead of the global state. To simplify the refactoring, we'll keep the global state for now. */
 export default function SettingsRoute() {
@@ -206,7 +207,7 @@ export default function SettingsRoute() {
               </div>
             </Card>
           </div>
-          <div className="w-full md:col-span-5">
+          <div className="w-full md:col-span-6">
             {/* <AutoHeight> */}
             <Card className="dark:bg-slate-800">
               <div
@@ -230,12 +231,14 @@ export function SettingsItem({
   description,
   children,
   className,
+  loading,
 }: {
   title: string;
   description: string | React.ReactNode;
   children?: React.ReactNode;
   className?: string;
   name?: string;
+  loading?: boolean;
 }) {
   return (
     <label
@@ -245,7 +248,10 @@ export function SettingsItem({
       )}
     >
       <div className="space-y-0.5">
-        <h3 className="text-base font-semibold text-black dark:text-white">{title}</h3>
+        <div className="flex items-center gap-x-2">
+          <h3 className="text-base font-semibold text-black dark:text-white">{title}</h3>
+          {loading && <LoadingSpinner className="h-4 w-4 text-blue-500" />}
+        </div>
         <p className="text-sm text-slate-700 dark:text-slate-300">{description}</p>
       </div>
       {children ? <div>{children}</div> : null}
diff --git a/ui/src/routes/devices.$id.tsx b/ui/src/routes/devices.$id.tsx
index 24a6428..d25b848 100644
--- a/ui/src/routes/devices.$id.tsx
+++ b/ui/src/routes/devices.$id.tsx
@@ -516,6 +516,10 @@ export default function KvmIdRoute() {
 
       <div
         className="isolate"
+        // onMouseMove={e => e.stopPropagation()}
+        // onMouseDown={e => e.stopPropagation()}
+        // onMouseUp={e => e.stopPropagation()}
+        // onPointerMove={e => e.stopPropagation()}
         onKeyUp={e => e.stopPropagation()}
         onKeyDown={e => {
           e.stopPropagation();
diff --git a/usb.go b/usb.go
index 8da6737..8a3538b 100644
--- a/usb.go
+++ b/usb.go
@@ -1,8 +1,9 @@
 package kvm
 
 import (
-	"github.com/jetkvm/kvm/internal/usbgadget"
 	"time"
+
+	"github.com/jetkvm/kvm/internal/usbgadget"
 )
 
 var gadget *usbgadget.UsbGadget
@@ -33,6 +34,10 @@ func rpcAbsMouseReport(x, y int, buttons uint8) error {
 	return gadget.AbsMouseReport(x, y, buttons)
 }
 
+func rpcRelMouseReport(dx, dy int8, buttons uint8) error {
+	return gadget.RelMouseReport(dx, dy, buttons)
+}
+
 func rpcWheelReport(wheelY int8) error {
 	return gadget.AbsMouseWheelReport(wheelY)
 }
diff --git a/usb_mass_storage.go b/usb_mass_storage.go
index 45f613f..6578069 100644
--- a/usb_mass_storage.go
+++ b/usb_mass_storage.go
@@ -15,9 +15,9 @@ import (
 	"time"
 
 	"github.com/gin-gonic/gin"
-	"github.com/psanford/httpreadat"
 	"github.com/google/uuid"
 	"github.com/pion/webrtc/v4"
+	"github.com/psanford/httpreadat"
 
 	"github.com/jetkvm/kvm/resource"
 )
@@ -27,7 +27,7 @@ func writeFile(path string, data string) error {
 }
 
 func setMassStorageImage(imagePath string) error {
-	massStorageFunctionPath, err := gadget.GetConfigPath("mass_storage_lun0")
+	massStorageFunctionPath, err := gadget.GetPath("mass_storage_lun0")
 	if err != nil {
 		return fmt.Errorf("failed to get mass storage path: %w", err)
 	}
@@ -39,7 +39,7 @@ func setMassStorageImage(imagePath string) error {
 }
 
 func setMassStorageMode(cdrom bool) error {
-	massStorageFunctionPath, err := gadget.GetConfigPath("mass_storage_lun0")
+	massStorageFunctionPath, err := gadget.GetPath("mass_storage_lun0")
 	if err != nil {
 		return fmt.Errorf("failed to get mass storage path: %w", err)
 	}
@@ -110,7 +110,7 @@ func rpcMountBuiltInImage(filename string) error {
 }
 
 func getMassStorageMode() (bool, error) {
-	massStorageFunctionPath, err := gadget.GetConfigPath("mass_storage_lun0")
+	massStorageFunctionPath, err := gadget.GetPath("mass_storage_lun0")
 	if err != nil {
 		return false, fmt.Errorf("failed to get mass storage path: %w", err)
 	}
diff --git a/web.go b/web.go
index 2f24d15..8d01b7e 100644
--- a/web.go
+++ b/web.go
@@ -11,10 +11,7 @@ import (
 
 	"github.com/gin-gonic/gin"
 	"github.com/google/uuid"
-	"github.com/prometheus/client_golang/prometheus"
-	versioncollector "github.com/prometheus/client_golang/prometheus/collectors/version"
 	"github.com/prometheus/client_golang/prometheus/promhttp"
-	"github.com/prometheus/common/version"
 	"golang.org/x/crypto/bcrypt"
 )
 
@@ -86,8 +83,6 @@ func setupRouter() *gin.Engine {
 	r.POST("/device/setup", handleSetup)
 
 	// A Prometheus metrics endpoint.
-	version.Version = builtAppVersion
-	prometheus.MustRegister(versioncollector.NewCollector("jetkvm"))
 	r.GET("/metrics", gin.WrapH(promhttp.Handler()))
 
 	// Protected routes (allows both password and noPassword modes)
diff --git a/web_tls.go b/web_tls.go
new file mode 100644
index 0000000..fff9253
--- /dev/null
+++ b/web_tls.go
@@ -0,0 +1,132 @@
+package kvm
+
+import (
+	"crypto/ecdsa"
+	"crypto/elliptic"
+	"crypto/rand"
+	"crypto/tls"
+	"crypto/x509"
+	"crypto/x509/pkix"
+	"encoding/pem"
+	"log"
+	"math/big"
+	"net"
+	"net/http"
+	"strings"
+	"sync"
+	"time"
+)
+
+const (
+	WebSecureListen                  = ":443"
+	WebSecureSelfSignedDefaultDomain = "jetkvm.local"
+	WebSecureSelfSignedDuration      = 365 * 24 * time.Hour
+)
+
+var (
+	tlsCerts    = make(map[string]*tls.Certificate)
+	tlsCertLock = &sync.Mutex{}
+)
+
+// RunWebSecureServer runs a web server with TLS.
+func RunWebSecureServer() {
+	r := setupRouter()
+
+	server := &http.Server{
+		Addr:    WebSecureListen,
+		Handler: r,
+		TLSConfig: &tls.Config{
+			// TODO: cache certificate in persistent storage
+			GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
+				hostname := WebSecureSelfSignedDefaultDomain
+				if info.ServerName != "" {
+					hostname = info.ServerName
+				} else {
+					hostname = strings.Split(info.Conn.LocalAddr().String(), ":")[0]
+				}
+
+				logger.Infof("TLS handshake for %s, SupportedProtos: %v", hostname, info.SupportedProtos)
+
+				cert := createSelfSignedCert(hostname)
+
+				return cert, nil
+			},
+		},
+	}
+	logger.Infof("Starting websecure server on %s", RunWebSecureServer)
+	err := server.ListenAndServeTLS("", "")
+	if err != nil {
+		panic(err)
+	}
+	return
+}
+
+func createSelfSignedCert(hostname string) *tls.Certificate {
+	if tlsCert := tlsCerts[hostname]; tlsCert != nil {
+		return tlsCert
+	}
+	tlsCertLock.Lock()
+	defer tlsCertLock.Unlock()
+
+	logger.Infof("Creating self-signed certificate for %s", hostname)
+
+	priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+	if err != nil {
+		log.Fatalf("Failed to generate private key: %v", err)
+	}
+	keyUsage := x509.KeyUsageDigitalSignature
+
+	notBefore := time.Now()
+	notAfter := notBefore.AddDate(1, 0, 0)
+
+	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
+	serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
+	if err != nil {
+		logger.Errorf("Failed to generate serial number: %v", err)
+	}
+
+	dnsName := hostname
+	ip := net.ParseIP(hostname)
+	if ip != nil {
+		dnsName = WebSecureSelfSignedDefaultDomain
+	}
+
+	template := x509.Certificate{
+		SerialNumber: serialNumber,
+		Subject: pkix.Name{
+			CommonName:   hostname,
+			Organization: []string{"JetKVM"},
+		},
+		NotBefore: notBefore,
+		NotAfter:  notAfter,
+
+		KeyUsage:              keyUsage,
+		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
+		BasicConstraintsValid: true,
+
+		DNSNames:    []string{dnsName},
+		IPAddresses: []net.IP{},
+	}
+
+	if ip != nil {
+		template.IPAddresses = append(template.IPAddresses, ip)
+	}
+
+	derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
+	if err != nil {
+		logger.Errorf("Failed to create certificate: %v", err)
+	}
+
+	cert := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
+	if cert == nil {
+		logger.Errorf("Failed to encode certificate")
+	}
+
+	tlsCert := &tls.Certificate{
+		Certificate: [][]byte{derBytes},
+		PrivateKey:  priv,
+	}
+	tlsCerts[hostname] = tlsCert
+
+	return tlsCert
+}