79 Commits
v0.4.5 ... main

Author SHA1 Message Date
85320215be remove search bar 2025-08-06 03:41:42 +02:00
f35903bfb7 allow for CUSTOM_DATA_DIRS variable 2025-08-05 03:58:33 +02:00
c8f8e27a65 only serach in custom director.
hide&wait for the program to exit.
2024-12-20 04:17:36 +01:00
Piotr Miller
4110554466 Merge pull request #141 from msmafra/patch-1
Update main.go
2024-12-14 00:56:03 +01:00
Marcelo dos Santos Mafra
7d1242d6ce Update main.go
Very little misspell
2024-12-13 16:37:34 -03:00
Piotr Miller
953239bb5c Merge pull request #140 from nwg-piotr/diamond
Migrate to diamondburned gtk3 bindings
2024-12-01 01:16:41 +01:00
piotr
2136a6165c update comments 2024-12-01 01:13:10 +01:00
piotr
a752be7434 update README.md 2024-12-01 01:10:23 +01:00
piotr
517054f013 update get 2024-11-29 00:34:06 +01:00
piotr
8b0d4f943b sort desktopEntries case insensitive 2024-11-29 00:21:20 +01:00
piotr
5c839eb576 userDirButton -> connect "activate" signal 2024-11-28 03:13:03 +01:00
piotr
68f42b7569 disallow userDirButton parent selection 2024-11-28 00:46:10 +01:00
piotr
725ce44058 fix gtk-theme-name detection 2024-11-27 03:20:25 +01:00
piotr
2d755f609d migrate to diamondburned GTK bindings 2024-11-27 03:05:46 +01:00
Piotr Miller
423adcd096 Merge pull request #138 from Vescrity/main
Fix env XDG_DATA_DIRS makes catagory disappear
2024-11-21 00:20:36 +01:00
vescrity
1b61a42b10 Fix env XDG_DATA_DIRS makes catagory disappear 2024-11-19 19:44:09 +08:00
piotr
c0eb0965a8 update README.md 2024-10-26 00:58:25 +02:00
piotr
c940139fd9 add default styling for #math-result label #134 2024-10-26 00:43:34 +02:00
piotr
10b1b2a1e6 Merge remote-tracking branch 'origin/main' 2024-10-26 00:38:44 +02:00
piotr
b4b7d3486a add CSS ID to math result label #134 2024-10-26 00:38:20 +02:00
Piotr Miller
61a5595d84 Merge pull request #132 from msmafra/main
Update README.md
2024-10-15 11:27:46 +02:00
Marcelo dos Santos Mafra
cb44c7215f Update README.md
nwg-drawer seems to look in its own folder ~/.config/nwg-drawer for preferred-apps.json and excluded-dirs and not nwg-panel's folder
2024-10-11 17:59:30 -03:00
piotr
e7fafebf55 bump to 0.5.2 2024-10-09 02:12:19 +02:00
piotr
702cbb1c5c update golang.org/x/sys 2024-10-09 02:07:51 +02:00
piotr
7de2854041 set gtk-layer-shell namespace 2024-10-09 02:06:30 +02:00
Piotr Miller
be6b71673d Merge pull request #131 from nwg-piotr/calc
add a simple calculator in the search box
2024-10-03 01:20:01 +02:00
piotr
9c48c585b7 add rounded corners 2024-10-03 01:10:35 +02:00
piotr
3977c4fc7c go -> 1.23 2024-10-03 01:05:14 +02:00
piotr
46a526ced8 close result win on btn release 2024-10-03 01:04:53 +02:00
piotr
12da77fbfc update README.md 2024-10-03 00:48:23 +02:00
piotr
484c6ccb29 add comments 2024-10-03 00:33:54 +02:00
piotr
7aac5a4f52 add expression calculator 2024-09-30 22:09:56 +02:00
Piotr Miller
44e4c95900 Merge pull request #130 from nwg-piotr/backports
Merge pull request #129 from nwg-piotr/main
2024-09-22 14:05:20 +02:00
Piotr Miller
86a3398bc3 Merge pull request #129 from nwg-piotr/main
A bunch of fixes
2024-09-22 14:04:33 +02:00
piotr
c4a268332a bump to 0.5.0 2024-09-22 13:57:01 +02:00
piotr
e77d189f4a println -> log.Debug 2024-09-22 13:54:32 +02:00
piotr
e3cb0af2bd println -> log.Debug 2024-09-22 13:51:46 +02:00
piotr
1015f4c48f fix hyprclt & mapping output->monitor 2024-09-22 13:44:02 +02:00
piotr
86c92328cd update dependencies 2024-09-22 13:29:07 +02:00
piotr
4afa9f1aa3 add riverctl spawn 2024-09-22 13:26:18 +02:00
Piotr Miller
0bce8cf971 Merge pull request #127 from mattkae/main
Update gotk3 to the latest version to avoid build failures
2024-08-11 22:20:16 +02:00
Matthew Kosarek
e6a1130524 Update gotk3 to the latest version to avoid build failures 2024-08-11 15:59:14 -04:00
Piotr Miller
433c05ca1e Merge pull request #125 from nwg-piotr/icon-theme
update README.md
2024-06-29 20:20:34 +02:00
piotr
cd06a981cb update README.md 2024-06-29 20:19:12 +02:00
Piotr Miller
0f6f92433e Merge pull request #124 from nwg-piotr/icon-theme
Add the `-pbuseicontheme` flagIcon theme
2024-06-29 03:01:20 +02:00
piotr
3ffd09b5d7 add more desc 2024-06-29 02:40:01 +02:00
piotr
071841da46 bump to 0.4.9 2024-06-29 02:38:43 +02:00
piotr
c7a0b564c0 improve description 2024-06-29 02:34:41 +02:00
piotr
6862711010 add pbUseIconTheme flag #122 2024-06-29 02:32:27 +02:00
piotr
6d671062df change logging level 2024-06-17 02:16:52 +02:00
piotr
9aebff5d98 fix typos 2024-06-17 02:10:03 +02:00
piotr
051590ce4b revert gotk3 to v0.6.3 2024-06-17 02:03:39 +02:00
piotr
16be4ff00d bump to 0.4.8 2024-06-17 02:01:20 +02:00
piotr
9f0f708cae Merge remote-tracking branch 'origin/main' 2024-06-17 01:53:43 +02:00
piotr
4e04e7a4f4 update dependencies 2024-06-17 01:53:25 +02:00
Piotr Miller
85a0b87024 Merge pull request #121 from RRRRRm/open_close
Add -open and -close options
2024-06-17 01:52:21 +02:00
RRRRRm
6649dcefdb Correct the default terminal 2024-06-17 00:31:52 +01:00
Zijia Xiong
e501038c64 Merge branch 'open_close' of github.com:RRRRRm/nwg-drawer into open_close 2024-06-16 19:04:31 +01:00
Zijia Xiong
008f31278d Fix logic about receive signals 2024-06-16 19:01:26 +01:00
RRRRRm
69e3b06b39 Update README.md,
Add some information about new flags `-open` and `-close`
2024-06-16 18:35:01 +01:00
Zijia Xiong
99765402d7 Change usage of SIGINT to SIGRTMIN+3 2024-06-14 00:48:10 +01:00
Zijia Xiong
3fb70fc0cb Fix bug of -open and -close 2024-06-13 23:30:58 +01:00
Zijia Xiong
04a04c1417 Add -close and -open options. 2024-06-13 19:09:33 +01:00
Piotr Miller
a54a56c4b9 Update FUNDING.yml 2024-02-16 13:17:28 +01:00
piotr
0cd48d1631 unhardcode desktop-directories path 2024-02-08 03:12:17 +01:00
piotr
943ca39055 update README.md 2024-02-08 02:51:16 +01:00
piotr
42b279ee02 update README.md 2024-02-08 02:49:57 +01:00
Piotr Miller
42f15ec2cb Merge pull request #116 from nwg-piotr/data
un-hardcode data dir
2024-02-08 02:35:16 +01:00
piotr
936f793be4 update README.md 2024-02-08 02:28:21 +01:00
piotr
b59058c5c2 focus 1st power button on Tab key 2024-02-08 02:23:34 +01:00
piotr
3ea4a7824c bump to 0.4.7 2024-02-08 01:32:37 +01:00
piotr
701d4e62f2 unhardcode data dir again #115 2024-02-08 01:31:22 +01:00
piotr
d4996af84a unhardcode data dir #115 2024-02-08 01:20:03 +01:00
piotr
71ce3b5d75 Merge remote-tracking branch 'origin/main' 2024-02-06 03:40:14 +01:00
piotr
b056b4c436 bump to 0.4.6 2024-02-06 03:39:35 +01:00
Piotr Miller
42b55d41df Merge pull request #113 from nwg-piotr/touch
Don't launch if the window has been scrolled
2024-02-06 03:38:09 +01:00
piotr
ec0ce767d9 get proper adjustment 2024-02-06 03:35:29 +01:00
piotr
ec3caa02d2 add comments #110 2024-02-06 03:33:12 +01:00
piotr
6b39eba14c check if window scrolled #110 2024-02-06 03:21:42 +01:00
10 changed files with 582 additions and 368 deletions

1
.github/FUNDING.yml vendored
View File

@@ -1 +1,2 @@
github: nwg-piotr
liberapay: nwg

View File

@@ -1,11 +1,11 @@
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/diamondburned/gotk4/pkg/gdk/v3
go get github.com/diamondburned/gotk4/pkg/glib/v2
go get github.com/diamondburned/gotk4/pkg/gtk/v3
go get github.com/diamondburned/gotk4-layer-shell/pkg/gtklayershell
go get github.com/joshuarubin/go-sway
go get github.com/allan-simon/go-singleinstance
go get "github.com/sirupsen/logrus"
go get github.com/sirupsen/logrus
go get github.com/fsnotify/fsnotify
build:

View File

