├── .gitignore
├── README.md
├── auto
└── auto.go
├── cmd
├── chat
│ ├── assets
│ │ ├── app.css
│ │ ├── app.js
│ │ ├── gopher.png
│ │ └── index.html
│ └── chat.go
├── gopher
│ ├── Makefile
│ ├── bindata.go
│ ├── bitmap.go
│ ├── data
│ │ ├── out01.png
│ │ ├── out02.png
│ │ ├── out03.png
│ │ └── waiting.png
│ ├── gopher.go
│ ├── gopher.ico
│ ├── gopher.rc
│ ├── main.go
│ └── window.go
├── gopherc
│ └── main.go
├── gopherfeed
│ └── gopherfeed.go
├── hey-gopher
│ ├── assets
│ │ ├── index.html
│ │ ├── out01.png
│ │ ├── out02.png
│ │ ├── out03.png
│ │ └── waiting.png
│ └── main.go
└── sl
│ └── sl.cmd
├── gopher.go
└── misc
├── screenshot
├── 9Xf0BTM.gif
├── BgiIAj9.gif
├── DTIBM9W.gif
├── K9h25F5.png
├── OKqbF7n.gif
├── PMVBSJ2.png
├── UEdmHYI.png
└── xt550tv.gif
└── vim
├── autoload
└── gopher.vim
└── plugin
└── heygopher.vim
/.gitignore:
--------------------------------------------------------------------------------
1 | *.exe*
2 | *.syso
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Gopher
2 |
3 | 
4 |
5 | Love Gopher
6 |
7 | This is a desktop mascot application running on your desktop on windows.
8 |
9 | ## Feature
10 |
11 | At the first, you need to run `gopher.exe`.
12 |
13 | ### Walking
14 |
15 | 
16 |
17 | So Sweet!
18 |
19 | ### SL
20 |
21 | If you did mis-type `ls` as `sl`.
22 |
23 | 
24 |
25 | So Fast!
26 |
27 | ### Notification
28 |
29 | This repository bundled `gopherc.exe` that is client application to operate Gopher.
30 |
31 | ```
32 | gopherc -m Hello
33 | ```
34 |
35 | 
36 |
37 | You need more gophers?
38 |
39 | 
40 |
41 | Hello Gopher!
42 |
43 | ### Jumping
44 |
45 | ```
46 | gopherc -j
47 | ```
48 |
49 | 
50 |
51 | Looking Good!
52 |
53 | ### Vim plugin
54 |
55 | Use `misc/vim` if you are vimmer.
56 |
57 | ```
58 | :HeyGopher おなかすいた
59 | ```
60 |
61 | 
62 |
63 | So don't worry even if you are remaining alone at your office and you feel lonely.
64 |
65 | ### RSS Notification
66 |
67 | Run `gopherfeed.exe` to aggregate RSS feed.
68 |
69 | 
70 |
71 | It's Nice!
72 |
73 | ### Websocket Chat
74 |
75 | 
76 |
77 | It seems that the members of the chat room are talking on your windows desktop.
78 |
79 | ## Requirements
80 |
81 | No, this is fully statically executable file. And this's not CGO.
82 | But unfortunately, this only works on windows.
83 |
84 | ## Installation
85 |
86 | ```
87 | cd cmd\gopher
88 | mingw32-make
89 | ```
90 | And copy `gopher.exe` into the path which is contained in %PATH% environment variables.
91 |
92 | ## License
93 |
94 | This code is provided under the MIT license. See http://mattn.mit-license.org/ .
95 | The image files of gopher which is created by Renee French are provided under the [Creative Commons 3.0 Attribution license](https://creativecommons.org/licenses/by/3.0/).
96 |
97 | ## Author
98 |
99 | Yasuhiro Matsumoto (a.k.a mattn)
100 |
--------------------------------------------------------------------------------
/auto/auto.go:
--------------------------------------------------------------------------------
1 | package auto
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/mattn/gopher"
7 | )
8 |
9 | func init() {
10 | for i := 1; i <= 10; i++ {
11 | gopher.Create(fmt.Sprintf("ʕ◔ϖ◔ʔ .oO( I'm No%d )", i))
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/cmd/chat/assets/app.css:
--------------------------------------------------------------------------------
1 | @charset "utf-8";
2 | body{ font-family: Meiryo, Verdana, sans-serif; }
3 | #logo { float: left; }
4 | #chat { margin-left: 200px }
5 | textarea { width: 500px; padding: 10px; }
6 | #whoami { font-weight: bold; }
7 |
--------------------------------------------------------------------------------
/cmd/chat/assets/app.js:
--------------------------------------------------------------------------------
1 | $(function() {
2 | var ws = new WebSocket("ws://" + location.host + "/chat");
3 | ws.onerror = function(m) {
4 | console.log("Error occured: " + m.data);
5 | };
6 | ws.onmessage = function(m) {
7 | var msg = JSON.parse(m.data);
8 | switch (msg.type) {
9 | case 'whoami': $('#whoami').text(msg.user); break;
10 | case 'message': $('#chatbox').append($('
').text(msg.user + ": " + msg.value)); break;
11 | }
12 | };
13 | $('#send').click(function() {
14 | ws.send(JSON.stringify({type: 'message', value: $('#text').val()}));
15 | $('#text').val("");
16 | });
17 | });
18 | // vim:set et ts=2:
19 |
--------------------------------------------------------------------------------
/cmd/chat/assets/gopher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattn/gopher/d28e0cd2e3b2fa5326645c51f8e0b53273137123/cmd/chat/assets/gopher.png
--------------------------------------------------------------------------------
/cmd/chat/assets/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Gopher Chat
6 |
7 |
8 |
9 |
10 |
11 | Gopher Chat
12 |
13 |

14 |
15 |
16 |
I am ...
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/cmd/chat/chat.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "io"
7 | "log"
8 | "net/http"
9 | "os"
10 | "os/signal"
11 | "sync"
12 |
13 | "github.com/mattn/gopher"
14 | "golang.org/x/net/websocket"
15 | )
16 |
17 | var (
18 | conns = make(map[*websocket.Conn]string)
19 | id = 0
20 | mutex sync.Mutex
21 | )
22 |
23 | type msg struct {
24 | Type string `json:"type"`
25 | User string `json:"user"`
26 | Value string `json:"value"`
27 | }
28 |
29 | func nextId() int {
30 | mutex.Lock()
31 | defer mutex.Unlock()
32 | id++
33 | return id
34 | }
35 |
36 | func server(ws *websocket.Conn) {
37 | defer ws.Close()
38 |
39 | user := fmt.Sprintf("user%03d", nextId())
40 | b, err := json.Marshal(&msg{Type: "whoami", User: user})
41 | if err != nil {
42 | log.Println("login failed:", err.Error())
43 | return
44 | }
45 | if err := websocket.Message.Send(ws, string(b)); err != nil {
46 | log.Println("login failed:", err.Error())
47 | return
48 | }
49 | b, err = json.Marshal(&msg{Type: "join", User: user})
50 | if err != nil {
51 | log.Println("login failed:", err.Error())
52 | return
53 | }
54 | for conn, _ := range conns {
55 | if err := websocket.Message.Send(conn, string(b)); err != nil {
56 | log.Println("send failed:", err)
57 | }
58 | }
59 | conns[ws] = user
60 |
61 | if ws.Request().URL.Query().Get("mode") == "" {
62 | gopher.Create(user)
63 | }
64 |
65 | for {
66 | var b []byte
67 | if err := websocket.Message.Receive(ws, &b); err != nil {
68 | if err != io.EOF {
69 | log.Println("receive failed:", err.Error())
70 | }
71 | delete(conns, ws)
72 | for _, t := range gopher.LookupByName(user) {
73 | t.Terminate()
74 | }
75 | return
76 | }
77 |
78 | var m msg
79 | err := json.Unmarshal(b, &m)
80 | if err != nil {
81 | log.Println("send failed:", err.Error())
82 | continue
83 | }
84 | m.User = user
85 |
86 | b, err = json.Marshal(&m)
87 | if err != nil {
88 | log.Println("send failed:", err.Error())
89 | continue
90 | }
91 |
92 | for conn, _ := range conns {
93 | if err := websocket.Message.Send(conn, string(b)); err != nil {
94 | log.Println("send failed:", err)
95 | }
96 | }
97 |
98 | if m.Type == "message" {
99 | for _, u := range gopher.LookupByName(user) {
100 | u.Message(user+": "+m.Value, "")
101 | }
102 | }
103 | }
104 | }
105 |
106 | func main() {
107 | sig := make(chan os.Signal, 1)
108 | signal.Notify(sig, os.Interrupt)
109 | go func() {
110 | <-sig
111 | for _, t := range gopher.Lookup() {
112 | t.Terminate()
113 | }
114 | os.Exit(0)
115 | }()
116 |
117 | http.Handle("/", http.FileServer(http.Dir("assets")))
118 | http.Handle("/chat", websocket.Handler(server))
119 | log.Println("started at :8888")
120 | log.Fatal(http.ListenAndServe(":8888", nil))
121 | }
122 |
--------------------------------------------------------------------------------
/cmd/gopher/Makefile:
--------------------------------------------------------------------------------
1 | IMAGES = $(wildcard data/*)
2 |
3 | all : gopher.exe
4 |
5 | gopher.exe : main.go bindata.go Makefile gopher.syso
6 | go build -ldflags="-H windowsgui"
7 |
8 | bindata.go : $(IMAGES) main.go
9 | go-bindata data
10 |
11 | gopher.syso : gopher.rc
12 | windres gopher.rc gopher.syso
13 |
14 | clean :
15 | -rm *.syso *.exe
16 |
--------------------------------------------------------------------------------
/cmd/gopher/bitmap.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "errors"
6 | "image"
7 | _ "image/png"
8 | "sync"
9 | "unsafe"
10 |
11 | "github.com/cwchiu/go-winapi"
12 | glib "github.com/mattn/gopher"
13 | )
14 |
15 | type sceneInfo struct {
16 | hBitmap winapi.HBITMAP
17 | hRgn winapi.HRGN
18 | off int // offset to play walking scene
19 | }
20 |
21 | func hBitmapFromImage(img image.Image) (winapi.HBITMAP, error) {
22 | var bi winapi.BITMAPV5HEADER
23 | bi.BiSize = uint32(unsafe.Sizeof(bi))
24 | bi.BiWidth = int32(img.Bounds().Dx())
25 | bi.BiHeight = -int32(img.Bounds().Dy())
26 | bi.BiPlanes = 1
27 | bi.BiBitCount = 32
28 | bi.BiCompression = winapi.BI_BITFIELDS
29 | bi.BV4RedMask = 0x00FF0000
30 | bi.BV4GreenMask = 0x0000FF00
31 | bi.BV4BlueMask = 0x000000FF
32 | bi.BV4AlphaMask = 0xFF000000
33 |
34 | hdc := winapi.GetDC(0)
35 | defer winapi.ReleaseDC(0, hdc)
36 |
37 | var bits unsafe.Pointer
38 | hBitmap := winapi.CreateDIBSection(
39 | hdc, &bi.BITMAPINFOHEADER, winapi.DIB_RGB_COLORS, &bits, 0, 0)
40 | switch hBitmap {
41 | case 0, winapi.ERROR_INVALID_PARAMETER:
42 | return 0, errors.New("CreateDIBSection failed")
43 | }
44 |
45 | ba := (*[1 << 30]byte)(unsafe.Pointer(bits))
46 | i := 0
47 | for y := img.Bounds().Min.Y; y != img.Bounds().Max.Y; y++ {
48 | for x := img.Bounds().Min.X; x != img.Bounds().Max.X; x++ {
49 | r, g, b, a := img.At(x, y).RGBA()
50 | ba[i+3] = byte(a >> 8)
51 | ba[i+2] = byte(r >> 8)
52 | ba[i+1] = byte(g >> 8)
53 | ba[i+0] = byte(b >> 8)
54 | i += 4
55 | }
56 | }
57 | return hBitmap, nil
58 | }
59 |
60 | func loadImage(filename string) (image.Image, error) {
61 | img, _, err := image.Decode(bytes.NewBuffer(MustAsset(filename)))
62 | return img, err
63 | }
64 |
65 | func reverseImage(img image.Image) image.Image {
66 | // make reversed image
67 | newimg := image.NewRGBA(img.Bounds())
68 | Dx := img.Bounds().Dx()
69 | for y := img.Bounds().Min.Y; y != img.Bounds().Max.Y; y++ {
70 | for x := img.Bounds().Min.X; x != img.Bounds().Max.X; x++ {
71 | newimg.Set(Dx-x, y, img.At(x, y))
72 | }
73 | }
74 | return newimg
75 | }
76 |
77 | func makeGopher() (*Gopher, error) {
78 | var err error
79 | var img [8]image.Image
80 |
81 | files := []string{
82 | "data/out01.png",
83 | "data/out02.png",
84 | "data/out03.png",
85 | "data/waiting.png",
86 | }
87 |
88 | var wg sync.WaitGroup
89 |
90 | // make scene 1, 2, 3, and schene waiting
91 | for i, fname := range files {
92 | img[i], err = loadImage(fname)
93 | if err != nil {
94 | return nil, err
95 | }
96 | hBitmap[i], err = hBitmapFromImage(img[i])
97 | if err != nil {
98 | return nil, err
99 | }
100 |
101 | // create region for window
102 | wg.Add(1)
103 | go func(i int) {
104 | hRgn[i] = toRgn(img[i])
105 | wg.Done()
106 | }(i)
107 | }
108 |
109 | // make reverse step 4, 5, 6, and waiting for right
110 | for i := 4; i <= 7; i++ {
111 | img[i] = reverseImage(img[i-4])
112 | hBitmap[i], err = hBitmapFromImage(img[i])
113 | if err != nil {
114 | return nil, err
115 | }
116 |
117 | // create region for window
118 | wg.Add(1)
119 | go func(i int) {
120 | hRgn[i] = toRgn(img[i])
121 | wg.Done()
122 | }(i)
123 | }
124 |
125 | wg.Wait()
126 |
127 | bounds := img[0].Bounds()
128 | var rc winapi.RECT
129 | winapi.SystemParametersInfo(winapi.SPI_GETWORKAREA, 0, unsafe.Pointer(&rc), 0)
130 |
131 | screenWidth = int(rc.Right) - bounds.Dx()
132 | scenes = [2][5]sceneInfo{
133 | {
134 | {hBitmap[0], hRgn[0], 0},
135 | {hBitmap[1], hRgn[1], 2},
136 | {hBitmap[2], hRgn[2], 4},
137 | {hBitmap[1], hRgn[1], 2},
138 | {hBitmap[3], hRgn[3], 0},
139 | },
140 | {
141 | {hBitmap[4], hRgn[4], 0},
142 | {hBitmap[5], hRgn[5], 2},
143 | {hBitmap[6], hRgn[6], 4},
144 | {hBitmap[5], hRgn[5], 2},
145 | {hBitmap[7], hRgn[7], 0},
146 | },
147 | }
148 |
149 | mode := Walking
150 | dx := 10
151 | if *slmode {
152 | mode = SL
153 | dx = 20
154 | }
155 |
156 | // initialize gopher
157 | return &Gopher{
158 | task: make(chan glib.Msg, 50),
159 | x: -bounds.Dx(),
160 | y: int(rc.Bottom) - bounds.Dy(),
161 | w: bounds.Dx(),
162 | h: bounds.Dy(),
163 | dx: dx,
164 | dy: 0,
165 | wait: 0,
166 | mode: mode,
167 | }, nil
168 | }
169 |
170 | func toRgn(img image.Image) winapi.HRGN {
171 | hRgn := winapi.CreateRectRgn(0, 0, 0, 0)
172 | for y := img.Bounds().Min.Y; y != img.Bounds().Max.Y; y++ {
173 | opaque := false
174 | v := 0
175 | for x := img.Bounds().Min.X; x != img.Bounds().Max.X; x++ {
176 | _, _, _, a := img.At(x, y).RGBA()
177 | // combine transparent colors
178 | if a > 0 {
179 | if !opaque {
180 | opaque = true
181 | v = x
182 | }
183 | } else {
184 | if opaque {
185 | addMask(hRgn, v, y, x, y+1)
186 | opaque = false
187 | }
188 | }
189 | }
190 | if opaque {
191 | addMask(hRgn, v, y, img.Bounds().Max.X, y+1)
192 | }
193 | }
194 | return hRgn
195 | }
196 |
197 | func addMask(hRgn winapi.HRGN, left, top, right, bottom int) {
198 | mask := winapi.CreateRectRgn(int32(left), int32(top), int32(right), int32(bottom))
199 | winapi.CombineRgn(hRgn, mask, hRgn, winapi.RGN_OR)
200 | winapi.DeleteObject(winapi.HGDIOBJ(mask))
201 | }
202 |
--------------------------------------------------------------------------------
/cmd/gopher/data/out01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattn/gopher/d28e0cd2e3b2fa5326645c51f8e0b53273137123/cmd/gopher/data/out01.png
--------------------------------------------------------------------------------
/cmd/gopher/data/out02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattn/gopher/d28e0cd2e3b2fa5326645c51f8e0b53273137123/cmd/gopher/data/out02.png
--------------------------------------------------------------------------------
/cmd/gopher/data/out03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattn/gopher/d28e0cd2e3b2fa5326645c51f8e0b53273137123/cmd/gopher/data/out03.png
--------------------------------------------------------------------------------
/cmd/gopher/data/waiting.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattn/gopher/d28e0cd2e3b2fa5326645c51f8e0b53273137123/cmd/gopher/data/waiting.png
--------------------------------------------------------------------------------
/cmd/gopher/gopher.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | gopherlib "github.com/mattn/gopher"
5 | )
6 |
7 | type (
8 | mode int
9 | scene int
10 | )
11 |
12 | const (
13 | Walking mode = iota
14 | Jumping
15 | HighJumping
16 | Waiting
17 | Stopping
18 | SL
19 | )
20 |
21 | type Gopher struct {
22 | msg gopherlib.Msg // current message
23 | task chan gopherlib.Msg // message queue
24 | x, y int // location of gopher
25 | w, h int // size of gopher
26 | dx, dy int // amount of movement
27 | wait int // counter for delay
28 | mode mode // animation mode
29 | scene scene // index of scenes
30 | }
31 |
32 | func (g *Gopher) NextScene() scene {
33 | g.scene += 1
34 | if g.scene == 4 {
35 | g.scene = 0
36 | }
37 | return g.scene
38 | }
39 |
40 | func (g *Gopher) Scene() scene {
41 | return g.scene
42 | }
43 |
44 | func (g *Gopher) X() int {
45 | return g.x
46 | }
47 |
48 | func (g *Gopher) Y() int {
49 | return g.y
50 | }
51 |
52 | func (g *Gopher) W() int {
53 | return g.w
54 | }
55 |
56 | func (g *Gopher) H() int {
57 | return g.h
58 | }
59 |
60 | func (g *Gopher) Dx() int {
61 | return g.dx
62 | }
63 |
64 | func (g *Gopher) Dy() int {
65 | return g.dy
66 | }
67 |
68 | func (g *Gopher) Motion() {
69 | switch g.mode {
70 | case HighJumping:
71 | g.x += g.dx / 2
72 | g.y += g.dy
73 | g.dy++
74 | if g.dy > 20 {
75 | g.SetMode(Walking)
76 | }
77 | case Jumping:
78 | g.x += g.dx / 2
79 | g.y += g.dy
80 | g.dy++
81 | if g.dy > 10 {
82 | g.SetMode(Walking)
83 | }
84 | default:
85 | g.x += g.dx
86 | g.y += g.dy
87 | }
88 | }
89 |
90 | func (g *Gopher) Move(dx, dy int) {
91 | g.x = dx
92 | g.y = dy
93 | }
94 |
95 | func (g *Gopher) MoveTo(x, y int) {
96 | g.x = x
97 | g.y = y
98 | }
99 |
100 | func (g *Gopher) Take() (string, bool) {
101 | select {
102 | case g.msg = <-g.task:
103 | return g.msg.Method, true
104 | default:
105 | }
106 | return "", false
107 | }
108 |
109 | func (g *Gopher) Mode() mode {
110 | return g.mode
111 | }
112 |
113 | func (g *Gopher) ClearMsg() {
114 | for len(g.task) > 0 {
115 | <-g.task
116 | }
117 | }
118 |
119 | func (g *Gopher) PushMsg(m gopherlib.Msg) {
120 | g.task <- m
121 | }
122 |
123 | func (g *Gopher) SetMode(m mode) {
124 | g.mode = m
125 | switch m {
126 | case Walking:
127 | g.dy = 0
128 | case Stopping:
129 | g.wait = 10
130 | case Jumping:
131 | g.dy = -10
132 | case HighJumping:
133 | g.scene = 0
134 | g.dy = -20
135 | case Waiting:
136 | g.scene = 0
137 | g.wait = 100
138 | }
139 | }
140 |
141 | func (g *Gopher) Turn() {
142 | g.dx = -g.dx // turn over
143 | }
144 |
145 | func (g *Gopher) WakeUp() {
146 | g.wait = 0
147 | }
148 |
149 | func (g *Gopher) Time() int {
150 | return g.wait
151 | }
152 |
153 | func (g *Gopher) Idle() int {
154 | g.wait--
155 | return g.wait
156 | }
157 |
158 | func (g *Gopher) CurrentSceneInfo() sceneInfo {
159 | s := g.scene
160 | if g.mode == Waiting {
161 | s = 4
162 | }
163 | dir := 0
164 | if g.dx < 0 {
165 | dir = 1
166 | }
167 | return scenes[dir][s]
168 | }
169 |
170 | func (g *Gopher) SetContent(c string) {
171 | g.msg.Content = c
172 | }
173 |
174 | func (g *Gopher) Content() string {
175 | return g.msg.Content
176 | }
177 |
178 | func (g *Gopher) Link() string {
179 | return g.msg.Link
180 | }
181 |
182 | func (g *Gopher) Busy() bool {
183 | return len(gopher.task) == cap(gopher.task)
184 | }
185 |
--------------------------------------------------------------------------------
/cmd/gopher/gopher.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattn/gopher/d28e0cd2e3b2fa5326645c51f8e0b53273137123/cmd/gopher/gopher.ico
--------------------------------------------------------------------------------
/cmd/gopher/gopher.rc:
--------------------------------------------------------------------------------
1 | #include "windows.h"
2 |
3 | #define VER_FILEVERSION 1,0,0,0
4 | #define VER_FILEVERSION_STR "1.0.0.0\0"
5 | #define VER_PRODUCTVERSION 1,0,0,0
6 | #define VER_PRODUCTVERSION_STR "1.0\0"
7 |
8 | VS_VERSION_INFO VERSIONINFO
9 | FILEVERSION VER_FILEVERSION
10 | PRODUCTVERSION VER_PRODUCTVERSION
11 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
12 | FILEOS VOS__WINDOWS32
13 | FILETYPE VFT_APP
14 | FILESUBTYPE VFT2_UNKNOWN
15 | BEGIN
16 | BLOCK "StringFileInfo"
17 | BEGIN
18 | BLOCK "040904b0"
19 | BEGIN
20 | VALUE "Comments", ""
21 | VALUE "CompanyName", ""
22 | VALUE "FileDescription", "Gopher"
23 | VALUE "FileVersion", VER_FILEVERSION_STR
24 | VALUE "InternalName", "Gopher"
25 | VALUE "OriginalFileName", "Gopher.exe"
26 | VALUE "LegalCopyright", "(c) mattn"
27 | VALUE "ProductName", "Gopher"
28 | VALUE "ProductVersion", VER_PRODUCTVERSION_STR
29 | END
30 | END
31 | BLOCK "VarFileInfo"
32 | BEGIN
33 | VALUE "Translation", 0x409, 1200
34 | END
35 | END
36 |
37 | IDI_GYAGOWIN ICON "gopher.ico"
38 |
--------------------------------------------------------------------------------
/cmd/gopher/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "os"
5 | )
6 |
7 | func main() {
8 | os.Exit(runGopher())
9 | }
10 |
--------------------------------------------------------------------------------
/cmd/gopher/window.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "errors"
7 | "flag"
8 | "fmt"
9 | "math/rand"
10 | "net/url"
11 | "os"
12 | "syscall"
13 | "time"
14 | "unsafe"
15 |
16 | "github.com/cwchiu/go-winapi"
17 | gopherlib "github.com/mattn/gopher"
18 | )
19 |
20 | const (
21 | DT_TOP = 0x00000000
22 | DT_LEFT = 0x00000000
23 | DT_CENTER = 0x00000001
24 | DT_RIGHT = 0x00000002
25 | DT_VCENTER = 0x00000004
26 | DT_BOTTOM = 0x00000008
27 | DT_WORDBREAK = 0x00000010
28 | DT_SINGLELINE = 0x00000020
29 | DT_EXPANDTABS = 0x00000040
30 | DT_TABSTOP = 0x00000080
31 | DT_NOCLIP = 0x00000100
32 | DT_EXTERNALLEADING = 0x00000200
33 | DT_CALCRECT = 0x00000400
34 | DT_NOPREFIX = 0x00000800
35 | DT_INTERNAL = 0x00001000
36 | DT_EDITCONTROL = 0x00002000
37 | DT_PATH_ELLIPSIS = 0x00004000
38 | DT_END_ELLIPSIS = 0x00008000
39 | DT_MODIFYSTRING = 0x00010000
40 | DT_RTLREADING = 0x00020000
41 | DT_WORD_ELLIPSIS = 0x00040000
42 | DT_NOFULLWIDTHCHARBREAK = 0x00080000
43 | DT_HIDEPREFIX = 0x00100000
44 | DT_PREFIXONLY = 0x00200000
45 | LWA_COLORKEY = 0x00001
46 | LWA_ALPHA = 0x00002
47 | )
48 |
49 | type tagCOPYDATASTRUCT struct {
50 | DwData uintptr
51 | CbData uint32
52 | LpData unsafe.Pointer
53 | }
54 |
55 | var (
56 | user32 = syscall.NewLazyDLL("user32.dll")
57 | procSetWindowRgn = user32.NewProc("SetWindowRgn")
58 | procSetLayeredWindowAttributes = user32.NewProc("SetLayeredWindowAttributes")
59 | procReplyMessage = user32.NewProc("ReplyMessage")
60 | procDrawText = user32.NewProc("DrawTextW")
61 | shell32 = syscall.NewLazyDLL("shell32.dll")
62 | procShellExecute = shell32.NewProc("ShellExecuteW")
63 | )
64 |
65 | var (
66 | hBitmap [8]winapi.HBITMAP
67 | hRgn [8]winapi.HRGN
68 | hFont winapi.HFONT
69 | white = winapi.RGB(0xFF, 0xFF, 0xFF)
70 | black = winapi.RGB(0x00, 0x00, 0x00)
71 |
72 | gopher *Gopher
73 |
74 | screenWidth int // screen width
75 |
76 | // sceneInfo have two directions. first is going right, second is left.
77 | // they have four scenesInfo, last one is gopher that have balloon.
78 | scenes [2][5]sceneInfo
79 |
80 | name = flag.String("n", "", "name of gopher")
81 | slmode = flag.Bool("sl", false, "sl mode")
82 | )
83 |
84 | func updateWindowRegion(hWnd winapi.HWND) {
85 | s := gopher.CurrentSceneInfo()
86 | tmp := winapi.CreateRectRgn(0, 0, 0, 0)
87 | winapi.CombineRgn(tmp, s.hRgn, 0, winapi.RGN_COPY)
88 | winapi.SetWindowPos(hWnd, 0, int32(gopher.X()), int32(gopher.Y()-s.off), 0, 0,
89 | winapi.SWP_NOSIZE|winapi.SWP_NOZORDER|winapi.SWP_NOOWNERZORDER)
90 | procSetWindowRgn.Call(uintptr(hWnd), uintptr(tmp), uintptr(1))
91 | winapi.InvalidateRect(hWnd, nil, false)
92 | }
93 |
94 | func openBrowser(hWnd winapi.HWND, uri string) {
95 | if _, err := url.Parse(uri); err != nil {
96 | return
97 | }
98 | procShellExecute.Call(
99 | uintptr(hWnd),
100 | uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("open"))),
101 | uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(uri))),
102 | 0,
103 | 0,
104 | uintptr(winapi.SW_SHOW))
105 | }
106 |
107 | func paintGopher(hWnd winapi.HWND) {
108 | var ps winapi.PAINTSTRUCT
109 | s := gopher.CurrentSceneInfo()
110 | hdc := winapi.BeginPaint(hWnd, &ps)
111 | hCompatDC := winapi.CreateCompatibleDC(hdc)
112 | winapi.SelectObject(hCompatDC, winapi.HGDIOBJ(s.hBitmap))
113 | winapi.BitBlt(hdc, 0, 0, int32(gopher.W()), int32(gopher.H()), hCompatDC, 0, 0, winapi.SRCCOPY)
114 | if gopher.Mode() == Waiting {
115 | pt, _ := syscall.UTF16PtrFromString(gopher.Content())
116 | winapi.SetTextColor(hdc, black)
117 | winapi.SetBkMode(hdc, winapi.TRANSPARENT)
118 | rc := winapi.RECT{13, 135, 190, 186}
119 | old := winapi.SelectObject(hdc, winapi.HGDIOBJ(hFont))
120 | procDrawText.Call(
121 | uintptr(hdc),
122 | uintptr(unsafe.Pointer(pt)), uintptr(len([]rune(gopher.Content()))),
123 | uintptr(unsafe.Pointer(&rc)),
124 | DT_LEFT|DT_VCENTER|DT_NOPREFIX|DT_EDITCONTROL|DT_WORDBREAK)
125 | winapi.SelectObject(hdc, old)
126 | }
127 | winapi.DeleteDC(hCompatDC)
128 | winapi.EndPaint(hWnd, &ps)
129 | }
130 |
131 | func handleMethod(hWnd winapi.HWND, method string) {
132 | switch method {
133 | case "terminate":
134 | gopher.SetMode(Stopping)
135 | case "jump":
136 | gopher.SetMode(HighJumping)
137 | case "message":
138 | gopher.SetMode(Waiting)
139 | winapi.SetWindowPos(hWnd, 0, 0, 0, 0, 0,
140 | winapi.SWP_NOMOVE|winapi.SWP_NOSIZE|winapi.SWP_NOOWNERZORDER)
141 | updateWindowRegion(hWnd)
142 | default:
143 | gopher.SetMode(Waiting)
144 | gopher.SetContent("What do you mean?")
145 | updateWindowRegion(hWnd)
146 | }
147 | }
148 |
149 | func randomAction() {
150 | if rand.Int()%10 == 0 {
151 | gopher.SetMode(Jumping)
152 | } else if rand.Int()%30 == 0 {
153 | gopher.Turn()
154 | }
155 | }
156 |
157 | func animateGopher(hWnd winapi.HWND) {
158 | switch gopher.Mode() {
159 | case Stopping:
160 | if gopher.Idle() <= 0 {
161 | winapi.DestroyWindow(hWnd)
162 | }
163 | gopher.NextScene()
164 | procSetLayeredWindowAttributes.Call(uintptr(hWnd), uintptr(white), uintptr(gopher.Time()*25), LWA_ALPHA)
165 | gopher.Motion()
166 | case Waiting:
167 | if gopher.Idle() <= 0 {
168 | gopher.SetMode(Walking)
169 | }
170 | case SL:
171 | gopher.NextScene()
172 | gopher.Motion()
173 | case Walking:
174 | if gopher.NextScene() == 0 {
175 | method, ok := gopher.Take()
176 | if ok {
177 | handleMethod(hWnd, method)
178 | } else {
179 | randomAction()
180 | }
181 | }
182 | gopher.Motion()
183 | default:
184 | gopher.Motion()
185 | }
186 |
187 | if gopher.Mode() == SL {
188 | if gopher.X() > screenWidth+gopher.W() {
189 | winapi.DestroyWindow(hWnd)
190 | return
191 | }
192 | } else {
193 | // turn over
194 | if (gopher.Dx() > 0 && gopher.X() > screenWidth) || (gopher.Dx() < 0 && gopher.X() < 0) {
195 | gopher.Turn()
196 | }
197 | }
198 |
199 | // redraw one
200 | if gopher.Mode() != Waiting {
201 | updateWindowRegion(hWnd)
202 | }
203 | }
204 |
205 | func clickGopher(hWnd winapi.HWND) {
206 | if winapi.GetKeyState(winapi.VK_SHIFT) < 0 {
207 | gopher.ClearMsg()
208 | gopher.PushMsg(gopherlib.Msg{Method: "terminate"})
209 | return
210 | }
211 |
212 | switch gopher.Mode() {
213 | case Waiting:
214 | if gopher.Link() != "" {
215 | openBrowser(hWnd, gopher.Link())
216 | gopher.WakeUp()
217 | }
218 | case Walking:
219 | gopher.SetMode(Jumping)
220 | }
221 | }
222 |
223 | func ipcGopher(hWnd winapi.HWND, cds *tagCOPYDATASTRUCT) {
224 | if gopher.Busy() {
225 | procReplyMessage.Call(2)
226 | return
227 | }
228 | b := make([]byte, cds.CbData)
229 | copy(b, (*(*[1 << 20]byte)(cds.LpData))[:])
230 | var msg gopherlib.Msg
231 | if err := json.Unmarshal(b, &msg); err != nil {
232 | procReplyMessage.Call(1)
233 | return
234 | }
235 | procReplyMessage.Call(0)
236 |
237 | switch msg.Method {
238 | case "terminate":
239 | gopher.ClearMsg()
240 | gopher.PushMsg(gopherlib.Msg{Method: "terminate"})
241 | case "clear":
242 | gopher.ClearMsg()
243 | default:
244 | gopher.PushMsg(msg)
245 | }
246 | }
247 |
248 | func showErrorMessage(hWnd winapi.HWND, msg string) {
249 | s, _ := syscall.UTF16PtrFromString(msg)
250 | t, _ := syscall.UTF16PtrFromString(gopherlib.WINDOW_CLASS)
251 | winapi.MessageBox(hWnd, s, t, winapi.MB_ICONWARNING|winapi.MB_OK)
252 | }
253 |
254 | func runGopher() int {
255 | var buf bytes.Buffer
256 | flag.CommandLine.SetOutput(&buf)
257 | flag.Usage = func() {
258 | fmt.Fprintf(&buf, "Usage of %s:\n", os.Args[0])
259 | flag.PrintDefaults()
260 | showErrorMessage(0, buf.String())
261 | os.Exit(1)
262 | }
263 | flag.Parse()
264 |
265 | rand.Seed(time.Now().UnixNano())
266 |
267 | hInstance := winapi.GetModuleHandle(nil)
268 |
269 | if registerWindowClass(hInstance) == 0 {
270 | showErrorMessage(0, "registerWindowClass failed")
271 | return 1
272 | }
273 |
274 | if err := initializeInstance(hInstance, winapi.SW_SHOW); err != nil {
275 | showErrorMessage(0, err.Error())
276 | return 1
277 | }
278 |
279 | var msg winapi.MSG
280 | for winapi.GetMessage(&msg, 0, 0, 0) != 0 {
281 | winapi.TranslateMessage(&msg)
282 | winapi.DispatchMessage(&msg)
283 | }
284 |
285 | finalizeInstance(hInstance)
286 |
287 | return int(msg.WParam)
288 | }
289 |
290 | func registerWindowClass(hInstance winapi.HINSTANCE) winapi.ATOM {
291 | var wc winapi.WNDCLASSEX
292 |
293 | wc.CbSize = uint32(unsafe.Sizeof(winapi.WNDCLASSEX{}))
294 | wc.Style = 0
295 | wc.LpfnWndProc = syscall.NewCallback(wndProc)
296 | wc.CbClsExtra = 0
297 | wc.CbWndExtra = 0
298 | wc.HInstance = hInstance
299 | wc.HIcon = winapi.LoadIcon(hInstance, winapi.MAKEINTRESOURCE(132))
300 | wc.HCursor = winapi.LoadCursor(0, winapi.MAKEINTRESOURCE(winapi.IDC_HAND))
301 | wc.HbrBackground = winapi.HBRUSH(winapi.GetStockObject(winapi.WHITE_BRUSH))
302 | wc.LpszMenuName = nil
303 | wc.LpszClassName, _ = syscall.UTF16PtrFromString(gopherlib.WINDOW_CLASS)
304 |
305 | return winapi.RegisterClassEx(&wc)
306 | }
307 |
308 | func initializeInstance(hInstance winapi.HINSTANCE, nCmdShow int) error {
309 | var err error
310 | gopher, err = makeGopher()
311 | if err != nil {
312 | return err
313 | }
314 |
315 | hFont = winapi.CreateFont(
316 | 15, 0, 0, 0, winapi.FW_NORMAL, 0, 0, 0,
317 | winapi.ANSI_CHARSET, winapi.OUT_DEVICE_PRECIS,
318 | winapi.CLIP_DEFAULT_PRECIS, winapi.DEFAULT_QUALITY,
319 | winapi.VARIABLE_PITCH|winapi.FF_ROMAN, nil)
320 |
321 | title := *name
322 | if title == "" {
323 | title = gopherlib.WINDOW_CLASS
324 | }
325 | pc, _ := syscall.UTF16PtrFromString(gopherlib.WINDOW_CLASS)
326 | pt, _ := syscall.UTF16PtrFromString(title)
327 | hWnd := winapi.CreateWindowEx(
328 | winapi.WS_EX_TOOLWINDOW|winapi.WS_EX_TOPMOST|winapi.WS_EX_NOACTIVATE|winapi.WS_EX_LAYERED,
329 | pc, pt, winapi.WS_POPUP,
330 | int32(gopher.X()),
331 | int32(gopher.X()),
332 | int32(gopher.W()),
333 | int32(gopher.H()),
334 | 0, 0, hInstance, nil)
335 | if hWnd == 0 {
336 | return errors.New("CreateWindowEx failed")
337 | }
338 |
339 | updateWindowRegion(hWnd)
340 |
341 | procSetLayeredWindowAttributes.Call(uintptr(hWnd), uintptr(white), 255, LWA_ALPHA)
342 | winapi.ShowWindow(hWnd, int32(nCmdShow))
343 | winapi.SetTimer(hWnd, 1, 50, 0)
344 | return nil
345 | }
346 |
347 | func finalizeInstance(hInstance winapi.HINSTANCE) error {
348 | winapi.DeleteObject(winapi.HGDIOBJ(hFont))
349 | for _, h := range hBitmap {
350 | winapi.DeleteObject(winapi.HGDIOBJ(h))
351 | }
352 | for _, h := range hRgn {
353 | winapi.DeleteObject(winapi.HGDIOBJ(h))
354 | }
355 | return nil
356 | }
357 |
358 | func wndProc(hWnd winapi.HWND, msg uint32, wParam uintptr, lParam uintptr) uintptr {
359 | switch msg {
360 | case winapi.WM_COPYDATA:
361 | ipcGopher(hWnd, (*tagCOPYDATASTRUCT)(unsafe.Pointer(lParam)))
362 | case winapi.WM_LBUTTONDOWN:
363 | clickGopher(hWnd)
364 | case winapi.WM_PAINT:
365 | paintGopher(hWnd)
366 | case winapi.WM_TIMER:
367 | animateGopher(hWnd)
368 | case winapi.WM_DESTROY:
369 | winapi.PostQuitMessage(0)
370 | default:
371 | return winapi.DefWindowProc(hWnd, msg, wParam, lParam)
372 | }
373 | return 0
374 | }
375 |
--------------------------------------------------------------------------------
/cmd/gopherc/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "math/rand"
7 | "os"
8 | "time"
9 |
10 | "github.com/mattn/gopher"
11 | )
12 |
13 | var (
14 | exit = flag.Bool("x", false, "exit all gophers")
15 | list = flag.Bool("l", false, "list gophers")
16 | message = flag.String("m", "", "send message to gopher")
17 | link = flag.String("u", "", "send url to gopher")
18 | clear = flag.Bool("c", false, "clear message queue")
19 | jump = flag.Bool("j", false, "jump gopher")
20 | )
21 |
22 | func main() {
23 | flag.Parse()
24 |
25 | gophers := gopher.Lookup()
26 |
27 | if *list {
28 | for _, gopher := range gophers {
29 | fmt.Println(gopher.Hwnd(), gopher.Name())
30 | }
31 | os.Exit(0)
32 | }
33 |
34 | if len(gophers) == 0 {
35 | fmt.Fprintln(os.Stderr, "Gopher not exists")
36 | os.Exit(1)
37 | }
38 |
39 | if *exit {
40 | // broadcast terminate message
41 | for _, gopher := range gophers {
42 | gopher.Terminate()
43 | }
44 | return
45 | }
46 |
47 | if *jump {
48 | // broadcast jump message
49 | for _, gopher := range gophers {
50 | gopher.Jump()
51 | }
52 | return
53 | }
54 |
55 | if *clear {
56 | // broadcast clear message
57 | for _, gopher := range gophers {
58 | gopher.Clear()
59 | }
60 | if *message == "" {
61 | return
62 | }
63 | }
64 |
65 | if *message != "" {
66 | // choice one of gophers to display message
67 | rand.Seed(time.Now().UnixNano())
68 | gopher := gophers[rand.Int()%len(gophers)]
69 | for {
70 | if err := gopher.Message(*message, *link); err == nil {
71 | break
72 | }
73 | time.Sleep(200 * time.Millisecond)
74 | }
75 | } else {
76 | flag.Usage()
77 | os.Exit(1)
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/cmd/gopherfeed/gopherfeed.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "io/ioutil"
7 | "math/rand"
8 | "os"
9 | "strings"
10 | "time"
11 |
12 | "github.com/fatih/color"
13 | rss "github.com/mattn/go-pkg-rss"
14 | "github.com/mattn/go-runewidth"
15 | "github.com/mattn/gopher"
16 | )
17 |
18 | var (
19 | file = flag.String("f", "", "list of rss/atom feeds")
20 | )
21 |
22 | func findGopher() *gopher.Gopher {
23 | gophers := gopher.Lookup()
24 | if len(gophers) == 0 {
25 | return nil
26 | }
27 | return gophers[rand.Int()%len(gophers)]
28 | }
29 |
30 | func notify(item *rss.Item) {
31 | for _, link := range item.Links {
32 | var err error
33 | if gopher := findGopher(); gopher != nil {
34 | err = gopher.Message(item.Title, link.Href)
35 | }
36 | if err != nil {
37 | title := runewidth.Truncate(item.Title, 79, "...")
38 | fmt.Fprintf(color.Output, "%s\n\t%s\n",
39 | color.YellowString("%v", title),
40 | color.MagentaString("%v", link.Href))
41 | }
42 | }
43 | }
44 |
45 | func main() {
46 | flag.Parse()
47 |
48 | rand.Seed(time.Now().UnixNano())
49 |
50 | uris := []string{}
51 |
52 | uris = append(uris, flag.Args()...)
53 | if *file != "" {
54 | b, err := ioutil.ReadFile(*file)
55 | if err != nil {
56 | fmt.Fprintln(os.Stderr, os.Args[0]+":", err)
57 | os.Exit(1)
58 | }
59 | for _, line := range strings.Split(string(b), "\n") {
60 | uris = append(uris, strings.TrimSpace(line))
61 | }
62 | }
63 |
64 | if len(uris) == 0 {
65 | uris = []string{"http://feeds.feedburner.com/hatena/b/hotentry"}
66 | }
67 | for {
68 | for _, uri := range uris {
69 | err := rss.New(5, true,
70 | func(feed *rss.Feed, newchannels []*rss.Channel) {
71 | fmt.Fprintf(color.Output, "%d new channel(s) in %s\n",
72 | len(newchannels), color.GreenString("%v", feed.Url))
73 | },
74 | func(feed *rss.Feed, ch *rss.Channel, newitems []*rss.Item) {
75 | fmt.Fprintf(color.Output, "%d new item(s) in %s\n",
76 | len(newitems), color.GreenString("%v", feed.Url))
77 | for _, item := range newitems {
78 | notify(item)
79 | }
80 | },
81 | ).Fetch(uri, nil)
82 |
83 | if err != nil {
84 | fmt.Fprintf(os.Stderr, "[e] %s: %s", uri, err)
85 | }
86 | time.Sleep(10 * time.Second)
87 | }
88 | time.Sleep(3 * time.Minute)
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/cmd/hey-gopher/assets/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Hey Gopher
6 |
21 |
22 |
58 |
59 |
60 |
61 |
Hey! Gopher!
62 |
63 |

64 |
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/cmd/hey-gopher/assets/out01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattn/gopher/d28e0cd2e3b2fa5326645c51f8e0b53273137123/cmd/hey-gopher/assets/out01.png
--------------------------------------------------------------------------------
/cmd/hey-gopher/assets/out02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattn/gopher/d28e0cd2e3b2fa5326645c51f8e0b53273137123/cmd/hey-gopher/assets/out02.png
--------------------------------------------------------------------------------
/cmd/hey-gopher/assets/out03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattn/gopher/d28e0cd2e3b2fa5326645c51f8e0b53273137123/cmd/hey-gopher/assets/out03.png
--------------------------------------------------------------------------------
/cmd/hey-gopher/assets/waiting.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattn/gopher/d28e0cd2e3b2fa5326645c51f8e0b53273137123/cmd/hey-gopher/assets/waiting.png
--------------------------------------------------------------------------------
/cmd/hey-gopher/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "math/rand"
6 | "net/http"
7 | "os/exec"
8 |
9 | "github.com/mattn/gopher"
10 | )
11 |
12 | type info struct {
13 | NumGopher int `json:"num_gopher"`
14 | }
15 |
16 | func findGopher() *gopher.Gopher {
17 | gophers := gopher.Lookup()
18 | if len(gophers) == 0 {
19 | return nil
20 | }
21 | return gophers[rand.Int()%len(gophers)]
22 | }
23 |
24 | func main() {
25 | http.Handle("/", http.FileServer(http.Dir("assets")))
26 | http.HandleFunc("/stat", func(w http.ResponseWriter, r *http.Request) {
27 | w.Header().Set("Content-Type", "application/json")
28 | json.NewEncoder(w).Encode(&info{len(gopher.Lookup())})
29 | })
30 | http.HandleFunc("/start", func(w http.ResponseWriter, r *http.Request) {
31 | if r.Method == "POST" {
32 | exec.Command("gopher").Start()
33 | }
34 | })
35 | http.HandleFunc("/say", func(w http.ResponseWriter, r *http.Request) {
36 | msg := r.FormValue("message")
37 | if r.Method != "POST" || msg == "" {
38 | http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
39 | return
40 | }
41 | if gopher := findGopher(); gopher != nil {
42 | gopher.Message(msg, "")
43 | }
44 | })
45 | http.ListenAndServe(":8080", nil)
46 | }
47 |
--------------------------------------------------------------------------------
/cmd/sl/sl.cmd:
--------------------------------------------------------------------------------
1 | @echo off
2 | gopher -sl
3 |
4 |
--------------------------------------------------------------------------------
/gopher.go:
--------------------------------------------------------------------------------
1 | package gopher
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "github.com/cwchiu/go-winapi"
7 | "os/exec"
8 | "syscall"
9 | "unsafe"
10 | )
11 |
12 | const (
13 | WINDOW_CLASS = "Gopher"
14 | )
15 |
16 | type tagCOPYDATASTRUCT struct {
17 | DwData uintptr
18 | CbData uint32
19 | LpData unsafe.Pointer
20 | }
21 |
22 | var (
23 | user32 = syscall.NewLazyDLL("user32.dll")
24 | procGetClassName = user32.NewProc("GetClassNameW")
25 | ErrInvalidRequest = errors.New("Invalid request")
26 | ErrTooManyRequest = errors.New("Too many request")
27 | )
28 |
29 | type Msg struct {
30 | Method string `json:"method"`
31 | Content string `json:"content"`
32 | Link string `json:"link"`
33 | }
34 |
35 | type Gopher struct {
36 | wnd winapi.HWND
37 | }
38 |
39 | var cb = syscall.NewCallback(func(h syscall.Handle, p uintptr) uintptr {
40 | var w [256]uint16
41 | procGetClassName.Call(uintptr(h), uintptr(unsafe.Pointer(&w[0])), 255)
42 | if syscall.UTF16ToString(w[:]) == WINDOW_CLASS {
43 | gophers := (*[]*Gopher)(unsafe.Pointer(p))
44 | *gophers = append(*gophers, &Gopher{winapi.HWND(h)})
45 | }
46 | return 1
47 | })
48 |
49 | // Lookup find gophers available.
50 | func Lookup() []*Gopher {
51 | gophers := []*Gopher{}
52 | winapi.EnumChildWindows(0, cb, uintptr(unsafe.Pointer(&gophers)))
53 | return gophers
54 | }
55 |
56 | // LookupByName find gophers by name.
57 | func LookupByName(name string) []*Gopher {
58 | gophers := []*Gopher{}
59 | for _, t := range Lookup() {
60 | var buf [1024]uint16
61 | if winapi.GetWindowText(t.wnd, &buf[0], 1024) > 0 {
62 | if syscall.UTF16ToString(buf[:]) == name {
63 | gophers = append(gophers, &Gopher{winapi.HWND(t.wnd)})
64 | }
65 | }
66 | }
67 | return gophers
68 | }
69 |
70 | // Create gopher.
71 | func Create(name string) error {
72 | return exec.Command("gopher", "-n", name).Start()
73 | }
74 |
75 | func sendMessage(hWnd winapi.HWND, m *Msg) error {
76 | b, err := json.Marshal(&m)
77 | if err != nil {
78 | return err
79 | }
80 | var cds tagCOPYDATASTRUCT
81 | cds.CbData = uint32(len(b))
82 | cds.LpData = unsafe.Pointer(&b[0])
83 | ret := winapi.SendMessage(hWnd, winapi.WM_COPYDATA, 0, uintptr(unsafe.Pointer(&cds)))
84 | switch ret {
85 | case 1:
86 | return ErrInvalidRequest
87 | case 2:
88 | return ErrTooManyRequest
89 | }
90 | return nil
91 | }
92 |
93 | // Terminate gopher. If the message can't be arrived to gopher, or message
94 | // queue is full of capacity, return error.
95 | func (g *Gopher) Terminate() error {
96 | return sendMessage(g.wnd, &Msg{Method: "terminate"})
97 | }
98 |
99 | // Message send text and link. Both can be empty string. If the message can't
100 | // be arrived to gopher, or message queue is full of capacity, return error.
101 | func (g *Gopher) Message(m string, l string) error {
102 | return sendMessage(g.wnd, &Msg{
103 | Method: "message",
104 | Content: m,
105 | Link: l,
106 | })
107 | }
108 |
109 | // Clear request to clear message queue. If the message can't be arrived to
110 | // gopher, return error.
111 | func (g *Gopher) Clear() error {
112 | return sendMessage(g.wnd, &Msg{
113 | Method: "clear",
114 | })
115 | }
116 |
117 | // Jump send request jumping to gopher. If the message can't be arrived to
118 | // gopher, or message queue is full of capacity, return error.
119 | func (g *Gopher) Jump() error {
120 | return sendMessage(g.wnd, &Msg{
121 | Method: "jump",
122 | })
123 | }
124 |
125 | func (g *Gopher) Name() string {
126 | var buf [1024]uint16
127 | if winapi.GetWindowText(g.wnd, &buf[0], 1024) > 0 {
128 | return syscall.UTF16ToString(buf[:])
129 | }
130 | return ""
131 | }
132 |
133 | func (g *Gopher) Hwnd() winapi.HWND {
134 | return g.wnd
135 | }
136 |
--------------------------------------------------------------------------------
/misc/screenshot/9Xf0BTM.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattn/gopher/d28e0cd2e3b2fa5326645c51f8e0b53273137123/misc/screenshot/9Xf0BTM.gif
--------------------------------------------------------------------------------
/misc/screenshot/BgiIAj9.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattn/gopher/d28e0cd2e3b2fa5326645c51f8e0b53273137123/misc/screenshot/BgiIAj9.gif
--------------------------------------------------------------------------------
/misc/screenshot/DTIBM9W.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattn/gopher/d28e0cd2e3b2fa5326645c51f8e0b53273137123/misc/screenshot/DTIBM9W.gif
--------------------------------------------------------------------------------
/misc/screenshot/K9h25F5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattn/gopher/d28e0cd2e3b2fa5326645c51f8e0b53273137123/misc/screenshot/K9h25F5.png
--------------------------------------------------------------------------------
/misc/screenshot/OKqbF7n.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattn/gopher/d28e0cd2e3b2fa5326645c51f8e0b53273137123/misc/screenshot/OKqbF7n.gif
--------------------------------------------------------------------------------
/misc/screenshot/PMVBSJ2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattn/gopher/d28e0cd2e3b2fa5326645c51f8e0b53273137123/misc/screenshot/PMVBSJ2.png
--------------------------------------------------------------------------------
/misc/screenshot/UEdmHYI.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattn/gopher/d28e0cd2e3b2fa5326645c51f8e0b53273137123/misc/screenshot/UEdmHYI.png
--------------------------------------------------------------------------------
/misc/screenshot/xt550tv.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattn/gopher/d28e0cd2e3b2fa5326645c51f8e0b53273137123/misc/screenshot/xt550tv.gif
--------------------------------------------------------------------------------
/misc/vim/autoload/gopher.vim:
--------------------------------------------------------------------------------
1 | function! gopher#hey_gopher(...)
2 | let ret = system('gopherc -m ' . shellescape(join(a:000, ' ')))
3 | if v:shell_error != 0
4 | echohl Error | echom substitute(ret, '[\r\n]', '', 'g') | echohl None
5 | endif
6 | endfunction
7 |
8 | function! gopher#good_bye_gophers(...)
9 | let ret = system('gopherc -x ')
10 | if v:shell_error != 0
11 | echohl Error | echom substitute(ret, '[\r\n]', '', 'g') | echohl None
12 | endif
13 | endfunction
14 |
15 |
16 |
--------------------------------------------------------------------------------
/misc/vim/plugin/heygopher.vim:
--------------------------------------------------------------------------------
1 | if !executable('gopherc')
2 | finish
3 | endif
4 |
5 | command! -nargs=+ HeyGopher call gopher#hey_gopher()
6 | command! -nargs=0 GoodByeGophers call gopher#good_bye_gophers()
7 |
--------------------------------------------------------------------------------