├── .github
└── workflows
│ └── build.yaml
├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── go.mod
├── go.sum
├── gui
├── data.go
├── gui.go
└── launcher.go
├── main.go
├── plugins
├── Makefile
├── README.md
├── golauncher-fuzzy.go
├── golauncher-process
├── golauncher-screenshot.go
├── golauncher-url.go
└── golauncher-window-switch
├── resources
├── assets
│ ├── Makefile
│ ├── golauncher.desktop
│ └── logo.png
└── resources.go
└── themes
├── blue_light.yaml
├── blue_night.yaml
├── matrix.yaml
├── monokai.yaml
└── sand.yaml
/.github/workflows/build.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | name: 'Build app'
3 |
4 | on:
5 | push:
6 | branches:
7 | - master
8 | tags:
9 | - '*'
10 |
11 | jobs:
12 | fyne-build:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - name: Checkout
16 | uses: actions/checkout@v2
17 | - name: Get dependencies
18 | run: sudo apt-get update && sudo apt-get install gcc libgl1-mesa-dev libegl1-mesa-dev libgles2-mesa-dev libx11-dev xorg-dev
19 | if: ${{ runner.os == 'Linux' }}
20 | - name: Install Go
21 | uses: actions/setup-go@v2
22 | with:
23 | go-version: '^1.17'
24 | - name: Build
25 | run: |
26 | VERSION=0.0.0
27 | SHORTREF=${GITHUB_SHA::8}
28 | # If this is git tag, use the tag name as a docker tag
29 | if [[ $GITHUB_REF == refs/tags/* ]]; then
30 | VERSION=${GITHUB_REF#refs/tags/}
31 | fi
32 |
33 | go get fyne.io/fyne/v2
34 | make BUNDLE_NAME=golauncher-$VERSION-$SHORTREF release
35 | mkdir build
36 | mv *.tar.xz build/
37 | - name: Upload results
38 | uses: actions/upload-artifact@v2
39 | with:
40 | name: golauncher
41 | path: build
42 | if-no-files-found: error
43 | - name: Release
44 | uses: softprops/action-gh-release@v1
45 | if: startsWith(github.ref, 'refs/tags/')
46 | with:
47 | files: |
48 | build/*
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /plugins/golauncher-fuzzy
2 | /plugins/golauncher-url
3 | /plugins/golauncher-screenshot
4 | /golauncher
5 | /out
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Ettore Di Giacinto
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | PREFIX ?= /usr/local
2 | UBINDIR ?= $(PREFIX)/bin
3 | BUNDLE_NAME ?= golauncher
4 |
5 | mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))
6 | current_dir := $(dir $(mkfile_path))
7 |
8 | build:
9 | $(MAKE) -C plugins/ build
10 | go build -o golauncher
11 |
12 | release: $(current_dir)/out
13 | mkdir -p $(current_dir)/out/$(PREFIX)/share/applications
14 | mkdir -p $(current_dir)/out/$(PREFIX)/share/pixmaps
15 | cp -rf $(current_dir)/resources/assets/logo.png $(current_dir)/out/$(PREFIX)/share/pixmaps/golauncher.png
16 | cp -rf $(current_dir)/resources/assets/golauncher.desktop $(current_dir)/out/$(PREFIX)/share/applications
17 | cp -rf $(current_dir)/resources/assets/Makefile $(current_dir)/out/
18 | cd out && tar -cJf $(current_dir)/$(BUNDLE_NAME).tar.xz .
19 |
20 | install: build
21 | $(MAKE) -C plugins/ install
22 | install -d $(DESTDIR)/$(UBINDIR)
23 | install -m 0755 golauncher $(DESTDIR)/$(UBINDIR)/
24 |
25 | run: build
26 | ./golauncher
27 |
28 | $(current_dir)/out:
29 | mkdir out
30 | $(MAKE) DESTDIR=$(current_dir)/out install
31 |
32 | test: $(current_dir)/out
33 | THEME=themes/sand.yaml PLUGIN_DIR=$(current_dir)/out/usr/local/bin $(current_dir)/out/usr/local/bin/golauncher
34 |
35 | clean:
36 | $(MAKE) -C plugins/ clean
37 | rm -rf $(current_dir)/out
38 | rm -rf golauncher
39 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | 
5 | golauncher
6 |
7 |
8 |
9 |
10 | A go application launcher
11 |
12 |
13 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | A simple, highly extensible, customizable application launcher and window switcher written in less than 300 lines of Golang and fyne
24 |
25 |
26 |
27 |
28 | 
30 | 
32 |
33 |
34 | golauncher is a simple, highly extensible application launcher written in Golang. Its written using [fyne](https://github.com/fyne-io/fyne) in less than 300 lines of Go (actually, most of them are just layouts!).
35 |
36 | Works on i3, Xfce, GNOME, Plasma, fynedesk, ...
37 |
38 | # :ledger: Features
39 |
40 | The basic plugin set adds the following functionalities to golauncher:
41 |
42 | - Window fuzzy search
43 | - Program fuzzy search
44 | - Opening URLs
45 | - Taking screenshot
46 | - Process kill
47 | - ..... add yours!
48 |
49 | # :computer: Installation
50 |
51 | Download the [release](https://github.com/mudler/golauncher/releases), extract the tarball in your system and run `make install`.
52 |
53 | As it does use `fyne`, does not depend on GTK or either QT installed on your system.
54 |
55 | ## :construction_worker: Build from source
56 |
57 | You can also build golauncher locally with `go build`.
58 |
59 | Note: plugins are standalone binaries and not part of golauncher, you need to install them separately, or if you are developing, use `--plugin-dir` to point to a specific plugin directory.
60 |
61 | # :runner: Run
62 |
63 | Once you have `golauncher` installed you can either run it from the terminal with `golauncher`, or either start it from the application menu.
64 |
65 | ```
66 | GLOBAL OPTIONS:
67 | --theme value [$THEME]
68 | --plugin-dir value [$PLUGIN_DIR]
69 | ```
70 |
71 | Golauncher takes optionally a theme with `--theme` and an additional directory to scan for plugins (`--plugin-dir`).
72 |
73 | The plugin directory must contains binary prefixed with `golauncher-` in order to be loaded.
74 |
75 | # :pencil2: Extensible
76 |
77 | golauncher is extensible from the bottom up, and indeed the core does provide no functionalities besides the GUI displaying.
78 |
79 | ## :gear: Building from source
80 |
81 | To build `golauncher` run:
82 |
83 | ```
84 | $ git clone https://github.com/mudler/golauncher
85 | $ cd golauncher
86 | $ make build
87 | ```
88 |
89 | Note that plugins are shipping core functionalities of `golauncher` are built separately, in order to build the default plugin set:
90 |
91 | ```
92 | $ make -C plugins/ build
93 | ```
94 |
95 | This will build the default set of plugins, to try them out you can either run `make test` or:
96 |
97 | ```
98 | $ mkdir plugin-build
99 | $ make DESTDIR=$PWD/plugin-build -C plugins install
100 | $ golauncher --plugin-dir plugin-build/usr/local/bin/
101 | ```
102 |
103 | ## :gear: Writing extensions
104 |
105 | Extensions can be written in any language. `golauncher` reads binaries starting with `golauncher-` prefix inside `$PATH` and automatically invokes them while the user is submitting inputs to retrieve results to be displayed to the user. Optionally, golauncher takes a `PLUGIN_DIR` environment variable (or `--plugin-dir` as args) to specify an additional plugin directory to use.
106 |
107 | All the current functionalities of golauncher are split into separate plugins.
108 |
109 | Plugins can be written in any language. For examples on how to extend golauncher, see the [plugin](https://github.com/mudler/golauncher/tree/master/plugins) folder in this repository and the [wiki page](https://github.com/mudler/golauncher/wiki/Create-a-plugin)
110 |
111 |
112 | # :art: Themes
113 |
114 | Golauncher supports custom themes, you can find examples inside the [themes](https://github.com/mudler/golauncher/tree/master/themes) folder.
115 |
116 | To run golauncher with a theme, run:
117 |
118 | ```
119 | $ THEME=/home/mudler/.golauncher/monokai.yaml golauncher
120 | or
121 | $ golauncher --theme /home/mudler/.golauncher/monokai.yaml
122 | ```
123 |
124 | Check also the [gallery in the wiki](https://github.com/mudler/golauncher/wiki/Themes-gallery)
125 |
126 | # License
127 |
128 | MIT License
129 |
130 | Copyright (c) 2022 Ettore Di Giacinto
131 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/mudler/golauncher
2 |
3 | go 1.17
4 |
5 | require fyne.io/fyne/v2 v2.1.2-rc2.0.20220107003733-0e6d56b7553c
6 |
7 | require (
8 | fyne.io/x/fyne v0.0.0-00010101000000-000000000000
9 | github.com/0xAX/notificator v0.0.0-20210731104411-c42e3d4a43ee
10 | github.com/ghodss/yaml v1.0.0
11 | github.com/kbinani/screenshot v0.0.0-20210720154843-7d3a670d8329
12 | github.com/mudler/go-pluggable v0.0.0-20211206135551-9263b05c562e
13 | github.com/sahilm/fuzzy v0.1.0
14 | github.com/urfave/cli v1.22.5
15 | go.deanishe.net/fuzzy v1.0.0
16 | mvdan.cc/xurls/v2 v2.3.0
17 | )
18 |
19 | require (
20 | github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9 // indirect
21 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d // indirect
22 | github.com/davecgh/go-spew v1.1.1 // indirect
23 | github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 // indirect
24 | github.com/fsnotify/fsnotify v1.4.9 // indirect
25 | github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5 // indirect
26 | github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 // indirect
27 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec // indirect
28 | github.com/godbus/dbus/v5 v5.0.6 // indirect
29 | github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff // indirect
30 | github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240 // indirect
31 | github.com/kylelemons/godebug v1.1.0 // indirect
32 | github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect
33 | github.com/magefile/mage v1.10.0 // indirect
34 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
35 | github.com/onsi/ginkgo v1.16.4 // indirect
36 | github.com/onsi/gomega v1.16.0 // indirect
37 | github.com/pkg/errors v0.9.1 // indirect
38 | github.com/pmezard/go-difflib v1.0.0 // indirect
39 | github.com/russross/blackfriday/v2 v2.0.1 // indirect
40 | github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
41 | github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 // indirect
42 | github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 // indirect
43 | github.com/stretchr/testify v1.7.0 // indirect
44 | github.com/yuin/goldmark v1.3.8 // indirect
45 | golang.org/x/image v0.0.0-20211028202545-6944b10bf410 // indirect
46 | golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 // indirect
47 | golang.org/x/sys v0.0.0-20211123173158-ef496fb156ab // indirect
48 | golang.org/x/text v0.3.6 // indirect
49 | gopkg.in/yaml.v2 v2.4.0 // indirect
50 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
51 | )
52 |
53 | replace fyne.io/x/fyne => github.com/mudler/fyne-x v0.0.0-20220113174306-c448e59b7baa
54 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | fyne.io/fyne/v2 v2.1.0/go.mod h1:c1vwI38Ebd0dAdxVa6H1Pj6/+cK1xtDy61+I31g+s14=
2 | fyne.io/fyne/v2 v2.1.2-rc2.0.20220107003733-0e6d56b7553c h1:UlNqZHVbhc19h+CNDy80BnQnyXYUTLFFuB6gy1dlAUg=
3 | fyne.io/fyne/v2 v2.1.2-rc2.0.20220107003733-0e6d56b7553c/go.mod h1:6ZZ3rn9AGPqDxqyWlfEucuxVWH48JWaQCeo1c6wzzag=
4 | github.com/0xAX/notificator v0.0.0-20210731104411-c42e3d4a43ee h1:LgokYDTCpaZBHtl/oGwLxNCr3kM5Qt+Z7mInv4MqFNM=
5 | github.com/0xAX/notificator v0.0.0-20210731104411-c42e3d4a43ee/go.mod h1:NtXa9WwQsukMHZpjNakTTz0LArxvGYdPA9CjIcUSZ6s=
6 | github.com/Andrew-M-C/go.jsonvalue v1.1.2-0.20211223013816-e873b56b4a84/go.mod h1:oTJGG91FhtsxvUFVwHSvr6zuaTcAuroj/ToxfT7Ox8U=
7 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
8 | github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
9 | github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9/go.mod h1:7uhhqiBaR4CpN0k9rMjOtjpcfGd6DG2m04zQxKnWQ0I=
10 | github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
11 | github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9 h1:xz6Nv3zcwO2Lila35hcb0QloCQsc38Al13RNEzWRpX4=
12 | github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9/go.mod h1:2wSM9zJkl1UQEFZgSd68NfCgRz1VL1jzy/RjCg+ULrs=
13 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
14 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
15 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
16 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
17 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
18 | github.com/eclipse/paho.mqtt.golang v1.3.5/go.mod h1:eTzb4gxwwyWpqBUHGQZ4ABAV7+Jgm1PklsYT/eo8Hcc=
19 | github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 h1:FDqhDm7pcsLhhWl1QtD8vlzI4mm59llRvNzrFg6/LAA=
20 | github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3/go.mod h1:CzM2G82Q9BDUvMTGHnXf/6OExw/Dz2ivDj48nVg7Lg8=
21 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
22 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
23 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
24 | github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5 h1:Y5Q2mEwfzjMt5+3u70Gtw93ZOu2UuPeeeTBDntF7FoY=
25 | github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5/go.mod h1:uF6rMu/1nvu+5DpiRLwusA6xB8zlkNoGzKn8lmYONUo=
26 | github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
27 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
28 | github.com/go-gl/gl v0.0.0-20210813123233-e4099ee2221f/go.mod h1:wjpnOv6ONl2SuJSxqCPVaPZibGFdSci9HFocT9qtVYM=
29 | github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 h1:zDw5v7qm4yH7N8C8uWd+8Ii9rROdgWxQuGoJ9WDXxfk=
30 | github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw=
31 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210410170116-ea3d685f79fb/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
32 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec h1:3FLiRYO6PlQFDpUU7OEFlWgjGD1jnBIVSJ5SYRWk+9c=
33 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
34 | github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
35 | github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
36 | github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
37 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
38 | github.com/godbus/dbus/v5 v5.0.6 h1:mkgN1ofwASrYnJ5W6U/BxG15eXXXjirgZc7CLqkcaro=
39 | github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
40 | github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff h1:W71vTCKoxtdXgnm1ECDFkfQnpdqAO00zzGXLA5yaEX8=
41 | github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw=
42 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
43 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
44 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
45 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
46 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
47 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
48 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
49 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
50 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
51 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
52 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
53 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
54 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
55 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
56 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
57 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
58 | github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc=
59 | github.com/jackmordaunt/icns/v2 v2.1.3/go.mod h1:6aYIB9eSzyfHHMKqDf17Xrs1zetQPReAkiUSHzdw4cI=
60 | github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240 h1:dy+DS31tGEGCsZzB45HmJJNHjur8GDgtRNX9U7HnSX4=
61 | github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240/go.mod h1:3P4UH/k22rXyHIJD2w4h2XMqPX4Of/eySEZq9L6wqc4=
62 | github.com/josephspurrier/goversioninfo v0.0.0-20200309025242-14b0ab84c6ca/go.mod h1:eJTEwMjXb7kZ633hO3Ln9mBUCOjX2+FlTljvpl9SYdE=
63 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
64 | github.com/kbinani/screenshot v0.0.0-20210720154843-7d3a670d8329 h1:qq2nCpSrXrmvDGRxW0ruW9BVEV1CN2a9YDOExdt+U0o=
65 | github.com/kbinani/screenshot v0.0.0-20210720154843-7d3a670d8329/go.mod h1:2VPVQDR4wO7KXHwP+DAypEy67rXf+okUx2zjgpCxZw4=
66 | github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
67 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
68 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
69 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
70 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
71 | github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
72 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
73 | github.com/lucor/goinfo v0.0.0-20210802170112-c078a2b0f08b/go.mod h1:PRq09yoB+Q2OJReAmwzKivcYyremnibWGbK7WfftHzc=
74 | github.com/lxn/win v0.0.0-20210218163916-a377121e959e h1:H+t6A/QJMbhCSEH5rAuRxh+CtW96g0Or0Fxa9IKr4uc=
75 | github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
76 | github.com/magefile/mage v1.10.0 h1:3HiXzCUY12kh9bIuyXShaVe529fJfyqoVM42o/uom2g=
77 | github.com/magefile/mage v1.10.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
78 | github.com/mudler/fyne-x v0.0.0-20220113174306-c448e59b7baa h1:zOEknAJXeUtcI9UiGY1JTHeWjEUYnwIGK7ZZLPIt3lM=
79 | github.com/mudler/fyne-x v0.0.0-20220113174306-c448e59b7baa/go.mod h1:yq4D2A1RuXW+bTE2DN4SWpFlo1wK5c55IBh91SubTJU=
80 | github.com/mudler/go-pluggable v0.0.0-20211206135551-9263b05c562e h1:CZI+kJW2+WjZXLWWnVzi6NDQ6SfwSfeNqq5d1iDiwyY=
81 | github.com/mudler/go-pluggable v0.0.0-20211206135551-9263b05c562e/go.mod h1:WmKcT8ONmhDQIqQ+HxU+tkGWjzBEyY/KFO8LTGCu4AI=
82 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
83 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
84 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
85 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
86 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
87 | github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
88 | github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
89 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
90 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
91 | github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
92 | github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
93 | github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
94 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
95 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
96 | github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
97 | github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c=
98 | github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
99 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
100 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
101 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
102 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
103 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
104 | github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
105 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
106 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
107 | github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
108 | github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
109 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
110 | github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI=
111 | github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
112 | github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
113 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
114 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
115 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
116 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
117 | github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
118 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
119 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
120 | github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 h1:HunZiaEKNGVdhTRQOVpMmj5MQnGnv+e8uZNu3xFLgyM=
121 | github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564/go.mod h1:afMbS0qvv1m5tfENCwnOdZGOF8RGR/FsZ7bvBxQGZG4=
122 | github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 h1:m59mIOBO4kfcNCEzJNy71UkeF4XIx2EVmL9KLwDQdmM=
123 | github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU=
124 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
125 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
126 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
127 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
128 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
129 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
130 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
131 | github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU=
132 | github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
133 | github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
134 | github.com/wagslane/go-password-validator v0.3.0/go.mod h1:TI1XJ6T5fRdRnHqHt14pvy1tNVnrwe7m3/f1f2fDphQ=
135 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
136 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
137 | github.com/yuin/goldmark v1.3.8 h1:Nw158Q8QN+CPgTmVRByhVwapp8Mm1e2blinhmx4wx5E=
138 | github.com/yuin/goldmark v1.3.8/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
139 | go.deanishe.net/fuzzy v1.0.0 h1:3Qp6PCX0DLb9z03b5OHwAGsbRSkgJpSLncsiDdXDt4Y=
140 | go.deanishe.net/fuzzy v1.0.0/go.mod h1:2yEEMfG7jWgT1s5EO0TteVWmx2MXFBRMr5cMm84bQNY=
141 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
142 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
143 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
144 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
145 | golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
146 | golang.org/x/image v0.0.0-20211028202545-6944b10bf410 h1:hTftEOvwiOq2+O8k2D5/Q7COC7k5Qcrgc2TFURJYnvQ=
147 | golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
148 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
149 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
150 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
151 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
152 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
153 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
154 | golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
155 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
156 | golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
157 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
158 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
159 | golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0=
160 | golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
161 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
162 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
163 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
164 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
165 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
166 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
167 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
168 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
169 | golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
170 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
171 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
172 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
173 | golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
174 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
175 | golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
176 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
177 | golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
178 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
179 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
180 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
181 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
182 | golang.org/x/sys v0.0.0-20211123173158-ef496fb156ab h1:rfJ1bsoJQQIAoAxTxB7bme+vHrNkRw8CqfsYh9w54cw=
183 | golang.org/x/sys v0.0.0-20211123173158-ef496fb156ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
184 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
185 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
186 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
187 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
188 | golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
189 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
190 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
191 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
192 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
193 | golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
194 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
195 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
196 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
197 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
198 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
199 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
200 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
201 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
202 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
203 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
204 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
205 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
206 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
207 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
208 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
209 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
210 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
211 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
212 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
213 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
214 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
215 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
216 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
217 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
218 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
219 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
220 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
221 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
222 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
223 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
224 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
225 | mvdan.cc/xurls/v2 v2.3.0 h1:59Olnbt67UKpxF1EwVBopJvkSUBmgtb468E4GVWIZ1I=
226 | mvdan.cc/xurls/v2 v2.3.0/go.mod h1:AjuTy7gEiUArFMjgBBDU4SMxlfUYsRokpJQgNWOt3e4=
227 |
--------------------------------------------------------------------------------
/gui/gui.go:
--------------------------------------------------------------------------------
1 | // Copyright © 2021 Ettore Di Giacinto
2 | //
3 | // This program is free software; you can redistribute it and/or modify
4 | // it under the terms of the GNU General Public License as published by
5 | // the Free Software Foundation; either version 2 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // This program is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU General Public License along
14 | // with this program; if not, see .
15 |
16 | package gui
17 |
18 | import (
19 | "io/ioutil"
20 |
21 | "fyne.io/fyne/v2"
22 | theme "fyne.io/fyne/v2/theme"
23 |
24 | "fyne.io/fyne/v2/app"
25 | "github.com/ghodss/yaml"
26 | )
27 |
28 | //go:generate fyne bundle -package gui -o data.go ../Icon.png
29 |
30 | // Load theme. Best effort
31 | func loadTheme(t string, app fyne.App) {
32 | if t != "" {
33 | b, err := ioutil.ReadFile(t)
34 | if err == nil {
35 | jb, err := yaml.YAMLToJSON(b)
36 | if err == nil {
37 | t, err := theme.FromJSON(string(jb))
38 | if err == nil {
39 | app.Settings().SetTheme(t)
40 | }
41 | }
42 | }
43 | }
44 | }
45 |
46 | func Run(tn, pluginDir string) {
47 | app := app.New()
48 |
49 | loadTheme(tn, app)
50 | app.SetIcon(resourceIconPng)
51 |
52 | c := newLauncher(pluginDir)
53 | c.loadUI(app)
54 | app.Run()
55 | }
56 |
--------------------------------------------------------------------------------
/gui/launcher.go:
--------------------------------------------------------------------------------
1 | // Copyright © 2022 Ettore Di Giacinto
2 | //
3 | // This program is free software; you can redistribute it and/or modify
4 | // it under the terms of the GNU General Public License as published by
5 | // the Free Software Foundation; either version 2 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // This program is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU General Public License along
14 | // with this program; if not, see .
15 |
16 | package gui
17 |
18 | import (
19 | "encoding/json"
20 | "fmt"
21 | "sync"
22 |
23 | "fyne.io/fyne/v2"
24 | "fyne.io/fyne/v2/container"
25 | "fyne.io/fyne/v2/driver/desktop"
26 | "fyne.io/fyne/v2/layout"
27 | "fyne.io/fyne/v2/theme"
28 | "fyne.io/fyne/v2/widget"
29 | fyneX "fyne.io/x/fyne/widget"
30 | "github.com/0xAX/notificator"
31 | pluggable "github.com/mudler/go-pluggable"
32 | "go.deanishe.net/fuzzy"
33 | )
34 |
35 | type Launcher struct {
36 | sync.Mutex
37 | pluginDir string
38 | window fyne.Window
39 | manager *pluggable.Manager
40 | notifier *notificator.Notificator
41 | defaultSize fyne.Size
42 | responses map[pluggable.EventType]map[*pluggable.Plugin]*pluggable.EventResponse
43 | }
44 |
45 | const (
46 | searchEvent pluggable.EventType = "search"
47 | submitEvent pluggable.EventType = "submit"
48 | appName string = "golauncher"
49 | )
50 |
51 | func listSize(n int) float32 {
52 | l := widget.NewList(func() int { return 1 }, func() fyne.CanvasObject { return widget.NewEntry() }, func(lii widget.ListItemID, f fyne.CanvasObject) {})
53 | ss := l.MinSize()
54 | return float32(n)*(ss.Height+2*theme.Padding()+theme.SeparatorThicknessSize()) + theme.Padding()
55 | }
56 |
57 | func (c *Launcher) Reload(app fyne.App) {
58 |
59 | entry := fyneX.NewCompletionEntry([]string{})
60 | entry.Wrapping = fyne.TextWrapBreak
61 |
62 | var selection interface{}
63 |
64 | entry.OnMenuNavigation = func(w widget.ListItemID) {
65 | selection = true
66 | }
67 |
68 | entry.OnTypeEvent = func(event *fyne.KeyEvent) {
69 | switch event.Name {
70 | case fyne.KeyEscape:
71 | // Quit the app if the user press esc
72 | app.Quit()
73 | }
74 | }
75 |
76 | entry.OnSubmitted = func(s string) {
77 | if len(s) == 0 {
78 | // Quit the app if the user submit no results
79 | app.Quit()
80 | }
81 |
82 | if len(s) < 3 {
83 | return
84 | }
85 |
86 | if selection == nil {
87 | // Nothing was selected and user hit enter
88 | // act as user selected the first item
89 | if len(entry.Options) > 0 {
90 | s = entry.Options[0]
91 | }
92 | }
93 |
94 | res := c.pluginSubmit(submitEvent, s)
95 | for _, r := range res {
96 | r.notify(c.notifier)
97 | }
98 |
99 | app.Quit()
100 | }
101 |
102 | noResults := func() {
103 | c.window.Resize(c.defaultSize)
104 | entry.HideCompletion()
105 | }
106 |
107 | // When the use typed text, complete the list.
108 | entry.OnChanged = func(s string) {
109 | // completion start for text length >= 3
110 | if len(s) < 3 {
111 | noResults()
112 | return
113 | }
114 |
115 | var results []string
116 | res := c.pluginSubmit(searchEvent, s)
117 | for _, r := range res {
118 | for _, r := range r.Responses() {
119 | if r != "" {
120 | results = append(results, r)
121 | }
122 | }
123 | }
124 |
125 | // no results
126 | if len(results) == 0 {
127 | noResults()
128 | return
129 | }
130 |
131 | fuzzy.SortStrings(results, s)
132 |
133 | // then show them
134 | entry.SetOptions(results)
135 |
136 | // resize the window based on the option results
137 | ls := listSize(len(results))
138 | if ls > 480/2 {
139 | ls = 480 / 2
140 | }
141 |
142 | c.window.Resize(c.defaultSize.Add(fyne.NewSize(0, ls)))
143 |
144 | entry.ShowCompletion()
145 | }
146 |
147 | s := container.NewBorder(entry, nil, nil, nil, layout.NewSpacer())
148 | c.window.SetContent(
149 | container.NewAdaptiveGrid(1, s),
150 | )
151 | c.window.Canvas().Focus(entry)
152 |
153 | }
154 |
155 | func (c *Launcher) responseHandler(ev pluggable.EventType) func(p *pluggable.Plugin, r *pluggable.EventResponse) {
156 | return func(p *pluggable.Plugin, r *pluggable.EventResponse) {
157 | c.Lock()
158 | if _, exists := c.responses[ev]; !exists {
159 | c.responses[ev] = make(map[*pluggable.Plugin]*pluggable.EventResponse)
160 | }
161 | c.responses[ev][p] = r
162 | c.Unlock()
163 | }
164 | }
165 |
166 | type pluginResult struct {
167 | plugin *pluggable.Plugin
168 | response *pluggable.EventResponse
169 | }
170 |
171 | func (p pluginResult) Responses() []string {
172 | pr := &struct {
173 | Response []string
174 | }{}
175 | json.Unmarshal([]byte(p.response.Data), pr)
176 | return pr.Response
177 | }
178 |
179 | func (p pluginResult) notify(n *notificator.Notificator) {
180 | title := fmt.Sprintf("%s %s", appName, p.plugin.Name)
181 | if p.response.Errored() {
182 | n.Push(title, p.response.Error, "", notificator.UR_CRITICAL)
183 | }
184 | if p.response.State != "" {
185 | n.Push(title, p.response.State, "", notificator.UR_NORMAL)
186 | }
187 | }
188 |
189 | func (c *Launcher) pluginSubmit(ev pluggable.EventType, input string) (res []*pluginResult) {
190 | c.manager.Publish(ev, struct{ Term string }{Term: input})
191 | for p, r := range c.responses[ev] {
192 | res = append(res, &pluginResult{plugin: p, response: r})
193 | }
194 | return
195 | }
196 |
197 | func (c *Launcher) loadUI(app fyne.App) {
198 | pluginsDir := c.pluginDir
199 | if pluginsDir == "" {
200 | pluginsDir = "plugins"
201 | }
202 |
203 | c.manager.Autoload(appName, pluginsDir)
204 | c.manager.Register()
205 | c.manager.Response(submitEvent, c.responseHandler(submitEvent))
206 | c.manager.Response(searchEvent, c.responseHandler(searchEvent))
207 |
208 | drv := fyne.CurrentApp().Driver()
209 | if drv, ok := drv.(desktop.Driver); ok {
210 | c.window = drv.CreateSplashWindow()
211 | } else {
212 | c.window = app.NewWindow(appName)
213 | }
214 |
215 | c.Reload(app)
216 |
217 | c.window.SetFixedSize(true)
218 |
219 | c.window.Resize(c.defaultSize)
220 | c.window.SetPadded(true)
221 | c.window.CenterOnScreen()
222 |
223 | c.window.Show()
224 | }
225 |
226 | func newLauncher(pluginDir string) *Launcher {
227 | return &Launcher{
228 | pluginDir: pluginDir,
229 | defaultSize: fyne.NewSize(640, listSize(1)),
230 | notifier: notificator.New(notificator.Options{
231 | AppName: appName,
232 | }),
233 | manager: pluggable.NewManager(
234 | []pluggable.EventType{
235 | submitEvent,
236 | searchEvent,
237 | },
238 | ),
239 | responses: make(map[pluggable.EventType]map[*pluggable.Plugin]*pluggable.EventResponse),
240 | }
241 | }
242 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "os"
6 |
7 | "github.com/mudler/golauncher/gui"
8 | "github.com/urfave/cli"
9 | )
10 |
11 | func main() {
12 | app := &cli.App{
13 | Name: "golauncher",
14 | Author: "Ettore Di Giacinto",
15 | Flags: []cli.Flag{
16 | &cli.StringFlag{
17 | Name: "theme",
18 | EnvVar: "THEME",
19 | },
20 | &cli.StringFlag{
21 | Name: "plugin-dir",
22 | EnvVar: "PLUGIN_DIR",
23 | },
24 | },
25 | Action: func(c *cli.Context) error {
26 | gui.Run(
27 | c.String("theme"),
28 | c.String("plugin-dir"),
29 | )
30 | return nil
31 | },
32 | }
33 | err := app.Run(os.Args)
34 | if err != nil {
35 | fmt.Println(err)
36 | os.Exit(1)
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/plugins/Makefile:
--------------------------------------------------------------------------------
1 | DESTDIR ?=
2 | PREFIX ?= /usr/local
3 | UBINDIR ?= $(PREFIX)/bin
4 |
5 | build:
6 | CGO_ENABLED=0 go build -o golauncher-fuzzy ./golauncher-fuzzy.go
7 | CGO_ENABLED=0 go build -o golauncher-url ./golauncher-url.go
8 | CGO_ENABLED=0 go build -o golauncher-screenshot ./golauncher-screenshot.go
9 |
10 | clean:
11 | rm -rf golauncher-fuzzy golauncher-url golauncher-screenshot
12 |
13 | install: build
14 | install -d $(DESTDIR)/$(UBINDIR)
15 | install -m 0755 golauncher-screenshot $(DESTDIR)/$(UBINDIR)/
16 | install -m 0755 golauncher-url $(DESTDIR)/$(UBINDIR)/
17 | install -m 0755 golauncher-fuzzy $(DESTDIR)/$(UBINDIR)/
18 | install -m 0755 golauncher-process $(DESTDIR)/$(UBINDIR)/
19 | install -m 0755 golauncher-window-switch $(DESTDIR)/$(UBINDIR)/
--------------------------------------------------------------------------------
/plugins/README.md:
--------------------------------------------------------------------------------
1 | # golauncher plugins
2 |
3 | This set of plugins are the "core" ones and can be used as examples to write plugins in other languages.
4 |
5 | A plugin is a standalone binary installed in your system. golauncher will scan your $PATH for binaries starting with `golauncher-` and call them while receiving the user input.
6 |
7 | Currently in this folder you will find plugins written in bash/go.
8 |
9 | Each plugin receives the event and the user input via args:
10 |
11 | Events name are passed by as first argument (`submit`, or `search`) and a json payload containing the user input is passed as second argument ( e.g. `{ "data": "{ "Term": "foo" }" }` , if we enter `foo` in the golauncher input box).
12 |
13 | This means that every plugin can be also tested individually, for example by running:
14 |
15 | ```bash
16 | $ ./plugins/golauncher-process search '{ "data": { "Term": "firefox" } }'
17 | {
18 | "state": "",
19 | "data": "{ \"response\": [ \"Kill process /usr/lib64/firefox/firefox pid:10633\" ] }",
20 | "error": ""
21 | }
22 | ```
--------------------------------------------------------------------------------
/plugins/golauncher-fuzzy.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "os"
7 | "os/exec"
8 | "path/filepath"
9 | "strings"
10 |
11 | "github.com/mudler/go-pluggable"
12 | "github.com/sahilm/fuzzy"
13 | )
14 |
15 | func listDir(dir string) []string {
16 | content := []string{}
17 |
18 | filepath.Walk(dir,
19 | func(path string, info os.FileInfo, err error) error {
20 | if err != nil {
21 | return err
22 | }
23 | if !info.IsDir() {
24 | content = append(content, path)
25 | }
26 |
27 | return nil
28 | })
29 |
30 | return content
31 | }
32 |
33 | func ff(f string) (res []string) {
34 | paths := strings.Split(os.Getenv("PATH"), ":")
35 | bins := []string{}
36 | for _, p := range paths {
37 | bins = append(bins, listDir(p)...)
38 | }
39 |
40 | m := fuzzy.Find(f, bins)
41 | for _, match := range m {
42 | res = append(res, match.Str)
43 | }
44 |
45 | // Cut results to first ten
46 | if len(res) > 10 {
47 | res = res[0:10]
48 | }
49 |
50 | return
51 | }
52 |
53 | type res struct {
54 | Term string
55 | }
56 |
57 | func mapS(s []string, m string) (res []string) {
58 | for _, r := range s {
59 | res = append(res, m+r)
60 | }
61 | return res
62 | }
63 |
64 | func main() {
65 |
66 | var (
67 | submit pluggable.EventType = "submit"
68 | search pluggable.EventType = "search"
69 | )
70 |
71 | factory := pluggable.NewPluginFactory()
72 |
73 | factory.Add(submit, func(e *pluggable.Event) pluggable.EventResponse {
74 | d := &res{}
75 | json.Unmarshal([]byte(e.Data), d)
76 | s := ""
77 | if strings.Contains(d.Term, "Execute: ") {
78 | proc := strings.ReplaceAll(d.Term, "Execute: ", "")
79 | cmd := exec.Command(proc)
80 | s = fmt.Sprintf("opening %s", proc)
81 | cmd.Start()
82 | go func() {
83 | cmd.Wait()
84 | }()
85 | }
86 | return pluggable.EventResponse{State: s}
87 | })
88 |
89 | factory.Add(search, func(e *pluggable.Event) pluggable.EventResponse {
90 | d := &res{}
91 | json.Unmarshal([]byte(e.Data), d)
92 | res := mapS(ff(d.Term), "Execute: ")
93 | b, _ := json.Marshal(struct{ Response []string }{Response: res})
94 | return pluggable.EventResponse{Data: string(b)}
95 | })
96 |
97 | factory.Run(pluggable.EventType(os.Args[1]), os.Args[2], os.Stdout)
98 | }
99 |
--------------------------------------------------------------------------------
/plugins/golauncher-process:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | event="$1"
4 | payload="$2"
5 | res=
6 | state=
7 |
8 | if [ "$event" == "search" ]; then
9 | # Search into ps aux the user input and return an action
10 | s=$(echo "$payload" | jq -r .data | jq -r .Term )
11 | d=$(ps aux | grep -i $s | grep -v grep | head -n1)
12 | pid=$(echo $d | awk '{ print $2 }')
13 | name=$(cat /proc/$pid/cmdline | strings -1 | head -n1)
14 | if [ -n "$d" ]; then
15 | res="{ \"response\": [ \"Kill process $name pid:$pid\" ] }"
16 | fi
17 | fi
18 |
19 | if [ "$event" == "submit" ]; then
20 | # If we match the action, kill the process and return the state
21 | s=$(echo "$payload" | jq -r .data | jq -r .Term )
22 |
23 | if echo $s | grep -q "pid"; then
24 | p=$(echo $s | sed 's/.*pid://g')
25 | kill -9 $p
26 | state="Killed $p"
27 | fi
28 | fi
29 |
30 | jq --arg key0 'state' \
31 | --arg value0 "$state" \
32 | --arg key1 'data' \
33 | --arg value1 "$res" \
34 | --arg key2 'error' \
35 | --arg value2 '' \
36 | '. | .[$key0]=$value0 | .[$key1]=$value1 | .[$key2]=$value2' \
37 | <<<'{}'
--------------------------------------------------------------------------------
/plugins/golauncher-screenshot.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "os"
7 | "os/exec"
8 | "strings"
9 | "time"
10 |
11 | "image/png"
12 |
13 | "github.com/kbinani/screenshot"
14 | "github.com/mudler/go-pluggable"
15 | )
16 |
17 | func fork() {
18 | exec.Command("/proc/self/exe",
19 | "_screenshot",
20 | ).Start()
21 | }
22 |
23 | func takeScreenshot() {
24 | time.Sleep(1 * time.Second)
25 | n := screenshot.NumActiveDisplays()
26 | for i := 0; i < n; i++ {
27 | bounds := screenshot.GetDisplayBounds(i)
28 |
29 | img, err := screenshot.CaptureRect(bounds)
30 | if err != nil {
31 | panic(err)
32 | }
33 | os.MkdirAll("/tmp/screenshots", os.ModePerm)
34 | fileName := fmt.Sprintf("/tmp/screenshots/%d_%dx%d.png", i, bounds.Dx(), bounds.Dy())
35 | file, _ := os.Create(fileName)
36 | defer file.Close()
37 | png.Encode(file, img)
38 | }
39 | // Open screenshot folder
40 | exec.Command("xdg-open", "/tmp/screenshots").Run()
41 | }
42 |
43 | func main() {
44 | var (
45 | submit pluggable.EventType = "submit"
46 | search pluggable.EventType = "search"
47 | )
48 |
49 | if os.Args[1] == "_screenshot" {
50 | takeScreenshot()
51 | }
52 |
53 | factory := pluggable.NewPluginFactory()
54 |
55 | factory.Add(submit, func(e *pluggable.Event) pluggable.EventResponse {
56 | d := &struct {
57 | Term string
58 | }{}
59 | json.Unmarshal([]byte(e.Data), d)
60 |
61 | s := ""
62 |
63 | if strings.Contains(d.Term, "Take screenshot") {
64 | s = "Taking screenshots in /tmp/screenshots"
65 | fork()
66 | }
67 | return pluggable.EventResponse{State: s}
68 | })
69 |
70 | factory.Add(search, func(e *pluggable.Event) pluggable.EventResponse {
71 | d := &struct {
72 | Term string
73 | }{}
74 | json.Unmarshal([]byte(e.Data), d)
75 | if strings.Contains(strings.ToLower(d.Term), "screen") || strings.Contains(strings.ToLower(d.Term), "take") {
76 | b, _ := json.Marshal(struct{ Response []string }{Response: []string{"Take screenshot"}})
77 | return pluggable.EventResponse{Data: string(b)}
78 | }
79 | return pluggable.EventResponse{}
80 | })
81 |
82 | factory.Run(pluggable.EventType(os.Args[1]), os.Args[2], os.Stdout)
83 | }
84 |
--------------------------------------------------------------------------------
/plugins/golauncher-url.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "os"
7 | "os/exec"
8 | "strings"
9 |
10 | "net/url"
11 |
12 | "github.com/mudler/go-pluggable"
13 | "mvdan.cc/xurls/v2"
14 | )
15 |
16 | func mapS(s []string, m string) (res []string) {
17 | for _, r := range s {
18 |
19 | URL, _ := url.Parse(r)
20 |
21 | if URL.Scheme == "" {
22 | URL.Scheme = "https"
23 | }
24 | res = append(res, m+URL.String())
25 | }
26 | return res
27 | }
28 |
29 | func main() {
30 | xurlsRelaxed := xurls.Relaxed()
31 | var (
32 | submit pluggable.EventType = "submit"
33 | search pluggable.EventType = "search"
34 | )
35 |
36 | factory := pluggable.NewPluginFactory()
37 |
38 | factory.Add(submit, func(e *pluggable.Event) pluggable.EventResponse {
39 | d := &struct{ Term string }{}
40 | json.Unmarshal([]byte(e.Data), d)
41 | s := ""
42 | if strings.Contains(d.Term, "Open url: ") {
43 | u := strings.ReplaceAll(d.Term, "Open url: ", "")
44 | cmd := exec.Command("xdg-open", u)
45 | s = fmt.Sprintf("opening %s", u)
46 | cmd.Start()
47 | go func() {
48 | cmd.Wait()
49 | }()
50 | }
51 | return pluggable.EventResponse{State: s}
52 | })
53 |
54 | factory.Add(search, func(e *pluggable.Event) pluggable.EventResponse {
55 | d := &struct{ Term string }{}
56 | json.Unmarshal([]byte(e.Data), d)
57 | res := mapS(xurlsRelaxed.FindAllString(d.Term, -1), "Open url: ")
58 |
59 | b, _ := json.Marshal(struct{ Response []string }{Response: res})
60 | return pluggable.EventResponse{Data: string(b)}
61 | })
62 |
63 | factory.Run(pluggable.EventType(os.Args[1]), os.Args[2], os.Stdout)
64 | }
65 |
--------------------------------------------------------------------------------
/plugins/golauncher-window-switch:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | event="$1"
4 | payload="$2"
5 | res=
6 | state=
7 |
8 | if [ "$event" == "search" ]; then
9 | # Search into ps aux the user input and return an action
10 | s=$(echo "$payload" | jq -r .data | jq -r .Term )
11 | res=$(wmctrl -l | grep -i $s | awk '{$1="";$2="";$3=""}1' | head -n1)
12 | if [ -n "$res" ]; then
13 | res="{ \"response\": [ \"window: $res\" ] }"
14 | fi
15 | fi
16 |
17 | if [ "$event" == "submit" ]; then
18 | # If we match the action, select a window
19 | s=$(echo "$payload" | jq -r .data | jq -r .Term )
20 |
21 | if echo $s | grep -q "window"; then
22 | p=$(echo $s | sed 's/.*window: //g')
23 | wmctrl -a $p
24 | state="Focus $p"
25 | fi
26 | fi
27 |
28 | jq --arg key0 'state' \
29 | --arg value0 "$state" \
30 | --arg key1 'data' \
31 | --arg value1 "$res" \
32 | --arg key2 'error' \
33 | --arg value2 '' \
34 | '. | .[$key0]=$value0 | .[$key1]=$value1 | .[$key2]=$value2' \
35 | <<<'{}'
--------------------------------------------------------------------------------
/resources/assets/Makefile:
--------------------------------------------------------------------------------
1 | # If PREFIX isn't provided, we check for $(DESTDIR)/usr/local and use that if it exists.
2 | # Otherwice we fall back to using /usr.
3 |
4 | LOCAL != test -d $(DESTDIR)/usr/local && echo -n "/local" || echo -n ""
5 | LOCAL ?= $(shell test -d $(DESTDIR)/usr/local && echo "/local" || echo "")
6 | PREFIX ?= /usr$(LOCAL)
7 |
8 | default:
9 | # Run "sudo make install" to install the application.
10 | # Run "sudo make uninstall" to uninstall the application.
11 |
12 | install:
13 | install -Dm00644 usr/local/share/applications/golauncher.desktop $(DESTDIR)$(PREFIX)/share/applications/golauncher.desktop
14 | install -Dm00755 usr/local/bin/golauncher $(DESTDIR)$(PREFIX)/bin/golauncher
15 | install -Dm00755 usr/local/bin/golauncher-url $(DESTDIR)$(PREFIX)/bin/golauncher-url
16 | install -Dm00755 usr/local/bin/golauncher-screenshot $(DESTDIR)$(PREFIX)/bin/golauncher-screenshot
17 | install -Dm00755 usr/local/bin/golauncher-fuzzy $(DESTDIR)$(PREFIX)/bin/golauncher-fuzzy
18 | install -Dm00755 usr/local/bin/golauncher-process $(DESTDIR)$(PREFIX)/bin/golauncher-process
19 | install -Dm00755 usr/local/bin/golauncher-window-switch $(DESTDIR)$(PREFIX)/bin/golauncher-window-switch
20 | install -Dm00644 usr/local/share/pixmaps/golauncher.png $(DESTDIR)$(PREFIX)/share/pixmaps/golauncher.png
21 |
22 | uninstall:
23 | -rm $(DESTDIR)$(PREFIX)/share/applications/golauncher.desktop
24 | -rm $(DESTDIR)$(PREFIX)/bin/golauncher
25 | -rm $(DESTDIR)$(PREFIX)/share/pixmaps/golauncher.png
--------------------------------------------------------------------------------
/resources/assets/golauncher.desktop:
--------------------------------------------------------------------------------
1 | [Desktop Entry]
2 | Type=Application
3 | Name=golauncher
4 | Exec=golauncher
5 | Icon=golauncher
6 |
--------------------------------------------------------------------------------
/resources/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mudler/golauncher/d6f7c0f33e3f4ed9730df6fa9d6b5d5f2790ab13/resources/assets/logo.png
--------------------------------------------------------------------------------
/resources/resources.go:
--------------------------------------------------------------------------------
1 | package resources
2 |
3 | import (
4 | "bufio"
5 | "embed"
6 | "io/ioutil"
7 | "log"
8 |
9 | "fyne.io/fyne/v2"
10 | )
11 |
12 | //go:embed assets
13 | var Assets embed.FS
14 |
15 | type Resource string
16 |
17 | const (
18 | LogoIcon Resource = "assets/logo.png"
19 | )
20 |
21 | func GetResource(res Resource, name string) *fyne.StaticResource {
22 | f, err := Assets.Open(string(res))
23 | r := bufio.NewReader(f)
24 |
25 | b, err := ioutil.ReadAll(r)
26 | if err != nil {
27 | log.Fatal(err)
28 | }
29 | return fyne.NewStaticResource(name, b)
30 | }
31 |
--------------------------------------------------------------------------------
/themes/blue_light.yaml:
--------------------------------------------------------------------------------
1 | Colors:
2 | background: "#112d4e"
3 | foreground: "#f9f7f7"
4 | primary: "#3f72af"
5 | inputBackground: "#3f72af"
6 | scrollbar: "#000"
7 | shadow: "#112d4e"
8 | selection: "#3f72af"
9 | hover: "#3f72af"
10 |
11 | # A theme from fyne has the following format:
12 | # Colors:
13 | # background: "#0d0208"
14 | # ...
15 | # Colors-light:
16 | # background: "#0d0208"
17 | # ...
18 | # Colors-dark:
19 | # background: "#0d0208"
20 | # ...
21 | # Sizes:
22 | # iconInline: 5.0
23 | #
24 | #
25 | # To see available theme keywords:
26 | # https://github.com/andydotxyz/fyne/blob/7e37c42a1748800c042d70b39f8c31d5d545c677/theme/theme.go#L46
--------------------------------------------------------------------------------
/themes/blue_night.yaml:
--------------------------------------------------------------------------------
1 | Colors:
2 | background: "#222831"
3 | foreground: "#eeeeee"
4 | primary: "#00acb5"
5 | inputBackground: "#393e46"
6 | scrollbar: "#000"
7 | shadow: "#222831"
8 | selection: "#393e46"
9 | hover: "#393e46"
10 |
11 | # A theme from fyne has the following format:
12 | # Colors:
13 | # background: "#0d0208"
14 | # ...
15 | # Colors-light:
16 | # background: "#0d0208"
17 | # ...
18 | # Colors-dark:
19 | # background: "#0d0208"
20 | # ...
21 | # Sizes:
22 | # iconInline: 5.0
23 | #
24 | #
25 | # To see available theme keywords:
26 | # https://github.com/andydotxyz/fyne/blob/7e37c42a1748800c042d70b39f8c31d5d545c677/theme/theme.go#L46
--------------------------------------------------------------------------------
/themes/matrix.yaml:
--------------------------------------------------------------------------------
1 | Colors:
2 | background: "#0d0208"
3 | foreground: "#00ff41"
4 | primary: "#00ff41"
5 | inputBackground: "#003b00"
6 | scrollbar: "#000"
7 | shadow: "#0d0208"
8 | selection: "#008f11"
9 | hover: "#008f11"
10 |
11 | # A theme from fyne has the following format:
12 | # Colors:
13 | # background: "#0d0208"
14 | # ...
15 | # Colors-light:
16 | # background: "#0d0208"
17 | # ...
18 | # Colors-dark:
19 | # background: "#0d0208"
20 | # ...
21 | # Sizes:
22 | # iconInline: 5.0
23 | #
24 | #
25 | # To see available theme keywords:
26 | # https://github.com/andydotxyz/fyne/blob/7e37c42a1748800c042d70b39f8c31d5d545c677/theme/theme.go#L46
27 |
--------------------------------------------------------------------------------
/themes/monokai.yaml:
--------------------------------------------------------------------------------
1 | Colors:
2 | background: "#2e2e40"
3 | foreground: "#d6d6d6"
4 | primary: "#e5b567"
5 | inputBackground: "#2e2e40"
6 | scrollbar: "#000"
7 | shadow: "#2e2e40"
8 | selection: "#e87d3e"
9 | hover: "#e87d3e"
10 |
11 | # A theme from fyne has the following format:
12 | # Colors:
13 | # background: "#0d0208"
14 | # ...
15 | # Colors-light:
16 | # background: "#0d0208"
17 | # ...
18 | # Colors-dark:
19 | # background: "#0d0208"
20 | # ...
21 | # Sizes:
22 | # iconInline: 5.0
23 | #
24 | #
25 | # To see available theme keywords:
26 | # https://github.com/andydotxyz/fyne/blob/7e37c42a1748800c042d70b39f8c31d5d545c677/theme/theme.go#L46
27 |
--------------------------------------------------------------------------------
/themes/sand.yaml:
--------------------------------------------------------------------------------
1 | Colors:
2 | background: "#2d4263"
3 | foreground: "#191919"
4 | primary: "#c84b31"
5 | inputBackground: "#ecdbba"
6 | scrollbar: "#000"
7 | shadow: "#2d4263"
8 | selection: "#ecdbba"
9 | hover: "#ecdbba"
10 |
11 | # A theme from fyne has the following format:
12 | # Colors:
13 | # background: "#0d0208"
14 | # ...
15 | # Colors-light:
16 | # background: "#0d0208"
17 | # ...
18 | # Colors-dark:
19 | # background: "#0d0208"
20 | # ...
21 | # Sizes:
22 | # iconInline: 5.0
23 | #
24 | #
25 | # To see available theme keywords:
26 | # https://github.com/andydotxyz/fyne/blob/7e37c42a1748800c042d70b39f8c31d5d545c677/theme/theme.go#L46
27 |
28 |
29 |
--------------------------------------------------------------------------------