├── writeValueToDisplay.exe ├── README.md └── writeValueToDisplay.cpp /writeValueToDisplay.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaleb422/NVapi-write-value-to-monitor/HEAD/writeValueToDisplay.exe -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NVapi-write-value-to-monitor 2 | Send commands to monitor over i2c using NVapi.
3 | This can be used to issue VCP commands or other manufacturer specific commands 4 | 5 | 6 | This program relies on the NVIDIA API (NVAPI), to compile it you will need to download the api which can be found here:
https://developer.nvidia.com/rtx/path-tracing/nvapi/get-started 7 | 8 | ### History 9 | This program was created after discovering that my display does not work with ControlMyMonitor to change inputs using VCP commands. Searching for an antlernative lead me to this thread https://github.com/rockowitz/ddcutil/issues/100 where other users had found a way to switch the inputs of their LG monitors using a linux program, I needed a windows solution. That lead to the NVIDIA API, this program is an adaptation of the i2c example code provided in the API 10 | 11 | ## Usage 12 | 13 | ### Syntax 14 | ``` 15 | writeValueToDisplay.exe [register_address] 16 | ``` 17 | 18 | | Argument | Description | 19 | | -------- | ----------- | 20 | | display_index | Index assigned to monitor by OS (Typically 0 for first screen, try running "mstsc.exe /l" in command prompt to see how windows has indexed your display(s)) | 21 | | input_value | value to write to screen | 22 | | command_code | VCP code or other| 23 | | register_address | Address to write to, default 0x51 for VCP codes | 24 | 25 | 26 | 27 | ## Example Usage 28 | Change display 0 brightness to 50% using VCP code 0x10 29 | ``` 30 | writeValueToDisplay.exe 0 0x32 0x10 31 | ``` 32 |
33 | 34 | Change display 0 input to HDMI 1 using VCP code 0x60 on supported displays 35 | ``` 36 | writeValueToDisplay.exe 0 0x11 0x60 37 | ``` 38 | 39 | ### Change input on some displays 40 | Some displays do not support using VCP codes to change inputs. I have tested this using values from this thread https://github.com/rockowitz/ddcutil/issues/100 with my LG Ultragear 27GP850-B. Your milage may vary with other monitors, use at your own risk! 41 | 42 | #### Change input to HDMI 1 on LG Ultragear 27GP850-B 43 | NOTE: LG Ultragear 27GP850-B is display 0 for me 44 | ``` 45 | writeValueToDisplay.exe 0 0x90 0xF4 0x50 46 | ``` 47 | 48 | #### Change input to Displayport on LG Ultragear 27GP850-B 49 | NOTE: LG Ultragear 27GP850-B is display 0 for me 50 | ``` 51 | writeValueToDisplay.exe 0 0xD0 0xF4 0x50 52 | ``` 53 | -------------------------------------------------------------------------------- /writeValueToDisplay.cpp: -------------------------------------------------------------------------------- 1 | #pragma comment(lib, "nvapi64.lib") 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "nvapi.h" 8 | #include "targetver.h" 9 | 10 | 11 | BOOL WriteValueToMonitor(NvPhysicalGpuHandle hPhysicalGpu, NvU32 displayId, BYTE input_value, BYTE command_code, BYTE register_address); 12 | 13 | 14 | int main(int argc, char* argv[]) { 15 | 16 | int display_index = 0; 17 | BYTE input_value = 0; 18 | BYTE command_code = 0; //VCP code or equivalent 19 | BYTE register_address = 0x51; 20 | 21 | // Usage: writeValueToMonitor.exe [display_index] [input_value] [command_code] 22 | // Uses default register addres 0x51 used for VCP codes 23 | if (argc == 4) { 24 | display_index = atoi(argv[1]); 25 | input_value = (BYTE)strtol(argv[2], NULL, 16); 26 | command_code = (BYTE)strtol(argv[3], NULL, 16); 27 | } 28 | 29 | // Usage: writeValueToMonitor.exe [display_index] [input_value] [command_code] [register_address] 30 | // Uses default register addres 0x51 used for VCP codes 31 | else if (argc == 5) { 32 | display_index = atoi(argv[1]); 33 | input_value = (BYTE)strtol(argv[2], NULL, 16); 34 | command_code = (BYTE)strtol(argv[3], NULL, 16); 35 | register_address = (BYTE)strtol(argv[4], NULL, 16); 36 | } 37 | else { 38 | printf("Incorrect Number of arguments!\n\n"); 39 | 40 | printf("Arguments:\n"); 41 | printf("display_index - Index assigned to monitor (0 for first screen)\n"); 42 | printf("input_value - value to right to screen\n"); 43 | printf("command_code - VCP code or other\n"); 44 | printf("register_address - Adress to write to, default 0x51 for VCP codes\n\n"); 45 | 46 | printf("Usage:\n"); 47 | printf("writeValueToScreen.exe [display_index] [input_value] [command_code]\n"); 48 | printf("OR\n"); 49 | printf("writeValueToScreen.exe [display_index] [input_value] [command_code] [register_address]\n"); 50 | return 1; 51 | } 52 | 53 | 54 | //printf("%d, %ld, %d, %d", display_index, input_value, command_code, register_address); 55 | 56 | NvAPI_Status nvapiStatus = NVAPI_OK; 57 | 58 | // Initialize NVAPI. 59 | if ((nvapiStatus = NvAPI_Initialize()) != NVAPI_OK) 60 | { 61 | printf("NvAPI_Initialize() failed with status %d\n", nvapiStatus); 62 | return 1; 63 | } 64 | 65 | 66 | // 67 | // Enumerate display handles 68 | // 69 | NvDisplayHandle hDisplay_a[NVAPI_MAX_PHYSICAL_GPUS * NVAPI_MAX_DISPLAY_HEADS] = { 0 }; 70 | for (unsigned int i = 0; nvapiStatus == NVAPI_OK; i++) 71 | { 72 | nvapiStatus = NvAPI_EnumNvidiaDisplayHandle(i, &hDisplay_a[i]); 73 | 74 | if (nvapiStatus != NVAPI_OK && nvapiStatus != NVAPI_END_ENUMERATION) 75 | { 76 | printf("NvAPI_EnumNvidiaDisplayHandle() failed with status %d\n", nvapiStatus); 77 | return 1; 78 | } 79 | } 80 | 81 | 82 | // Get GPU id assiciated with display ID 83 | NvPhysicalGpuHandle hGpu = NULL; 84 | NvU32 pGpuCount = 0; 85 | nvapiStatus = NvAPI_GetPhysicalGPUsFromDisplay(hDisplay_a[display_index], &hGpu, &pGpuCount); 86 | if (nvapiStatus != NVAPI_OK) 87 | { 88 | printf("NvAPI_GetPhysicalGPUFromDisplay() failed with status %d\n", nvapiStatus); 89 | return 1; 90 | } 91 | 92 | // Get the display id for subsequent I2C calls via NVAPI: 93 | NvU32 outputID = 0; 94 | nvapiStatus = NvAPI_GetAssociatedDisplayOutputId(hDisplay_a[display_index], &outputID); 95 | if (nvapiStatus != NVAPI_OK) 96 | { 97 | printf("NvAPI_GetAssociatedDisplayOutputId() failed with status %d\n", nvapiStatus); 98 | return 1; 99 | } 100 | 101 | 102 | BOOL result = WriteValueToMonitor(hGpu, outputID, input_value, command_code, register_address); 103 | if (!result) 104 | { 105 | printf("Changing input failed\n"); 106 | } 107 | printf("\n"); 108 | 109 | 110 | } 111 | 112 | 113 | // This function calculates the (XOR) checksum of the I2C register 114 | void CalculateI2cChecksum(const NV_I2C_INFO& i2cInfo) 115 | { 116 | // Calculate the i2c packet checksum and place the 117 | // value into the packet 118 | 119 | // i2c checksum is the result of xor'ing all the bytes in the 120 | // packet (except for the last data byte, which is the checksum 121 | // itself) 122 | 123 | // Packet consists of: 124 | 125 | // The device address... 126 | BYTE checksum = i2cInfo.i2cDevAddress; 127 | 128 | // Register address... 129 | for (unsigned int i = 0; i < i2cInfo.regAddrSize; ++i) 130 | { 131 | checksum ^= i2cInfo.pbI2cRegAddress[i]; 132 | } 133 | 134 | // And data bytes less last byte for checksum... 135 | for (unsigned int i = 0; i < i2cInfo.cbSize - 1; ++i) 136 | { 137 | checksum ^= i2cInfo.pbData[i]; 138 | } 139 | 140 | // Store calculated checksum in the last byte of i2c packet 141 | i2cInfo.pbData[i2cInfo.cbSize - 1] = checksum; 142 | } 143 | 144 | // This macro initializes the i2cinfo structure 145 | #define INIT_I2CINFO(i2cInfo, i2cVersion, displayId, isDDCPort, \ 146 | i2cDevAddr, regAddr, regSize, dataBuf, bufSize, speed) \ 147 | do { \ 148 | i2cInfo.version = i2cVersion; \ 149 | i2cInfo.displayMask = displayId; \ 150 | i2cInfo.bIsDDCPort = isDDCPort; \ 151 | i2cInfo.i2cDevAddress = i2cDevAddr; \ 152 | i2cInfo.pbI2cRegAddress = (BYTE*) ®Addr; \ 153 | i2cInfo.regAddrSize = regSize; \ 154 | i2cInfo.pbData = (BYTE*) &dataBuf; \ 155 | i2cInfo.cbSize = bufSize; \ 156 | i2cInfo.i2cSpeed = speed; \ 157 | }while (0) 158 | 159 | // This function writes the input_value to the display over the I2C bus by issuing commands and data 160 | BOOL WriteValueToMonitor(NvPhysicalGpuHandle hPhysicalGpu, NvU32 displayId,BYTE input_value, BYTE command_code, BYTE register_address) 161 | { 162 | NvAPI_Status nvapiStatus = NVAPI_OK; 163 | 164 | NV_I2C_INFO i2cInfo = { 0 }; 165 | i2cInfo.version = NV_I2C_INFO_VER; 166 | // 167 | // The 7-bit I2C address for display = Ox37 168 | // Since we always use 8bits to address, this 7-bit addr (0x37) is placed on 169 | // the upper 7 bits, and the LSB contains the Read/Write flag: 170 | // Write = 0 and Read =1; 171 | // 172 | NvU8 i2cDeviceAddr = 0x37; 173 | NvU8 i2cWriteDeviceAddr = i2cDeviceAddr << 1; //0x6E 174 | NvU8 i2cReadDeviceAddr = i2cWriteDeviceAddr | 1; //0x6F 175 | 176 | 177 | // 178 | // Now Send a write packet to modify current brightness value to 20 (0x14) 179 | // The packet consists of the following bytes 180 | // 0x6E - i2cWriteDeviceAddr 181 | // Ox?? - register_address 182 | // 0x84 - 0x80 OR n where n = 4 bytes for "modify a value" request 183 | // 0x03 - change a value flag 184 | // 0x?? - command_code 185 | // Ox00 - input_value high byte 186 | // 0x?? - input_value low byte 187 | // 0x?? - checksum, , xor'ing all the above bytes 188 | // 189 | BYTE registerAddr[] = { register_address }; 190 | BYTE modifyBytes[] = { 0x84, 0x03, command_code, 0x00, input_value, 0xDD }; 191 | 192 | INIT_I2CINFO(i2cInfo, NV_I2C_INFO_VER, displayId, TRUE, i2cWriteDeviceAddr, 193 | registerAddr, sizeof(registerAddr), modifyBytes, sizeof(modifyBytes), 27); 194 | CalculateI2cChecksum(i2cInfo); 195 | 196 | nvapiStatus = NvAPI_I2CWrite(hPhysicalGpu, &i2cInfo); 197 | if (nvapiStatus != NVAPI_OK) 198 | { 199 | printf(" NvAPI_I2CWrite (revise brightness) failed with status %d\n", nvapiStatus); 200 | return FALSE; 201 | } 202 | 203 | return TRUE; 204 | } 205 | --------------------------------------------------------------------------------