Move desktop-related code to external package

This commit is contained in:
Trevor Slocum 2019-07-15 22:16:50 -07:00
parent 0103b3e242
commit f6204aca7f
8 changed files with 415 additions and 595 deletions

108
cmd/gmenu/gui.go Normal file
View File

@ -0,0 +1,108 @@
package main
import (
"github.com/jroimartin/gocui"
)
func initGUI() (*gocui.Gui, error) {
g, err := gocui.NewGui(gocui.OutputNormal)
if err != nil {
return nil, err
}
g.InputEsc = true
g.Cursor = true
g.Mouse = !disableMouseSupport
g.SetManagerFunc(layout)
if err := keybindings(g); err != nil {
return nil, err
}
go updateEntryInfo()
return g, nil
}
func layout(g *gocui.Gui) error {
maxX, maxY := g.Size()
listWidth := maxX
if !hideAppDetails {
listWidth = maxX / 2
if v, err := g.SetView("ex", maxX/2, 0, maxX, 2); err != nil {
if err != gocui.ErrUnknownView {
return err
}
ex = v
v.Frame = false
v.Wrap = false
}
if v, err := g.SetView("comment", maxX/2, 1, maxX, 3); err != nil {
if err != gocui.ErrUnknownView {
return err
}
comment = v
v.Frame = false
v.Wrap = false
}
}
if v, err := g.SetView("list", -1, 0, listWidth, maxY); err != nil {
if err != gocui.ErrUnknownView {
return err
}
list = v
v.Frame = false
v.Wrap = false
v.Highlight = true
v.SelBgColor = gocui.ColorGreen
v.SelFgColor = gocui.ColorBlack
updateEntries("")
}
if v, err := g.SetView("main", -1, -1, maxX, 1); err != nil {
if err != gocui.ErrUnknownView {
return err
}
input = v
v.Frame = false
v.Wrap = false
v.Editable = true
v.Wrap = true
v.Editor = gocui.EditorFunc(searchEditor)
if _, err := g.SetCurrentView("main"); err != nil {
return err
}
}
_, _ = maxX, maxY
return nil
}
func closeGUI() {
if closedGUI {
return
}
closedGUI = true
gui.Close()
gui.Update(func(g *gocui.Gui) error {
return gocui.ErrQuit
})
}
func quit(g *gocui.Gui, v *gocui.View) error {
closeGUI()
return gocui.ErrQuit
}

109
cmd/gmenu/gui_input.go Normal file
View File

@ -0,0 +1,109 @@
package main
import (
"log"
"github.com/jroimartin/gocui"
)
func searchEditor(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) {
switch {
case ch != 0 && mod == 0:
v.EditWrite(ch)
case key == gocui.KeySpace:
v.EditWrite(' ')
case key == gocui.KeyBackspace || key == gocui.KeyBackspace2:
v.EditDelete(true)
case key == gocui.KeyDelete:
v.EditDelete(false)
case key == gocui.KeyInsert:
v.Overwrite = !v.Overwrite
case key == gocui.KeyEnter:
err := listSelect()
if err != nil {
log.Fatal(err)
}
return
case key == gocui.KeyArrowLeft:
v.MoveCursor(-1, 0, false)
case key == gocui.KeyArrowRight:
v.MoveCursor(1, 0, false)
}
updateEntries(v.Buffer())
}
func listPrev(g *gocui.Gui, v *gocui.View) error {
list.MoveCursor(0, -1, false)
updateEntryInfo()
return nil
}
func listNext(g *gocui.Gui, v *gocui.View) error {
list.MoveCursor(0, 1, false)
updateEntryInfo()
return nil
}
func listClickFromMouse(g *gocui.Gui, v *gocui.View) error {
clickedList = true
return nil
}
func listSelectFromMouse(g *gocui.Gui, v *gocui.View) error {
if !clickedList {
return nil
}
return listSelect()
}
func listDeselectFromMouse(g *gocui.Gui, v *gocui.View) error {
clickedList = false
return nil
}
func listSelectFromKey(g *gocui.Gui, v *gocui.View) error {
return listSelect()
}
func keybindings(g *gocui.Gui) error {
if err := g.SetKeybinding("", gocui.KeyArrowUp, gocui.ModNone, listPrev); err != nil {
return err
}
if err := g.SetKeybinding("", gocui.KeyArrowDown, gocui.ModNone, listNext); err != nil {
return err
}
if err := g.SetKeybinding("", gocui.KeyEnter, gocui.ModNone, listSelectFromKey); err != nil {
return err
}
if err := g.SetKeybinding("list", gocui.MouseLeft, gocui.ModNone, listClickFromMouse); err != nil {
return err
}
if err := g.SetKeybinding("list", gocui.MouseRelease, gocui.ModNone, listSelectFromMouse); err != nil {
return err
}
if err := g.SetKeybinding("", gocui.MouseRelease, gocui.ModNone, listDeselectFromMouse); err != nil {
return err
}
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
return err
}
if err := g.SetKeybinding("", gocui.KeyEsc, gocui.ModNone, quit); err != nil {
return err
}
return nil
}