@@ -13,9 +13,9 @@ and for files in XDG user directories. The grid view may also be filtered by cat
You may pin applications by right-clicking them. Pinned items will appear above the application grid. Right-click
a pinned item to unpin it. The pinned items cache is shared with [nwg-menu](https://github.com/nwg-piotr/nwg-menu).
Below the grid there is the power bar - a row of buttons to lock the screen, exit the compositor, reboot, suspend and power
the machine off. For each button to appear, you need to provide a corresponding command. See "Command line arguments"
below.
Below the grid there is the **power bar** - a row of buttons to lock the screen, exit the compositor, reboot, suspend
and power the machine off. For each button to appear, you need to provide a corresponding command. See "Command line
arguments" below. If the power bar is present, pressing **Tab** will move focus to its first button.
<img src="https://github.com/nwg-piotr/nwg-drawer/assets/20579136/8f4eacb4-5395-4350-889b-a9037aa34f08" width=640 alt="screenshot"><br>
@@ -27,7 +27,7 @@ To close the window w/o running a program, you may use the `Esc` key, or right-c
### Dependencies
- go >=1.20 (just to build)
- go
- gtk3
- gtk-layer-shell
- xdg-utils
@@ -51,9 +51,11 @@ confirmed to work well with the program. Also see **Files** below.
```text
$ nwg-drawer -h
Usage of /tmp/go-build3511850078/b001/exe/nwg-drawer:
Usage of nwg-drawer:
-c uint
number of Columns (default 6)
-close
close drawer of existing instance
-d Turn on Debug messages
-fm string
File Manager (default "thunar")
@@ -86,6 +88,8 @@ Usage of /tmp/go-build3511850078/b001/exe/nwg-drawer:
Disable file search
-o string
name of the Output to display the drawer on (sway & Hyprland only)
-open
open drawer of existing instance
-ovl
use OVerLay layer
-pbexit string
@@ -97,9 +101,11 @@ Usage of /tmp/go-build3511850078/b001/exe/nwg-drawer:
-pbreboot string
command for the Reboot power bar icon
-pbsize int
power bar icon size (default 64)
power bar icon size (only works w/ built-in icons) (default 64)
-pbsleep string
command for the sleep power bar icon
-pbuseicontheme
use icon theme instead of built-in icons in power bar
-r Leave the program resident in memory
-s string
Styling: css file name (default "drawer.css")
@@ -137,6 +143,15 @@ this should be a little bit faster.
Running a resident instance should speed up use of the drawer significantly. Pay attention to the fact, that you
need to `pkill -f nwg-drawer` and reload the compositor to apply any new arguments!
If you want to explicitly specify commands to open and close the resident instance, which can be helpful for touchpad gestures, please use the `-open` and `-close` parameters. Similarly, some signals can also be use: pkill -USR2 nwg-drawer to open and pkill -SIGRTMIN+3 nwg-drawer to close.
For a MacOS-style three-finger pinch:
```text
bindgesture pinch:4:inward exec pkill -SIGUSR2 nwg-drawer
bindgesture pinch:4:outward exec pkill -SIGRTMIN+3 nwg-drawer
```
## Logging
In case you encounter an issue, you may need debug messages. If you use the resident instance, you'll see nothing
@@ -157,7 +172,7 @@ Edit `~/.config/nwg-drawer/drawer.css` to your taste.
When the search phrase is at least 3 characters long, your XDG user directories are being searched.
Use the **left mouse button** to open a file with the `xdg-open` command. As configuring file associations for it is
PITA, you may override them, by creating the `~/.config/nwg-panel/preferred-apps.json` file with your own definitions.
PITA, you may override them, by creating the `~/.config/nwg-drawer/preferred-apps.json` file with your own definitions.
### Sample `preferred-apps.json` file content
@@ -184,21 +199,36 @@ I've noy yet tried other file managers.
### File search exclusions
You may want to exclude some paths inside your XDG user directories from searching. If so, define exclusions in the
`~/.config/nwg-panel/excluded-dirs` file, e.g. like this:
`~/.config/nwg-drawer/excluded-dirs` file, e.g. like this:
```text
# exclude all paths containing 'node_modules'
node_modules
```
### Calculations in the search box
If the search box is not empty, and you press Enter, the search box content will be evaluated as an arithmetic operation.
If the result is not an error, it will be displayed in a small window, and copied to the clipboard with wl-copy.
Press any key to close the window.
You may change the result label styling e.g. like this:
```css
/* math operation result label */
#math-label {
font-weight: bold;
font-size: 16px
}
```
## Credits
This program uses some great libraries:
- [gotk3](https://github.com/gotk3/gotk3) Copyright (c) 2013-2014 Conformal Systems LLC,
Copyright (c) 2015-2018 gotk3 contributors
- [gotk3-layershell](https://github.com/dlasky/gotk3-layershell) by [@dlasky](https://github.com/dlasky/gotk3-layershell/commits?author=dlasky) - many thanks for writing this software, and for patience with my requests!
- [gotk4](https://github.com/diamondburned/gotk4) by [diamondburned](https://github.com/diamondburned) released under [GNU Affero General Public License v3.0](https://github.com/diamondburned/gotk4/blob/4/LICENSE.md)
- [go-sway](https://github.com/joshuarubin/go-sway) Copyright (c) 2019 Joshua Rubin
- [go-singleinstance](github.com/allan-simon/go-singleinstance) Copyright (c) 2015 Allan Simon
- [logrus](https://github.com/sirupsen/logrus) Copyright (c) 2014 Simon Eskildsen
- [fsnotify](https://github.com/fsnotify/fsnotify) Copyright (c) 2012-2019 fsnotify Authors
- [expr](https://github.com/expr-lang/expr) Copyright (c) 2018 Anton Medvedev

View File

@@ -1,5 +1,5 @@
window {
background-color: rgba (36, 47, 79, 0.95);
background-color: rgba(36, 47, 79, 0.95);
color: #eeeeee
}
@@ -32,3 +32,9 @@ button:hover {
border: 1px dotted gray;
border-radius: 15px
}
/* math operation result label */
#math-label {
font-weight: bold;
font-size: 16px
}

15
go.mod
View File

@@ -1,19 +1,22 @@
module github.com/nwg-piotr/nwg-drawer
go 1.21
go 1.23
require (
github.com/allan-simon/go-singleinstance v0.0.0-20210120080615-d0997106ab37
github.com/dlasky/gotk3-layershell v0.0.0-20230802002603-b0c42cd8474f
github.com/fsnotify/fsnotify v1.7.0
github.com/gotk3/gotk3 v0.6.3
github.com/diamondburned/gotk4-layer-shell/pkg v0.0.0-20240109211357-6efa9f6dc438
github.com/diamondburned/gotk4/pkg v0.3.1
github.com/expr-lang/expr v1.16.9
github.com/fsnotify/fsnotify v1.8.0
github.com/joshuarubin/go-sway v1.2.0
github.com/sirupsen/logrus v1.9.3
)
require (
github.com/KarpelesLab/weak v0.1.1 // indirect
github.com/joshuarubin/lifecycle v1.1.4 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/sync v0.6.0 // indirect
golang.org/x/sys v0.16.0 // indirect
go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6 // indirect
golang.org/x/sync v0.9.0 // indirect
golang.org/x/sys v0.27.0 // indirect
)

45
go.sum
View File

@@ -1,21 +1,20 @@
github.com/KarpelesLab/weak v0.1.1 h1:fNnlPo3aypS9tBzoEQluY13XyUfd/eWaSE/vMvo9s4g=
github.com/KarpelesLab/weak v0.1.1/go.mod h1:pzXsWs5f2bf+fpgHayTlBE1qJpO3MpJKo5sRaLu1XNw=
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-20221218201547-1f6674a3f872 h1:16qcNl+UgbvudN7wPv+zq4mmDSYJWdLv5jbVhS7+OVI=
github.com/dlasky/gotk3-layershell v0.0.0-20221218201547-1f6674a3f872/go.mod h1:JHLx2Wz4mAPVwn4PFhC69ydwyHP4A3wQvlg7HKVVc1U=
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/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/diamondburned/gotk4-layer-shell/pkg v0.0.0-20240109211357-6efa9f6dc438 h1:Ymnl4B+Fn4srLxXbRV2RY1iHT2SH3oAkOfxeEeMI3Fg=
github.com/diamondburned/gotk4-layer-shell/pkg v0.0.0-20240109211357-6efa9f6dc438/go.mod h1:AjrxxF6teeNWgaEg0zIUwoqFtXlVTHlEGZvrOn7RXaQ=
github.com/diamondburned/gotk4/pkg v0.3.1 h1:uhkXSUPUsCyz3yujdvl7DSN8jiLS2BgNTQE95hk6ygg=
github.com/diamondburned/gotk4/pkg v0.3.1/go.mod h1:DqeOW+MxSZFg9OO+esk4JgQk0TiUJJUBfMltKhG+ub4=
github.com/expr-lang/expr v1.16.9 h1:WUAzmR0JNI9JCiF0/ewwHB1gmcGw5wW7nWt8gc6PpCI=
github.com/expr-lang/expr v1.16.9/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
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/gotk3/gotk3 v0.6.3 h1:+Ke4WkM1TQUNOlM2TZH6szqknqo+zNbX3BZWVXjSHYw=
github.com/gotk3/gotk3 v0.6.3/go.mod h1:/hqFpkNa9T3JgNAE2fLvCdov7c5bw//FHNZrZ3Uv9/Q=
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
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=
@@ -33,21 +32,19 @@ 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=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6 h1:lGdhQUN/cnWdSH3291CUuxSEqc+AsGTiDxPP3r2J0l4=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.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=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

370
main.go
View File

@@ -12,26 +12,31 @@ import (
"syscall"
"time"
log "github.com/sirupsen/logrus"
"github.com/diamondburned/gotk4-layer-shell/pkg/gtklayershell"
"github.com/expr-lang/expr"
"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"
log "github.com/sirupsen/logrus"
"github.com/diamondburned/gotk4/pkg/gdk/v3"
"github.com/diamondburned/gotk4/pkg/glib/v2"
"github.com/diamondburned/gotk4/pkg/gtk/v3"
)
const version = "0.4.5"
const version = "0.6.0"
var (
appDirs []string
configDirectory string
dataDirectory string
pinnedFile string
pinned []string
id2entry map[string]desktopEntry
preferredApps map[string]interface{}
exclusions []string
hyprlandMonitors []monitor
beenScrolled bool
firstPowerBtn *gtk.Button
)
var categoryNames = [...]string{
@@ -108,10 +113,10 @@ var desktopEntries []desktopEntry
// UI elements
var (
win *gtk.Window
resultWindow *gtk.ScrolledWindow
fileSearchResults []string
searchEntry *gtk.SearchEntry
win *gtk.Window
resultWindow *gtk.ScrolledWindow
fileSearchResults []string
//searchEntry *gtk.SearchEntry
phrase string
fileSearchResultFlowBox *gtk.FlowBox
userDirsMap map[string]string
@@ -152,6 +157,8 @@ var targetOutput = flag.String("o", "", "name of the Output to display the drawe
var displayVersion = flag.Bool("v", false, "display Version information")
var keyboard = flag.Bool("k", false, "set GTK layer shell Keyboard interactivity to 'on-demand' mode")
var overlay = flag.Bool("ovl", false, "use OVerLay layer")
var flagDrawerOpen = flag.Bool("open", false, "open drawer of existing instance")
var flagDrawerClose = flag.Bool("close", false, "close drawer of existing instance")
var gtkTheme = flag.String("g", "", "GTK theme name")
var gtkIconTheme = flag.String("i", "", "GTK icon theme name")
var iconSize = flag.Int("is", 64, "Icon Size")
@@ -166,7 +173,7 @@ 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", defaultTermIfBlank(os.Getenv("TERM"), "foot"), "Terminal emulator")
var wm = flag.String("wm", "", "use swaymsg exec (with 'sway' argument) or hyprctl dispatch exec (with 'hyprland') to launch programs")
var wm = flag.String("wm", "", "use swaymsg exec (with 'sway' argument) or hyprctl dispatch exec (with 'hyprland') or riverctl spawn (with 'river') to launch programs")
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")
@@ -176,7 +183,8 @@ var pbLock = flag.String("pblock", "", "command for the Lock power bar icon")
var pbPoweroff = flag.String("pbpoweroff", "", "command for the Poweroff power bar icon")
var pbReboot = flag.String("pbreboot", "", "command for the Reboot power bar icon")
var pbSleep = flag.String("pbsleep", "", "command for the sleep power bar icon")
var pbSize = flag.Int("pbsize", 64, "power bar icon size")
var pbSize = flag.Int("pbsize", 64, "power bar icon size (only works w/ built-in icons)")
var pbUseIconTheme = flag.Bool("pbuseicontheme", false, "use icon theme instead of built-in icons in power bar")
var debug = flag.Bool("d", false, "Turn on Debug messages")
func main() {
@@ -198,8 +206,10 @@ func main() {
// v0.2: we also need to support SIGUSR from now on
showWindowChannel := make(chan interface{}, 1)
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, syscall.SIGTERM, syscall.SIGUSR1)
const (
SIG25 = syscall.Signal(0x25) // Which is SIGRTMIN+3 on Linux, it's not used by the system
)
signal.Notify(signalChan, syscall.SIGTERM, syscall.SIGUSR1, syscall.SIGUSR2, SIG25)
go func() {
for {
s := <-signalChan
@@ -207,7 +217,7 @@ func main() {
case syscall.SIGTERM:
log.Info("SIGTERM received, bye bye")
gtk.MainQuit()
case syscall.SIGUSR1:
case syscall.SIGUSR1: // toggle drawer
if *resident {
// As win.Show() called from inside a goroutine randomly crashes GTK,
// let's just set e helper variable here. We'll be checking it with glib.TimeoutAdd.
@@ -222,6 +232,23 @@ func main() {
log.Info("SIGUSR1 received, and I'm not resident, bye bye")
gtk.MainQuit()
}
case syscall.SIGUSR2: // open drawer
if *resident {
log.Debug("SIGUSR2 received, showing the window")
showWindowChannel <- struct{}{}
} else {
log.Info("SIGUSR2 received, and I'm not resident but I'm still here, doing nothing")
}
case SIG25: // close drawer
if *resident {
log.Debug("SIG25 received, hiding the window")
if win.IsVisible() {
restoreStateAndHide()
}
} else {
log.Info("A signal received, and I'm not resident, bye bye")
gtk.MainQuit()
}
default:
log.Infof("Unknown signal: %s", s.String())
}
@@ -230,10 +257,10 @@ func main() {
// If running instance found, we want it to show the window. The new instance will send SIGUSR1 and die
// (equivalent of `pkill -USR1 nwg-drawer`).
// Otherwise the command may behave in two ways:
// 1. kill the running non-residennt instance and exit;
// Otherwise, the command may behave in two ways:
// 1. kill the running non-resident instance and exit;
// 2. die if a resident instance found.
lockFilePath := path.Join(dataDir(), "nwg-drawer.lock")
lockFilePath := path.Join(dataHome(), "nwg-drawer.lock")
lockFile, err := singleinstance.CreateLockFile(lockFilePath)
if err != nil {
pid, err := readTextFile(lockFilePath)
@@ -243,8 +270,17 @@ func main() {
if *resident {
log.Warnf("Resident instance already running (PID %v)", i)
} else {
log.Infof("Showing resident instance (PID %v)", i)
err := syscall.Kill(i, syscall.SIGUSR1)
var err error
if *flagDrawerClose {
log.Infof("Closing resident instance (PID %v)", i)
err = syscall.Kill(i, SIG25)
} else if *flagDrawerOpen {
log.Infof("Showing resident instance (PID %v)", i)
err = syscall.Kill(i, syscall.SIGUSR2)
} else {
log.Infof("Toggling resident instance (PID %v)", i)
err = syscall.Kill(i, syscall.SIGUSR1)
}
if err != nil {
return
}
@@ -265,6 +301,7 @@ func main() {
// ENVIRONMENT
configDirectory = configDir()
dataDirectory = dataDir()
// Placing the drawer config files in the nwg-panel config directory was a mistake.
// Let's move them to their own location.
@@ -290,7 +327,7 @@ func main() {
// Copy default style sheet if not found
if !pathExists(filepath.Join(configDirectory, "drawer.css")) {
err := copyFile("/usr/share/nwg-drawer/drawer.css", filepath.Join(configDirectory, "drawer.css"))
err := copyFile(filepath.Join(dataDirectory, "drawer.css"), filepath.Join(configDirectory, "drawer.css"))
if err != nil {
log.Errorf("Failed copying 'drawer.css' file: %s", err)
}
@@ -351,91 +388,79 @@ func main() {
}
// USER INTERFACE
gtk.Init(nil)
gtk.Init()
settings, _ := gtk.SettingsGetDefault()
settings := gtk.SettingsGetDefault()
if *gtkTheme != "" {
err = settings.SetProperty("gtk-theme-name", *gtkTheme)
if err != nil {
log.Error("Unable to set theme:", err)
} else {
log.Infof("User demanded theme: %s", *gtkTheme)
}
settings.SetObjectProperty("gtk-theme-name", *gtkTheme)
log.Infof("User demanded theme: %s", *gtkTheme)
} else {
err := settings.SetProperty("gtk-application-prefer-dark-theme", true)
if err != nil {
log.Error("Error setting 'gtk-application-prefer-dark-theme' property")
return
}
settings.SetObjectProperty("gtk-application-prefer-dark-theme", true)
log.Info("Preferring dark theme variants")
}
if *gtkIconTheme != "" {
err = settings.SetProperty("gtk-icon-theme-name", *gtkIconTheme)
if err != nil {
log.Error("Unable to set icon theme:", err)
} else {
log.Infof("User demanded icon theme: %s", *gtkIconTheme)
}
settings.SetObjectProperty("gtk-icon-theme-name", *gtkIconTheme)
log.Infof("User demanded icon theme: %s", *gtkIconTheme)
}
cssProvider, _ := gtk.CssProviderNew()
cssProvider := gtk.NewCSSProvider()
err = cssProvider.LoadFromPath(*cssFileName)
if err != nil {
log.Errorf("ERROR: %s css file not found or erroneous. Using GTK styling.", *cssFileName)
log.Errorf("%s", err)
} else {
log.Info(fmt.Sprintf("Using style from %s", *cssFileName))
screen, _ := gdk.ScreenGetDefault()
gtk.AddProviderForScreen(screen, cssProvider, gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
screen := gdk.ScreenGetDefault()
gtk.StyleContextAddProviderForScreen(screen, cssProvider, gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
}
win, err = gtk.WindowNew(gtk.WINDOW_TOPLEVEL)
win = gtk.NewWindow(gtk.WindowToplevel)
if err != nil {
log.Fatal("Unable to create window:", err)
}
if wayland() {
layershell.InitForWindow(win)
gtklayershell.InitForWindow(win)
gtklayershell.SetNamespace(win, "nwg-drawer")
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()
fmt.Println(">>>", output2mon)
log.Debugf("output2mon: %v", output2mon)
if err == nil {
monitor := output2mon[*targetOutput]
layershell.SetMonitor(win, monitor)
mon := output2mon[*targetOutput]
gtklayershell.SetMonitor(win, mon)
} else {
log.Errorf("%s", err)
}
}
layershell.SetAnchor(win, layershell.LAYER_SHELL_EDGE_BOTTOM, true)
layershell.SetAnchor(win, layershell.LAYER_SHELL_EDGE_TOP, true)
layershell.SetAnchor(win, layershell.LAYER_SHELL_EDGE_LEFT, true)
layershell.SetAnchor(win, layershell.LAYER_SHELL_EDGE_RIGHT, true)
gtklayershell.SetAnchor(win, gtklayershell.LayerShellEdgeBottom, true)
gtklayershell.SetAnchor(win, gtklayershell.LayerShellEdgeTop, true)
gtklayershell.SetAnchor(win, gtklayershell.LayerShellEdgeLeft, true)
gtklayershell.SetAnchor(win, gtklayershell.LayerShellEdgeRight, true)
if *overlay {
layershell.SetLayer(win, layershell.LAYER_SHELL_LAYER_OVERLAY)
layershell.SetExclusiveZone(win, -1)
gtklayershell.SetLayer(win, gtklayershell.LayerShellLayerOverlay)
gtklayershell.SetExclusiveZone(win, -1)
} else {
layershell.SetLayer(win, layershell.LAYER_SHELL_LAYER_TOP)
gtklayershell.SetLayer(win, gtklayershell.LayerShellLayerTop)
}
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)
gtklayershell.SetMargin(win, gtklayershell.LayerShellEdgeTop, *marginTop)
gtklayershell.SetMargin(win, gtklayershell.LayerShellEdgeLeft, *marginLeft)
gtklayershell.SetMargin(win, gtklayershell.LayerShellEdgeRight, *marginRight)
gtklayershell.SetMargin(win, gtklayershell.LayerShellEdgeBottom, *marginBottom)
if *keyboard {
log.Info("Setting GTK layer shell keyboard mode to: on-demand")
layershell.SetKeyboardMode(win, layershell.LAYER_SHELL_KEYBOARD_MODE_ON_DEMAND)
gtklayershell.SetKeyboardMode(win, gtklayershell.LayerShellKeyboardModeOnDemand)
} else {
log.Info("Setting GTK layer shell keyboard mode to default: exclusive")
layershell.SetKeyboardMode(win, layershell.LAYER_SHELL_KEYBOARD_MODE_EXCLUSIVE)
gtklayershell.SetKeyboardMode(win, gtklayershell.LayerShellKeyboardModeExclusive)
}
}
@@ -445,12 +470,14 @@ func main() {
})
win.Connect("key-release-event", func(_ *gtk.Window, event *gdk.Event) bool {
key := &gdk.EventKey{Event: event}
if key.KeyVal() == gdk.KEY_Escape {
s, _ := searchEntry.GetText()
//key := &gdk.EventKey{Event: event}
key := event.AsKey()
if key.Keyval() == gdk.KEY_Escape {
//s := searchEntry.Text()
s := ""
if s != "" {
searchEntry.GrabFocus()
searchEntry.SetText("")
//searchEntry.GrabFocus()
//searchEntry.SetText("")
} else {
if !*resident {
gtk.MainQuit()
@@ -459,23 +486,43 @@ func main() {
}
}
return true
} else if key.Keyval() == gdk.KEY_Tab {
if firstPowerBtn != nil {
firstPowerBtn.GrabFocus()
}
} else if key.Keyval() == gdk.KEY_Return {
//s := searchEntry.Text()
s := ""
if s != "" {
// Check if the search box content is an arithmetic expression. If so, display the result
// and copy to the clipboard with wl-copy.
result, e := expr.Eval(s, nil)
if e == nil {
log.Debugf("Setting up mathemathical operation result window. Operation: %s, result: %v", s, result)
setUpOperationResultWindow(s, fmt.Sprintf("%v", result))
}
}
return true
}
return false
return true
})
win.Connect("key-press-event", func(_ *gtk.Window, event *gdk.Event) bool {
key := &gdk.EventKey{Event: event}
switch key.KeyVal() {
//key := &gdk.EventKey{Event: event}
key := event.AsKey()
switch key.Keyval() {
case gdk.KEY_downarrow, gdk.KEY_Up, gdk.KEY_Down, gdk.KEY_Left, gdk.KEY_Right, gdk.KEY_Tab,
gdk.KEY_Return, gdk.KEY_Page_Up, gdk.KEY_Page_Down, gdk.KEY_Home, gdk.KEY_End:
return false
default:
if !searchEntry.IsFocus() {
searchEntry.GrabFocusWithoutSelecting()
}
return false
//if !searchEntry.IsFocus() {
// searchEntry.GrabFocusWithoutSelecting()
//}
}
return false
})
/*
@@ -490,36 +537,49 @@ func main() {
}
// Set up UI
outerVBox, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
outerVBox := gtk.NewBox(gtk.OrientationVertical, 0)
win.Add(outerVBox)
searchBoxWrapper, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
outerVBox.PackStart(searchBoxWrapper, false, false, 10)
//searchBoxWrapper := gtk.NewBox(gtk.OrientationHorizontal, 0)
//outerVBox.PackStart(searchBoxWrapper, false, false, 10)
searchEntry = setUpSearchEntry()
searchEntry.SetMaxWidthChars(30)
searchBoxWrapper.PackStart(searchEntry, true, false, 0)
//searchEntry = setUpSearchEntry()
//searchEntry.SetMaxWidthChars(30)
//searchBoxWrapper.PackStart(searchEntry, true, false, 0)
if !*noCats {
categoriesWrapper, _ = gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
categoriesWrapper = gtk.NewBox(gtk.OrientationHorizontal, 0)
categoriesButtonBox := setUpCategoriesButtonBox()
categoriesWrapper.PackStart(categoriesButtonBox, true, false, 0)
outerVBox.PackStart(categoriesWrapper, false, false, 0)
}
pinnedWrapper, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
pinnedWrapper := gtk.NewBox(gtk.OrientationHorizontal, 0)
outerVBox.PackStart(pinnedWrapper, false, false, 0)
pinnedFlowBoxWrapper, _ = gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
pinnedFlowBoxWrapper = gtk.NewBox(gtk.OrientationHorizontal, 0)
outerVBox.PackStart(pinnedFlowBoxWrapper, false, false, 0)
pinnedFlowBox = setUpPinnedFlowBox()
resultWindow, _ = gtk.ScrolledWindowNew(nil, nil)
resultWindow.SetEvents(int(gdk.ALL_EVENTS_MASK))
resultWindow.SetPolicy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
resultWindow = gtk.NewScrolledWindow(nil, nil)
resultWindow.SetEvents(int(gdk.AllEventsMask))
resultWindow.SetPolicy(gtk.PolicyAutomatic, gtk.PolicyAutomatic)
// On touch screen we don't want the button-release-event to launch the app if the user just wanted to scroll the
// window. Let's forbid doing so if the content has been scrolled. We will reset the value on button-press-event.
// Resolves https://github.com/nwg-piotr/nwg-drawer/issues/110
vAdj := resultWindow.VAdjustment()
vAdj.Connect("value-changed", func() {
beenScrolled = true
})
hAdj := resultWindow.HAdjustment()
hAdj.Connect("value-changed", func() {
beenScrolled = true
})
resultWindow.Connect("button-release-event", func(_ *gtk.ScrolledWindow, event *gdk.Event) bool {
btnEvent := gdk.EventButtonNewFromEvent(event)
//btnEvent := gdk.EventButtonNewFromEvent(event)
btnEvent := event.AsButton()
if btnEvent.Button() == 3 {
if !*resident {
gtk.MainQuit()
@@ -532,83 +592,117 @@ func main() {
})
outerVBox.PackStart(resultWindow, true, true, 10)
resultsWrapper, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
resultsWrapper := gtk.NewBox(gtk.OrientationVertical, 0)
resultWindow.Add(resultsWrapper)
appSearchResultWrapper, _ = gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
appSearchResultWrapper = gtk.NewBox(gtk.OrientationVertical, 0)
resultsWrapper.PackStart(appSearchResultWrapper, false, false, 0)
appFlowBox = setUpAppsFlowBox(nil, "")
// Focus 1st pinned item if any, otherwise focus 1st found app icon
var button gtk.IWidget
if pinnedFlowBox.GetChildren().Length() > 0 {
button, err = pinnedFlowBox.GetChildAtIndex(0).GetChild()
var button gtk.Widget
if len(pinnedFlowBox.Children()) > 0 {
button = pinnedFlowBox.ChildAtIndex(0).Widget
} else {
button, err = appFlowBox.GetChildAtIndex(0).GetChild()
button = appFlowBox.ChildAtIndex(0).Widget
}
if err == nil {
button.ToWidget().GrabFocus()
button.GrabFocus()
}
userDirsMap = mapXdgUserDirs()
log.Debugf("User dirs map: %s", userDirsMap)
placeholder, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
placeholder := gtk.NewBox(gtk.OrientationVertical, 0)
resultsWrapper.PackStart(placeholder, true, true, 0)
placeholder.SetSizeRequest(20, 20)
if !*noFS {
wrapper, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
fileSearchResultWrapper, _ = gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
fileSearchResultWrapper.SetProperty("name", "files-box")
wrapper := gtk.NewBox(gtk.OrientationHorizontal, 0)
fileSearchResultWrapper = gtk.NewBox(gtk.OrientationHorizontal, 0)
fileSearchResultWrapper.SetObjectProperty("name", "files-box")
wrapper.PackStart(fileSearchResultWrapper, true, false, 0)
resultsWrapper.PackEnd(wrapper, false, false, 10)
}
// Power Button Bar
if *pbExit != "" || *pbLock != "" || *pbPoweroff != "" || *pbReboot != "" || *pbSleep != "" {
powerBarWrapper, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
outerVBox.PackStart(powerBarWrapper, false, false, 0)
powerButtonsWrapper, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
powerBarWrapper.PackStart(powerButtonsWrapper, true, false, 12)
if dataDirectory != "" {
if *pbExit != "" || *pbLock != "" || *pbPoweroff != "" || *pbReboot != "" || *pbSleep != "" {
powerBarWrapper := gtk.NewBox(gtk.OrientationHorizontal, 0)
outerVBox.PackStart(powerBarWrapper, false, false, 0)
powerButtonsWrapper := gtk.NewBox(gtk.OrientationHorizontal, 0)
powerBarWrapper.PackStart(powerButtonsWrapper, true, false, 12)
if *pbLock != "" {
btn := powerButton("/usr/share/nwg-drawer/img/lock.svg", *pbLock)
powerButtonsWrapper.PackStart(btn, true, false, 0)
}
if *pbExit != "" {
btn := powerButton("/usr/share/nwg-drawer/img/exit.svg", *pbExit)
powerButtonsWrapper.PackStart(btn, true, false, 0)
}
if *pbReboot != "" {
btn := powerButton("/usr/share/nwg-drawer/img/reboot.svg", *pbReboot)
powerButtonsWrapper.PackStart(btn, true, false, 0)
}
if *pbSleep != "" {
btn := powerButton("/usr/share/nwg-drawer/img/sleep.svg", *pbSleep)
powerButtonsWrapper.PackStart(btn, true, false, 0)
}
if *pbPoweroff != "" {
btn := powerButton("/usr/share/nwg-drawer/img/poweroff.svg", *pbPoweroff)
powerButtonsWrapper.PackStart(btn, true, false, 0)
if *pbPoweroff != "" {
btn := gtk.NewButton()
if !*pbUseIconTheme {
btn = powerButton(filepath.Join(dataDirectory, "img/poweroff.svg"), *pbPoweroff)
} else {
btn = powerButton("system-shutdown-symbolic", *pbPoweroff)
}
powerButtonsWrapper.PackEnd(btn, true, false, 0)
firstPowerBtn = btn
}
if *pbSleep != "" {
btn := gtk.NewButton()
if !*pbUseIconTheme {
btn = powerButton(filepath.Join(dataDirectory, "img/sleep.svg"), *pbSleep)
} else {
btn = powerButton("face-yawn-symbolic", *pbSleep)
}
powerButtonsWrapper.PackEnd(btn, true, false, 0)
firstPowerBtn = btn
}
if *pbReboot != "" {
btn := gtk.NewButton()
if !*pbUseIconTheme {
btn = powerButton(filepath.Join(dataDirectory, "img/reboot.svg"), *pbReboot)
} else {
btn = powerButton("system-reboot-symbolic", *pbReboot)
}
powerButtonsWrapper.PackEnd(btn, true, false, 0)
firstPowerBtn = btn
}
if *pbExit != "" {
btn := gtk.NewButton()
if !*pbUseIconTheme {
btn = powerButton(filepath.Join(dataDirectory, "img/exit.svg"), *pbExit)
} else {
btn = powerButton("system-log-out-symbolic", *pbExit)
}
powerButtonsWrapper.PackEnd(btn, true, false, 0)
firstPowerBtn = btn
}
if *pbLock != "" {
btn := gtk.NewButton()
if !*pbUseIconTheme {
btn = powerButton(filepath.Join(dataDirectory, "img/lock.svg"), *pbLock)
} else {
btn = powerButton("system-lock-screen-symbolic", *pbLock)
}
powerButtonsWrapper.PackEnd(btn, true, false, 0)
firstPowerBtn = btn
}
}
} else {
log.Warn("Couldn't find data dir, power bar icons unavailable")
}
statusLineWrapper, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
statusLineWrapper.SetProperty("name", "status-line-wrapper")
statusLineWrapper := gtk.NewBox(gtk.OrientationHorizontal, 0)
statusLineWrapper.SetObjectProperty("name", "status-line-wrapper")
outerVBox.PackStart(statusLineWrapper, false, false, 10)
statusLabel, _ = gtk.LabelNew(status)
statusLabel.SetProperty("name", "status-label")
statusLabel = gtk.NewLabel(status)
statusLabel.SetObjectProperty("name", "status-label")
statusLineWrapper.PackStart(statusLabel, true, false, 0)
win.ShowAll()
if !*noFS {
fileSearchResultWrapper.SetSizeRequest(appFlowBox.GetAllocatedWidth(), 1)
fileSearchResultWrapper.SetSizeRequest(appFlowBox.AllocatedWidth(), 1)
fileSearchResultWrapper.Hide()
}
if !*noCats {
categoriesWrapper.SetSizeRequest(1, categoriesWrapper.GetAllocatedHeight()*2)
categoriesWrapper.SetSizeRequest(1, categoriesWrapper.AllocatedHeight()*2)
}
if *resident {
win.Hide()
@@ -642,14 +736,14 @@ func main() {
fileSearchResultWrapper.Hide()
}
// focus 1st element
var button gtk.IWidget
if pinnedFlowBox.GetChildren().Length() > 0 {
button, err = pinnedFlowBox.GetChildAtIndex(0).GetChild()
var button gtk.Widget
if len(pinnedFlowBox.Children()) > 0 {
button = pinnedFlowBox.ChildAtIndex(0).Widget
} else {
button, err = appFlowBox.GetChildAtIndex(0).GetChild()
button = appFlowBox.ChildAtIndex(0).Widget
}
if err == nil {
button.ToWidget().GrabFocus()
button.GrabFocus()
}
}
@@ -680,20 +774,20 @@ func restoreStateAndHide() {
}
// clear search
searchEntry.SetText("")
//searchEntry.SetText("")
// clear category filter (in gotk3 it means: rebuild, as we have no filtering here)
// One day or another we'll add SetFilterFunction here; it was impossible on the gotk3 library
appFlowBox = setUpAppsFlowBox(nil, "")
for _, btn := range catButtons {
btn.SetImagePosition(gtk.POS_LEFT)
btn.SetImagePosition(gtk.PosLeft)
btn.SetSizeRequest(0, 0)
}
// scroll to the top
if resultWindow != nil {
resultWindow.GetVAdjustment().SetValue(0)
resultWindow.VAdjustment().SetValue(0)
}
t := time.Now()
log.Debugf(fmt.Sprintf("UI hidden and restored in the backgroud in %v ms", t.Sub(timeStart1).Milliseconds()))
log.Debugf(fmt.Sprintf("UI hidden and restored in the background in %v ms", t.Sub(timeStart1).Milliseconds()))
}

11
run_forever.sh Normal file
View File

@@ -0,0 +1,11 @@
#!/bin/bash
# Infinite loop to run the command
while true; do
echo "Starting nwg-drawer..."
bin/nwg-drawer -nocats -nofs -ovl -d
done &
# Return immediately after starting the loop
echo "The loop is running in the background."

165
tools.go
View File

@@ -5,8 +5,6 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/joshuarubin/go-sway"
log "github.com/sirupsen/logrus"
"io"
"io/fs"
"net"
@@ -20,22 +18,23 @@ import (
"syscall"
"time"
"github.com/gotk3/gotk3/gdk"
"github.com/gotk3/gotk3/gtk"
"github.com/joshuarubin/go-sway"
log "github.com/sirupsen/logrus"
"github.com/diamondburned/gotk4/pkg/gdk/v3"
"github.com/diamondburned/gotk4/pkg/gdkpixbuf/v2"
"github.com/diamondburned/gotk4/pkg/gtk/v3"
)
func wayland() bool {
return os.Getenv("WAYLAND_DISPLAY") != "" || os.Getenv("XDG_SESSION_TYPE") == "wayland"
}
func createPixbuf(icon string, size int) (*gdk.Pixbuf, error) {
iconTheme, err := gtk.IconThemeGetDefault()
if err != nil {
log.Fatal("Couldn't get default theme: ", err)
}
func createPixbuf(icon string, size int) (*gdkpixbuf.Pixbuf, error) {
iconTheme := gtk.IconThemeGetDefault()
if strings.Contains(icon, "/") {
pixbuf, err := gdk.PixbufNewFromFileAtSize(icon, size, size)
pixbuf, err := gdkpixbuf.NewPixbufFromFileAtSize(icon, size, size)
if err != nil {
log.Errorf("%s", err)
return nil, err
@@ -47,18 +46,18 @@ func createPixbuf(icon string, size int) (*gdk.Pixbuf, error) {
icon = strings.Split(icon, ".")[0]
}
pixbuf, err := iconTheme.LoadIcon(icon, size, gtk.ICON_LOOKUP_FORCE_SIZE)
pixbuf, err := iconTheme.LoadIcon(icon, size, gtk.IconLookupForceSize)
if err != nil {
if strings.HasPrefix(icon, "/") {
pixbuf, err := gdk.PixbufNewFromFileAtSize(icon, size, size)
if err != nil {
return nil, err
pixbuf, e := gdkpixbuf.NewPixbufFromFileAtSize(icon, size, size)
if e != nil {
return nil, e
}
return pixbuf, nil
}
pixbuf, err := iconTheme.LoadIcon(icon, size, gtk.ICON_LOOKUP_FORCE_SIZE)
pixbuf, err := iconTheme.LoadIcon(icon, size, gtk.IconLookupForceSize)
if err != nil {
return nil, err
}
@@ -173,7 +172,7 @@ func configHome() string {
return path.Join(os.Getenv("HOME"), ".config")
}
func dataDir() string {
func dataHome() string {
var dir string
if xdgData := os.Getenv("XDG_DATA_HOME"); xdgData != "" {
dir = path.Join(xdgData, "nwg-drawer")
@@ -181,7 +180,7 @@ func dataDir() string {
dir = path.Join(home, ".local/share/nwg-drawer")
}
log.Infof("Data dir: %s", dir)
log.Debugf("Data home: %s", dir)
createDir(dir)
return dir
@@ -197,7 +196,7 @@ func createDir(dir string) {
}
func copyFile(src, dst string) error {
log.Infof("Copying file: %s", dst)
log.Infof("Copying: '%s' => '%s'", src, dst)
var err error
var srcfd *os.File
@@ -233,37 +232,55 @@ func copyFile(src, dst string) error {
return os.Chmod(dst, srcinfo.Mode())
}
func getAppDirs() []string {
var dirs []string
home := os.Getenv("HOME")
xdgDataHome := os.Getenv("XDG_DATA_HOME")
func dataDir() string {
xdgDataDirs := os.Getenv("XDG_DATA_DIRS")
if xdgDataDirs == "" {
xdgDataDirs = "/usr/local/share/:/usr/share/"
}
if xdgDataHome != "" {
dirs = append(dirs, filepath.Join(xdgDataHome, "applications"))
} else if home != "" {
dirs = append(dirs, filepath.Join(home, ".local/share/applications"))
}
for _, d := range strings.Split(xdgDataDirs, ":") {
dirs = append(dirs, filepath.Join(d, "applications"))
}
flatpakDirs := []string{filepath.Join(home, ".local/share/flatpak/exports/share/applications"),
"/var/lib/flatpak/exports/share/applications"}
for _, d := range flatpakDirs {
if pathExists(d) && !isIn(dirs, d) {
dirs = append(dirs, d)
p := filepath.Join(d, "nwg-drawer")
q := filepath.Join(p, "desktop-directories")
if pathExists(q) {
log.Infof("Data dir: %v", p)
return p
}
}
log.Warnf("Data dir not found")
return ""
}
func getAppDirs() []string {
var dirs []string
log.Info("App dirs::")
home := os.Getenv("HOME")
//xdgDataHome := os.Getenv("XDG_DATA_HOME")
//xdgDataDirs := os.Getenv("XDG_DATA_DIRS")
custom := os.Getenv("CUSTOM_DATA_DIRS")
if home != "" {
if custom == "" {
dirs = append(dirs, filepath.Join(home, "/my_applications"))
} else {
dirs = append(dirs, filepath.Join(home, custom))
}
}
log.Infof("App dirs: %v", dirs)
var confirmedDirs []string
for _, d := range dirs {
if pathExists(d) {
confirmedDirs = append(confirmedDirs, d)
}
}
log.Infof("App dirs: %v", dirs)
return confirmedDirs
}
@@ -321,9 +338,10 @@ func listDesktopFiles() []string {
func setUpCategories() {
var other category
dDir := dataDir()
for _, cName := range categoryNames {
fileName := fmt.Sprintf("%s.directory", cName)
fp := filepath.Join("/usr/share/nwg-drawer/desktop-directories", fileName)
fp := filepath.Join(dDir, "desktop-directories", fileName)
lines, err := loadTextFile(fp)
if err == nil {
var cat category
@@ -407,7 +425,7 @@ func parseDesktopFiles(desktopFiles []string) string {
assignToLists(entry.DesktopID, entry.Category)
}
sort.Slice(desktopEntries, func(i, j int) bool {
return desktopEntries[i].NameLoc < desktopEntries[j].NameLoc
return strings.ToLower(desktopEntries[i].NameLoc) < strings.ToLower(desktopEntries[j].NameLoc)
})
summary := fmt.Sprintf("%v entries (+%v hidden)", len(desktopEntries)-hidden, hidden)
log.Infof("Skipped %v duplicates; %v .desktop entries hidden by \"NoDisplay=true\"", skipped, hidden)
@@ -554,7 +572,7 @@ func savePinned() {
}
}
func launch(command string, terminal bool) {
func launch(command string, terminal bool, terminate bool) {
// trim % and everything afterwards
if strings.Contains(command, "%") {
cutAt := strings.Index(command, "%")
@@ -564,13 +582,11 @@ func launch(command string, terminal bool) {
}
themeToPrepend := ""
// add "GTK_THEME=<default_gtk_theme>" environment variable
//add "GTK_THEME=<default_gtk_theme>" environment variable
if *forceTheme {
settings, _ := gtk.SettingsGetDefault()
th, err := settings.GetProperty("gtk-theme-name")
if err == nil {
themeToPrepend = th.(string)
}
settings := gtk.SettingsGetDefault()
th := settings.ObjectProperty("gtk-theme-name")
themeToPrepend = th.(string)
}
if themeToPrepend != "" {
@@ -594,9 +610,11 @@ func launch(command string, terminal bool) {
cmd = exec.Command("swaymsg", "exec", strings.Join(elements, " "))
} else if *wm == "hyprland" || *wm == "Hyprland" {
cmd = exec.Command("hyprctl", "dispatch", "exec", strings.Join(elements, " "))
} else if *wm == "river" {
cmd = exec.Command("riverctl", "spawn", strings.Join(elements, " "))
}
msg := fmt.Sprintf("command: %q; args: %q\n", cmd.Args[0], cmd.Args[1:])
msg := fmt.Sprintf("Executing command: %q; args: %q\n", cmd.Args[0], cmd.Args[1:])
log.Info(msg)
cmd.SysProcAttr = &syscall.SysProcAttr{
@@ -609,14 +627,18 @@ func launch(command string, terminal bool) {
// Collect the exit code of the child process to prevent zombies
// if the drawer runs in resident mode
go func() {
restoreStateAndHide()
_ = cmd.Wait()
gtk.MainQuit()
}()
}
if *resident {
restoreStateAndHide()
} else {
gtk.MainQuit()
if terminate {
if *resident {
restoreStateAndHide()
} else {
}
}
}
@@ -662,21 +684,13 @@ func mapOutputs() (map[string]*gdk.Monitor, error) {
err := listHyprlandMonitors()
if err == nil {
display, err := gdk.DisplayGetDefault()
if err != nil {
return nil, err
}
display := gdk.DisplayGetDefault()
num := display.GetNMonitors()
num := display.NMonitors()
for i := 0; i < num; i++ {
mon, _ := display.GetMonitor(i)
geometry := mon.GetGeometry()
// assign output to monitor on the basis of the same x, y coordinates
for _, output := range hyprlandMonitors {
if int(output.X) == geometry.GetX() && int(output.Y) == geometry.GetY() {
result[output.Name] = mon
}
}
mon := display.Monitor(i)
output := hyprlandMonitors[i]
result[output.Name] = mon
}
} else {
return nil, err
@@ -696,21 +710,16 @@ func mapOutputs() (map[string]*gdk.Monitor, error) {
return nil, err
}
display, err := gdk.DisplayGetDefault()
display := gdk.DisplayGetDefault()
if err != nil {
return nil, err
}
num := display.GetNMonitors()
num := display.NMonitors()
for i := 0; i < num; i++ {
mon, _ := display.GetMonitor(i)
geometry := mon.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] = mon
}
}
mon := display.Monitor(i)
output := outputs[i]
result[output.Name] = mon
}
} else {
return nil, errors.New("output assignment only supported on sway and Hyprland")
@@ -737,7 +746,15 @@ func substring(s string, start int, end int) string {
func hyprctl(cmd string) ([]byte, error) {
his := os.Getenv("HYPRLAND_INSTANCE_SIGNATURE")
socketFile := fmt.Sprintf("/tmp/hypr/%s/.socket.sock", his)
xdgRuntimeDir := os.Getenv("XDG_RUNTIME_DIR")
hyprDir := ""
if xdgRuntimeDir != "" {
hyprDir = fmt.Sprintf("%s/hypr", xdgRuntimeDir)
} else {
hyprDir = "/tmp/hypr"
}
socketFile := fmt.Sprintf("%s/%s/.socket.sock", hyprDir, his)
conn, err := net.Dial("unix", socketFile)
if err != nil {
return nil, err

View File

@@ -6,17 +6,20 @@ import (
"path/filepath"
"strings"
"github.com/diamondburned/gotk4-layer-shell/pkg/gtklayershell"
log "github.com/sirupsen/logrus"
"github.com/gotk3/gotk3/gdk"
"github.com/gotk3/gotk3/gtk"
"github.com/diamondburned/gotk4/pkg/gdk/v3"
"github.com/diamondburned/gotk4/pkg/gdkpixbuf/v2"
"github.com/diamondburned/gotk4/pkg/gtk/v3"
)
func setUpPinnedFlowBox() *gtk.FlowBox {
if pinnedFlowBox != nil {
pinnedFlowBox.Destroy()
}
flowBox, _ := gtk.FlowBoxNew()
flowBox := gtk.NewFlowBox()
if uint(len(pinned)) >= *columnsNumber {
flowBox.SetMaxChildrenPerLine(*columnsNumber)
} else if len(pinned) > 0 {
@@ -26,8 +29,8 @@ func setUpPinnedFlowBox() *gtk.FlowBox {
flowBox.SetColumnSpacing(*itemSpacing)
flowBox.SetRowSpacing(*itemSpacing)
flowBox.SetHomogeneous(true)
flowBox.SetProperty("name", "pinned-box")
flowBox.SetSelectionMode(gtk.SELECTION_NONE)
flowBox.SetObjectProperty("name", "pinned-box")
flowBox.SetSelectionMode(gtk.SelectionNone)
if len(pinned) > 0 {
for _, desktopID := range pinned {
@@ -37,19 +40,19 @@ func setUpPinnedFlowBox() *gtk.FlowBox {
continue
}
btn, _ := gtk.ButtonNew()
btn := gtk.NewButton()
var img *gtk.Image
if entry.Icon != "" {
pixbuf, _ := createPixbuf(entry.Icon, *iconSize)
img, _ = gtk.ImageNewFromPixbuf(pixbuf)
img = gtk.NewImageFromPixbuf(pixbuf)
} else {
img, _ = gtk.ImageNewFromIconName("image-missing", gtk.ICON_SIZE_INVALID)
img = gtk.NewImageFromIconName("image-missing", int(gtk.IconSizeInvalid))
}
btn.SetImage(img)
btn.SetAlwaysShowImage(true)
btn.SetImagePosition(gtk.POS_TOP)
btn.SetImagePosition(gtk.PosTop)
name := ""
if entry.NameLoc != "" {
@@ -59,14 +62,14 @@ func setUpPinnedFlowBox() *gtk.FlowBox {
}
if len(name) > 20 {
r := substring(name, 0, 17)
name = fmt.Sprintf("%s…", string(r))
name = fmt.Sprintf("%s…", r)
}
btn.SetLabel(name)
btn.Connect("button-release-event", func(row *gtk.Button, e *gdk.Event) bool {
btnEvent := gdk.EventButtonNewFromEvent(e)
btn.Connect("button-release-event", func(row *gtk.Button, event *gdk.Event) bool {
btnEvent := event.AsButton()
if btnEvent.Button() == 1 {
launch(entry.Exec, entry.Terminal)
launch(entry.Exec, entry.Terminal, true)
return true
} else if btnEvent.Button() == 3 {
unpinItem(entry.DesktopID)
@@ -75,7 +78,7 @@ func setUpPinnedFlowBox() *gtk.FlowBox {
return false
})
btn.Connect("activate", func() {
launch(entry.Exec, entry.Terminal)
launch(entry.Exec, entry.Terminal, true)
})
btn.Connect("enter-notify-event", func() {
statusLabel.SetText(entry.CommentLoc)
@@ -84,13 +87,9 @@ func setUpPinnedFlowBox() *gtk.FlowBox {
statusLabel.SetText(entry.CommentLoc)
})
flowBox.Add(btn)
btn.Parent().(*gtk.FlowBoxChild).SetCanFocus(false)
}
pinnedFlowBoxWrapper.PackStart(flowBox, true, false, 0)
//While moving focus with arrow keys we want buttons to get focus directly
flowBox.GetChildren().Foreach(func(item interface{}) {
item.(*gtk.Widget).SetCanFocus(false)
})
}
flowBox.ShowAll()
@@ -110,17 +109,17 @@ func setUpCategoriesButtonBox() *gtk.EventBox {
"other": listOther,
}
eventBox, _ := gtk.EventBoxNew()
eventBox := gtk.NewEventBox()
hBox, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
hBox := gtk.NewBox(gtk.OrientationHorizontal, 0)
eventBox.Add(hBox)
button, _ := gtk.ButtonNewWithLabel("All")
button.SetProperty("name", "category-button")
button := gtk.NewButtonWithLabel("All")
button.SetObjectProperty("name", "category-button")
button.Connect("clicked", func(item *gtk.Button) {
searchEntry.SetText("")
//searchEntry.SetText("")
appFlowBox = setUpAppsFlowBox(nil, "")
for _, btn := range catButtons {
btn.SetImagePosition(gtk.POS_LEFT)
btn.SetImagePosition(gtk.PosLeft)
btn.SetSizeRequest(0, 0)
}
})
@@ -128,8 +127,8 @@ func setUpCategoriesButtonBox() *gtk.EventBox {
for _, cat := range categories {
if isSupposedToShowUp(cat.Name) {
button, _ = gtk.ButtonNewFromIconName(cat.Icon, gtk.ICON_SIZE_MENU)
button.SetProperty("name", "category-button")
button = gtk.NewButtonFromIconName(cat.Icon, int(gtk.IconSizeMenu))
button.SetObjectProperty("name", "category-button")
catButtons = append(catButtons, button)
button.SetLabel(cat.DisplayName)
button.SetAlwaysShowImage(true)
@@ -137,14 +136,14 @@ func setUpCategoriesButtonBox() *gtk.EventBox {
name := cat.Name
b := *button
button.Connect("clicked", func(item *gtk.Button) {
searchEntry.SetText("")
// !!! since gotk3 FlowBox type does not implement set_filter_func, we need to rebuild appFlowBox
//searchEntry.SetText("")
// One day or another we'll add SetFilterFunction here; it was impossible on the gotk3 library
appFlowBox = setUpAppsFlowBox(lists[name], "")
for _, btn := range catButtons {
btn.SetImagePosition(gtk.POS_LEFT)
btn.SetImagePosition(gtk.PosLeft)
}
w := b.GetAllocatedWidth()
b.SetImagePosition(gtk.POS_TOP)
w := b.AllocatedWidth()
b.SetImagePosition(gtk.PosTop)
b.SetSizeRequest(w, 0)
if fileSearchResultWrapper != nil {
fileSearchResultWrapper.Hide()
@@ -186,13 +185,13 @@ func setUpAppsFlowBox(categoryList []string, searchPhrase string) *gtk.FlowBox {
if appFlowBox != nil {
appFlowBox.Destroy()
}
flowBox, _ := gtk.FlowBoxNew()
flowBox := gtk.NewFlowBox()
flowBox.SetMinChildrenPerLine(*columnsNumber)
flowBox.SetMaxChildrenPerLine(*columnsNumber)
flowBox.SetColumnSpacing(*itemSpacing)
flowBox.SetRowSpacing(*itemSpacing)
flowBox.SetHomogeneous(true)
flowBox.SetSelectionMode(gtk.SELECTION_NONE)
flowBox.SetSelectionMode(gtk.SelectionNone)
for _, entry := range desktopEntries {
if searchPhrase == "" {
@@ -205,6 +204,7 @@ func setUpAppsFlowBox(categoryList []string, searchPhrase string) *gtk.FlowBox {
} else {
button := flowBoxButton(entry)
flowBox.Add(button)
button.Parent().(*gtk.FlowBoxChild).SetCanFocus(false)
}
}
} else {
@@ -214,26 +214,24 @@ func setUpAppsFlowBox(categoryList []string, searchPhrase string) *gtk.FlowBox {
strings.Contains(strings.ToLower(entry.Exec), strings.ToLower(searchPhrase))) {
button := flowBoxButton(entry)
flowBox.Add(button)
button.Parent().(*gtk.FlowBoxChild).SetCanFocus(false)
}
}
}
hWrapper, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
hWrapper := gtk.NewBox(gtk.OrientationHorizontal, 0)
appSearchResultWrapper.PackStart(hWrapper, false, false, 0)
hWrapper.PackStart(flowBox, true, false, 0)
// While moving focus with arrow keys we want buttons to get focus directly
flowBox.GetChildren().Foreach(func(item interface{}) {
item.(*gtk.Widget).SetCanFocus(false)
})
resultWindow.ShowAll()
return flowBox
}
func flowBoxButton(entry desktopEntry) *gtk.Button {
button, _ := gtk.ButtonNew()
button := gtk.NewButton()
button.SetAlwaysShowImage(true)
var pixbuf *gdk.Pixbuf
var pixbuf *gdkpixbuf.Pixbuf
var img *gtk.Image
var err error
if entry.Icon != "" {
@@ -245,14 +243,14 @@ func flowBoxButton(entry desktopEntry) *gtk.Button {
if err != nil {
pixbuf, _ = createPixbuf("unknown", *iconSize)
}
img, _ = gtk.ImageNewFromPixbuf(pixbuf)
img = gtk.NewImageFromPixbuf(pixbuf)
button.SetImage(img)
button.SetImagePosition(gtk.POS_TOP)
button.SetImagePosition(gtk.PosTop)
name := entry.NameLoc
if len(name) > 20 {
r := substring(name, 0, 17)
name = fmt.Sprintf("%s…", string(r))
name = fmt.Sprintf("%s…", r)
}
button.SetLabel(name)
@@ -262,13 +260,21 @@ func flowBoxButton(entry desktopEntry) *gtk.Button {
desc := entry.CommentLoc
if len(desc) > 120 {
r := substring(desc, 0, 117)
desc = fmt.Sprintf("%s…", string(r))
desc = fmt.Sprintf("%s…", r)
}
button.Connect("button-release-event", func(btn *gtk.Button, e *gdk.Event) bool {
btnEvent := gdk.EventButtonNewFromEvent(e)
button.Connect("button-press-event", func() {
// if not scrolled from now on, we will allow launching apps on button-release-event
beenScrolled = false
})
button.Connect("button-release-event", func(btn *gtk.Button, event *gdk.Event) bool {
btnEvent := event.AsButton()
if btnEvent.Button() == 1 {
launch(exec, terminal)
return true
if !beenScrolled {
launch(exec, terminal, true)
return true
}
} else if btnEvent.Button() == 3 {
pinItem(ID)
return true
@@ -276,7 +282,7 @@ func flowBoxButton(entry desktopEntry) *gtk.Button {
return false
})
button.Connect("activate", func() {
launch(exec, terminal)
launch(exec, terminal, true)
})
button.Connect("enter-notify-event", func() {
statusLabel.SetText(desc)
@@ -290,32 +296,37 @@ func flowBoxButton(entry desktopEntry) *gtk.Button {
return button
}
func powerButton(iconPath, command string) *gtk.Button {
button, _ := gtk.ButtonNew()
func powerButton(iconPathOrName, command string) *gtk.Button {
button := gtk.NewButton()
button.SetAlwaysShowImage(true)
var pixbuf *gdk.Pixbuf
var pixbuf *gdkpixbuf.Pixbuf
var img *gtk.Image
var err error
pixbuf, err = gdk.PixbufNewFromFileAtSize(iconPath, *pbSize, *pbSize)
if err != nil {
pixbuf, _ = createPixbuf("unknown", *pbSize)
log.Warnf("Couldn't find icon %s", iconPath)
if !*pbUseIconTheme {
pixbuf, err = gdkpixbuf.NewPixbufFromFileAtSize(iconPathOrName, *pbSize, *pbSize)
if err != nil {
pixbuf, _ = createPixbuf("unknown", *pbSize)
log.Warnf("Couldn't find icon %s", iconPathOrName)
}
img = gtk.NewImageFromPixbuf(pixbuf)
} else {
img = gtk.NewImageFromIconName(iconPathOrName, int(gtk.IconSizeDialog))
}
img, _ = gtk.ImageNewFromPixbuf(pixbuf)
button.SetImage(img)
button.SetImagePosition(gtk.POS_TOP)
button.Connect("button-release-event", func(btn *gtk.Button, e *gdk.Event) bool {
btnEvent := gdk.EventButtonNewFromEvent(e)
button.SetImage(img)
button.SetImagePosition(gtk.PosTop)
button.Connect("button-release-event", func(btn *gtk.Button, event *gdk.Event) bool {
btnEvent := event.AsButton()
if btnEvent.Button() == 1 {
launch(command, false)
launch(command, false, true)
return true
}
return false
})
button.Connect("activate", func() {
launch(command, false)
launch(command, false, true)
})
button.Connect("enter-notify-event", func() {
statusLabel.SetText(command)
@@ -333,8 +344,8 @@ func setUpFileSearchResultContainer() *gtk.FlowBox {
if fileSearchResultFlowBox != nil {
fileSearchResultFlowBox.Destroy()
}
flowBox, _ := gtk.FlowBoxNew()
flowBox.SetProperty("orientation", gtk.ORIENTATION_VERTICAL)
flowBox := gtk.NewFlowBox()
flowBox.SetObjectProperty("orientation", gtk.OrientationVertical)
fileSearchResultWrapper.PackStart(flowBox, false, false, 10)
return flowBox
@@ -347,7 +358,7 @@ func walk(path string, d fs.DirEntry, e error) error {
// don't search leading part of the path, as e.g. '/home/user/Pictures'
toSearch := strings.Split(path, ignore)[1]
// Remaing part of the path (w/o file name) must be checked against being present in excluded dirs
// Remaining part of the path (w/o file name) must be checked against being present in excluded dirs
doSearch := true
parts := strings.Split(toSearch, "/")
remainingPart := ""
@@ -371,18 +382,15 @@ func walk(path string, d fs.DirEntry, e error) error {
}
func setUpSearchEntry() *gtk.SearchEntry {
searchEntry, _ := gtk.SearchEntryNew()
searchEntry.SetPlaceholderText("Type to search")
/*searchEntry.Connect("enter-notify-event", func() {
cancelClose()
})*/
searchEntry.Connect("search-changed", func() {
sEntry := gtk.NewSearchEntry()
sEntry.SetPlaceholderText("Type to search")
sEntry.Connect("search-changed", func() {
for _, btn := range catButtons {
btn.SetImagePosition(gtk.POS_LEFT)
btn.SetImagePosition(gtk.PosLeft)
btn.SetSizeRequest(0, 0)
}
phrase, _ = searchEntry.GetText()
phrase = sEntry.Text()
if len(phrase) > 0 {
// search apps
@@ -402,7 +410,7 @@ func setUpSearchEntry() *gtk.SearchEntry {
searchUserDir(key)
}
}
if fileSearchResultFlowBox.GetChildren().Length() == 0 {
if len(fileSearchResultFlowBox.Children()) == 0 {
fileSearchResultWrapper.Hide()
statusLabel.SetText("0 results")
}
@@ -416,25 +424,22 @@ func setUpSearchEntry() *gtk.SearchEntry {
}
}
// focus 1st search result #17
var w *gtk.Widget
var w *gtk.Button
if appFlowBox != nil {
b := appFlowBox.GetChildAtIndex(0)
b := appFlowBox.ChildAtIndex(0)
if b != nil {
button, err := b.GetChild()
if err == nil {
button.ToWidget().GrabFocus()
w = button.ToWidget()
}
button := b.Child().(*gtk.Button)
button.SetCanFocus(true)
button.GrabFocus()
w = button
}
}
if w == nil && fileSearchResultFlowBox != nil {
f := fileSearchResultFlowBox.GetChildAtIndex(0)
f := fileSearchResultFlowBox.ChildAtIndex(0)
if f != nil {
button, err := f.GetChild()
if err == nil {
button.ToWidget().SetCanFocus(true)
button.ToWidget().GrabFocus()
}
button := f.Child().(*gtk.Box)
button.SetCanFocus(true)
button.GrabFocus()
}
}
} else {
@@ -451,7 +456,7 @@ func setUpSearchEntry() *gtk.SearchEntry {
}
})
return searchEntry
return sEntry
}
func isExcluded(dir string) bool {
@@ -471,14 +476,16 @@ func searchUserDir(dir string) {
if len(fileSearchResults) > 0 {
btn := setUpUserDirButton(fmt.Sprintf("folder-%s", dir), "", dir, userDirsMap)
fileSearchResultFlowBox.Add(btn)
btn.Parent().(*gtk.FlowBoxChild).SetCanFocus(false)
for _, path := range fileSearchResults {
log.Debugf("Path: %s", path)
partOfPathToShow := strings.Split(path, userDirsMap[dir])[1]
if partOfPathToShow != "" {
if !(strings.HasPrefix(path, "#is_dir#") && isExcluded(path)) {
btn := setUpUserFileSearchResultButton(partOfPathToShow, path)
fileSearchResultFlowBox.Add(btn)
button := setUpUserFileSearchResultButton(partOfPathToShow, path)
fileSearchResultFlowBox.Add(button)
button.Parent().(*gtk.FlowBoxChild).SetCanFocus(false)
}
}
@@ -486,14 +493,11 @@ func searchUserDir(dir string) {
fileSearchResultFlowBox.Hide()
statusLabel.SetText(fmt.Sprintf("%v results | LMB: xdg-open | RMB: file manager",
fileSearchResultFlowBox.GetChildren().Length()))
num := uint(fileSearchResultFlowBox.GetChildren().Length() / *fsColumns)
len(fileSearchResultFlowBox.Children())))
num := uint(len(fileSearchResultFlowBox.Children())) / *fsColumns
fileSearchResultFlowBox.SetMinChildrenPerLine(num + 1)
fileSearchResultFlowBox.SetMaxChildrenPerLine(num + 1)
//While moving focus with arrow keys we want buttons to get focus directly
fileSearchResultFlowBox.GetChildren().Foreach(func(item interface{}) {
item.(*gtk.Widget).SetCanFocus(false)
})
fileSearchResultFlowBox.ShowAll()
}
}
@@ -503,10 +507,10 @@ func setUpUserDirButton(iconName, displayName, entryName string, userDirsMap map
parts := strings.Split(userDirsMap[entryName], "/")
displayName = parts[(len(parts) - 1)]
}
box, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
button, _ := gtk.ButtonNew()
box := gtk.NewBox(gtk.OrientationHorizontal, 0)
button := gtk.NewButton()
button.SetAlwaysShowImage(true)
img, _ := gtk.ImageNewFromIconName(iconName, gtk.ICON_SIZE_MENU)
img := gtk.NewImageFromIconName(iconName, int(gtk.IconSizeMenu))
button.SetImage(img)
if len(displayName) > *nameLimit {
@@ -514,8 +518,8 @@ func setUpUserDirButton(iconName, displayName, entryName string, userDirsMap map
}
button.SetLabel(displayName)
button.Connect("button-release-event", func(btn *gtk.Button, e *gdk.Event) bool {
btnEvent := gdk.EventButtonNewFromEvent(e)
button.Connect("button-release-event", func(btn *gtk.Button, event *gdk.Event) bool {
btnEvent := event.AsButton()
if btnEvent.Button() == 1 {
open(userDirsMap[entryName], true)
return true
@@ -526,18 +530,22 @@ func setUpUserDirButton(iconName, displayName, entryName string, userDirsMap map
return false
})
button.Connect("activate", func() {
open(userDirsMap[entryName], true)
})
box.PackStart(button, false, true, 0)
return box
}
func setUpUserFileSearchResultButton(fileName, filePath string) *gtk.Box {
box, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
button, _ := gtk.ButtonNew()
box := gtk.NewBox(gtk.OrientationHorizontal, 0)
button := gtk.NewButton()
// in the walk function we've marked directories with the '#is_dir#' prefix
if strings.HasPrefix(filePath, "#is_dir#") {
filePath = filePath[8:]
img, _ := gtk.ImageNewFromIconName("folder", gtk.ICON_SIZE_MENU)
img := gtk.NewImageFromIconName("folder", int(gtk.IconSizeMenu))
button.SetAlwaysShowImage(true)
button.SetImage(img)
}
@@ -552,8 +560,8 @@ func setUpUserFileSearchResultButton(fileName, filePath string) *gtk.Box {
button.SetTooltipText(tooltipText)
}
button.Connect("button-release-event", func(btn *gtk.Button, e *gdk.Event) bool {
btnEvent := gdk.EventButtonNewFromEvent(e)
button.Connect("button-release-event", func(btn *gtk.Button, event *gdk.Event) bool {
btnEvent := event.AsButton()
if btnEvent.Button() == 1 {
open(filePath, true)
return true
@@ -567,7 +575,54 @@ func setUpUserFileSearchResultButton(fileName, filePath string) *gtk.Box {
button.Connect("activate", func() {
open(filePath, true)
})
box.PackStart(button, false, true, 0)
return box
}
func setUpOperationResultWindow(operation string, result string) {
window := gtk.NewWindow(gtk.WindowToplevel)
window.SetModal(true)
if wayland() {
gtklayershell.InitForWindow(window)
gtklayershell.SetLayer(window, gtklayershell.LayerShellLayerOverlay)
gtklayershell.SetKeyboardMode(window, gtklayershell.LayerShellKeyboardModeExclusive)
}
// any key to close the window
window.Connect("key-release-event", func(_ *gtk.Window, event *gdk.Event) bool {
window.Destroy()
return true
})
// any button to close the window
window.Connect("button-release-event", func(_ *gtk.Window, event *gdk.Event) bool {
window.Destroy()
return true
})
outerVBox := gtk.NewBox(gtk.OrientationVertical, 6)
window.Add(outerVBox)
vBox := gtk.NewBox(gtk.OrientationHorizontal, 5)
outerVBox.PackStart(vBox, true, true, 6)
lbl := gtk.NewLabel(fmt.Sprintf("%s = %s", operation, result))
lbl.SetObjectProperty("name", "math-label")
vBox.PackStart(lbl, true, true, 12)
mRefProvider := gtk.NewCSSProvider()
css := "window { background-color: rgba (0, 0, 0, 255); color: #fff; border: solid 1px grey; border-radius: 5px}"
err := mRefProvider.LoadFromData(css)
if err != nil {
log.Warn(err)
}
ctx := window.StyleContext()
ctx.AddProvider(mRefProvider, gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
window.ShowAll()
if wayland() {
cmd := fmt.Sprintf("wl-copy %v", result)
launch(cmd, false, false)
}
}