1LIDInfo {
2	var <name, <bustype, <vendorID, <productID, <version, <physical, <unique;
3	var <>path;
4
5	printOn { | stream |
6		super.printOn(stream);
7		stream << $( << name << ", " << path << ", ";
8		[
9			vendorID,
10			productID,
11			version,
12			bustype
13		].collect({ | x | "0x" ++ x.asHexString(4) }).printItemsOn(stream);
14		stream << ", " << physical << ", " << unique;
15		stream.put($));
16	}
17
18	postInfo {
19		"\tName: \t%\n".postf( name );
20		"\tVendor and product ID: \t%, %\n".postf( vendorID, productID );
21		"\tPath: \t%\n".postf( path );
22		"\tPhysical: \t%\n".postf( physical );
23		"\tVersion and bustype: \t%, %\n".postf( version, bustype );
24		"\tUnique: \t%\n".postf( unique );
25		// "\tUsage name and page: \t%, \t%\n".postf( this.usageName, this.pageName );
26		// "\tVendor name: \t%\n".postf( vendor );
27		// "\tProduct name: \t%\n".postf( product );
28	}
29
30	open{
31		^LID.new( path );
32	}
33
34	findArgs {
35		^[vendorID, productID, path, version, physical, unique]
36	}
37}
38
39LIDAbsInfo {
40	var <value = 0, <min = 0, <max = 0, <fuzz = 0, <flat = 0;
41
42	printOn { | stream |
43		stream
44		<< this.class.name << $(
45		<< "value: " << value << ", "
46		<< "min: " << min << ", "
47		<< "max: " << max << ", "
48		<< "fuzz: " << fuzz << ", "
49		<< "flat: " << flat << $)
50	}
51}
52
53LID {
54	var dataPtr, <path, <info, <caps, spec, <slots, <isGrabbed=false, <>action;
55	var <>closeAction;
56	var debugAction;
57	classvar openDevices, eventTypes, <specs, <>deviceRoot = "/dev/input", <available;
58	classvar eventLoopIsRunning = false;
59	classvar globalDebugAction;
60	classvar <action, prAction;
61
62	*running{
63		^eventLoopIsRunning;
64	}
65
66	*initClass {
67		// all = []; // becomes openDevices
68		specs = IdentityDictionary.new;
69
70		available = IdentityDictionary.new;
71		openDevices = [];
72		// availableUsages = IdentityDictionary.new;
73
74		eventTypes = [
75			// maps event type (index) to max code value
76			0x0001,		// EV_SYN
77			0x02ff,		// EV_KEY
78			0x000f,		// EV_REL
79			0x003f,		// EV_ABS
80			0x0007,		// EV_MSC
81			0x000f,     // EV_SW (switch) added by nescivi
82
83			nil, nil, nil,
84			nil, nil, nil, nil,
85			nil, nil, nil, nil,
86
87			0x000f,		// EV_LED
88			0x0007,		// EV_SND
89
90			nil,
91
92			0x0001,		// EV_REP
93			0x007f,		// EV_FF
94			0x0000,		// EV_PWR
95			0x0001,		// EV_FF_STATUS
96
97			nil, nil, nil, nil,
98			nil, nil, nil
99		];
100	}
101
102	*initializeLID{
103		"Starting LID eventloop".postln;
104		this.prStartEventLoop;
105		eventLoopIsRunning = true;
106		ShutDown.add {
107			this.closeAll;
108			this.prStopEventLoop;
109		};
110	}
111
112	*findAvailable{ |name|
113		var devicePaths, d, open;
114		if ( eventLoopIsRunning.not ){ this.initializeLID; };
115		name = name ? "event";
116		devicePaths = (deviceRoot++"/"++name++"*").pathMatch;
117		// deviceList = Array.fill( devices.size, 0 );
118
119		available = IdentityDictionary.new;
120
121		devicePaths.do{ |it,i|
122			open = false;
123			if ( openDevices.detect({ | dev | dev.path == it }).notNil, {open = true});
124			d = try { LID( it ) };
125			if ( d != nil,
126				{
127					d.info.path_( it );
128					available.put( i, d.info ); // why did I need the slots already here?
129					if ( open.not, {d.close} );
130				},{
131					// just print that device is not openable, and don't add it to the available list
132					("LID: could not open device with path"+ it + "\n" ).warn;
133				}
134			);
135		};
136		"LID: found % devices\n".postf( available.size );
137		^available
138	}
139
140	*postAvailable {
141		this.available.sortedKeysValuesDo { |k, v| "%: ".postf( k ); v.postInfo; };
142	}
143
144	*register { | name, spec |
145		specs[name] = spec;
146	}
147
148	*mouseDeviceSpec {
149		^(
150			// key
151			b1: #[0x0001, 0x0110],	// left button
152			b2: #[0x0001, 0x0111],	// middle button
153			b3: #[0x0001, 0x0112],	// right button
154			// rel
155			x: #[0x0002, 0x0000],	// x axis
156			y: #[0x0002, 0x0001],	// y axis
157			s: #[0x0002, 0x0008]	// scroll wheel
158		)
159	}
160	*keyboardDeviceSpec {
161		^(
162			esc: [1, 1],
163			one:  [1, 2], two: [1, 3], three: [1, 4], four: [1, 5],
164			five: [1, 6], six: [1, 7], seven: [1, 8], eight: [1, 9],
165			nine: [1, 10], zero: [1, 11], minus: [1, 12], equal: [1, 13],
166			backspace: [1, 14],
167			tab: [1, 15], q: [1, 16], w: [1, 17], e: [1, 18],
168			r: [1, 19], t: [1, 20], y: [1, 21], u: [1, 22], i: [1, 23],
169			o: [1, 24], p: [1, 25], leftbrace: [1, 26], rightbrace: [1, 27],
170			enter: [1, 28],
171			leftctrl: [1, 29],
172			a: [1, 30], s: [1, 31], d: [1, 32], f: [1, 33], g: [1, 34],
173			h: [1, 35], j: [1, 36], k: [1, 37], l: [1, 38], semicolon: [1, 39],
174			apostrophe: [1, 40],
175			grave: [1, 41],
176			leftshift: [1, 42],
177			backslash: [1, 43],
178			z: [1, 44], x: [1, 45], c: [1, 46], v: [1, 47], b: [1, 48],
179			n: [1, 49], m: [1, 50], comma: [1, 51], dot: [1, 52],
180			slash: [1, 53], rightshift: [1, 54],
181			kpasterisk: [1, 55],
182			leftalt: [1, 56], space: [1, 57], capslock: [1, 58],
183			f1: [1, 59], f2: [1, 60], f3: [1, 61], f4: [1, 62],
184			f5: [1, 63], f6: [1, 64], f7: [1, 65], f8: [1, 66],
185			f9: [1, 67], f10: [1, 68], numlock: [1, 69], scrolllock: [1, 70],
186			kp7: [1, 71], kp8: [1, 72], kp9: [1, 73], kpminus: [1, 74],
187			kp4: [1, 75], kp5: [1, 76], kp6: [1, 77], kpplus: [1, 78],
188			kp1: [1, 79], kp2: [1, 80], kp3: [1, 81],
189			kp0: [1, 82], kpdot: [1, 83],
190			zenkakuhankaku: [1, 85],
191			the102nd: [1, 86],
192			f11: [1, 87],
193			f12: [1, 88],
194			ro: [1, 89],
195			katakana: [1, 90],
196			hiragana: [1, 91],
197			henkan: [1, 92],
198			katakanahiragana: [1, 93],
199			muhenkan: [1, 94],
200			kpjpcomma: [1, 95],
201			kpenter: [1, 96],
202			rightctrl: [1, 97],
203			kpslash: [1, 98],
204			sysrq: [1, 99],
205			rightalt: [1, 100],
206			linefeed: [1, 101],
207			home: [1, 102],
208			up: [1, 103],
209			pageup: [1, 104],
210			left: [1, 105],
211			right: [1, 106],
212			end: [1, 107],
213			down: [1, 108],
214			pagedown: [1, 109],
215			insert: [1, 110],
216			delete: [1, 111],
217			macro: [1, 112],
218			mute: [1, 113],
219			volumedown: [1, 114],
220			volumeup: [1, 115],
221			power: [1, 116],
222			kpequal: [1, 117],
223			kpplusminus: [1, 118],
224			pause: [1, 119],
225			kpcomma: [1, 121],
226			hanguel: [1, 122],
227			hanja: [1, 123],
228			yen: [1, 124],
229			leftmeta: [1, 125],
230			rightmeta: [1, 126],
231			compose: [1, 127],
232			stop: [1, 128],
233			again: [1, 129],
234			props: [1, 130],
235			undo: [1, 131],
236			front: [1, 132],
237			copy: [1, 133],
238			open: [1, 134],
239			paste: [1, 135],
240			find: [1, 136],
241			cut: [1, 137],
242			help: [1, 138],
243			menu: [1, 139],
244			calc: [1, 140],
245			setup: [1, 141],
246			sleep: [1, 142],
247			wakeup: [1, 143],
248			file: [1, 144],
249			sendfile: [1, 145],
250			deletefile: [1, 146],
251			xfer: [1, 147],
252			prog1: [1, 148],
253			prog2: [1, 149],
254			www: [1, 150],
255			msdos: [1, 151],
256			coffee: [1, 152],
257			direction: [1, 153],
258			cyclewindows: [1, 154],
259			mail: [1, 155],
260			bookmarks: [1, 156],
261			computer: [1, 157],
262			back: [1, 158],
263			forward: [1, 159],
264			closecd: [1, 160],
265			ejectcd: [1, 161],
266			ejectclosecd: [1, 162],
267			nextsong: [1, 163],
268			playpause: [1, 164],
269			previoussong: [1, 165],
270			stopcd: [1, 166],
271			record: [1, 167],
272			rewind: [1, 168],
273			phone: [1, 169],
274			iso: [1, 170],
275			config: [1, 171],
276			homepage: [1, 172],
277			refresh: [1, 173],
278			exit: [1, 174],
279			move: [1, 175],
280			edit: [1, 176],
281			scrollup: [1, 177],
282			scrolldown: [1, 178],
283			kpleftparen: [1, 179],
284			kprightparen: [1, 180],
285			new: [1, 181],
286			redo: [1, 182],
287			f13: [1, 183],
288			f14: [1, 184],
289			f15: [1, 185],
290			f16: [1, 186],
291			f17: [1, 187],
292			f18: [1, 188],
293			f19: [1, 189],
294			f20: [1, 190],
295			f21: [1, 191],
296			f22: [1, 192],
297			f23: [1, 193],
298			f24: [1, 194],
299			playcd: [1, 200],
300			pausecd: [1, 201],
301			prog3: [1, 202],
302			prog4: [1, 203],
303			suspend: [1, 205],
304			close: [1, 206],
305			play: [1, 207],
306			fastforward: [1, 208],
307			bassboost: [1, 209],
308			print: [1, 210],
309			hp: [1, 211],
310			camera: [1, 212],
311			sound: [1, 213],
312			question: [1, 214],
313			email: [1, 215],
314			chat: [1, 216],
315			search: [1, 217],
316			connect: [1, 218],
317			finance: [1, 219],
318			sport: [1, 220],
319			shop: [1, 221],
320			alterase: [1, 222],
321			cancel: [1, 223],
322			brightnessdown: [1, 224],
323			brightnessup: [1, 225],
324			media: [1, 226],
325			switchvideomode: [1, 227],
326			kbdillumtoggle: [1, 228],
327			kbdillumdown: [1, 229],
328			kbdillumup: [1, 230],
329			send: [1, 231],
330			reply: [1, 232],
331			forwardmail: [1, 233],
332			save: [1, 234],
333			documents: [1, 235]
334		)
335	}
336
337	*openDevices{
338		^openDevices.copy;
339	}
340
341	*closeAll {
342		openDevices.copy.do{ |dev| dev.close };
343		this.prStopEventLoop;
344		eventLoopIsRunning = false;
345	}
346
347	*openAt{ |index|
348		^available.at( index ).open;
349	}
350
351	*findBy{ |vendorID, productID, path, version, physical, unique|
352		if ( [vendorID, productID, path, version, physical, unique].every( _.isNil ) ) {
353			^nil;
354		};
355		^LID.available.select{ |info|
356			vendorID.isNil or: { info.vendorID == vendorID } and:
357			{ productID.isNil or: { info.productID == productID } } and:
358			{ path.isNil or: { info.path == path } } and:
359			{ version.isNil or: { info.version == version } } and:
360			{ physical.isNil or: { info.physical == physical.asSymbol } } and:
361			{ unique.isNil or: { info.unique == unique.asSymbol } }
362		};
363	}
364
365	*open{ |vendorID, productID, path, version, physical, unique|
366		var devInfo, device;
367		devInfo = this.findBy( vendorID, productID, path, version, physical, unique );
368		if ( devInfo.isNil ){
369			("LID: could not find device" + vendorID + "," + productID + "," + path + "\n").error;
370			^nil;
371		};
372		devInfo = devInfo.asArray.first;
373		device = LID.new( devInfo.path );
374		// merge usageDict?
375		^device;
376	}
377
378	*openPath { |path|
379		// "LID: Opening device %\n".postf( path );
380		^LID.new( path );
381	}
382
383	/*
384	*mergeUsageDict { |dev|
385		dev.usages.keysValuesDo { |key, val|
386			if ( availableUsages.at( key ).isNil ) {
387				availableUsages.put( key, IdentityDictionary.new );
388			};
389			availableUsages.at( key ).put( dev.id, val );
390		};
391	}
392
393	*removeUsageDict { |dev| // when device is closed
394		availableUsages.do { |val|
395			val.removeAt( dev.id );
396		};
397	}
398	*/
399
400	*new { | path |
401		path = PathName(path);
402		if (path.isRelativePath) {
403			path = (deviceRoot ++ "/" ++ path.fullPath).standardizePath
404		}{
405			path = path.fullPath;
406		};
407		^openDevices.detect({ | dev | dev.path == path }) ?? { super.new.prInit(path) }
408	}
409
410	postInfo{
411		this.info.postInfo;
412	}
413
414	vendor{
415		^this.info.vendorID;
416	}
417
418	product{
419		^this.info.productID;
420	}
421
422	postSlots{
423		slots.sortedKeysValuesDo{ |k,v|
424			v.sortedKeysValuesDo{ |ks,vs|
425				"%,%: %\n".format( k, ks, vs.key ).post;
426				vs.postInfo;
427			};
428		};
429	}
430
431	isOpen {
432		^dataPtr.notNil
433	}
434
435	close {
436		if (this.isOpen) {
437			closeAction.value;
438			this.prClose;
439			openDevices.remove(this);
440		};
441	}
442
443	dumpCaps {
444		caps.keys.do { | evtType |
445			Post << "0x" << evtType.asHexString << ":\n";
446			caps[evtType].do { | evtCode |
447				Post << $\t << "0x" << evtCode.asHexString << "\n";
448			}
449		}
450	}
451
452	debug_{ |onoff|
453		if ( onoff ){
454			debugAction =  { | evtType, evtCode, value |
455				[this.info.name, evtType, evtCode, value].postln;
456			};
457		}{
458			debugAction = nil;
459		}
460	}
461
462	debug{
463		^debugAction.notNil;
464	}
465
466	slot { | evtType, evtCode |
467		^slots.atFail(evtType, {
468			Error("event type not supported").throw
469		}).atFail(evtCode, {
470			Error("event code not supported").throw
471		})
472	}
473	at { | controlName |
474		^this.slot(*this.spec.atFail(controlName, {
475			Error("invalid control name").throw
476		}))
477	}
478
479	getAbsInfo { | evtCode |
480		^this.prGetAbsInfo(evtCode, LIDAbsInfo.new)
481	}
482	getKeyState { | evtCode |
483		^this.prGetKeyState(evtCode)
484	}
485	getLEDState { | evtCode |
486		^0
487	}
488	setLEDState { |evtCode, evtValue |
489		^this.prSetLedState( evtCode, evtValue )
490	}
491	setMSCState { |evtCode, evtValue |
492		^this.prSetMscState( evtCode, evtValue )
493	}
494
495	grab { | flag = true |
496		// useful when using mouse or keyboard. be sure to have an
497		// 'exit point', or your desktop will be rendered useless ...
498		if (isGrabbed != flag) {
499			this.prGrab(flag);
500			isGrabbed = flag;
501		};
502	}
503	ungrab {
504		this.grab(false)
505	}
506
507	*debug_{ |onoff = true|
508		if ( onoff ){
509			globalDebugAction = { | device, evtType, evtCode, value |
510				[device.info.name, evtType, evtCode, value].postln;
511			};
512		}{
513			globalDebugAction = nil;
514		}
515	}
516
517	*debug{
518		^globalDebugAction.notNil;
519	}
520
521	// action interface:
522	*addRecvFunc { |function|
523		if ( prAction.isNil ) {
524			prAction = FunctionList.new;
525		};
526		prAction = prAction.addFunc( function );
527	}
528
529	*removeRecvFunc { |function|
530		prAction.removeFunc( function );
531	}
532
533
534	*action_ { |function|
535		if ( action.notNil ) {
536			this.removeRecvFunc( action );
537		};
538		action = function;
539		this.addRecvFunc( function );
540	}
541
542	spec{ |forceLookup = false|
543		if ( spec.notNil and: forceLookup.not ){ ^spec };
544		spec = specs.atFail(info.name, { IdentityDictionary.new });
545		spec.keysValuesDo{ |k,v|
546			var slot = slots[ v[0] ][ v[1] ];
547			if ( slot.notNil ){ slot.key = k };
548		};
549		^spec;
550	}
551
552	// PRIVATE
553	*prStartEventLoop {
554		_LID_Start
555		^this.primitiveFailed
556	}
557	*prStopEventLoop {
558		_LID_Stop
559		^this.primitiveFailed
560	}
561	prInit { | argPath |
562		this.prOpen(argPath);
563		openDevices = openDevices.add(this);
564		closeAction = {};
565		path = argPath;
566		info = this.prGetInfo(LIDInfo.new);
567		info.path_( path );
568		("LID: Opened device: %\n".postf( this.info ) );
569		caps = IdentityDictionary.new;
570		slots = IdentityDictionary.new;
571		eventTypes.do { | evtTypeMax, evtType |
572			// nescivi: below was evtType.notNil, but since that is the index, that makes no sense... however evtTypeMax can be nil, and should be skipped if it is... so I'm changing it.
573			if (evtTypeMax.notNil and: { this.prEventTypeSupported(evtType) }) {
574				caps[evtType] = List.new;
575				slots[evtType] = IdentityDictionary.new;
576				for (0, evtTypeMax, { | evtCode |
577					if (this.prEventCodeSupported(evtType, evtCode)) {
578						caps[evtType].add(evtCode);
579						slots[evtType][evtCode] = LIDSlot.new(
580							this, evtType, evtCode
581						);
582					};
583				});
584				caps[evtType].sort;
585			}
586		};
587	}
588	prOpen { | path |
589		_LID_Open
590		^this.primitiveFailed
591	}
592	prClose {
593		_LID_Close
594		^this.primitiveFailed
595	}
596	prEventTypeSupported { | evtType |
597		_LID_EventTypeSupported
598		^this.primitiveFailed
599	}
600	prEventCodeSupported { | evtType, evtCode |
601		_LID_EventCodeSupported
602		^this.primitiveFailed
603	}
604	prGetInfo { | info |
605		_LID_GetInfo
606		^this.primitiveFailed
607	}
608	prGetKeyState { | evtCode |
609		_LID_GetKeyState
610		^this.primitiveFailed
611	}
612	prGetAbsInfo { | evtCode, absInfo |
613		_LID_GetAbsInfo
614		^this.primitiveFailed
615	}
616	prGrab { | flag |
617		_LID_Grab
618		^this.primitiveFailed
619	}
620	prHandleEvent { | evtType, evtCode, evtValue |
621		if ( debugAction.notNil ){
622			debugAction.value( evtType, evtCode, evtValue );
623		};
624		if ( globalDebugAction.notNil ){
625			globalDebugAction.value( this, evtType, evtCode, evtValue );
626		};
627		if ( slots.notNil ){
628			// not either or for the device action. Do slot actions in any case:
629			slots[evtType][evtCode].rawValue_(evtValue);
630			// event callback
631			if (action.notNil) {
632				action.value(evtType, evtCode, evtValue, slots[evtType][evtCode].value);
633			};
634		};
635		if ( prAction.notNil ){
636			prAction.value( this, evtType, evtCode, evtValue );
637		}
638	}
639
640	// this prevents a high cpu cycle when device was detached; added by marije
641	prReadError{
642		this.close;
643		("WARNING: Device was removed: " + this.path + this.info).postln;
644	}
645
646	prSetLedState { |evtCode, evtValue|	// added by Marije Baalman
647		// set LED value
648		_LID_SetLedState
649		^this.primitiveFailed
650	}
651	prSetMscState { |evtCode, evtValue|
652		// set MSC value
653		_LID_SetMscState
654		^this.primitiveFailed
655	}
656}
657
658LIDSlot {
659	var <device, <type, <code, <spec, <>action;
660	var rawValue = 0;
661	classvar slotTypeMap, <slotTypeStrings;
662	var <bus, busAction;
663	var debugAction;
664	var <>key;
665
666
667	*initClass {
668		slotTypeMap = IdentityDictionary.new.addAll([
669			0x0001 -> LIDKeySlot,
670			0x0002 -> LIDRelSlot,
671			0x0003 -> LIDAbsSlot,
672			0x0004 -> LIDMscSlot,
673			0x0011 -> LIDLedSlot,
674		]);
675		slotTypeStrings = IdentityDictionary.new.addAll([
676			0x0000 -> "Syn",
677			0x0001 -> "Button",
678			0x0002 -> "Relative",
679			0x0003 -> "Absolute",
680			0x0004 -> "Miscellaneous",
681			0x0011 -> "LED",
682			0x0012 -> "Sound",
683			0x0014 -> "Rep",
684			0x0015 -> "Force Feedback",
685			0x0016 -> "Power",
686			0x0017 -> "Force Feedback Status",
687			0x0FFF -> "Linear"
688		]);
689
690	}
691
692	postInfo {
693		"\tType: \t%, %\n".postf( type, slotTypeStrings.at( type ) );
694		"\tCode: \t%\n".postf( code );
695		"\tKey: \t%\n".postf( key );
696		"\tSpec: \t%\n".postf( spec );
697		"\tValue: \t%\n".postf( this.value );
698	}
699
700	*new { | device, evtType, evtCode |
701		^(slotTypeMap[evtType] ? this).newCopyArgs(device, evtType, evtCode).init.initSpec
702	}
703
704	init{
705		busAction = {};
706		debugAction = {};
707		action = {};
708	}
709
710	initSpec {
711		spec = ControlSpec(0, 1, \lin, 1, 0);
712	}
713	rawValue {
714		^rawValue
715	}
716	value {
717		^spec.unmap(rawValue)
718	}
719	rawValue_ { | inValue |
720		rawValue = inValue;
721		action.value(this);
722		busAction.value( this );
723		debugAction.value( this );
724	}
725	next {
726		^this.value
727	}
728
729	debug_{ |onoff|
730		if ( onoff, {
731			debugAction = { |slot| [ slot.type, slot.code, slot.value, slot.key ].postln; };
732		}, {
733			debugAction = {};
734		});
735	}
736
737	debug{
738		^debugAction.notNil;
739	}
740
741	createBus { |server|
742		server = server ? Server.default;
743		if ( bus.isNil, {
744			bus = Bus.control( server, 1 );
745		}, {
746			if ( bus.index.isNil, {
747				bus = Bus.control( server, 1 );
748			});
749		});
750		busAction = { |v| bus.set( v.value ); };
751	}
752
753	freeBus {
754		busAction = {};
755		bus.free;
756		bus = nil;
757	}
758
759	// JITLib support
760	kr {
761		this.createBus;
762		^In.kr( bus );
763	}
764}
765
766LIDKeySlot : LIDSlot {
767	initSpec {
768		super.initSpec;
769		rawValue = device.getKeyState(code);
770	}
771}
772
773LIDRelSlot : LIDSlot {
774	var delta = 0, <>deltaAction;
775
776	initSpec { }
777	value { ^rawValue }
778	rawValue_ { | inDelta |
779		delta = inDelta;
780		rawValue = rawValue + delta;
781		action.value(this);
782		busAction.value( this );
783		debugAction.value( this );
784		deltaAction.value(this);
785	}
786
787	delta { ^delta }
788
789	debug_{ |onoff|
790		if ( onoff, {
791			debugAction = { |slot| [ slot.type, slot.code, slot.value, slot.delta, slot.key ].postln; };
792		}, {
793			debugAction = {};
794		});
795	}
796
797
798}
799
800LIDLedSlot : LIDSlot {
801
802	initSpec { }
803	value { ^rawValue }
804	value_{ |inValue| this.rawValue_( spec.map( inValue ) ); }
805	rawValue_ { | inValue |
806		rawValue = inValue;
807		device.setLEDState( code, inValue );
808		action.value(this);
809		busAction.value( this );
810		debugAction.value( this );
811	}
812}
813
814LIDMscSlot : LIDSlot {
815
816	initSpec { }
817	value { ^rawValue }
818	value_{ |inValue| this.rawValue_( spec.map( inValue ) ); }
819	rawValue_ { | inValue |
820		rawValue = inValue;
821		device.setMSCState( code, rawValue );
822		action.value(this);
823		busAction.value( this );
824		debugAction.value( this );
825	}
826}
827
828LIDAbsSlot : LIDSlot {
829	var <info;
830
831	initSpec {
832		info = device.getAbsInfo(code);
833		spec = ControlSpec(info.min, info.max, \lin, 1);
834		spec.default = spec.map(0.5).asInteger;
835		rawValue = info.value;
836	}
837}
838
839// EOF
840