Codebase list golang-github-inconshreveable-mousetrap / 6d77fce
Imported Upstream version 0.0~git20141017.0.76626ae Anthony Fok 8 years ago
5 changed file(s) with 195 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0 Copyright 2014 Alan Shreve
1
2 Licensed under the Apache License, Version 2.0 (the "License");
3 you may not use this file except in compliance with the License.
4 You may obtain a copy of the License at
5
6 http://www.apache.org/licenses/LICENSE-2.0
7
8 Unless required by applicable law or agreed to in writing, software
9 distributed under the License is distributed on an "AS IS" BASIS,
10 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 See the License for the specific language governing permissions and
12 limitations under the License.
0 # mousetrap
1
2 mousetrap is a tiny library that answers a single question.
3
4 On a Windows machine, was the process invoked by someone double clicking on
5 the executable file while browsing in explorer?
6
7 ### Motivation
8
9 Windows developers unfamiliar with command line tools will often "double-click"
10 the executable for a tool. Because most CLI tools print the help and then exit
11 when invoked without arguments, this is often very frustrating for those users.
12
13 mousetrap provides a way to detect these invocations so that you can provide
14 more helpful behavior and instructions on how to run the CLI tool. To see what
15 this looks like, both from an organizational and a technical perspective, see
16 https://inconshreveable.com/09-09-2014/sweat-the-small-stuff/
17
18 ### The interface
19
20 The library exposes a single interface:
21
22 func StartedByExplorer() (bool)
0 // +build !windows
1
2 package mousetrap
3
4 // StartedByExplorer returns true if the program was invoked by the user
5 // double-clicking on the executable from explorer.exe
6 //
7 // It is conservative and returns false if any of the internal calls fail.
8 // It does not guarantee that the program was run from a terminal. It only can tell you
9 // whether it was launched from explorer.exe
10 //
11 // On non-Windows platforms, it always returns false.
12 func StartedByExplorer() bool {
13 return false
14 }
0 // +build windows
1 // +build !go1.4
2
3 package mousetrap
4
5 import (
6 "fmt"
7 "os"
8 "syscall"
9 "unsafe"
10 )
11
12 const (
13 // defined by the Win32 API
14 th32cs_snapprocess uintptr = 0x2
15 )
16
17 var (
18 kernel = syscall.MustLoadDLL("kernel32.dll")
19 CreateToolhelp32Snapshot = kernel.MustFindProc("CreateToolhelp32Snapshot")
20 Process32First = kernel.MustFindProc("Process32FirstW")
21 Process32Next = kernel.MustFindProc("Process32NextW")
22 )
23
24 // ProcessEntry32 structure defined by the Win32 API
25 type processEntry32 struct {
26 dwSize uint32
27 cntUsage uint32
28 th32ProcessID uint32
29 th32DefaultHeapID int
30 th32ModuleID uint32
31 cntThreads uint32
32 th32ParentProcessID uint32
33 pcPriClassBase int32
34 dwFlags uint32
35 szExeFile [syscall.MAX_PATH]uint16
36 }
37
38 func getProcessEntry(pid int) (pe *processEntry32, err error) {
39 snapshot, _, e1 := CreateToolhelp32Snapshot.Call(th32cs_snapprocess, uintptr(0))
40 if snapshot == uintptr(syscall.InvalidHandle) {
41 err = fmt.Errorf("CreateToolhelp32Snapshot: %v", e1)
42 return
43 }
44 defer syscall.CloseHandle(syscall.Handle(snapshot))
45
46 var processEntry processEntry32
47 processEntry.dwSize = uint32(unsafe.Sizeof(processEntry))
48 ok, _, e1 := Process32First.Call(snapshot, uintptr(unsafe.Pointer(&processEntry)))
49 if ok == 0 {
50 err = fmt.Errorf("Process32First: %v", e1)
51 return
52 }
53
54 for {
55 if processEntry.th32ProcessID == uint32(pid) {
56 pe = &processEntry
57 return
58 }
59
60 ok, _, e1 = Process32Next.Call(snapshot, uintptr(unsafe.Pointer(&processEntry)))
61 if ok == 0 {
62 err = fmt.Errorf("Process32Next: %v", e1)
63 return
64 }
65 }
66 }
67
68 func getppid() (pid int, err error) {
69 pe, err := getProcessEntry(os.Getpid())
70 if err != nil {
71 return
72 }
73
74 pid = int(pe.th32ParentProcessID)
75 return
76 }
77
78 // StartedByExplorer returns true if the program was invoked by the user double-clicking
79 // on the executable from explorer.exe
80 //
81 // It is conservative and returns false if any of the internal calls fail.
82 // It does not guarantee that the program was run from a terminal. It only can tell you
83 // whether it was launched from explorer.exe
84 func StartedByExplorer() bool {
85 ppid, err := getppid()
86 if err != nil {
87 return false
88 }
89
90 pe, err := getProcessEntry(ppid)
91 if err != nil {
92 return false
93 }
94
95 name := syscall.UTF16ToString(pe.szExeFile[:])
96 return name == "explorer.exe"
97 }
0 // +build windows
1 // +build go1.4
2
3 package mousetrap
4
5 import (
6 "os"
7 "syscall"
8 "unsafe"
9 )
10
11 func getProcessEntry(pid int) (*syscall.ProcessEntry32, error) {
12 snapshot, err := syscall.CreateToolhelp32Snapshot(syscall.TH32CS_SNAPPROCESS, 0)
13 if err != nil {
14 return nil, err
15 }
16 defer syscall.CloseHandle(snapshot)
17 var procEntry syscall.ProcessEntry32
18 procEntry.Size = uint32(unsafe.Sizeof(procEntry))
19 if err = syscall.Process32First(snapshot, &procEntry); err != nil {
20 return nil, err
21 }
22 for {
23 if procEntry.ProcessID == uint32(pid) {
24 return &procEntry, nil
25 }
26 err = syscall.Process32Next(snapshot, &procEntry)
27 if err != nil {
28 return nil, err
29 }
30 }
31 }
32
33 // StartedByExplorer returns true if the program was invoked by the user double-clicking
34 // on the executable from explorer.exe
35 //
36 // It is conservative and returns false if any of the internal calls fail.
37 // It does not guarantee that the program was run from a terminal. It only can tell you
38 // whether it was launched from explorer.exe
39 func StartedByExplorer() bool {
40 pe, err := getProcessEntry(os.Getppid())
41 if err != nil {
42 return false
43 }
44 return "explorer.exe" == syscall.UTF16ToString(pe.ExeFile[:])
45 }