├── 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 |
--------------------------------------------------------------------------------