├── LICENSE ├── README.md ├── app.go ├── go.mod ├── go.sum ├── main.go ├── preferences.go ├── settings.go ├── setup.go ├── ui.go ├── update.go └── wine.go /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Belac Darkstorm 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LinuxPA 2 | 3 | LinuxPA is a try to bring a [PortableApps.com](http://portableapps.com) type launcher to Linux. 4 | 5 | ## How to use 6 | 7 | Just double click on an app to launch it! If there are multiple executables, you can either select the specific executable, or if you just double click the app it'll launch the first linux executable it finds. .sh script files have priority over other executable files. 8 | 9 | ## Apps 10 | 11 | The below place provides linux executables that don't need libs installed on the host system: 12 | [https://appimage.github.io/](https://appimage.github.io/) 13 | 14 | ## PortableApps.com Compatibility 15 | 16 | LinuxPA works will with the PortableApps.com launcher, as it looks for apps in the PortableApps folder and grabs the app's name and icon from where it should be in the PortableApps.com format. 17 | My forum at PortableApps.com can be found [here](http://portableapps.com/node/54998). 18 | 19 | ## common.sh 20 | 21 | common.sh is found in the PortableApps/LinuxPACom folder and is executed before the app. I mainly use it to set environment variables (such as HOME). You can create and edit the common.sh from settings. 22 | 23 | ## Simple App Setup 24 | 25 | Because apps aren't natively formated in the PortableApps.com format, LinuxPA will look in the root directory for a AppInfo.ini (for basic info such as category and name) and appicon.png. If they aren't found, it looks where the appicon_\*.png and AppInfo.ini is in PortableApps format. You can set what the AppInfo.ini and appicon.png are from LinuxPA. 26 | 27 | ## AppImage Support 28 | 29 | [AppImage Website](http://appimage.org) 30 | I'm looking into improving AppImage support. As of 2.1.5.0 IF `unsquashfs` is in $PATH then some advanced AppImage support is available and it will automagically get the name and possibly the icon of it. I'm looking into better support, but it might be a while. 31 | 32 | ## USB mount 33 | 34 | Unfortunately Linux, by default, doesn't support running executables off of FAT formated flash drives, requiring you to mount your drive with special mount arguments or format in a linux friendly format (such as EXT4). I personally use the arguments `exec,noauto,nodev,nosuid,umask=0000` 35 | 36 | ## Screenshots 37 | 38 | Photos are found [Here](https://goo.gl/photos/VtBUL6DyZTMidj5n6). The screenshots are with the adapta gtk theme 39 | 40 | ## TODO (Might be in order) 41 | 42 | 1. MAKE IT BETTER 43 | 1. Integrate [goappimage](https://github.com/probonod/go-appimage) library for better AppImage integration. 44 | 1. Try to `chmod +x` executables if they don't have the permission 45 | 1. Manual update check 46 | 1. Better AppImage integrations (Specifically updating and better appimage downloading) 47 | 1. Get information (such as name and icon) directly from an appimage 48 | 1. Better appimage downloading (probably based around [AppImageHub](https://appimage.github.io/apps/)) 49 | 1. Sandboxing support 50 | 1. Might be possible by packaging as an AppImage and providing Firejail, or simply just downloading it (like with Wine) 51 | 1. Check if all apps are closed when it closes and ask if you want to force stop the apps 52 | -------------------------------------------------------------------------------- /app.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | "os/exec" 8 | "strings" 9 | 10 | "github.com/gotk3/gotk3/gdk" 11 | "github.com/gotk3/gotk3/gtk" 12 | ) 13 | 14 | type app struct { 15 | name string 16 | cat string 17 | appimg []string 18 | lin []string 19 | ex []string 20 | icon *gdk.Pixbuf 21 | dir string 22 | ini *os.File 23 | wine bool 24 | } 25 | 26 | func (a *app) getTreeIter(store *gtk.TreeStore) *gtk.TreeIter { 27 | it := store.Append(nil) 28 | scaled, _ := a.icon.ScaleSimple(32, 32, gdk.INTERP_HYPER) 29 | store.SetValue(it, 0, scaled) 30 | if portableHide { 31 | store.SetValue(it, 1, strings.TrimSuffix(a.name, "Portable")) 32 | } else { 33 | store.SetValue(it, 1, a.name) 34 | } 35 | if len(a.ex) > 1 { 36 | if wine { 37 | for _, v := range a.ex { 38 | i := store.Append(it) 39 | store.SetValue(i, 1, v) 40 | } 41 | } else { 42 | for _, v := range a.lin { 43 | i := store.Append(it) 44 | store.SetValue(i, 1, v) 45 | } 46 | } 47 | } 48 | return it 49 | } 50 | 51 | func (a *app) launch() { 52 | if len(a.ex) == 1 { 53 | if wine { 54 | var cmd *exec.Cmd 55 | if !contains(a.lin, a.ex[0]) { 56 | if comEnbld { 57 | cmd = exec.Command("/bin/sh", "-c", "export APPNAME="+a.name+";export FILENAME="+a.ex[0]+";. PortableApps/LinuxPACom/common.sh || exit 1;cd \""+a.dir+"\"; wine \""+a.ex[0]+"\"") 58 | } else { 59 | cmd = exec.Command("/bin/sh", "-c", "cd \""+a.dir+"\"; wine \""+a.ex[0]+"\"") 60 | } 61 | } else { 62 | if paDirs && strings.HasSuffix(strings.ToLower(a.ex[0]), ".appimage") { 63 | os.Mkdir(a.dir+"/"+a.ex[0]+".home", 0777) 64 | os.Mkdir(a.dir+"/"+a.ex[0]+".config", 0777) 65 | } 66 | if comEnbld { 67 | cmd = exec.Command("/bin/sh", "-c", "export APPNAME="+a.name+";export FILENAME="+a.ex[0]+";. PortableApps/LinuxPACom/common.sh || exit 1;cd \""+a.dir+"\"; \"./"+a.ex[0]+"\"") 68 | } else { 69 | cmd = exec.Command("/bin/sh", "-c", "cd \""+a.dir+"\"; \"./"+a.ex[0]+"\"") 70 | } 71 | } 72 | cmd.Stdout = os.Stdout 73 | cmd.Stderr = os.Stderr 74 | cmd.Start() 75 | } else { 76 | var cmd *exec.Cmd 77 | if paDirs && strings.HasSuffix(strings.ToLower(a.ex[0]), ".appimage") { 78 | os.Mkdir(a.dir+"/"+a.ex[0]+".home", 0777) 79 | os.Mkdir(a.dir+"/"+a.ex[0]+".config", 0777) 80 | } 81 | if comEnbld { 82 | cmd = exec.Command("/bin/sh", "-c", "export APPNAME="+a.name+";export FILENAME="+a.ex[0]+";. PortableApps/LinuxPACom/common.sh || exit 1;cd \""+a.dir+"\"; \"./"+a.ex[0]+"\"") 83 | } else { 84 | cmd = exec.Command("/bin/sh", "-c", "cd \""+a.dir+"\"; \"./"+a.ex[0]+"\"") 85 | } 86 | cmd.Stdout = os.Stdout 87 | cmd.Stderr = os.Stderr 88 | cmd.Start() 89 | } 90 | } else { 91 | if wine { 92 | var cmd *exec.Cmd 93 | if len(a.lin) == 0 { 94 | if comEnbld { 95 | cmd = exec.Command("/bin/sh", "-c", "export APPNAME="+a.name+";export FILENAME="+a.ex[0]+";. PortableApps/LinuxPACom/common.sh || exit 1;cd \""+a.dir+"\"; wine \""+a.ex[0]+"\"") 96 | } else { 97 | cmd = exec.Command("/bin/sh", "-c", "cd \""+a.dir+"\"; wine \""+a.ex[0]+"\"") 98 | } 99 | } else { 100 | var ind int 101 | for i, v := range a.lin { 102 | if strings.HasSuffix(v, ".sh") { 103 | ind = i 104 | break 105 | } 106 | } 107 | if paDirs && strings.HasSuffix(strings.ToLower(a.ex[ind]), ".appimage") { 108 | os.Mkdir(a.dir+"/"+a.ex[ind]+".home", 0777) 109 | os.Mkdir(a.dir+"/"+a.ex[ind]+".config", 0777) 110 | } 111 | if comEnbld { 112 | cmd = exec.Command("/bin/sh", "-c", "export APPNAME="+a.name+";export FILENAME="+a.ex[ind]+";. PortableApps/LinuxPACom/common.sh || exit 1;cd \""+a.dir+"\"; \"./"+a.lin[ind]+"\"") 113 | } else { 114 | cmd = exec.Command("/bin/sh", "-c", "cd \""+a.dir+"\"; \"./"+a.lin[ind]+"\"") 115 | } 116 | } 117 | cmd.Stdout = os.Stdout 118 | cmd.Stderr = os.Stderr 119 | cmd.Start() 120 | } else { 121 | if len(a.lin) != 0 { 122 | var ind int 123 | for i, v := range a.lin { 124 | if strings.HasSuffix(v, ".sh") { 125 | ind = i 126 | break 127 | } 128 | } 129 | var cmd *exec.Cmd 130 | if paDirs && strings.HasSuffix(strings.ToLower(a.ex[ind]), ".appimage") { 131 | os.Mkdir(a.dir+"/"+a.ex[ind]+".home", 0777) 132 | os.Mkdir(a.dir+"/"+a.ex[ind]+".config", 0777) 133 | } 134 | if comEnbld { 135 | cmd = exec.Command("/bin/sh", "-c", "export APPNAME="+a.name+";export FILENAME="+a.ex[ind]+";. PortableApps/LinuxPACom/common.sh || exit 1;cd \""+a.dir+"\"; \"./"+a.lin[ind]+"\"") 136 | } else { 137 | cmd = exec.Command("/bin/sh", "-c", "cd \""+a.dir+"\"; \"./"+a.lin[ind]+"\"") 138 | } 139 | cmd.Stdout = os.Stdout 140 | cmd.Stderr = os.Stderr 141 | cmd.Start() 142 | } 143 | } 144 | } 145 | } 146 | 147 | func (a *app) launchSub(sub int) { 148 | if wine { 149 | var cmd *exec.Cmd 150 | if !contains(a.lin, a.ex[sub]) { 151 | if comEnbld { 152 | cmd = exec.Command("/bin/sh", "-c", "export APPNAME="+a.name+";export FILENAME="+a.ex[0]+";. PortableApps/LinuxPACom/common.sh || exit 1;cd \""+a.dir+"\"; wine \""+a.ex[sub]+"\"") 153 | } else { 154 | cmd = exec.Command("/bin/sh", "-c", "cd \""+a.dir+"\"; wine \""+a.ex[sub]+"\"") 155 | } 156 | } else { 157 | if paDirs && strings.HasSuffix(strings.ToLower(a.ex[sub]), ".appimage") { 158 | os.Mkdir(a.dir+"/"+a.ex[sub]+".home", 0777) 159 | os.Mkdir(a.dir+"/"+a.ex[sub]+".config", 0777) 160 | } 161 | if comEnbld { 162 | cmd = exec.Command("/bin/sh", "-c", "export APPNAME="+a.name+";export FILENAME="+a.ex[sub]+";. PortableApps/LinuxPACom/common.sh || exit 1;cd \""+a.dir+"\"; \"./"+a.ex[sub]+"\"") 163 | } else { 164 | cmd = exec.Command("/bin/sh", "-c", "cd \""+a.dir+"\"; \"./"+a.ex[sub]+"\"") 165 | } 166 | } 167 | cmd.Stdout = os.Stdout 168 | cmd.Stderr = os.Stderr 169 | cmd.Start() 170 | } else { 171 | var cmd *exec.Cmd 172 | if paDirs && strings.HasSuffix(strings.ToLower(a.ex[sub]), ".appimage") { 173 | os.Mkdir(a.dir+"/"+a.ex[sub]+".home", 0777) 174 | os.Mkdir(a.dir+"/"+a.ex[sub]+".config", 0777) 175 | } 176 | if comEnbld { 177 | cmd = exec.Command("/bin/sh", "-c", "export APPNAME="+a.name+";export FILENAME="+a.ex[sub]+";. PortableApps/LinuxPACom/common.sh || exit 1;cd \""+a.dir+"\"; \"./"+a.ex[sub]+"\"") 178 | } else { 179 | cmd = exec.Command("/bin/sh", "-c", "cd \""+a.dir+"\"; \"./"+a.ex[sub]+"\"") 180 | } 181 | cmd.Stdout = os.Stdout 182 | cmd.Stderr = os.Stderr 183 | cmd.Start() 184 | } 185 | } 186 | 187 | func (a *app) edit(parent *gtk.Window, reload func()) { 188 | tmp := *a 189 | parent.SetSensitive(false) 190 | win, _ := gtk.WindowNew(gtk.WINDOW_TOPLEVEL) 191 | win.Connect("destroy", func() { 192 | master = make(map[string][]app) 193 | linmaster = make(map[string][]app) 194 | cats = make([]string, 0) 195 | lin = make([]string, 0) 196 | setup() 197 | reload() 198 | parent.SetSensitive(true) 199 | }) 200 | win.SetDefaultSize(400, 135) 201 | topLvl, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 5) 202 | topLvl.SetMarginStart(10) 203 | topLvl.SetMarginEnd(10) 204 | topLvl.SetMarginTop(10) 205 | topLvl.SetMarginBottom(10) 206 | top, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 5) 207 | img, _ := gtk.ImageNewFromPixbuf(a.icon) 208 | imgBut, _ := gtk.ButtonNew() 209 | imgBut.SetImage(img) 210 | imgBut.SetSizeRequest(100, 100) 211 | imgBut.Connect("clicked", func() { 212 | fil, _ := gtk.FileChooserDialogNewWith1Button("Select Icon", win, gtk.FILE_CHOOSER_ACTION_OPEN, "Open", gtk.RESPONSE_ACCEPT) 213 | filter, _ := gtk.FileFilterNew() 214 | filter.AddPixbufFormats() 215 | filter.SetName("Supported Pictures") 216 | fil.AddFilter(filter) 217 | but, _ := fil.AddButton("Cancel", gtk.RESPONSE_CANCEL) 218 | but.Connect("clicked", func() { 219 | fil.Close() 220 | }) 221 | resp := fil.Run() 222 | if resp == gtk.RESPONSE_ACCEPT { 223 | filename := fil.GetFilename() 224 | _, err := os.Open(filename) 225 | if err != nil { 226 | fmt.Println(err) 227 | return 228 | } 229 | pix, _ := gdk.PixbufNewFromFileAtSize(filename, 32, 32) 230 | tmp.icon = pix 231 | img.SetFromPixbuf(pix) 232 | fil.Close() 233 | } 234 | }) 235 | topRt, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 5) 236 | nameLbl, _ := gtk.LabelNew("Name:") 237 | nameLbl.SetHAlign(gtk.ALIGN_START) 238 | txtgtbl, _ := gtk.TextTagTableNew() 239 | txtBuf, _ := gtk.TextBufferNew(txtgtbl) 240 | nameTxt, _ := gtk.TextViewNewWithBuffer(txtBuf) 241 | nameTxt.SetAcceptsTab(false) 242 | nameTxt.SetWrapMode(gtk.WRAP_CHAR) 243 | nameTxt.SetPixelsBelowLines(5) 244 | nameTxt.SetHExpand(true) 245 | nameTxt.SetVExpand(false) 246 | nameTxt.SetBorderWindowSize(gtk.TEXT_WINDOW_BOTTOM, 5) 247 | nameShow := tmp.name 248 | if tmp.wine { 249 | nameShow = strings.TrimSuffix(nameShow, " (Wine)") 250 | } 251 | txtBuf.SetText(nameShow) 252 | vScrollName, _ := gtk.AdjustmentNew(0, 0, 0, 0, 0, 0) 253 | hScrollName, _ := gtk.AdjustmentNew(0, 0, 0, 0, 0, 0) 254 | nameScr, _ := gtk.ScrolledWindowNew(hScrollName, vScrollName) 255 | nameScr.SetPolicy(gtk.POLICY_AUTOMATIC, gtk.POLICY_NEVER) 256 | nameScr.SetSizeRequest(300, 25) 257 | nameScr.SetVExpand(false) 258 | nameScr.Add(nameTxt) 259 | catLbl, _ := gtk.LabelNew("Category:") 260 | catLbl.SetHAlign(gtk.ALIGN_START) 261 | catTbl, _ := gtk.TextTagTableNew() 262 | catBuf, _ := gtk.TextBufferNew(catTbl) 263 | catTxt, _ := gtk.TextViewNewWithBuffer(catBuf) 264 | catBuf.SetText(tmp.cat) 265 | catTxt.SetAcceptsTab(false) 266 | catTxt.SetWrapMode(gtk.WRAP_CHAR) 267 | catTxt.SetPixelsBelowLines(5) 268 | catTxt.SetHExpand(true) 269 | catTxt.SetVExpand(false) 270 | catTxt.SetBorderWindowSize(gtk.TEXT_WINDOW_BOTTOM, 5) 271 | vScrollCat, _ := gtk.AdjustmentNew(0, 0, 0, 0, 0, 0) 272 | hScrollCat, _ := gtk.AdjustmentNew(0, 0, 0, 0, 0, 0) 273 | catScr, _ := gtk.ScrolledWindowNew(hScrollCat, vScrollCat) 274 | catScr.SetPolicy(gtk.POLICY_AUTOMATIC, gtk.POLICY_NEVER) 275 | catScr.SetSizeRequest(300, 25) 276 | catScr.SetVExpand(false) 277 | catScr.Add(catTxt) 278 | topRt.Add(nameLbl) 279 | topRt.Add(nameScr) 280 | topRt.Add(catLbl) 281 | topRt.Add(catScr) 282 | top.Add(imgBut) 283 | top.Add(topRt) 284 | bot, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 5) 285 | sv, _ := gtk.ButtonNewWithLabel("Save") 286 | sv.Connect("clicked", func() { 287 | tmp.name, _ = txtBuf.GetText(txtBuf.GetStartIter(), txtBuf.GetEndIter(), true) 288 | tmp.cat, _ = catBuf.GetText(catBuf.GetStartIter(), catBuf.GetEndIter(), true) 289 | tmp.makeIni() 290 | os.Remove(a.dir + "/appicon.png") 291 | tmp.icon.SavePNG(a.dir+"/appicon.png", 0) 292 | win.Close() 293 | }) 294 | cnl, _ := gtk.ButtonNewWithLabel("Cancel") 295 | cnl.Connect("clicked", func() { 296 | win.Close() 297 | }) 298 | bot.PackEnd(sv, false, false, 0) 299 | bot.PackEnd(cnl, false, false, 0) 300 | topLvl.Add(top) 301 | topLvl.Add(bot) 302 | win.Add(topLvl) 303 | win.ShowAll() 304 | win.Show() 305 | } 306 | 307 | func (a *app) makeIni() { 308 | os.Remove(a.dir + "/appinfo.ini") 309 | fil, err := os.Create(a.dir + "/appinfo.ini") 310 | if err != nil { 311 | return 312 | } 313 | ini := "[General]\n" 314 | ini += "Category=" + a.cat + "\n" 315 | ini += "Name=" + a.name + "\n" 316 | wrt := bufio.NewWriter(fil) 317 | wrt.WriteString(ini) 318 | wrt.Flush() 319 | } 320 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/CalebQ42/LinuxPA 2 | 3 | go 1.23.1 4 | 5 | require ( 6 | github.com/CalebQ42/GoAppImage v0.5.0 7 | github.com/gotk3/gotk3 v0.6.4 8 | github.com/mholt/archiver/v3 v3.5.1 9 | github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c 10 | ) 11 | 12 | require ( 13 | github.com/adrg/xdg v0.5.0 // indirect 14 | github.com/andybalholm/brotli v1.1.0 // indirect 15 | github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect 16 | github.com/golang/snappy v0.0.4 // indirect 17 | github.com/klauspost/compress v1.17.10 // indirect 18 | github.com/klauspost/pgzip v1.2.6 // indirect 19 | github.com/nwaples/rardecode v1.1.3 // indirect 20 | github.com/pierrec/lz4/v4 v4.1.21 // indirect 21 | github.com/ulikunitz/xz v0.5.12 // indirect 22 | github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect 23 | go.lsp.dev/uri v0.3.0 // indirect 24 | golang.org/x/sys v0.25.0 // indirect 25 | gopkg.in/ini.v1 v1.67.0 // indirect 26 | ) 27 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/CalebQ42/GoAppImage v0.5.0 h1:znoKNXtliH754tS9sYwyOIg/0wFDjFN5Twc7PAh1rSM= 2 | github.com/CalebQ42/GoAppImage v0.5.0/go.mod h1:qHudJKAn/dlkNWNnH4h1YKXp29EZ7Bppsn7sNP2HuvU= 3 | github.com/adrg/xdg v0.2.2/go.mod h1:7I2hH/IT30IsupOpKZ5ue7/qNi3CoKzD6tL3HwpaRMQ= 4 | github.com/adrg/xdg v0.5.0 h1:dDaZvhMXatArP1NPHhnfaQUqWBLBsmx1h1HXQdMoFCY= 5 | github.com/adrg/xdg v0.5.0/go.mod h1:dDdY4M4DF9Rjy4kHPeNL+ilVF+p2lK8IdM9/rTSGcI4= 6 | github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= 7 | github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= 8 | github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= 9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY= 13 | github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= 14 | github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= 15 | github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 16 | github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= 17 | github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 18 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 19 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 20 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 21 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 22 | github.com/gotk3/gotk3 v0.6.4 h1:5ur/PRr86PwCG8eSj98D1eXvhrNNK6GILS2zq779dCg= 23 | github.com/gotk3/gotk3 v0.6.4/go.mod h1:/hqFpkNa9T3JgNAE2fLvCdov7c5bw//FHNZrZ3Uv9/Q= 24 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 25 | github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= 26 | github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= 27 | github.com/klauspost/compress v1.17.10 h1:oXAz+Vh0PMUvJczoi+flxpnBEPxoER1IaAnU/NMPtT0= 28 | github.com/klauspost/compress v1.17.10/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= 29 | github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= 30 | github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= 31 | github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= 32 | github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= 33 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 34 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 35 | github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo= 36 | github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4= 37 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 38 | github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= 39 | github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc= 40 | github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= 41 | github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= 42 | github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= 43 | github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= 44 | github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= 45 | github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= 46 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 47 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 48 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 49 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 50 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 51 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 52 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 53 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 54 | github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= 55 | github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= 56 | github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= 57 | github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= 58 | github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= 59 | github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= 60 | go.lsp.dev/uri v0.3.0 h1:KcZJmh6nFIBeJzTugn5JTU6OOyG0lDOo3R9KwTxTYbo= 61 | go.lsp.dev/uri v0.3.0/go.mod h1:P5sbO1IQR+qySTWOCnhnK7phBx+W3zbLqSMDJNTw88I= 62 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 63 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 64 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 65 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 66 | golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= 67 | golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 68 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 69 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 70 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 71 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 72 | gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 73 | gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 74 | gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= 75 | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 76 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 77 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 78 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 79 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/gotk3/gotk3/gtk" 9 | ) 10 | 11 | const ( 12 | version = "2.2.0.0" 13 | ) 14 | 15 | var ( 16 | master map[string][]app 17 | linmaster map[string][]app 18 | cats []string 19 | lin []string 20 | comEnbld bool 21 | populated bool 22 | ) 23 | 24 | func main() { 25 | forced := flag.Bool("force-update", false, "Force the update dialog to be shown") 26 | flag.Parse() 27 | os.MkdirAll("PortableApps/LinuxPACom", 0777) 28 | master = make(map[string][]app) 29 | linmaster = make(map[string][]app) 30 | uiStart(*forced) 31 | } 32 | 33 | func uiStart(forced bool) { 34 | gtk.Init(nil) 35 | setup() 36 | win, err := gtk.WindowNew(gtk.WINDOW_TOPLEVEL) 37 | if err != nil { 38 | fmt.Println("Window not created", err) 39 | } 40 | win.SetTitle("LinuxPA") 41 | win.Connect("destroy", func() { 42 | savePrefs() 43 | gtk.MainQuit() 44 | }) 45 | win.SetDefaultSize(500, 500) 46 | win.SetPosition(gtk.WIN_POS_CENTER) 47 | ui(win) 48 | win.ShowAll() 49 | win.Show() 50 | update(win, forced) 51 | gtk.Main() 52 | } 53 | 54 | func contains(arr []string, str string) bool { 55 | for _, v := range arr { 56 | if v == str { 57 | return true 58 | } 59 | } 60 | return false 61 | } 62 | -------------------------------------------------------------------------------- /preferences.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/gob" 5 | "os" 6 | ) 7 | 8 | var ( 9 | wine bool 10 | wineAvail bool 11 | portableHide bool 12 | betaUpdate bool 13 | versionNewest = true 14 | paDirs = true 15 | ) 16 | 17 | func savePrefs() { 18 | os.Remove("PortableApps/LinuxPACom/Prefs.gob") 19 | fil, err := os.Create("PortableApps/LinuxPACom/Prefs.gob") 20 | if err != nil { 21 | return 22 | } 23 | enc := gob.NewEncoder(fil) 24 | err = enc.Encode(wine) 25 | if err != nil { 26 | return 27 | } 28 | err = enc.Encode(portableHide) 29 | if err != nil { 30 | return 31 | } 32 | err = enc.Encode(versionNewest) 33 | if err != nil { 34 | return 35 | } 36 | err = enc.Encode(paDirs) 37 | if err != nil { 38 | return 39 | } 40 | err = enc.Encode(betaUpdate) 41 | if err != nil { 42 | return 43 | } 44 | } 45 | 46 | func loadPrefs() { 47 | fil, err := os.Open("PortableApps/LinuxPACom/Prefs.gob") 48 | if err != nil { 49 | return 50 | } 51 | dec := gob.NewDecoder(fil) 52 | err = dec.Decode(&wine) 53 | if err != nil { 54 | return 55 | } 56 | err = dec.Decode(&portableHide) 57 | if err != nil { 58 | return 59 | } 60 | err = dec.Decode(&versionNewest) 61 | if err != nil { 62 | return 63 | } 64 | err = dec.Decode(&paDirs) 65 | if err != nil { 66 | return 67 | } 68 | err = dec.Decode(&betaUpdate) 69 | if err != nil { 70 | return 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /settings.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "os/exec" 7 | 8 | "github.com/gotk3/gotk3/glib" 9 | "github.com/gotk3/gotk3/gtk" 10 | ) 11 | 12 | const ( 13 | commonHelp = "The common.sh is run before every app is launched and allows you to set variables such as $HOME. For directories, ALWAYS start the directory with $PWD which points to the directory where LinuxPA is. To allow for greater customization and isolation, you can use the $FILENAME variable which is the filename of the executable you're using and the $APPNAME variable which is the name of the app being lanched." 14 | ) 15 | 16 | func settingsUI(parent *gtk.Window, onExit func()) { 17 | win, _ := gtk.WindowNew(gtk.WINDOW_TOPLEVEL) 18 | win.SetTransientFor(parent) 19 | parent.SetSensitive(false) 20 | win.SetDefaultSize(600, 300) 21 | win.SetPosition(gtk.WIN_POS_CENTER_ON_PARENT) 22 | win.Connect("destroy", func() { 23 | savePrefs() 24 | parent.SetSensitive(true) 25 | onExit() 26 | }) 27 | comTagTbl, _ := gtk.TextTagTableNew() 28 | comBuf, _ := gtk.TextBufferNew(comTagTbl) 29 | ntbk, _ := gtk.NotebookNew() 30 | gnrl, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 5) 31 | gnrl.SetMarginStart(10) 32 | gnrl.SetMarginEnd(10) 33 | gnrl.SetMarginTop(10) 34 | gnrl.SetMarginBottom(10) 35 | dlWine, _ := gtk.ButtonNewWithLabel("Download Wine") 36 | wineCheck, _ := gtk.CheckButtonNewWithLabel("Show Windows apps (Wine)") 37 | wineLbl, _ := gtk.LabelNew("PortableApps/LinuxPACom/Wine present") 38 | dlWine.Connect("clicked", func() { 39 | cb := make(chan bool) 40 | downloadWine(win, cb) 41 | go func() { 42 | v := <-cb 43 | if v { 44 | setupTxt(comBuf) 45 | wineLbl.Show() 46 | } 47 | if _, err := os.Open("PortableApps/LinuxPACom/Wine"); os.IsNotExist(err) { 48 | if _, errd := exec.LookPath("wine"); errd == nil { 49 | wineAvail = true 50 | } 51 | } else if err == nil { 52 | wineAvail = true 53 | } 54 | glib.IdleAdd(func() { 55 | if !wineAvail { 56 | wineCheck.SetSensitive(false) 57 | wineCheck.SetTooltipText("Download wine to run windows apps") 58 | } else { 59 | wineCheck.SetSensitive(true) 60 | wineCheck.SetTooltipText("") 61 | } 62 | }) 63 | }() 64 | }) 65 | if !comEnbld { 66 | dlWine.SetSensitive(false) 67 | dlWine.SetTooltipText("common.sh needed") 68 | } 69 | pthdCheck, _ := gtk.CheckButtonNewWithLabel("Hide \"Portable\" from app name") 70 | pthdCheck.Connect("toggled", func() { 71 | portableHide = pthdCheck.GetActive() 72 | master = make(map[string][]app) 73 | linmaster = make(map[string][]app) 74 | cats = make([]string, 0) 75 | lin = make([]string, 0) 76 | setup() 77 | }) 78 | pthdCheck.SetActive(portableHide) 79 | if !wineAvail { 80 | wineCheck.SetSensitive(false) 81 | wineCheck.SetTooltipText("Download wine to run windows apps") 82 | } 83 | wineCheck.SetActive(wine) 84 | wineCheck.Connect("toggled", func() { 85 | wine = wineCheck.GetActive() 86 | }) 87 | versCheck, _ := gtk.CheckButtonNewWithLabel("Only show newest app version in downloads (A bit iffy ATM)") 88 | versCheck.SetActive(versionNewest) 89 | versCheck.Connect("toggled", func() { 90 | versionNewest = versCheck.GetActive() 91 | }) 92 | paDirsCheck, _ := gtk.CheckButtonNewWithLabel("Create .home and .config directories for AppImages") 93 | paDirsCheck.SetActive(paDirs) 94 | paDirsCheck.Connect("toggled", func() { 95 | paDirs = paDirsCheck.GetActive() 96 | }) 97 | betaCheck, _ := gtk.CheckButtonNewWithLabel("Update to beta releases") 98 | gnrl.Add(wineLbl) 99 | gnrl.Add(dlWine) 100 | gnrl.Add(pthdCheck) 101 | gnrl.Add(wineCheck) 102 | gnrl.Add(versCheck) 103 | gnrl.Add(paDirsCheck) 104 | gnrl.Add(betaCheck) 105 | ntbk.AppendPage(gnrl, getLabel("General")) 106 | com, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 5) 107 | com.SetMarginStart(10) 108 | com.SetMarginEnd(10) 109 | com.SetMarginTop(10) 110 | com.SetMarginBottom(10) 111 | comEdit, _ := gtk.TextViewNewWithBuffer(comBuf) 112 | comEdit.SetVExpand(true) 113 | comEdit.SetHExpand(true) 114 | vScroll, _ := gtk.AdjustmentNew(0, 0, 0, 0, 0, 0) 115 | hScroll, _ := gtk.AdjustmentNew(0, 0, 0, 0, 0, 0) 116 | comScrl, _ := gtk.ScrolledWindowNew(hScroll, vScroll) 117 | comScrl.Add(comEdit) 118 | svBox, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 5) 119 | sv, _ := gtk.ButtonNewWithLabel("Save") 120 | sv.Connect("clicked", func() { 121 | beg, end := comBuf.GetBounds() 122 | txt, _ := comBuf.GetText(beg, end, true) 123 | ioutil.WriteFile("PortableApps/LinuxPACom/common.sh", []byte(txt), 0777) 124 | }) 125 | cnl, _ := gtk.ButtonNewWithLabel("Cancel") 126 | cnl.Connect("clicked", func() { 127 | setupTxt(comBuf) 128 | }) 129 | info, _ := gtk.ButtonNewWithLabel("Info") 130 | info.Connect("clicked", func() { 131 | infoBox, _ := gtk.WindowNew(gtk.WINDOW_TOPLEVEL) 132 | infoBox.SetTransientFor(parent) 133 | infoBox.SetDefaultSize(300, 80) 134 | infoBox.SetName("common.sh info") 135 | infoBox.SetPosition(gtk.WIN_POS_CENTER_ON_PARENT) 136 | box, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 5) 137 | infolbl, _ := gtk.LabelNew(commonHelp) 138 | infolbl.SetLineWrap(true) 139 | infolbl.SetSizeRequest(200, 50) 140 | box.Add(infolbl) 141 | infoBox.Add(box) 142 | infoBox.ShowAll() 143 | infoBox.Show() 144 | }) 145 | svBox.Add(sv) 146 | svBox.Add(cnl) 147 | svBox.Add(info) 148 | com.Add(comScrl) 149 | com.Add(svBox) 150 | ntbk.AppendPage(com, getLabel("common.sh")) 151 | win.Add(ntbk) 152 | win.ShowAll() 153 | if !comEnbld { 154 | comScrl.Hide() 155 | svBox.Hide() 156 | mkCom, _ := gtk.ButtonNewWithLabel("Create common.sh") 157 | mkCom.Connect("clicked", func() { 158 | err := ioutil.WriteFile("PortableApps/LinuxPACom/common.sh", []byte("export HOME=$PWD/PortableApps/LinuxPACom/Home"), 0777) 159 | if err == nil { 160 | mkCom.Hide() 161 | comScrl.Show() 162 | svBox.Show() 163 | setupTxt(comBuf) 164 | comEnbld = true 165 | dlWine.SetSensitive(true) 166 | dlWine.SetTooltipText("") 167 | } 168 | }) 169 | in, _ := gtk.ButtonNewWithLabel("Info") 170 | in.Connect("clicked", func() { 171 | infoBox, _ := gtk.WindowNew(gtk.WINDOW_TOPLEVEL) 172 | infoBox.SetTransientFor(parent) 173 | infoBox.SetDefaultSize(300, 80) 174 | infoBox.SetName("common.sh info") 175 | infoBox.SetPosition(gtk.WIN_POS_CENTER_ON_PARENT) 176 | box, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 5) 177 | infolbl, _ := gtk.LabelNew(commonHelp) 178 | infolbl.SetLineWrap(true) 179 | infolbl.SetSizeRequest(200, 50) 180 | box.Add(infolbl) 181 | infoBox.Add(box) 182 | infoBox.ShowAll() 183 | infoBox.Show() 184 | }) 185 | mkCom.Show() 186 | com.Add(mkCom) 187 | com.Add(in) 188 | } else { 189 | setupTxt(comBuf) 190 | } 191 | if _, err := os.Open("PortableApps/LinuxPACom/Wine"); err != nil && os.IsNotExist(err) { 192 | wineLbl.Hide() 193 | } 194 | win.Show() 195 | } 196 | 197 | func setupTxt(buf *gtk.TextBuffer) { 198 | fil, _ := os.Open("PortableApps/LinuxPACom/common.sh") 199 | btys, _ := ioutil.ReadAll(fil) 200 | buf.SetText(string(btys)) 201 | } 202 | 203 | func getLabel(name string) *gtk.Label { 204 | lbl, _ := gtk.LabelNew(name) 205 | return lbl 206 | } 207 | -------------------------------------------------------------------------------- /setup.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | _ "image/png" 6 | "os" 7 | "os/exec" 8 | "reflect" 9 | "sort" 10 | "strings" 11 | 12 | goappimage "github.com/CalebQ42/GoAppImage" 13 | "github.com/gotk3/gotk3/gdk" 14 | "github.com/gotk3/gotk3/gtk" 15 | ) 16 | 17 | func setup() { 18 | loadPrefs() 19 | if _, err := os.Open("PortableApps/LinuxPACom/Wine"); os.IsNotExist(err) { 20 | if _, errd := exec.LookPath("wine"); errd == nil { 21 | wineAvail = true 22 | } 23 | } else if err == nil { 24 | wineAvail = true 25 | } 26 | if !wineAvail { 27 | wine = false 28 | } 29 | PortableAppsFold, err := os.Open("PortableApps") 30 | if PAStat, _ := PortableAppsFold.Stat(); err != nil || !PAStat.IsDir() { 31 | os.Mkdir("PortableApps", 0777) 32 | PortableAppsFold, err = os.Open("PortableApps") 33 | if err != nil { 34 | panic("Can't find PortableApps folder and can't create one!") 35 | } 36 | } 37 | if _, err = os.Open("PortableApps/LinuxPACom"); err != nil { 38 | os.Mkdir("PortableApps/LinuxPACom", 0777) 39 | } 40 | _, err = os.Open("PortableApps/LinuxPACom/common.sh") 41 | if err == nil { 42 | comEnbld = true 43 | } 44 | PAFolds, _ := PortableAppsFold.Readdirnames(-1) 45 | sort.Strings(PAFolds) 46 | for _, v := range PAFolds { 47 | fold, _ := os.Open("PortableApps/" + v) 48 | if stat, _ := fold.Stat(); stat.IsDir() && stat.Name() != "PortableApps.com" && stat.Name() != "LinuxPACom" { 49 | ap := processApp("PortableApps/" + v) 50 | if !reflect.DeepEqual(ap, app{}) { 51 | if _, ok := master[ap.cat]; !ok { 52 | cats = append(cats, ap.cat) 53 | sort.Strings(cats) 54 | } 55 | if len(ap.lin) != 0 { 56 | if _, ok := linmaster[ap.cat]; !ok { 57 | lin = append(lin, ap.cat) 58 | sort.Strings(lin) 59 | } 60 | } 61 | master[ap.cat] = append(master[ap.cat], ap) 62 | if len(ap.lin) != 0 { 63 | linmaster[ap.cat] = append(linmaster[ap.cat], ap) 64 | } 65 | } 66 | } 67 | } 68 | populated = true 69 | } 70 | 71 | func processApp(fold string) (out app) { 72 | wd, _ := os.Getwd() 73 | out.dir = wd + "/" + fold 74 | folder, _ := os.Open(fold) 75 | fis, _ := folder.Readdirnames(-1) 76 | for _, v := range fis { 77 | tmp, _ := os.Open(fold + "/" + v) 78 | stat, _ := tmp.Stat() 79 | if stat.IsDir() { 80 | continue 81 | } 82 | //TODO: check permission to see if it has exec permission 83 | if strings.HasSuffix(strings.ToLower(v), ".appimage") { 84 | out.appimg = append(out.appimg, v) 85 | out.ex = append(out.ex, v) 86 | out.lin = append(out.lin, v) 87 | } else if strings.HasSuffix(strings.ToLower(v), ".exe") { 88 | out.ex = append(out.ex, v) 89 | } else { 90 | btys := make([]byte, 4) 91 | rdr := bufio.NewReader(tmp) 92 | rdr.Read(btys) 93 | if (strings.Contains(strings.ToLower(string(btys)), "elf") && !strings.HasSuffix(strings.ToLower(v), ".so") && !strings.Contains(v, ".so.")) || strings.HasPrefix(strings.ToLower(string(btys)), "#!") { 94 | out.ex = append(out.ex, v) 95 | out.lin = append(out.lin, v) 96 | } 97 | } 98 | } 99 | if len(out.ex) == 0 { 100 | return app{} 101 | } 102 | if len(out.lin) == 0 { 103 | out.name += " (Wine)" 104 | out.wine = true 105 | } 106 | out.icon = getIcon(fold) 107 | out.ini = findInfo(fold) 108 | if out.ini != nil { 109 | out.name = getName(out.ini) 110 | out.cat = getCat(out.ini) 111 | } 112 | if len(out.appimg) > 0 && out.name == "" && out.cat == "" && out.icon == nil { 113 | os.Mkdir(out.dir+"/.appimageconfig", 0777) 114 | ai := goappimage.NewAppImage(out.dir + "/" + out.appimg[0]) 115 | fil, err := os.Open(out.dir + "/.appimageconfig/the.md5") 116 | if os.IsNotExist(err) { 117 | ai.ExtractFile("*.desktop", out.dir+"/.appimageconfig/", false) 118 | appimageconfig, _ := os.Open(out.dir + "/.appimageconfig") 119 | appdirs, _ := appimageconfig.Readdirnames(-1) 120 | for _, dirs := range appdirs { 121 | desktopFil, _ := os.Open(out.dir + "/.appimageconfig/" + dirs) 122 | if stat, _ := desktopFil.Stat(); strings.HasSuffix(dirs, ".desktop") && !stat.IsDir() { 123 | os.Rename(out.dir+"/.appimageconfig/"+dirs, out.dir+"/.appimageconfig/the.desktop") 124 | break 125 | } 126 | } 127 | desk, _ := os.Open(out.dir + "/.appimageconfig/the.desktop") 128 | name, cat, icon := extractDesktopInfo(desk) 129 | if out.name == "" { 130 | out.name = name 131 | } 132 | if out.cat == "" { 133 | out.cat = cat 134 | } 135 | if out.icon == nil { 136 | it, _ := gtk.IconThemeGetDefault() 137 | out.icon, err = it.LoadIcon(icon, 64, gtk.ICON_LOOKUP_GENERIC_FALLBACK) 138 | } 139 | fil, _ = os.Create(out.dir + "/.appimageconfig/the.md5") 140 | wrtr := bufio.NewWriter(fil) 141 | wrtr.WriteString(ai.Md5) 142 | wrtr.Flush() 143 | } else { 144 | rdr := bufio.NewReader(fil) 145 | filMd, _, _ := rdr.ReadLine() 146 | oldMd := string(filMd) 147 | if oldMd != ai.Md5 { 148 | ai.ExtractFile("*.desktop", out.dir+"/.appimageconfig/", false) 149 | appimageconfig, _ := os.Open(out.dir + "/.appimageconfig") 150 | appdirs, _ := appimageconfig.Readdirnames(-1) 151 | for _, dirs := range appdirs { 152 | desktopFil, _ := os.Open(out.dir + "/.appimageconfig/" + dirs) 153 | if stat, _ := desktopFil.Stat(); strings.HasSuffix(dirs, ".desktop") && !stat.IsDir() { 154 | os.Rename(out.dir+"/.appimageconfig/"+dirs, out.dir+"/.appimageconfig/the.desktop") 155 | break 156 | } 157 | } 158 | os.Remove(out.dir + "/.appimageconfig/the.md5") 159 | fil, _ = os.Create(out.dir + "/.appimageconfig/the.md5") 160 | wrtr := bufio.NewWriter(fil) 161 | wrtr.WriteString(ai.Md5) 162 | wrtr.Flush() 163 | } 164 | } 165 | desk, _ := os.Open(out.dir + "/.appimageconfig/the.desktop") 166 | name, cat, icon := extractDesktopInfo(desk) 167 | if out.name == "" { 168 | out.name = name 169 | } 170 | if out.cat == "" { 171 | out.cat = cat 172 | } 173 | if out.icon == nil { 174 | it, _ := gtk.IconThemeGetDefault() 175 | out.icon, err = it.LoadIcon(icon, 32, gtk.ICON_LOOKUP_GENERIC_FALLBACK) 176 | } 177 | } 178 | if out.name == "" { 179 | out.name = strings.TrimPrefix(fold, "PortableApps/") 180 | } 181 | if out.cat == "" { 182 | out.cat = "Other" 183 | } 184 | if portableHide { 185 | out.name = strings.TrimSuffix(out.name, "Portable") 186 | } 187 | out.name = strings.TrimSpace(out.name) 188 | return 189 | } 190 | 191 | func getCat(ini *os.File) string { 192 | rdr := bufio.NewReader(ini) 193 | var ret string 194 | for line, _, err := rdr.ReadLine(); err == nil; line, _, err = rdr.ReadLine() { 195 | if strings.HasPrefix(string(line), "Category=") { 196 | ret = strings.TrimPrefix(string(line), "Category=") 197 | break 198 | } else if strings.HasPrefix(string(line), "category=") { 199 | ret = strings.TrimPrefix(string(line), "category=") 200 | } 201 | } 202 | rdr.Reset(ini) 203 | return ret 204 | } 205 | 206 | func extractDesktopInfo(desk *os.File) (name, category, iconName string) { 207 | rdr := bufio.NewReader(desk) 208 | var nameGot, catGot, iconGot bool 209 | for line, _, err := rdr.ReadLine(); err == nil; line, _, err = rdr.ReadLine() { 210 | ln := string(line) 211 | if !nameGot && strings.HasPrefix(ln, "Name=") { 212 | name = strings.TrimPrefix(ln, "Name=") 213 | nameGot = true 214 | } else if !catGot && strings.HasPrefix(ln, "Categories=") { 215 | cats := strings.Split(strings.TrimPrefix(ln, "Categories="), ";") 216 | if len(cats) > 0 { 217 | category = cats[0] 218 | } 219 | catGot = true 220 | } else if !iconGot && strings.HasPrefix(ln, "Icon=") { 221 | iconName = strings.TrimPrefix(ln, "Icon=") 222 | iconGot = true 223 | } 224 | if nameGot && catGot && iconGot { 225 | break 226 | } 227 | } 228 | return 229 | } 230 | 231 | func getName(ini *os.File) string { 232 | rdr := bufio.NewReader(ini) 233 | var ret string 234 | for line, _, err := rdr.ReadLine(); err == nil; line, _, err = rdr.ReadLine() { 235 | if strings.HasPrefix(string(line), "Name=") { 236 | ret = strings.TrimPrefix(string(line), "Name=") 237 | break 238 | } else if strings.HasPrefix(string(line), "name=") { 239 | ret = strings.TrimPrefix(string(line), "name=") 240 | break 241 | } 242 | } 243 | rdr.Reset(ini) 244 | return ret 245 | } 246 | 247 | func getIcon(fold string) *gdk.Pixbuf { 248 | var pic string 249 | if _, err := os.Open(fold + "/appicon.png"); err == nil { 250 | pic = fold + "/appicon.png" 251 | } else if folder, err := os.Open(fold + "/App/AppInfo"); err == nil { 252 | fis, _ := folder.Readdir(-1) 253 | var pics []string 254 | for _, v := range fis { 255 | if !v.IsDir() && strings.HasSuffix(strings.ToLower(v.Name()), ".png") && strings.HasPrefix(strings.ToLower(v.Name()), "appicon_") { 256 | pics = append(pics, v.Name()) 257 | } 258 | } 259 | sort.Strings(pics) 260 | if len(pics) > 1 { 261 | var ind int 262 | if !contains(pics, "appicon_32.png") { 263 | ind = len(pics) - 1 264 | } else { 265 | ind = sort.SearchStrings(pics, "appicon_32.png") 266 | } 267 | pic = fold + "/App/AppInfo/" + pics[ind] 268 | } 269 | } else { 270 | img, _ := gtk.ImageNewFromIconName("application-x-executable", gtk.ICON_SIZE_BUTTON) 271 | buf := img.GetPixbuf() 272 | return buf 273 | } 274 | img, _ := gtk.ImageNewFromFile(pic) 275 | buf, _ := img.GetPixbuf().ScaleSimple(32, 32, gdk.INTERP_BILINEAR) 276 | return buf 277 | } 278 | 279 | func findInfo(fold string) *os.File { 280 | if fi, err := os.Open(fold + "/appinfo.ini"); err == nil { 281 | return fi 282 | } 283 | tmp, err := os.Open(fold + "/App/AppInfo") 284 | if err == nil { 285 | fis, _ := tmp.Readdirnames(-1) 286 | for _, v := range fis { 287 | if strings.ToLower(v) == "appinfo.ini" { 288 | tmp, _ := os.Open(fold + "/App/AppInfo/" + v) 289 | return tmp 290 | } 291 | } 292 | } 293 | return nil 294 | } 295 | -------------------------------------------------------------------------------- /ui.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gotk3/gotk3/glib" 5 | "github.com/gotk3/gotk3/gtk" 6 | "github.com/pkg/browser" 7 | ) 8 | 9 | func ui(win *gtk.Window) { 10 | ls := getCatRows() 11 | var treeApps []*gtk.TreeIter 12 | header, _ := gtk.HeaderBarNew() 13 | header.SetShowCloseButton(true) 14 | header.SetTitle("LinuxPA") 15 | header.SetSubtitle("PortableApps.com type launcher") 16 | settings, _ := gtk.ButtonNewFromIconName("applications-system", gtk.ICON_SIZE_SMALL_TOOLBAR) 17 | settings.SetTooltipText("Settings") 18 | dnl, _ := gtk.ButtonNewFromIconName("emblem-downloads", gtk.ICON_SIZE_SMALL_TOOLBAR) 19 | dnl.SetTooltipText("Download Apps") 20 | header.PackStart(settings) 21 | header.PackEnd(dnl) 22 | win.SetTitlebar(header) 23 | topLvl, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0) 24 | lrBox, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 5) 25 | catList, _ := gtk.ListBoxNew() 26 | catList.SetActivateOnSingleClick(true) 27 | store, _ := gtk.TreeStoreNew(glib.TYPE_OBJECT, glib.TYPE_STRING) 28 | appsList, _ := gtk.TreeViewNewWithModel(store) 29 | render, _ := gtk.CellRendererPixbufNew() 30 | pixColumn, _ := gtk.TreeViewColumnNewWithAttribute("", render, "pixbuf", 0) 31 | txtRender, _ := gtk.CellRendererTextNew() 32 | txtColumn, _ := gtk.TreeViewColumnNewWithAttribute("", txtRender, "text", 1) 33 | appsList.AppendColumn(pixColumn) 34 | appsList.AppendColumn(txtColumn) 35 | appsList.SetHeadersVisible(false) 36 | catList.SetHExpand(true) 37 | catList.SetVExpand(true) 38 | appsList.SetHExpand(true) 39 | appsList.SetVExpand(true) 40 | vScrollCat, _ := gtk.AdjustmentNew(0, 0, 0, 0, 0, 0) 41 | hScrollCat, _ := gtk.AdjustmentNew(0, 0, 0, 0, 0, 0) 42 | vScrollApp, _ := gtk.AdjustmentNew(0, 0, 0, 0, 0, 0) 43 | hScrollApp, _ := gtk.AdjustmentNew(0, 0, 0, 0, 0, 0) 44 | catScrl, _ := gtk.ScrolledWindowNew(hScrollCat, vScrollCat) 45 | catScrl.Add(catList) 46 | catScrl.SetSizeRequest(170, 500) 47 | appScrl, _ := gtk.ScrolledWindowNew(hScrollApp, vScrollApp) 48 | appScrl.Add(appsList) 49 | appScrl.SetSizeRequest(300, 500) 50 | lrBox.Add(catScrl) 51 | lrBox.Add(appScrl) 52 | botBox, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 2) 53 | botBox.SetMarginStart(10) 54 | botBox.SetMarginEnd(10) 55 | botBox.SetMarginTop(10) 56 | botBox.SetMarginBottom(10) 57 | edit, _ := gtk.ButtonNewWithLabel("Edit App..") 58 | edit.Connect("clicked", func() { 59 | selec, _ := appsList.GetSelection() 60 | _, it, ok := selec.GetSelected() 61 | if ok { 62 | pth, _ := store.GetPath(it) 63 | ind := pth.GetIndices() 64 | if wine { 65 | appLnch := master[cats[catList.GetSelectedRow().GetIndex()]][ind[0]] 66 | appLnch.edit(win, func() { 67 | store.Clear() 68 | for i := range ls { 69 | catList.Remove(catList.GetRowAtIndex(len(ls) - i - 1)) 70 | } 71 | ls = getCatRows() 72 | for i, v := range ls { 73 | catList.Insert(v, i) 74 | } 75 | catList.ShowAll() 76 | }) 77 | } else { 78 | appLnch := linmaster[lin[catList.GetSelectedRow().GetIndex()]][ind[0]] 79 | appLnch.edit(win, func() { 80 | store.Clear() 81 | for i := range ls { 82 | catList.Remove(catList.GetRowAtIndex(len(ls) - i - 1)) 83 | } 84 | ls = getCatRows() 85 | for i, v := range ls { 86 | catList.Insert(v, i) 87 | } 88 | catList.ShowAll() 89 | }) 90 | } 91 | } 92 | }) 93 | botBox.PackEnd(edit, false, false, 0) 94 | topLvl.Add(lrBox) 95 | topLvl.PackEnd(botBox, false, true, 0) 96 | win.Add(topLvl) 97 | for _, v := range ls { 98 | catList.Add(v) 99 | } 100 | catList.Connect("row-selected", func() { 101 | store.Clear() 102 | if catList.GetSelectedRow().GetIndex() >= 0 { 103 | treeApps = make([]*gtk.TreeIter, 0) 104 | if wine { 105 | apps := master[cats[catList.GetSelectedRow().GetIndex()]] 106 | for _, v := range apps { 107 | treeApps = append(treeApps, v.getTreeIter(store)) 108 | } 109 | } else { 110 | apps := linmaster[lin[catList.GetSelectedRow().GetIndex()]] 111 | for _, v := range apps { 112 | treeApps = append(treeApps, v.getTreeIter(store)) 113 | } 114 | } 115 | } 116 | }) 117 | appsList.Connect("row-activated", func() { 118 | selec, _ := appsList.GetSelection() 119 | _, it, ok := selec.GetSelected() 120 | if ok { 121 | pth, _ := store.GetPath(it) 122 | ind := pth.GetIndices() 123 | if len(ind) == 1 { 124 | if wine { 125 | appLnch := master[cats[catList.GetSelectedRow().GetIndex()]][ind[0]] 126 | appLnch.launch() 127 | } else { 128 | appLnch := linmaster[lin[catList.GetSelectedRow().GetIndex()]][ind[0]] 129 | appLnch.launch() 130 | } 131 | } else if len(ind) == 2 { 132 | if wine { 133 | appLnch := master[cats[catList.GetSelectedRow().GetIndex()]][ind[0]] 134 | appLnch.launchSub(ind[1]) 135 | } else { 136 | appLnch := linmaster[lin[catList.GetSelectedRow().GetIndex()]][ind[0]] 137 | appLnch.launchSub(ind[1]) 138 | } 139 | } 140 | } 141 | }) 142 | dnl.Connect("clicked", func() { 143 | //TODO: detect if a webbrowser app is available and use that instead 144 | browser.OpenURL("https://appimage.github.io/apps/") 145 | }) 146 | settings.Connect("clicked", func() { 147 | settingsUI(win, func() { 148 | store.Clear() 149 | for i := range ls { 150 | catList.Remove(catList.GetRowAtIndex(len(ls) - i - 1)) 151 | } 152 | ls = getCatRows() 153 | for i, v := range ls { 154 | catList.Insert(v, i) 155 | } 156 | catList.ShowAll() 157 | }) 158 | }) 159 | } 160 | 161 | func getCatRows() (out []*gtk.Label) { 162 | if wine { 163 | for _, v := range cats { 164 | txt, _ := gtk.LabelNew(v) 165 | out = append(out, txt) 166 | } 167 | } else { 168 | for _, v := range lin { 169 | txt, _ := gtk.LabelNew(v) 170 | out = append(out, txt) 171 | } 172 | } 173 | return 174 | } 175 | -------------------------------------------------------------------------------- /update.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "net/http" 9 | "os" 10 | "os/exec" 11 | "strconv" 12 | "strings" 13 | 14 | "github.com/gotk3/gotk3/gtk" 15 | ) 16 | 17 | const ( 18 | versionURL = "https://www.dropbox.com/s/a0xizzo0a4vsfqt/Version?dl=1" 19 | downloadURL = "https://github.com/CalebQ42/LinuxPA/releases/download/vXXX/LinuxPA" 20 | changelogURL = "https://www.dropbox.com/s/rk8ec9p14imkh03/Changelog?dl=1" 21 | changelogBetaURL = "https://www.dropbox.com/s/h2u34g5s8qr8sef/ChangelogBeta?dl=1" 22 | ) 23 | 24 | //Thanks to https://www.socketloop.com/tutorials/golang-download-file-example 25 | //For some of the code 26 | 27 | //Returns if success 28 | func versionDL() (bool, error) { 29 | versionFile, err := os.Create("PortableApps/LinuxPACom/Version") 30 | if err != nil { 31 | return false, err 32 | } 33 | versionFile.Chmod(0777) 34 | check := http.Client{ 35 | CheckRedirect: func(r *http.Request, _ []*http.Request) error { 36 | r.URL.Opaque = r.URL.Path 37 | return nil 38 | }, 39 | } 40 | response, err := check.Get(versionURL) 41 | if err != nil { 42 | return false, err 43 | } 44 | _, err = io.Copy(versionFile, response.Body) 45 | if err != nil { 46 | return false, err 47 | } 48 | return true, nil 49 | } 50 | 51 | func getVersionFileInfo() (stable string, beta string) { 52 | fil, err := os.Open("PortableApps/LinuxPACom/Version") 53 | if err != nil { 54 | return "Error!", "" 55 | } 56 | rdr := bufio.NewReader(fil) 57 | out, _, _ := rdr.ReadLine() 58 | stable = string(out) 59 | out, _, _ = rdr.ReadLine() 60 | beta = string(out) 61 | return 62 | } 63 | 64 | func changelogDL() (bool, error) { 65 | changelogFile, err := os.Create("PortableApps/LinuxPACom/Changelog") 66 | if err != nil { 67 | return false, err 68 | } 69 | changelogFile.Chmod(0777) 70 | check := http.Client{ 71 | CheckRedirect: func(r *http.Request, _ []*http.Request) error { 72 | r.URL.Opaque = r.URL.Path 73 | return nil 74 | }, 75 | } 76 | var response *http.Response 77 | if betaUpdate { 78 | response, err = check.Get(changelogBetaURL) 79 | } else { 80 | response, err = check.Get(changelogURL) 81 | } 82 | if err != nil { 83 | return false, err 84 | } 85 | _, err = io.Copy(changelogFile, response.Body) 86 | if err != nil { 87 | return false, err 88 | } 89 | return true, nil 90 | } 91 | 92 | func getChangelog() string { 93 | fil, err := os.Open("PortableApps/LinuxPACom/Changelog") 94 | if err != nil { 95 | return "Error!" 96 | } 97 | out, _ := ioutil.ReadAll(fil) 98 | return string(out) 99 | } 100 | 101 | func checkForUpdate(stable, beta string) (bool, error) { 102 | new := stable 103 | if betaUpdate { 104 | new = beta 105 | } 106 | curSlice := strings.Split(version, ".") 107 | newSlice := strings.Split(new, ".") 108 | curNums := make([]int, 4) 109 | newNums := make([]int, 4) 110 | for i, v := range curSlice { 111 | num, err := strconv.Atoi(v) 112 | if err == nil { 113 | curNums[i] = num 114 | } 115 | num, err = strconv.Atoi(newSlice[i]) 116 | if err == nil { 117 | newNums[i] = num 118 | } else { 119 | return false, err 120 | } 121 | if newNums[i] > curNums[i] { 122 | return true, nil 123 | } else if curNums[i] > newNums[i] { 124 | return false, nil 125 | } 126 | } 127 | return false, nil 128 | } 129 | 130 | func downloadUpdate(newVersion string) (bool, error) { 131 | url := strings.Replace(downloadURL, "XXX", newVersion, -1) 132 | err := os.Rename("LinuxPA", ".LinuxPA.old") 133 | if err != nil { 134 | return false, err 135 | } 136 | fil, err := os.Create("LinuxPA") 137 | fil.Chmod(0777) 138 | defer fil.Close() 139 | if err != nil { 140 | os.Rename(".LinuxPA.old", "LinuxPA") 141 | return false, err 142 | } 143 | check := http.Client{ 144 | CheckRedirect: func(r *http.Request, _ []*http.Request) error { 145 | r.URL.Opaque = r.URL.Path 146 | return nil 147 | }, 148 | } 149 | re, err := check.Get(url) 150 | if err != nil { 151 | return false, err 152 | } 153 | defer re.Body.Close() 154 | _, err = io.Copy(fil, re.Body) 155 | if err != nil { 156 | return false, err 157 | } 158 | return true, nil 159 | } 160 | 161 | func update(win *gtk.Window, forced bool) { 162 | stat, err := versionDL() 163 | if stat { 164 | stable, beta := getVersionFileInfo() 165 | if stable != "Error!" { 166 | stat, err = checkForUpdate(stable, beta) 167 | if stat || forced { 168 | stat, err = changelogDL() 169 | if stat { 170 | updateWin, _ := gtk.WindowNew(gtk.WINDOW_TOPLEVEL) 171 | updateWin.SetTransientFor(win) 172 | updateWin.SetPosition(gtk.WIN_POS_CENTER) 173 | updateWin.SetDefaultSize(600, 300) 174 | topLvl, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 5) 175 | lbl, _ := gtk.LabelNew("There's a new update! Here's the changelog:") 176 | tagTbl, _ := gtk.TextTagTableNew() 177 | buf, _ := gtk.TextBufferNew(tagTbl) 178 | tv, _ := gtk.TextViewNewWithBuffer(buf) 179 | tv.SetWrapMode(gtk.WRAP_WORD) 180 | tv.SetEditable(false) 181 | buf.SetText(getChangelog()) 182 | butBox, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 5) 183 | upBut, _ := gtk.ButtonNewWithLabel("Update") 184 | upBut.Connect("clicked", func() { 185 | updateWin.Close() 186 | actuallyUpdate(win, forced) 187 | }) 188 | cnlBut, _ := gtk.ButtonNewWithLabel("Cancel") 189 | cnlBut.Connect("clicked", func() { 190 | updateWin.Close() 191 | }) 192 | butBox.Add(upBut) 193 | butBox.Add(cnlBut) 194 | topLvl.Add(lbl) 195 | topLvl.Add(tv) 196 | topLvl.Add(butBox) 197 | topLvl.SetMarginBottom(10) 198 | topLvl.SetMarginEnd(10) 199 | topLvl.SetMarginStart(10) 200 | topLvl.SetMarginTop(10) 201 | updateWin.Add(topLvl) 202 | updateWin.ShowAll() 203 | updateWin.Show() 204 | } else { 205 | fmt.Println(err) 206 | } 207 | } else { 208 | fmt.Println(err) 209 | } 210 | } else { 211 | fmt.Println("Failed Version File Info") 212 | } 213 | } else { 214 | fmt.Println(err) 215 | } 216 | } 217 | 218 | func actuallyUpdate(win *gtk.Window, forced bool) { 219 | updateWin, _ := gtk.WindowNew(gtk.WINDOW_POPUP) 220 | updateWin.SetTransientFor(win) 221 | updateWin.SetSizeRequest(150, 50) 222 | topLvl, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 5) 223 | spin, _ := gtk.SpinnerNew() 224 | spin.Start() 225 | lbl, _ := gtk.LabelNew("Updating") 226 | topLvl.Add(spin) 227 | topLvl.Add(lbl) 228 | topLvl.SetMarginBottom(10) 229 | topLvl.SetMarginEnd(10) 230 | topLvl.SetMarginStart(10) 231 | topLvl.SetMarginTop(10) 232 | updateWin.SetPosition(gtk.WIN_POS_CENTER_ON_PARENT) 233 | updateWin.Add(topLvl) 234 | updateWin.ShowAll() 235 | updateWin.Show() 236 | go func(win, updateWin *gtk.Window) { 237 | defer updateWin.Close() 238 | stat, err := versionDL() 239 | if stat { 240 | stable, beta := getVersionFileInfo() 241 | if stable != "Error!" { 242 | stat, err = checkForUpdate(stable, beta) 243 | if stat || forced { 244 | if betaUpdate { 245 | downloadUpdate(beta) 246 | } else { 247 | downloadUpdate(stable) 248 | } 249 | win.Close() 250 | cmd := exec.Command("./LinuxPA") 251 | cmd.Stdin = os.Stdin 252 | cmd.Stdout = os.Stdout 253 | cmd.Start() 254 | } else { 255 | fmt.Println(err) 256 | } 257 | } else { 258 | fmt.Println("Failed Version File Info") 259 | } 260 | } else { 261 | fmt.Println(err) 262 | } 263 | }(win, updateWin) 264 | } 265 | -------------------------------------------------------------------------------- /wine.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "io/ioutil" 7 | "net/http" 8 | "os" 9 | "strings" 10 | 11 | "github.com/gotk3/gotk3/gtk" 12 | "github.com/mholt/archiver/v3" 13 | ) 14 | 15 | const ( 16 | wineURL = "https://www.playonlinux.com/wine/binaries/phoenicis/staging-linux-amd64/PlayOnLinux-wine-5.20-staging-linux-amd64.tar.gz" 17 | ) 18 | 19 | func downloadWine(parent *gtk.Window, cb chan bool) { 20 | win, _ := gtk.WindowNew(gtk.WINDOW_TOPLEVEL) 21 | win.SetTransientFor(parent) 22 | win.SetDestroyWithParent(true) 23 | win.Connect("destroy", func() { 24 | parent.SetSensitive(true) 25 | }) 26 | parent.SetSensitive(false) 27 | spin, _ := gtk.SpinnerNew() 28 | spin.Start() 29 | txt, _ := gtk.LabelNew("Downloading Wine") 30 | box, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 5) 31 | box.SetMarginBottom(10) 32 | box.SetMarginEnd(10) 33 | box.SetMarginStart(10) 34 | box.SetMarginTop(10) 35 | box.Add(spin) 36 | box.Add(txt) 37 | win.Add(box) 38 | win.SetPosition(gtk.WIN_POS_CENTER_ON_PARENT) 39 | win.ShowAll() 40 | win.Show() 41 | go func(win *gtk.Window, txt *gtk.Label) { 42 | defer win.Close() 43 | wineTar, err := os.Create("PortableApps/LinuxPACom/wine5.20.tar.bz2") 44 | if err != nil { 45 | fmt.Println(err) 46 | cb <- false 47 | return 48 | } 49 | defer wineTar.Close() 50 | check := http.Client{ 51 | CheckRedirect: func(r *http.Request, _ []*http.Request) error { 52 | r.URL.Opaque = r.URL.Path 53 | return nil 54 | }, 55 | } 56 | resp, err := check.Get(wineURL) 57 | if err != nil { 58 | fmt.Println(err) 59 | cb <- false 60 | return 61 | } 62 | os.RemoveAll("PortableApps/LinuxPACom/Wine") 63 | defer resp.Body.Close() 64 | _, err = io.Copy(wineTar, resp.Body) 65 | if err != nil { 66 | fmt.Println(err) 67 | cb <- false 68 | return 69 | } 70 | txt.SetText("Extracting Wine") 71 | err = archiver.DefaultTarBz2.Unarchive("PortableApps/LinuxPACom/wine2.5.tar.bz2", "PortableApps/LinuxPACom/Wine") 72 | if err != nil { 73 | fmt.Println(err) 74 | cb <- false 75 | return 76 | } 77 | fil, err := os.Open("PortableApps/LinuxPACom/common.sh") 78 | if err != nil { 79 | fmt.Println(err) 80 | cb <- false 81 | return 82 | } 83 | tmp, err := ioutil.ReadAll(fil) 84 | if err != nil { 85 | fmt.Println(err) 86 | cb <- false 87 | return 88 | } 89 | if !strings.Contains(string(tmp), "export PATH=$PWD/PortableApps/LinuxPACom/Wine/wineversion/2.5/bin:$PATH") { 90 | tmp = append(tmp, []byte("\nexport PATH=$PWD/PortableApps/LinuxPACom/Wine/wineversion/2.5/bin:$PATH")...) 91 | ioutil.WriteFile("PortableApps/LinuxPACom/common.sh", tmp, 0777) 92 | if err != nil { 93 | fmt.Println(err) 94 | cb <- false 95 | return 96 | } 97 | } 98 | cb <- true 99 | return 100 | }(win, txt) 101 | } 102 | --------------------------------------------------------------------------------