]> git.donarmstrong.com Git - kiibohd-kll.git/blob - kll_lib/containers.py
d54ff1029e8d7e3a5b5c8a244ba59c93056c0d44
[kiibohd-kll.git] / kll_lib / containers.py
1 #!/usr/bin/env python3
2 # KLL Compiler Containers
3 #
4 # Copyright (C) 2014 by Jacob Alexander
5 #
6 # This file 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 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This file 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 file.  If not, see <http://www.gnu.org/licenses/>.
18
19 ### Imports ###
20
21 import copy
22
23
24
25 ### Decorators ###
26
27  ## Print Decorator Variables
28 ERROR = '\033[5;1;31mERROR\033[0m:'
29
30
31
32 ### Parsing ###
33
34  ## Containers
35 class Capabilities:
36         # Container for capabilities dictionary and convenience functions
37         def __init__( self ):
38                 self.capabilities = dict()
39
40         def __getitem__( self, name ):
41                 return self.capabilities[ name ]
42
43         def __setitem__( self, name, contents ):
44                 self.capabilities[ name ] = contents
45
46         def __repr__( self ):
47                 return "Capabilities => {0}\nIndexed Capabilities => {1}".format( self.capabilities, sorted( self.capabilities, key = self.capabilities.get ) )
48
49
50         # Total bytes needed to store arguments
51         def totalArgBytes( self, name ):
52                 totalBytes = 0
53
54                 # Iterate over the arguments, summing the total bytes
55                 for arg in self.capabilities[ name ][ 1 ]:
56                         totalBytes += int( arg[ 1 ] )
57
58                 return totalBytes
59
60         # Name of the capability function
61         def funcName( self, name ):
62                 return self.capabilities[ name ][ 0 ]
63
64
65         # Only valid while dictionary keys are not added/removed
66         def getIndex( self, name ):
67                 return sorted( self.capabilities, key = self.capabilities.get ).index( name )
68
69         def getName( self, index ):
70                 return sorted( self.capabilities, key = self.capabilities.get )[ index ]
71
72         def keys( self ):
73                 return sorted( self.capabilities, key = self.capabilities.get )
74
75
76 class Macros:
77         # Container for Trigger Macro : Result Macro correlation
78         # Layer selection for generating TriggerLists
79         #
80         # Only convert USB Code list once all the ResultMacros have been accumulated (does a macro reduction; not reversible)
81         # Two staged list for ResultMacros:
82         #  1) USB Code/Non-converted (may contain capabilities)
83         #  2) Capabilities
84         def __init__( self ):
85                 # Default layer (0)
86                 self.layer = 0
87
88                 # Macro Storage
89                 self.macros = [ dict() ]
90
91                 # Base Layout Storage
92                 self.baseLayout = None
93                 self.layerLayoutMarkers = []
94
95                 # Correlated Macro Data
96                 self.resultsIndex = dict()
97                 self.triggersIndex = dict()
98                 self.resultsIndexSorted = []
99                 self.triggersIndexSorted = []
100                 self.triggerList = []
101                 self.maxScanCode = []
102
103                 # USBCode Assignment Cache
104                 self.assignmentCache = []
105
106         def __repr__( self ):
107                 return "{0}".format( self.macros )
108
109         def completeBaseLayout( self ):
110                 # Copy base layout for later use when creating partial layers and add marker
111                 self.baseLayout = copy.deepcopy( self.macros[ 0 ] )
112                 self.layerLayoutMarkers.append( copy.deepcopy( self.baseLayout ) ) # Not used for default layer, just simplifies coding
113
114         def removeUnmarked( self ):
115                 # Remove all of the unmarked mappings from the partial layer
116                 for trigger in self.layerLayoutMarkers[ self.layer ].keys():
117                         del self.macros[ self.layer ][ trigger ]
118
119         def addLayer( self ):
120                 # Increment layer count, and append another macros dictionary
121                 self.layer += 1
122                 self.macros.append( copy.deepcopy( self.baseLayout ) )
123
124                 # Add a layout marker for each layer
125                 self.layerLayoutMarkers.append( copy.deepcopy( self.baseLayout ) )
126
127         # Use for ScanCode trigger macros
128         def appendScanCode( self, trigger, result ):
129                 if not trigger in self.macros[ self.layer ]:
130                         self.replaceScanCode( trigger, result )
131                 else:
132                         self.macros[ self.layer ][ trigger ].append( result )
133
134         # Remove the given trigger/result pair
135         def removeScanCode( self, trigger, result ):
136                 # Remove all instances of the given trigger/result pair
137                 while result in self.macros[ self.layer ][ trigger ]:
138                         self.macros[ self.layer ][ trigger ].remove( result )
139
140         # Replaces the given trigger with the given result
141         # If multiple results for a given trigger, clear, then add
142         def replaceScanCode( self, trigger, result ):
143                 self.macros[ self.layer ][ trigger ] = [ result ]
144
145                 # Mark layer scan code, so it won't be removed later
146                 if not self.baseLayout is None:
147                         del self.layerLayoutMarkers[ self.layer ][ trigger ]
148
149         # Return a list of ScanCode triggers with the given USB Code trigger
150         def lookupUSBCodes( self, usbCode ):
151                 scanCodeList = []
152
153                 # Scan current layer for USB Codes
154                 for macro in self.macros[ self.layer ].keys():
155                         if usbCode in self.macros[ self.layer ][ macro ]:
156                                 scanCodeList.append( macro )
157
158                 return scanCodeList
159
160         # Cache USBCode Assignment
161         def cacheAssignment( self, operator, scanCode, result ):
162                 self.assignmentCache.append( [ operator, scanCode, result ] )
163
164         # Assign cached USBCode Assignments
165         def replayCachedAssignments( self ):
166                 # Iterate over each item in the assignment cache
167                 for item in self.assignmentCache:
168                         # Check operator, and choose the specified assignment action
169                         # Append Case
170                         if item[0] == ":+":
171                                 self.appendScanCode( item[1], item[2] )
172
173                         # Remove Case
174                         elif item[0] == ":-":
175                                 self.removeScanCode( item[1], item[2] )
176
177                         # Replace Case
178                         elif item[0] == ":":
179                                 self.replaceScanCode( item[1], item[2] )
180
181                 # Clear assignment cache
182                 self.assignmentCache = []
183
184         # Generate/Correlate Layers
185         def generate( self ):
186                 self.generateIndices()
187                 self.sortIndexLists()
188                 self.generateTriggerLists()
189
190         # Generates Index of Results and Triggers
191         def generateIndices( self ):
192                 # Iterate over every trigger result, and add to the resultsIndex and triggersIndex
193                 for layer in range( 0, len( self.macros ) ):
194                         for trigger in self.macros[ layer ].keys():
195                                 # Each trigger has a list of results
196                                 for result in self.macros[ layer ][ trigger ]:
197                                         # Only add, with an index, if result hasn't been added yet
198                                         if not result in self.resultsIndex:
199                                                 self.resultsIndex[ result ] = len( self.resultsIndex )
200
201                                         # Then add a trigger for each result, if trigger hasn't been added yet
202                                         triggerItem = tuple( [ trigger, self.resultsIndex[ result ] ] )
203                                         if not triggerItem in self.triggersIndex:
204                                                 self.triggersIndex[ triggerItem ] = len( self.triggersIndex )
205
206         # Sort Index Lists using the indices rather than triggers/results
207         def sortIndexLists( self ):
208                 self.resultsIndexSorted = [ None ] * len( self.resultsIndex )
209                 # Iterate over the resultsIndex and sort by index
210                 for result in self.resultsIndex.keys():
211                         self.resultsIndexSorted[ self.resultsIndex[ result ] ] = result
212
213                 self.triggersIndexSorted = [ None ] * len( self.triggersIndex )
214                 # Iterate over the triggersIndex and sort by index
215                 for trigger in self.triggersIndex.keys():
216                         self.triggersIndexSorted[ self.triggersIndex[ trigger ] ] = trigger
217
218         # Generates Trigger Lists per layer using index lists
219         def generateTriggerLists( self ):
220                 for layer in range( 0, len( self.macros ) ):
221                         # Set max scancode to 0xFF (255)
222                         # But keep track of the actual max scancode and reduce the list size
223                         self.triggerList.append( [ [] ] * 0xFF )
224                         self.maxScanCode.append( 0x00 )
225
226                         # Iterate through trigger macros to locate necessary ScanCodes and corresponding triggerIndex
227                         for trigger in self.macros[ layer ].keys():
228                                 for variant in range( 0, len( self.macros[ layer ][ trigger ] ) ):
229                                         # Identify result index
230                                         resultIndex = self.resultsIndex[ self.macros[ layer ][ trigger ][ variant ] ]
231
232                                         # Identify trigger index
233                                         triggerIndex = self.triggersIndex[ tuple( [ trigger, resultIndex ] ) ]
234
235                                         # Iterate over the trigger to locate the ScanCodes
236                                         for sequence in trigger:
237                                                 for combo in sequence:
238                                                         # Append triggerIndex for each found scanCode of the Trigger List
239                                                         # Do not re-add if triggerIndex is already in the Trigger List
240                                                         if not triggerIndex in self.triggerList[ layer ][ combo ]:
241                                                                 # Append is working strangely with list pre-initialization
242                                                                 # Doing a 0 check replacement instead -HaaTa
243                                                                 if len( self.triggerList[ layer ][ combo ] ) == 0:
244                                                                         self.triggerList[ layer ][ combo ] = [ triggerIndex ]
245                                                                 else:
246                                                                         self.triggerList[ layer ][ combo ].append( triggerIndex )
247
248                                                         # Look for max Scan Code
249                                                         if combo > self.maxScanCode[ layer ]:
250                                                                 self.maxScanCode[ layer ] = combo
251
252                         # Shrink triggerList to actual max size
253                         self.triggerList[ layer ] = self.triggerList[ layer ][ : self.maxScanCode[ layer ] + 1 ]
254
255                 # Determine overall maxScanCode
256                 self.overallMaxScanCode = 0x00
257                 for maxVal in self.maxScanCode:
258                         if maxVal > self.overallMaxScanCode:
259                                 self.overallMaxScanCode = maxVal
260