Skip to content

Commit 110d23e

Browse files
committed
ps2: Added mouse and keyboard stacked decoders
Mouse decoder interprets the 3-byte packets for movement and clicking, but ignores the configuration commands from the host. Keyboard decoder interprets press and release messages for each key but does not keep track of state (capslock, shift, etc).
1 parent 7d8409c commit 110d23e

5 files changed

Lines changed: 363 additions & 0 deletions

File tree

decoders/ps2_keyboard/__init__.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
##
2+
## This file is part of the libsigrokdecode project.
3+
##
4+
## Copyright (C) 2023 Marshal Horn <kamocat@gmail.com>
5+
##
6+
## This program is free software; you can redistribute it and/or modify
7+
## it under the terms of the GNU General Public License as published by
8+
## the Free Software Foundation; either version 2 of the License, or
9+
## (at your option) any later version.
10+
##
11+
## This program is distributed in the hope that it will be useful,
12+
## but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
## GNU General Public License for more details.
15+
##
16+
## You should have received a copy of the GNU General Public License
17+
## along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
##
19+
20+
'''
21+
This protocol decoder can decode PS/2 keyboard commands.
22+
It should be stacked on the PS/2 packet decoder.
23+
'''
24+
25+
from .pd import Decoder

decoders/ps2_keyboard/pd.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
##
2+
## This file is part of the libsigrokdecode project.
3+
##
4+
## Copyright (C) 2023 Marshal Horn <kamocat@gmail.com>
5+
##
6+
## This program is free software; you can redistribute it and/or modify
7+
## it under the terms of the GNU General Public License as published by
8+
## the Free Software Foundation; either version 2 of the License, or
9+
## (at your option) any later version.
10+
##
11+
## This program is distributed in the hope that it will be useful,
12+
## but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
## GNU General Public License for more details.
15+
##
16+
## You should have received a copy of the GNU General Public License
17+
## along with this program; if not, see <http://www.gnu.org/licenses/>.
18+
##
19+
20+
import sigrokdecode as srd
21+
from .sc import key_decode
22+
23+
class Ps2Packet:
24+
def __init__(self, val, host=False, pok=False, ack=False):
25+
self.val = val #byte value
26+
self.host = host #Host transmissions
27+
self.pok = pok #Parity ok
28+
self.ack = ack #Acknowlege ok for host transmission.
29+
30+
class Decoder(srd.Decoder):
31+
api_version = 3
32+
id = 'keyboard'
33+
name = 'PS/2 Keyboard'
34+
longname = 'PS/2 Keyboard'
35+
desc = 'PS/2 keyboard interface.'
36+
license = 'gplv2+'
37+
inputs = ['ps2_packet']
38+
outputs = []
39+
tags = ['PC']
40+
binary = (
41+
('Keys', 'Key presses'),
42+
)
43+
annotations = (
44+
('Press', 'Key pressed'),
45+
('Release', 'Key released'),
46+
('Ack', 'Acknowledge'),
47+
)
48+
annotation_rows = (
49+
('keys', 'key presses and releases',(0,1,2)),
50+
)
51+
52+
def __init__(self):
53+
self.reset()
54+
55+
def reset(self):
56+
self.sw = 0 #for switch statement
57+
self.ann = 0 #defualt to Make code (keypress)
58+
self.extended = False
59+
60+
def start(self):
61+
self.out_binary = self.register(srd.OUTPUT_BINARY)
62+
self.out_ann = self.register(srd.OUTPUT_ANN)
63+
64+
def decode(self,startsample,endsample,data):
65+
if data.host:
66+
# Ignore host commands or interrupted keycodes
67+
self.reset()
68+
return
69+
if self.sw < 1:
70+
self.ss = startsample
71+
self.sw = 1
72+
if self.sw < 2:
73+
if data.val == 0xF0: #Break code
74+
self.ann = 1
75+
return
76+
elif data.val == 0xE0: #Extended character
77+
self.extended = True
78+
return
79+
elif data.val == 0xFA: #Acknowledge code
80+
c = ['Acknowledge','ACK']
81+
self.ann = 2
82+
self.sw = 4
83+
if self.sw < 3:
84+
c = key_decode(data.val, self.extended)
85+
86+
self.put(self.ss,endsample,self.out_ann,[self.ann,c])
87+
if self.ann == 0: #Make code
88+
self.put(self.ss,endsample,self.out_binary,[0,c[-1].encode('UTF-8')])
89+
self.reset()
90+

