Run in terminal on ALT+Enter

pull/9/head
Trevor Slocum 2019-11-15 14:49:40 -08:00
parent cabbf45d36
commit 08d285b02f
11 changed files with 175 additions and 218 deletions

View File

@ -1,5 +1,6 @@
0.2.4:
- Add -version flag
- Run in terminal on ALT+Enter
0.2.3:
- gmenu: Resolve search not always updating app list

View File

@ -5,6 +5,8 @@
Desktop application launcher
Note: Linux is currently the only supported OS.
## Screenshots
### Console
@ -21,12 +23,10 @@ Desktop application launcher
## Download
[**Download gmenu**](https://gmenu.rocketnine.space/download/?sort=name&order=desc) (Linux binaries are available)
[**Download gmenu and gtkmenu binaries**](https://gmenu.rocketnine.space/download/?sort=name&order=desc)
## Compile
Note: This is only necessary if you did not download a binary.
### Console
```
@ -41,63 +41,27 @@ GO111MODULE=on go get git.sr.ht/~tslocum/gmenu/cmd/gtkmenu
## Usage
### Console
Start typing the name of an application or shortcut and, when necessary, use the arrow keys to select the desired entry.
```
Usage of ./gmenu:
-browser string
browser command
-data-dirs string
application data directories (default: $XDG_DATA_DIRS)
-mouse
enable mouse support
-no-details
hide application details
-no-generic
hide application generic names
-terminal string
terminal command
```
Press Enter to run normally, or press ALT+Enter to run in a terminal.
### GUI
The input buffer may be executed as a shell command by selecting the last entry in the list.
```
Usage of ./gtkmenu:
-browser string
browser command
-data-dirs string
application data directories (default: $XDG_DATA_DIRS)
-height int
window height (default 200)
-no-details
hide application details
-no-generic
hide application generic names
-no-icons
hide application icons
-resizable
allow window to be resized
-terminal string
terminal command
-width int
window width (default 800)
```
## Support
## Usage example - [sway](https://swaywm.org)/[i3](https://i3wm.org) + [alacritty](https://github.com/jwilm/alacritty)
Please share suggestions/issues [here](https://todo.sr.ht/~tslocum/gmenu).
## Integration Example - [sway](https://swaywm.org)/[i3](https://i3wm.org) + [alacritty](https://github.com/jwilm/alacritty)
### Console
```
bindsym $mod+d exec --no-startup-id alacritty --class gmenu --title gmenu --working-directory ~ -e gmenu
for_window [app_id="gmenu"] floating enable; resize set 745 105
for_window [app_id="gmenu"] floating enable; resize set 750 260
```
### GUI
```
bindsym $mod+d exec --no-startup-id gtkmenu
bindsym $mod+d exec --no-startup-id gtkmenu --width 500 --height 260
```
## Support
Please share suggestions/issues [here](https://todo.sr.ht/~tslocum/gmenu).

View File

@ -147,7 +147,9 @@ func initGUI() (*tview.Application, error) {
return nil
} else if event.Key() == tcell.KeyEnter {
err := listSelect()
runInTerminal := event.Modifiers()&tcell.ModAlt > 0
err := listSelect(runInTerminal)
if err != nil {
panic(err)
}

View File

@ -67,12 +67,11 @@ func updateEntryInfo() {
appDetailsView = appDetailsView.SetText(exLine + "\n\n" + comLine)
}
func listSelect() error {
func listSelect(runInTerminal bool) error {
defer closeGUI()
var (
execute string
runInTerminal bool
waitUntilFinished bool
)
entry := selectedEntry()

84
cmd/gtkmenu/gui.go Normal file
View File

@ -0,0 +1,84 @@
package main
import (
"log"
"os"
"github.com/gotk3/gotk3/gdk"
"github.com/gotk3/gotk3/gtk"
)
func initWindow() *gtk.Window {
w, err := gtk.WindowNew(gtk.WINDOW_TOPLEVEL)
if err != nil {
log.Fatal("failed to create application window:", err)
}
_, err = w.Connect("key-press-event", handleKeybinding)
if err != nil {
log.Fatal("failed to connect key-press-event:", err)
}
_, err = w.Connect("destroy", func() {
os.Exit(0)
})
if err != nil {
log.Fatal("failed to create application window:", err)
}
w.SetTitle("gmenu")
w.SetDecorated(false)
w.SetBorderWidth(0)
w.Stick()
w.SetKeepAbove(true)
w.SetTypeHint(gdk.WINDOW_TYPE_HINT_UTILITY)
if !config.Fullscreen {
w.SetResizable(config.Resizable)
w.SetSizeRequest(config.Width, config.Height)
w.SetPosition(gtk.WIN_POS_CENTER)
} else {
w.Fullscreen()
}
return w
}
func handleKeybinding(_ *gtk.Window, ev *gdk.Event) bool {
keyEvent := &gdk.EventKey{ev}
switch keyEvent.KeyVal() {
case gdk.KEY_Up, gdk.KEY_Down:
offset := -1
if keyEvent.KeyVal() == gdk.KEY_Down {
offset = 1
}
index := 0
row := listBox.GetSelectedRow()
if row != nil {
index = row.GetIndex()
}
row = listBox.GetRowAtIndex(index + offset)
if row != nil {
listBox.SelectRow(row)
row.GrabFocus()
inputView.GrabFocus()
}
return true
case gdk.KEY_Return:
runInTerminal := keyEvent.State()&uint(gdk.GDK_MOD1_MASK) > 0
err := listSelect(inputView, runInTerminal)
if err != nil {
log.Fatal(err)
}
return true
case gdk.KEY_Escape:
os.Exit(0)
}
return false
}

View File

@ -7,6 +7,8 @@ import (
"os"
"path"
"git.sr.ht/~tslocum/desktop"
"git.sr.ht/~tslocum/gmenu/pkg/gmenu"
"github.com/gotk3/gotk3/gdk"
"github.com/gotk3/gotk3/gtk"
)
@ -56,3 +58,36 @@ func cacheIcon(cachedIcon string, pbuf *gdk.Pixbuf) {
pbuf.SavePNG(f.Name(), CachedIconCompressionLevel)
}
func fallbackIcon(entry *gmenu.ListEntry) string {
if entry.Entry == nil {
return "utilities-terminal"
} else if entry.Type == desktop.Application {
return "application-x-executable"
}
return "text-html"
}
func loadIconImage(img *gtk.Image, entry *gmenu.ListEntry) {
img.SetMarginStart(iconMarginStart)
img.SetMarginTop(iconMargin)
img.SetMarginEnd(iconMargin)
img.SetMarginBottom(iconMargin)
var (
pbuf *gdk.Pixbuf
err error
)
if entry.Entry != nil && entry.Icon != "" {
pbuf, err = loadIcon(entry.Icon)
}
if pbuf == nil || err != nil {
pbuf, err = loadIcon(fallbackIcon(entry))
}
if err != nil {
log.Fatal("failed to create Icon:", err)
}
img.SetFromPixbuf(pbuf)
}

View File

@ -11,7 +11,6 @@ import (
"git.sr.ht/~tslocum/desktop"
"git.sr.ht/~tslocum/gmenu/pkg/gmenu"
"github.com/gotk3/gotk3/gdk"
"github.com/gotk3/gotk3/glib"
"github.com/gotk3/gotk3/gtk"
"github.com/gotk3/gotk3/pango"
@ -201,29 +200,6 @@ func initLabel(l *gtk.Label) {
l.SetEllipsize(pango.ELLIPSIZE_END)
}
func loadIconImage(img *gtk.Image, entry *gmenu.ListEntry) {
img.SetMarginStart(iconMarginStart)
img.SetMarginTop(iconMargin)
img.SetMarginEnd(iconMargin)
img.SetMarginBottom(iconMargin)
var (
pbuf *gdk.Pixbuf
err error
)
if entry.Entry != nil && entry.Icon != "" {
pbuf, err = loadIcon(entry.Icon)
}
if pbuf == nil || err != nil {
pbuf, err = loadIcon(fallbackIcon(entry))
}
if err != nil {
log.Fatal("failed to create Icon:", err)
}
img.SetFromPixbuf(pbuf)
}
func updateList(input string) {
execLabel.SetText(input)
@ -268,6 +244,29 @@ func textViewText(tv *gtk.TextView) string {
return text
}
func setNoExpand(v *gtk.Widget) {
v.SetHExpand(false)
v.SetVExpand(false)
}
func rowID(row *gtk.ListBoxRow) int {
if row == nil {
return -1
}
name, err := row.GetName()
if err != nil || len(name) < 2 || name[0] != '#' {
return -1
}
id, err := strconv.Atoi(name[1:])
if err != nil {
return -1
}
return id
}
func selectedIndex() int {
if listBox == nil {
return -1
@ -276,11 +275,6 @@ func selectedIndex() int {
return rowID(listBox.GetSelectedRow())
}
func setNoExpand(v *gtk.Widget) {
v.SetHExpand(false)
v.SetVExpand(false)
}
func selectedEntry() *desktop.Entry {
i := selectedIndex()
if len(gmenu.FilteredEntries) == 0 || i < 0 || i > len(gmenu.FilteredEntries)-1 {
@ -293,13 +287,9 @@ func selectedEntry() *desktop.Entry {
func listSelect(_ *gtk.TextView, runInTerminal bool) error {
gmenu.CloseInput()
var (
execute string
waitUntilFinished bool
)
var execute string
entry := selectedEntry()
if entry == nil {
waitUntilFinished = true
execute = textViewText(inputView)
} else if entry.Type == desktop.Application {
if entry.Terminal {
@ -316,7 +306,7 @@ func listSelect(_ *gtk.TextView, runInTerminal bool) error {
path = entry.Path
}
err := gmenu.Run(&config.Config, execute, path, runInTerminal, waitUntilFinished)
err := gmenu.Run(&config.Config, execute, path, runInTerminal, false)
if err != nil {
return err
}
@ -325,14 +315,6 @@ func listSelect(_ *gtk.TextView, runInTerminal bool) error {
return nil
}
func listFilter(row *gtk.ListBoxRow, _ uintptr) bool {
match := gmenu.MatchEntry(rowID(row))
row.SetSelectable(match)
return match
}
func listSort(row1 *gtk.ListBoxRow, row2 *gtk.ListBoxRow, _ uintptr) int {
r1 := rowID(row1)
r2 := rowID(row2)
@ -358,72 +340,10 @@ func listSort(row1 *gtk.ListBoxRow, row2 *gtk.ListBoxRow, _ uintptr) int {
return 1
}
func rowID(row *gtk.ListBoxRow) int {
if row == nil {
return -1
}
func listFilter(row *gtk.ListBoxRow, _ uintptr) bool {
match := gmenu.MatchEntry(rowID(row))
name, err := row.GetName()
if err != nil || len(name) < 2 || name[0] != '#' {
return -1
}
row.SetSelectable(match)
id, err := strconv.Atoi(name[1:])
if err != nil {
return -1
}
return id
}
func fallbackIcon(entry *gmenu.ListEntry) string {
if entry.Entry == nil {
return "utilities-terminal"
} else if entry.Type == desktop.Application {
return "application-x-executable"
}
return "text-html"
}
func setupKeyBindings(w *gtk.Window) {
}
func handleKeybinding(_ *gtk.Window, ev *gdk.Event) bool {
keyEvent := &gdk.EventKey{ev}
switch keyEvent.KeyVal() {
case gdk.KEY_Up, gdk.KEY_Down:
offset := -1
if keyEvent.KeyVal() == gdk.KEY_Down {
offset = 1
}
index := 0
row := listBox.GetSelectedRow()
if row != nil {
index = row.GetIndex()
}
row = listBox.GetRowAtIndex(index + offset)
if row != nil {
listBox.SelectRow(row)
row.GrabFocus()
inputView.GrabFocus()
}
return true
case gdk.KEY_Return:
runInTerminal := keyEvent.State()&uint(gdk.GDK_CONTROL_MASK) != 0
err := listSelect(inputView, runInTerminal)
if err != nil {
log.Fatal(err)
}
return true
case gdk.KEY_Escape:
os.Exit(0)
}
return false
return match
}

View File

@ -10,7 +10,6 @@ import (
"time"
"git.sr.ht/~tslocum/gmenu/pkg/gmenu"
"github.com/gotk3/gotk3/gdk"
"github.com/gotk3/gotk3/glib"
"github.com/gotk3/gotk3/gtk"
)
@ -111,48 +110,14 @@ func main() {
gtk.Init(nil)
w := initWindow()
container = newBox(gtk.ORIENTATION_VERTICAL)
w, err := gtk.WindowNew(gtk.WINDOW_TOPLEVEL)
if err != nil {
log.Fatal("failed to create application window:", err)
}
_, err = w.Connect("key-press-event", handleKeybinding)
if err != nil {
log.Fatal("failed to connect key-press-event:", err)
}
_, err = w.Connect("destroy", func() {
os.Exit(0)
})
if err != nil {
log.Fatal("failed to create application window:", err)
}
w.SetTitle("gmenu")
w.SetDecorated(false)
w.SetBorderWidth(0)
w.Stick()
w.SetKeepAbove(true)
w.SetTypeHint(gdk.WINDOW_TYPE_HINT_UTILITY)
if !config.Fullscreen {
w.SetResizable(config.Resizable)
w.SetSizeRequest(config.Width, config.Height)
w.SetPosition(gtk.WIN_POS_CENTER)
} else {
w.Fullscreen()
}
w.ShowNow()
w.Add(container)
<-loaded
initList(container)
w.Add(container)
w.SetFocus(&inputView.Widget)
w.ShowAll()
if config.CPUProfile != "" {

2
go.mod
View File

@ -13,5 +13,5 @@ require (
github.com/mattn/go-runewidth v0.0.6 // indirect
github.com/pkg/errors v0.8.1
github.com/rivo/tview v0.0.0-20191018125527-685bf6da76c2
golang.org/x/sys v0.0.0-20191113165036-4c7a9d0fe056 // indirect
golang.org/x/sys v0.0.0-20191115151921-52ab43148777 // indirect
)

4
go.sum
View File

@ -31,8 +31,8 @@ github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191018095205-727590c5006e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191113165036-4c7a9d0fe056 h1:dHtDnRWQtSx0Hjq9kvKFpBh9uPPKfQN70NZZmvssGwk=
golang.org/x/sys v0.0.0-20191113165036-4c7a9d0fe056/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191115151921-52ab43148777 h1:wejkGHRTr38uaKRqECZlsCsJ1/TGxIyFbH32x5zUdu4=
golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=

View File

@ -44,24 +44,11 @@ func SharedInit(c *Config) {
}
func HandleInput(u InputUpdateHandler) {
var (
in string
ok bool
)
for in := range inputBuffer {
input = in
inputLower = strings.ToLower(in)
inputLoop:
for {
select {
case in, ok = <-inputBuffer:
if !ok {
break inputLoop
}
input = in
inputLower = strings.ToLower(in)
u(input)
}
u(input)
}
inputFlushed <- true