From e2f22e6d6dbd51ef4ea7ccf72aa5b4a3ce64481d Mon Sep 17 00:00:00 2001 From: crschnick Date: Tue, 13 Oct 2026 18:26:35 +0000 Subject: [PATCH] Make workspaces deleteable --- .../xpipe/app/comp/base/TileButtonComp.java | 2 +- .../xpipe/app/prefs/WorkspaceEntryComp.java | 24 ++++++++++-- .../io/xpipe/app/prefs/WorkspaceManager.java | 39 +++++++++++++++++++ lang/strings/translations_da.properties | 2 + lang/strings/translations_de.properties | 2 + lang/strings/translations_en.properties | 2 + lang/strings/translations_es.properties | 2 + lang/strings/translations_fr.properties | 2 + lang/strings/translations_id.properties | 2 + lang/strings/translations_it.properties | 2 + lang/strings/translations_ja.properties | 2 + lang/strings/translations_ko.properties | 2 + lang/strings/translations_nl.properties | 2 + lang/strings/translations_pl.properties | 2 + lang/strings/translations_pt.properties | 2 + lang/strings/translations_ru.properties | 2 + lang/strings/translations_sv.properties | 2 + lang/strings/translations_tr.properties | 2 + lang/strings/translations_vi.properties | 2 + lang/strings/translations_zh-Hans.properties | 4 +- lang/strings/translations_zh-Hant.properties | 2 + 21 files changed, 98 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/io/xpipe/app/comp/base/TileButtonComp.java b/app/src/main/java/io/xpipe/app/comp/base/TileButtonComp.java index ff31ce819..58b188139 100644 --- a/app/src/main/java/io/xpipe/app/comp/base/TileButtonComp.java +++ b/app/src/main/java/io/xpipe/app/comp/base/TileButtonComp.java @@ -59,7 +59,7 @@ public class TileButtonComp extends RegionStructureBuilder { if (action != null) { diff --git a/app/src/main/java/io/xpipe/app/prefs/WorkspaceEntryComp.java b/app/src/main/java/io/xpipe/app/prefs/WorkspaceEntryComp.java index 9b0d36493..24ac065f4 100644 --- a/app/src/main/java/io/xpipe/app/prefs/WorkspaceEntryComp.java +++ b/app/src/main/java/io/xpipe/app/prefs/WorkspaceEntryComp.java @@ -5,10 +5,12 @@ import io.xpipe.app.comp.base.HorizontalComp; import io.xpipe.app.comp.base.IconButtonComp; import io.xpipe.app.comp.base.TileButtonComp; import io.xpipe.app.core.AppI18n; +import io.xpipe.app.core.window.AppDialog; import io.xpipe.app.platform.LabelGraphic; import io.xpipe.app.util.DesktopHelper; import javafx.beans.property.ReadOnlyStringWrapper; +import javafx.beans.property.SimpleBooleanProperty; import javafx.scene.layout.Region; import java.util.List; @@ -27,10 +29,21 @@ public class WorkspaceEntryComp extends SimpleRegionBuilder { DesktopHelper.browseFile(workspace.getDir()); }); view.describe(d -> d.nameKey("browse")); - var buttons = new HorizontalComp(List.of(view)); var available = workspace.isAvailable(); + view.disable(!available); + var current = workspace.equals(WorkspaceManager.get().getCurrent()); + var delete = new IconButtonComp(new LabelGraphic.IconGraphic("mdi2t-trash-can-outline"), () -> { + if (AppDialog.confirm("workspaceDeletion")) { + WorkspaceManager.get().removeWorkspace(workspace); + } + }); + delete.describe(d -> d.nameKey("delete")); + delete.disable(!available); + delete.hide(new SimpleBooleanProperty(current)); + var buttons = new HorizontalComp(List.of(view, delete)); + buttons.spacing(7); var header = workspace.getName() - + (workspace.equals(WorkspaceManager.get().getCurrent()) ? " (" + AppI18n.get("active") + ")" : "") + + (current ? " (" + AppI18n.get("active") + ")" : "") + (!available ? " (" + AppI18n.get("unavailable") + ")" : ""); var tile = new TileButtonComp( new ReadOnlyStringWrapper(header), @@ -43,7 +56,12 @@ public class WorkspaceEntryComp extends SimpleRegionBuilder { tile.setRight(buttons); tile.setIconSize(1.0); tile.maxWidth(2000); - tile.apply(struc -> struc.setDisable(!available)); + // Don't disable the whole tile, as that would also disable the delete button + tile.applyStructure(struc -> { + struc.getGraphic().setDisable(!available); + struc.getName().setDisable(!available); + struc.getDescription().setDisable(!available); + }); return tile.build(); } } diff --git a/app/src/main/java/io/xpipe/app/prefs/WorkspaceManager.java b/app/src/main/java/io/xpipe/app/prefs/WorkspaceManager.java index a293e290c..dd36cfd48 100644 --- a/app/src/main/java/io/xpipe/app/prefs/WorkspaceManager.java +++ b/app/src/main/java/io/xpipe/app/prefs/WorkspaceManager.java @@ -104,6 +104,45 @@ public class WorkspaceManager { } } + public void removeWorkspace(WorkspaceEntry workspace) { + if (current.equals(workspace)) { + return; + } + + workspaces.remove(workspace); + save(); + + var dir = workspace.getDir(); + if (!canDeleteDirectory(dir)) { + return; + } + + ThreadHelper.runAsync(() -> { + try { + FileUtils.deleteDirectory(dir.toFile()); + } catch (Exception e) { + ErrorEventFactory.fromThrowable(e).expected().handle(); + } + }); + } + + private boolean canDeleteDirectory(Path dir) { + if (!Files.isDirectory(dir)) { + return false; + } + + // Never delete the default data dir, which also contains the workspace list, or any parent of an active dir + var defaultDir = AppProperties.get().getDefaultReleaseDataDir(); + if (defaultDir.startsWith(dir) || current.getDir().startsWith(dir)) { + return false; + } + + // Only delete directories that actually look like a data directory + return Files.isDirectory(dir.resolve("storage")) + || Files.isDirectory(dir.resolve("settings")) + || Files.isDirectory(dir.resolve("logs")); + } + public void open(WorkspaceEntry workspace) { if (current.equals(workspace) || !workspace.isAvailable()) { return; diff --git a/lang/strings/translations_da.properties b/lang/strings/translations_da.properties index ca36823ee..f159e2bc6 100644 --- a/lang/strings/translations_da.properties +++ b/lang/strings/translations_da.properties @@ -424,6 +424,8 @@ workspaceNameDescription=Arbejdsområdets visningsnavn workspacePath=Stien til arbejdsområdet workspacePathDescription=Placeringen af arbejdsområdets datakatalog workspaceCreationAlertTitle=Oprettelse af arbejdsområde +workspaceDeletionTitle=Slet arbejdsområde +workspaceDeletionContent=Vil du slette dette arbejdsområde? Dette vil permanent slette dets datakatalog, herunder alle forbindelser og indstillinger, der er gemt deri. developerForceSshTty=Tving SSH TTY developerForceSshTtyDescription=Sørg for, at alle SSH-forbindelser allokerer en pty for at teste understøttelsen af en manglende stderr og en pty. developerDisableSshTunnelGateways=Deaktiver SSH-gateway-tunneling diff --git a/lang/strings/translations_de.properties b/lang/strings/translations_de.properties index 90ee77b52..1b3550b76 100644 --- a/lang/strings/translations_de.properties +++ b/lang/strings/translations_de.properties @@ -430,6 +430,8 @@ workspaceNameDescription=Der Anzeigename des Arbeitsbereichs workspacePath=Pfad zum Arbeitsbereich workspacePathDescription=Der Speicherort des Arbeitsbereichs-Datenverzeichnisses workspaceCreationAlertTitle=Erstellung eines Arbeitsbereichs +workspaceDeletionTitle=Arbeitsbereich löschen +workspaceDeletionContent=Möchtest du diesen Arbeitsbereich löschen? Dadurch wird sein Datenverzeichnis einschließlich aller darin gespeicherten Verbindungen und Einstellungen endgültig gelöscht. developerForceSshTty=SSH-TTY erzwingen developerForceSshTtyDescription=Stell sicher, dass alle SSH-Verbindungen einen pty zuweisen, um die Unterstützung für ein fehlendes stderr und einen pty zu testen. developerDisableSshTunnelGateways=SSH-Gateway-Tunneling deaktivieren diff --git a/lang/strings/translations_en.properties b/lang/strings/translations_en.properties index 3a1a2149d..2578a23a0 100644 --- a/lang/strings/translations_en.properties +++ b/lang/strings/translations_en.properties @@ -473,6 +473,8 @@ workspaceNameDescription=The display name of the workspace workspacePath=Workspace path workspacePathDescription=The location of the workspace data directory workspaceCreationAlertTitle=Workspace creation +workspaceDeletionTitle=Delete workspace +workspaceDeletionContent=Do you want to delete this workspace? This will permanently delete its data directory including all connections and settings stored in it. developerForceSshTty=Force SSH TTY developerForceSshTtyDescription=Make all SSH connections allocate a pty to test the support for a missing stderr and a pty. developerDisableSshTunnelGateways=Disable SSH gateway tunneling diff --git a/lang/strings/translations_es.properties b/lang/strings/translations_es.properties index 832a286dd..b3e3732da 100644 --- a/lang/strings/translations_es.properties +++ b/lang/strings/translations_es.properties @@ -416,6 +416,8 @@ workspaceNameDescription=El nombre que se muestra del espacio de trabajo workspacePath=Ruta del espacio de trabajo workspacePathDescription=La ubicación del directorio de datos del espacio de trabajo workspaceCreationAlertTitle=Creación de un espacio de trabajo +workspaceDeletionTitle=Eliminar espacio de trabajo +workspaceDeletionContent=¿Quieres borrar este espacio de trabajo? Esto borrará de forma permanente su directorio de datos, incluidas todas las conexiones y configuraciones almacenadas en él. developerForceSshTty=Forzar TTY de SSH developerForceSshTtyDescription=Haz que todas las conexiones SSH asignen un pty para comprobar si hay soporte para un stderr que falta y un pty. developerDisableSshTunnelGateways=Desactivar el túnel de la pasarela SSH diff --git a/lang/strings/translations_fr.properties b/lang/strings/translations_fr.properties index 55c201896..98019f3e3 100644 --- a/lang/strings/translations_fr.properties +++ b/lang/strings/translations_fr.properties @@ -426,6 +426,8 @@ workspaceNameDescription=Le nom d'affichage de l'espace de travail workspacePath=Chemin d'accès à l'espace de travail workspacePathDescription=L'emplacement du répertoire de données de l'espace de travail workspaceCreationAlertTitle=Création d'un espace de travail +workspaceDeletionTitle=Supprimer l'espace de travail +workspaceDeletionContent=Tu veux supprimer cet espace de travail ? Ça va supprimer définitivement son répertoire de données, y compris toutes les connexions et tous les paramètres qui y sont stockés. developerForceSshTty=Forcer le TTY SSH developerForceSshTtyDescription=Fais en sorte que toutes les connexions SSH allouent un pty pour tester la prise en charge d'un stderr manquant et d'un pty. developerDisableSshTunnelGateways=Désactiver le tunneling de la passerelle SSH diff --git a/lang/strings/translations_id.properties b/lang/strings/translations_id.properties index fb071fe8c..3c8aa01bc 100644 --- a/lang/strings/translations_id.properties +++ b/lang/strings/translations_id.properties @@ -416,6 +416,8 @@ workspaceNameDescription=Nama tampilan ruang kerja workspacePath=Jalur ruang kerja workspacePathDescription=Lokasi direktori data ruang kerja workspaceCreationAlertTitle=Pembuatan ruang kerja +workspaceDeletionTitle=Hapus ruang kerja +workspaceDeletionContent=Apakah Anda ingin menghapus ruang kerja ini? Tindakan ini akan menghapus secara permanen direktori datanya, termasuk semua koneksi dan pengaturan yang tersimpan di dalamnya. developerForceSshTty=Memaksa SSH TTY developerForceSshTtyDescription=Pastikan semua koneksi SSH mengalokasikan pty untuk menguji dukungan terhadap stderr yang hilang dan pty. developerDisableSshTunnelGateways=Nonaktifkan terowongan gateway SSH diff --git a/lang/strings/translations_it.properties b/lang/strings/translations_it.properties index 522e9fb19..3c8983656 100644 --- a/lang/strings/translations_it.properties +++ b/lang/strings/translations_it.properties @@ -414,6 +414,8 @@ workspaceNameDescription=Il nome visualizzato dell'area di lavoro workspacePath=Percorso dell'area di lavoro workspacePathDescription=La posizione della directory dei dati dell'area di lavoro workspaceCreationAlertTitle=Creazione di un'area di lavoro +workspaceDeletionTitle=Elimina area di lavoro +workspaceDeletionContent=Vuoi eliminare questo spazio di lavoro? In questo modo la directory dei dati verrà eliminata definitivamente, comprese tutte le connessioni e le impostazioni in essa memorizzate. developerForceSshTty=Forza SSH TTY developerForceSshTtyDescription=Fai in modo che tutte le connessioni SSH assegnino un pty per verificare il supporto in caso di assenza di stderr e di un pty. developerDisableSshTunnelGateways=Disattiva il tunneling del gateway SSH diff --git a/lang/strings/translations_ja.properties b/lang/strings/translations_ja.properties index d228633b3..051ff3c74 100644 --- a/lang/strings/translations_ja.properties +++ b/lang/strings/translations_ja.properties @@ -420,6 +420,8 @@ workspaceNameDescription=ワークスペースの表示名 workspacePath=ワークスペースのパス workspacePathDescription=ワークスペースのデータディレクトリの場所 workspaceCreationAlertTitle=ワークスペースの作成 +workspaceDeletionTitle=ワークスペースを削除する +workspaceDeletionContent=このワークスペースを削除してもよろしいか?これにより、そのデータディレクトリが完全に削除され、そこに保存されているすべての接続や設定も削除される。 developerForceSshTty=SSH TTYを強制する developerForceSshTtyDescription=すべてのSSH接続でptyを割り当てるようにし、stderrの欠落およびptyのサポート状況をテストする。 developerDisableSshTunnelGateways=SSHゲートウェイトンネリングを無効にする diff --git a/lang/strings/translations_ko.properties b/lang/strings/translations_ko.properties index 34ddcecac..1317b8536 100644 --- a/lang/strings/translations_ko.properties +++ b/lang/strings/translations_ko.properties @@ -432,6 +432,8 @@ workspaceNameDescription=작업 공간의 표시 이름 workspacePath=작업 공간 경로 workspacePathDescription=작업 공간 데이터 디렉터리의 위치 workspaceCreationAlertTitle=작업 공간 생성 +workspaceDeletionTitle=작업 공간 삭제 +workspaceDeletionContent=이 작업 공간을 삭제하시겠습니까? 이렇게 하면 해당 데이터 디렉터리가 영구적으로 삭제되며, 여기에 저장된 모든 연결 정보와 설정도 함께 삭제됩니다. developerForceSshTty=SSH TTY 강제 설정 developerForceSshTtyDescription=모든 SSH 연결에서 pty를 할당하도록 하여, stderr이 누락되었을 때와 pty에 대한 지원 여부를 테스트합니다. developerDisableSshTunnelGateways=SSH 게이트웨이 터널링 비활성화 diff --git a/lang/strings/translations_nl.properties b/lang/strings/translations_nl.properties index 8e21d311b..3f3043490 100644 --- a/lang/strings/translations_nl.properties +++ b/lang/strings/translations_nl.properties @@ -413,6 +413,8 @@ workspaceNameDescription=De weergavenaam van de werkruimte workspacePath=Pad naar de werkruimte workspacePathDescription=De locatie van de gegevensmap van de werkruimte workspaceCreationAlertTitle=Een werkruimte aanmaken +workspaceDeletionTitle=Werkruimte verwijderen +workspaceDeletionContent=Wil je deze werkruimte verwijderen? Hierdoor wordt de gegevensmap definitief verwijderd, inclusief alle verbindingen en instellingen die daarin zijn opgeslagen. developerForceSshTty=SSH TTY afdwingen developerForceSshTtyDescription=Zorg ervoor dat alle SSH-verbindingen een pty toewijzen om te testen of er ondersteuning is voor een ontbrekende stderr en een pty. developerDisableSshTunnelGateways=SSH-gateway-tunneling uitschakelen diff --git a/lang/strings/translations_pl.properties b/lang/strings/translations_pl.properties index 64c5f22fd..b14784063 100644 --- a/lang/strings/translations_pl.properties +++ b/lang/strings/translations_pl.properties @@ -418,6 +418,8 @@ workspaceNameDescription=Nazwa wyświetlana obszaru roboczego workspacePath=Ścieżka do obszaru roboczego workspacePathDescription=Lokalizacja katalogu danych obszaru roboczego workspaceCreationAlertTitle=Tworzenie obszaru roboczego +workspaceDeletionTitle=Usuń obszar roboczy +workspaceDeletionContent=Chcesz usunąć ten obszar roboczy? Spowoduje to trwałe usunięcie katalogu danych, w tym wszystkich połączeń i ustawień w nim zapisanych. developerForceSshTty=Wymuś SSH TTY developerForceSshTtyDescription=Spraw, żeby wszystkie połączenia SSH przydzielały pty, żeby sprawdzić obsługę brakującego stderr i pty. developerDisableSshTunnelGateways=Wyłącz tunelowanie przez bramę SSH diff --git a/lang/strings/translations_pt.properties b/lang/strings/translations_pt.properties index f4585bafe..ec9441054 100644 --- a/lang/strings/translations_pt.properties +++ b/lang/strings/translations_pt.properties @@ -418,6 +418,8 @@ workspaceNameDescription=O nome de exibição do espaço de trabalho workspacePath=Caminho do espaço de trabalho workspacePathDescription=A localização do diretório de dados da área de trabalho workspaceCreationAlertTitle=Criação de um espaço de trabalho +workspaceDeletionTitle=Apagar o espaço de trabalho +workspaceDeletionContent=Queres eliminar este espaço de trabalho? Isso vai eliminar definitivamente o seu diretório de dados, incluindo todas as ligações e definições guardadas nele. developerForceSshTty=Forçar SSH TTY developerForceSshTtyDescription=Faz com que todas as ligações SSH aloquem um pty para testar o suporte a um stderr em falta e a um pty. developerDisableSshTunnelGateways=Desativar o tunelamento do gateway SSH diff --git a/lang/strings/translations_ru.properties b/lang/strings/translations_ru.properties index f90196b4a..29a609354 100644 --- a/lang/strings/translations_ru.properties +++ b/lang/strings/translations_ru.properties @@ -458,6 +458,8 @@ workspaceNameDescription=Отображаемое имя рабочей обла workspacePath=Путь к рабочей области workspacePathDescription=Расположение каталога данных рабочей области workspaceCreationAlertTitle=Создание рабочей области +workspaceDeletionTitle=Удалить рабочую область +workspaceDeletionContent=Хочешь удалить эту рабочую область? Это приведёт к окончательному удалению её каталога данных, включая все сохраненные в нём подключения и настройки. developerForceSshTty=Принудительное использование SSH TTY developerForceSshTtyDescription=Заставь все SSH-соединения выделять pty, чтобы проверить поддержку отсутствующего stderr и pty. developerDisableSshTunnelGateways=Отключить туннелирование через SSH-шлюз diff --git a/lang/strings/translations_sv.properties b/lang/strings/translations_sv.properties index dd0b8ab0a..b88b32359 100644 --- a/lang/strings/translations_sv.properties +++ b/lang/strings/translations_sv.properties @@ -413,6 +413,8 @@ workspaceNameDescription=Arbetsytans visningsnamn workspacePath=Sökväg till arbetsytan workspacePathDescription=Platsen för arbetsytans datakatalog workspaceCreationAlertTitle=Skapande av arbetsyta +workspaceDeletionTitle=Ta bort arbetsytan +workspaceDeletionContent=Vill du ta bort detta arbetsområde? Detta kommer att permanent radera dess datakatalog, inklusive alla anslutningar och inställningar som finns lagrade där. developerForceSshTty=Tvinga SSH TTY developerForceSshTtyDescription=Se till att alla SSH-anslutningar tilldelar en pty för att testa stödet för en saknad stderr och en pty. developerDisableSshTunnelGateways=Inaktivera SSH-gateway-tunneling diff --git a/lang/strings/translations_tr.properties b/lang/strings/translations_tr.properties index ac6b34348..c1a54c59d 100644 --- a/lang/strings/translations_tr.properties +++ b/lang/strings/translations_tr.properties @@ -415,6 +415,8 @@ workspaceNameDescription=Çalışma alanının görüntü adı workspacePath=Çalışma alanı yolu workspacePathDescription=Çalışma alanı veri dizininin konumu workspaceCreationAlertTitle=Çalışma alanı oluşturma +workspaceDeletionTitle=Çalışma alanını sil +workspaceDeletionContent=Bu çalışma alanını silmek istiyor musunuz? Bu işlem, içinde depolanan tüm bağlantılar ve ayarlar dahil olmak üzere veri dizinini kalıcı olarak silecektir. developerForceSshTty=SSH TTY'yi zorla developerForceSshTtyDescription=Eksik stderr ve pty desteğini test etmek için tüm SSH bağlantılarının bir pty tahsis etmesini sağlayın. developerDisableSshTunnelGateways=SSH ağ geçidi tünellemesini devre dışı bırak diff --git a/lang/strings/translations_vi.properties b/lang/strings/translations_vi.properties index f36eda560..a892f2c68 100644 --- a/lang/strings/translations_vi.properties +++ b/lang/strings/translations_vi.properties @@ -413,6 +413,8 @@ workspaceNameDescription=Tên hiển thị của không gian làm việc workspacePath=Đường dẫn đến không gian làm việc workspacePathDescription=Vị trí của thư mục dữ liệu không gian làm việc workspaceCreationAlertTitle=Tạo không gian làm việc +workspaceDeletionTitle=Xóa không gian làm việc +workspaceDeletionContent=Cậu có muốn xóa không gian làm việc này không? Thao tác này sẽ xóa vĩnh viễn thư mục dữ liệu của nó, bao gồm tất cả các kết nối và cài đặt được lưu trữ trong đó. developerForceSshTty=Buộc sử dụng SSH TTY developerForceSshTtyDescription=Hãy thiết lập sao cho tất cả các kết nối SSH đều phân bổ một pty để kiểm tra xem hệ thống có hỗ trợ stderr và pty hay không. developerDisableSshTunnelGateways=Tắt tính năng tạo đường hầm qua cổng SSH diff --git a/lang/strings/translations_zh-Hans.properties b/lang/strings/translations_zh-Hans.properties index 766def1cf..6717e0596 100644 --- a/lang/strings/translations_zh-Hans.properties +++ b/lang/strings/translations_zh-Hans.properties @@ -558,6 +558,8 @@ workspacePath=工作区路径 #custom workspacePathDescription=工作区数据目录路径 workspaceCreationAlertTitle=工作区创建 +workspaceDeletionTitle=删除工作区 +workspaceDeletionContent=您要删除此工作区吗?这将永久删除其数据目录,包括其中存储的所有连接和设置。 #custom developerForceSshTty=强制 SSH 分配 TTY #custom @@ -1742,7 +1744,7 @@ addUnsupportedKexMethodDescription=允许在此连接中使用密钥交换方法 addUnsupportedHostKeyType=添加不受支持的主机密钥类型 addUnsupportedHostKeyTypeDescription=允许在此连接中使用主机密钥类型$VAL$ addUnsupportedMacType=添加不支持的 MAC 类型 -addUnsupportedMacTypeDescription=允许在此连接中使用 MAC 类型$VAL$ +addUnsupportedMacTypeDescription=允许在此连接中使用 MAC 类型$VAL$ addUnsupportedCipher=添加不受支持的加密算法 addUnsupportedCipherDescription=允许在此连接中使用$VAL$ 加密算法 #custom diff --git a/lang/strings/translations_zh-Hant.properties b/lang/strings/translations_zh-Hant.properties index 5580676e4..5bb8000e0 100644 --- a/lang/strings/translations_zh-Hant.properties +++ b/lang/strings/translations_zh-Hant.properties @@ -415,6 +415,8 @@ workspaceNameDescription=工作區的顯示名稱 workspacePath=工作區路徑 workspacePathDescription=工作區資料目錄的位置 workspaceCreationAlertTitle=工作區建立 +workspaceDeletionTitle=刪除工作區 +workspaceDeletionContent=您要刪除此工作區嗎?此操作將永久刪除其資料目錄,包括其中儲存的所有連線與設定。 developerForceSshTty=強制使用 SSH TTY developerForceSshTtyDescription=讓所有 SSH 連線皆分配一個 pty,以測試系統對缺失的 stderr 及 pty 的支援情況。 developerDisableSshTunnelGateways=停用 SSH 閘道隧道功能