Run in terminal on ALT+Enter
parent
cabbf45d36
commit
08d285b02f
|
@ -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
|
||||
|
|
60
README.md
60
README.md
|
@ -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).
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
2
go.mod
|
@ -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
4
go.sum
|
@ -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=
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue