*/}}
Bladeren bron

Controller and screen config r/w

YimingWu 3 maanden geleden
bovenliggende
commit
ce5b8552ff
7 gewijzigde bestanden met toevoegingen van 389 en 124 verwijderingen
  1. 178 72
      la_controllers.c
  2. 22 5
      la_interface.h
  3. 43 2
      la_kernel.c
  4. 29 0
      resources/la_operators.c
  5. 36 0
      resources/la_properties.c
  6. 79 44
      resources/la_templates.c
  7. 2 1
      resources/la_widgets.c

+ 178 - 72
la_controllers.c

@@ -43,8 +43,15 @@ extern LA MAIN;
 #define LA_JS_TYPE_X56_THROTTLE 1
 #define LA_JS_TYPE_X56_STICK 2
 
-char LA_JS_BTN_NAMES[LA_JS_MAX_BUTTONS][10];
-char LA_JS_AXIS_NAMES[LA_JS_MAX_AXES][10];
+char LA_JS_BTN_NAMES[LA_JS_MAX_BUTTONS][12];
+char LA_JS_BTN_MAP_NAMES[LA_JS_MAX_BUTTONS][12];
+
+char LA_JS_AXIS_NAMES[LA_JS_MAX_AXES][12];
+char LA_JS_AXIS_MAP_NAMES[LA_JS_MAX_AXES][12];
+char LA_JS_AXIS_CMAX_NAMES[LA_JS_MAX_AXES][12];
+char LA_JS_AXIS_CMIN_NAMES[LA_JS_MAX_AXES][12];
+char LA_JS_AXIS_LMAX_NAMES[LA_JS_MAX_AXES][12];
+char LA_JS_AXIS_LMIN_NAMES[LA_JS_MAX_AXES][12];
 
 int la_IdentifyControllerInternalType(char* name){
     if(strstr(name, "X-56") && strstr(name, "Throttle")){ return LA_JS_TYPE_X56_THROTTLE; }
@@ -52,15 +59,7 @@ int la_IdentifyControllerInternalType(char* name){
     return 0;
 }
 
-laController* la_NewController(char* name, char* path, int device, int NumAxes, int NumButtons){
-    laController* c=memAcquire(sizeof(laController));
-    logPrint("Found controller %s\n    at %s\n    with %d axes, %d buttons\n", name, path, NumAxes, NumButtons);
-    strSafeSet(&c->Name, name); strSafeSet(&c->Path, path); c->fd=device;
-    c->NumAxes = NumAxes; c->NumButtons=NumButtons;
-    c->InternalType = la_IdentifyControllerInternalType(name);
-    lstAppendItem(&MAIN.Controllers,c);
-    c->UserAssignedID=MAIN.NextControllerID; MAIN.NextControllerID++;
-    
+void la_EnsureControllerNames(laController* c){
     laPropContainer*pc=la_EnsureSubTarget(LA_PROP_CONTROLLER,c);
     if(!pc){ return c; }
     for(laProp* p=pc->Props.pFirst;p;p=p->Item.pNext){
@@ -78,12 +77,24 @@ laController* la_NewController(char* name, char* path, int device, int NumAxes,
             }
         }
     }
+}
+
+laController* la_NewController(char* name, char* path, int device, int NumAxes, int NumButtons){
+    laController* c=memAcquire(sizeof(laController));
+    logPrint("Found controller %s\n    at %s\n    with %d axes, %d buttons\n", name, path, NumAxes, NumButtons);
+    strSafeSet(&c->Name, name); strSafeSet(&c->Path, path); c->fd=device;
+    c->NumAxes = NumAxes; c->NumButtons=NumButtons;
+    c->InternalType = la_IdentifyControllerInternalType(name);
+    lstPushItem(&MAIN.Controllers,c);
+    c->UserAssignedID=MAIN.NextControllerID; MAIN.NextControllerID++;
+    
+    la_EnsureControllerNames(c);
 
     return c;
 }
 void la_DestroyController(laController* c){
-    strSafeDestroy(&c->Name); strSafeDestroy(&c->Path);
-    memFree(c); laNotifyUsers("la.controllers");
+    strSafeDestroy(&c->Name); strSafeDestroy(&c->Path); if(c->fd){ close(c->fd); }
+    lstRemoveItem(&MAIN.Controllers,c); laNotifyInstanceUsers(c);  memFree(c); laNotifyUsers("la.controllers");
 }
 
 laController* la_FindControllerWithID(int id){
@@ -185,6 +196,7 @@ void la_UpdateControllerStatus(){
 #ifdef __linux__
     struct input_event event; int HasEvent=0;
     for(laController* c=MAIN.Controllers.pFirst;c;c=c->Item.pNext){ if(c->Error) continue;
+        if(!c->Error && !c->fd){ c->fd=open(SSTR(c->Path), O_RDWR | O_NONBLOCK); if(c->fd<0){ c->Error=1; } continue; }
         int bytes; while((bytes=read(c->fd, &event, sizeof(struct input_event)))>0){
             if(event.type == EV_KEY){
                 int idx = la_ControllerButtonToIndex(c,event.code); if(idx<0) continue;
@@ -194,8 +206,6 @@ void la_UpdateControllerStatus(){
             else if(event.type == EV_ABS){
                 int idx = la_ControllerAxisToIndex(c,event.code); if(idx<0) continue; HasEvent=1;
                 c->AxisValues[idx]=rint(tnsLinearItp(-32768.0f,32767.0f,(((real)event.value-c->AxisMins[idx])/(c->AxisMaxes[idx]-c->AxisMins[idx]))));
-                if(idx>4)
-                    printf("%d %d\n",event.value,idx);
                 if(abs(c->AxisValues[idx]-c->SaveAxisValues[idx])>10000){ c->SaveAxisValues[idx]=c->AxisValues[idx];
                     MAIN.LastControllerAxis=idx; MAIN.LastControllerAxisDevice=c->UserAssignedID; MAIN.ControllerHasNewAxis = 1; laRetriggerOperators();
                 }
@@ -207,11 +217,38 @@ void la_UpdateControllerStatus(){
 #endif
 }
 
+void la_CopyControllerConfig(laController* to, laController* from){
+    strSafeSet(&to->Path,SSTR(from->Path));
+    to->UserAssignedID = from->UserAssignedID;
+    memcpy(to->AxisCenterMaxes,from->AxisCenterMaxes,sizeof(int16_t)*LA_JS_MAX_AXES);
+    memcpy(to->AxisCenterMins,from->AxisCenterMins,sizeof(int16_t)*LA_JS_MAX_AXES);
+    memcpy(to->AxisLimitMaxes,from->AxisLimitMaxes,sizeof(int16_t)*LA_JS_MAX_AXES);
+    memcpy(to->AxisLimitMins,from->AxisLimitMins,sizeof(int16_t)*LA_JS_MAX_AXES);
+    memcpy(to->ButtonsMap,from->ButtonsMap,sizeof(u8bit)*LA_JS_MAX_BUTTONS);
+    memcpy(to->AxisMap,from->AxisMap,sizeof(u8bit)*LA_JS_MAX_AXES);
+}
+
+void la_RemoveDuplicatedControllers(){
+    laController* NextC;
+    for(laController* fc=MAIN.Controllers.pFirst;fc;fc=fc->Item.pNext){
+        if(fc->fd>=0){ close(fc->fd); fc->fd=0; }
+    }
+    for(laController* c=MAIN.Controllers.pFirst;c;c=c->Item.pNext){
+        for(laController* fc=c->Item.pNext;fc;fc=NextC){
+            NextC=fc->Item.pNext;
+            if(strSame(SSTR(fc->Name),SSTR(c->Name))){
+                la_CopyControllerConfig(c,fc); la_EnsureControllerNames(c);
+                la_DestroyController(fc);
+            }
+        }
+    }
+}
+
 void la_RefreshControllers(){
-    laController* c; while(c=lstPopItem(&MAIN.Controllers)){ la_DestroyController(c); } MAIN.NextControllerID=0;
     la_InitControllers();
+    la_RemoveDuplicatedControllers();
 }
-int OPINV_RefreshControllers(){
+int OPINV_RefreshControllers(laOperator* a, laEvent* e){
     la_RefreshControllers();
 #ifdef __linux__
     la_ScanWacomDevices(MAIN.dpy,XIAllDevices);
@@ -220,20 +257,68 @@ int OPINV_RefreshControllers(){
     MAIN.WinTabOpened = 0;
     if(MAIN.CurrentWindow){ la_OpenWacomWinTab(MAIN.CurrentWindow->win); }
 #endif
+    laNotifyUsers("la.controllers");
     return LA_FINISHED;
 }
+int OPINV_RemoveController(laOperator* a, laEvent* e){
+    if(!a->This || !a->This->EndInstance) return LA_FINISHED;
+    laEnableYesNoPanel(0, 0, "Confirm?", "Will remove this controller config.", e->x-200, e->y, 200, e);
+    return LA_RUNNING;
+}
+int OPMOD_RemoveController(laOperator* a, laEvent* e){
+    if(!a->This || !a->This->EndInstance) return LA_FINISHED; laController* c=a->This->EndInstance;
+    if(a->ConfirmData){
+        if(a->ConfirmData->Mode == LA_CONFIRM_OK){
+            la_DestroyController(c); laNotifyUsers("la.controllers");
+        }
+        return LA_FINISHED;
+    }
+    return LA_FINISHED;
+}
+
+void lapost_Controller(laController* c){
+    c->InternalType = la_IdentifyControllerInternalType(SSTR(c->Name));
+    c->fd=open(SSTR(c->Path),O_RDWR | O_NONBLOCK); if(c->fd<0){ c->Error=1; }
+}
+
+void* lagetraw_ControllerButtonMap(laController* c, int* r_size, int* ret_is_copy){
+    int use_size=/*sizeof(int16_t)*LA_JS_MAX_AXES*4 +*/ sizeof(u8bit)*LA_JS_MAX_BUTTONS + sizeof(u8bit)*LA_JS_MAX_AXES;
+    char* data=malloc(use_size); char* p=data;
+    //memcpy(p,c->AxisCenterMaxes,sizeof(int16_t)*LA_JS_MAX_AXES); p+= sizeof(int16_t)*LA_JS_MAX_AXES;
+    //memcpy(p,c->AxisCenterMins,sizeof(int16_t)*LA_JS_MAX_AXES); p+= sizeof(int16_t)*LA_JS_MAX_AXES;
+    //memcpy(p,c->AxisLimitMaxes,sizeof(int16_t)*LA_JS_MAX_AXES); p+= sizeof(int16_t)*LA_JS_MAX_AXES;
+    //memcpy(p,c->AxisLimitMins,sizeof(int16_t)*LA_JS_MAX_AXES); p+= sizeof(int16_t)*LA_JS_MAX_AXES;
+    memcpy(p,c->ButtonsMap,sizeof(u8bit)*LA_JS_MAX_BUTTONS); p+= sizeof(u8bit)*LA_JS_MAX_BUTTONS;
+    memcpy(p,c->AxisMap,sizeof(u8bit)*LA_JS_MAX_AXES); p+= sizeof(u8bit)*LA_JS_MAX_AXES;
+    return data;
+}
+void lasetraw_ControllerButtonMap(laController* c, void* data, int DataSize){
+    int use_size=/*sizeof(int16_t)*LA_JS_MAX_AXES*4 +*/ sizeof(u8bit)*LA_JS_MAX_BUTTONS + sizeof(u8bit)*LA_JS_MAX_AXES;
+    if(DataSize!=use_size){ c->Error=1; return; /* need reinit */ }
+    char* p=data;
+    //memcpy(c->AxisCenterMaxes,p,sizeof(int16_t)*LA_JS_MAX_AXES); p+= sizeof(int16_t)*LA_JS_MAX_AXES;
+    //memcpy(c->AxisCenterMins,p,sizeof(int16_t)*LA_JS_MAX_AXES); p+= sizeof(int16_t)*LA_JS_MAX_AXES;
+    //memcpy(c->AxisLimitMaxes,p,sizeof(int16_t)*LA_JS_MAX_AXES); p+= sizeof(int16_t)*LA_JS_MAX_AXES;
+    //memcpy(c->AxisLimitMins,p,sizeof(int16_t)*LA_JS_MAX_AXES); p+= sizeof(int16_t)*LA_JS_MAX_AXES;
+    memcpy(c->ButtonsMap,p,sizeof(u8bit)*LA_JS_MAX_BUTTONS); p+= sizeof(u8bit)*LA_JS_MAX_BUTTONS;
+    memcpy(c->AxisMap,p,sizeof(u8bit)*LA_JS_MAX_AXES); p+= sizeof(u8bit)*LA_JS_MAX_AXES;
+}
 
 
-void la_AddButtonProp(laPropContainer* pc, char* id, char* name, char* desc, int i, int array_len, char* array_prefix){
+void la_AddButtonProp(laPropContainer* pc, char* id, char* id_map, char* name, char* desc, int i, int array_len, char* array_prefix){
     laProp* p=laAddEnumProperty(pc, id, name, desc, LA_WIDGET_ENUM_HIGHLIGHT,
-        array_prefix,0,0,0,offsetof(laController, ButtonValues[i]),0,0,array_len,0,0,0,0,0,0,LA_READ_ONLY);
+        array_prefix,0,0,0,offsetof(laController, ButtonValues[i]),0,0,array_len,0,0,0,0,0,0,LA_READ_ONLY|LA_UDF_IGNORE);
     laAddEnumItemAs(p,"IDLE", "Idle", "Button is not pressed", 0, 0);
     laAddEnumItemAs(p,"ACTIVE", "Active", "Button is pressed", 1, 0);
     p->ElementBytes=1;
+    laAddIntProperty(pc,id_map,name,desc,0,
+        array_prefix,0,0,0,1,0,0,offsetof(laController, ButtonsMap[i]),0,0,array_len,0,0,0,0,0,0,0,0)->ElementBytes = 1;
 }
-void la_AddAxisProp(laPropContainer* pc, char* id, char* name, char* id_min, char* id_max, char* id_cmin, char* id_cmax, char* desc, int i, int array_len, char* array_prefix){
+void la_AddAxisProp(laPropContainer* pc, char* id, char* name, char* id_min, char* id_max, char* id_cmin, char* id_cmax, char* id_map, char* desc, int i, int array_len, char* array_prefix){
     laAddIntProperty(pc,id,name,desc,array_len>1?LA_WIDGET_VALUE_METER_2D:LA_WIDGET_METER_TYPE2,
-        array_prefix,0,32767,-32768,1,0,0,offsetof(laController, AxisValues[i]),0,0,array_len,0,0,0,0,0,0,0,LA_READ_ONLY)->ElementBytes = 2;
+        array_prefix,0,32767,-32768,1,0,0,offsetof(laController, AxisValues[i]),0,0,array_len,0,0,0,0,0,0,0,LA_READ_ONLY|LA_UDF_IGNORE)->ElementBytes = 2;
+    laAddIntProperty(pc,id_map,name,desc,0,
+        array_prefix,0,0,0,1,0,0,offsetof(laController, AxisMap[i]),0,0,array_len,0,0,0,0,0,0,0,0)->ElementBytes = 1;
     if(id_min) laAddIntProperty(pc,id_min,id_min,desc,0,
         array_prefix,0,-25000,-32768,1,0,0,offsetof(laController, AxisLimitMins[i]),0,0,array_len,0,0,0,0,0,0,0,0)->ElementBytes = 2;
     if(id_max) laAddIntProperty(pc,id_max,id_max,desc,0,
@@ -243,14 +328,20 @@ void la_AddAxisProp(laPropContainer* pc, char* id, char* name, char* id_min, cha
     if(id_cmax) laAddIntProperty(pc,id_cmin,id_cmin,desc,0,
         array_prefix,0,32767,25000,1,0,0,offsetof(laController, AxisCenterMaxes[i]),0,0,array_len,0,0,0,0,0,0,0,0)->ElementBytes = 2;
 }
+
+#define ADD_BUTTON_PROP(pc,id,name,desc,i,array_len,array_prefix) \
+    la_AddButtonProp(pc,id,id "_map",name,desc,i,array_len,array_prefix)
+
+#define ADD_AXIS_PROP(pc,id,name,desc,i,array_len,array_prefix) \
+    la_AddAxisProp(pc,id,name,id "_min",id "_max",id "_cmin",id "_cmax", id "_map",desc,i,array_len,array_prefix)
+
 void la_AddGenericButtonProps(laPropContainer* pc){
-    for(int i=0;i<LA_JS_MAX_BUTTONS;i++){ char* name=LA_JS_BTN_NAMES[i]; la_AddButtonProp(pc,name,name,name,i,0,0); }
+    for(int i=0;i<LA_JS_MAX_BUTTONS;i++){ char* name=LA_JS_BTN_NAMES[i]; la_AddButtonProp(pc,name,LA_JS_BTN_MAP_NAMES[i],name,name,i,0,0); }
 }
 void la_AddGenericAxisProps(laPropContainer* pc){
-    for(int i=0;i<LA_JS_MAX_AXES;i++){ char* name=LA_JS_AXIS_NAMES[i]; la_AddAxisProp(pc,name,name,0,0,0,0,name,i,0,0); }
+    for(int i=0;i<LA_JS_MAX_AXES;i++){ char* name=LA_JS_BTN_NAMES[i]; la_AddAxisProp(pc,name, name, LA_JS_AXIS_LMIN_NAMES[i], LA_JS_AXIS_LMAX_NAMES[i],
+        LA_JS_AXIS_CMIN_NAMES[i],LA_JS_AXIS_CMAX_NAMES[i], LA_JS_AXIS_MAP_NAMES[i], name,i,0,0); }
 }
-#define ADD_AXIS_PROP(pc,id,name,desc,i,array_len,array_prefix) \
-    la_AddAxisProp(pc,id,name,id "_min",id "_max",id "_cmin",id "_cmax",desc,i,array_len,array_prefix)
 
 void laui_GenericJoystick(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context);
 void laui_X56Throttle(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context);
@@ -269,19 +360,27 @@ laPropContainer* laget_ControllerType(laController* c){
 }
 
 void la_RegisterControllerProps(){
-    for(int i=0;i<LA_JS_MAX_AXES;i++){ sprintf(LA_JS_AXIS_NAMES[i],"a%d",i); }
-    for(int i=0;i<LA_JS_MAX_BUTTONS;i++){ sprintf(LA_JS_BTN_NAMES[i],"b%d",i); }
+    for(int i=0;i<LA_JS_MAX_AXES;i++){ sprintf(LA_JS_AXIS_NAMES[i],"a%d",i);
+        sprintf(LA_JS_AXIS_MAP_NAMES[i],"a%d_map",i);
+        sprintf(LA_JS_AXIS_CMAX_NAMES[i],"a%d_cmax",i);
+        sprintf(LA_JS_AXIS_CMIN_NAMES[i],"a%d_cmin",i);
+        sprintf(LA_JS_AXIS_LMAX_NAMES[i],"a%d_max",i);
+        sprintf(LA_JS_AXIS_LMIN_NAMES[i],"a%d_min",i);
+    }
+    for(int i=0;i<LA_JS_MAX_BUTTONS;i++){ sprintf(LA_JS_BTN_NAMES[i],"b%d",i); sprintf(LA_JS_BTN_MAP_NAMES[i],"b%d_map",i); }
 
     laCreateOperatorType("LA_refresh_controllers", "Refresh Controllers", "Look for connected controllers",0,0,0,OPINV_RefreshControllers,0,U'🗘',0);
+    laCreateOperatorType("LA_remove_controller", "Remove Controller", "Remove controller config",0,0,0,OPINV_RemoveController,OPMOD_RemoveController,U'🞫',0);
 
     laPropContainer* pc; laProp* p;
 
-    pc=laAddPropertyContainer("la_controller", "Controller", "A joystick/gamepad controller", U'🕹', laui_GenericJoystick, sizeof(laController), 0,0,1);
+    pc=laAddPropertyContainer("la_controller", "Controller", "A joystick/gamepad controller", U'🕹', laui_GenericJoystick, sizeof(laController), lapost_Controller,0,1);
     LA_PC_JS_GENERIC = pc;
     laAddStringProperty(pc,"name","Name","Name of the controller", LA_WIDGET_STRING_PLAIN,0,0,0,1,offsetof(laController,Name),0,0,0,0,LA_READ_ONLY|LA_AS_IDENTIFIER);
     laAddStringProperty(pc,"path","Path","Device path to the controller", LA_WIDGET_STRING_PLAIN,0,0,0,1,offsetof(laController,Path),0,0,0,0,LA_READ_ONLY);
     laAddIntProperty(pc,"user_assigned_id", "ID", "User assigned ID", 0,0,0,0,0,0,0,0,offsetof(laController, UserAssignedID),0,0,0,0,0,0,0,0,0,0,0);
     laAddIntProperty(pc,"error", "Error", "Device error", 0,0,0,0,0,0,0,0,offsetof(laController, Error),0,0,0,0,0,0,0,0,0,0,0);
+    laAddOperatorProperty(pc,"remove","Remove","Remove this controller config","LA_remove_controller",0,0);
     la_AddGenericButtonProps(pc);
     la_AddGenericAxisProps(pc);
 
@@ -296,34 +395,34 @@ void la_RegisterControllerProps(){
     ADD_AXIS_PROP(pc,"wg","WG","Wheel G (smaller wheel)",5,0,0);
     ADD_AXIS_PROP(pc,"r4","RTY4","Rotary 4",6,0,0);
     ADD_AXIS_PROP(pc,"r3","RTY3","Rotary 3",7,0,0);
-    la_AddButtonProp(pc,"be","E","Button E (thumb flat switch)", 0,0,0);
-    la_AddButtonProp(pc,"bf","F","Button F (big push down switch)", 1,0,0);
-    la_AddButtonProp(pc,"bg","G","Button G (smaller up-side-down switch)", 2,0,0);
-    la_AddButtonProp(pc,"bi","I","Button I (left reverser)", 3,0,0);
-    la_AddButtonProp(pc,"bh","H","Button H (right reverser)", 4,0,0);
-    la_AddButtonProp(pc,"sw1","SW1","Switch 1", 5,0,0);
-    la_AddButtonProp(pc,"sw2","SW2","Switch 2", 6,0,0);
-    la_AddButtonProp(pc,"sw3","SW3","Switch 3", 7,0,0);
-    la_AddButtonProp(pc,"sw4","SW4","Switch 4", 8,0,0);
-    la_AddButtonProp(pc,"sw5","SW5","Switch 5", 9,0,0);
-    la_AddButtonProp(pc,"sw6","SW6","Switch 6", 10,0,0);
-    la_AddButtonProp(pc,"t1_up","T1+","Toggle 1+", 11,0,0);
-    la_AddButtonProp(pc,"t1_dn","T1-","Toggle 1-", 12,0,0);
-    la_AddButtonProp(pc,"t2_up","T2+","Toggle 2+", 13,0,0);
-    la_AddButtonProp(pc,"t2_dn","T2-","Toggle 2-", 14,0,0);
-    la_AddButtonProp(pc,"t3_up","T3+","Toggle 3+", 15,0,0);
-    la_AddButtonProp(pc,"t3_dn","T3-","Toggle 3-", 16,0,0);
-    la_AddButtonProp(pc,"t4_up","T4+","Toggle 4+", 17,0,0);
-    la_AddButtonProp(pc,"t4_dn","T4-","Toggle 4-", 18,0,0);
-    la_AddButtonProp(pc,"h3","H3","Hat 3 (Upper round hat)", 19,4,"N,E,S,W");
-    la_AddButtonProp(pc,"h4","H4","Hat 4 (lower jagged hat)", 23,4,"N,E,S,W");
-    la_AddButtonProp(pc,"pinky_up","P+","Pinky up", 27,0,0);
-    la_AddButtonProp(pc,"pinky_dn","P-","Pinky down", 28,0,0);
-    la_AddButtonProp(pc,"dial_fwd","D+","Dial forward", 29,0,0);
-    la_AddButtonProp(pc,"dial_back","D-","Dial backward", 30,0,0);
-    la_AddButtonProp(pc,"bball","BP","Ball push", 31,0,0);
-    la_AddButtonProp(pc,"slider","SLD","Slider", 32,0,0);
-    la_AddButtonProp(pc,"mode","Mode","Mode switch", 33,3,"M1,M2,S1");
+    ADD_BUTTON_PROP(pc,"be","E","Button E (thumb flat switch)", 0,0,0);
+    ADD_BUTTON_PROP(pc,"bf","F","Button F (big push down switch)", 1,0,0);
+    ADD_BUTTON_PROP(pc,"bg","G","Button G (smaller up-side-down switch)", 2,0,0);
+    ADD_BUTTON_PROP(pc,"bi","I","Button I (left reverser)", 3,0,0);
+    ADD_BUTTON_PROP(pc,"bh","H","Button H (right reverser)", 4,0,0);
+    ADD_BUTTON_PROP(pc,"sw1","SW1","Switch 1", 5,0,0);
+    ADD_BUTTON_PROP(pc,"sw2","SW2","Switch 2", 6,0,0);
+    ADD_BUTTON_PROP(pc,"sw3","SW3","Switch 3", 7,0,0);
+    ADD_BUTTON_PROP(pc,"sw4","SW4","Switch 4", 8,0,0);
+    ADD_BUTTON_PROP(pc,"sw5","SW5","Switch 5", 9,0,0);
+    ADD_BUTTON_PROP(pc,"sw6","SW6","Switch 6", 10,0,0);
+    ADD_BUTTON_PROP(pc,"t1_up","T1+","Toggle 1+", 11,0,0);
+    ADD_BUTTON_PROP(pc,"t1_dn","T1-","Toggle 1-", 12,0,0);
+    ADD_BUTTON_PROP(pc,"t2_up","T2+","Toggle 2+", 13,0,0);
+    ADD_BUTTON_PROP(pc,"t2_dn","T2-","Toggle 2-", 14,0,0);
+    ADD_BUTTON_PROP(pc,"t3_up","T3+","Toggle 3+", 15,0,0);
+    ADD_BUTTON_PROP(pc,"t3_dn","T3-","Toggle 3-", 16,0,0);
+    ADD_BUTTON_PROP(pc,"t4_up","T4+","Toggle 4+", 17,0,0);
+    ADD_BUTTON_PROP(pc,"t4_dn","T4-","Toggle 4-", 18,0,0);
+    ADD_BUTTON_PROP(pc,"h3","H3","Hat 3 (Upper round hat)", 19,4,"N,E,S,W");
+    ADD_BUTTON_PROP(pc,"h4","H4","Hat 4 (lower jagged hat)", 23,4,"N,E,S,W");
+    ADD_BUTTON_PROP(pc,"pinky_up","P+","Pinky up", 27,0,0);
+    ADD_BUTTON_PROP(pc,"pinky_dn","P-","Pinky down", 28,0,0);
+    ADD_BUTTON_PROP(pc,"dial_fwd","D+","Dial forward", 29,0,0);
+    ADD_BUTTON_PROP(pc,"dial_back","D-","Dial backward", 30,0,0);
+    ADD_BUTTON_PROP(pc,"bball","BP","Ball push", 31,0,0);
+    ADD_BUTTON_PROP(pc,"slider","SLD","Slider", 32,0,0);
+    ADD_BUTTON_PROP(pc,"mode","Mode","Mode switch", 33,3,"M1,M2,S1");
 
 
     pc=laAddPropertyContainer("la_controller_x56_stick", "X56 Stick", "X56 Stick", 0,laui_X56Stick,sizeof(laController),0,0,1);
@@ -334,14 +433,14 @@ void la_RegisterControllerProps(){
     ADD_AXIS_PROP(pc,"ball","Ball","Ball stick",2,2,"Left/Right,Up/Down");
     ADD_AXIS_PROP(pc,"rudder","Rudder","Ruder twist",4,0,0);
     ADD_AXIS_PROP(pc,"pov","POV","POV hat",5,2,"Left/Right,Up/Down");
-    la_AddButtonProp(pc,"trigger","Trigger","Trigger", 0,0,0);
-    la_AddButtonProp(pc,"ba","A","Button A", 1,0,0);
-    la_AddButtonProp(pc,"bb","B","Button B (Side of stick)", 2,0,0);
-    la_AddButtonProp(pc,"bball","BP","Ball push", 3,0,0);  la_AddButtonProp(pc,"bc","BC","Button C (ball push)", 3,0,0);
-    la_AddButtonProp(pc,"pinky","PK","Pinky small button", 4,0,0); la_AddButtonProp(pc,"bd","BD","Button D pinky small button", 4,0,0);
-    la_AddButtonProp(pc,"pinkyl","PKL","Pinky lever", 5,0,0);
-    la_AddButtonProp(pc,"h1","H1","Hat 1 (Upper round hat)", 6,4,"N,E,S,W");
-    la_AddButtonProp(pc,"h2","H2","Hat 2 (lower jagged hat)", 10,4,"N,E,S,W");
+    ADD_BUTTON_PROP(pc,"trigger","Trigger","Trigger", 0,0,0);
+    ADD_BUTTON_PROP(pc,"ba","A","Button A", 1,0,0);
+    ADD_BUTTON_PROP(pc,"bb","B","Button B (Side of stick)", 2,0,0);
+    ADD_BUTTON_PROP(pc,"bball","BP","Ball push", 3,0,0);  ADD_BUTTON_PROP(pc,"bc","BC","Button C (ball push)", 3,0,0);
+    ADD_BUTTON_PROP(pc,"pinky","PK","Pinky small button", 4,0,0); ADD_BUTTON_PROP(pc,"bd","BD","Button D pinky small button", 4,0,0);
+    ADD_BUTTON_PROP(pc,"pinkyl","PKL","Pinky lever", 5,0,0);
+    ADD_BUTTON_PROP(pc,"h1","H1","Hat 1 (Upper round hat)", 6,4,"N,E,S,W");
+    ADD_BUTTON_PROP(pc,"h2","H2","Hat 2 (lower jagged hat)", 10,4,"N,E,S,W");
     //button 14-16 not sure where it is....
 }
 
@@ -356,6 +455,7 @@ void laui_GenericJoystick(laUiList *uil, laPropPack *This, laPropPack *Extra, la
         laShowLabel(uil,c,"Device error.",0,0)->Expand=1;
     }laElse(uil,b1);{
         laShowItem(uil,c,This,"path")->Expand=1;
+        laShowItem(uil,c,This,"remove");
     }laEndCondition(uil,b1);
     laEndRow(uil,b);
     laShowSeparator(uil,c);
@@ -403,13 +503,16 @@ void laui_X56Throttle(laUiList *uil, laPropPack *This, laPropPack *Extra, laColu
     laSplitColumn(uil,rc,0.4); rcl=laLeftColumn(rc,2); rcr=laRightColumn(rc,0);
     laUiItem* b,*ui,*g; laUiList*gu;
 
-    laShowItem(uil,c,This,"base.name")->Flags|=LA_TEXT_ALIGN_CENTER;
+    laUiItem* JB=laShowInvisibleItem(uil,c,This,"base");
+
+    laShowItem(uil,c,&JB->PP, "name")->Flags|=LA_TEXT_ALIGN_CENTER;
     b=laBeginRow(uil,c,0,0);
-    laShowItem(uil,c,This,"base.user_assigned_id");
-    laUiItem* b1=laOnConditionThat(uil,c,laPropExpression(This,"base.error"));{
+    laShowItem(uil,c,&JB->PP, "user_assigned_id");
+    laUiItem* b1=laOnConditionThat(uil,c,laPropExpression(&JB->PP, "error"));{
         laShowLabel(uil,c,"Device error.",0,0)->Expand=1;
     }laElse(uil,b1);{
-        laShowItem(uil,c,This,"base.path")->Expand=1;
+        laShowItem(uil,c,&JB->PP, "path")->Expand=1;
+        laShowItem(uil,c,&JB->PP, "remove");
     }laEndCondition(uil,b1);
     laEndRow(uil,b);
     laShowSeparator(uil,c);
@@ -490,13 +593,16 @@ void laui_X56Stick(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn
     laSplitColumn(uil,cr,0.8); cc=laLeftColumn(cr,0); cr=laRightColumn(cr,10);
     laUiItem* b;
 
-    laShowItem(uil,c,This,"base.name")->Flags|=LA_TEXT_ALIGN_CENTER;
+    laUiItem* JB=laShowInvisibleItem(uil,c,This,"base");
+
+    laShowItem(uil,c,&JB->PP, "name")->Flags|=LA_TEXT_ALIGN_CENTER;
     b=laBeginRow(uil,c,0,0);
-    laShowItem(uil,c,This,"base.user_assigned_id");
-    laUiItem* b1=laOnConditionThat(uil,c,laPropExpression(This,"base.error"));{
+    laShowItem(uil,c,&JB->PP, "user_assigned_id");
+    laUiItem* b1=laOnConditionThat(uil,c,laPropExpression(&JB->PP, "error"));{
         laShowLabel(uil,c,"Device error.",0,0)->Expand=1;
     }laElse(uil,b1);{
-        laShowItem(uil,c,This,"base.path")->Expand=1;
+        laShowItem(uil,c,&JB->PP, "path")->Expand=1;
+        laShowItem(uil,c,&JB->PP, "remove");
     }laEndCondition(uil,b1);
     laEndRow(uil,b);
     laShowSeparator(uil,c);

+ 22 - 5
la_interface.h

@@ -330,6 +330,8 @@ STRUCTURE(LA){
     laInputProcessF InputProcess;
     laListHandle Windows;
     laListHandle WastedPanels;
+    laListHandle Screens;
+    int AutoSwitchColorSpace;
 
     laConfirmData *InvokeConfirmData;
     
@@ -624,6 +626,15 @@ STRUCTURE(laKeyMapItem){
     laSafeString *Instructions;
 };
 
+STRUCTURE(laScreen){
+    laListItem Item;
+    laSafeString* Name;
+    laSafeString* Description;
+    int RoughDPI;
+    int ColorSpace;
+    int x,y,w,h;
+};
+
 NEED_STRUCTURE(laLayout);
 NEED_STRUCTURE(laBlock);
 NEED_STRUCTURE(laPanel);
@@ -1476,13 +1487,13 @@ STRUCTURE(laController){
     int fd; //device;
     int NumButtons; int NumAxes; int UserAssignedID; int Error;
     int InternalType; // used to identify models and use specific props.
-    short AxisValues[LA_JS_MAX_AXES],SaveAxisValues[LA_JS_MAX_AXES];
+    int16_t AxisValues[LA_JS_MAX_AXES],SaveAxisValues[LA_JS_MAX_AXES];
     int AxisMaxes[LA_JS_MAX_AXES];
     int AxisMins[LA_JS_MAX_AXES];
-    short AxisLimitMaxes[LA_JS_MAX_AXES]; // calibrated max;
-    short AxisLimitMins[LA_JS_MAX_AXES];
-    short AxisCenterMaxes[LA_JS_MAX_AXES]; // calibrated center;
-    short AxisCenterMins[LA_JS_MAX_AXES];
+    int16_t AxisLimitMaxes[LA_JS_MAX_AXES]; // calibrated max;
+    int16_t AxisLimitMins[LA_JS_MAX_AXES];
+    int16_t AxisCenterMaxes[LA_JS_MAX_AXES]; // calibrated center;
+    int16_t AxisCenterMins[LA_JS_MAX_AXES];
     char ButtonValues[LA_JS_MAX_BUTTONS];
 
     u8bit ButtonsMap[LA_JS_MAX_BUTTONS];
@@ -1491,7 +1502,9 @@ STRUCTURE(laController){
     laSafeString* AxisNames[LA_JS_MAX_AXES];
 };
 
+void la_RefreshControllers();
 void la_InitControllers();
+void la_RemoveDuplicatedControllers();
 void la_UpdateControllerStatus();
 void la_RegisterControllerProps();
 laPropContainer* laget_ControllerType(laController* c);
@@ -2138,9 +2151,11 @@ void laSetWindowCursor(int id);
 void laRenameWindow(laWindow* wnd, char* name);
 
 #ifdef __linux__
+int la_GetDPI(Window* root_win);
 void la_ScanWacomDevices(SYSTEMDISPLAY *display, int deviceid);
 #endif
 #ifdef _WIN32
+int la_GetDPI(HWND win);
 void la_OpenWacomWinTab(HWND hwnd);
 #endif
 
@@ -2174,6 +2189,8 @@ void laRemoveInputMapping(laInputMapping* im);
 laCustomSignal* laNewCustomSignal(char* Name, int Signal);
 void laRemoveCustomSignal(laCustomSignal* cs);
 
+void la_RemoveScreen(laScreen*s);
+laScreen* laGetWindowScreen(laWindow* w);
 laWindow *laDesignWindow(int X, int Y, int W, int H);
 laLayout *laDesignLayout(laWindow *w, char *Title);
 void laDestroyLayout(laWindow *w, laLayout* l);

+ 43 - 2
la_kernel.c

@@ -692,6 +692,40 @@ void logClear(){
     laNotifyUsers("la.logs");
 }
 
+laScreen* la_EnsureScreen(char* Name, int mmw, int mmh, int x, int y, int w, int h,int dpi){ if(!Name || !Name[0]) return 0;
+    for(laScreen* s=MAIN.Screens.pFirst;s;s=s->Item.pNext){
+        if(strSame(SSTR(s->Name),Name)){ s->x=x;s->y=y;s->w=w;s->h=h; return s; }
+    }
+    laScreen* s=memAcquire(sizeof(laScreen)); lstAppendItem(&MAIN.Screens,s);
+    strSafeSet(&s->Name,Name);
+    strSafePrint(&s->Description,"%dmm x %dmm with %dx%d ~%ddpi",mmw,mmh,w,h,dpi); s->RoughDPI=dpi;
+    s->x=x;s->y=y;s->w=w;s->h=h;
+    return s;
+}
+void la_RemoveScreen(laScreen*s){
+    strSafeDestroy(&s->Description); strSafeDestroy(&s->Name);
+    lstRemoveItem(&MAIN.Screens,s); memFree(s);
+}
+void la_RemoveDuplicatedScreenConf(){
+    laScreen* NextS;
+    for(laScreen* s=MAIN.Screens.pFirst;s;s=NextS){
+        NextS = s->Item.pNext;
+        for(laScreen* fs=s->Item.pNext;fs;fs=fs->Item.pNext){
+            if(strSame(SSTR(fs->Name),SSTR(s->Name))){ fs->x=s->x;fs->y=s->y;fs->w=s->w;fs->h=s->h; la_RemoveScreen(s); break; }
+        }
+    }
+}
+laScreen* laGetWindowScreen(laWindow* w){
+    int l,r,u,b; int area=-1; laScreen* maxs=0;
+    for(laScreen* s=MAIN.Screens.pFirst;s;s=s->Item.pNext){
+        l=TNS_MAX2(s->x,w->X); r=TNS_MIN2(s->x+s->w,w->X+w->W);
+        u=TNS_MAX2(s->y,w->Y); b=TNS_MIN2(s->y+s->h,w->Y+w->H);
+        int w=(r-l); if(w<0){w=0;} int h=(b-u); if(h<0){h=0;}
+        int a=w*h; if(a>area){ maxs = s; area = a; }
+    }
+    return maxs;
+}
+
 #ifdef __linux__
 int la_GetDPI(Window* root_win){
     XRRScreenResources *screen;
@@ -708,6 +742,7 @@ int la_GetDPI(Window* root_win){
         crtc=XRRGetCrtcInfo(MAIN.dpy,screen,info->crtc);
         dpi=(real)crtc->width/(real)info->mm_width*25.4;
         logPrint("    CRTC: %d x %d, around %ddpi\n",crtc->width,crtc->height,dpi);
+        laScreen* s=la_EnsureScreen(info->name,info->mm_width,info->mm_height,crtc->x,crtc->y,crtc->width,crtc->height,dpi);
         XRRFreeCrtcInfo(crtc);
         //if(info->connection==RR_Connected){
         //    for (icrtc=0;icrtc<info->ncrtc;icrtc++) {
@@ -1229,7 +1264,7 @@ int laGetReadyWith(laInitArguments* ia){
     laFinalizeOperators();
 
     la_RegisterControllerProps();
-    la_InitControllers();
+    la_RefreshControllers();
     
     la_RegisterBasicNodes();
     tns_RegisterNodes();
@@ -1336,6 +1371,7 @@ void laSaveUserPreferences(){
     laWriteProp(udf,"la.windows");
     laWriteProp(udf,"la.user_preferences");
     laWriteProp(udf,"la.input_mapping");
+    laWriteProp(udf,"la.controllers");
     for(laListItemPointer* lip=MAIN.ExtraPreferencePaths.pFirst;lip;lip=lip->pNext){
         laWriteProp(udf,lip->p);
     }
@@ -1351,6 +1387,8 @@ void laEnsureUserPreferences(){
     while(MAIN.InputMapping->InputMappings.pFirst){ laRemoveInputMapping(MAIN.InputMapping->InputMappings.pFirst); }
     laExtractUDF(udf,0,LA_UDF_MODE_OVERWRITE,0);
     laCloseUDF(udf);
+    la_RemoveDuplicatedScreenConf();
+    la_RemoveDuplicatedControllers();
     laRefreshUDFRegistries();
     //restore forced settings
     if(MAIN.InitArgs.EnableLogStdOut){ MAIN.EnableLogStdOut=1; }
@@ -1529,6 +1567,10 @@ void la_CommandResizeWindow(SYSWINDOW hwnd, int x, int y, int w, int h){
     window->CW = w; window->CH = h;
     window->W = w; window->H = h;
     window->X = x; window->Y = y;
+    if(MAIN.AutoSwitchColorSpace){
+        laScreen* s = laGetWindowScreen(window);
+        if(s){ window->OutputColorSpace = s->ColorSpace; }
+    }
 #endif
     la_UpdateUiPlacement(window);
 }
@@ -2302,7 +2344,6 @@ void la_PanelDefDraw(laWindow *w, laPanel *p, laBoxedTheme *bt){
                 if (DrawState_){ p->Refresh = LA_TAG_RECALC; laRefreshWindow(); }
             }
         }
-
         tnsDrawToScreen();
         tnsViewportWithScissor(0, 0, w->CW, w->CH);
         tnsOrtho(0, w->CW, w->CH, 0, -100, 100);

+ 29 - 0
resources/la_operators.c

@@ -2123,6 +2123,32 @@ int OPINV_TranslationDumpMisMatch(laOperator *a, laEvent *e){
     return LA_FINISHED;
 }
 
+int OPINV_RefreshScreens(laOperator *a, laEvent *e){
+#ifdef __linux__
+    la_GetDPI(MAIN.win);
+#endif
+#ifdef _WIN32
+#endif
+    return LA_FINISHED;
+}
+int OPINV_RemoveScreenConfig(laOperator *a, laEvent *e){
+    if(!a->This || !a->This->EndInstance) return LA_FINISHED;
+    laEnableYesNoPanel(0, 0, "Confirm?", "Will remove this screen entry", e->x, e->y, 200, e);
+    return LA_RUNNING;
+}
+
+int OPMOD_RemoveScreenConfig(laOperator *a, laEvent *e){
+    if(!a->This || !a->This->EndInstance) return LA_FINISHED; laScreen* s=a->This->EndInstance;
+    if(a->ConfirmData){
+        if(a->ConfirmData->Mode == LA_CONFIRM_OK){
+            la_RemoveScreen(s); laNotifyUsers("la.user_preferences.screens");
+        }
+        return LA_FINISHED;
+    }
+    return LA_FINISHED;
+}
+
+
 int OPINV_OpenInternetLink(laOperator *a, laEvent *e){
     char *link = strGetArgumentString(a->ExtraInstructionsP, "link");
 
@@ -2144,6 +2170,9 @@ void la_RegisterBuiltinOperators(){
     laCreateOperatorType("LA_undo", "Undo", "Undo from recorded data state", OPCHK_Undo, 0, 0, OPINV_Undo, 0, U'⮌', LA_ACTUATOR_SYSTEM);
     laCreateOperatorType("LA_redo", "Redo", "Redo using recorded data state", OPCHK_Redo, 0, 0, OPINV_Redo, 0, U'⮎', LA_ACTUATOR_SYSTEM);
 
+    laCreateOperatorType("LA_refresh_screens", "Refresh Screens", "Refresh Screens", 0, 0, 0, OPINV_RefreshScreens, 0, U'⭯', LA_ACTUATOR_SYSTEM);
+    laCreateOperatorType("LA_remove_screen_config", "Remove Screen", "Remove this screen configuration", 0, 0, 0, OPINV_RemoveScreenConfig, OPMOD_RemoveScreenConfig, U'🞫', LA_ACTUATOR_SYSTEM);
+
     laCreateOperatorType("LA_translation_dump", "Dump Untranslated Text", "Dump Untranslated Text To File", 0, 0, 0, OPINV_TranslationDumpMisMatch, 0, U'📥', LA_ACTUATOR_SYSTEM);
     laCreateOperatorType("LA_open_internet_link", "Goto", "Open Internet Link", 0, 0, 0, OPINV_OpenInternetLink, 0, U'🌐', LA_ACTUATOR_SYSTEM);
     laCreateOperatorType("LA_system_paste", "SYSWINDOW Paste", "Generate a syetem paste event",  0, 0, 0, OPINV_SystemPaste, 0, U'📋', LA_ACTUATOR_SYSTEM | LA_ACTUATOR_HIDDEN);

+ 36 - 0
resources/la_properties.c

@@ -540,6 +540,26 @@ void* laset_CurrentAnimationAction(void* unused_a, laAction* c){
     laNotifyUsers("la.animation.current_action");
 }
 
+void laset_AutoSwitchColorSpace(void* unused, int enabled){
+    MAIN.AutoSwitchColorSpace = enabled;
+    if(enabled){
+        for(laWindow* w=MAIN.Windows.pFirst;w;w=w->Item.pNext){
+            laScreen* s = laGetWindowScreen(w);
+            if(s){ w->OutputColorSpace = s->ColorSpace; laNotifyInstanceUsers(w); }
+        }
+    }
+    laNotifyUsers("la.user_preferences.auto_switch_color_space");
+}
+void laset_ScreenColorSpace(laScreen* s, int space){
+    s->ColorSpace=space;
+    if(MAIN.AutoSwitchColorSpace){
+        for(laWindow* w=MAIN.Windows.pFirst;w;w=w->Item.pNext){
+            laScreen* s = laGetWindowScreen(w);
+            if(s){ w->OutputColorSpace = s->ColorSpace; laNotifyInstanceUsers(w); }
+        }
+    }
+}
+
 void laset_WindowColorSpace(laWindow* w, int space){ w->OutputColorSpace=space; if(w->win) laRedrawCurrentWindow(); }
 void laset_WindowShowStripes(laWindow* w, int stripes){ w->OutputShowStripes=stripes; if(w->win) laRedrawCurrentWindow(); }
 void laset_WindowUseComposing(laWindow* w, int comp){ w->UseComposing=comp; if(w->win) laRedrawCurrentWindow(); }
@@ -1745,6 +1765,12 @@ void la_RegisterInternalProps(){
             laAddFloatProperty(p, "viewport_halftone_size", "Halftone Size", "Viewport halftone size", 0,0,0,8.0,1.9, 0.05,3.7, 0,offsetof(LA, ViewportHalftoneSize), 0,laset_ViewportHalftoneSize,0,0,0,0,0,0,0,0,0);
 
             laAddStringProperty(p,"theme_name","Theme Name","Using theme name",0,0,0,0,0,0,0,laget_UsingTheme,0,laread_UsingTheme,0);
+
+            laAddSubGroup(p,"screens","Screens","Screens connected to this computer","la_screen",0,0,0,-1,0,0,0,0,0,0,offsetof(LA,Screens),0);
+            ep = laAddEnumProperty(p, "auto_switch_color_space", "Auto Switch Color Space", "Automatically switch color space for windows on different screens",LA_WIDGET_ENUM_HIGHLIGHT, 0, 0, 0, 0, offsetof(LA, AutoSwitchColorSpace), 0, laset_AutoSwitchColorSpace, 0, 0, 0, 0, 0, 0, 0, 0); {
+                laAddEnumItemAs(ep, "NONE", "None", "Windows keep their own color space", 0, 0);
+                laAddEnumItemAs(ep, "ENABLED", "Enabled", "Automatically switch window's color space based on settings here", 1, 0);
+            }
         }
 
         p = laAddPropertyContainer("la_input_mapping_bundle", "Input Mapping Bundle", "Bundle of input mapping data", 0,0,sizeof(laInputMappingBundle), 0,0,1);{
@@ -1813,6 +1839,16 @@ void la_RegisterInternalProps(){
             laAddIntProperty(p, "is_fullscreen", "Is Fullscreen", "Is the window fullscreen", 0,0,0,0,0,0,0,0,offsetof(laWindow, IsFullScreen),0,0,0,0,0,0,0,0,0,0,LA_READ_ONLY);
         }
 
+        p = laAddPropertyContainer("la_screen","Screen","Screen detected from system",0,0,sizeof(laScreen),0,0,1);{
+            laAddStringProperty(p,"name","Name","Name of the screen",LA_WIDGET_STRING_PLAIN,0,0,0,1,offsetof(laScreen,Name),0,0,0,0,LA_READ_ONLY|LA_AS_IDENTIFIER);
+            laAddStringProperty(p,"description","Description","Desctiption of this screen",LA_WIDGET_STRING_MONO_PLAIN,0,0,0,1,offsetof(laScreen,Description),0,0,0,0,LA_READ_ONLY);
+            ep = laAddEnumProperty(p, "color_space", "Output Color Space", "Output color space of this screen", 0,0,0,0,0,offsetof(laScreen,ColorSpace), 0,laset_ScreenColorSpace, 0,0,0,0,0,0,0,0);{
+                laAddEnumItemAs(ep, "SRGB", "sRGB", "Standard sRGB diplay", TNS_COLOR_SPACE_SRGB, 0);
+                laAddEnumItemAs(ep, "CLAY", "Clay", "Clay color space (AdobeRGB 1998 compatible)", TNS_COLOR_SPACE_CLAY, 0);
+            }
+            laAddOperatorProperty(p,"remove","Remove","Remove this screen config","LA_remove_screen_config",0,0);
+        }
+
         // UI LAYOUT ========================================================================================
 
         p = laAddPropertyContainer("ui_block", "Ui Block", "Property container for single ui block", 0,0, sizeof(laBlock), lapost_Block,0,1);{

+ 79 - 44
resources/la_templates.c

@@ -574,21 +574,26 @@ void laui_DefaultMenuBarActual(laUiList *uil, laPropPack *pp, laPropPack *actins
         }laEndCondition(uil,mui);
 
         laUiItem* uc=laOnConditionThat(uil,c,laPropExpression(0,"la.user_preferences.enable_color_management"));{
-            laShowSeparator(uil,c);
-            laShowItem(uil,c,0,"la.windows.output_color_space");
-            laShowItemFull(uil,c,0,"la.windows.output_show_overflow",0,"text=🟩;",0,0);
-            laShowItemFull(uil,c,0,"la.windows.use_composing",0,"text=☀",0,0);
-            laUiItem* cmp=laOnConditionThat(uil,c,laPropExpression(0,"la.windows.use_composing"));{
-                muil = laMakeMenuPage(uil, c, "⯆");{ mc = laFirstColumn(muil);
-                    laUiItem*b,*ui;
-                    b=laBeginRow(muil,mc,0,0); laShowLabel(muil,mc,"γ",0,0);
-                    ui=laShowItemFull(muil, mc, 0, "la.windows.composing_gamma",0,"text=Gamma",0,0); ui->Expand=1;
-                    laShowItem(muil,mc,&ui->PP,"restore")->Flags|=LA_UI_FLAGS_NO_CONFIRM|LA_UI_FLAGS_ICON; laEndRow(muil,b);
-                    b=laBeginRow(muil,mc,0,0); laShowLabel(muil,mc,"◩",0,0);
-                    ui=laShowItemFull(muil, mc, 0, "la.windows.composing_blackpoint",0,"text=Blackpoint",0,0); ui->Expand=1;
-                    laShowItem(muil,mc,&ui->PP,"restore")->Flags|=LA_UI_FLAGS_NO_CONFIRM|LA_UI_FLAGS_ICON; laEndRow(muil,b);
-                }
-            }laEndCondition(uil, cmp);
+                laShowSeparator(uil,c);
+                laShowItemFull(uil,c,0,"la.user_preferences.auto_switch_color_space",0,"icon=A",0,0)->Flags|=LA_UI_FLAGS_ICON|LA_UI_FLAGS_CYCLE|LA_UI_FLAGS_HIGHLIGHT;
+                laUiItem* uc1=laOnConditionThat(uil,c,laPropExpression(0,"la.user_preferences.auto_switch_color_space"));{
+                    laShowItem(uil,c,0,"la.windows.output_color_space")->Flags|=LA_UI_FLAGS_PLAIN;
+                }laElse(uil,uc1);{
+                    laShowItem(uil,c,0,"la.windows.output_color_space");
+                }laEndCondition(uil, uc1);
+                laShowItemFull(uil,c,0,"la.windows.output_show_overflow",0,"text=🟩;",0,0);
+                laShowItemFull(uil,c,0,"la.windows.use_composing",0,"text=☀",0,0);
+                laUiItem* cmp=laOnConditionThat(uil,c,laPropExpression(0,"la.windows.use_composing"));{
+                    muil = laMakeMenuPage(uil, c, "⯆");{ mc = laFirstColumn(muil);
+                        laUiItem*b,*ui;
+                        b=laBeginRow(muil,mc,0,0); laShowLabel(muil,mc,"γ",0,0);
+                        ui=laShowItemFull(muil, mc, 0, "la.windows.composing_gamma",0,"text=Gamma",0,0); ui->Expand=1;
+                        laShowItem(muil,mc,&ui->PP,"restore")->Flags|=LA_UI_FLAGS_NO_CONFIRM|LA_UI_FLAGS_ICON; laEndRow(muil,b);
+                        b=laBeginRow(muil,mc,0,0); laShowLabel(muil,mc,"◩",0,0);
+                        ui=laShowItemFull(muil, mc, 0, "la.windows.composing_blackpoint",0,"text=Blackpoint",0,0); ui->Expand=1;
+                        laShowItem(muil,mc,&ui->PP,"restore")->Flags|=LA_UI_FLAGS_NO_CONFIRM|LA_UI_FLAGS_ICON; laEndRow(muil,b);
+                    }
+                }laEndCondition(uil, cmp);
         }laEndCondition(uil, uc);
 
         if(MAIN.MenuExtras){ laShowSeparator(uil,c); MAIN.MenuExtras(uil,0,0,0,0); }
@@ -1194,6 +1199,19 @@ void laui_NodeCategory(laUiList *uil, laPropPack *This, laPropPack *Extra, laCol
     }
 }
 
+void laui_Screen(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context){
+    laColumn* c=laFirstColumn(uil); laSplitColumn(uil,c,0.25); laColumn*cl,*cr; cl=laLeftColumn(c,1); cr=laRightColumn(c,0);
+
+    laUiItem* b=laBeginRow(uil,cr,0,0);
+    laShowItem(uil,cr,This,"color_space");
+    laShowItem(uil,cr,This,"name")->Expand=1;
+    laShowItem(uil,cr,This,"remove")->Flags|=LA_UI_FLAGS_ICON;
+    laEndRow(uil,b);
+    b=laOnConditionToggle(uil,cl,0,0,0,0,0);
+    laShowItem(uil,cr,This,"description");
+    laEndCondition(uil,b);
+}
+
 void laui_UserPreference(laUiList *uil, laPropPack *Base, laPropPack *OperatorInst, laColumn *ExtraColumns, int context){
     laColumn* c = laFirstColumn(uil),*cl,*cr; laSplitColumn(uil,c,0.5);cl=laLeftColumn(c,0);cr=laRightColumn(c,0);
     laUiList *muil;
@@ -1213,12 +1231,16 @@ void laui_UserPreference(laUiList *uil, laPropPack *Base, laPropPack *OperatorIn
     laEndRow(uil,b);
     laShowItem(uil, cr, 0, "LA_save_user_preferences")->Expand = 1;
 
+
+    laUiItem* UP = laShowInvisibleItem(uil,c,0,"la.user_preferences");
+    
     bracket = laMakeTab(uil, c, 0);{
 
         for(laExtraPreferencePage* epp=MAIN.ExtraPreferencePages.pFirst;epp;epp=epp->Item.pNext){
             muil = laAddTabPage(bracket, epp->Name); epp->Func(muil,0,0,0,0);
         }
 
+        
         muil = laAddTabPage(bracket, "Display");{
             //muil->HeightCoeff = -1;
             mc = laFirstColumn(muil);
@@ -1230,47 +1252,58 @@ void laui_UserPreference(laUiList *uil, laPropPack *Base, laPropPack *OperatorIn
 
             laShowLabel(muil, mc, "Interface:", 0, 0);
             laShowLabel(muil, mcl, "Row Height:", 0, 0);
-            laShowItem(muil, mcr, 0, "la.user_preferences.interface_size");
+            laShowItem(muil, mcr, &UP->PP, "interface_size");
             laShowLabel(muil, mcl, "Font Size:", 0, 0);
-            laShowItem(muil, mcr, 0, "la.user_preferences.font_size");
+            laShowItem(muil, mcr, &UP->PP, "font_size");
             laShowLabel(muil, mcl, "Margin Size:", 0, 0);
-            laShowItem(muil, mcr, 0, "la.user_preferences.margin_size");
+            laShowItem(muil, mcr, &UP->PP, "margin_size");
             laShowLabel(muil, mcl, "Panel Multisample:", 0, 0);
-            laShowItem(muil, mcr, 0, "la.user_preferences.panel_multisample")->Flags|=LA_UI_FLAGS_EXPAND;
+            laShowItem(muil, mcr, &UP->PP, "panel_multisample")->Flags|=LA_UI_FLAGS_EXPAND;
             laShowLabel(muil, mcl, "Top Framerate:", 0, 0);
-            laShowItem(muil, mc, 0, "la.user_preferences.top_framerate");
+            laShowItem(muil, mc, &UP->PP, "top_framerate");
 
             laShowSeparator(muil, mc);
 
             laShowLabel(muil, mc, "Color:", 0, 0);
-            laShowItem(muil, mcl, 0, "la.user_preferences.color_picker_gamma");
-            laShowItem(muil, mcr, 0, "la.user_preferences.enable_color_management");
+            laShowItem(muil, mcl, &UP->PP, "color_picker_gamma");
+            laShowItem(muil, mcr, &UP->PP, "enable_color_management");
+
+            laUiItem* bb=laOnConditionThat(muil,mc,laPropExpression(&UP->PP,"enable_color_management"));{
+                laUiItem*bbr=laBeginRow(muil,mc,0,0);
+                laShowLabel(muil, mcl, "Per screen config:", 0, 0)->Expand=1;
+                laShowItem(muil,mc,0,"LA_refresh_screens")->Flags|=LA_UI_FLAGS_ICON;
+                laShowItemFull(muil, mcr, &UP->PP, "auto_switch_color_space",0,"text=Auto Switch",0,0);
+                laEndRow(muil,bbr);
+                laShowItemFull(muil, mc, &UP->PP, "screens",0,0,laui_Screen,0);
+            }laEndCondition(muil,bb);
+
+            laShowSeparator(muil, mc);
 
             laShowLabel(muil, mc, "Viewport:", 0, 0);
-            laShowItem(muil, mcl, 0, "la.user_preferences.viewport_halftone_factor");
-            laShowItem(muil, mcr, 0, "la.user_preferences.viewport_halftone_size");
+            laShowItem(muil, mcl, &UP->PP, "viewport_halftone_factor");
+            laShowItem(muil, mcr, &UP->PP, "viewport_halftone_size");
 
             laShowSeparator(muil, mc);
 
             laShowLabel(muil, mc, "Nodes:", 0, 0);
-            laShowItem(muil, mcr, 0, "la.user_preferences.wire_color_slices");
-            laShowItem(muil, mcr, 0, "la.user_preferences.wire_thickness");
-            laShowItem(muil, mcr, 0, "la.user_preferences.wire_saggyness");
+            laShowItem(muil, mcr, &UP->PP, "wire_color_slices");
+            laShowItem(muil, mcr, &UP->PP, "wire_thickness");
+            laShowItem(muil, mcr, &UP->PP, "wire_saggyness");
 
             laShowSeparator(muil, mc);
 
             laShowLabel(muil, mc, "Floating Panel:", 0, 0);
-            laShowItem(muil, mcl, 0, "la.user_preferences.floating_alpha");
-            laShowItem(muil, mcr, 0, "la.user_preferences.solid_shadow_length");
+            laShowItem(muil, mcl, &UP->PP, "floating_alpha");
+            laShowItem(muil, mcr, &UP->PP, "solid_shadow_length");
 
             laShowSeparator(muil, mc);
 
             laShowLabel(muil, mc, "Translation:", 0, 0);
             laShowLabel(muil, mcl, "Enable Translation:", 0, 0);
-            laShowItemFull(muil, mcr, 0, "la.user_preferences.enable_translation",LA_WIDGET_ENUM_CYCLE,0,0,0);
-            b = laOnConditionThat(muil, mcl, laPropExpression(0, "la.user_preferences.enable_translation"));{
+            laShowItemFull(muil, mcr, &UP->PP, "enable_translation",LA_WIDGET_ENUM_CYCLE,0,0,0);
+            b = laOnConditionThat(muil, mcl, laPropExpression(&UP->PP, "enable_translation"));{
             laShowLabel(muil, mcl, "Language:", 0, 0);
-                laShowItemFull(muil, mcr, 0, "la.user_preferences.languages", LA_WIDGET_COLLECTION_SELECTOR, 0, 0, 0);
+                laShowItemFull(muil, mcr, &UP->PP, "languages", LA_WIDGET_COLLECTION_SELECTOR, 0, 0, 0);
                 laShowItem(muil, mcl, 0, "LA_translation_dump");
             }
             laEndCondition(muil, b);
@@ -1286,13 +1319,13 @@ void laui_UserPreference(laUiList *uil, laPropPack *Base, laPropPack *OperatorIn
             if(MAIN.PreferencePageInput){ MAIN.PreferencePageInput(muil,0,0,0,0); }
 
             laShowLabel(muil, mc, "User Interactions:", 0, 0);
-            laShowItem(muil, mc, 0, "la.user_preferences.scroll_speed");
-            laShowItem(muil, mcl, 0, "la.user_preferences.animation_speed");
-            laShowItem(muil, mcr, 0, "la.user_preferences.panel_animation_speed");
-            laShowItem(muil, mc, 0, "la.user_preferences.valuator_threshold");
-            laShowItem(muil, mc, 0, "la.user_preferences.zoom_speed_2d");
-            laShowItem(muil, mcl, 0, "la.user_preferences.tooltip_close_distance");
-            laShowItem(muil, mcr, 0, "la.user_preferences.idle_time");
+            laShowItem(muil, mc, &UP->PP, "scroll_speed");
+            laShowItem(muil, mcl, &UP->PP, "animation_speed");
+            laShowItem(muil, mcr, &UP->PP, "panel_animation_speed");
+            laShowItem(muil, mc, &UP->PP, "valuator_threshold");
+            laShowItem(muil, mc, &UP->PP, "zoom_speed_2d");
+            laShowItem(muil, mcl, &UP->PP, "tooltip_close_distance");
+            laShowItem(muil, mcr, &UP->PP, "idle_time");
 
             laShowSeparator(muil,mc);
 
@@ -1313,10 +1346,10 @@ void laui_UserPreference(laUiList *uil, laPropPack *Base, laPropPack *OperatorIn
             laShowLabel(muil, mcl, "Driver:", 0, 0)->Flags|=LA_TEXT_ALIGN_RIGHT;
             laShowItem(muil, mcl, 0, "LA_refresh_controllers")->Flags|=LA_UI_FLAGS_ICON;
             laEndRow(muil, b);
-            laShowItem(muil, mcr, 0, "la.user_preferences.wacom_driver");
+            laShowItem(muil, mcr, &UP->PP, "wacom_driver");
 #else
-            laShowItem(muil, mcl, 0, "la.user_preferences.wacom_device_stylus");
-            laShowItem(muil, mcl, 0, "la.user_preferences.wacom_device_eraser");
+            laShowItem(muil, mcl, &UP->PP, "wacom_device_stylus");
+            laShowItem(muil, mcl, &UP->PP, "wacom_device_eraser");
             laShowItemFull(muil, mcr, 0, "LA_refresh_controllers",0,"text=Refresh",0,0);
 #endif
         }
@@ -1341,7 +1374,7 @@ void laui_UserPreference(laUiList *uil, laPropPack *Base, laPropPack *OperatorIn
             if(MAIN.PreferencePageResource){ MAIN.PreferencePageResource(muil,0,0,0,0); }
 
             laShowLabel(muil, mcl, "UDF Manager Default View:", 0, 0);
-            laShowItem(muil, mcr, 0, "la.user_preferences.manager_default_view")->Flags|=LA_UI_FLAGS_EXPAND;
+            laShowItem(muil, mcr, &UP->PP, "manager_default_view")->Flags|=LA_UI_FLAGS_EXPAND;
 
             laShowSeparator(muil,mc);
 
@@ -1349,7 +1382,7 @@ void laui_UserPreference(laUiList *uil, laPropPack *Base, laPropPack *OperatorIn
             laShowLabel(muil, mc, "Resource Folders", 0, 0)->Expand=1;
             laShowItem(muil, mc, 0, "LA_add_resource_folder");
             laEndRow(muil,b);
-            laShowItem(muil, mc, 0, "la.user_preferences.resource_folders");
+            laShowItem(muil, mc, &UP->PP, "resource_folders");
         }
 
         muil = laAddTabPage(bracket, "Theme");{
@@ -1528,6 +1561,8 @@ void lauidetached_TextureInspector(laPanel* p){
 void laui_GameController(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context){
     laColumn* c=laFirstColumn(uil),*cl, *cr;
     laSplitColumn(uil,c,0.5); cl=laLeftColumn(c,0); cr=laRightColumn(c,0);
+
+    laShowInvisibleItem(uil,c,0,"la.controllers");
     
     laUiItem* b=laBeginRow(uil,cl,0,0);
     laUiItem* l=laShowLabel(uil,cl,"Controller:",0,0); l->Flags|=LA_TEXT_ALIGN_RIGHT; l->Expand=1;

+ 2 - 1
resources/la_widgets.c

@@ -247,9 +247,10 @@ int la_EnumGetMinWidth(laUiItem *ui){
     int IsCycle = ui->Flags&LA_UI_FLAGS_CYCLE;
     int IsIcon = ui->Flags&LA_UI_FLAGS_ICON;
     int Highlight=ui->Flags&LA_UI_FLAGS_HIGHLIGHT;
+    int NoDecal=ui->Flags&LA_UI_FLAGS_NO_DECAL;
     int SharedWidth;
     if(!IsIcon){
-        SharedWidth = bt->LM + bt->RM + ((IsCycle||IsExpand)?0:LA_RH); int HasIcon=0;
+        SharedWidth = bt->LM + bt->RM + ((IsCycle||IsExpand||NoDecal)?0:LA_RH); int HasIcon=0;
         if(Highlight){ int ico=0;
             if (ui->ExtraInstructions){
                 if (ui->Type->OperatorType->ParseArgs){