Scopes tutorials
Go scopes development
This document describes how to start developing go scopes from a Vivid system installed from scratch.
It lists packages you need to install to build go applications using the go-
unityscopes
library and how to cross compile for an ARM device.
Preparing the system for development
Pre-requisites
Install required packages
$ sudo apt-get install unity-scope-tool golang git bzr python3-scope-harness mercurial
Create a development directory for the go files
$ mkdir ~/dev-go
Export and/or set GOPATH
environment variable
$ export GOPATH=~/dev-go
You can also set permanently the environment variable in your .bashrc file
GOPATH=~/dev-go
Get the go-scopes bindings base code
$ go get launchpad.net/go-unityscopes/v2
This will download all source files needed to implement scopes in Go to your
GOPATH
. The source files will be located at $GOPATH/src
Developing a scope in Go
Create a developement directory under $GOPATH/src
$ mkdir $GOPATH/src/my-goscope
Change to the my-goscope directory, and create a new go file
$ gedit my-goscope.go
Fill the base go-scope template with your own code
package main import ( "launchpad.net/go-unityscopes/v2" "log" ) const searchCategoryTemplate = `{ "schema-version": 1, "template": { "category-layout": "grid", "card-size": "small" }, "components": { "title": "title", "art": "art", "subtitle": "username" } }` // SCOPE *********************************************************************** var scope_interface scopes.Scope type MyScope struct { base *scopes.ScopeBase } func (s *MyScope) Preview(result *scopes.Result, metadata *scopes.ActionMetadata, reply *scopes.PreviewReply, cancelled <-chan bool) error { // FILL WITH YOUR CODE } func (s *MyScope) Search(query *scopes.CannedQuery, metadata *scopes.SearchMetadata, reply *scopes.SearchReply, cancelled <-chan bool) error { // FILL WITH YOUR CODE } func (s *MyScope) SetScopeBase(base *scopes.ScopeBase) { s.base = base } // MAIN ************************************************************************ func main() { if err := scopes.Run(&MyScope{}); err != nil { log.Fatalln(err) } }
Example
This is an example returning 2 simple items with their preview.
You can find this example under $GOPATH/src/launchpad.net/go-
unityscopes/v2/tests/goscope
package main import ( "launchpad.net/go-unityscopes/v2" "log" ) const searchCategoryTemplate = `{ "schema-version": 1, "template": { "category-layout": "grid", "card-size": "small" }, "components": { "title": "title", "art": "art", "subtitle": "username" } }` // SCOPE *********************************************************************** var scope_interface scopes.Scope type MyScope struct { base *scopes.ScopeBase } func (s *MyScope) Preview(result *scopes.Result, metadata *scopes.ActionMetadata, reply *scopes.PreviewReply, cancelled <-chan bool) error { layout1col := scopes.NewColumnLayout(1) layout2col := scopes.NewColumnLayout(2) layout3col := scopes.NewColumnLayout(3) // Single column layout layout1col.AddColumn("image", "header", "summary", "actions") // Two column layout layout2col.AddColumn("image") layout2col.AddColumn("header", "summary", "actions") // Three cokumn layout layout3col.AddColumn("image") layout3col.AddColumn("header", "summary", "actions") layout3col.AddColumn() // Register the layouts we just created reply.RegisterLayout(layout1col, layout2col, layout3col) header := scopes.NewPreviewWidget("header", "header") // It has title and a subtitle properties header.AddAttributeMapping("title", "title") header.AddAttributeMapping("subtitle", "subtitle") // Define the image section image := scopes.NewPreviewWidget("image", "image") // It has a single source property, mapped to the result's art property image.AddAttributeMapping("source", "art") // Define the summary section description := scopes.NewPreviewWidget("summary", "text") // It has a text property, mapped to the result's description property description.AddAttributeMapping("text", "description") // build variant map. tuple1 := make(map[string]interface{}) tuple1["id"] = "open" tuple1["label"] = "Open" tuple1["uri"] = "application:///tmp/non-existent.desktop" tuple2 := make(map[string]interface{}) tuple1["id"] = "download" tuple1["label"] = "Download" tuple3 := make(map[string]interface{}) tuple1["id"] = "hide" tuple1["label"] = "Hide" actions := scopes.NewPreviewWidget("actions", "actions") actions.AddAttributeValue("actions", []interface{}{tuple1, tuple2, tuple3}) var scope_data string metadata.ScopeData(scope_data) if len(scope_data) > 0 { extra := scopes.NewPreviewWidget("extra", "text") extra.AddAttributeValue("text", "test Text") reply.PushWidgets(header, image, description, actions, extra) } else { reply.PushWidgets(header, image, description, actions) } return nil } func (s *MyScope) Search(query *scopes.CannedQuery, metadata *scopes.SearchMetadata, reply *scopes.SearchReply, cancelled <-chan bool) error { root_department := s.CreateDepartments(query, metadata, reply) reply.RegisterDepartments(root_department) // test incompatible features in RTM version of libunity-scopes filter1 := scopes.NewOptionSelectorFilter("f1", "Options", false) var filterState scopes.FilterState // for RTM version of libunity-scopes we should see a log message reply.PushFilters([]scopes.Filter{filter1}, filterState) return s.AddQueryResults(reply, query.QueryString()) } func (s *MyScope) SetScopeBase(base *scopes.ScopeBase) { s.base = base } // RESULTS ********************************************************************* func (s *MyScope) AddQueryResults(reply *scopes.SearchReply, query string) error { cat := reply.RegisterCategory("category", "Category", "", searchCategoryTemplate) result := scopes.NewCategorisedResult(cat) result.SetURI("http://localhost/" + query) result.SetDndURI("http://localhost_dnduri" + query) result.SetTitle("TEST" + query) result.SetArt("https://pbs.twimg.com/profile_images/1117820653/5ttls5.jpg.png") result.Set("test_value_bool", true) result.Set("test_value_string", "test_value"+query) result.Set("test_value_int", 1999) result.Set("test_value_float", 1.999) if err := reply.Push(result); err != nil { return err } result.SetURI("http://localhost2/" + query) result.SetDndURI("http://localhost_dnduri2" + query) result.SetTitle("TEST2") result.SetArt("https://pbs.twimg.com/profile_images/1117820653/5ttls5.jpg.png") result.Set("test_value_bool", false) result.Set("test_value_string", "test_value2"+query) result.Set("test_value_int", 2000) result.Set("test_value_float", 2.100) // add a variant map value m := make(map[string]interface{}) m["value1"] = 1 m["value2"] = "string_value" result.Set("test_value_map", m) // add a variant array value l := make([]interface{}, 0) l = append(l, 1999) l = append(l, "string_value") result.Set("test_value_array", l) if err := reply.Push(result); err != nil { return err } return nil } // DEPARTMENTS ***************************************************************** func SearchDepartment(root *scopes.Department, id string) *scopes.Department { sub_depts := root.Subdepartments() for _, element := range sub_depts { if element.Id() == id { return element } } return nil } func (s *MyScope) GetRockSubdepartments(query *scopes.CannedQuery, metadata *scopes.SearchMetadata, reply *scopes.SearchReply) *scopes.Department { active_dep, err := scopes.NewDepartment("Rock", query, "Rock Music") if err == nil { active_dep.SetAlternateLabel("Rock Music Alt") department, _ := scopes.NewDepartment("60s", query, "Rock from the 60s") active_dep.AddSubdepartment(department) department2, _ := scopes.NewDepartment("70s", query, "Rock from the 70s") active_dep.AddSubdepartment(department2) } return active_dep } func (s *MyScope) GetSoulSubdepartments(query *scopes.CannedQuery, metadata *scopes.SearchMetadata, reply *scopes.SearchReply) *scopes.Department { active_dep, err := scopes.NewDepartment("Soul", query, "Soul Music") if err == nil { active_dep.SetAlternateLabel("Soul Music Alt") department, _ := scopes.NewDepartment("Motown", query, "Motown Soul") active_dep.AddSubdepartment(department) department2, _ := scopes.NewDepartment("New Soul", query, "New Soul") active_dep.AddSubdepartment(department2) } return active_dep } func (s *MyScope) CreateDepartments(query *scopes.CannedQuery, metadata *scopes.SearchMetadata, reply *scopes.SearchReply) *scopes.Department { department, _ := scopes.NewDepartment("", query, "Browse Music") department.SetAlternateLabel("Browse Music Alt") rock_dept := s.GetRockSubdepartments(query, metadata, reply) if rock_dept != nil { department.AddSubdepartment(rock_dept) } soul_dept := s.GetSoulSubdepartments(query, metadata, reply) if soul_dept != nil { department.AddSubdepartment(soul_dept) } return department } // MAIN ************************************************************************ func main() { if err := scopes.Run(&MyScope{}); err != nil { log.Fatalln(err) } }
Compiling the scope
Change to the directory containing your go scope code
$ cd $GOPATH/src/my-goscope
Build the scope
$ go build
You will find the binary in the current directory.
Installing the scope to the GOPATH
$ go install my-goscope
You will find the binary at $GOPATH/bin
.
Note that you can just install the scope without the compilation step. Installing forces a previous compilation.
Running the scope
You can run the scope on your desktop with the unity-scope-tool
command that
we installed previously.
You need to provide an .ini
file indicating to unity-scope-tool
where to
find your scope binary, similar to what you have to do for a C++ scope.
This is a sample ini file for the example above.
[ScopeConfig] ScopeRunner=$GOPATH/bin/my-goscope --runtime %R --scope %S DisplayName = mock.DisplayName Description = mock.Description Author = test Art = mock.Art Icon = /mock.Icon SearchHint = mock.SearchHint HotKey = mock.HotKey [Appearance] PageHeader.Logo = http://assets.ubuntu.com/sites/ubuntu/1110/u/img/logos/logo-ubuntu-orange.svg PageHeader.ForegroundColor = white PageHeader.Background = color://black ShapeImages = false
To run the scope with unity-scope-tool run the following command:
$ unity-scope-tool ./my-goscope.ini
Cross Compiling Go scopes
In this section we will cross compile our Go scope for ARM architecture using a chroot.
First, install the required packages to setup a chroot
$ sudo apt-get install click-dev schroot
Create a chroot to cross-compile
In this case we are using version 15.04 (Vivid), if you use a different distribution the names of packages and/or version may change.
Note that you can also create a click chroot from the UbuntuSDK.
$ sudo click chroot -a armhf -f ubuntu-sdk-15.04 -s vivid create
This will setup yout chroot. It will download all the required packages for the base system
Then, install all necessary packages in the chroot
$ sudo click chroot -a armhf -f ubuntu-sdk-15.04 -s vivid maint apt-get install golang-go golang-go-linux-arm libglib2.0-dev:armhf crossbuild-essential-armhf
Cross-compile your scope
Change to the scope directory
$ cd $GOPATH/src/my-goscope
Cross-compile with the following command
$ click chroot -a armhf -f ubuntu-sdk-15.04 -s vivid run CGO_ENABLED=1 GOARCH=arm GOARM=7 PKG_CONFIG_LIBDIR=/usr/lib/arm-linux-gnueabihf/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig GOPATH=/usr/share/gocode/:~/dev-go CC=arm-linux-gnueabihf-gcc CXX=arm-linux-gnueabihf-g++ go build -ldflags '-extld=arm-linux-gnueabihf-g++'
You will find the ARM binary in the $GOPATH/src/my-goscope directory.
Going further
Now that you have a working scope, you can learn how to make it shine by changing its appearance and making its content feel at home.
Get started with our customization and brandingguide.