Skip to content

Commit 8a492a5

Browse files
committed
initial working 0.2.0 version
1 parent 23003b7 commit 8a492a5

File tree

8 files changed

+301
-1
lines changed

8 files changed

+301
-1
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,7 @@
1313

1414
# Dependency directories (remove the comment below to include it)
1515
# vendor/
16+
17+
**/.DS_Store
18+
/local
19+
/dist

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
2+
build:
3+
go build -o local/topframe .

README.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,21 @@ User programmable screen overlay using web technologies
2121

2222
* download or run shell command
2323
* run with edit flag `topframe --edit`
24-
* install with launchctl `topframe --plist`
24+
25+
### Launching on Startup
26+
27+
Topframe works with `launchd` to run as an agent on startup. You can generate
28+
a plist file with `topframe -agent`, which you can write to a file and move
29+
into `/Library/LaunchAgents`:
30+
31+
```
32+
$ topframe -agent > com.progrium.Topframe.plist
33+
$ sudo mv com.progrium.Topframe.plist /Library/LaunchAgents
34+
```
35+
36+
The generated plist will use the current binary location, so make sure it's
37+
in the right place before generating, or modify the plist file.
38+
2539

2640
## Documentation
2741

agent.plist

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>Label</key>
6+
<string>com.progrium.Topframe</string>
7+
<key>EnvironmentVariables</key>
8+
<dict>
9+
<key>PATH</key>
10+
<string>/usr/local/bin:/usr/bin:/bin</string>
11+
</dict>
12+
<key>WorkingDirectory</key>
13+
<string>{{.Dir}}</string>
14+
<key>ProgramArguments</key>
15+
<array>
16+
<string>{{.Bin}}</string>
17+
</array>
18+
<key>RunAtLoad</key>
19+
<true/>
20+
<key>KeepAlive</key>
21+
<false/>
22+
<key>LaunchOnlyOnce</key>
23+
<true/>
24+
</dict>
25+
</plist>

go.mod

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
module github.com/progrium/topframe
2+
3+
go 1.16
4+
5+
require (
6+
github.com/progrium/macdriver v0.0.1
7+
github.com/progrium/watcher v1.0.7
8+
)

go.sum

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
2+
github.com/manifold/qtalk v0.0.0-20201222233608-81c04ab41f37/go.mod h1:UX1KjRclAJyV+ydrpEO1CnF8x95Le/Adx8BMGPJaYqg=
3+
github.com/mitchellh/mapstructure v1.4.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
4+
github.com/progrium/macdriver v0.0.1 h1:IB6OIz+OotdKiCYRo9OLcrPSu1kP29RHmhEaol//sQk=
5+
github.com/progrium/macdriver v0.0.1/go.mod h1:9sPyjQ2zVExbC7I5tL3GPzyaet/cS3o4g8Ht2JcYuRk=
6+
github.com/progrium/watcher v1.0.7 h1:A+ciAtU09mHrTW0tdODa6SwWEuKMKF+n/vBJYNU/FqE=
7+
github.com/progrium/watcher v1.0.7/go.mod h1:JQYLbWpkvkx6EDYKgqn4Tv5zwhRXe9a+MQw1wnmcyvI=
8+
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
9+
github.com/webview/webview v0.0.0-20200724072439-e0c01595b361/go.mod h1:rpXAuuHgyEJb6kXcXldlkOjU6y4x+YcASKKXJNUhh0Y=
10+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
11+
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
12+
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
13+
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
14+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
15+
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
16+
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
17+
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
18+
gopkg.in/vmihailenco/msgpack.v2 v2.9.1/go.mod h1:/3Dn1Npt9+MYyLpYYXjInO/5jvMLamn+AEGwNEOatn8=

index.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<html>
2+
<head>
3+
<link rel="stylesheet" href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" />
4+
</head>
5+
<body class="border-2 border-purple-500 text-white">
6+
<div class="w-96 h-64 m-8 absolute right-0 rounded p-4 bg-purple-500">
7+
<h2 class="text-3xl my-4 text-center animate-bounce">Hello!</h2>
8+
</div>
9+
</body>
10+
</html>

