diff --git a/lib/filepicker/filepicker.go b/lib/filepicker/filepicker.go
new file mode 100644
index 0000000..3f07431
--- /dev/null
+++ b/lib/filepicker/filepicker.go
@@ -0,0 +1,49 @@
+// gomuks - A terminal Matrix client written in Go.
+// Copyright (C) 2022 Tulir Asokan
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see .
+
+package filepicker
+
+import (
+	"bytes"
+	"errors"
+	"os/exec"
+	"strings"
+)
+
+var zenity string
+
+func init() {
+	zenity, _ = exec.LookPath("zenity")
+}
+
+func IsSupported() bool {
+	return len(zenity) > 0
+}
+
+func Open() (string, error) {
+	cmd := exec.Command(zenity, "--file-selection")
+	var output bytes.Buffer
+	cmd.Stdout = &output
+	err := cmd.Run()
+	if err != nil {
+		var exitErr *exec.ExitError
+		if errors.As(err, &exitErr) && exitErr.ExitCode() == 1 {
+			return "", nil
+		}
+		return "", err
+	}
+	return strings.TrimSpace(output.String()), nil
+}
diff --git a/ui/commands.go b/ui/commands.go
index 4d9c531..7466511 100644
--- a/ui/commands.go
+++ b/ui/commands.go
@@ -45,6 +45,7 @@ import (
 	"maunium.net/go/mautrix/id"
 
 	"maunium.net/go/gomuks/debug"
+	"maunium.net/go/gomuks/lib/filepicker"
 )
 
 func cmdMe(cmd *Command) {
@@ -258,15 +259,28 @@ func cmdDownload(cmd *Command) {
 }
 
 func cmdUpload(cmd *Command) {
+	var path string
+	var err error
 	if len(cmd.Args) == 0 {
-		cmd.Reply("Usage: /upload ")
-		return
-	}
-
-	path, err := filepath.Abs(cmd.RawArgs)
-	if err != nil {
-		cmd.Reply("Failed to get absolute path: %v", err)
-		return
+		if filepicker.IsSupported() {
+			path, err = filepicker.Open()
+			if err != nil {
+				cmd.Reply("Failed to open file picker: %v", err)
+				return
+			} else if len(path) == 0 {
+				cmd.Reply("File picking cancelled")
+				return
+			}
+		} else {
+			cmd.Reply("Usage: /upload ")
+			return
+		}
+	} else {
+		path, err = filepath.Abs(cmd.RawArgs)
+		if err != nil {
+			cmd.Reply("Failed to get absolute path: %v", err)
+			return
+		}
 	}
 
 	go cmd.Room.SendMessageMedia(path)