Skip to content
This repository has been archived by the owner on Feb 16, 2024. It is now read-only.

Commit

Permalink
Added dock
Browse files Browse the repository at this point in the history
  • Loading branch information
asticode committed Apr 1, 2018
1 parent 172da7d commit 6ad972c
Show file tree
Hide file tree
Showing 14 changed files with 186 additions and 37 deletions.
31 changes: 20 additions & 11 deletions astilectron.go
Expand Up @@ -51,6 +51,7 @@ type Astilectron struct {
closeOnce sync.Once
dispatcher *dispatcher
displayPool *displayPool
dock *Dock
executer Executer
identifier *identifier
listener net.Listener
Expand Down Expand Up @@ -139,7 +140,7 @@ func (a *Astilectron) SetExecuter(e Executer) *Astilectron {

// On implements the Listenable interface
func (a *Astilectron) On(eventName string, l Listener) {
a.dispatcher.addListener(mainTargetID, eventName, l)
a.dispatcher.addListener(targetIDApp, eventName, l)
}

// Start starts Astilectron
Expand Down Expand Up @@ -206,8 +207,8 @@ func (a *Astilectron) watchNoAccept(timeout time.Duration, chanAccepted chan boo
return
case <-t.C:
astilog.Errorf("No TCP connection has been accepted in the past %s", timeout)
a.dispatcher.dispatch(Event{Name: EventNameAppNoAccept, TargetID: mainTargetID})
a.dispatcher.dispatch(Event{Name: EventNameAppCmdStop, TargetID: mainTargetID})
a.dispatcher.dispatch(Event{Name: EventNameAppNoAccept, TargetID: targetIDApp})
a.dispatcher.dispatch(Event{Name: EventNameAppCmdStop, TargetID: targetIDApp})
return
}
}
Expand All @@ -221,17 +222,17 @@ func (a *Astilectron) acceptTCP(chanAccepted chan bool) {
var err error
if conn, err = a.listener.Accept(); err != nil {
astilog.Errorf("%s while TCP accepting", err)
a.dispatcher.dispatch(Event{Name: EventNameAppErrorAccept, TargetID: mainTargetID})
a.dispatcher.dispatch(Event{Name: EventNameAppCmdStop, TargetID: mainTargetID})
a.dispatcher.dispatch(Event{Name: EventNameAppErrorAccept, TargetID: targetIDApp})
a.dispatcher.dispatch(Event{Name: EventNameAppCmdStop, TargetID: targetIDApp})
return
}

// We only accept the first connection which should be Astilectron, close the next one and stop
// the app
if i > 0 {
astilog.Errorf("Too many TCP connections")
a.dispatcher.dispatch(Event{Name: EventNameAppTooManyAccept, TargetID: mainTargetID})
a.dispatcher.dispatch(Event{Name: EventNameAppCmdStop, TargetID: mainTargetID})
a.dispatcher.dispatch(Event{Name: EventNameAppTooManyAccept, TargetID: targetIDApp})
a.dispatcher.dispatch(Event{Name: EventNameAppCmdStop, TargetID: targetIDApp})
conn.Close()
return
}
Expand Down Expand Up @@ -276,6 +277,9 @@ func (a *Astilectron) executeCmd(cmd *exec.Cmd) (err error) {
if e.Displays != nil {
a.displayPool.update(e.Displays)
}

// Create dock
a.dock = newDock(e.Dock, a.canceller, a.dispatcher, a.identifier, a.writer)
return
}

Expand All @@ -287,12 +291,12 @@ func (a *Astilectron) watchCmd(cmd *exec.Cmd) {
// Check the canceller to check whether it was a crash
if !a.canceller.Cancelled() {
astilog.Debug("App has crashed")
a.dispatcher.dispatch(Event{Name: EventNameAppCrash, TargetID: mainTargetID})
a.dispatcher.dispatch(Event{Name: EventNameAppCrash, TargetID: targetIDApp})
} else {
astilog.Debug("App has closed")
a.dispatcher.dispatch(Event{Name: EventNameAppClose, TargetID: mainTargetID})
a.dispatcher.dispatch(Event{Name: EventNameAppClose, TargetID: targetIDApp})
}
a.dispatcher.dispatch(Event{Name: EventNameAppCmdStop, TargetID: mainTargetID})
a.dispatcher.dispatch(Event{Name: EventNameAppCmdStop, TargetID: targetIDApp})
}

// Close closes Astilectron properly
Expand Down Expand Up @@ -357,14 +361,19 @@ func (a *Astilectron) Displays() []*Display {
return a.displayPool.all()
}

// Dock returns the dock
func (a *Astilectron) Dock() *Dock {
return a.dock
}

// PrimaryDisplay returns the primary display
func (a *Astilectron) PrimaryDisplay() *Display {
return a.displayPool.primary()
}

// NewMenu creates a new app menu
func (a *Astilectron) NewMenu(i []*MenuItemOptions) *Menu {
return newMenu(nil, mainTargetID, i, a.canceller, a.dispatcher, a.identifier, a.writer)
return newMenu(nil, targetIDApp, i, a.canceller, a.dispatcher, a.identifier, a.writer)
}

// NewWindow creates a new window
Expand Down
2 changes: 1 addition & 1 deletion astilectron_test.go
Expand Up @@ -166,7 +166,7 @@ func TestAstilectron_NewMenu(t *testing.T) {
a, err := New(Options{})
assert.NoError(t, err)
m := a.NewMenu([]*MenuItemOptions{})
assert.Equal(t, mainTargetID, m.rootID)
assert.Equal(t, targetIDApp, m.rootID)
}

func TestAstilectron_Actions(t *testing.T) {
Expand Down
134 changes: 134 additions & 0 deletions dock.go
@@ -0,0 +1,134 @@
package astilectron

import "github.com/asticode/go-astitools/context"

// Dock event names
const (
eventNameDockCmdBounce = "dock.cmd.bounce"
eventNameDockCmdBounceDownloads = "dock.cmd.bounce.downloads"
eventNameDockCmdCancelBounce = "dock.cmd.cancel.bounce"
eventNameDockCmdHide = "dock.cmd.hide"
eventNameDockCmdSetBadge = "dock.cmd.set.badge"
eventNameDockCmdSetIcon = "dock.cmd.set.icon"
eventNameDockCmdShow = "dock.cmd.show"
eventNameDockEventBadgeSet = "dock.event.badge.set"
eventNameDockEventBouncing = "dock.event.bouncing"
eventNameDockEventBouncingCancelled = "dock.event.bouncing.cancelled"
eventNameDockEventDownloadsBouncing = "dock.event.download.bouncing"
eventNameDockEventHidden = "dock.event.hidden"
eventNameDockEventIconSet = "dock.event.icon.set"
eventNameDockEventShown = "dock.event.shown"
)

// Dock bounce types
const (
DockBounceTypeCritical = "critical"
DockBounceTypeInformational = "informational"
)

// Dock represents a dock
// https://github.com/electron/electron/blob/v1.8.1/docs/api/app.md#appdockbouncetype-macos
type Dock struct {
*object
o *DockOptions
}

// DockOptions represents dock options
type DockOptions struct {
Badge string `json:"badge,omitempty"`
IsVisible *bool `json:"isVisible,omitempty"`
}

func newDock(o *DockOptions, c *asticontext.Canceller, d *dispatcher, i *identifier, wrt *writer) *Dock {
return &Dock{
o: o,
object: newObject(nil, c, d, i, wrt, targetIDDock),
}
}

// Badge returns the badge
func (d *Dock) Badge() string {
return d.o.Badge
}

// Bounce bounces the dock
func (d *Dock) Bounce(bounceType string) (id int, err error) {
if err = d.isActionable(); err != nil {
return
}
var e Event
if e, err = synchronousEvent(d.c, d, d.w, Event{Name: eventNameDockCmdBounce, TargetID: d.id, BounceType: bounceType}, eventNameDockEventBouncing); err != nil {
return
}
if e.ID != nil {
id = *e.ID
}
return
}

// BounceDownloads bounces the downloads part of the dock
func (d *Dock) BounceDownloads(filePath string) (err error) {
if err = d.isActionable(); err != nil {
return
}
_, err = synchronousEvent(d.c, d, d.w, Event{Name: eventNameDockCmdBounceDownloads, TargetID: d.id, FilePath: filePath}, eventNameDockEventDownloadsBouncing)
return
}

// CancelBounce cancels the dock bounce
func (d *Dock) CancelBounce(id int) (err error) {
if err = d.isActionable(); err != nil {
return
}
_, err = synchronousEvent(d.c, d, d.w, Event{Name: eventNameDockCmdCancelBounce, TargetID: d.id, ID: PtrInt(id)}, eventNameDockEventBouncingCancelled)
return
}

// Hide hides the dock
func (d *Dock) Hide() (err error) {
if err = d.isActionable(); err != nil {
return
}
_, err = synchronousEvent(d.c, d, d.w, Event{Name: eventNameDockCmdHide, TargetID: d.id}, eventNameDockEventHidden)
d.o.IsVisible = PtrBool(false)
return
}

// IsVisible returns whether the dock is visible
func (d *Dock) IsVisible() *bool {
return d.o.IsVisible
}

// NewMenu creates a new dock menu
func (d *Dock) NewMenu(i []*MenuItemOptions) *Menu {
return newMenu(d.ctx, d.id, i, d.c, d.d, d.i, d.w)
}

// SetBadge sets the badge of the dock
func (d *Dock) SetBadge(badge string) (err error) {
if err = d.isActionable(); err != nil {
return
}
_, err = synchronousEvent(d.c, d, d.w, Event{Name: eventNameDockCmdSetBadge, TargetID: d.id, Badge: badge}, eventNameDockEventBadgeSet)
d.o.Badge = badge
return
}

// SetIcon sets the icon of the dock
func (d *Dock) SetIcon(image string) (err error) {
if err = d.isActionable(); err != nil {
return
}
_, err = synchronousEvent(d.c, d, d.w, Event{Name: eventNameDockCmdSetIcon, TargetID: d.id, Image: image}, eventNameDockEventIconSet)
return
}

// Show shows the dock
func (d *Dock) Show() (err error) {
if err = d.isActionable(); err != nil {
return
}
_, err = synchronousEvent(d.c, d, d.w, Event{Name: eventNameDockCmdShow, TargetID: d.id}, eventNameDockEventShown)
d.o.IsVisible = PtrBool(true)
return
}
10 changes: 8 additions & 2 deletions event.go
Expand Up @@ -5,9 +5,10 @@ import (
"errors"
)

// Misc constants
// Target IDs
const (
mainTargetID = "main"
targetIDApp = "app"
targetIDDock = "dock"
)

// Event represents an event
Expand All @@ -19,8 +20,13 @@ type Event struct {
// This is a list of all possible payloads.
// A choice was made not to use interfaces since it's a pain in the ass asserting each an every payload afterwards
// We use pointers so that omitempty works
Badge string `json:"badge,omitempty"`
BounceType string `json:"bounceType,omitempty"`
CallbackID string `json:"callbackId,omitempty"`
Displays *EventDisplays `json:"displays,omitempty"`
Dock *DockOptions `json:"dock,omitempty"`
FilePath string `json:"filePath,omitempty"`
ID *int `json:"id,omitempty"`
Image string `json:"image,omitempty"`
Menu *EventMenu `json:"menu,omitempty"`
MenuItem *EventMenuItem `json:"menuItem,omitempty"`
Expand Down
2 changes: 1 addition & 1 deletion menu_item.go
Expand Up @@ -97,7 +97,7 @@ type MenuItemOptions struct {
func newMenuItem(parentCtx context.Context, rootID string, o *MenuItemOptions, c *asticontext.Canceller, d *dispatcher, i *identifier, w *writer) (m *MenuItem) {
m = &MenuItem{
o: o,
object: newObject(parentCtx, c, d, i, w),
object: newObject(parentCtx, c, d, i, w, i.new()),
rootID: rootID,
}
if o.OnClick != nil {
Expand Down
6 changes: 3 additions & 3 deletions menu_item_test.go
Expand Up @@ -10,9 +10,9 @@ import (

func TestMenuItem_ToEvent(t *testing.T) {
var o = &MenuItemOptions{Label: PtrStr("1"), SubMenu: []*MenuItemOptions{{Label: PtrStr("2")}, {Label: PtrStr("3")}}}
var mi = newMenuItem(context.Background(), "main", o, nil, nil, newIdentifier(), nil)
var mi = newMenuItem(context.Background(), targetIDApp, o, nil, nil, newIdentifier(), nil)
e := mi.toEvent()
assert.Equal(t, &EventMenuItem{ID: "1", RootID: "main", Options: o, SubMenu: &EventSubMenu{ID: "2", Items: []*EventMenuItem{{ID: "3", Options: &MenuItemOptions{Label: PtrStr("2")}, RootID: "main"}, {ID: "4", Options: &MenuItemOptions{Label: PtrStr("3")}, RootID: "main"}}, RootID: "main"}}, e)
assert.Equal(t, &EventMenuItem{ID: "1", RootID: targetIDApp, Options: o, SubMenu: &EventSubMenu{ID: "2", Items: []*EventMenuItem{{ID: "3", Options: &MenuItemOptions{Label: PtrStr("2")}, RootID: targetIDApp}, {ID: "4", Options: &MenuItemOptions{Label: PtrStr("3")}, RootID: targetIDApp}}, RootID: targetIDApp}}, e)
assert.Len(t, mi.SubMenu().items, 2)
}

Expand All @@ -23,7 +23,7 @@ func TestMenuItem_Actions(t *testing.T) {
var i = newIdentifier()
var wrt = &mockedWriter{}
var w = newWriter(wrt)
var mi = newMenuItem(context.Background(), "main", &MenuItemOptions{Label: PtrStr("label")}, c, d, i, w)
var mi = newMenuItem(context.Background(), targetIDApp, &MenuItemOptions{Label: PtrStr("label")}, c, d, i, w)

// Actions
testObjectAction(t, func() error { return mi.SetChecked(true) }, mi.object, wrt, "{\"name\":\""+EventNameMenuItemCmdSetChecked+"\",\"targetID\":\""+mi.id+"\",\"menuItemOptions\":{\"checked\":true}}\n", EventNameMenuItemEventCheckedSet)
Expand Down
10 changes: 5 additions & 5 deletions menu_test.go
Expand Up @@ -9,9 +9,9 @@ import (
)

func TestMenu_ToEvent(t *testing.T) {
var m = newMenu(nil, "main", []*MenuItemOptions{{Label: PtrStr("1")}, {Label: PtrStr("2")}}, asticontext.NewCanceller(), newDispatcher(), newIdentifier(), nil)
var m = newMenu(nil, targetIDApp, []*MenuItemOptions{{Label: PtrStr("1")}, {Label: PtrStr("2")}}, asticontext.NewCanceller(), newDispatcher(), newIdentifier(), nil)
e := m.toEvent()
assert.Equal(t, &EventMenu{EventSubMenu: &EventSubMenu{ID: "1", Items: []*EventMenuItem{{ID: "2", Options: &MenuItemOptions{Label: PtrStr("1")}, RootID: "main"}, {ID: "3", Options: &MenuItemOptions{Label: PtrStr("2")}, RootID: "main"}}, RootID: "main"}}, e)
assert.Equal(t, &EventMenu{EventSubMenu: &EventSubMenu{ID: "1", Items: []*EventMenuItem{{ID: "2", Options: &MenuItemOptions{Label: PtrStr("1")}, RootID: targetIDApp}, {ID: "3", Options: &MenuItemOptions{Label: PtrStr("2")}, RootID: targetIDApp}}, RootID: targetIDApp}}, e)
}

func TestMenu_Actions(t *testing.T) {
Expand All @@ -21,10 +21,10 @@ func TestMenu_Actions(t *testing.T) {
var i = newIdentifier()
var wrt = &mockedWriter{}
var w = newWriter(wrt)
var m = newMenu(context.Background(), "main", []*MenuItemOptions{{Label: PtrStr("1")}, {Label: PtrStr("2")}}, c, d, i, w)
var m = newMenu(context.Background(), targetIDApp, []*MenuItemOptions{{Label: PtrStr("1")}, {Label: PtrStr("2")}}, c, d, i, w)

// Actions
testObjectAction(t, func() error { return m.Create() }, m.object, wrt, "{\"name\":\""+EventNameMenuCmdCreate+"\",\"targetID\":\""+m.id+"\",\"menu\":{\"id\":\"1\",\"items\":[{\"id\":\"2\",\"options\":{\"label\":\"1\"},\"rootId\":\"main\"},{\"id\":\"3\",\"options\":{\"label\":\"2\"},\"rootId\":\"main\"}],\"rootId\":\"main\"}}\n", EventNameMenuEventCreated)
testObjectAction(t, func() error { return m.Destroy() }, m.object, wrt, "{\"name\":\""+EventNameMenuCmdDestroy+"\",\"targetID\":\""+m.id+"\",\"menu\":{\"id\":\"1\",\"items\":[{\"id\":\"2\",\"options\":{\"label\":\"1\"},\"rootId\":\"main\"},{\"id\":\"3\",\"options\":{\"label\":\"2\"},\"rootId\":\"main\"}],\"rootId\":\"main\"}}\n", EventNameMenuEventDestroyed)
testObjectAction(t, func() error { return m.Create() }, m.object, wrt, "{\"name\":\""+EventNameMenuCmdCreate+"\",\"targetID\":\""+m.id+"\",\"menu\":{\"id\":\"1\",\"items\":[{\"id\":\"2\",\"options\":{\"label\":\"1\"},\"rootId\":\""+targetIDApp+"\"},{\"id\":\"3\",\"options\":{\"label\":\"2\"},\"rootId\":\""+targetIDApp+"\"}],\"rootId\":\""+targetIDApp+"\"}}\n", EventNameMenuEventCreated)
testObjectAction(t, func() error { return m.Destroy() }, m.object, wrt, "{\"name\":\""+EventNameMenuCmdDestroy+"\",\"targetID\":\""+m.id+"\",\"menu\":{\"id\":\"1\",\"items\":[{\"id\":\"2\",\"options\":{\"label\":\"1\"},\"rootId\":\""+targetIDApp+"\"},{\"id\":\"3\",\"options\":{\"label\":\"2\"},\"rootId\":\""+targetIDApp+"\"}],\"rootId\":\""+targetIDApp+"\"}}\n", EventNameMenuEventDestroyed)
assert.True(t, m.IsDestroyed())
}
4 changes: 2 additions & 2 deletions object.go
Expand Up @@ -25,12 +25,12 @@ type object struct {
}

// newObject returns a new base object
func newObject(parentCtx context.Context, c *asticontext.Canceller, d *dispatcher, i *identifier, w *writer) (o *object) {
func newObject(parentCtx context.Context, c *asticontext.Canceller, d *dispatcher, i *identifier, w *writer, id string) (o *object) {
o = &object{
c: c,
d: d,
i: i,
id: i.new(),
id: id,
w: w,
}
if parentCtx != nil {
Expand Down
2 changes: 1 addition & 1 deletion object_test.go
Expand Up @@ -10,7 +10,7 @@ import (
func TestObject_IsActionable(t *testing.T) {
// Init
var c = asticontext.NewCanceller()
var o = newObject(nil, c, nil, newIdentifier(), nil)
var o = newObject(nil, c, nil, newIdentifier(), nil, "")

// Test success
assert.NoError(t, o.isActionable())
Expand Down
2 changes: 1 addition & 1 deletion session.go
Expand Up @@ -22,7 +22,7 @@ type Session struct {

// newSession creates a new session
func newSession(parentCtx context.Context, c *asticontext.Canceller, d *dispatcher, i *identifier, w *writer) *Session {
return &Session{object: newObject(parentCtx, c, d, i, w)}
return &Session{object: newObject(parentCtx, c, d, i, w, i.new())}
}

// ClearCache clears the Session's HTTP cache
Expand Down
2 changes: 1 addition & 1 deletion sub_menu.go
Expand Up @@ -40,7 +40,7 @@ type subMenu struct {
func newSubMenu(parentCtx context.Context, rootID string, items []*MenuItemOptions, c *asticontext.Canceller, d *dispatcher, i *identifier, w *writer) *subMenu {
// Init
var m = &subMenu{
object: newObject(parentCtx, c, d, i, w),
object: newObject(parentCtx, c, d, i, w, i.new()),
rootID: rootID,
}

Expand Down

0 comments on commit 6ad972c

Please sign in to comment.