vigilem-evdev 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.txt +22 -0
- data/ext/Rakefile +31 -0
- data/ext/rake_helper.rb +10 -0
- data/lib/vigilem/_evdev.rb +24 -0
- data/lib/vigilem/evdev.rb +14 -0
- data/lib/vigilem/evdev/at_exit.rb +8 -0
- data/lib/vigilem/evdev/context_filter.rb +92 -0
- data/lib/vigilem/evdev/demultiplexer.rb +26 -0
- data/lib/vigilem/evdev/device.rb +182 -0
- data/lib/vigilem/evdev/device_capabilities.rb +53 -0
- data/lib/vigilem/evdev/dom.rb +8 -0
- data/lib/vigilem/evdev/dom/adapter.rb +87 -0
- data/lib/vigilem/evdev/dom/code_values_tables.rb +172 -0
- data/lib/vigilem/evdev/dom/input_event_converter.rb +329 -0
- data/lib/vigilem/evdev/dom/input_event_utils.rb +48 -0
- data/lib/vigilem/evdev/dom/key_values_tables.rb +248 -0
- data/lib/vigilem/evdev/dom/kp_table.rb +52 -0
- data/lib/vigilem/evdev/focus_context_filter.rb +124 -0
- data/lib/vigilem/evdev/input_system_handler.rb +69 -0
- data/lib/vigilem/evdev/key_map_cache.rb +64 -0
- data/lib/vigilem/evdev/multiplexer.rb +73 -0
- data/lib/vigilem/evdev/system.rb +17 -0
- data/lib/vigilem/evdev/system/input.rb +1053 -0
- data/lib/vigilem/evdev/system/input/event.rb +4 -0
- data/lib/vigilem/evdev/system/input/input_event.rb +33 -0
- data/lib/vigilem/evdev/system/int.rb +9 -0
- data/lib/vigilem/evdev/system/ioctl.rb +143 -0
- data/lib/vigilem/evdev/system/keymap_loaders.rb +89 -0
- data/lib/vigilem/evdev/system/keymap_loaders/dumpkeys_loader.rb +98 -0
- data/lib/vigilem/evdev/system/keymap_loaders/kmap_loader.rb +74 -0
- data/lib/vigilem/evdev/system/posix_types.rb +5 -0
- data/lib/vigilem/evdev/system/time.rb +21 -0
- data/lib/vigilem/evdev/transfer_agent.rb +40 -0
- data/lib/vigilem/evdev/version.rb +5 -0
- data/lib/vigilem/evdev/vty_context_filter.rb +216 -0
- data/spec/after_each_example_group.rb +29 -0
- data/spec/delete_test_cache_after_group.rb +26 -0
- data/spec/spec_helper.rb +30 -0
- data/spec/vigilem/_evdev_spec.rb +11 -0
- data/spec/vigilem/evdev/context_filter_spec.rb +114 -0
- data/spec/vigilem/evdev/demultiplexer_spec.rb +44 -0
- data/spec/vigilem/evdev/device_capabilities_spec.rb +0 -0
- data/spec/vigilem/evdev/device_spec.rb +97 -0
- data/spec/vigilem/evdev/dom/adapter_spec.rb +5 -0
- data/spec/vigilem/evdev/dom/input_event_converter_spec.rb +253 -0
- data/spec/vigilem/evdev/dom/input_event_utils_spec.rb +23 -0
- data/spec/vigilem/evdev/focus_context_filter_spec.rb +112 -0
- data/spec/vigilem/evdev/input_system_handler_spec.rb +146 -0
- data/spec/vigilem/evdev/key_map_cache_spec.rb +65 -0
- data/spec/vigilem/evdev/multiplexer_spec.rb +55 -0
- data/spec/vigilem/evdev/system/input/input_event_spec.rb +33 -0
- data/spec/vigilem/evdev/system/input_spec.rb +274 -0
- data/spec/vigilem/evdev/system/int_spec.rb +30 -0
- data/spec/vigilem/evdev/system/ioctl_spec.rb +206 -0
- data/spec/vigilem/evdev/system/keymap_loaders/dumpkeys_loader_spec.rb +5 -0
- data/spec/vigilem/evdev/system/keymap_loaders/kmap_loader_spec.rb +24 -0
- data/spec/vigilem/evdev/system/keymap_loaders_spec.rb +22 -0
- data/spec/vigilem/evdev/system/posix_types_spec.rb +15 -0
- data/spec/vigilem/evdev/system/time_spec.rb +18 -0
- data/spec/vigilem/evdev/transfer_agent_spec.rb +57 -0
- data/spec/vigilem/evdev/vty_context_filter_spec.rb +282 -0
- metadata +286 -0
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'vigilem/support'
|
2
|
+
|
3
|
+
require 'vigilem/support/metadata'
|
4
|
+
|
5
|
+
require 'vigilem/evdev/system/input'
|
6
|
+
|
7
|
+
require 'vigilem/evdev/system/time'
|
8
|
+
|
9
|
+
module Vigilem
|
10
|
+
module Evdev
|
11
|
+
module System
|
12
|
+
module Input
|
13
|
+
#
|
14
|
+
# represents struct input_event from input.h
|
15
|
+
class InputEvent < ::VFFIStruct
|
16
|
+
layout_with_methods :time, Time::Timeval,
|
17
|
+
:type, :__u16,
|
18
|
+
:code, :__u16,
|
19
|
+
:value, :__s32
|
20
|
+
|
21
|
+
include Support::Metadata
|
22
|
+
|
23
|
+
# @fixme kludge
|
24
|
+
attr_accessor :leds
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
require 'vigilem/evdev/system/input/event'
|
@@ -0,0 +1,143 @@
|
|
1
|
+
require 'vigilem/evdev/system'
|
2
|
+
|
3
|
+
module Vigilem
|
4
|
+
module Evdev
|
5
|
+
module System
|
6
|
+
# @see http://lxr.free-electrons.com/source/include/uapi/asm-generic/ioctl.h
|
7
|
+
# @todo consider the possibility of switching to RubyInline?
|
8
|
+
# - RubyInline (and ZenTest) gave significant problems
|
9
|
+
# installing "invalid gem: package metadata is missing"
|
10
|
+
# updating to the newest rubygems and clearing the gem from the cache worked
|
11
|
+
# A C/C++ compiler (the same one that compiled your ruby interpreter)
|
12
|
+
# irb keeps throwing
|
13
|
+
# Errno::ENOENT: No such file or directory - /...home dir.../(irb)
|
14
|
+
# from /.../.rvm/gems/ruby-1.9.3-p545/gems/RubyInline-3.12.3/lib/inline.rb:532:in `mtime'
|
15
|
+
# - move this note to it's own file
|
16
|
+
module IOCTL
|
17
|
+
|
18
|
+
include Support::Sys
|
19
|
+
|
20
|
+
# versions which are compatible with
|
21
|
+
# this file
|
22
|
+
# @return [Array<Integer>]
|
23
|
+
def self.kernel_versions
|
24
|
+
%w(3.7 3.8 3.9 3.10 3.11 3.12 3.13 3.14 3.15 3.16)
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [Integer] 8
|
28
|
+
def _IOC_NRBITS; 8; end
|
29
|
+
# @return [Integer] 8
|
30
|
+
def _IOC_TYPEBITS; 8; end
|
31
|
+
# @return [Integer] 14
|
32
|
+
def _IOC_SIZEBITS; 14; end
|
33
|
+
# @return [Integer] 2
|
34
|
+
def _IOC_DIRBITS; 2; end
|
35
|
+
# @return [Integer]
|
36
|
+
def _IOC_NRMASK; ((1 << _IOC_NRBITS)-1); end
|
37
|
+
# @return [Integer]
|
38
|
+
def _IOC_TYPEMASK; ((1 << _IOC_TYPEBITS)-1); end
|
39
|
+
# @return [Integer]
|
40
|
+
def _IOC_SIZEMASK; ((1 << _IOC_SIZEBITS)-1); end
|
41
|
+
# @return [Integer]
|
42
|
+
def _IOC_DIRMASK; ((1 << _IOC_DIRBITS)-1); end
|
43
|
+
# @return [Integer]
|
44
|
+
def _IOC_NRSHIFT; 0; end
|
45
|
+
# @return [Integer]
|
46
|
+
def _IOC_TYPESHIFT; (_IOC_NRSHIFT+_IOC_NRBITS); end
|
47
|
+
# @return [Integer]
|
48
|
+
def _IOC_SIZESHIFT; (_IOC_TYPESHIFT+_IOC_TYPEBITS); end
|
49
|
+
# @return [Integer]
|
50
|
+
def _IOC_DIRSHIFT; (_IOC_SIZESHIFT+_IOC_SIZEBITS); end
|
51
|
+
#
|
52
|
+
def IOC_IN; (_IOC_WRITE << _IOC_DIRSHIFT); end
|
53
|
+
#
|
54
|
+
def IOC_OUT; (_IOC_READ << _IOC_DIRSHIFT); end
|
55
|
+
#
|
56
|
+
def IOC_INOUT; ((_IOC_WRITE|_IOC_READ) << _IOC_DIRSHIFT); end
|
57
|
+
#
|
58
|
+
def IOCSIZE_MASK; (_IOC_SIZEMASK << _IOC_SIZESHIFT); end
|
59
|
+
#
|
60
|
+
def IOCSIZE_SHIFT; (_IOC_SIZESHIFT); end
|
61
|
+
|
62
|
+
# @return [Integer]
|
63
|
+
def IOCSIZE_MASK; (_IOC_SIZEMASK << _IOC_SIZESHIFT); end
|
64
|
+
# @return [Integer]
|
65
|
+
def IOCSIZE_SHIFT; (_IOC_SIZESHIFT); end
|
66
|
+
|
67
|
+
# direction bits
|
68
|
+
|
69
|
+
# no data transfer
|
70
|
+
# @return [Integer] 0
|
71
|
+
def _IOC_NONE; 0; end
|
72
|
+
# @return [Integer] 1
|
73
|
+
def _IOC_WRITE; 1; end
|
74
|
+
# @return [Integer] 2
|
75
|
+
def _IOC_READ; 2; end
|
76
|
+
|
77
|
+
#
|
78
|
+
# @param [Integer] dir The direction of data transfer
|
79
|
+
# @option dir [Integer] _IOC_NONE
|
80
|
+
# @option dir [Integer] _IOC_READ
|
81
|
+
# @option dir [Integer] _IOC_WRITE
|
82
|
+
# @option dir [Integer] _IOC_READ|_IOC_WRITE
|
83
|
+
# @!macro type_nr_fmt_return_Integer
|
84
|
+
def _IOC(dir, type, nr, size)
|
85
|
+
(System.native_signed_long(dir << _IOC_DIRSHIFT) | (type.ord << _IOC_TYPESHIFT) |
|
86
|
+
(nr << _IOC_NRSHIFT) | (size << _IOC_SIZESHIFT))
|
87
|
+
end
|
88
|
+
|
89
|
+
# /* used to create numbers */
|
90
|
+
|
91
|
+
# an ioctl with no parameters
|
92
|
+
# @!macro type_nr
|
93
|
+
# @return [Integer]
|
94
|
+
def _IO(type,nr); return _IOC(_IOC_NONE,type.ord,nr,0); end
|
95
|
+
# for reading data (copy_to_user)
|
96
|
+
# @!macro type_nr_fmt_return_Integer
|
97
|
+
def _IOR(type,nr,fmt_or_size); return _IOC(_IOC_READ,type.ord,nr,_size_of(fmt_or_size)); end
|
98
|
+
# for writing data (copy_from_user)
|
99
|
+
# @!macro type_nr_fmt_return_Integer
|
100
|
+
def _IOW(type,nr,fmt_or_size); return _IOC(_IOC_WRITE,(type.ord),(nr),_size_of(fmt_or_size)); end
|
101
|
+
# for bidirectional transfers
|
102
|
+
# @!macro type_nr_fmt_return_Integer
|
103
|
+
def _IOWR(type,nr,fmt_or_size); return _IOC(_IOC_READ|_IOC_WRITE,(type.ord),(nr),_size_of(fmt_or_size)); end
|
104
|
+
|
105
|
+
#define _IOR_BAD(type,nr,size) _IOC(_IOC_READ,(type),(nr),sizeof(size))
|
106
|
+
#define _IOW_BAD(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),sizeof(size))
|
107
|
+
#define _IOWR_BAD(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))
|
108
|
+
|
109
|
+
# /* used to decode ioctl numbers.. */
|
110
|
+
|
111
|
+
#
|
112
|
+
# @!macro nr
|
113
|
+
# @return [Integer]
|
114
|
+
def _IOC_DIR(nr); return (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK); end
|
115
|
+
# @!macro nr
|
116
|
+
# @return [Integer]
|
117
|
+
def _IOC_TYPE(nr); return (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK); end
|
118
|
+
# @!macro nr
|
119
|
+
# @return [Integer]
|
120
|
+
def _IOC_NR(nr); return (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK); end
|
121
|
+
# @!macro nr
|
122
|
+
# @return [Integer]
|
123
|
+
def _IOC_SIZE(nr); return (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK); end
|
124
|
+
|
125
|
+
#
|
126
|
+
# @param [String || Numeric] numeric_or_format
|
127
|
+
# @return [Fixnum]
|
128
|
+
def _size_of(numeric_or_format)
|
129
|
+
if numeric_or_format.is_a? String
|
130
|
+
sizeof(numeric_or_format)
|
131
|
+
elsif numeric_or_format.is_a? Numeric
|
132
|
+
numeric_or_format
|
133
|
+
else numeric_or_format.respond_to? :size
|
134
|
+
numeric_or_format.size
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
extend self
|
139
|
+
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'vigilem/support/key_map'
|
2
|
+
|
3
|
+
require 'vigilem/evdev/key_map_cache'
|
4
|
+
|
5
|
+
module Vigilem
|
6
|
+
module Evdev
|
7
|
+
module System
|
8
|
+
#
|
9
|
+
# the entry point for loading keymaps
|
10
|
+
module KeymapLoaders
|
11
|
+
|
12
|
+
extend Support::Metadata
|
13
|
+
|
14
|
+
KeyMap = Support::KeyMap
|
15
|
+
|
16
|
+
attr_writer :all
|
17
|
+
|
18
|
+
attr_reader :loader
|
19
|
+
|
20
|
+
#
|
21
|
+
# @return [Array<KeymapLoader>]
|
22
|
+
def all
|
23
|
+
@all ||= []
|
24
|
+
end
|
25
|
+
|
26
|
+
#
|
27
|
+
# @return [FalseClass || TrueClass]
|
28
|
+
def cached?
|
29
|
+
Evdev::KeyMapCache.exists?
|
30
|
+
end
|
31
|
+
|
32
|
+
#
|
33
|
+
# @raise [NotImplementedError] 'Cannot find keymap'
|
34
|
+
# @return [KeyMap]
|
35
|
+
def exec
|
36
|
+
attempt ||= 1
|
37
|
+
if cached?
|
38
|
+
k = KeyMapCache.restore
|
39
|
+
@loader = k.metadata[:loader]
|
40
|
+
metadata[:cached] = true
|
41
|
+
else
|
42
|
+
#@todo just gets the first
|
43
|
+
if @loader = all.find(&:available?)
|
44
|
+
@loader.exec
|
45
|
+
else
|
46
|
+
raise NotImplementedError, 'Cannot find keymap'
|
47
|
+
end
|
48
|
+
end
|
49
|
+
rescue ArgumentError #, 'marshal data too short'
|
50
|
+
KeyMapCache.delete
|
51
|
+
retry unless (attempts -= 1).zero?
|
52
|
+
else
|
53
|
+
KeyMapCache.dump((mapping = @loader.key_map))
|
54
|
+
mapping[:cached] = true
|
55
|
+
mapping.left_side_aliases(:keycode, :keycodes)
|
56
|
+
mapping.right_side_aliases(:keysym, :keysyms, :char_ref, :char_refs)
|
57
|
+
mapping
|
58
|
+
end
|
59
|
+
|
60
|
+
#
|
61
|
+
# @return [Array<KeymapLoader>]
|
62
|
+
def set_default
|
63
|
+
if not @set
|
64
|
+
@set = true
|
65
|
+
all.concat([DumpkeysLoader.new, KmapLoader.new])
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# returns the default system keymap
|
70
|
+
# @return [KeyMap]
|
71
|
+
def key_map
|
72
|
+
set_default
|
73
|
+
if loader
|
74
|
+
loader.key_map
|
75
|
+
else
|
76
|
+
exec
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
alias_method :build_cache, :key_map
|
81
|
+
|
82
|
+
extend self
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
require 'vigilem/evdev/system/keymap_loaders/dumpkeys_loader'
|
89
|
+
require 'vigilem/evdev/system/keymap_loaders/kmap_loader'
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'vigilem/support/key_map'
|
2
|
+
|
3
|
+
require 'vigilem/support/key_map_info'
|
4
|
+
|
5
|
+
module Vigilem
|
6
|
+
module Evdev
|
7
|
+
module System
|
8
|
+
module KeymapLoaders
|
9
|
+
#
|
10
|
+
# this takes longer than keymap_loader, but is more thorough
|
11
|
+
class DumpkeysLoader
|
12
|
+
|
13
|
+
KeyMap = Support::KeyMap
|
14
|
+
|
15
|
+
attr_writer :key_map
|
16
|
+
|
17
|
+
#
|
18
|
+
# @param [String] args
|
19
|
+
# @return [KeyMap]
|
20
|
+
def exec(args='')
|
21
|
+
@key_map = if self.class.available?
|
22
|
+
if args =~ /\s+/
|
23
|
+
raise ArgumentError, "Argument: `#{args}' invalid"
|
24
|
+
else
|
25
|
+
k = KeyMap.load_string(self.class.run_dumpkeys("fn#{args}"))
|
26
|
+
k.metadata[:loader] = self
|
27
|
+
k
|
28
|
+
end
|
29
|
+
end
|
30
|
+
key_map_info
|
31
|
+
@key_map
|
32
|
+
end
|
33
|
+
|
34
|
+
alias_method :key_map!, :exec
|
35
|
+
|
36
|
+
#
|
37
|
+
# @return [KeyMap]
|
38
|
+
def key_map
|
39
|
+
@key_map ||= exec
|
40
|
+
end
|
41
|
+
|
42
|
+
#
|
43
|
+
# @return [KeyMapInfo]
|
44
|
+
def key_map_info
|
45
|
+
key_map.info ||= key_map.info(self.class.run_dumpkeys('l'))
|
46
|
+
end
|
47
|
+
|
48
|
+
#
|
49
|
+
# @return [TrueClass || FalseClass]
|
50
|
+
def available?
|
51
|
+
self.class.available?
|
52
|
+
end
|
53
|
+
|
54
|
+
class << self
|
55
|
+
#
|
56
|
+
# @return [FalseClass || TrueClass]
|
57
|
+
def available?
|
58
|
+
tty and command
|
59
|
+
end
|
60
|
+
|
61
|
+
#
|
62
|
+
# @return [String || NilClass] returns the command path, otherwise
|
63
|
+
# returns nil if there isn't one
|
64
|
+
def command
|
65
|
+
if not @checked_for_command
|
66
|
+
str = `which dumpkeys`.chomp
|
67
|
+
@checked_for_command = true
|
68
|
+
@command = str.empty? ? nil : str
|
69
|
+
else
|
70
|
+
@command
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# as long as it's sudo does this matter?
|
75
|
+
# @return [String || nil]
|
76
|
+
def tty
|
77
|
+
@tty_command ||= `which tty`.chomp
|
78
|
+
@tty ||= `#{@tty_command}` if @tty_command
|
79
|
+
end
|
80
|
+
|
81
|
+
# needs sudo on xserver
|
82
|
+
# loads dumpkeys string from system command
|
83
|
+
# @param [Array] *dumpkeys_args
|
84
|
+
# @raise command not run?
|
85
|
+
# @return [String || nil]
|
86
|
+
def run_dumpkeys(*dumpkeys_args)
|
87
|
+
if tty and command
|
88
|
+
args = dumpkeys_args.empty? ? '' : "-#{dumpkeys_args.join}"
|
89
|
+
`#{command} #{args} < #{tty}`
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'zlib'
|
2
|
+
|
3
|
+
require 'vigilem/support/key_map'
|
4
|
+
|
5
|
+
module Vigilem
|
6
|
+
module Evdev
|
7
|
+
module System
|
8
|
+
module KeymapLoaders
|
9
|
+
#
|
10
|
+
# change name to console-setup kmap
|
11
|
+
class KmapLoader
|
12
|
+
|
13
|
+
KeyMap = Support::KeyMap
|
14
|
+
|
15
|
+
attr_writer :cache_glob, :key_map
|
16
|
+
|
17
|
+
#
|
18
|
+
# @param [NilClass || String] path
|
19
|
+
# @return [KeyMap]
|
20
|
+
def exec(path=nil)
|
21
|
+
k = if not path
|
22
|
+
if filepath = filepaths.find {|f| File.extname(f) !~ /\.g(un)?z(ip)?/ }
|
23
|
+
KeyMap.load_string(File.read(filepath))
|
24
|
+
elsif filepath = filepaths.find {|f| File.extname(f) =~ /\.g(un)?z(ip)?/ }
|
25
|
+
KeyMap.load_string(Zlib::GzipReader.open(filepath).read)
|
26
|
+
end
|
27
|
+
else
|
28
|
+
KeyMap.load_string(Zlib::GzipReader.open(path).read)
|
29
|
+
end
|
30
|
+
k.metadata[:loader] = self
|
31
|
+
@key_map = k
|
32
|
+
key_map_info
|
33
|
+
@key_map
|
34
|
+
end
|
35
|
+
|
36
|
+
alias_method :keymap!, :exec
|
37
|
+
|
38
|
+
#
|
39
|
+
# @return
|
40
|
+
def key_map
|
41
|
+
@key_map ||= exec
|
42
|
+
end
|
43
|
+
|
44
|
+
#
|
45
|
+
# @return [KeyMapInfo]
|
46
|
+
def key_map_info
|
47
|
+
@key_map_info ||= if DumpkeysLoader.available?
|
48
|
+
key_map.info(DumpkeysLoader.run_dumpkeys('l'))
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
#
|
53
|
+
# @return [Array<String>]
|
54
|
+
def filepaths
|
55
|
+
@filepaths ||= Dir[cache_glob]
|
56
|
+
end
|
57
|
+
|
58
|
+
#
|
59
|
+
# @return [TrueClass || FalseClass]
|
60
|
+
def available?
|
61
|
+
filepaths.any? {|f| File.exists? f }
|
62
|
+
end
|
63
|
+
|
64
|
+
#
|
65
|
+
# @return [String] glob
|
66
|
+
def cache_glob
|
67
|
+
@cache_glob ||= '/etc/console-setup/cached*.kmap*'
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|