12 Commits

Author SHA1 Message Date
Piotr Miller
6ca8e6a175 Merge pull request #24 from nwg-piotr/fix_nodisplay
Fix nodisplay, fix running in terminal
2021-09-07 02:51:26 +02:00
piotr
f12be46583 fix #23 2021-09-07 02:47:17 +02:00
piotr
9d50258042 don't skip hidden entries: fixes #22 added in #19 2021-09-07 02:07:29 +02:00
piotr
1be8e432fa print errors in human-readable format 2021-09-05 01:57:24 +02:00
piotr
aa227b51b8 update README 2021-09-05 01:35:44 +02:00
piotr
56d6cd264d update README 2021-09-05 01:34:57 +02:00
piotr
bea2fdf90c verrsion bump 2021-09-05 01:26:36 +02:00
Piotr Miller
6a0f9d4a93 Merge pull request #20 from james-lawrence/use-terminal-env
use terminal environment variable if present
2021-09-05 01:12:30 +02:00
James Lawrence
206a602e77 use terminal environment variable if present 2021-09-04 15:29:58 -04:00
Piotr Miller
c655f99cd9 Merge pull request #19 from james-lawrence/bugfix/whitespace-handling
Bugfix/whitespace handling
2021-09-01 02:18:26 +02:00
James Lawrence
3a59228eaa remove old parse entry version 2021-08-31 06:33:04 -04:00
James Lawrence
57826c064d improve parsing of desktop entries.
- speed up parsing by not creating nearly as many strings.
- fix basic whitespace handling. fixes #4

goos: linux
goarch: amd64
pkg: github.com/nwg-piotr/nwg-drawer
cpu: AMD Ryzen 7 1800X Eight-Core Processor
BenchmarkDesktopEntryParserOld-16    	   10000	    146094 ns/op
BenchmarkDesktopEntryParser-16       	   26097	     43303 ns/op
PASS
ok  	github.com/nwg-piotr/nwg-drawer	3.090s
2021-08-31 06:32:40 -04:00
7 changed files with 170 additions and 99 deletions

View File

@@ -20,4 +20,5 @@ uninstall:
rm /usr/bin/nwg-drawer
run:
go run *.go
go build -o bin/nwg-drawer *.go
bin/nwg-drawer

View File

