├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── go.mod ├── go.sum └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | /update-notifier -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © 2016 Zlatko Čalušić 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 | # Copyright © 2018 Zlatko Čalušić 2 | # 3 | # Use of this source code is governed by an MIT-style license that can be found in the LICENSE file. 4 | # 5 | 6 | .PHONY: default update-notifier install uninstall clean 7 | 8 | default: update-notifier 9 | 10 | update-notifier: 11 | @go build -v 12 | 13 | install: update-notifier 14 | sudo /usr/bin/install -m 755 update-notifier /usr/local/bin/update-notifier 15 | 16 | uninstall: 17 | sudo rm -f /usr/local/bin/update-notifier 18 | 19 | clean: 20 | rm -f update-notifier 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Update notifier 2 | 3 | [![Build Status](https://travis-ci.org/zcalusic/update-notifier.svg?branch=master)](https://travis-ci.org/zcalusic/update-notifier) 4 | [![Go Report Card](https://goreportcard.com/badge/github.com/zcalusic/update-notifier)](https://goreportcard.com/report/github.com/zcalusic/update-notifier) 5 | [![GoDoc](https://godoc.org/github.com/zcalusic/update-notifier?status.svg)](https://godoc.org/github.com/zcalusic/update-notifier) 6 | [![License](https://img.shields.io/badge/license-MIT-a31f34.svg?maxAge=2592000)](https://github.com/zcalusic/update-notifier/blob/master/LICENSE) 7 | [![Powered by](https://img.shields.io/badge/powered_by-Go-5272b4.svg?maxAge=2592000)](https://golang.org/) 8 | [![Platform](https://img.shields.io/badge/platform-Linux-009bde.svg?maxAge=2592000)](https://www.linuxfoundation.org/) 9 | 10 | Update notifier displays notification and an icon in the panel tray area when Debian package updates are available. You 11 | can hover the mouse over the icon to check how many updates are available. It's especially suitable for Debian 12 | testing/unstable users, because it checks for updates very often. Developed and tested on Xfce desktop environment. 13 | 14 | ![notification](https://cloud.githubusercontent.com/assets/12140851/17998625/ac821782-6b75-11e6-911a-dc0e9f2cffa0.png) 15 |   16 | ![trayicon](https://cloud.githubusercontent.com/assets/12140851/17998626/ac84dfda-6b75-11e6-8ac7-c06486ff6a37.png) 17 | 18 | ## Motivation 19 | 20 | Once upon a time, there was this neat little package in the Debian GNU/Linux repository called update-notifier. It 21 | notified when package updates were available. Nowadays, the same named package in the repository is marked transitional, 22 | and is depending on quite a large number of other application and library packages. But, I wanted just the icon! So, I 23 | used that as an excuse to see how hard it would be to write something like that in Go. Not at all, it seems. 24 | 25 | ## Requirements 26 | 27 | To compile: 28 | - Go 1.6+ 29 | - libgtk2.0-dev 30 | - libnotify-dev 31 | 32 | To run: 33 | - libgtk2.0-0 34 | - libnotify4 35 | - gnome-icon-theme (icons) 36 | - apt-get (to check for updates) 37 | - apt-daily.timer (or similar, to periodically update package lists in the background) 38 | 39 | ## Installation 40 | 41 | Just use go get. 42 | 43 | ``` 44 | go get github.com/zcalusic/update-notifier 45 | ``` 46 | 47 | Here's a simple desktop file you can save as ```~/.config/autostart/update-notifier.desktop```, so that application is 48 | automatically started when you login. Just adapt TryExec directive to point to the location of the update-notifier 49 | executable on your system. 50 | 51 | ```ini 52 | [Desktop Entry] 53 | Encoding=UTF-8 54 | Name=Update notifier 55 | Comment=Notify when package updates are available 56 | TryExec=/path/to/update-notifier 57 | Exec=update-notifier 58 | Terminal=false 59 | Type=Application 60 | Categories= 61 | Hidden=false 62 | ``` 63 | 64 | ## Contributors 65 | 66 | Contributors are welcome, just open a new issue / pull request. 67 | 68 | ## License 69 | 70 | ``` 71 | The MIT License (MIT) 72 | 73 | Copyright © 2016 Zlatko Čalušić 74 | 75 | Permission is hereby granted, free of charge, to any person obtaining a copy 76 | of this software and associated documentation files (the "Software"), to deal 77 | in the Software without restriction, including without limitation the rights 78 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 79 | copies of the Software, and to permit persons to whom the Software is 80 | furnished to do so, subject to the following conditions: 81 | 82 | The above copyright notice and this permission notice shall be included in all 83 | copies or substantial portions of the Software. 84 | 85 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 86 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 87 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 88 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 89 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 90 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 91 | SOFTWARE. 92 | ``` 93 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/zcalusic/update-notifier 2 | 3 | go 1.24 4 | 5 | require ( 6 | github.com/mattn/go-gtk v0.0.0-20240119050609-48574e312fac 7 | github.com/mqu/go-notify v0.0.0-20130719194048-ef6f6f49d093 8 | ) 9 | 10 | require ( 11 | github.com/mattn/go-pointer v0.0.1 // indirect 12 | github.com/stretchr/testify v1.7.0 // indirect 13 | ) 14 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/mattn/go-gtk v0.0.0-20240119050609-48574e312fac h1:tNm7zRceQAOg9D8vQFq0K9hy49j39+9+7rSjML4YREI= 4 | github.com/mattn/go-gtk v0.0.0-20240119050609-48574e312fac/go.mod h1:PwzwfeB5syFHXORC3MtPylVcjIoTDT/9cvkKpEndGVI= 5 | github.com/mattn/go-pointer v0.0.1 h1:n+XhsuGeVO6MEAp7xyEukFINEa+Quek5psIR/ylA6o0= 6 | github.com/mattn/go-pointer v0.0.1/go.mod h1:2zXcozF6qYGgmsG+SeTZz3oAbFLdD3OWqnUbNvJZAlc= 7 | github.com/mqu/go-notify v0.0.0-20130719194048-ef6f6f49d093 h1:OvySnanP8CQIKS+MTq9AXBwEXzm0YaKeu331bWql3ug= 8 | github.com/mqu/go-notify v0.0.0-20130719194048-ef6f6f49d093/go.mod h1:AthsKyBZ9hqwU7DBWFiOxYObyF8nVyYVubXv/pQNC5E= 9 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 10 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 11 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 12 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 13 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 14 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 15 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 16 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 17 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Zlatko Čalušić 2 | // 3 | // Use of this source code is governed by an MIT-style license that can be found in the LICENSE file. 4 | 5 | // Update notifier displays notification and an icon in the panel tray area when Debian package updates are 6 | // available. You can hover the mouse over the icon to check how many updates are available. It's especially suitable 7 | // for Debian testing/unstable users, because it checks for updates very often. Developed and tested on Xfce desktop 8 | // environment. 9 | package main 10 | 11 | import ( 12 | "bytes" 13 | "fmt" 14 | "log" 15 | "os" 16 | "os/exec" 17 | "path" 18 | "regexp" 19 | "strconv" 20 | "time" 21 | 22 | "github.com/mattn/go-gtk/glib" 23 | "github.com/mattn/go-gtk/gtk" 24 | notify "github.com/mqu/go-notify" 25 | ) 26 | 27 | var ( 28 | aptRunning bool 29 | aptLastCheck time.Time 30 | updLastCheck time.Time 31 | updAvailable int 32 | updTooltip int 33 | updNotified int 34 | ) 35 | 36 | func showIcon(icon *gtk.StatusIcon) { 37 | if !icon.GetVisible() { 38 | icon.SetVisible(true) 39 | } 40 | } 41 | 42 | func hideIcon(icon *gtk.StatusIcon) { 43 | if icon.GetVisible() { 44 | icon.SetVisible(false) 45 | } 46 | } 47 | 48 | func showNotification(text string) { 49 | notify.Init("update-notifier") 50 | defer notify.UnInit() 51 | 52 | if notification := notify.NotificationNew("Software updates available", text, 53 | "/usr/share/icons/gnome/48x48/status/software-update-available.png"); notification != nil { 54 | _ = notify.NotificationShow(notification) // ignore errors 55 | } 56 | } 57 | 58 | func userNotify(icon *gtk.StatusIcon) { 59 | var word1, word2 string 60 | 61 | if updAvailable == 1 { 62 | word1 = "is" 63 | word2 = "update" 64 | } else { 65 | word1 = "are" 66 | word2 = "updates" 67 | } 68 | 69 | if updAvailable != updTooltip { 70 | icon.SetTooltipMarkup(fmt.Sprintf("%d %s available", updAvailable, word2)) 71 | updTooltip = updAvailable 72 | } 73 | 74 | showIcon(icon) 75 | 76 | if updAvailable != updNotified { 77 | showNotification(fmt.Sprintf("There %s %d %s ready to install.", word1, updAvailable, word2)) 78 | updNotified = updAvailable 79 | } 80 | } 81 | 82 | // Check if package manager is running (every 5 seconds). 83 | func isAptRunning() bool { 84 | if time.Since(aptLastCheck) >= 5*time.Second { 85 | aptRunning = false 86 | 87 | dir, err := os.ReadDir("/proc") 88 | if err != nil { 89 | log.Fatal(err) 90 | } 91 | 92 | reApt := regexp.MustCompile(`(?m:^(apt-get|dselect|aptitude)$)`) 93 | reProcess := regexp.MustCompile(`^\d+$`) 94 | 95 | for _, v := range dir { 96 | if !v.IsDir() { 97 | continue 98 | } 99 | 100 | if !reProcess.MatchString(v.Name()) { 101 | continue 102 | } 103 | 104 | comm, err := os.ReadFile(path.Join("/proc", v.Name(), "comm")) 105 | if err != nil { 106 | continue 107 | } 108 | 109 | if reApt.Match(comm) { 110 | aptRunning = true 111 | updLastCheck = time.Time{} 112 | break 113 | } 114 | } 115 | 116 | aptLastCheck = time.Now() 117 | } 118 | 119 | return aptRunning 120 | } 121 | 122 | // Check if updates are available (every 5 minutes). 123 | func updatesAvailable() int { 124 | if time.Since(updLastCheck) >= 5*time.Minute { 125 | var out bytes.Buffer 126 | 127 | cmd := exec.Command("/usr/bin/apt-get", "-s", "dist-upgrade") 128 | cmd.Stdout = &out 129 | if err := cmd.Run(); err == nil { 130 | 131 | if sl := regexp.MustCompile(`(?m:^(\d+) upgraded, (\d+) newly installed)`).FindStringSubmatch(out.String()); len(sl) == 3 { 132 | if upgraded, err := strconv.Atoi(sl[1]); err == nil { 133 | if newInstall, err := strconv.Atoi(sl[2]); err == nil { 134 | updAvailable = upgraded + newInstall 135 | } 136 | } 137 | } 138 | } 139 | 140 | updLastCheck = time.Now() 141 | } 142 | 143 | return updAvailable 144 | } 145 | 146 | func main() { 147 | // https://github.com/mattn/go-gtk/issues/251 148 | gtk.Init(nil) 149 | glib.SetApplicationName("update-notifier") 150 | 151 | icon := gtk.NewStatusIconFromFile("/usr/share/icons/gnome/24x24/status/software-update-available.png") 152 | icon.SetTitle("update-notifier") 153 | icon.SetVisible(false) 154 | 155 | // Don't fight with system notifications during login. 156 | time.Sleep(15 * time.Second) 157 | 158 | for { 159 | if !isAptRunning() && updatesAvailable() > 0 { 160 | userNotify(icon) 161 | } else { 162 | hideIcon(icon) 163 | } 164 | 165 | for gtk.EventsPending() { 166 | gtk.MainIteration() 167 | } 168 | 169 | time.Sleep(time.Second / 10) 170 | } 171 | } 172 | --------------------------------------------------------------------------------