174
cmd/gmenu/gui_list.go Normal file
View File

@ -0,0 +1,174 @@
package main
import (
"flag"
"fmt"
"log"
"os"
"os/exec"
"sort"
"strings"
"syscall"
"time"
"git.sr.ht/~tslocum/desktop"
"git.sr.ht/~tslocum/gmenu/pkg/config"
"github.com/jroimartin/gocui"
"github.com/kballard/go-shellquote"
"github.com/pkg/errors"
)
func updateEntries(buf string) {
filteredEntries = nil
buf = strings.ToLower(strings.TrimSpace(buf))
if buf == "" {
for _, entries := range desktopEntries {
filteredEntries = append(filteredEntries, entries...)
}
} else {
var ranks []int
var rank int
var i int
for _, entries := range desktopEntries {
for _, entry := range entries {
rank = -1
if strings.Contains(strings.ToLower(entry.Name), buf) {
rank = 1
}
if rank == -1 {
continue
}
filteredEntries = append(filteredEntries, entry)
ranks = append(ranks, rank)
i++
}
}
sort.Slice(filteredEntries, func(i, j int) bool {
return ranks[i] < ranks[j]
})
}
list.Clear()
list.SetOrigin(0, 0)
list.SetCursor(0, 0)
defer updateEntryInfo()
if len(filteredEntries) == 0 {
return
}
lastEntry := len(filteredEntries) - 1
for i, entry := range filteredEntries {
if i == lastEntry {
fmt.Fprint(list, entry.Name)
} else {
fmt.Fprint(list, entry.Name+"\n")
}
}
}
func selectedEntry() *desktop.Entry {
if list == nil {
return nil
}
_, selectedOrigin := list.Origin()
_, selectedCursor := list.Cursor()
selected := selectedOrigin + selectedCursor
if len(filteredEntries) == 0 || selected < 0 || selected > len(filteredEntries)-1 {
return nil
}
return filteredEntries[selected]
}
func updateEntryInfo() {
if hideAppDetails {
return
}
for {
if list != nil && comment != nil && ex != nil {
break
}
time.Sleep(10 * time.Millisecond)
}
var comLine, exLine string
entry := selectedEntry()
if entry != nil {
comLine = entry.Comment
exLine = entry.Exec
} else {
comLine = "Shell command"
exLine = "/bin/bash"
}
ex.Clear()
fmt.Fprint(ex, exLine)
comment.Clear()
fmt.Fprint(comment, comLine)
}
func listSelect() error {
defer closeGUI()
var (
execute string
runInTerminal bool
waitUntilFinished bool
)
entry := selectedEntry()
if entry != nil {
runInTerminal = entry.Terminal
execute = entry.ExpandExec(shellquote.Join(flag.Args()...))
} else {
waitUntilFinished = true
execute = input.Buffer()
}
execute = strings.TrimSpace(execute)
closeGUI()
log.Println(execute)
runScript, err := desktop.RunScript(execute)
if err != nil {
return errors.Wrap(err, "failed to create run script")
}
var cmd *exec.Cmd
if runInTerminal {
cmd = exec.Command(config.TerminalCommand, "-e", runScript)
} else {
cmd = exec.Command("/usr/bin/env", "bash", "-c", runScript)
}
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 {
return errors.Wrap(err, "failed to start command")
}
if !waitUntilFinished {
return gocui.ErrQuit
}
err = cmd.Wait()
_, isExitErr := err.(*exec.ExitError)
if err != nil && !isExitErr {
return errors.Wrap(err, "failed to execute command")
}
return gocui.ErrQuit
}

