menuet

package module
v1.0.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 8, 2023 License: MIT Imports: 21 Imported by: 14

README

Menuet

Golang library to create menubar apps- programs that live only in OSX's NSStatusBar

Development Status

Under active development. API still changing rapidly.

Installation

menuet requires OS X.

go get github.com/caseymrm/menuet

Documentation

https://godoc.org/github.com/caseymrm/menuet

Apps built with Menuet

  • Why Awake? - shows why your Mac can't sleep, and lets you force it awake

  • Not a Fan - shows your Mac's temperature and fan speed, notifies you when your CPU is being throttled due to excessive heat

  • Traytter - minimalist Twitter client for following a few users

Hello World

package main

import (
	"time"

	"github.com/caseymrm/menuet"
)

func helloClock() {
	for {
		menuet.App().SetMenuState(&menuet.MenuState{
			Title: "Hello World " + time.Now().Format(":05"),
		})
		time.Sleep(time.Second)
	}
}

func main() {
	go helloClock()
	menuet.App().RunApplication()
}

Output

Catalog

The catalog app is useful for trying many of the possible combinations of features.

Advanced Features

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"sort"
	"strconv"
	"time"

	"github.com/caseymrm/menuet"
)

func temperature(woeid string) (temp, unit, text string) {
	url := "https://query.yahooapis.com/v1/public/yql?format=json&q=select%20item.condition%20from%20weather.forecast%20where%20woeid%20%3D%20" + woeid
	resp, err := http.Get(url)
	if err != nil {
		log.Fatal(err)
	}
	var response struct {
		Query struct {
			Results struct {
				Channel struct {
					Item struct {
						Condition struct {
							Temp string `json:"temp"`
							Text string `json:"text"`
						} `json:"condition"`
					} `json:"item"`
					Units struct {
						Temperature string `json:"temperature"`
					} `json:"units"`
				} `json:"channel"`
			} `json:"results"`
		} `json:"query"`
	}
	dec := json.NewDecoder(resp.Body)
	err = dec.Decode(&response)
	if err != nil {
		log.Fatal(err)
	}
	return response.Query.Results.Channel.Item.Condition.Temp, response.Query.Results.Channel.Units.Temperature, response.Query.Results.Channel.Item.Condition.Text
}

func location(query string) (string, string) {
	url := "https://query.yahooapis.com/v1/public/yql?format=json&q=select%20woeid,name%20from%20geo.places%20where%20text%3D%22" + url.QueryEscape(query) + "%22"
	resp, err := http.Get(url)
	if err != nil {
		log.Printf("Get: %v", err)
		menuet.App().Alert(menuet.Alert{
			MessageText:     "Could not get the weather",
			InformativeText: err.Error(),
		})
		return "", ""
	}
	var response struct {
		Query struct {
			Results struct {
				Place struct {
					Name  string `json:"name"`
					WoeID string `json:"woeid"`
				} `json:"place"`
			} `json:"results"`
		} `json:"query"`
	}
	dec := json.NewDecoder(resp.Body)
	err = dec.Decode(&response)
	if err != nil {
		log.Printf("Decode: %v", err)
		menuet.App().Alert(menuet.Alert{
			MessageText:     "Could not search for location",
			InformativeText: err.Error(),
		})
		return "", ""
	}
	return response.Query.Results.Place.Name, response.Query.Results.Place.WoeID
}

func temperatureString(woeid string) string {
	temp, unit, text := temperature(woeid)
	return fmt.Sprintf("%s°%s and %s", temp, unit, text)
}

func setWeather() {
	menuet.App().SetMenuState(&menuet.MenuState{
		Title: temperatureString(menuet.Defaults().String("loc")),
	})
}

var woeids = map[int]string{
	2442047: "Los Angeles",
	2487956: "San Francisco",
	2459115: "New York",
}

func menuPreview(woeid string) func() []menuet.MenuItem {
	return func() []menuet.MenuItem {
		return []menuet.MenuItem{
			menuet.MenuItem{
				Text: temperatureString(woeid),
				Clicked: func() {
					setLocation(woeid)
				},
			},
		}
	}
}

