Skip to content

Commit 8c3b5e5

Browse files
committed
feat: add input device support
Add support for libvirt input devices (tablet, mouse, keyboard, etc.) including driver options and source configuration (passthrough/evdev). Resolves: #1148
1 parent f5e8419 commit 8c3b5e5

File tree

3 files changed

+529
-0
lines changed

3 files changed

+529
-0
lines changed

internal/provider/domain_conversion.go

Lines changed: 361 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1151,6 +1151,122 @@ func domainModelToXML(ctx context.Context, client *libvirt.Client, model *Domain
11511151
domain.Devices.TPMs = append(domain.Devices.TPMs, tpm)
11521152
}
11531153
}
1154+
1155+
// Process Inputs
1156+
if !devices.Inputs.IsNull() && !devices.Inputs.IsUnknown() {
1157+
var inputs []DomainInputModel
1158+
diags := devices.Inputs.ElementsAs(ctx, &inputs, false)
1159+
if diags.HasError() {
1160+
return nil, fmt.Errorf("failed to extract inputs: %v", diags.Errors())
1161+
}
1162+
1163+
for _, inputModel := range inputs {
1164+
input := libvirtxml.DomainInput{}
1165+
1166+
// Set type (required)
1167+
if !inputModel.Type.IsNull() && !inputModel.Type.IsUnknown() {
1168+
input.Type = inputModel.Type.ValueString()
1169+
}
1170+
1171+
// Set bus (optional)
1172+
if !inputModel.Bus.IsNull() && !inputModel.Bus.IsUnknown() {
1173+
input.Bus = inputModel.Bus.ValueString()
1174+
}
1175+
1176+
// Set model (optional)
1177+
if !inputModel.Model.IsNull() && !inputModel.Model.IsUnknown() {
1178+
input.Model = inputModel.Model.ValueString()
1179+
}
1180+
1181+
// Set driver (optional nested object)
1182+
if !inputModel.Driver.IsNull() && !inputModel.Driver.IsUnknown() {
1183+
var driver DomainInputDriverModel
1184+
diags := inputModel.Driver.As(ctx, &driver, basetypes.ObjectAsOptions{})
1185+
if diags.HasError() {
1186+
return nil, fmt.Errorf("failed to extract input driver: %v", diags.Errors())
1187+
}
1188+
1189+
inputDriver := &libvirtxml.DomainInputDriver{}
1190+
1191+
if !driver.IOMMU.IsNull() && !driver.IOMMU.IsUnknown() {
1192+
inputDriver.IOMMU = driver.IOMMU.ValueString()
1193+
}
1194+
1195+
if !driver.ATS.IsNull() && !driver.ATS.IsUnknown() {
1196+
inputDriver.ATS = driver.ATS.ValueString()
1197+
}
1198+
1199+
if !driver.Packed.IsNull() && !driver.Packed.IsUnknown() {
1200+
inputDriver.Packed = driver.Packed.ValueString()
1201+
}
1202+
1203+
if !driver.PagePerVQ.IsNull() && !driver.PagePerVQ.IsUnknown() {
1204+
inputDriver.PagePerVQ = driver.PagePerVQ.ValueString()
1205+
}
1206+
1207+
input.Driver = inputDriver
1208+
}
1209+
1210+
// Set source (optional nested object)
1211+
if !inputModel.Source.IsNull() && !inputModel.Source.IsUnknown() {
1212+
var source DomainInputSourceModel
1213+
diags := inputModel.Source.As(ctx, &source, basetypes.ObjectAsOptions{})
1214+
if diags.HasError() {
1215+
return nil, fmt.Errorf("failed to extract input source: %v", diags.Errors())
1216+
}
1217+
1218+
inputSource := &libvirtxml.DomainInputSource{}
1219+
1220+
// Handle passthrough variant
1221+
if !source.Passthrough.IsNull() && !source.Passthrough.IsUnknown() {
1222+
var passthrough DomainInputSourcePassthroughModel
1223+
diags := source.Passthrough.As(ctx, &passthrough, basetypes.ObjectAsOptions{})
1224+
if diags.HasError() {
1225+
return nil, fmt.Errorf("failed to extract input source passthrough: %v", diags.Errors())
1226+
}
1227+
1228+
if !passthrough.EVDev.IsNull() && !passthrough.EVDev.IsUnknown() {
1229+
inputSource.Passthrough = &libvirtxml.DomainInputSourcePassthrough{
1230+
EVDev: passthrough.EVDev.ValueString(),
1231+
}
1232+
}
1233+
}
1234+
1235+
// Handle evdev variant
1236+
if !source.EVDev.IsNull() && !source.EVDev.IsUnknown() {
1237+
var evdev DomainInputSourceEVDevModel
1238+
diags := source.EVDev.As(ctx, &evdev, basetypes.ObjectAsOptions{})
1239+
if diags.HasError() {
1240+
return nil, fmt.Errorf("failed to extract input source evdev: %v", diags.Errors())
1241+
}
1242+
1243+
inputSourceEVDev := &libvirtxml.DomainInputSourceEVDev{}
1244+
1245+
if !evdev.Dev.IsNull() && !evdev.Dev.IsUnknown() {
1246+
inputSourceEVDev.Dev = evdev.Dev.ValueString()
1247+
}
1248+
1249+
if !evdev.Grab.IsNull() && !evdev.Grab.IsUnknown() {
1250+
inputSourceEVDev.Grab = evdev.Grab.ValueString()
1251+
}
1252+
1253+
if !evdev.GrabToggle.IsNull() && !evdev.GrabToggle.IsUnknown() {
1254+
inputSourceEVDev.GrabToggle = evdev.GrabToggle.ValueString()
1255+
}
1256+
1257+
if !evdev.Repeat.IsNull() && !evdev.Repeat.IsUnknown() {
1258+
inputSourceEVDev.Repeat = evdev.Repeat.ValueString()
1259+
}
1260+
1261+
inputSource.EVDev = inputSourceEVDev
1262+
}
1263+
1264+
input.Source = inputSource
1265+
}
1266+
1267+
domain.Devices.Inputs = append(domain.Devices.Inputs, input)
1268+
}
1269+
}
11541270
}
11551271