View File

@ -5,29 +5,22 @@ package main
import (
"flag"
"fmt"
"log"
"os"
"os/exec"
"path"
"sort"
"strings"
"syscall"
"time"
"git.sr.ht/~tslocum/gmenu/pkg/config"
"git.sr.ht/~tslocum/gmenu/pkg/dmenu"
"git.sr.ht/~tslocum/desktop"
"github.com/jroimartin/gocui"
"github.com/mattn/go-isatty"
)
var (
configPath string
dataDirs string
disableMouseSupport bool
hideAppDetails bool
desktopEntries []*dmenu.DesktopEntry
filteredEntries []*dmenu.DesktopEntry
desktopEntries map[int][]*desktop.Entry
filteredEntries []*desktop.Entry
gui *gocui.Gui
input, comment, ex, list *gocui.View
@ -39,6 +32,7 @@ var (
func init() {
log.SetFlags(0)
flag.StringVar(&dataDirs, "data-dirs", "", "application data directories (default: $XDG_DATA_DIRS)")
flag.BoolVar(&disableMouseSupport, "no-mouse", false, "disable mouse support")
flag.BoolVar(&hideAppDetails, "no-details", false, "hide application details")
}
@ -48,63 +42,30 @@ func main() {
tty := isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd())
if !tty {
log.Fatal("failed to start gmenu: only interactive terminals are supported")
log.Fatal("failed to start gmenu: non-interactive terminals are not supported")
}
run()
}
func run() {
var dataDirs []string
dataDirsSetting := strings.Split(os.Getenv("XDG_DATA_DIRS"), ":")
for _, dataDir := range dataDirsSetting {
dataDir = strings.TrimSpace(dataDir)
if dataDir == "" {
continue
}
dataDirs = append(dataDirs, dataDir)
var dirs []string
if dataDirs != "" {
dirs = strings.Split(dataDirs, ":")
} else {
dirs = desktop.DataDirs()
}
if len(dataDirs) == 0 {
dataDirs = []string{"/usr/local/share", "/usr/share"}
}
homeDir := os.Getenv("HOME")
if strings.TrimSpace(homeDir) == "" {
homeDir = "~/"
}
dataHomeSetting := os.Getenv("XDG_DATA_HOME")
if dataHomeSetting == "" {
dataHomeSetting = path.Join(homeDir, ".local/share")
}
dataDirs = append(dataDirs, dataHomeSetting)
var err error
desktopEntries, err = dmenu.ScanEntries(dataDirs)
desktopEntries, err = desktop.Scan(dirs)
if err != nil {
log.Fatal(err)
}
gui, err = gocui.NewGui(gocui.OutputNormal)
gui, err = initGUI()
if err != nil {
log.Panicln(err)
log.Fatal(err)
}
gui.InputEsc = true
gui.Cursor = true
gui.Mouse = !disableMouseSupport
gui.SetManagerFunc(layout)
if err := keybindings(); err != nil {
log.Panicln(err)
}
go updateEntryInfo()
go func() {
if err := gui.MainLoop(); err != nil && err != gocui.ErrQuit {
log.Panicln(err)
log.Fatal(err)
}
done <- true
@ -118,335 +79,3 @@ func run() {
gui.Close()
}
}
func layout(g *gocui.Gui) error {
maxX, maxY := g.Size()
listWidth := maxX
if !hideAppDetails {
listWidth = maxX / 2
if v, err := g.SetView("ex", maxX/2, 0, maxX, 2); err != nil {
if err != gocui.ErrUnknownView {
return err
}
ex = v
v.Frame = false
v.Wrap = false
}
if v, err := g.SetView("comment", maxX/2, 1, maxX, 3); err != nil {
if err != gocui.ErrUnknownView {
return err
}
comment = v
v.Frame = false
v.Wrap = false
}
}
if v, err := g.SetView("list", -1, 0, listWidth, maxY); err != nil {
if err != gocui.ErrUnknownView {
return err
}
list = v
v.Frame = false
v.Wrap = false
v.Highlight = true
v.SelBgColor = gocui.ColorGreen
v.SelFgColor = gocui.ColorBlack
updateEntries("")
}
if v, err := g.SetView("main", -1, -1, maxX, 1); err != nil {
if err != gocui.ErrUnknownView {
return err
}
input = v
v.Frame = false
v.Wrap = false
v.Editable = true
v.Wrap = true
v.Editor = gocui.EditorFunc(searchEditor)
if _, err := g.SetCurrentView("main"); err != nil {
return err
}
}
_, _ = maxX, maxY
return nil
}
func keybindings() error {
if err := gui.SetKeybinding("", gocui.KeyArrowUp, gocui.ModNone, listPrev); err != nil {
return err
}
if err := gui.SetKeybinding("", gocui.KeyArrowDown, gocui.ModNone, listNext); err != nil {
return err
}
if err := gui.SetKeybinding("", gocui.KeyEnter, gocui.ModNone, listSelectFromKey); err != nil {
return err
}
if err := gui.SetKeybinding("list", gocui.MouseLeft, gocui.ModNone, listClickFromMouse); err != nil {
return err
}
if err := gui.SetKeybinding("list", gocui.MouseRelease, gocui.ModNone, listSelectFromMouse); err != nil {
return err
}
if err := gui.SetKeybinding("", gocui.MouseRelease, gocui.ModNone, listDeselectFromMouse); err != nil {
return err
}
if err := gui.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
return err
}
if err := gui.SetKeybinding("", gocui.KeyEsc, gocui.ModNone, quit); err != nil {
return err
}
return nil
}
func listPrev(g *gocui.Gui, v *gocui.View) error {
list.MoveCursor(0, -1, false)
updateEntryInfo()
return nil
}
func listNext(g *gocui.Gui, v *gocui.View) error {
list.MoveCursor(0, 1, false)
updateEntryInfo()
return nil
}
func updateEntryInfo() {
if hideAppDetails {
return
}
for {
if list != nil && comment != nil && ex != nil {
break
}
time.Sleep(10 * time.Millisecond)
}
var comLine, exLine string
entry := selectedEntry()
if entry != nil {
comLine = entry.Comment
exLine = entry.Exec
} else {
comLine = "Shell command"
exLine = "/bin/bash"
}
ex.Clear()
fmt.Fprint(ex, exLine)
comment.Clear()
fmt.Fprint(comment, comLine)
}
func listClickFromMouse(g *gocui.Gui, v *gocui.View) error {
clickedList = true
return nil
}
func listSelectFromMouse(g *gocui.Gui, v *gocui.View) error {
if !clickedList {
return nil
}
return listSelect()
}
func listDeselectFromMouse(g *gocui.Gui, v *gocui.View) error {
clickedList = false
return nil
}
func listSelectFromKey(g *gocui.Gui, v *gocui.View) error {
return listSelect()
}
func listSelect() error {
defer closeGUI()
var (
execute string
runInTerminal bool
waitUntilFinished bool
)
entry := selectedEntry()
if entry != nil {
runInTerminal = entry.Terminal
execute = dmenu.ExpandFieldCodes(entry.Exec)
} else {
waitUntilFinished = true
execute = input.Buffer()
}
execute = strings.TrimSpace(execute)
closeGUI()
log.Println(execute)
runScript, err := dmenu.WriteRunScript(execute)
if err != nil {
log.Fatalf("failed to write run script: %s", err)
}
var cmd *exec.Cmd
if runInTerminal {
cmd = exec.Command(config.TerminalCommand, "-e", runScript)
} else {
cmd = exec.Command("/usr/bin/env", "bash", "-c", runScript)
}
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)
}
if !waitUntilFinished {
return gocui.ErrQuit
}
err = cmd.Wait()
if err != nil {
log.Fatal(err)
}
return gocui.ErrQuit
}
func quit(g *gocui.Gui, v *gocui.View) error {
closeGUI()
return gocui.ErrQuit
}
func selectedEntry() *dmenu.DesktopEntry {
if list == nil {
return nil
}
_, selectedOrigin := list.Origin()
_, selectedCursor := list.Cursor()
selected := selectedOrigin + selectedCursor
if len(filteredEntries) == 0 || selected < 0 || selected > len(desktopEntries)-1 {
return nil
}
return filteredEntries[selected]
}
func closeGUI() {
if closedGUI {
return
}
closedGUI = true
gui.Close()
gui.Update(func(g *gocui.Gui) error {
return gocui.ErrQuit
})
}
func searchEditor(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) {
switch {
case ch != 0 && mod == 0:
v.EditWrite(ch)
case key == gocui.KeySpace:
v.EditWrite(' ')
case key == gocui.KeyBackspace || key == gocui.KeyBackspace2:
v.EditDelete(true)
case key == gocui.KeyDelete:
v.EditDelete(false)
case key == gocui.KeyInsert:
v.Overwrite = !v.Overwrite
case key == gocui.KeyEnter:
listSelect()
return
case key == gocui.KeyArrowLeft:
v.MoveCursor(-1, 0, false)
case key == gocui.KeyArrowRight:
v.MoveCursor(1, 0, false)
}
updateEntries(v.Buffer())
}
func updateEntries(buf string) {
buf = strings.ToLower(strings.TrimSpace(buf))
if buf == "" {
filteredEntries = desktopEntries
} else {
filteredEntries = nil
ranks := make([]int, len(desktopEntries))
var rank int
var i int
for _, entry := range desktopEntries {
rank = -1
if strings.Contains(strings.ToLower(entry.Name), buf) {
rank = 1
}
if rank == -1 {
continue
}
filteredEntries = append(filteredEntries, entry)
ranks[i] = rank
i++
}
sort.Slice(filteredEntries, func(i, j int) bool {
return ranks[i] < ranks[j]
})
}
list.Clear()
list.SetOrigin(0, 0)
list.SetCursor(0, 0)
defer updateEntryInfo()
if len(filteredEntries) == 0 {
return
}
lastEntry := len(filteredEntries) - 1
for i, entry := range filteredEntries {
if i == lastEntry {
fmt.Fprint(list, entry.Name)
} else {
fmt.Fprint(list, entry.Name+"\n")
}
}
}

