Skip to content

Commit 89ce30c

Browse files
authored
Merge pull request #3 from simo787c/feat/windows-support
Fix: filter Windows desktop apps and fix watch --stats display
2 parents 255a7dc + da6c079 commit 89ce30c

5 files changed

Lines changed: 106 additions & 6 deletions

File tree

install.ps1

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ function Write-Ok($msg) { Write-Host " OK " -ForegroundColor Green -NoNewline;
1111
function Write-Err($msg) { Write-Host " ERR " -ForegroundColor Red -NoNewline; Write-Host $msg; exit 1 }
1212

1313
# Detect architecture
14-
$Arch = switch ($env:PROCESSOR_ARCHITECTURE) {
15-
"AMD64" { "amd64" }
16-
"ARM64" { "arm64" }
14+
$Arch = switch ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture) {
15+
"X64" { "amd64" }
16+
"Arm64" { "arm64" }
1717
default { Write-Err "Unsupported architecture: $_" }
1818
}
1919

internal/cmd/terminal_other.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
//go:build !windows
2+
3+
package cmd
4+
5+
// enableVTProcessing is a no-op on non-Windows platforms.
6+
func enableVTProcessing() {}

internal/cmd/terminal_windows.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//go:build windows
2+
3+
package cmd
4+
5+
import (
6+
"os"
7+
"syscall"
8+
"unsafe"
9+
)
10+
11+
var (
12+
kernel32 = syscall.NewLazyDLL("kernel32.dll")
13+
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
14+
procSetConsoleMode = kernel32.NewProc("SetConsoleMode")
15+
enableVirtualTerminalProcessing uint32 = 0x0004
16+
)
17+
18+
var vtEnabled bool
19+
20+
// enableVTProcessing enables ANSI escape code support on Windows consoles.
21+
func enableVTProcessing() {
22+
if vtEnabled {
23+
return
24+
}
25+
vtEnabled = true
26+
27+
handle := os.Stdout.Fd()
28+
var mode uint32
29+
r, _, _ := procGetConsoleMode.Call(handle, uintptr(unsafe.Pointer(&mode)))
30+
if r == 0 {
31+
return
32+
}
33+
procSetConsoleMode.Call(handle, uintptr(mode|enableVirtualTerminalProcessing))
34+
}

internal/cmd/watch.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ func scanAndEnrichWithHost(host string, withStats bool) ([]ports.ListeningPort,
122122
var prevLines int
123123

124124
func renderLiveTable(pp []ports.ListeningPort, columns []string) {
125+
enableVTProcessing()
125126
if prevLines > 0 {
126127
// Move cursor up to the beginning of the previous output
127128
fmt.Printf("\033[%dA\r", prevLines)

internal/ports/enrich.go

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ func Enrich(pp []ListeningPort) {
2121
}
2222
if pp[i].Type != PortTypeDocker {
2323
pp[i].Type = ClassifyPort(pp[i].Port)
24-
pp[i].IsApp = isDesktopApp(pp[i].Command)
24+
pp[i].IsApp = isDesktopApp(pp[i].Command, pp[i].Process, pp[i].PID)
2525
}
2626
}
2727
}
@@ -276,8 +276,14 @@ func collectPIDs(pp []ListeningPort) []int {
276276
return pids
277277
}
278278

279-
// isDesktopApp detects if the command belongs to a desktop application.
280-
func isDesktopApp(command string) bool {
279+
// isDesktopApp detects if the command belongs to a desktop application or
280+
// OS-level system service that is not relevant to development.
281+
func isDesktopApp(command string, process string, pid int) bool {
282+
if runtime.GOOS == "windows" {
283+
return isWindowsDesktopApp(command, process, pid)
284+
}
285+
286+
// macOS / Linux
281287
if command == "" {
282288
return false
283289
}
@@ -290,6 +296,59 @@ func isDesktopApp(command string) bool {
290296
return false
291297
}
292298

299+
// isWindowsDesktopApp detects Windows desktop apps and system services.
300+
// PID 0 (System Idle) and PID 4 (System) own ports like 135, 139, 445.
301+
func isWindowsDesktopApp(command string, process string, pid int) bool {
302+
if pid == 0 || pid == 4 {
303+
return true
304+
}
305+
306+
lower := strings.ToLower(command)
307+
if lower == "" {
308+
lower = strings.ToLower(process)
309+
}
310+
if lower == "" {
311+
// No command or process name — system service not visible without elevation
312+
return true
313+
}
314+
315+
// Windows system services
316+
if strings.Contains(lower, `\windows\`) {
317+
return true
318+
}
319+
// User-installed desktop apps (AppData\Local houses Discord, Cursor, Slack, etc.)
320+
if strings.Contains(lower, `\appdata\`) {
321+
return true
322+
}
323+
// Microsoft Store apps
324+
if strings.Contains(lower, `\windowsapps\`) {
325+
return true
326+
}
327+
// Known desktop app executable names (for cases where only the process name is available)
328+
knownApps := []string{
329+
"discord", "cursor", "slack", "spotify", "figma", "zoom",
330+
"teams", "onedrive", "dropbox", "githubdesktop", "notion",
331+
"telegram", "whatsapp", "1password", "bitwarden",
332+
"chrome", "firefox", "msedge", "brave", "opera",
333+
"explorer", "searchhost", "widgets",
334+
}
335+
// Extract the base executable name without .exe
336+
baseName := strings.ToLower(process)
337+
if i := strings.LastIndex(baseName, `\`); i >= 0 {
338+
baseName = baseName[i+1:]
339+
}
340+
baseName = strings.TrimSuffix(baseName, ".exe")
341+
// Also strip any trailing quote artifacts from netstat parsing
342+
baseName = strings.TrimRight(baseName, `"`)
343+
344+
for _, app := range knownApps {
345+
if strings.Contains(baseName, app) {
346+
return true
347+
}
348+
}
349+
return false
350+
}
351+
293352
// ClassifyPort returns PortTypeSystem for well-known ports (<1024), else PortTypeUser.
294353
func ClassifyPort(port int) PortType {
295354
if port < 1024 {

0 commit comments

Comments
 (0)