decoders/ps2_keyboard/sc.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
#A list of scancodes for the PS2 keyboard
2+
ext = {
3+
0x1F:'L Sup',
4+
0x14:'R Ctrl',
5+
0x27:'R Sup',
6+
0x11:'R Alt',
7+
0x2F:'Menu',
8+
0x12:'PrtScr',
9+
0x7C:'SysRq',
10+
0x70:'Insert',
11+
0x6C:'Home',
12+
0x7D:'Pg Up',
13+
0x71:'Delete',
14+
0x69:'End',
15+
0x7A:'Pg Dn',
16+
0x75:['Up arrow','^'],
17+
0x6B:['Left arrow','Left','<-'],
18+
0x74:['Right arrow','Right','->'],
19+
0x72:['Down arrow','Down','v'],
20+
0x4A:['KP /','/'],
21+
0x5A:['KP Ent','\n'],
22+
}
23+
24+
std = {
25+
0x1C:'A',
26+
0x32:'B',
27+
0x21:'C',
28+
0x23:'D',
29+
0x24:'E',
30+
0x2B:'F',
31+
0x34:'G',
32+
0x33:'H',
33+
0x43:'I',
34+
0x3B:'J',
35+
0x42:'K',
36+
0x4B:'L',
37+
0x3A:'M',
38+
0x31:'N',
39+
0x44:'O',
40+
0x4D:'P',
41+
0x15:'Q',
42+
0x2D:'R',
43+
0x1B:'S',
44+
0x2C:'T',
45+
0x3C:'U',
46+
0x2A:'V',
47+
0x1D:'W',
48+
0x22:'X',
49+
0x35:'Y',
50+
0x1A:'Z',
51+
0x45:'0)',
52+
0x16:'1!',
53+
0x1E:'2@',
54+
0x26:'3#',
55+
0x25:'4$',
56+
0x2E:'5%',
57+
0x36:'6^',
58+
0x3D:'7&',
59+
0x3E:'8*',
60+
0x46:'9(',
61+
0x0E:'`~',
62+
0x4E:'-_',
63+
0x55:'=+',
64+
0x5D:'\|',
65+
0x66:'Backsp',
66+
0x29:['Space',' '],
67+
0x0D:['Tab','\t'],
68+
0x58:'CapsLk',
69+
0x12:'L Shft',
70+
0x14:'L Ctrl',
71+
0x11:'L Alt',
72+
0x59:'R Shft',
73+
0x5A:['Enter','\n'],
74+
0x76:'Esc',
75+
0x5:'F1',
76+
0x6:'F2',
77+
0x4:'F3',
78+
0x0C:'F4',
79+
0x3:'F5',
80+
0x0B:'F6',
81+
0x83:'F7',
82+
0x0A:'F8',
83+
0x1:'F9',
84+
0x9:'F10',
85+
0x78:'F11',
86+
0x7:'F12',
87+
0x7E:'ScrLck',
88+
}
89+
90+
91+
def key_decode(code, extended):
92+
try:
93+
c = ext[code] if extended else std[code]
94+
except KeyError:
95+
fs = '[E0%0X]' if extended else '[%0X]'
96+
c = fs % code
97+
if not isinstance(0,list):
98+
c = [c]
99+
return c

decoders/ps2_mouse/__init__.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
##
2+
## This file is part of the libsigrokdecode project.
3+
##
4+
## Copyright (C) 2023 Marshal Horn <kamocat@gmail.com>
5+
##
6+
## This program is free software; you can redistribute it and/or modify
7+
## it under the terms of the GNU General Public License as published by
8+
## the Free Software Foundation; either version 2 of the License, or
9+
## (at your option) any later version.
10+
##
11+
## This program is distributed in the hope that it will be useful,
12+
## but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
## GNU General Public License for more details.
15+
##
16+
## You should have received a copy of the GNU General Public License
17+
## along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
##
19+
20+
'''
21+
This protocol decoder can decode PS/2 mouse commands.
22+
It should be stacked on the PS/2 packet decoder.
23+
'''
24+
25+
from .pd import Decoder