func menuItems() []menuet.MenuItem {
	items := []menuet.MenuItem{}

	currentWoeid := menuet.Defaults().String("loc")
	currentNumber, err := strconv.Atoi(currentWoeid)
	if err != nil {
		log.Printf("Atoi: %v", err)
	}
	found := false
	for woeid, name := range woeids {
		woeStr := strconv.Itoa(woeid)
		items = append(items, menuet.MenuItem{
			Text: name,
			Clicked: func() {
				setLocation(woeStr)
			},
			State:    woeStr == menuet.Defaults().String("loc"),
			Children: menuPreview(woeStr),
		})
		if woeid == currentNumber {
			found = true
		}
	}
	if !found {
		items = append(items, menuet.MenuItem{
			Text: menuet.Defaults().String("name"),
			Clicked: func() {
				setLocation(currentWoeid)
			},
			Children: menuPreview(currentWoeid),
			State:    true,
		})
	}
	sort.Slice(items, func(i, j int) bool {
		return items[i].Text < items[j].Text
	})
	items = append(items, menuet.MenuItem{
		Text: "Other...",
		Clicked: func() {
			response := menuet.App().Alert(menuet.Alert{
				MessageText: "Where would you like to display the weather for?",
				Inputs:      []string{"Location"},
				Buttons:     []string{"Search", "Cancel"},
			})
			if response.Button == 0 && len(response.Inputs) == 1 && response.Inputs[0] != "" {
				newName, newWoeid := location(response.Inputs[0])
				if newWoeid != "" && newName != "" {
					menuet.Defaults().SetString("loc", newWoeid)
					menuet.Defaults().SetString("name", newName)
					menuet.App().Notification(menuet.Notification{
						Title:    fmt.Sprintf("Showing weather for %s", newName),
						Subtitle: temperatureString(newWoeid),
					})
					setWeather()
				}
			}
		},
	})
	return items
}

func hourlyWeather() {
	for {
		setWeather()
		time.Sleep(time.Hour)
	}
}

func setLocation(woeid string) {
	menuet.Defaults().SetString("loc", woeid)
	setWeather()
}

func main() {
	// Load the location from last time
	woeid := menuet.Defaults().String("loc")
	if woeid == "" {
		menuet.Defaults().SetString("loc", "2442047")
	}

	// Start the hourly check, and set the first value
	go hourlyWeather()

	// Configure the application
	menuet.App().Label = "com.github.caseymrm.menuet.weather"

	// Hook up the on-click to populate the menu
	menuet.App().Children = menuItems

	// Run the app (does not return)
	menuet.App().RunApplication()
}

Output

License

Menuet is licensed under the MIT license, so you are welcome to make closed source menubar apps with it as long as you preserve the copyright. For details see the LICENSE file.

Documentation

Index

Constants

View Source
const (
	// WeightUltraLight is equivalent to NSFontWeightUltraLight
	WeightUltraLight FontWeight = -0.8
	// WeightThin is equivalent to NSFontWeightThin
	WeightThin = -0.6
	// WeightLight is equivalent to NSFontWeightLight
	WeightLight = -0.4
	// WeightRegular is equivalent to NSFontWeightRegular, and is the default
	WeightRegular = 0
	// WeightMedium is equivalent to NSFontWeightMedium
	WeightMedium = 0.23
	// WeightSemibold is equivalent to NSFontWeightSemibold
	WeightSemibold = 0.3
	// WeightBold is equivalent to NSFontWeightBold
	WeightBold = 0.4
	// WeightHeavy is equivalent to NSFontWeightHeavy
	WeightHeavy = 0.56
	// WeightBlack is equivalent to NSFontWeightBlack
	WeightBlack = 0.62
)
View Source
const (
	// Regular is a normal item with text and optional callback
	Regular ItemType = ""
	// Separator is a horizontal line
	Separator = "separator"
	// Root is the top level menu directly off the menubar
	Root = "root"
)

Variables

This section is empty.

Functions

This section is empty.

Types

type Alert

type Alert struct {
	MessageText     string
	InformativeText string
	Buttons         []string
	Inputs          []string
}

Alert represents an NSAlert

type AlertClicked

type AlertClicked struct {
	Button int
	Inputs []string
}

AlertClicked represents a selected alert button

type Application

