Support quoted commands, execute non-matching input
parent
6c49005342
commit
372c2be50f
|
@ -10,18 +10,12 @@ packages:
|
|||
sources:
|
||||
- https://git.sr.ht/~tslocum/gmenu
|
||||
tasks:
|
||||
- setup:
|
||||
- mkdir -p $PROJECT_DIR
|
||||
- mv $PROJECT_NAME $PROJECT_DIR/$PROJECT_NAME
|
||||
- test-gmenu:
|
||||
- cd $PROJECT_DIR/$PROJECT_NAME/cmd/gmenu
|
||||
- go test
|
||||
- test-gtkmenu: |
|
||||
cd $PROJECT_DIR/$PROJECT_NAME/cmd/gtkmenu
|
||||
go test
|
||||
- build-gmenu: |
|
||||
cd $PROJECT_DIR/$PROJECT_NAME/cmd/gmenu
|
||||
go build
|
||||
- build-gtkmenu: |
|
||||
cd $PROJECT_DIR/$PROJECT_NAME/cmd/gtkmenu
|
||||
go build
|
||||
- setup: |
|
||||
mkdir -p $PROJECT_DIR
|
||||
mv $PROJECT_NAME $PROJECT_DIR/$PROJECT_NAME
|
||||
- test: |
|
||||
cd $PROJECT_DIR/$PROJECT_NAME/cmd/gmenu
|
||||
go test -v -cover ./...
|
||||
- build: |
|
||||
cd $PROJECT_DIR/$PROJECT_NAME/cmd/gmenu
|
||||
go build
|
||||
|
|
|
@ -11,17 +11,11 @@ sources:
|
|||
- https://git.sr.ht/~tslocum/gmenu
|
||||
tasks:
|
||||
- setup: |
|
||||
mkdir -p $PROJECT_DIR
|
||||
mv $PROJECT_NAME $PROJECT_DIR/$PROJECT_NAME
|
||||
- test-gmenu: |
|
||||
cd $PROJECT_DIR/$PROJECT_NAME/cmd/gmenu
|
||||
go test
|
||||
- test-gtkmenu: |
|
||||
cd $PROJECT_DIR/$PROJECT_NAME/cmd/gtkmenu
|
||||
go test
|
||||
- build-gmenu: |
|
||||
cd $PROJECT_DIR/$PROJECT_NAME/cmd/gmenu
|
||||
go build
|
||||
- build-gtkmenu: |
|
||||
cd $PROJECT_DIR/$PROJECT_NAME/cmd/gtkmenu
|
||||
go build
|
||||
mkdir -p $PROJECT_DIR
|
||||
mv $PROJECT_NAME $PROJECT_DIR/$PROJECT_NAME
|
||||
- test: |
|
||||
cd $PROJECT_DIR/$PROJECT_NAME/cmd/gmenu
|
||||
go test -v -cover ./...
|
||||
- build: |
|
||||
cd $PROJECT_DIR/$PROJECT_NAME/cmd/gmenu
|
||||
go build
|
||||
|
|
|
@ -7,8 +7,6 @@ Desktop application launcher
|
|||
|
||||
## Warning: Experimental
|
||||
|
||||
The only platform currently supported is Linux. Windows support is planned.
|
||||
|
||||
Only a terminal interface has been implemented. A GTK interface is planned.
|
||||
|
||||
## Installation
|
||||
|
|
|
@ -5,19 +5,23 @@ package main
|
|||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"git.sr.ht/~tslocum/gmenu/pkg/dmenu"
|
||||
"github.com/tslocum/promptui"
|
||||
)
|
||||
|
||||
var terminal = "i3-sensible-terminal"
|
||||
|
||||
func init() {
|
||||
log.SetFlags(0)
|
||||
log.SetPrefix("gmenu: ")
|
||||
|
||||
promptui.SearchPrompt = ""
|
||||
}
|
||||
|
@ -68,51 +72,79 @@ func main() {
|
|||
Label: "Launch:",
|
||||
Active: ">{{ .Name }}",
|
||||
Inactive: " {{ .Name }}",
|
||||
Selected: "gmenu: Launching {{ .Name }}...",
|
||||
Selected: "Launching {{ .Name }}...",
|
||||
Details: `{{ "Name(s)" | faint }} {{ .Name }}{{ if .GenericName }}, {{ .GenericName }}{{ end }}
|
||||
{{ "Comment" | faint }} {{ .Comment }}
|
||||
{{ "Exec" | faint }} {{ .Exec }}`,
|
||||
Help: "",
|
||||
}
|
||||
prompt := promptui.Select{
|
||||
Items: desktopEntries,
|
||||
IsVimMode: false,
|
||||
Templates: templates,
|
||||
Searcher: searcher,
|
||||
StartInSearchMode: true,
|
||||
Items: desktopEntries,
|
||||
IsVimMode: false,
|
||||
Templates: templates,
|
||||
Searcher: searcher,
|
||||
StartInSearchMode: true,
|
||||
EnterAlwaysReturns: true,
|
||||
}
|
||||
selected, _, err := prompt.Run()
|
||||
selected, input, err := prompt.Run()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if selected < 0 {
|
||||
return
|
||||
}
|
||||
entry := desktopEntries[selected]
|
||||
|
||||
// TODO: Support field codes
|
||||
execSplit := strings.Split(entry.Exec, " ")
|
||||
for i, arg := range execSplit {
|
||||
if arg == "%F" || arg == "%f" || arg == "%U" || arg == "%u" {
|
||||
execSplit[i] = ""
|
||||
var (
|
||||
ex string
|
||||
)
|
||||
if selected >= 0 {
|
||||
ex = dmenu.ExpandFieldCodes(desktopEntries[selected].Exec)
|
||||
|
||||
if desktopEntries[selected].Terminal {
|
||||
runScript, err := ioutil.TempFile("", "gmenu-*")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = runScript.WriteString("#!/bin/sh\n")
|
||||
if err != nil {
|
||||
runScript.Close()
|
||||
log.Fatal(err)
|
||||
}
|
||||
_, err = runScript.WriteString(fmt.Sprintf("rm %s\n", runScript.Name()))
|
||||
if err != nil {
|
||||
runScript.Close()
|
||||
log.Fatal(err)
|
||||
}
|
||||
_, err = runScript.WriteString(fmt.Sprintf("exec %s\n", ex))
|
||||
if err != nil {
|
||||
runScript.Close()
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
runScript.Close()
|
||||
|
||||
if err := os.Chmod(runScript.Name(), 0744); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
ex = fmt.Sprintf(`%s -e '%s'`, terminal, runScript.Name())
|
||||
} else {
|
||||
ex = fmt.Sprintf(`/bin/bash -i -c '%s'`, strings.ReplaceAll(ex, `'`, `'\''`))
|
||||
}
|
||||
}
|
||||
|
||||
var args []string
|
||||
if len(execSplit) > 1 {
|
||||
args = execSplit[1:]
|
||||
}
|
||||
|
||||
cmd := exec.Command(execSplit[0], args...)
|
||||
if entry.Terminal {
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
err = cmd.Run()
|
||||
} else {
|
||||
err = cmd.Start()
|
||||
ex = fmt.Sprintf(`/bin/bash -i -c '%s'`, strings.ReplaceAll(input, `'`, `'\''`))
|
||||
}
|
||||
|
||||
command, args, err := dmenu.CommandNameAndArgs(ex)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
cmd := exec.Command(command, args...)
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true, Pgid: 0}
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
|
5
go.mod
5
go.mod
|
@ -4,7 +4,6 @@ go 1.12
|
|||
|
||||
require (
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e
|
||||
github.com/kr/pty v1.1.5 // indirect
|
||||
github.com/mattn/go-isatty v0.0.8 // indirect
|
||||
github.com/tslocum/promptui v0.3.3-0.20190626143017-6cce979adef8
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/tslocum/promptui v0.3.4-0.20190628082230-cf53eafff9c5
|
||||
)
|
||||
|
|
14
go.sum
14
go.sum
|
@ -1,5 +1,4 @@
|
|||
github.com/alecthomas/gometalinter v2.0.11+incompatible/go.mod h1:qfIpQGGz3d+NmgyPBqv+LSh50emm1pt72EtcX2vKYQk=
|
||||
github.com/alecthomas/gometalinter v2.0.12+incompatible/go.mod h1:qfIpQGGz3d+NmgyPBqv+LSh50emm1pt72EtcX2vKYQk=
|
||||
github.com/alecthomas/gometalinter v3.0.0+incompatible/go.mod h1:qfIpQGGz3d+NmgyPBqv+LSh50emm1pt72EtcX2vKYQk=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
|
||||
|
@ -12,9 +11,10 @@ github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIE
|
|||
github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU=
|
||||
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU=
|
||||
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a h1:weJVJJRzAJBFRlAiJQROKQs8oC9vOxvm4rZmBBk0ONw=
|
||||
github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||
|
@ -24,18 +24,12 @@ github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRU
|
|||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/tsenart/deadcode v0.0.0-20160724212837-210d2dc333e9/go.mod h1:q+QjxYvZ+fpjMXqs+XEriussHjSYqeXVnAdSV1tkMYk=
|
||||
github.com/tslocum/promptui v0.3.3-0.20190626142258-8da0c0f6cdc6 h1:ApJCfIVMWJ1AwC0IkFO6vx7B2pkGkaearWQdkZtb/5s=
|
||||
github.com/tslocum/promptui v0.3.3-0.20190626142258-8da0c0f6cdc6/go.mod h1:ib2idig9p59EUDQJwRIN3/JHD8ThqsUu9fnPuA4zspA=
|
||||
github.com/tslocum/promptui v0.3.3-0.20190626143017-6cce979adef8 h1:OkqQt5hcU507iYyocFea4Rz18xuPoKdUOUD7SQMtc1s=
|
||||
github.com/tslocum/promptui v0.3.3-0.20190626143017-6cce979adef8/go.mod h1:ib2idig9p59EUDQJwRIN3/JHD8ThqsUu9fnPuA4zspA=
|
||||
github.com/tslocum/promptui v0.3.4-0.20190628082230-cf53eafff9c5 h1:ngqI2eodloV7WADE+TRxuXo9lrPLXnsn+7nQvwG8kBY=
|
||||
github.com/tslocum/promptui v0.3.4-0.20190628082230-cf53eafff9c5/go.mod h1:ib2idig9p59EUDQJwRIN3/JHD8ThqsUu9fnPuA4zspA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/tools v0.0.0-20181122213734-04b5d21e00f1/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
|
|
@ -8,7 +8,8 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/kballard/go-shellquote"
|
||||
)
|
||||
|
||||
// TODO: Support Type=URL
|
||||
|
@ -28,6 +29,17 @@ var (
|
|||
entryNoDisplay = []byte("nodisplay=true")
|
||||
)
|
||||
|
||||
var quotes = map[string]string{
|
||||
`%%`: `%`,
|
||||
`\\\\ `: `\\ `,
|
||||
`\\\\` + "`": `\\` + "`",
|
||||
`\\\\$`: `\\$`,
|
||||
`\\\\(`: `\\(`,
|
||||
`\\\\)`: `\\)`,
|
||||
`\\\\\`: `\\\`,
|
||||
`\\\\\\\\`: `\\\\`,
|
||||
}
|
||||
|
||||
type DesktopEntry struct {
|
||||
Name string
|
||||
GenericName string
|
||||
|
@ -72,6 +84,64 @@ func ScanEntries(dirs []string) ([]*DesktopEntry, error) {
|
|||
return entries, nil
|
||||
}
|
||||
|
||||
func UnquoteExec(ex string) string {
|
||||
for qs, qr := range quotes {
|
||||
ex = strings.ReplaceAll(ex, qs, qr)
|
||||
}
|
||||
|
||||
return ex
|
||||
}
|
||||
|
||||
// TODO
|
||||
func ExpandFieldCodes(ex string) string {
|
||||
ex = strings.ReplaceAll(ex, "%F", "")
|
||||
ex = strings.ReplaceAll(ex, "%f", "")
|
||||
ex = strings.ReplaceAll(ex, "%U", "")
|
||||
ex = strings.ReplaceAll(ex, "%u", "")
|
||||
|
||||
return ex
|
||||
}
|
||||
|
||||
func CommandNameAndArgs(ex string) (string, []string, error) {
|
||||
execLen := len(ex)
|
||||
if execLen == 0 {
|
||||
return "", nil, nil
|
||||
} else if execLen <= 2 || ex[0] != '"' {
|
||||
split := strings.SplitN(ex, " ", 2)
|
||||
if len(split) == 1 {
|
||||
return split[0], nil, nil
|
||||
}
|
||||
|
||||
args, err := shellquote.Split(split[1])
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
return split[0], args, nil
|
||||
} else {
|
||||
endQuote := strings.IndexRune(ex[1:], '"')
|
||||
if endQuote > 0 {
|
||||
var argsAfterQuote string
|
||||
if execLen > (endQuote + 2) {
|
||||
if ex[endQuote+2] == ' ' {
|
||||
argsAfterQuote = ex[endQuote+3:]
|
||||
} else {
|
||||
argsAfterQuote = ex[endQuote+2:]
|
||||
}
|
||||
}
|
||||
|
||||
args, err := shellquote.Split(argsAfterQuote)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
return ex[1 : endQuote+1], args, nil
|
||||
} else {
|
||||
return ex, nil, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func scanDirectory(path string, f os.FileInfo, err error) error {
|
||||
if f.IsDir() || !strings.HasSuffix(strings.ToLower(path), ".desktop") {
|
||||
return nil
|
||||
|
@ -89,8 +159,9 @@ func scanDirectory(path string, f os.FileInfo, err error) error {
|
|||
var foundHeader bool
|
||||
|
||||
for scanner.Scan() {
|
||||
scannedBytes = bytes.TrimRightFunc(scanner.Bytes(), unicode.IsSpace)
|
||||
scannedBytes = bytes.TrimSpace(scanner.Bytes())
|
||||
scannedBytesLen = len(scannedBytes)
|
||||
|
||||
if scannedBytesLen == 0 || scannedBytes[0] == byte('#') {
|
||||
continue
|
||||
} else if scannedBytes[0] == byte('[') {
|
||||
|
@ -114,7 +185,7 @@ func scanDirectory(path string, f os.FileInfo, err error) error {
|
|||
} else if scannedBytesLen >= 5 && bytes.EqualFold(scannedBytes[0:5], entryIcon) {
|
||||
entry.Icon = string(scannedBytes[5:])
|
||||
} else if scannedBytesLen >= 5 && bytes.EqualFold(scannedBytes[0:5], entryExec) {
|
||||
entry.Exec = string(scannedBytes[5:])
|
||||
entry.Exec = UnquoteExec(string(scannedBytes[5:]))
|
||||
} else if scannedBytesLen >= 13 && bytes.EqualFold(scannedBytes, entryTerminal) {
|
||||
entry.Terminal = true
|
||||
} else if scannedBytesLen >= 14 && bytes.EqualFold(scannedBytes, entryNoDisplay) {
|
||||
|
|
Loading…
Reference in New Issue