pax_global_header00006660000000000000000000000064145556065300014523gustar00rootroot0000000000000052 comment=c4da7d817747ad2f8cfda332d95bd4fc55eb6b34 nwg-bar-0.1.6/000077500000000000000000000000001455560653000130645ustar00rootroot00000000000000nwg-bar-0.1.6/.github/000077500000000000000000000000001455560653000144245ustar00rootroot00000000000000nwg-bar-0.1.6/.github/FUNDING.yml000066400000000000000000000000221455560653000162330ustar00rootroot00000000000000github: nwg-piotr nwg-bar-0.1.6/.gitignore000066400000000000000000000004551455560653000150600ustar00rootroot00000000000000# Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Test binary, built with `go test -c` *.test # Binaries bin nwg-bar /.idea # Output of the go coverage tool, specifically when used with LiteIDE *.out # Dependency directories (remove the comment below to include it) # vendor/ nwg-bar-0.1.6/LICENSE000066400000000000000000000021011455560653000140630ustar00rootroot00000000000000MIT License Copyright (c) 2021-2023 Piotr Miller & Contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. nwg-bar-0.1.6/Makefile000066400000000000000000000016251455560653000145300ustar00rootroot00000000000000PREFIX ?= /usr DESTDIR ?= get: go get github.com/gotk3/gotk3 go get github.com/gotk3/gotk3/gdk go get github.com/gotk3/gotk3/glib go get github.com/dlasky/gotk3-layershell/layershell go get github.com/joshuarubin/go-sway go get github.com/allan-simon/go-singleinstance build: go build -v -o bin/nwg-bar . install: mkdir -p $(DESTDIR)$(PREFIX)/share/nwg-bar cp config/* $(DESTDIR)$(PREFIX)/share/nwg-bar mkdir -p $(DESTDIR)$(PREFIX)/share/nwg-bar/images cp images/* $(DESTDIR)$(PREFIX)/share/nwg-bar/images mkdir -p $(DESTDIR)$(PREFIX)/bin cp bin/nwg-bar $(DESTDIR)$(PREFIX)/bin/nwg-bar mkdir -p $(DESTDIR)$(PREFIX)/share/doc/nwg-bar cp README.md $(DESTDIR)$(PREFIX)/share/doc/nwg-bar mkdir -p $(DESTDIR)$(PREFIX)/share/licenses/nwg-bar cp LICENSE $(DESTDIR)$(PREFIX)/share/licenses/nwg-bar uninstall: rm -r $(DESTDIR)$(PREFIX)/share/nwg-bar rm $(DESTDIR)$(PREFIX)/bin/nwg-bar run: go run . nwg-bar-0.1.6/README.md000066400000000000000000000066421455560653000143530ustar00rootroot00000000000000# nwg-bar This application is a part of the [nwg-shell](https://nwg-piotr.github.io/nwg-shell) project. **Contributing:** please read the [general contributing rules for the nwg-shell project](https://nwg-piotr.github.io/nwg-shell/contribution). nwg-bar is a Golang replacement to the `nwgbar` command (a part of [nwg-launchers](https://github.com/nwg-piotr/nwg-launchers)), with some improvements. Originally aimed at sway, works with wlroots-based compositors only. The `nwg-bar` command creates a button bar on the basis of a JSON template placed in the `~/.config/nwg-bar/` folder. By default the command displays a horizontal bar in the center of the screen. Use command line arguments to change the placement. ![image](https://user-images.githubusercontent.com/20579136/163154930-883140f3-0f69-481f-b07f-cbd4d4c75117.png) [![Packaging status](https://repology.org/badge/vertical-allrepos/nwg-bar.svg)](https://repology.org/project/nwg-bar/versions) ## Installation ### Requirements - `go` 1.20 - `gtk3` - `gtk-layer-shell` ### Steps 1. Clone the repository, cd into it. 2. Install golang libraries with `make get`. First time it may take ages, be patient. 3. `make build` 4. `sudo make install` If your machine is x86_64, you may skip 2 and 3, and just install the provided binary with `sudo make install`. To uninstall run `sudo make uninstall`. ## Running ```text Usage of nwg-bar: -a string Alignment in full width/height: "start" or "end" (default "middle") -f take Full screen width/height -i int Icon size (default 48) -mb int Margin Bottom -ml int Margin Left -mr int Margin Right -mt int Margin Top -o string name of Output to display the bar on -p string Position: "bottom", "top", "left" or "right" (default "center") -s string csS file name (default "style.css") -t string Template file name (default "bar.json") -v display Version information -x open on top layer witch eXclusive zone ``` *NOTE: for now the `-o` argument works on sway only.* ## Templates Templates in JSON format should be placed in the `~/.config/nwg-bar` folder. The default `bar.json` template creates sample Exit menu for sway on Arch Linux. You may adjust it to your system, and also add as many other templates, as you need. Use the `-t somename.json` argument to specify the template name to use. ```json [ { "label": "Lock", "exec": "swaylock -f -c 000000", "icon": "/usr/share/nwg-bar/images/system-lock-screen.svg" }, { "label": "Logout", "exec": "swaymsg exit", "icon": "/usr/share/nwg-bar/images/system-log-out.svg" }, { "label": "Reboot", "exec": "systemctl reboot", "icon": "/usr/share/nwg-bar/images/system-reboot.svg" }, { "label": "Shutdown", "exec": "systemctl -i poweroff", "icon": "/usr/share/nwg-bar/images/system-shutdown.svg" } ] ``` - `label` field defines the button label; - `exec` field defines the command to execute on button click; - `icon` field specifies the button icon; you may use a system icon name, like e.g. `system-lock-screen`, or a path to .svg/.png file. Use labels with uderscores (like `_Lock`, `_Reboot') to create keyboard shortcuts to the buttons. You will be able to access them with the `Alt` key. ## Styling Edit the `~/.config/nwg-bar/style.css` file to change the bar appearance. You may also specify another .css file (in the same folder) with the `-s somename.css` argument. nwg-bar-0.1.6/config/000077500000000000000000000000001455560653000143315ustar00rootroot00000000000000nwg-bar-0.1.6/config/bar.json000066400000000000000000000007701455560653000157740ustar00rootroot00000000000000[ { "label": "Lock", "exec": "swaylock -f -c 000000", "icon": "/usr/share/nwg-bar/images/system-lock-screen.svg" }, { "label": "Logout", "exec": "swaymsg exit", "icon": "/usr/share/nwg-bar/images/system-log-out.svg" }, { "label": "Reboot", "exec": "systemctl reboot", "icon": "/usr/share/nwg-bar/images/system-reboot.svg" }, { "label": "Shutdown", "exec": "systemctl -i poweroff", "icon": "/usr/share/nwg-bar/images/system-shutdown.svg" } ]nwg-bar-0.1.6/config/style.css000066400000000000000000000011101455560653000161740ustar00rootroot00000000000000window { background-color: rgba (0, 0, 0, 1.0) } /* Outer bar container, takes all the window width/height */ #outer-box { margin: 0px } /* Inner bar container, surrounds buttons */ #inner-box { background-color: rgba (0, 0, 0, 0.85); border-radius: 10px; border-style: none; border-width: 1px; border-color: rgba (156, 142, 122, 0.7); padding: 5px; margin: 5px } button, image { background: none; border: none; box-shadow: none } button { padding-left: 10px; padding-right: 10px; margin: 5px } button:hover { background-color: rgba (255, 255, 255, 0.1) } nwg-bar-0.1.6/go.mod000066400000000000000000000006461455560653000142000ustar00rootroot00000000000000module github.com/nwg-piotr/nwg-bar go 1.21 require ( github.com/allan-simon/go-singleinstance v0.0.0-20210120080615-d0997106ab37 github.com/dlasky/gotk3-layershell v0.0.0-20230802002603-b0c42cd8474f github.com/gotk3/gotk3 v0.6.2 github.com/joshuarubin/go-sway v1.2.0 ) require ( github.com/joshuarubin/lifecycle v1.1.4 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/sync v0.6.0 // indirect ) nwg-bar-0.1.6/go.sum000066400000000000000000000057311455560653000142250ustar00rootroot00000000000000github.com/allan-simon/go-singleinstance v0.0.0-20210120080615-d0997106ab37 h1:28uU3TtuvQ6KRndxg9TrC868jBWmSKgh0GTXkACCXmA= github.com/allan-simon/go-singleinstance v0.0.0-20210120080615-d0997106ab37/go.mod h1:6AXRstqK+32jeFmw89QGL2748+dj34Av4xc/I9oo9BY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dlasky/gotk3-layershell v0.0.0-20230802002603-b0c42cd8474f h1:qDnUQAD7tVX/gnL6uSgouzfGNA4xXH+B/fd6Ko19GgM= github.com/dlasky/gotk3-layershell v0.0.0-20230802002603-b0c42cd8474f/go.mod h1:JHLx2Wz4mAPVwn4PFhC69ydwyHP4A3wQvlg7HKVVc1U= github.com/gotk3/gotk3 v0.6.1/go.mod h1:/hqFpkNa9T3JgNAE2fLvCdov7c5bw//FHNZrZ3Uv9/Q= github.com/gotk3/gotk3 v0.6.2 h1:sx/PjaKfKULJPTPq8p2kn2ZbcNFxpOJqi4VLzMbEOO8= github.com/gotk3/gotk3 v0.6.2/go.mod h1:/hqFpkNa9T3JgNAE2fLvCdov7c5bw//FHNZrZ3Uv9/Q= github.com/joshuarubin/go-sway v1.2.0 h1:t3eqW504//uj9PDwFf0+IVfkD+WoOGaDX5gYIe0BHyM= github.com/joshuarubin/go-sway v1.2.0/go.mod h1:qcDd6f25vJ0++wICwA1BainIcRC67p2Mb4lsrZ0k3/k= github.com/joshuarubin/lifecycle v1.0.0/go.mod h1:sRy++ATvR9Ee21tkRdFkQeywAWvDsue66V70K0Dnl54= github.com/joshuarubin/lifecycle v1.1.4 h1:9ZjvYSsWax9DC3Jpz6vGf/0KnU8FNMjh0/vJ3SpSBRQ= github.com/joshuarubin/lifecycle v1.1.4/go.mod h1:QqHrqwMPMA9dbJY3XgIyVLhzHMSGOFrcCAQ59bke1mo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= nwg-bar-0.1.6/images/000077500000000000000000000000001455560653000143315ustar00rootroot00000000000000nwg-bar-0.1.6/images/system-hibernate.svg000066400000000000000000000044441455560653000203430ustar00rootroot00000000000000 image/svg+xml nwg-bar-0.1.6/images/system-lock-screen.svg000066400000000000000000000045241455560653000206060ustar00rootroot00000000000000 image/svg+xml nwg-bar-0.1.6/images/system-log-out.svg000066400000000000000000000035541455560653000177710ustar00rootroot00000000000000 image/svg+xml nwg-bar-0.1.6/images/system-reboot.svg000066400000000000000000000053311455560653000176700ustar00rootroot00000000000000 image/svg+xml nwg-bar-0.1.6/images/system-shutdown.svg000066400000000000000000000035351455560653000202550ustar00rootroot00000000000000 image/svg+xml nwg-bar-0.1.6/images/system-suspend.svg000066400000000000000000000037701455560653000200640ustar00rootroot00000000000000 image/svg+xml nwg-bar-0.1.6/main.go000066400000000000000000000204111455560653000143350ustar00rootroot00000000000000package main import ( "encoding/json" "flag" "fmt" "log" "os" "os/signal" "os/user" "path/filepath" "strconv" "strings" "syscall" "github.com/allan-simon/go-singleinstance" "github.com/dlasky/gotk3-layershell/layershell" "github.com/gotk3/gotk3/gdk" "github.com/gotk3/gotk3/glib" "github.com/gotk3/gotk3/gtk" ) const version = "0.1.6" var ( configDirectory string dataHome string buttons []Button src glib.SourceHandle outerOrientation gtk.Orientation innerOrientation gtk.Orientation ) type Button struct { Icon string Label string Exec string } // Flags var alignment = flag.String("a", "middle", "Alignment in full width/height: \"start\" or \"end\"") var full = flag.Bool("f", false, "take Full screen width/height") var imgSize = flag.Int("i", 48, "Icon size") var targetOutput = flag.String("o", "", "name of Output to display the bar on") var position = flag.String("p", "center", "Position: \"bottom\", \"top\", \"left\" or \"right\"") var marginTop = flag.Int("mt", 0, "Margin Top") var marginLeft = flag.Int("ml", 0, "Margin Left") var marginRight = flag.Int("mr", 0, "Margin Right") var marginBottom = flag.Int("mb", 0, "Margin Bottom") var cssFileName = flag.String("s", "style.css", "csS file name") var templateFileName = flag.String("t", "bar.json", "Template file name") var displayVersion = flag.Bool("v", false, "display Version information") var exclusiveZone = flag.Bool("x", false, "open on top layer witch eXclusive zone") var gtkTheme = flag.String("g", "", "GTK theme name") func main() { flag.Parse() if *displayVersion { fmt.Printf("nwg-bar version %s\n", version) os.Exit(0) } // Gentle SIGTERM handler thanks to reiki4040 https://gist.github.com/reiki4040/be3705f307d3cd136e85 signalChan := make(chan os.Signal, 1) signal.Notify(signalChan, syscall.SIGTERM) go func() { for { s := <-signalChan if s == syscall.SIGTERM { println("SIGTERM received, bye bye!") gtk.MainQuit() } } }() // We want the same key/mouse binding to turn the bar off. Kill the running instance and exit. currentUserId := "no-user" currentUser, err := user.Current() if err == nil { currentUserId = currentUser.Uid } lockFilePath := fmt.Sprintf("%s/%s-nwg-bar.lock", tempDir(), currentUserId) lockFile, err := singleinstance.CreateLockFile(lockFilePath) if err != nil { pid, err := readTextFile(lockFilePath) if err == nil { i, err := strconv.Atoi(pid) if err == nil { syscall.Kill(i, syscall.SIGTERM) } } os.Exit(0) } defer lockFile.Close() dataHome = getDataHome() configDirectory = configDir() // will only be created if it does not yet exist createDir(configDirectory) // Copy default config if !pathExists(filepath.Join(configDirectory, "style.css")) { err := copyFile(filepath.Join(dataHome, "nwg-bar/style.css"), filepath.Join(configDirectory, "style.css")) if err != nil { return } } if !pathExists(filepath.Join(configDirectory, "bar.json")) { err := copyFile(filepath.Join(dataHome, "nwg-bar/bar.json"), filepath.Join(configDirectory, "bar.json")) if err != nil { return } } // load JSON template if !strings.HasPrefix(*templateFileName, "/") { *templateFileName = filepath.Join(configDirectory, *templateFileName) } templateJson, err := readTextFile(*templateFileName) if err != nil { log.Fatal(err) } else { // parse JSON to []Button err := json.Unmarshal([]byte(templateJson), &buttons) if err != nil { return } else { println(fmt.Sprintf("%v items loaded from template %s", len(buttons), *templateFileName)) } } // load style sheet if !strings.HasPrefix(*cssFileName, "/") { *cssFileName = filepath.Join(configDirectory, *cssFileName) } gtk.Init(nil) settings, _ := gtk.SettingsGetDefault() if *gtkTheme != "" { err = settings.SetProperty("gtk-theme-name", *gtkTheme) if err != nil { fmt.Printf("Unable to set theme: %s\n", err) } else { fmt.Printf("User demanded theme: %s\n", *gtkTheme) } } screen, _ := gdk.ScreenGetDefault() cssProvider, _ := gtk.CssProviderNew() err = cssProvider.LoadFromPath(*cssFileName) if err != nil { fmt.Printf("%s file erroneous or not found, using GTK styling\n", *cssFileName) } else { fmt.Printf("Using style: %s\n", *cssFileName) gtk.AddProviderForScreen(screen, cssProvider, gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) } win, err := gtk.WindowNew(gtk.WINDOW_TOPLEVEL) if err != nil { log.Fatal("Unable to create window:", err) } visual, _ := screen.GetRGBAVisual() if visual != nil && screen.IsComposited() { win.SetVisual(visual) } layershell.InitForWindow(win) // if -o argument given var output2mon map[string]*gdk.Monitor if *targetOutput != "" { // We want to assign layershell to a monitor, but we only know the output name! output2mon, err = mapOutputs() if err == nil { layershell.SetMonitor(win, output2mon[*targetOutput]) } else { fmt.Println(err) } } outerOrientation = gtk.ORIENTATION_VERTICAL innerOrientation = gtk.ORIENTATION_HORIZONTAL if *position == "bottom" || *position == "top" { if *position == "bottom" { layershell.SetAnchor(win, layershell.LAYER_SHELL_EDGE_BOTTOM, true) } else { layershell.SetAnchor(win, layershell.LAYER_SHELL_EDGE_TOP, true) } outerOrientation = gtk.ORIENTATION_VERTICAL innerOrientation = gtk.ORIENTATION_HORIZONTAL layershell.SetAnchor(win, layershell.LAYER_SHELL_EDGE_LEFT, *full) layershell.SetAnchor(win, layershell.LAYER_SHELL_EDGE_RIGHT, *full) } if *position == "left" || *position == "right" { if *position == "left" { layershell.SetAnchor(win, layershell.LAYER_SHELL_EDGE_LEFT, true) } else { layershell.SetAnchor(win, layershell.LAYER_SHELL_EDGE_RIGHT, true) } layershell.SetAnchor(win, layershell.LAYER_SHELL_EDGE_TOP, *full) layershell.SetAnchor(win, layershell.LAYER_SHELL_EDGE_BOTTOM, *full) outerOrientation = gtk.ORIENTATION_HORIZONTAL innerOrientation = gtk.ORIENTATION_VERTICAL } layershell.SetMargin(win, layershell.LAYER_SHELL_EDGE_TOP, *marginTop) layershell.SetMargin(win, layershell.LAYER_SHELL_EDGE_LEFT, *marginLeft) layershell.SetMargin(win, layershell.LAYER_SHELL_EDGE_RIGHT, *marginRight) layershell.SetMargin(win, layershell.LAYER_SHELL_EDGE_BOTTOM, *marginBottom) if !*exclusiveZone { layershell.SetLayer(win, layershell.LAYER_SHELL_LAYER_OVERLAY) layershell.SetExclusiveZone(win, -1) } else { layershell.SetLayer(win, layershell.LAYER_SHELL_LAYER_TOP) layershell.SetExclusiveZone(win, 0) } layershell.SetKeyboardMode(win, layershell.LAYER_SHELL_KEYBOARD_MODE_EXCLUSIVE) win.Connect("destroy", func() { gtk.MainQuit() }) // Close the window on leave, but not immediately, to avoid accidental closes win.Connect("leave-notify-event", func() { src = glib.TimeoutAdd(uint(500), func() bool { gtk.MainQuit() src = 0 return false }) }) win.Connect("enter-notify-event", func() { cancelClose() }) win.Connect("key-release-event", func(window *gtk.Window, event *gdk.Event) { key := &gdk.EventKey{Event: event} if key.KeyVal() == gdk.KEY_Escape { gtk.MainQuit() } }) outerBox, _ := gtk.BoxNew(outerOrientation, 0) outerBox.SetProperty("name", "outer-box") win.Add(outerBox) alignmentBox, _ := gtk.BoxNew(innerOrientation, 0) outerBox.PackStart(alignmentBox, true, false, 0) mainBox, _ := gtk.BoxNew(innerOrientation, 0) mainBox.SetHomogeneous(true) mainBox.SetProperty("name", "inner-box") if *alignment == "start" { alignmentBox.PackStart(mainBox, false, true, 0) } else if *alignment == "end" { alignmentBox.PackEnd(mainBox, false, true, 0) } else { alignmentBox.PackStart(mainBox, true, false, 0) } for _, b := range buttons { button, _ := gtk.ButtonNew() button.SetProperty("use-underline", true) if b.Icon != "" { button.SetAlwaysShowImage(true) button.SetImagePosition(gtk.POS_TOP) pixbuf, err := createPixbuf(b.Icon, *imgSize) var img *gtk.Image if err == nil { img, _ = gtk.ImageNewFromPixbuf(pixbuf) } else { img, _ = gtk.ImageNewFromIconName("image-missing", gtk.ICON_SIZE_INVALID) } button.SetImage(img) } if b.Label != "" { button.SetLabel(b.Label) } button.Connect("enter-notify-event", func() { cancelClose() }) exec := b.Exec button.Connect("clicked", func() { launch(exec) }) mainBox.PackStart(button, true, true, 0) } win.ShowAll() gtk.Main() } nwg-bar-0.1.6/tools.go000066400000000000000000000104601455560653000145540ustar00rootroot00000000000000package main import ( "context" "fmt" "io" "log" "os" "os/exec" "strings" "time" "github.com/gotk3/gotk3/gdk" "github.com/gotk3/gotk3/glib" "github.com/gotk3/gotk3/gtk" "github.com/joshuarubin/go-sway" ) func tempDir() string { if os.Getenv("TMPDIR") != "" { return os.Getenv("TMPDIR") } else if os.Getenv("TEMP") != "" { return os.Getenv("TEMP") } else if os.Getenv("TMP") != "" { return os.Getenv("TMP") } return "/tmp" } func readTextFile(path string) (string, error) { bytes, err := os.ReadFile(path) if err != nil { return "", err } return string(bytes), nil } func configDir() string { if os.Getenv("XDG_CONFIG_HOME") != "" { return (fmt.Sprintf("%s/nwg-bar", os.Getenv("XDG_CONFIG_HOME"))) } return fmt.Sprintf("%s/.config/nwg-bar", os.Getenv("HOME")) } func getDataHome() string { if os.Getenv("XDG_DATA_HOME") != "" { return os.Getenv("XDG_DATA_HOME") } return "/usr/share/" } func createDir(dir string) { if _, err := os.Stat(dir); os.IsNotExist(err) { err := os.MkdirAll(dir, os.ModePerm) if err == nil { fmt.Println("Creating dir:", dir) } } } func pathExists(name string) bool { if _, err := os.Stat(name); err != nil { if os.IsNotExist(err) { return false } } return true } func copyFile(src, dst string) error { fmt.Println("Copying file:", dst) var err error var srcfd *os.File var dstfd *os.File var srcinfo os.FileInfo if srcfd, err = os.Open(src); err != nil { return err } defer srcfd.Close() if dstfd, err = os.Create(dst); err != nil { return err } defer dstfd.Close() if _, err = io.Copy(dstfd, srcfd); err != nil { return err } if srcinfo, err = os.Stat(src); err != nil { return err } return os.Chmod(dst, srcinfo.Mode()) } func mapOutputs() (map[string]*gdk.Monitor, error) { result := make(map[string]*gdk.Monitor) ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel() client, err := sway.New(ctx) if err != nil { return nil, err } outputs, err := client.GetOutputs(ctx) if err != nil { return nil, err } display, err := gdk.DisplayGetDefault() if err != nil { return nil, err } num := display.GetNMonitors() for i := 0; i < num; i++ { monitor, _ := display.GetMonitor(i) geometry := monitor.GetGeometry() // assign output to monitor on the basis of the same x, y coordinates for _, output := range outputs { if int(output.Rect.X) == geometry.GetX() && int(output.Rect.Y) == geometry.GetY() { result[output.Name] = monitor } } } return result, nil } /* Window on-leave-notify event hides the dock with glib Timeout 500 ms. We might have left the window by accident, so let's clear the timeout if window re-entered. Furthermore - hovering a button triggers window on-leave-notify event, and the timeout needs to be cleared as well. */ func cancelClose() { if src > 0 { glib.SourceRemove(src) src = 0 } } func createPixbuf(icon string, size int) (*gdk.Pixbuf, error) { if strings.HasPrefix(icon, "/") { pixbuf, err := gdk.PixbufNewFromFileAtSize(icon, size, size) if err != nil { fmt.Println(err) return nil, err } return pixbuf, nil } iconTheme, err := gtk.IconThemeGetDefault() if err != nil { log.Fatal("Couldn't get default theme: ", err) } pixbuf, err := iconTheme.LoadIcon(icon, size, gtk.ICON_LOOKUP_FORCE_SIZE) if err != nil { return nil, err } return pixbuf, nil } func launch(command string) { // trim % and everything afterwards if strings.Contains(command, "%") { cutAt := strings.Index(command, "%") if cutAt != -1 { command = command[:cutAt-1] } } elements := strings.Split(command, " ") // find prepended env variables, if any envVarsNum := strings.Count(command, "=") var envVars []string cmdIdx := 0 lastEnvVarIdx := 0 if envVarsNum > 0 { for idx, item := range elements { if strings.Contains(item, "=") { lastEnvVarIdx = idx envVars = append(envVars, item) } } cmdIdx = lastEnvVarIdx + 1 } cmd := exec.Command(elements[cmdIdx], elements[1+cmdIdx:]...) // set env variables if len(envVars) > 0 { cmd.Env = os.Environ() cmd.Env = append(cmd.Env, envVars...) } msg := fmt.Sprintf("env vars: %s; command: '%s'; args: %s\n", envVars, elements[cmdIdx], elements[1+cmdIdx:]) println(msg) go cmd.Run() glib.TimeoutAdd(uint(150), func() bool { gtk.MainQuit() return false }) }