type Application struct {
	Name  string
	Label string

	// Children returns the top level children
	Children func() []MenuItem

	// If Version and Repo are set, checks for updates every day
	AutoUpdate struct {
		Version string
		Repo    string // For example "caseymrm/menuet"
	}

	// NotificationResponder is a handler called when notification respond
	NotificationResponder func(id, response string)
	// contains filtered or unexported fields
}

Application represents the OSX application

func App

func App() *Application

App returns the application singleton

func (*Application) Alert

func (a *Application) Alert(alert Alert) AlertClicked

Alert shows an alert, and returns the index of the button pressed, or -1 if none

func (*Application) GracefulShutdownHandles added in v1.0.2

func (a *Application) GracefulShutdownHandles() (*sync.WaitGroup, context.Context)

GracefulShutdownHandles returns a WaitGroup and Context that can be used to manage graceful shutdown of go resources when the menuabar app is terminated. Use the WaitGroup to track your running goroutines, then shut them down when the context is Done.

func (*Application) HideStartup added in v1.0.2

func (a *Application) HideStartup()

HideStartup prevents the Start at Login menu item from being displayed

func (*Application) MenuChanged

func (a *Application) MenuChanged()

MenuChanged refreshes any open menus

func (*Application) Notification

func (a *Application) Notification(notification Notification)

Notification shows a notification to the user. Note that you have to be part of a proper application bundle for them to show up.

func (*Application) RunApplication

func (a *Application) RunApplication()

RunApplication does not return

func (*Application) SetMenuState

func (a *Application) SetMenuState(state *MenuState)

SetMenuState changes what is shown in the dropdown

type FontWeight

type FontWeight float64

FontWeight represents the weight of the font

type ItemType

type ItemType string

ItemType represents what type of menu item this is

type MenuItem struct {
	Type ItemType

	Text       string
	Image      string // In Resources dir or URL, should have height 16
	FontSize   int    // Default: 14
	FontWeight FontWeight
	State      bool // shows checkmark when set

	Clicked  func()            `json:"-"`
	Children func() []MenuItem `json:"-"`
}

MenuItem represents one item in the dropdown

type MenuState struct {
	Title string
	Image string // // In Resources dir or URL, should have height 22
}

MenuState represents the title and drop down,

type Notification

type Notification struct {
	// The basic text of the notification
	Title    string
	Subtitle string
	Message  string

	// These add an optional action button, change what the close button says, and adds an in-line reply
	ActionButton        string
	CloseButton         string
	ResponsePlaceholder string

	// Duplicate identifiers do not re-display, but instead update the notification center
	Identifier string

	// If true, the notification is shown, but then deleted from the notification center
	RemoveFromNotificationCenter bool
}

Notification represents an NSUserNotification

type UserDefaults

type UserDefaults struct {
	// contains filtered or unexported fields
}

UserDefaults represents values stored in NSUserDefaults

func Defaults

func Defaults() *UserDefaults

Defaults returns the userDefaults singleton

func (*UserDefaults) Boolean

func (u *UserDefaults) Boolean(key string) bool

Boolean gets a boolean default, 0 if not set

func (*UserDefaults) Integer

func (u *UserDefaults) Integer(key string) int

Integer gets an integer default, 0 if not set

func (*UserDefaults) Marshal

func (u *UserDefaults) Marshal(key string, v interface{}) error

Marshal marshals an object into JSON and stores it in user defaults, see json.Marshal docs

func (*UserDefaults) SetBoolean

func (u *UserDefaults) SetBoolean(key string, value bool)

SetBoolean sets a boolean default

func (*UserDefaults) SetInteger

func (u *UserDefaults) SetInteger(key string, value int)

SetInteger sets an integer default

func (*UserDefaults) SetString

func (u *UserDefaults) SetString(key, value string)

SetString sets a string default

func (*UserDefaults) String

func (u *UserDefaults) String(key string) string

String gets a string default, "" if not set

func (*UserDefaults) Unmarshal

func (u *UserDefaults) Unmarshal(key string, v interface{}) error

Unmarshal unmarshals an object from JSON that was stored in user defaults, see json.Unmarshal docs

Directories

Path Synopsis
cmd
slowquit
SlowQuit is an example application that demonstrates how to use the graceful shutdown handles of a menuet app.
SlowQuit is an example application that demonstrates how to use the graceful shutdown handles of a menuet app.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL