24 Commits

Author SHA1 Message Date
Belac Darkstorm 9da352a315 Updating is working!! 2017-04-03 10:43:22 -05:00
Belac Darkstorm 34b8f7c926 Forgot to implement updating 2017-04-03 09:23:41 -05:00
Belac Darkstorm 5d5cedec58 Ready to test then bug fix :)
Also, I'm ALIVE!!!!
2017-04-03 09:17:49 -05:00
Belac Darkstorm 8441a8b752 Working on an auto-update mechanism 2017-04-03 08:23:25 -05:00
Belac Darkstorm d64d88e0a5 Tries to make PortableApps folder before failing 2016-09-23 14:32:30 -05:00
Belac Darkstorm 5eeb9cc702 Fixed some bugs (oops) 2016-09-22 20:25:26 -05:00
Belac Darkstorm fc3411e568 Added Button to launch PortableApps.com launcher. Added back common.sh support (oops) 2016-09-22 08:39:56 -05:00
Belac Darkstorm 77ce9e8ad4 appinfo.ini detection no longer capitalization dependent 2016-09-22 00:25:58 -05:00
Belac Darkstorm 63dd8ebb83 Added a bit better appinfo.ini detection 2016-09-22 00:21:00 -05:00
Belac Darkstorm 7cf45c9ac8 Allow for multiple executables. Double click to launch instead of the launch button 2016-09-22 00:11:32 -05:00
Belac Darkstorm 2f8de0bed2 fix with wine 2016-09-14 09:30:06 -05:00
Belac Darkstorm 67ca030bb2 Updated README 2016-09-14 08:05:18 -05:00
Belac Darkstorm da46399ded Wine support work continued 2016-09-14 03:18:09 -05:00
Belac Darkstorm e5d0b6a9a2 First rendition of wine support (needs some work still) 2016-09-14 02:31:04 -05:00
Belac Darkstorm f4ca2115d4 Stderr output 2016-09-14 02:06:56 -05:00
Belac Darkstorm 0cc7f8f445 Updated README 2016-09-13 08:27:05 -05:00
Belac Darkstorm 767e894acd Updated README 2016-09-13 08:25:52 -05:00
Belac Darkstorm 06e2afef1d Updated README 2016-09-13 08:03:55 -05:00
Belac Darkstorm f614c14013 Updated README 2016-09-13 03:30:27 -05:00
Belac Darkstorm 998c07b90b Update README 2016-09-13 03:26:08 -05:00
Belac Darkstorm 3d44c82e65 Updated README 2016-09-13 03:24:17 -05:00
Belac Darkstorm 08a453b5d5 Proper executable detection (looks for #! or ELF at the beginning of the file) 2016-09-13 02:54:59 -05:00
Belac Darkstorm be42963441 updated README 2016-09-09 09:31:27 -05:00
Belac Darkstorm 50ad374124 Added Screenshot 2016-09-09 09:30:14 -05:00
8 changed files with 715 additions and 268 deletions
+23 -19
View File
@@ -1,33 +1,37 @@
# LinuxPA # LinuxPA
The goal is to create a fully functional PortableApps.com type launcher that can properly parse data from the PortableApps.com format. Apps are launched by a .sh file in the app's directory. Currently pulls out the Name and Category from App/AppInfo/appinfo.ini LinuxPA is a try to bring a [PortableApps.com](http://portableapps.com) type launcher to Linux.
Works well with AppImage apps.
# Why? # How to use
I know that Linux only has about 2% desktop usage and I know that the traditional way to install apps isn't portable, but over the past year or so I've started to put linux apps on my flash drive (AppImage is a great example of a portable solution to linux apps. Also a lot of DRM-free games can be run portably), but there was no easy way to organize my linux apps, so I created one. I personally have used the PortableApps.com launcher for years now and I love how properly formated the apps are, which allows me to grab info about the app easily. 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 (.sh script files have priority), but if one isn't found it launches the first executable in general.
# Why script files? # Apps:
In general linux executable files have no extensions and can be a pain when trying to figure out what is executable and what isn't. I figured script files are easy to detect and allow a large amount of flexibility for me (and others who want to make apps work with this launcher). See below for .AppImage support (Get AppImages from [here](https://bintray.com/probono/AppImages)) Both of the below places provide linux executables that don't need libs installed on the host system:
[AppImage](https://bintray.com/probono/AppImages)
# Why Go? # PortableApps.com Compatibility
Because I like Go :) Also the way it includes all it needs into one friendly executable. 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.
My forum at PortableApps.com can be found [here](http://portableapps.com/node/54998).
# What is needed?
Basically you need go to compile the source, AND YOU ALSO NEED TO MOUNT YOUR FLASH DRIVE SO YOU CAN EXECUTE FILES ON IT!!!! I've found that the mount arguments of `exec,noauto,nodev,nosuid,umask=0000` works well (I personally put my flash drive into /etc/fstab).
# Format
The first place the program looks for an app's icon and info is in the /App/AppInfo directory (icon defaults to appicon_32.png, otherwise it just picks the last one it finds), but if it can't find the appinfo.ini or app icon, it looks in the apps root directory for appinfo.ini and appicon.png for info and icon respectively(Just to make it easier for custom settings in an app).
# common.sh # common.sh
common.sh is run before any program so you can set environment variables (such as HOME). common.sh should be in PortableApps/LinuxPACom folder. 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).
# AppImage support # Simple App Setup
It will now launch .AppImage files! If a .sh script and an .AppImage executable are both in a directory, the .sh script takes precedence. You can get AppImages from [here](https://bintray.com/probono/AppImages). Because apps aren't natively formated in the PortableApps.com format, if LinuxPA doesn't find the AppInfo.ini or appicon_\*.png in the App/AppInfo folder of the app it looks for them in the root directory of the app (except it looks, nor for appicon_\*.png, but appicon.png). If an AppInfo.ini file isn't found then the name of the app is grabbed from the folder name and it's category is set to other. It specifically looks for the lines starting with `Name=` and `Category=`
# AppImage Support
[AppImage Website](http://appimage.org)
Right now AppImages are simply supported via the native linux executable support, but later I'm hoping to add downloading and automatic updating support later on.
# USB mount
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`
# Screenshots
Photos are found [Here](https://goo.gl/photos/VtBUL6DyZTMidj5n6)
# TODO (Might be in order) # TODO (Might be in order)
1. MAKE IT BETTER 1. MAKE IT BETTER
1. Improve linux executable detection (A.K.A. a pain in the butt)
1. Launching of .exe files via wine (wine will have to be installed on the host system, unless there is some portable wine, I may have found one)
1. Add settings menu 1. Add settings menu
1. Add updater for .AppImage files 1. Add updater for .AppImage files
1. Download .AppImage files (maybe) 1. Download .AppImage files (maybe)
1. Check if all apps are closed when it closes and ask if you want to force stop the apps. 1. Check if all apps are closed when it closes and ask if you want to force stop the apps.
1. Portable wine (Should be able to get a working version from PlayOnLinux, but I need to add a check to see if filesystem is EXT as Wine doesn't like filesystems w/o permission control)
+272
View File
@@ -0,0 +1,272 @@
package main
import (
"os"
"os/exec"
"strings"
"github.com/nelsam/gxui"
"github.com/nelsam/gxui/math"
)
type app struct {
name string
cat string
appimg []string
lin []string
ex []string
icon gxui.Texture
dir string
ini *os.File
}
type appExNode struct {
ap app
exInd int
}
func (a *appExNode) launch() {
if wine {
var cmd *exec.Cmd
if !contains(a.ap.lin, a.ap.ex[a.exInd]) {
cmd = exec.Command("/bin/sh", "-c", "cd \""+a.ap.dir+"\"; wine \""+a.ap.ex[a.exInd]+"\"")
} else {
if comEnbld {
cmd = exec.Command("/bin/sh", "-c", ". PortableApps/LinuxPACom/common.sh || exit 1;cd \""+a.ap.dir+"\"; \"./"+a.ap.ex[a.exInd]+"\"")
} else {
cmd = exec.Command("/bin/sh", "-c", "cd \""+a.ap.dir+"\"; \"./"+a.ap.ex[a.exInd]+"\"")
}
}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Start()
}
var cmd *exec.Cmd
if comEnbld {
cmd = exec.Command("/bin/sh", "-c", ". PortableApps/LinuxPACom/common.sh || exit 1;cd \""+a.ap.dir+"\"; \"./"+a.ap.ex[a.exInd]+"\"")
} else {
cmd = exec.Command("/bin/sh", "-c", "cd \""+a.ap.dir+"\"; \"./"+a.ap.ex[a.exInd]+"\"")
}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Start()
}
func (a *appExNode) Count() int {
return 0
}
func (a *appExNode) NodeAt(int) gxui.TreeNode {
return nil
}
func (a *appExNode) ItemIndex(gxui.AdapterItem) int {
return -1
}
func (a *appExNode) Item() gxui.AdapterItem {
if wine {
return a.ap.ex[a.exInd]
}
return a.ap.lin[a.exInd]
}
func (a *appExNode) Create(the gxui.Theme) gxui.Control {
box := the.CreateLinearLayout()
box.SetDirection(gxui.LeftToRight)
box.SetVerticalAlignment(gxui.AlignMiddle)
img := the.CreateImage()
img.SetTexture(a.ap.icon)
img.SetExplicitSize(math.Size{H: 32, W: 32})
lbl := the.CreateLabel()
lbl.SetText(a.ap.ex[a.exInd])
box.AddChild(img)
box.AddChild(lbl)
box.OnDoubleClick(func(gxui.MouseEvent) {
a.launch()
})
return box
}
type appNode struct {
ap app
}
func (a *appNode) launch() {
if len(a.ap.ex) == 1 {
if wine {
var cmd *exec.Cmd
if !contains(a.ap.lin, a.ap.ex[0]) {
cmd = exec.Command("/bin/sh", "-c", "cd \""+a.ap.dir+"\"; wine \""+a.ap.ex[0]+"\"")
} else {
if comEnbld {
cmd = exec.Command("/bin/sh", "-c", ". PortableApps/LinuxPACom/common.sh || exit 1;cd \""+a.ap.dir+"\"; \"./"+a.ap.ex[0]+"\"")
} else {
cmd = exec.Command("/bin/sh", "-c", "cd \""+a.ap.dir+"\"; \"./"+a.ap.ex[0]+"\"")
}
}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Start()
} else {
var cmd *exec.Cmd
if comEnbld {
cmd = exec.Command("/bin/sh", "-c", ". PortableApps/LinuxPACom/common.sh || exit 1;cd \""+a.ap.dir+"\"; \"./"+a.ap.ex[0]+"\"")
} else {
cmd = exec.Command("/bin/sh", "-c", "cd \""+a.ap.dir+"\"; \"./"+a.ap.ex[0]+"\"")
}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Start()
}
} else {
if wine {
var cmd *exec.Cmd
if len(a.ap.lin) == 0 {
cmd = exec.Command("/bin/sh", "-c", "cd \""+a.ap.dir+"\"; wine \""+a.ap.ex[0]+"\"")
} else {
var ind int
for i, v := range a.ap.lin {
if strings.HasSuffix(v, ".sh") {
ind = i
break
}
}
if comEnbld {
cmd = exec.Command("/bin/sh", "-c", ". PortableApps/LinuxPACom/common.sh || exit 1;cd \""+a.ap.dir+"\"; \"./"+a.ap.lin[ind]+"\"")
} else {
cmd = exec.Command("/bin/sh", "-c", "cd \""+a.ap.dir+"\"; \"./"+a.ap.lin[ind]+"\"")
}
}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Start()
} else {
if len(a.ap.lin) != 0 {
var ind int
for i, v := range a.ap.lin {
if strings.HasSuffix(v, ".sh") {
ind = i
break
}
}
var cmd *exec.Cmd
if comEnbld {
cmd = exec.Command("/bin/sh", "-c", ". PortableApps/LinuxPACom/common.sh || exit 1;cd \""+a.ap.dir+"\"; \"./"+a.ap.lin[ind]+"\"")
} else {
cmd = exec.Command("/bin/sh", "-c", "cd \""+a.ap.dir+"\"; \"./"+a.ap.lin[ind]+"\"")
}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Start()
}
}
}
}
func (a *appNode) Count() int {
if wine {
if len(a.ap.ex) > 1 {
return len(a.ap.ex)
}
return 0
}
if len(a.ap.lin) > 1 {
return len(a.ap.lin)
}
return 0
}
func (a *appNode) NodeAt(i int) gxui.TreeNode {
return &appExNode{ap: a.ap, exInd: i}
}
func (a *appNode) ItemIndex(item gxui.AdapterItem) int {
if wine {
for i, v := range a.ap.ex {
if v == item {
return i
}
}
} else {
for i, v := range a.ap.lin {
if v == item {
return i
}
}
}
return -1
}
func (a *appNode) Item() gxui.AdapterItem {
return a.ap.name
}
func (a *appNode) Create(the gxui.Theme) gxui.Control {
box := the.CreateLinearLayout()
box.SetDirection(gxui.LeftToRight)
box.SetPadding(math.CreateSpacing(2))
box.SetVerticalAlignment(gxui.AlignMiddle)
img := the.CreateImage()
if a.ap.icon != nil {
img.SetTexture(a.ap.icon)
}
img.SetExplicitSize(math.Size{H: 32, W: 32})
lbl := the.CreateLabel()
lbl.SetText(a.ap.name)
box.AddChild(img)
box.AddChild(lbl)
box.OnDoubleClick(func(gxui.MouseEvent) {
a.launch()
})
return box
}
type catAdap struct {
gxui.AdapterBase
cat string
}
func (a *catAdap) setCat(cat string) {
a.cat = cat
a.DataChanged(false)
}
func (a *catAdap) refresh() {
a.DataChanged(false)
}
func (a *catAdap) Count() int {
if wine {
return len(master[a.cat])
}
return len(linmaster[a.cat])
}
func (a *catAdap) NodeAt(i int) gxui.TreeNode {
if wine {
return &appNode{ap: master[a.cat][i]}
}
return &appNode{ap: linmaster[a.cat][i]}
}
func (a *catAdap) Size(gxui.Theme) math.Size {
return math.Size{H: 34, W: math.MaxSize.W}
}
func (a *catAdap) ItemIndex(item gxui.AdapterItem) int {
if wine {
for i, v := range master[a.cat] {
if v.name == item {
return i
}
}
} else {
for i, v := range linmaster[a.cat] {
if v.name == item {
return i
}
}
}
return -1
}
+57 -101
View File
@@ -1,127 +1,83 @@
package main package main
import ( import (
"bufio" "fmt"
"os" "os"
"path" "os/exec"
"sort"
"strings"
"github.com/nelsam/gxui"
"github.com/nelsam/gxui/drivers/gl" "github.com/nelsam/gxui/drivers/gl"
"github.com/nelsam/gxui/themes/dark"
"github.com/nelsam/gxui/themes/light"
)
const (
version = "1.1.0.0"
defIni = "[basic]\ntheme=dk"
) )
var ( var (
appMaster map[string][]prtap dr gxui.Driver
th gxui.Theme
master map[string][]app
linmaster map[string][]app
cats []string cats []string
conf *os.File lin []string
common string wine bool
commEnbl bool comEnbld bool
darkTheme = true
) )
type prtap struct {
name string
cat string
ex string
desc string
}
func main() { func main() {
commEnbl = true updated := false
appMaster = make(map[string][]prtap) os.MkdirAll("PortableApps/LinuxPACom", 0777)
os.Mkdir("PortableApps", 0777) stat := versionDL()
os.Mkdir("PortableApps/LinuxPACom", 0777) if stat {
common = "PortableApps/LinuxPACom/common.sh" res := getVersionFileInfo()
_, err := os.Open(common) if res != "Error!" {
if os.IsNotExist(err) { stat = checkForUpdate(res)
commEnbl = false if stat {
} downloadUpdate(res)
pa, err := os.Open("PortableApps") updated = true
if err != nil {
panic(err)
}
appstmp, _ := pa.Readdir(-1)
var folds []string
for _, v := range appstmp {
if v.IsDir() && v.Name() != "LinuxPACom" {
folds = append(folds, v.Name())
}
}
sort.Strings(folds)
for _, v := range folds {
fi, _ := os.Open("PortableApps/" + v)
pat := processApp(fi)
if (pat != prtap{}) {
if _, ok := appMaster[pat.cat]; !ok {
cats = append(cats, pat.cat)
}
appMaster[pat.cat] = append(appMaster[pat.cat], pat)
}
}
gl.StartDriver(uiMain)
}
func processApp(fi *os.File) (out prtap) {
fis, _ := fi.Readdir(-1)
if fil, err := os.Open(fi.Name() + "/App/AppInfo/appinfo.ini"); err == nil {
out.name = getName(fil)
fil, _ = os.Open(fi.Name() + "/App/AppInfo/appinfo.ini")
out.cat = getCat(fil)
} else if fil, err := os.Open(fi.Name() + "/appinfo.ini"); err == nil {
out.name = getName(fil)
fil, _ = os.Open(fi.Name() + "/appinfo.ini")
out.cat = getCat(fil)
} else { } else {
out.cat = "other" fmt.Println("Failed DL")
} }
if out.name == "" { } else {
out.name = path.Base(fi.Name()) fmt.Println("Failed Version File Info")
} }
if out.cat == "" { } else {
out.cat = "other" fmt.Println("Failed Version DL")
} }
for _, v := range fis { if updated {
if !v.IsDir() && strings.HasSuffix(strings.ToLower(v.Name()), ".sh") { cmd := exec.Command("./LinuxPA")
//do os check here for possible cross platform support cmd.Stdin = os.Stdin
out.ex = fi.Name() + "/" + v.Name() cmd.Stdout = os.Stdout
return cmd.Start()
fmt.Println("updated!")
} else {
master = make(map[string][]app)
linmaster = make(map[string][]app)
gl.StartDriver(appMain)
} }
} }
for _, v := range fis {
if !v.IsDir() && strings.HasSuffix(strings.ToLower(v.Name()), ".appimage") {
//do os check here for possible cross platform support
out.ex = fi.Name() + "/" + v.Name()
return
}
}
return prtap{}
}
func getCat(fi *os.File) (out string) { func appMain(dri gxui.Driver) {
rdr := bufio.NewReader(fi) dr = dri
var err error setup()
var ln []byte if darkTheme {
for err == nil { th = dark.CreateTheme(dr)
ln, _, err = rdr.ReadLine() } else {
str := string(ln) th = light.CreateTheme(dr)
if strings.HasPrefix(str, "Category=") {
out = strings.TrimPrefix(str, "Category=")
return
} }
} th = dark.CreateTheme(dr)
return ui()
} }
func getName(fi *os.File) (out string) { func contains(arr []string, str string) bool {
rdr := bufio.NewReader(fi) for _, v := range arr {
var err error if v == str {
var ln []byte return true
for err == nil {
ln, _, err = rdr.ReadLine()
str := string(ln)
if strings.HasPrefix(str, "Name=") {
out = strings.TrimPrefix(str, "Name=")
return
} }
} }
return return false
} }
-103
View File
@@ -1,103 +0,0 @@
package main
import (
"image"
"image/draw"
_ "image/png"
"os"
"path"
"sort"
"strings"
"github.com/nelsam/gxui"
"github.com/nelsam/gxui/math"
)
type prtapAdap struct {
gxui.AdapterBase
apps []prtap
}
func (p *prtapAdap) SetApps(apps []prtap) {
p.apps = apps
p.DataChanged(false)
}
func (p *prtapAdap) Count() int {
return len(p.apps)
}
func (p *prtapAdap) Create(th gxui.Theme, index int) gxui.Control {
box := th.CreateLinearLayout()
box.SetPadding(math.CreateSpacing(2))
box.SetDirection(gxui.LeftToRight)
box.SetVerticalAlignment(gxui.AlignMiddle)
dir := path.Dir(p.apps[index].ex)
if fold, err := os.Open(dir + "/App/AppInfo"); err == nil {
var pics []string
fi, _ := fold.Readdirnames(-1)
for _, v := range fi {
if strings.HasPrefix(v, "appicon_") && strings.HasSuffix(v, ".png") {
pics = append(pics, v)
}
}
if len(pics) > 0 {
ind := sort.SearchStrings(pics, "appicon_128.png")
if ind == len(pics) {
ind = len(pics) - 1
}
imgfi, _ := os.Open(dir + "/App/AppInfo/" + pics[ind])
img, _, err := image.Decode(imgfi)
if err == nil {
rgba := image.NewRGBA(img.Bounds())
draw.Draw(rgba, img.Bounds(), img, image.ZP, draw.Src)
tex := dr.CreateTexture(rgba, 1)
icon := th.CreateImage()
icon.SetExplicitSize(math.Size{H: 32, W: 32})
icon.SetTexture(tex)
box.AddChild(icon)
}
}
} else if fi, err := os.Open(dir + "/appicon.png"); err == nil {
img, _, err := image.Decode(fi)
if err == nil {
rgba := image.NewRGBA(img.Bounds())
draw.Draw(rgba, img.Bounds(), img, image.ZP, draw.Src)
tex := dr.CreateTexture(rgba, 1)
icon := th.CreateImage()
icon.SetExplicitSize(math.Size{H: 32, W: 32})
icon.SetTexture(tex)
box.AddChild(icon)
}
} else {
//Creating empty Image so that names line up
icon := th.CreateImage()
icon.SetExplicitSize(math.Size{H: 32, W: 32})
box.AddChild(icon)
}
lbl := th.CreateLabel()
lbl.SetText(p.apps[index].name)
box.AddChild(lbl)
return box
}
func (p *prtapAdap) ItemAt(index int) gxui.AdapterItem {
return p.apps[index]
}
func (p *prtapAdap) ItemIndex(item gxui.AdapterItem) int {
it, ok := item.(prtap)
if !ok {
return -1
}
for i, v := range p.apps {
if v == it {
return i
}
}
return -1
}
func (p *prtapAdap) Size(gxui.Theme) math.Size {
return math.Size{W: math.MaxSize.W, H: 36}
}
+210
View File
@@ -0,0 +1,210 @@
package main
import (
"bufio"
"fmt"
"image"
"image/draw"
_ "image/png"
"os"
"reflect"
"sort"
"strings"
"github.com/nelsam/gxui"
)
const ()
func setup() {
PortableAppsFold, err := os.Open("PortableApps")
if PAStat, _ := PortableAppsFold.Stat(); err != nil || !PAStat.IsDir() {
os.Mkdir("PortableApps", 0777)
PortableAppsFold, err = os.Open("PortableApps")
if err != nil {
panic("Can't find PortableApps folder and can't create one!")
}
}
if _, err = os.Open("PortableApps/LinuxPACom"); err != nil {
os.Mkdir("PortableApps/LinuxPACom", 0777)
}
fmt.Println(err)
_, err = os.Open("PortableApps/LinuxPACom/common.sh")
if err == nil {
comEnbld = true
}
fi, err := os.Open("PortableApps/LinuxPACom/Info.ini")
if err != nil {
fi, err = os.Create("PortableApps/LinuxPACom/Info.ini")
if err == nil {
wrt := bufio.NewWriter(fi)
wrt.WriteString(defIni)
wrt.Flush()
}
}
if err == nil {
rdr := bufio.NewReader(fi)
for err != nil {
ln, _, error := rdr.ReadLine()
err = error
str := string(ln)
if strings.HasPrefix(str, "theme=") {
str = strings.TrimPrefix(str, "theme=")
if str == "lt" {
darkTheme = false
}
}
}
}
PAFolds, _ := PortableAppsFold.Readdirnames(-1)
sort.Strings(PAFolds)
for _, v := range PAFolds {
fold, _ := os.Open("PortableApps/" + v)
if stat, _ := fold.Stat(); stat.IsDir() && stat.Name() != "PortableApps.com" && stat.Name() != "LinuxPACom" {
ap := processApp("PortableApps/" + v)
if !reflect.DeepEqual(ap, app{}) {
if _, ok := master[ap.cat]; !ok {
cats = append(cats, ap.cat)
sort.Strings(cats)
}
if len(ap.lin) != 0 {
if _, ok := linmaster[ap.cat]; !ok {
lin = append(lin, ap.cat)
sort.Strings(lin)
}
}
master[ap.cat] = append(master[ap.cat], ap)
if len(ap.lin) != 0 {
linmaster[ap.cat] = append(linmaster[ap.cat], ap)
}
}
}
}
}
func processApp(fold string) (out app) {
wd, _ := os.Getwd()
out.dir = wd + "/" + fold
out.ini = findInfo(fold)
if out.ini != nil {
out.name = getName(out.ini)
out.ini = findInfo(fold)
out.cat = getCat(out.ini)
out.ini = findInfo(fold)
}
if out.name == "" {
out.name = strings.TrimPrefix(fold, "PortableApps/")
}
if out.cat == "" {
out.cat = "Other"
}
out.icon = getIcon(fold)
folder, _ := os.Open(fold)
fis, _ := folder.Readdirnames(-1)
for _, v := range fis {
tmp, _ := os.Open(fold + "/" + v)
if stat, _ := tmp.Stat(); stat.IsDir() {
continue
}
if strings.HasSuffix(strings.ToLower(v), ".appimage") {
out.appimg = append(out.appimg, v)
out.ex = append(out.ex, v)
out.lin = append(out.lin, v)
} else if strings.HasSuffix(strings.ToLower(v), ".exe") {
out.ex = append(out.ex, v)
} else {
btys := make([]byte, 4)
rdr := bufio.NewReader(tmp)
rdr.Read(btys)
if (strings.Contains(strings.ToLower(string(btys)), "elf") && !strings.HasSuffix(strings.ToLower(v), ".so") && !strings.Contains(v, ".so.")) || strings.HasPrefix(strings.ToLower(string(btys)), "#!") {
out.ex = append(out.ex, v)
out.lin = append(out.lin, v)
}
}
}
if len(out.ex) == 0 {
return app{}
}
if len(out.lin) == 0 {
out.name += " (Wine)"
}
return
}
func getCat(ini *os.File) string {
rdr := bufio.NewReader(ini)
var ret string
for line, _, err := rdr.ReadLine(); err == nil; line, _, err = rdr.ReadLine() {
if strings.HasPrefix(string(line), "Category=") {
ret = strings.TrimPrefix(string(line), "Category=")
break
}
}
rdr.Reset(ini)
return ret
}
func getName(ini *os.File) string {
rdr := bufio.NewReader(ini)
var ret string
for line, _, err := rdr.ReadLine(); err == nil; line, _, err = rdr.ReadLine() {
if strings.HasPrefix(string(line), "Name=") {
ret = strings.TrimPrefix(string(line), "Name=")
break
}
}
rdr.Reset(ini)
return ret
}
func getIcon(fold string) gxui.Texture {
var pic *os.File
if folder, err := os.Open(fold + "/App/AppInfo"); err == nil {
fis, _ := folder.Readdir(-1)
var pics []string
for _, v := range fis {
if !v.IsDir() && strings.HasSuffix(strings.ToLower(v.Name()), ".png") && strings.HasPrefix(strings.ToLower(v.Name()), "appicon_") {
pics = append(pics, v.Name())
}
}
sort.Strings(pics)
if len(pics) > 1 {
var ind int
if !contains(pics, "appicon_32.png") {
ind = len(pics) - 1
} else {
ind = sort.SearchStrings(pics, "appicon_32.png")
}
pic, _ = os.Open(fold + "/App/AppInfo/" + pics[ind])
}
} else if fi, err := os.Open(fold + "/appicon.png"); err == nil {
pic = fi
} else {
return nil
}
img, _, err := image.Decode(pic)
if err != nil {
return nil
}
rgba := image.NewRGBA(img.Bounds())
draw.Draw(rgba, img.Bounds(), img, image.ZP, draw.Src)
ret := dr.CreateTexture(rgba, 1)
return ret
}
func findInfo(fold string) *os.File {
tmp, err := os.Open(fold + "/App/AppInfo")
if err == nil {
fis, _ := tmp.Readdirnames(-1)
for _, v := range fis {
if strings.ToLower(v) == "appinfo.ini" {
tmp, _ := os.Open(fold + "/App/AppInfo/" + v)
return tmp
}
}
}
if fi, err := os.Open(fold + "/appinfo.ini"); err == nil {
return fi
}
return nil
}
Executable → Regular
View File
+44 -42
View File
@@ -3,62 +3,64 @@ package main
import ( import (
"os" "os"
"os/exec" "os/exec"
"path"
"github.com/nelsam/gxui" "github.com/nelsam/gxui"
"github.com/nelsam/gxui/themes/dark"
) )
var ( func ui() {
dr gxui.Driver catListAdap := &StrList{}
) appListAdap := &catAdap{}
catListAdap.SetStrings(lin)
func uiMain(dri gxui.Driver) {
dr = dri
catAdap := &StrList{}
catAdap.SetStrings(cats)
appAdap := &prtapAdap{}
th := dark.CreateTheme(dr)
win := th.CreateWindow(500, 500, "LinuxPA") win := th.CreateWindow(500, 500, "LinuxPA")
top := th.CreateLinearLayout() top := th.CreateLinearLayout()
top.SetDirection(gxui.BottomToTop) top.SetDirection(gxui.BottomToTop)
top.SetHorizontalAlignment(gxui.AlignRight) splBox := th.CreateLinearLayout()
spl := th.CreateSplitterLayout() spl := th.CreateSplitterLayout()
spl.SetOrientation(gxui.Horizontal) spl.SetOrientation(gxui.Horizontal)
catlist := th.CreateList() catList := th.CreateList()
catlist.SetAdapter(catAdap) catList.SetAdapter(catListAdap)
catlist.OnItemClicked(func(_ gxui.MouseEvent, it gxui.AdapterItem) { catList.OnSelectionChanged(func(it gxui.AdapterItem) {
str := it.(string) appListAdap.setCat(it.(string))
appAdap.SetApps(appMaster[str])
}) })
applist := th.CreateList() appList := th.CreateTree()
applist.SetAdapter(appAdap) appList.SetAdapter(appListAdap)
spl.AddChild(catlist) spl.AddChild(catList)
spl.AddChild(applist) spl.AddChild(appList)
but := th.CreateLinearLayout() splBox.AddChild(spl)
but.SetDirection(gxui.RightToLeft) butBox := th.CreateLinearLayout()
launch := th.CreateButton() butBox.SetDirection(gxui.LeftToRight)
launch.SetText("Launch!") if _, err := exec.LookPath("wine"); err == nil {
launch.OnClick(func(gxui.MouseEvent) { wineBut := th.CreateButton()
if appAdap.ItemIndex(applist.Selected()) != -1 { wineBut.SetType(gxui.ToggleButton)
app := applist.Selected().(prtap) wineBut.SetChecked(wine)
dir, fi := path.Split(app.ex) wineBut.SetText("Show Windows Apps")
var cmd *exec.Cmd wineBut.OnClick(func(gxui.MouseEvent) {
if commEnbl { wine = wineBut.IsChecked()
cmd = exec.Command("/bin/sh", "-c", ". "+common+" || exit 1;cd \""+dir+"\"; \"./"+fi+"\"") appListAdap.refresh()
if wineBut.IsChecked() {
catListAdap.SetStrings(cats)
wineBut.SetText("Hide Windows Apps")
} else { } else {
cmd = exec.Command("/bin/sh", "-c", "cd \""+dir+"\"; \"./"+fi+"\"") catListAdap.SetStrings(lin)
wineBut.SetText("Show Windows Apps")
} }
cmd.Stdin = os.Stdin })
_, err := os.Open("Start.exe")
if err == nil {
pa := th.CreateButton()
pa.SetText("Open PortableApps Launcher")
pa.OnClick(func(gxui.MouseEvent) {
cmd := exec.Command("wine", "Start.exe")
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Start() cmd.Start()
}
}) })
but.AddChild(launch) butBox.AddChild(pa)
top.AddChild(but) }
top.AddChild(spl) butBox.AddChild(wineBut)
}
top.AddChild(butBox)
top.AddChild(splBox)
win.AddChild(top) win.AddChild(top)
win.OnClose(func() { win.OnClose(dr.Terminate)
dr.Terminate()
})
} }
+106
View File
@@ -0,0 +1,106 @@
package main
import (
"bufio"
"fmt"
"io"
"net/http"
"os"
"strconv"
"strings"
)
const (
versionURL = "https://www.dropbox.com/s/a0xizzo0a4vsfqt/Version?dl=1"
downloadURL = "https://github.com/CalebQ42/LinuxPA/releases/download/vXXX/LinuxPA"
)
//Thanks to https://www.socketloop.com/tutorials/golang-download-file-example
//For some of the code
//Returns if success
func versionDL() bool {
versionFile, err := os.Create("PortableApps/LinuxPACom/Version")
if err != nil {
return false
}
versionFile.Chmod(0777)
check := http.Client{
CheckRedirect: func(r *http.Request, via []*http.Request) error {
r.URL.Opaque = r.URL.Path
return nil
},
}
response, err := check.Get(versionURL)
if err != nil {
return false
}
_, err = io.Copy(versionFile, response.Body)
if err != nil {
return false
}
return true
}
func getVersionFileInfo() string {
fil, err := os.Open("PortableApps/LinuxPACom/Version")
if err != nil {
return "Error!"
}
rdr := bufio.NewReader(fil)
out, _, _ := rdr.ReadLine()
return string(out)
}
func checkForUpdate(new string) bool {
curSlice := strings.Split(version, ".")
newSlice := strings.Split(new, ".")
curNums := make([]int, 4)
newNums := make([]int, 4)
for i, v := range curSlice {
num, err := strconv.Atoi(v)
if err == nil {
curNums[i] = num
}
num, err = strconv.Atoi(newSlice[i])
if err == nil {
newNums[i] = num
}
if newNums[i] > curNums[i] {
return true
}
}
return false
}
func downloadUpdate(newVersion string) bool {
url := strings.Replace(downloadURL, "XXX", newVersion, -1)
err := os.Rename("LinuxPA", ".LinuxPA.old")
if err != nil {
fmt.Println(err)
return false
}
fil, err := os.Create("LinuxPA")
fil.Chmod(0777)
defer fil.Close()
if err != nil {
os.Rename(".LinuxPA.old", "LinuxPA")
return false
}
check := http.Client{
CheckRedirect: func(r *http.Request, via []*http.Request) error {
r.URL.Opaque = r.URL.Path
return nil
},
}
re, err := check.Get(url)
defer re.Body.Close()
if err != nil {
return false
}
_, err = io.Copy(fil, re.Body)
if err != nil {
return false
}
return true
}