Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| da46399ded | |||
| e5d0b6a9a2 | |||
| f4ca2115d4 | |||
| 0cc7f8f445 | |||
| 767e894acd | |||
| 06e2afef1d | |||
| f614c14013 | |||
| 998c07b90b | |||
| 3d44c82e65 | |||
| 08a453b5d5 | |||
| be42963441 | |||
| 50ad374124 | |||
| f160e6d311 | |||
| 6d946a123f | |||
| c758d57f2a |
@@ -1,25 +1,31 @@
|
||||
# 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
|
||||
Works well with AppImage apps.
|
||||
LinuxPA is a try to bring a [PortableApps.com](http://portableapps.com) type launcher to Linux.
|
||||
|
||||
# Why?
|
||||
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.
|
||||
# App Detection
|
||||
LinuxPA looks in all folders in the PortableApps folder for, first, a script file (starts with the shebang (`#!`)) and, secondly, a native linux executable (starts with ELF). It will only add the first one it finds.
|
||||
|
||||
# Why script files?
|
||||
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).
|
||||
# PortableApps.com Compatibility
|
||||
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.
|
||||
|
||||
# Why Go?
|
||||
Because I like Go :) Also the way it includes all it needs into one friendly executable.
|
||||
# common.sh
|
||||
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).
|
||||
|
||||
# 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).
|
||||
# Simple App Setup
|
||||
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=`
|
||||
|
||||
# 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).
|
||||
# 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.
|
||||
|
||||
# TODO
|
||||
Add in a common.sh that is executed with each script. (Allows for setting environment variables such as HOME)
|
||||
MAKE IT BETTER
|
||||
Add an open button (I know, I just wanted to get the initial working before making it user friendly)
|
||||
Check if all apps are closed when it closes and ask if you want to force stop the apps.
|
||||
(Maybe)Create an installer.
|
||||
# USB mount
|
||||
Unfortunately Linux, by default, doesn't support running executables off of flash drives, requiring you to mount your drive with special mount arguments, 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)
|
||||
1. MAKE IT BETTER
|
||||
1. Add settings menu
|
||||
1. Add updater for .AppImage files
|
||||
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.
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bufio"
|
||||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
@@ -13,17 +14,31 @@ import (
|
||||
var (
|
||||
appMaster map[string][]prtap
|
||||
cats []string
|
||||
wineOnly []string
|
||||
linOnly []string
|
||||
conf *os.File
|
||||
common string
|
||||
commEnbl bool
|
||||
)
|
||||
|
||||
type prtap struct {
|
||||
name string
|
||||
cat string
|
||||
ex string
|
||||
desc string
|
||||
wine bool
|
||||
}
|
||||
|
||||
func main() {
|
||||
commEnbl = true
|
||||
appMaster = make(map[string][]prtap)
|
||||
os.Mkdir("PortableApps", 0777)
|
||||
os.Mkdir("PortableApps/LinuxPACom", 0777)
|
||||
common = "PortableApps/LinuxPACom/common.sh"
|
||||
_, err := os.Open(common)
|
||||
if os.IsNotExist(err) {
|
||||
commEnbl = false
|
||||
}
|
||||
pa, err := os.Open("PortableApps")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -31,7 +46,7 @@ func main() {
|
||||
appstmp, _ := pa.Readdir(-1)
|
||||
var folds []string
|
||||
for _, v := range appstmp {
|
||||
if v.IsDir() {
|
||||
if v.IsDir() && v.Name() != "LinuxPACom" && v.Name() != "PortableApps.com" {
|
||||
folds = append(folds, v.Name())
|
||||
}
|
||||
}
|
||||
@@ -41,11 +56,29 @@ func main() {
|
||||
pat := processApp(fi)
|
||||
if (pat != prtap{}) {
|
||||
if _, ok := appMaster[pat.cat]; !ok {
|
||||
cats = append(cats, pat.cat)
|
||||
if pat.wine {
|
||||
wineOnly = append(wineOnly, pat.cat)
|
||||
cats = append(cats, pat.cat)
|
||||
} else {
|
||||
linOnly = append(linOnly, pat.cat)
|
||||
cats = append(cats, pat.cat)
|
||||
}
|
||||
} else {
|
||||
if !pat.wine {
|
||||
for i, v := range wineOnly {
|
||||
if pat.cat == v {
|
||||
wineOnly = append(wineOnly[:i], wineOnly[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
appMaster[pat.cat] = append(appMaster[pat.cat], pat)
|
||||
}
|
||||
}
|
||||
sort.Strings(linOnly)
|
||||
sort.Strings(wineOnly)
|
||||
sort.Strings(cats)
|
||||
gl.StartDriver(uiMain)
|
||||
}
|
||||
|
||||
@@ -60,19 +93,60 @@ func processApp(fi *os.File) (out prtap) {
|
||||
fil, _ = os.Open(fi.Name() + "/appinfo.ini")
|
||||
out.cat = getCat(fil)
|
||||
} else {
|
||||
out.cat = "other"
|
||||
out.cat = "Other"
|
||||
}
|
||||
if out.name == "" {
|
||||
out.name = path.Base(fi.Name())
|
||||
}
|
||||
if out.cat == "" {
|
||||
out.cat = "other"
|
||||
out.cat = "Other"
|
||||
}
|
||||
//executable detection
|
||||
wd, _ := os.Getwd()
|
||||
var rdr *bufio.Reader
|
||||
for _, v := range fis {
|
||||
fil, err := os.Open(wd + "/" + fi.Name() + "/" + v.Name())
|
||||
if err == nil {
|
||||
stat, _ := fil.Stat()
|
||||
if !stat.IsDir() {
|
||||
rdr = bufio.NewReader(fil)
|
||||
shebang := []byte{'#', '!'}
|
||||
two := make([]byte, 2)
|
||||
rdr.Read(two)
|
||||
if reflect.DeepEqual(shebang, two) {
|
||||
out.ex = wd + "/" + fi.Name() + "/" + v.Name()
|
||||
rdr.Reset(fil)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, v := range fis {
|
||||
if !v.IsDir() && strings.HasSuffix(v.Name(), ".sh") {
|
||||
//do os check here for possible cross platform support
|
||||
out.ex = fi.Name() + "/" + v.Name()
|
||||
return
|
||||
fil, err := os.Open(wd + "/" + fi.Name() + "/" + v.Name())
|
||||
if err == nil {
|
||||
stat, _ := fil.Stat()
|
||||
if !stat.IsDir() {
|
||||
rdr = bufio.NewReader(fil)
|
||||
thr := make([]byte, 4)
|
||||
rdr.Read(thr)
|
||||
if strings.Contains(string(thr), "ELF") {
|
||||
out.ex = wd + "/" + fi.Name() + "/" + v.Name()
|
||||
rdr.Reset(fil)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, v := range fis {
|
||||
fil, err := os.Open(wd + "/" + fi.Name() + "/" + v.Name())
|
||||
if err == nil {
|
||||
stat, _ := fil.Stat()
|
||||
if !stat.IsDir() && strings.HasSuffix(stat.Name(), "exe") {
|
||||
out.wine = true
|
||||
out.ex = wd + "/" + fi.Name() + "/" + v.Name()
|
||||
out.name += " (Wine)"
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return prtap{}
|
||||
|
||||
+34
-7
@@ -15,16 +15,43 @@ import (
|
||||
|
||||
type prtapAdap struct {
|
||||
gxui.AdapterBase
|
||||
apps []prtap
|
||||
wine bool
|
||||
master []prtap
|
||||
cur []prtap
|
||||
}
|
||||
|
||||
func (p *prtapAdap) SetApps(apps []prtap) {
|
||||
p.apps = apps
|
||||
p.master = apps
|
||||
if p.wine {
|
||||
p.cur = p.master
|
||||
} else {
|
||||
p.cur = make([]prtap, 0)
|
||||
for _, v := range p.master {
|
||||
if !v.wine {
|
||||
p.cur = append(p.cur, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
p.DataChanged(false)
|
||||
}
|
||||
|
||||
func (p *prtapAdap) Count() int {
|
||||
return len(p.apps)
|
||||
return len(p.cur)
|
||||
}
|
||||
|
||||
func (p *prtapAdap) Wine(show bool) {
|
||||
p.wine = show
|
||||
if show {
|
||||
p.cur = p.master
|
||||
} else {
|
||||
p.cur = make([]prtap, 0)
|
||||
for _, v := range p.master {
|
||||
if !v.wine {
|
||||
p.cur = append(p.cur, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
p.DataChanged(false)
|
||||
}
|
||||
|
||||
func (p *prtapAdap) Create(th gxui.Theme, index int) gxui.Control {
|
||||
@@ -32,7 +59,7 @@ func (p *prtapAdap) Create(th gxui.Theme, index int) gxui.Control {
|
||||
box.SetPadding(math.CreateSpacing(2))
|
||||
box.SetDirection(gxui.LeftToRight)
|
||||
box.SetVerticalAlignment(gxui.AlignMiddle)
|
||||
dir := path.Dir(p.apps[index].ex)
|
||||
dir := path.Dir(p.cur[index].ex)
|
||||
if fold, err := os.Open(dir + "/App/AppInfo"); err == nil {
|
||||
var pics []string
|
||||
fi, _ := fold.Readdirnames(-1)
|
||||
@@ -76,13 +103,13 @@ func (p *prtapAdap) Create(th gxui.Theme, index int) gxui.Control {
|
||||
box.AddChild(icon)
|
||||
}
|
||||
lbl := th.CreateLabel()
|
||||
lbl.SetText(p.apps[index].name)
|
||||
lbl.SetText(p.cur[index].name)
|
||||
box.AddChild(lbl)
|
||||
return box
|
||||
}
|
||||
|
||||
func (p *prtapAdap) ItemAt(index int) gxui.AdapterItem {
|
||||
return p.apps[index]
|
||||
return p.cur[index]
|
||||
}
|
||||
|
||||
func (p *prtapAdap) ItemIndex(item gxui.AdapterItem) int {
|
||||
@@ -90,7 +117,7 @@ func (p *prtapAdap) ItemIndex(item gxui.AdapterItem) int {
|
||||
if !ok {
|
||||
return -1
|
||||
}
|
||||
for i, v := range p.apps {
|
||||
for i, v := range p.cur {
|
||||
if v == it {
|
||||
return i
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
@@ -16,8 +17,9 @@ var (
|
||||
func uiMain(dri gxui.Driver) {
|
||||
dr = dri
|
||||
catAdap := &StrList{}
|
||||
catAdap.SetStrings(cats)
|
||||
catAdap.SetStrings(linOnly)
|
||||
appAdap := &prtapAdap{}
|
||||
appAdap.Wine(false)
|
||||
th := dark.CreateTheme(dr)
|
||||
win := th.CreateWindow(500, 500, "LinuxPA")
|
||||
top := th.CreateLinearLayout()
|
||||
@@ -43,15 +45,46 @@ func uiMain(dri gxui.Driver) {
|
||||
if appAdap.ItemIndex(applist.Selected()) != -1 {
|
||||
app := applist.Selected().(prtap)
|
||||
dir, fi := path.Split(app.ex)
|
||||
cmd := exec.Command("/bin/sh", "-c", "cd \""+dir+"\"; \"./"+fi+"\"")
|
||||
var cmd *exec.Cmd
|
||||
if app.wine {
|
||||
cmd = exec.Command("/bin/sh", "-c", "cd \""+dir+"\"; wine \""+fi+"\"")
|
||||
} else {
|
||||
if commEnbl {
|
||||
cmd = exec.Command("/bin/sh", "-c", ". "+common+" || exit 1;cd \""+dir+"\"; \"./"+fi+"\"")
|
||||
} else {
|
||||
cmd = exec.Command("/bin/sh", "-c", "cd \""+dir+"\"; \"./"+fi+"\"")
|
||||
}
|
||||
}
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Start()
|
||||
}
|
||||
})
|
||||
if _, err := exec.LookPath("wine"); err == nil {
|
||||
fmt.Println("Wine found!")
|
||||
wine := th.CreateButton()
|
||||
wine.SetType(gxui.ToggleButton)
|
||||
wine.OnClick(func(gxui.MouseEvent) {
|
||||
if wine.IsChecked() {
|
||||
catAdap.SetStrings(cats)
|
||||
appAdap.Wine(true)
|
||||
} else {
|
||||
catAdap.SetStrings(linOnly)
|
||||
appAdap.Wine(false)
|
||||
}
|
||||
})
|
||||
wine.SetText("Show Windows Apps")
|
||||
wine.SetChecked(appAdap.wine)
|
||||
but.AddChild(wine)
|
||||
} else {
|
||||
fmt.Println("Wine not found!")
|
||||
}
|
||||
but.AddChild(launch)
|
||||
top.AddChild(but)
|
||||
top.AddChild(spl)
|
||||
win.AddChild(top)
|
||||
win.OnClose(dr.Terminate)
|
||||
win.OnClose(func() {
|
||||
dr.Terminate()
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user