├── .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 | logo
5 | golauncher 6 | 7 |
8 |

9 | 10 |

A go application launcher

11 |

12 | 13 | license 15 | 16 | 17 | 18 | go report card 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 | screenshot
30 | screenshot
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 | --------------------------------------------------------------------------------