@@ -82,6 +82,8 @@ Usage of nwg-drawer:
-v display Version information
```
*NOTE: the `$TERM` environment variable overrides the `-term` argument if defined.*
## Styling
Edit `~/.config/nwg-panel/drawer.css` to your taste.

Binary file not shown.

19
main.go
View File

@@ -19,7 +19,7 @@ import (
"github.com/gotk3/gotk3/gtk"
)
const version = "0.1.9"
const version = "0.1.10"
var (
appDirs []string
@@ -59,6 +59,7 @@ type desktopEntry struct {
CommentLoc string
Icon string
Exec string
Category string
Terminal bool
NoDisplay bool
}
@@ -98,6 +99,16 @@ var (
ignore string
)
func defaultStringIfBlank(s, fallback string) string {
s = strings.TrimSpace(s)
// os.Getenv("TERM") returns "linux" instead of empty string, if program has been started
// from a key binding defined in the config file. See #23.
if s == "" || s == "linux" {
return fallback
}
return s
}
// Flags
var cssFileName = flag.String("s", "drawer.css", "Styling: css file name")
var targetOutput = flag.String("o", "", "name of the Output to display the drawer on (sway only)")
@@ -109,7 +120,7 @@ var columnsNumber = flag.Uint("c", 6, "number of Columns")
var itemSpacing = flag.Uint("spacing", 20, "icon spacing")
var lang = flag.String("lang", "", "force lang, e.g. \"en\", \"pl\"")
var fileManager = flag.String("fm", "thunar", "File Manager")
var term = flag.String("term", "alacritty", "Terminal emulator")
var term = flag.String("term", defaultStringIfBlank(os.Getenv("TERM"), "alacritty"), "Terminal emulator")
var nameLimit = flag.Int("fslen", 80, "File Search name length Limit")
var noCats = flag.Bool("nocats", false, "Disable filtering by category")
var noFS = flag.Bool("nofs", false, "Disable file search")
@@ -207,7 +218,7 @@ func main() {
err = cssProvider.LoadFromPath(cssFile)
if err != nil {
println(fmt.Sprintf("ERROR: %s css file not found or erroneous. Using GTK styling.", cssFile))
println(fmt.Sprintf(">>> %s", err))
println(fmt.Sprintf("%s", err))
} else {
println(fmt.Sprintf("Using style from %s", cssFile))
screen, _ := gdk.ScreenGetDefault()
@@ -231,7 +242,7 @@ func main() {
layershell.SetMonitor(win, monitor)
} else {
println(err)
println(fmt.Sprintf("%s", err))
}
}

104
tools.go
View File

@@ -49,7 +49,7 @@ func createPixbuf(icon string, size int) (*gdk.Pixbuf, error) {
if strings.Contains(icon, "/") {
pixbuf, err := gdk.PixbufNewFromFileAtSize(icon, size, size)
if err != nil {
println(err)
println(fmt.Sprintf("%s", err))
return nil, err
}
return pixbuf, nil
@@ -350,103 +350,29 @@ func setUpCategories() {
func parseDesktopFiles(desktopFiles []string) string {
id2entry = make(map[string]desktopEntry)
var added []string
skipped := 0
hidden := 0
for _, file := range desktopFiles {
lines, err := loadTextFile(file)
if err == nil {
parts := strings.Split(file, "/")
desktopID := parts[len(parts)-1]
name := ""
nameLoc := ""
comment := ""
commentLoc := ""
icon := ""
exec := ""
terminal := false
noDisplay := false
id := filepath.Base(file)
if _, ok := id2entry[id]; ok {
skipped++
continue
}
categories := ""
entry, err := parseDesktopEntryFile(id, file)
if err != nil {
continue
}
for _, l := range lines {
if strings.HasPrefix(l, "[") && l != "[Desktop Entry]" {
break
}
if strings.HasPrefix(l, "Name=") {
name = strings.Split(l, "=")[1]
continue
}
if strings.HasPrefix(l, fmt.Sprintf("Name[%s]=", strings.Split(*lang, "_")[0])) {
nameLoc = strings.Split(l, "=")[1]
continue
}
if strings.HasPrefix(l, "Comment=") {
comment = strings.Split(l, "=")[1]
continue
}
if strings.HasPrefix(l, fmt.Sprintf("Comment[%s]=", strings.Split(*lang, "_")[0])) {
commentLoc = strings.Split(l, "=")[1]
continue
}
if strings.HasPrefix(l, "Icon=") {
icon = strings.Split(l, "=")[1]
continue
}
if strings.HasPrefix(l, "Exec=") {
exec = strings.Split(l, "Exec=")[1]
disallowed := [2]string{"\"", "'"}
for _, char := range disallowed {
exec = strings.Replace(exec, char, "", -1)
}
continue
}
if strings.HasPrefix(l, "Categories=") {
categories = strings.Split(l, "Categories=")[1]
continue
}
if l == "Terminal=true" {
terminal = true
continue
}
if l == "NoDisplay=true" {
noDisplay = true
if entry.NoDisplay {
hidden++
continue
// We still need hidden entries, so `continue` is disallowed here
// Fixes introduced in #19
}
}
// if name[ln] not found, let's try to find name[ln_LN]
if nameLoc == "" {
nameLoc = name
}
if commentLoc == "" {
commentLoc = comment
}
if !isIn(added, desktopID) {
added = append(added, desktopID)
var entry desktopEntry
entry.DesktopID = desktopID
entry.Name = name
entry.NameLoc = nameLoc
entry.Comment = comment
entry.CommentLoc = commentLoc
entry.Icon = icon
entry.Exec = exec
entry.Terminal = terminal
entry.NoDisplay = noDisplay
desktopEntries = append(desktopEntries, entry)
id2entry[entry.DesktopID] = entry
assignToLists(entry.DesktopID, categories)
} else {
skipped++
}
}
desktopEntries = append(desktopEntries, entry)
assignToLists(entry.DesktopID, entry.Category)
}
sort.Slice(desktopEntries, func(i, j int) bool {
return desktopEntries[i].NameLoc < desktopEntries[j].NameLoc

78
xdgdesktop_parser.go Normal file
View File

@@ -0,0 +1,78 @@
package main
import (
"bufio"
"fmt"
"io"
"os"
"strconv"
"strings"
)
func parseDesktopEntryFile(id string, path string) (e desktopEntry, err error) {
o, err := os.Open(path)
if err != nil {
return e, err
}
defer o.Close()
return parseDesktopEntry(id, o)
}
func parseDesktopEntry(id string, in io.Reader) (entry desktopEntry, err error) {
cleanexec := strings.NewReplacer("\"", "", "'", "")
entry.DesktopID = id
localizedName := fmt.Sprintf("Name[%s]", strings.Split(*lang, "_")[0])
localizedComment := fmt.Sprintf("Comment[%s]", strings.Split(*lang, "_")[0])
scanner := bufio.NewScanner(in)
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
l := scanner.Text()
if strings.HasPrefix(l, "[") && l != "[Desktop Entry]" {
break
}
name, value := parseKeypair(l)
if value == "" {
continue
}
switch name {
case "Name":
entry.Name = value
case localizedName:
entry.NameLoc = value
case "Comment":
entry.Comment = value
case localizedComment:
entry.CommentLoc = value
case "Icon":
entry.Icon = value
case "Categories":
entry.Category = value
case "Terminal":
entry.Terminal, _ = strconv.ParseBool(value)
case "NoDisplay":
entry.NoDisplay, _ = strconv.ParseBool(value)
case "Exec":
entry.Exec = cleanexec.Replace(value)
}
}
// if name[ln] not found, let's try to find name[ln_LN]
if entry.NameLoc == "" {
entry.NameLoc = entry.Name
}
if entry.CommentLoc == "" {
entry.CommentLoc = entry.Comment
}
return entry, err
}
func parseKeypair(s string) (string, string) {
if idx := strings.IndexRune(s, '='); idx > 0 {
return strings.TrimSpace(s[:idx]), strings.TrimSpace(s[idx+1:])
}
return s, ""
}

53
xdgdesktop_parser_test.go Normal file
View File

@@ -0,0 +1,53 @@
package main
import (
"strings"
"testing"
)
var result desktopEntry
func BenchmarkDesktopEntryParser(b *testing.B) {
var entry desktopEntry
for n := 0; n < b.N; n++ {
entry, _ = parseDesktopEntryFile("id", "./desktop-directories/game.directory")
}
result = entry
}
func TestWhitespaceHandling(t *testing.T) {
const whitespace = `[Desktop Entry]
Categories = Debugger; Development; Git; IDE; Programming; TextEditor;
Comment = Editor for building and debugging modern web and cloud applications
Exec = bash -c "code-insiders ~/Workspaces/Linux/Flutter.code-workspace"
GenericName = Text Editor
Icon = vscode-flutter
Keywords = editor; IDE; plaintext; text; write;
MimeType = application/x-shellscript; inode/directory; text/english; text/plain; text/x-c; text/x-c++; text/x-c++hdr; text/x-c++src; text/x-chdr; text/x-csrc; text/x-java; text/x-makefile; text/x-moc; text/x-pascal; text/x-tcl; text/x-tex;
Name = VSCode Insiders with Flutter
Name[pt] = VSCode Insiders com Flutter
StartupNotify = true
StartupWMClass = code - insiders
Terminal = false
NoDisplay = false
Type = Application
Version = 1.0`
*lang = "pt"
entry, err := parseDesktopEntry("id", strings.NewReader(whitespace))
if err != nil {
t.Fatal(err)
}
if entry.Name != "VSCode Insiders with Flutter" {
t.Error("failed to parse desktop entry name")
}
if entry.NameLoc != "VSCode Insiders com Flutter" {
t.Error("failed to parse localized name")
}
if entry.NoDisplay {
t.Error("failed to parse desktop entry no display")
}
}