pax_global_header 0000666 0000000 0000000 00000000064 14555606530 0014523 g ustar 00root root 0000000 0000000 52 comment=c4da7d817747ad2f8cfda332d95bd4fc55eb6b34
nwg-bar-0.1.6/ 0000775 0000000 0000000 00000000000 14555606530 0013064 5 ustar 00root root 0000000 0000000 nwg-bar-0.1.6/.github/ 0000775 0000000 0000000 00000000000 14555606530 0014424 5 ustar 00root root 0000000 0000000 nwg-bar-0.1.6/.github/FUNDING.yml 0000664 0000000 0000000 00000000022 14555606530 0016233 0 ustar 00root root 0000000 0000000 github: nwg-piotr
nwg-bar-0.1.6/.gitignore 0000664 0000000 0000000 00000000455 14555606530 0015060 0 ustar 00root root 0000000 0000000 # 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/LICENSE 0000664 0000000 0000000 00000002101 14555606530 0014063 0 ustar 00root root 0000000 0000000 MIT 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/Makefile 0000664 0000000 0000000 00000001625 14555606530 0014530 0 ustar 00root root 0000000 0000000 PREFIX ?= /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.md 0000664 0000000 0000000 00000006642 14555606530 0014353 0 ustar 00root root 0000000 0000000 # 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.

[](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/ 0000775 0000000 0000000 00000000000 14555606530 0014331 5 ustar 00root root 0000000 0000000 nwg-bar-0.1.6/config/bar.json 0000664 0000000 0000000 00000000770 14555606530 0015774 0 ustar 00root root 0000000 0000000 [
{
"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.css 0000664 0000000 0000000 00000001110 14555606530 0016174 0 ustar 00root root 0000000 0000000 window {
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.mod 0000664 0000000 0000000 00000000646 14555606530 0014200 0 ustar 00root root 0000000 0000000 module 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.sum 0000664 0000000 0000000 00000005731 14555606530 0014225 0 ustar 00root root 0000000 0000000 github.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/ 0000775 0000000 0000000 00000000000 14555606530 0014331 5 ustar 00root root 0000000 0000000 nwg-bar-0.1.6/images/system-hibernate.svg 0000664 0000000 0000000 00000004444 14555606530 0020343 0 ustar 00root root 0000000 0000000
nwg-bar-0.1.6/images/system-lock-screen.svg 0000664 0000000 0000000 00000004524 14555606530 0020606 0 ustar 00root root 0000000 0000000
nwg-bar-0.1.6/images/system-log-out.svg 0000664 0000000 0000000 00000003554 14555606530 0017771 0 ustar 00root root 0000000 0000000
nwg-bar-0.1.6/images/system-reboot.svg 0000664 0000000 0000000 00000005331 14555606530 0017670 0 ustar 00root root 0000000 0000000
nwg-bar-0.1.6/images/system-shutdown.svg 0000664 0000000 0000000 00000003535 14555606530 0020255 0 ustar 00root root 0000000 0000000
nwg-bar-0.1.6/images/system-suspend.svg 0000664 0000000 0000000 00000003770 14555606530 0020064 0 ustar 00root root 0000000 0000000
nwg-bar-0.1.6/main.go 0000664 0000000 0000000 00000020411 14555606530 0014335 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000010460 14555606530 0014554 0 ustar 00root root 0000000 0000000 package 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
})
}