11561272
return domain, nil
@@ -2568,6 +2684,216 @@ func xmlToDomainModel(ctx context.Context, domain *libvirtxml.Domain, model *Dom
25682684
}
25692685
}
25702686

2687+
// Process Inputs
2688+
inputsType := types.ListType{
2689+
ElemType: types.ObjectType{
2690+
AttrTypes: map[string]attr.Type{
2691+
"type": types.StringType,
2692+
"bus": types.StringType,
2693+
"model": types.StringType,
2694+
"driver": types.ObjectType{
2695+
AttrTypes: map[string]attr.Type{
2696+
"iommu": types.StringType,
2697+
"ats": types.StringType,
2698+
"packed": types.StringType,
2699+
"page_per_vq": types.StringType,
2700+
},
2701+
},
2702+
"source": types.ObjectType{
2703+
AttrTypes: map[string]attr.Type{
2704+
"passthrough": types.ObjectType{
2705+
AttrTypes: map[string]attr.Type{
2706+
"evdev": types.StringType,
2707+
},
2708+
},
2709+
"evdev": types.ObjectType{
2710+
AttrTypes: map[string]attr.Type{
2711+
"dev": types.StringType,
2712+
"grab": types.StringType,
2713+
"grab_toggle": types.StringType,
2714+
"repeat": types.StringType,
2715+
},
2716+
},
2717+
},
2718+
},
2719+
},
2720+
},
2721+
}
2722+
inputsObjType, ok := inputsType.ElemType.(types.ObjectType)
2723+
if !ok {
2724+
diags.AddError("Type Error", "Expected inputsType.ElemType to be ObjectType")
2725+
return diags
2726+
}
2727+
inputsList := types.ListNull(inputsObjType)
2728+
2729+
if !model.Devices.IsNull() && !model.Devices.IsUnknown() {
2730+
var existingDevices DomainDevicesModel
2731+
diags.Append(model.Devices.As(ctx, &existingDevices, basetypes.ObjectAsOptions{})...)
2732+
2733+
if !existingDevices.Inputs.IsNull() && !existingDevices.Inputs.IsUnknown() && len(domain.Devices.Inputs) > 0 {
2734+
// Get the count of user-configured inputs
2735+
var userInputs []DomainInputModel
2736+
diags.Append(existingDevices.Inputs.ElementsAs(ctx, &userInputs, false)...)
2737+
userInputCount := len(userInputs)
2738+
2739+
// Only read back the number of inputs the user configured (libvirt adds defaults)
2740+
inputsToRead := domain.Devices.Inputs
2741+
if len(inputsToRead) > userInputCount {
2742+
inputsToRead = inputsToRead[:userInputCount]
2743+
}
2744+
2745+
inputs := make([]DomainInputModel, 0, len(inputsToRead))
2746+
for _, input := range inputsToRead {
2747+
inputModel := DomainInputModel{
2748+
Driver: types.ObjectNull(map[string]attr.Type{
2749+
"iommu": types.StringType,
2750+
"ats": types.StringType,
2751+
"packed": types.StringType,
2752+
"page_per_vq": types.StringType,
2753+
}),
2754+
Source: types.ObjectNull(map[string]attr.Type{
2755+
"passthrough": types.ObjectType{
2756+
AttrTypes: map[string]attr.Type{
2757+
"evdev": types.StringType,
2758+
},
2759+
},
2760+
"evdev": types.ObjectType{
2761+
AttrTypes: map[string]attr.Type{
2762+
"dev": types.StringType,
2763+
"grab": types.StringType,
2764+
"grab_toggle": types.StringType,
2765+
"repeat": types.StringType,
2766+
},
2767+
},
2768+
}),
2769+
}
2770+
2771+
if input.Type != "" {
2772+
inputModel.Type = types.StringValue(input.Type)
2773+
}
2774+
2775+
if input.Bus != "" {
2776+
inputModel.Bus = types.StringValue(input.Bus)
2777+
}
2778+
2779+
if input.Model != "" {
2780+
inputModel.Model = types.StringValue(input.Model)
2781+
}
2782+
2783+
// Process driver
2784+
if input.Driver != nil && (input.Driver.IOMMU != "" || input.Driver.ATS != "" || input.Driver.Packed != "" || input.Driver.PagePerVQ != "") {
2785+
driverModel := DomainInputDriverModel{}
2786+
2787+
if input.Driver.IOMMU != "" {
2788+
driverModel.IOMMU = types.StringValue(input.Driver.IOMMU)
2789+
}
2790+
2791+
if input.Driver.ATS != "" {
2792+
driverModel.ATS = types.StringValue(input.Driver.ATS)
2793+
}
2794+
2795+
if input.Driver.Packed != "" {
2796+
driverModel.Packed = types.StringValue(input.Driver.Packed)
2797+
}
2798+
2799+
if input.Driver.PagePerVQ != "" {
2800+
driverModel.PagePerVQ = types.StringValue(input.Driver.PagePerVQ)
2801+
}
2802+
2803+
driverObj, d := types.ObjectValueFrom(ctx, map[string]attr.Type{
2804+
"iommu": types.StringType,
2805+
"ats": types.StringType,
2806+
"packed": types.StringType,
2807+
"page_per_vq": types.StringType,
2808+
}, driverModel)
2809+
diags.Append(d...)
2810+
if !diags.HasError() {
2811+
inputModel.Driver = driverObj
2812+
}
2813+
}
2814+
2815+
// Process source
2816+
if input.Source != nil && (input.Source.Passthrough != nil || input.Source.EVDev != nil) {
2817+
sourceModel := DomainInputSourceModel{}
2818+
2819+
if input.Source.Passthrough != nil {
2820+
passthroughModel := DomainInputSourcePassthroughModel{}
2821+
if input.Source.Passthrough.EVDev != "" {
2822+
passthroughModel.EVDev = types.StringValue(input.Source.Passthrough.EVDev)
2823+
}
2824+
2825+
passthroughObj, d := types.ObjectValueFrom(ctx, map[string]attr.Type{
2826+
"evdev": types.StringType,
2827+
}, passthroughModel)
2828+
diags.Append(d...)
2829+
if !diags.HasError() {
2830+
sourceModel.Passthrough = passthroughObj
2831+
}
2832+
}
2833+
2834+
if input.Source.EVDev != nil {
2835+
evdevModel := DomainInputSourceEVDevModel{}
2836+
2837+
if input.Source.EVDev.Dev != "" {
2838+
evdevModel.Dev = types.StringValue(input.Source.EVDev.Dev)
2839+
}
2840+
2841+
if input.Source.EVDev.Grab != "" {
2842+
evdevModel.Grab = types.StringValue(input.Source.EVDev.Grab)
2843+
}
2844+
2845+
if input.Source.EVDev.GrabToggle != "" {
2846+
evdevModel.GrabToggle = types.StringValue(input.Source.EVDev.GrabToggle)
2847+
}
2848+
2849+
if input.Source.EVDev.Repeat != "" {
2850+
evdevModel.Repeat = types.StringValue(input.Source.EVDev.Repeat)
2851+
}
2852+
2853+
evdevObj, d := types.ObjectValueFrom(ctx, map[string]attr.Type{
2854+
"dev": types.StringType,
2855+
"grab": types.StringType,
2856+
"grab_toggle": types.StringType,
2857+
"repeat": types.StringType,
2858+
}, evdevModel)
2859+
diags.Append(d...)
2860+
if !diags.HasError() {
2861+
sourceModel.EVDev = evdevObj
2862+
}
2863+
}
2864+
2865+
sourceObj, d := types.ObjectValueFrom(ctx, map[string]attr.Type{
2866+
"passthrough": types.ObjectType{
2867+
AttrTypes: map[string]attr.Type{
2868+
"evdev": types.StringType,
2869+
},
2870+
},
2871+
"evdev": types.ObjectType{
2872+
AttrTypes: map[string]attr.Type{
2873+
"dev": types.StringType,
2874+
"grab": types.StringType,
2875+
"grab_toggle": types.StringType,
2876+
"repeat": types.StringType,
2877+
},
2878+
},
2879+
}, sourceModel)
2880+
diags.Append(d...)
2881+
if !diags.HasError() {
2882+
inputModel.Source = sourceObj
2883+
}
2884+
}
2885+
2886+
inputs = append(inputs, inputModel)
2887+
}
2888+
2889+
inputsList, d = types.ListValueFrom(ctx, inputsObjType, inputs)
2890+
diags.Append(d...)
2891+
if diags.HasError() {
2892+
return diags
2893+
}
2894+
}
2895+
}
2896+
25712897
newDevices := DomainDevicesModel{
25722898
Disks: disksList,
25732899
Interfaces: interfacesList,
@@ -2579,6 +2905,7 @@ func xmlToDomainModel(ctx context.Context, domain *libvirtxml.Domain, model *Dom
25792905
Serials: serialsList,
25802906
RNGs: rngsList,
25812907
TPMs: tpmsList,
2908+
Inputs: inputsList,
25822909
}
25832910

25842911
// Create the devices object
@@ -2699,6 +3026,40 @@ func xmlToDomainModel(ctx context.Context, domain *libvirtxml.Domain, model *Dom
26993026
},
27003027
},
27013028
},
3029+
"inputs": types.ListType{
3030+
ElemType: types.ObjectType{
3031+
AttrTypes: map[string]attr.Type{
3032+
"type": types.StringType,
3033+
"bus": types.StringType,
3034+
"model": types.StringType,
3035+
"driver": types.ObjectType{
3036+
AttrTypes: map[string]attr.Type{
3037+
"iommu": types.StringType,
3038+
"ats": types.StringType,
3039+
"packed": types.StringType,
3040+
"page_per_vq": types.StringType,
3041+
},
3042+
},
3043+
"source": types.ObjectType{
3044+
AttrTypes: map[string]attr.Type{
3045+
"passthrough": types.ObjectType{
3046+
AttrTypes: map[string]attr.Type{
3047+
"evdev": types.StringType,
3048+
},
3049+
},
3050+
"evdev": types.ObjectType{
3051+
AttrTypes: map[string]attr.Type{
3052+
"dev": types.StringType,
3053+
"grab": types.StringType,
3054+
"grab_toggle": types.StringType,
3055+
"repeat": types.StringType,
3056+
},
3057+
},
3058+
},
3059+
},
3060+
},
3061+
},
3062+
},
27023063
}, newDevices)
27033064
diags.Append(d...)
27043065
if diags.HasError() {

0 commit comments

Comments
 (0)