decoders/ps2_mouse/pd.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
##
2+
## This file is part of the libsigrokdecode project.
3+
##
4+
## Copyright (C) 2023 Marshal Horn <kamocat@gmail.com>
5+
##
6+
## This program is free software; you can redistribute it and/or modify
7+
## it under the terms of the GNU General Public License as published by
8+
## the Free Software Foundation; either version 2 of the License, or
9+
## (at your option) any later version.
10+
##
11+
## This program is distributed in the hope that it will be useful,
12+
## but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
## GNU General Public License for more details.
15+
##
16+
## You should have received a copy of the GNU General Public License
17+
## along with this program; if not, see <http://www.gnu.org/licenses/>.
18+
##
19+
20+
import sigrokdecode as srd
21+
22+
class Ps2Packet:
23+
def __init__(self, val, host=False, pok=False, ack=False):
24+
self.val = val #byte value
25+
self.host = host #Host transmissions
26+
self.pok = pok #Parity ok
27+
self.ack = ack #Acknowlege ok for host transmission.
28+
29+
class Decoder(srd.Decoder):
30+
api_version = 3
31+
id = 'mouse'
32+
name = 'PS/2 Mouse'
33+
longname = 'PS/2 Mouse'
34+
desc = 'PS/2 mouse interface.'
35+
license = 'gplv2+'
36+
inputs = ['ps2_packet']
37+
outputs = []
38+
tags = ['PC']
39+
binary = (
40+
('bytes', 'Bytes without explanation'),
41+
('movement', 'Explanation of mouse movement and clicks'),
42+
)
43+
annotations = (
44+
('Movement', 'Mouse movement packets'),
45+
)
46+
annotation_rows = (
47+
('mov', 'Mouse Movement',(0,)),
48+
)
49+
50+
def __init__(self):
51+
self.reset()
52+
53+
def reset(self):
54+
self.packets = []
55+
self.es = 0
56+
self.ss = 0
57+
58+
def start(self):
59+
self.out_binary = self.register(srd.OUTPUT_BINARY)
60+
self.out_ann = self.register(srd.OUTPUT_ANN)
61+
62+
def metadata(self,key,value):
63+
if key == srd.SRD_CONF_SAMPLERATE:
64+
self.samplerate = value
65+
66+
def mouse_movement(self):
67+
if len(self.packets) >= 3:
68+
if not self.packets[0].host:
69+
msg = ''
70+
[flags,x,y] = [p.val for p in self.packets[:3]]
71+
if flags & 1:
72+
msg += 'L'
73+
if flags & 2:
74+
msg += 'M'
75+
if flags & 4:
76+
msg += 'R'
77+
if flags & 0x10:
78+
x = x-256
79+
if flags & 0x20:
80+
y = y-256
81+
if x != 0:
82+
msg += ' X%+d' % x
83+
if flags & 0x40:
84+
msg += '!!'
85+
if y != 0:
86+
msg += ' Y%+d' % y
87+
if flags & 0x80:
88+
msg += '!!'
89+
if msg == '':
90+
msg = 'No Movement'
91+
ustring = ('\n' + msg).encode('UTF-8')
92+
self.put(self.ss,self.es,self.out_binary, [1,ustring] )
93+
self.put(self.ss,self.es,self.out_ann, [0,[msg]] )
94+
95+
96+
def print_packets(self):
97+
self.mouse_movement()
98+
tag = "Host: " if self.packets[-1].host else "Mouse:"
99+
octets = ' '.join(["%02X" % x.val for x in self.packets])
100+
unicode_string = ("\n"+tag+" "+octets).encode('UTF-8')
101+
self.put(self.ss,self.es,self.out_binary, [0,unicode_string] )
102+
self.reset()
103+
104+
def mouse_ack(self,ss,es):
105+
self.put(ss,es,self.out_binary, [0,b' ACK'] )
106+
107+
def decode(self,startsample,endsample,data):
108+
if len(self.packets) == 0:
109+
self.ss = startsample
110+
elif data.host != self.packets[-1].host:
111+
self.print_packets()
112+
self.ss = startsample #Packets were cleared, need to set startsample again
113+
if data.val == 0xFA and not data.host:
114+
#Special case: acknowledge byte from mouse
115+
self.mouse_ack(startsample,endsample)
116+
self.reset()
117+
return
118+
119+
self.packets.append(data)
120+
self.es = endsample
121+
#Mouse streaming packets are in 3s
122+
#Timing is not guaranteed because host can hold the clock at any point
123+
if len(self.packets)>2:
124+
self.print_packets()

0 commit comments

Comments
 (0)