3
go.mod
View File

@ -3,8 +3,11 @@ module git.sr.ht/~tslocum/gmenu
go 1.12
require (
git.sr.ht/~tslocum/desktop v0.1.0
github.com/jroimartin/gocui v0.4.0
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/mattn/go-isatty v0.0.8
github.com/mattn/go-runewidth v0.0.4 // indirect
github.com/nsf/termbox-go v0.0.0-20190624072549-eeb6cd0a1762 // indirect
github.com/pkg/errors v0.8.1
)

6
go.sum
View File

@ -1,10 +1,16 @@
git.sr.ht/~tslocum/desktop v0.1.0 h1:+zF9xEJMkmgRnKWxqDab+QkebPG7Hi83PhGXMQQUvKE=
git.sr.ht/~tslocum/desktop v0.1.0/go.mod h1:cUn0Q8ALjkAq40qSei795yN3CfO5pkeYKo2gmzaZ2SI=
github.com/jroimartin/gocui v0.4.0 h1:52jnalstgmc25FmtGcWqa0tcbMEWS6RpFLsOIO+I+E8=
github.com/jroimartin/gocui v0.4.0/go.mod h1:7i7bbj99OgFHzo7kB2zPb8pXLqMBSQegY7azfqXMkyY=
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/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/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/nsf/termbox-go v0.0.0-20190624072549-eeb6cd0a1762 h1:44Lv0bNi88GweB54TCjB/lEJgp+2Ze5WFpwNu0nh0ag=
github.com/nsf/termbox-go v0.0.0-20190624072549-eeb6cd0a1762/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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=

