PageRenderTime 1674ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/src/main.go

https://gitlab.com/AlekseyLobanov/gokeystat
Go | 305 lines | 242 code | 45 blank | 18 comment | 68 complexity | 68d124c1bb7f4d5a102bbc0dc78a3d3a MD5 | raw file
  1. // main.go
  2. package main
  3. import (
  4. "database/sql"
  5. "flag"
  6. "log"
  7. "os/exec"
  8. "regexp"
  9. "sort"
  10. "strconv"
  11. "strings"
  12. "time"
  13. _ "github.com/mattn/go-sqlite3"
  14. )
  15. const (
  16. SLEEP_TIME = 3 * time.Second // time between processing xinput output
  17. KEYBOARD_BUFER_SIZE = 10000
  18. DATABASE_NAME = "file:gokeystat.db?cache=shared&mode=rwc"
  19. CAPTURE_TIME = 5 // time in seconds between capturing keyboard to db
  20. )
  21. // StatForTime stotres pressed keys and beginning time
  22. type StatForTime struct {
  23. time int64
  24. keys map[uint8]int
  25. }
  26. func (stat *StatForTime) Init() {
  27. stat.time = time.Now().Unix()
  28. stat.keys = make(map[uint8]int)
  29. }
  30. // GetKeymap returns map from key numbers to key names like "F1", "Tab", "d"
  31. func GetKeymap() map[uint8]string {
  32. return GetKeymapFromOutput(GetKeymapOutput())
  33. }
  34. // GetKeymapOutput returns output of utility that prints system keymap
  35. func GetKeymapOutput() []byte {
  36. cmd := exec.Command("xmodmap", "-pke")
  37. out, err := cmd.Output()
  38. if err != nil {
  39. log.Fatal(err)
  40. }
  41. return out
  42. }
  43. // GetKeymapFromOutput returns map with keymap from text
  44. func GetKeymapFromOutput(buf []byte) map[uint8]string {
  45. const KEY_NUM_STRING_RE = "\\d+[ ]*=[ ]*\\S+"
  46. re := regexp.MustCompile(KEY_NUM_STRING_RE)
  47. resByte := re.FindAll(buf, -1)
  48. keyMap := make(map[uint8]string)
  49. for _, line := range resByte {
  50. lineSpitted := strings.Split(string(line), " ")
  51. if key, err := strconv.Atoi(lineSpitted[0]); err == nil {
  52. keyMap[uint8(key)] = lineSpitted[2]
  53. }
  54. }
  55. return keyMap
  56. }
  57. // GetKeyNumsFromOutput extract pressed keys from bufer buf
  58. // It returns slice with key numbers in the same order
  59. func GetKeyNumsFromOutput(buf []byte) []uint8 {
  60. const KEY_NUM_STRING_RE = "press[ ]+(\\d+)"
  61. re := regexp.MustCompile(KEY_NUM_STRING_RE)
  62. resByte := re.FindAll(buf, -1)
  63. keyNums := make([]uint8, len(resByte))
  64. re = regexp.MustCompile("\\d+")
  65. for i, line := range resByte {
  66. numByte := re.Find(line)
  67. if num, err := strconv.Atoi(string(numByte)); err == nil {
  68. keyNums[i] = uint8(num)
  69. } else {
  70. log.Fatal(err)
  71. }
  72. }
  73. return keyNums
  74. }
  75. func GetKeyNumsFromKeyMap(keyMap map[uint8]string) []int {
  76. res := make([]int, 0, len(keyMap))
  77. for keyNum := range keyMap {
  78. res = append(res, int(keyNum))
  79. }
  80. sort.Ints(res)
  81. return res
  82. }
  83. // InitDb creates tables, inserts keymap to db
  84. func InitDb(db *sql.DB, keyMap map[uint8]string) {
  85. keyNums := GetKeyNumsFromKeyMap(keyMap)
  86. sqlInit := `CREATE TABLE IF NOT EXISTS keylog (
  87. time INTEGER primary key`
  88. for keyNum := range keyNums {
  89. sqlInit += ",\n" + "KEY" + strconv.Itoa(keyNum) + " INTEGER"
  90. }
  91. sqlInit += "\n);"
  92. // Inserting keymap to table
  93. sqlInit += ` CREATE TABLE IF NOT EXISTS keymap (
  94. num INTEGER primary key,
  95. value STRING
  96. );`
  97. _, err := db.Exec(sqlInit)
  98. if err != nil {
  99. log.Fatalf("%q: %s\n", err, sqlInit)
  100. }
  101. rows, err := db.Query("SELECT COUNT(*) FROM keymap")
  102. if err != nil {
  103. log.Fatal(err)
  104. }
  105. var rowsCount int
  106. rows.Next()
  107. rows.Scan(&rowsCount)
  108. if rowsCount > 0 {
  109. // already inserted keymap
  110. return
  111. }
  112. rows.Close()
  113. tx, err := db.Begin()
  114. if err != nil {
  115. log.Fatal(err)
  116. }
  117. stmt, err := tx.Prepare("INSERT INTO keymap(num, value) VALUES(?, ?)")
  118. if err != nil {
  119. log.Fatal(err)
  120. }
  121. defer stmt.Close()
  122. for keyNum, keyName := range keyMap {
  123. _, err = stmt.Exec(keyNum, keyName)
  124. if err != nil {
  125. log.Fatal(err)
  126. }
  127. }
  128. tx.Commit()
  129. }
  130. func AddStatTimeToDb(db *sql.DB, statTime StatForTime, keyMap map[uint8]string) {
  131. keyNums := GetKeyNumsFromKeyMap(keyMap)
  132. sqlStmt := "insert into keylog(time"
  133. for keyNum := range keyNums {
  134. sqlStmt += ",\n" + "KEY" + strconv.Itoa(keyNum)
  135. }
  136. sqlStmt += ") values "
  137. sqlStmt += "(" + strconv.FormatInt(statTime.time, 10)
  138. for keyNum := range keyNums {
  139. keyNumber, _ := statTime.keys[uint8(keyNum)]
  140. sqlStmt += ",\n" + strconv.Itoa(keyNumber)
  141. }
  142. sqlStmt += ")"
  143. tx, err := db.Begin()
  144. if err != nil {
  145. log.Fatal(err)
  146. }
  147. _, err = tx.Exec(sqlStmt)
  148. if err != nil {
  149. log.Printf("%q: %s\n", err, sqlStmt)
  150. }
  151. tx.Commit()
  152. }
  153. // GetStatTimesFromDb returns slice with StatForTime objects that
  154. func GetStatTimesFromDb(db *sql.DB, fromTime int64, keyMap map[uint8]string) []StatForTime {
  155. sqlStmt := "select * from keylog where time > " + strconv.FormatInt(fromTime, 10)
  156. rows, err := db.Query(sqlStmt)
  157. if err != nil {
  158. log.Fatalln("Error with query", sqlStmt, " is ", err)
  159. }
  160. defer rows.Close()
  161. cols, err := rows.Columns()
  162. if err != nil {
  163. log.Fatalln("Failed to get columns", err)
  164. }
  165. rawResult := make([][]byte, len(cols))
  166. result := make([]int64, len(cols))
  167. dest := make([]interface{}, len(cols)) // A temporary interface{} slice
  168. for i := range rawResult {
  169. dest[i] = &rawResult[i] // Put pointers to each string in the interface slice
  170. }
  171. // keyNums[i] stores i-th keynum
  172. keyNums := GetKeyNumsFromKeyMap(keyMap)
  173. // result
  174. res := make([]StatForTime, 0)
  175. for rows.Next() {
  176. err = rows.Scan(dest...)
  177. if err != nil {
  178. log.Fatalln("Failed to scan row", err)
  179. }
  180. for i, raw := range rawResult {
  181. if raw == nil {
  182. result[i] = 0
  183. } else {
  184. // Only numbers in db: converting it to int64
  185. result[i], err = strconv.ParseInt(string(raw), 10, 64)
  186. if err != nil {
  187. log.Fatalln("Error when parsing ", raw, " from db:", err)
  188. }
  189. }
  190. }
  191. var resStatTime StatForTime
  192. resStatTime.time = result[0]
  193. resStatTime.keys = make(map[uint8]int)
  194. for index, val := range result[1:] {
  195. if val == 0 {
  196. continue
  197. }
  198. resStatTime.keys[uint8(keyNums[index])] = int(val)
  199. }
  200. res = append(res, resStatTime)
  201. }
  202. if err = rows.Err(); err != nil {
  203. log.Fatalln("Error when iterating over rows", err)
  204. }
  205. return res
  206. }
  207. func main() {
  208. keyboardID := flag.Int("id", -1, "Your keyboard id")
  209. outputPath := flag.String("o", "", "Path to export file")
  210. flag.Parse()
  211. log.Println("keyboardID =", *keyboardID, "outputPath =", *outputPath)
  212. // Opening database
  213. db, err := sql.Open("sqlite3", DATABASE_NAME)
  214. if err != nil {
  215. log.Fatal(err)
  216. }
  217. db.SetMaxIdleConns(5)
  218. db.SetMaxOpenConns(5)
  219. defer db.Close()
  220. keyMap := GetKeymap()
  221. InitDb(db, keyMap)
  222. switch {
  223. case *keyboardID == -1 && *outputPath == "":
  224. flag.PrintDefaults()
  225. return
  226. case *keyboardID != -1:
  227. cmd := exec.Command("xinput", "test", strconv.Itoa(*keyboardID))
  228. stdout, err := cmd.StdoutPipe()
  229. if err != nil {
  230. log.Fatal(err)
  231. }
  232. if err := cmd.Start(); err != nil {
  233. log.Fatal(err)
  234. }
  235. // output of xinput command
  236. buf := make([]byte, KEYBOARD_BUFER_SIZE)
  237. var curStat StatForTime
  238. curStat.Init()
  239. for {
  240. n, err := stdout.Read(buf)
  241. if err != nil {
  242. log.Fatal(err)
  243. }
  244. // processing buf here
  245. for _, keyNum := range GetKeyNumsFromOutput(buf[:n]) {
  246. oldKeyCount, _ := curStat.keys[keyNum]
  247. curStat.keys[keyNum] = oldKeyCount + 1
  248. }
  249. // Every CAPTURE_TIME seconds save to BD
  250. if time.Now().Unix()-curStat.time > CAPTURE_TIME {
  251. AddStatTimeToDb(db, curStat, keyMap)
  252. curStat.Init()
  253. }
  254. time.Sleep(SLEEP_TIME)
  255. }
  256. case *outputPath != "":
  257. exportingData := GetStatTimesFromDb(db, 0, keyMap) //exporting here
  258. SaveToCsvFile(exportingData, keyMap, *outputPath, false)
  259. }
  260. }