topframe.go

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"io/ioutil"
7+
"log"
8+
"net"
9+
"net/http"
10+
"os"
11+
"os/exec"
12+
"os/user"
13+
"path/filepath"
14+
"runtime"
15+
"text/template"
16+
"time"
17+
18+
_ "embed"
19+
20+
"github.com/progrium/macdriver/cocoa"
21+
"github.com/progrium/macdriver/core"
22+
"github.com/progrium/macdriver/objc"
23+
"github.com/progrium/macdriver/webkit"
24+
"github.com/progrium/watcher"
25+
)
26+
27+
var (
28+
flagEdit bool
29+
flagAgent bool
30+
31+
dir string
32+
)
33+
34+
func init() {
35+
runtime.LockOSThread()
36+
flag.BoolVar(&flagEdit, "edit", false, "edit your topframe")
37+
flag.BoolVar(&flagAgent, "agent", false, "generate agent plist")
38+
}
39+
40+
func edit() {
41+
editor := os.Getenv("EDITOR")
42+
if editor == "" {
43+
log.Println("unable to edit, no value for EDITOR")
44+
return
45+
}
46+
if err := exec.Command(editor, dir).Run(); err != nil {
47+
log.Println(err)
48+
}
49+
}
50+
51+
func main() {
52+
flag.Parse()
53+
54+
var err error
55+
usr, err := user.Current()
56+
if err != nil {
57+
log.Fatal(err)
58+
}
59+
60+
dir = filepath.Join(usr.HomeDir, ".topframe")
61+
os.MkdirAll(dir, 0755)
62+
63+
//go:embed index.html
64+
var defaultIndex []byte
65+
if _, err := os.Stat(filepath.Join(dir, "index.html")); os.IsNotExist(err) {
66+
ioutil.WriteFile(filepath.Join(dir, "index.html"), defaultIndex, 0644)
67+
}
68+
69+
if flagAgent {
70+
//go:embed agent.plist
71+
var plist string
72+
73+
tmpl, err := template.New("plist").Parse(plist)
74+
if err != nil {
75+
log.Fatal(err)
76+
}
77+
bin, _ := filepath.Abs(os.Args[0])
78+
err = tmpl.Execute(os.Stdout, struct {
79+
Dir, Bin string
80+
}{
81+
Dir: dir,
82+
Bin: bin,
83+
})
84+
if err != nil {
85+
log.Fatal(err)
86+
}
87+
return
88+
}
89+
90+
if flagEdit {
91+
go edit()
92+
}
93+
94+
srv := http.Server{
95+
Handler: http.FileServer(http.Dir(dir)),
96+
}
97+
98+
ln, err := net.Listen("tcp", ":0")
99+
if err != nil {
100+
log.Fatal(err)
101+
}
102+
103+
go srv.Serve(ln)
104+
105+
fw := watcher.New()
106+
if err := fw.AddRecursive(dir); err != nil {
107+
log.Fatal(err)
108+
}
109+
110+
go fw.Start(400 * time.Millisecond)
111+
112+
cocoa.TerminateAfterWindowsClose = false
113+
app := cocoa.NSApp_WithDidLaunch(func(notification objc.Object) {
114+
config := webkit.WKWebViewConfiguration_New()
115+
config.Preferences().SetValueForKey(core.True, core.String("developerExtrasEnabled"))
116+
117+
wv := webkit.WKWebView_Init(cocoa.NSScreen_Main().Frame(), config)
118+
wv.SetOpaque(false)
119+
wv.SetBackgroundColor(cocoa.NSColor_Clear())
120+
wv.SetValueForKey(core.False, core.String("drawsBackground"))
121+
122+
url := core.URL(fmt.Sprintf("http://localhost:%d", ln.Addr().(*net.TCPAddr).Port))
123+
req := core.NSURLRequest_Init(url)
124+
wv.LoadRequest(req)
125+
126+
w := cocoa.NSWindow_Init(cocoa.NSScreen_Main().Frame(), cocoa.NSClosableWindowMask,
127+
cocoa.NSBackingStoreBuffered, false)
128+
w.SetContentView(wv)
129+
w.SetBackgroundColor(cocoa.NSColor_Clear())
130+
w.SetOpaque(false)
131+
w.SetTitleVisibility(cocoa.NSWindowTitleHidden)
132+
w.SetTitlebarAppearsTransparent(true)
133+
w.SetIgnoresMouseEvents(true)
134+
w.SetLevel(cocoa.NSMainMenuWindowLevel + 2)
135+
w.MakeKeyAndOrderFront(w)
136+
w.SetCollectionBehavior(cocoa.NSWindowCollectionBehaviorCanJoinAllSpaces)
137+
138+
statusBar := cocoa.NSStatusBar_System().StatusItemWithLength(cocoa.NSVariableStatusItemLength)
139+
statusBar.Retain()
140+
statusBar.Button().SetTitle("🔲")
141+
142+
itemInteract := cocoa.NSMenuItem_New()
143+
itemInteract.Retain()
144+
itemInteract.SetTitle("Interactive")
145+
itemInteract.SetAction(objc.Sel("interact:"))
146+
cocoa.DefaultDelegateClass.AddMethod("interact:", func(_ objc.Object) {
147+
if w.IgnoresMouseEvents() {
148+
fmt.Println("Mouse events on")
149+
w.SetLevel(cocoa.NSMainMenuWindowLevel - 1)
150+
w.SetIgnoresMouseEvents(false)
151+
itemInteract.SetState(1)
152+
} else {
153+
fmt.Println("Mouse events off")
154+
w.SetIgnoresMouseEvents(true)
155+
w.SetLevel(cocoa.NSMainMenuWindowLevel + 2)
156+
itemInteract.SetState(0)
157+
}
158+
})
159+
160+
itemEnabled := cocoa.NSMenuItem_New()
161+
itemEnabled.Retain()
162+
itemEnabled.SetTitle("Enabled")
163+
itemEnabled.SetState(1)
164+
itemEnabled.SetAction(objc.Sel("enabled:"))
165+
cocoa.DefaultDelegateClass.AddMethod("enabled:", func(_ objc.Object) {
166+
if w.IsVisible() {
167+
w.Send("orderOut:", w)
168+
itemInteract.SetEnabled(false)
169+
itemEnabled.SetState(0)
170+
} else {
171+
w.Send("orderFront:", w)
172+
itemInteract.SetEnabled(true)
173+
itemEnabled.SetState(1)
174+
}
175+
})
176+
177+
itemQuit := cocoa.NSMenuItem_New()
178+
itemQuit.SetTitle("Quit")
179+
itemQuit.SetAction(objc.Sel("terminate:"))
180+
181+
menu := cocoa.NSMenu_New()
182+
menu.SetAutoenablesItems(false)
183+
menu.AddItem(itemEnabled)
184+
menu.AddItem(itemInteract)
185+
186+
if os.Getenv("EDITOR") != "" {
187+
itemEdit := cocoa.NSMenuItem_New()
188+
itemEdit.SetTitle("Edit Source...")
189+
itemEdit.SetAction(objc.Sel("edit:"))
190+
cocoa.DefaultDelegateClass.AddMethod("edit:", func(_ objc.Object) {
191+
edit()
192+
})
193+
menu.AddItem(itemEdit)
194+
}
195+
196+
menu.AddItem(cocoa.NSMenuItem_Separator())
197+
menu.AddItem(itemQuit)
198+
statusBar.SetMenu(menu)
199+
200+
go func() {
201+
for {
202+
select {
203+
case event := <-fw.Event:
204+
if event.IsDir() {
205+
continue
206+
}
207+
wv.Reload(nil)
208+
case <-fw.Closed:
209+
return
210+
}
211+
}
212+
}()
213+
})
214+
app.ActivateIgnoringOtherApps(true)
215+
216+
log.Printf("topframe 0.2.0 by progrium\n")
217+
app.Run()
218+
}

0 commit comments

Comments
 (0)