View File

@ -1,167 +0,0 @@
package dmenu
import (
"bufio"
"bytes"
"fmt"
"log"
"os"
"path/filepath"
"strings"
)
var (
scannedEntries []*DesktopEntry
scannedBytes []byte
scannedBytesLen int
entryHeader = []byte("[desktop entry]")
entryName = []byte("name=")
entryGenericName = []byte("genericname=")
entryComment = []byte("comment=")
entryIcon = []byte("icon=")
entryExec = []byte("exec=")
entryTerminal = []byte("terminal=true")
entryNoDisplay = []byte("nodisplay=true")
)
var quotes = map[string]string{
`%%`: `%`,
`\\\\ `: `\\ `,
`\\\\` + "`": `\\` + "`",
`\\\\$`: `\\$`,
`\\\\(`: `\\(`,
`\\\\)`: `\\)`,
`\\\\\`: `\\\`,
`\\\\\\\\`: `\\\\`,
}
type DesktopEntry struct {
Name string
GenericName string
Comment string
Icon string
Exec string
Terminal bool
}
func (e *DesktopEntry) String() string {
name := ""
if e.Name != "" {
name = e.Name
}
if e.GenericName != "" {
if name != "" {
name += " / "
}
name += e.GenericName
}
comment := "no comment"
if e.Comment != "" {
comment = e.Comment
}
return fmt.Sprintf("{%s - %s}", name, comment)
}
func ScanEntries(dirs []string) ([]*DesktopEntry, error) {
var entries []*DesktopEntry
for _, dir := range dirs {
err := filepath.Walk(filepath.Join(dir, "applications"), scanDirectory)
if err != nil {
return nil, err
}
entries = append(entries, scannedEntries...)
scannedEntries = nil
}
return entries, nil
}
func UnquoteExec(ex string) string {
for qs, qr := range quotes {
ex = strings.ReplaceAll(ex, qs, qr)
}
return ex
}
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 scanDirectory(path string, f os.FileInfo, err error) error {
if err != nil {
return err
}
if f == nil || f.IsDir() || !strings.HasSuffix(strings.ToLower(path), ".desktop") {
return nil
}
entry := &DesktopEntry{}
file, err := os.OpenFile(path, os.O_RDONLY, 0644)
if err != nil {
return err
}
defer file.Close()
scanner := bufio.NewScanner(file)
var foundHeader bool
for scanner.Scan() {
scannedBytes = bytes.TrimSpace(scanner.Bytes())
scannedBytesLen = len(scannedBytes)
if scannedBytesLen == 0 || scannedBytes[0] == byte('#') {
continue
} else if scannedBytes[0] == byte('[') {
if !foundHeader {
if scannedBytesLen < 15 || !bytes.EqualFold(scannedBytes[0:15], entryHeader) {
log.Printf("Warning: invalid desktop entry %s", path)
return nil
} else {
foundHeader = true
continue
}
} else {
break // Start of new section
}
} else if scannedBytesLen >= 5 && bytes.EqualFold(scannedBytes[0:5], entryName) {
entry.Name = string(scannedBytes[5:])
} else if scannedBytesLen >= 12 && bytes.EqualFold(scannedBytes[0:12], entryGenericName) {
entry.GenericName = string(scannedBytes[12:])
} else if scannedBytesLen >= 8 && bytes.EqualFold(scannedBytes[0:8], entryComment) {
entry.Comment = string(scannedBytes[8:])
} 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 = UnquoteExec(string(scannedBytes[5:]))
} else if scannedBytesLen >= 13 && bytes.EqualFold(scannedBytes, entryTerminal) {
entry.Terminal = true
} else if scannedBytesLen >= 14 && bytes.EqualFold(scannedBytes, entryNoDisplay) {
return nil // NoDisplay=true
}
}
if err := scanner.Err(); err != nil {
return err
}
if entry.Name == "" && entry.GenericName != "" {
entry.Name = entry.GenericName
entry.GenericName = ""
} else if entry.Name == entry.GenericName {
entry.GenericName = ""
}
scannedEntries = append(scannedEntries, entry)
return nil
}

View File

@ -1,42 +0,0 @@
package dmenu
import (
"fmt"
"io/ioutil"
"os"
)
func WriteRunScript(ex string) (string, error) {
runScript, err := ioutil.TempFile("", "gmenu-*")
if err != nil {
return "", err
}
_, err = runScript.WriteString("#!/bin/sh\n")
if err != nil {
runScript.Close()
return "", err
}
_, err = runScript.WriteString(fmt.Sprintf("rm %s\n", runScript.Name()))
if err != nil {
runScript.Close()
return "", err
}
_, err = runScript.WriteString("exec " + ex + "\n")
if err != nil {
runScript.Close()
return "", err
}
err = runScript.Close()
if err != nil {
return "", err
}
err = os.Chmod(runScript.Name(), 0744)
if err != nil {
return "", err
}
return runScript.Name(), nil
}