├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── command.go ├── export_test.go ├── go.mod ├── go.sum ├── xrandr.go └── xrandr_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | env: 3 | - GO111MODULE=on 4 | go: 5 | - 1.11.x 6 | before_install: 7 | - go get github.com/mattn/goveralls 8 | script: 9 | - go test -v ./... 10 | - $GOPATH/bin/goveralls -service=travis-ci 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Viorel Craescu & Contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Golang XRandR wrapper [![Go Report Card](https://goreportcard.com/badge/github.com/vcraescu/go-xrandr)](https://goreportcard.com/report/github.com/vcraescu/go-xrandr) [![Build Status](https://travis-ci.com/vcraescu/go-xrandr.svg?branch=master)](https://travis-ci.com/vcraescu/go-xrandr) [![Coverage Status](https://coveralls.io/repos/github/vcraescu/go-xrandr/badge.svg?branch=master)](https://coveralls.io/github/vcraescu/go-xrandr?branch=master) 2 | 3 | Golang xrandr wrapper. 4 | The entire functionality is based on xrandr output. 5 | 6 | ```go 7 | import ( 8 | "github.com/vcraescu/go-xrandr" 9 | "github.com/davecgh/go-spew/spew" 10 | ) 11 | 12 | func main() { 13 | screens, err := xrandr.GetScreens() 14 | if err != nil { 15 | panic(err) 16 | } 17 | 18 | spew.Dump(screens) 19 | } 20 | ``` 21 | 22 | will print: 23 | 24 | ``` 25 | (xrandr.Screens) (len=1 cap=1) { 26 | (xrandr.Screen) { 27 | No: (int) 0, 28 | CurrentResolution: (xrandr.Size) { 29 | Width: (float32) 8959, 30 | Height: (float32) 2880 31 | }, 32 | MinResolution: (xrandr.Size) { 33 | Width: (float32) 8, 34 | Height: (float32) 8 35 | }, 36 | MaxResolution: (xrandr.Size) { 37 | Width: (float32) 32767, 38 | Height: (float32) 32767 39 | }, 40 | Monitors: ([]xrandr.Monitor) (len=3 cap=4) { 41 | (xrandr.Monitor) { 42 | ID: (string) (len=6) "HDMI-0", 43 | Modes: ([]xrandr.Mode) (len=14 cap=16) { 44 | (xrandr.Mode) { 45 | Resolution: (xrandr.Size) { 46 | Width: (float32) 3840, 47 | Height: (float32) 2160 48 | }, 49 | RefreshRates: ([]xrandr.RefreshRate) (len=4 cap=4) { 50 | (xrandr.RefreshRate) { 51 | Value: (xrandr.RefreshRateValue) 30, 52 | Current: (bool) true, 53 | Preferred: (bool) true 54 | }, 55 | (xrandr.RefreshRate) { 56 | Value: (xrandr.RefreshRateValue) 29.97, 57 | Current: (bool) false, 58 | Preferred: (bool) false 59 | }, 60 | (xrandr.RefreshRate) { 61 | Value: (xrandr.RefreshRateValue) 25, 62 | Current: (bool) false, 63 | Preferred: (bool) false 64 | }, 65 | (xrandr.RefreshRate) { 66 | Value: (xrandr.RefreshRateValue) 23.98, 67 | Current: (bool) false, 68 | Preferred: (bool) false 69 | } 70 | } 71 | }, 72 | (xrandr.Mode) { 73 | Resolution: (xrandr.Size) { 74 | Width: (float32) 1920, 75 | Height: (float32) 1200 76 | }, 77 | RefreshRates: ([]xrandr.RefreshRate) (len=1 cap=1) { 78 | (xrandr.RefreshRate) { 79 | Value: (xrandr.RefreshRateValue) 59.88, 80 | Current: (bool) false, 81 | Preferred: (bool) false 82 | } 83 | } 84 | }, 85 | (xrandr.Mode) { 86 | Resolution: (xrandr.Size) { 87 | Width: (float32) 1920, 88 | Height: (float32) 1080 89 | }, 90 | RefreshRates: ([]xrandr.RefreshRate) (len=7 cap=8) { 91 | (xrandr.RefreshRate) { 92 | Value: (xrandr.RefreshRateValue) 60, 93 | Current: (bool) false, 94 | Preferred: (bool) false 95 | }, 96 | (xrandr.RefreshRate) { 97 | Value: (xrandr.RefreshRateValue) 59.94, 98 | Current: (bool) false, 99 | Preferred: (bool) false 100 | }, 101 | (xrandr.RefreshRate) { 102 | Value: (xrandr.RefreshRateValue) 50, 103 | Current: (bool) false, 104 | Preferred: (bool) false 105 | }, 106 | (xrandr.RefreshRate) { 107 | Value: (xrandr.RefreshRateValue) 23.98, 108 | Current: (bool) false, 109 | Preferred: (bool) false 110 | }, 111 | (xrandr.RefreshRate) { 112 | Value: (xrandr.RefreshRateValue) 60.05, 113 | Current: (bool) false, 114 | Preferred: (bool) false 115 | }, 116 | (xrandr.RefreshRate) { 117 | Value: (xrandr.RefreshRateValue) 60, 118 | Current: (bool) false, 119 | Preferred: (bool) false 120 | }, 121 | (xrandr.RefreshRate) { 122 | Value: (xrandr.RefreshRateValue) 50.04, 123 | Current: (bool) false, 124 | Preferred: (bool) false 125 | } 126 | } 127 | }, 128 | (xrandr.Mode) { 129 | Resolution: (xrandr.Size) { 130 | Width: (float32) 1680, 131 | Height: (float32) 1050 132 | }, 133 | RefreshRates: ([]xrandr.RefreshRate) (len=1 cap=1) { 134 | (xrandr.RefreshRate) { 135 | Value: (xrandr.RefreshRateValue) 59.95, 136 | Current: (bool) false, 137 | Preferred: (bool) false 138 | } 139 | } 140 | }, 141 | (xrandr.Mode) { 142 | Resolution: (xrandr.Size) { 143 | Width: (float32) 1600, 144 | Height: (float32) 1200 145 | }, 146 | RefreshRates: ([]xrandr.RefreshRate) (len=1 cap=1) { 147 | (xrandr.RefreshRate) { 148 | Value: (xrandr.RefreshRateValue) 60, 149 | Current: (bool) false, 150 | Preferred: (bool) false 151 | } 152 | } 153 | }, 154 | (xrandr.Mode) { 155 | Resolution: (xrandr.Size) { 156 | Width: (float32) 1280, 157 | Height: (float32) 1024 158 | }, 159 | RefreshRates: ([]xrandr.RefreshRate) (len=2 cap=2) { 160 | (xrandr.RefreshRate) { 161 | Value: (xrandr.RefreshRateValue) 75.02, 162 | Current: (bool) false, 163 | Preferred: (bool) false 164 | }, 165 | (xrandr.RefreshRate) { 166 | Value: (xrandr.RefreshRateValue) 60.02, 167 | Current: (bool) false, 168 | Preferred: (bool) false 169 | } 170 | } 171 | }, 172 | (xrandr.Mode) { 173 | Resolution: (xrandr.Size) { 174 | Width: (float32) 1280, 175 | Height: (float32) 800 176 | }, 177 | RefreshRates: ([]xrandr.RefreshRate) (len=1 cap=1) { 178 | (xrandr.RefreshRate) { 179 | Value: (xrandr.RefreshRateValue) 59.81, 180 | Current: (bool) false, 181 | Preferred: (bool) false 182 | } 183 | } 184 | }, 185 | (xrandr.Mode) { 186 | Resolution: (xrandr.Size) { 187 | Width: (float32) 1280, 188 | Height: (float32) 720 189 | }, 190 | RefreshRates: ([]xrandr.RefreshRate) (len=3 cap=4) { 191 | (xrandr.RefreshRate) { 192 | Value: (xrandr.RefreshRateValue) 60, 193 | Current: (bool) false, 194 | Preferred: (bool) false 195 | }, 196 | (xrandr.RefreshRate) { 197 | Value: (xrandr.RefreshRateValue) 59.94, 198 | Current: (bool) false, 199 | Preferred: (bool) false 200 | }, 201 | (xrandr.RefreshRate) { 202 | Value: (xrandr.RefreshRateValue) 50, 203 | Current: (bool) false, 204 | Preferred: (bool) false 205 | } 206 | } 207 | }, 208 | (xrandr.Mode) { 209 | Resolution: (xrandr.Size) { 210 | Width: (float32) 1152, 211 | Height: (float32) 864 212 | }, 213 | RefreshRates: ([]xrandr.RefreshRate) (len=1 cap=1) { 214 | (xrandr.RefreshRate) { 215 | Value: (xrandr.RefreshRateValue) 75, 216 | Current: (bool) false, 217 | Preferred: (bool) false 218 | } 219 | } 220 | }, 221 | (xrandr.Mode) { 222 | Resolution: (xrandr.Size) { 223 | Width: (float32) 1024, 224 | Height: (float32) 768 225 | }, 226 | RefreshRates: ([]xrandr.RefreshRate) (len=2 cap=2) { 227 | (xrandr.RefreshRate) { 228 | Value: (xrandr.RefreshRateValue) 75.03, 229 | Current: (bool) false, 230 | Preferred: (bool) false 231 | }, 232 | (xrandr.RefreshRate) { 233 | Value: (xrandr.RefreshRateValue) 60, 234 | Current: (bool) false, 235 | Preferred: (bool) false 236 | } 237 | } 238 | }, 239 | (xrandr.Mode) { 240 | Resolution: (xrandr.Size) { 241 | Width: (float32) 800, 242 | Height: (float32) 600 243 | }, 244 | RefreshRates: ([]xrandr.RefreshRate) (len=2 cap=2) { 245 | (xrandr.RefreshRate) { 246 | Value: (xrandr.RefreshRateValue) 75, 247 | Current: (bool) false, 248 | Preferred: (bool) false 249 | }, 250 | (xrandr.RefreshRate) { 251 | Value: (xrandr.RefreshRateValue) 60.32, 252 | Current: (bool) false, 253 | Preferred: (bool) false 254 | } 255 | } 256 | }, 257 | (xrandr.Mode) { 258 | Resolution: (xrandr.Size) { 259 | Width: (float32) 720, 260 | Height: (float32) 576 261 | }, 262 | RefreshRates: ([]xrandr.RefreshRate) (len=1 cap=1) { 263 | (xrandr.RefreshRate) { 264 | Value: (xrandr.RefreshRateValue) 50, 265 | Current: (bool) false, 266 | Preferred: (bool) false 267 | } 268 | } 269 | }, 270 | (xrandr.Mode) { 271 | Resolution: (xrandr.Size) { 272 | Width: (float32) 720, 273 | Height: (float32) 480 274 | }, 275 | RefreshRates: ([]xrandr.RefreshRate) (len=1 cap=1) { 276 | (xrandr.RefreshRate) { 277 | Value: (xrandr.RefreshRateValue) 59.94, 278 | Current: (bool) false, 279 | Preferred: (bool) false 280 | } 281 | } 282 | }, 283 | (xrandr.Mode) { 284 | Resolution: (xrandr.Size) { 285 | Width: (float32) 640, 286 | Height: (float32) 480 287 | }, 288 | RefreshRates: ([]xrandr.RefreshRate) (len=3 cap=4) { 289 | (xrandr.RefreshRate) { 290 | Value: (xrandr.RefreshRateValue) 75, 291 | Current: (bool) false, 292 | Preferred: (bool) false 293 | }, 294 | (xrandr.RefreshRate) { 295 | Value: (xrandr.RefreshRateValue) 59.94, 296 | Current: (bool) false, 297 | Preferred: (bool) false 298 | }, 299 | (xrandr.RefreshRate) { 300 | Value: (xrandr.RefreshRateValue) 59.93, 301 | Current: (bool) false, 302 | Preferred: (bool) false 303 | } 304 | } 305 | } 306 | }, 307 | Primary: (bool) true, 308 | Size: (xrandr.Size) { 309 | Width: (float32) 597, 310 | Height: (float32) 336 311 | }, 312 | Connected: (bool) true, 313 | Resolution: (xrandr.Size) { 314 | Width: (float32) 5119, 315 | Height: (float32) 2879 316 | }, 317 | Position: (xrandr.Position) { 318 | X: (int) 0, 319 | Y: (int) 0 320 | } 321 | }, 322 | (xrandr.Monitor) { 323 | ID: (string) (len=7) "eDP-1-1", 324 | Modes: ([]xrandr.Mode) (len=50 cap=64) { 325 | (xrandr.Mode) { 326 | Resolution: (xrandr.Size) { 327 | Width: (float32) 3840, 328 | Height: (float32) 2160 329 | }, 330 | RefreshRates: ([]xrandr.RefreshRate) (len=4 cap=4) { 331 | (xrandr.RefreshRate) { 332 | Value: (xrandr.RefreshRateValue) 60, 333 | Current: (bool) true, 334 | Preferred: (bool) true 335 | }, 336 | (xrandr.RefreshRate) { 337 | Value: (xrandr.RefreshRateValue) 59.98, 338 | Current: (bool) false, 339 | Preferred: (bool) false 340 | }, 341 | (xrandr.RefreshRate) { 342 | Value: (xrandr.RefreshRateValue) 48.02, 343 | Current: (bool) false, 344 | Preferred: (bool) false 345 | }, 346 | (xrandr.RefreshRate) { 347 | Value: (xrandr.RefreshRateValue) 59.97, 348 | Current: (bool) false, 349 | Preferred: (bool) false 350 | } 351 | } 352 | }, 353 | (xrandr.Mode) { 354 | Resolution: (xrandr.Size) { 355 | Width: (float32) 3200, 356 | Height: (float32) 1800 357 | }, 358 | RefreshRates: ([]xrandr.RefreshRate) (len=2 cap=2) { 359 | (xrandr.RefreshRate) { 360 | Value: (xrandr.RefreshRateValue) 59.96, 361 | Current: (bool) false, 362 | Preferred: (bool) false 363 | }, 364 | (xrandr.RefreshRate) { 365 | Value: (xrandr.RefreshRateValue) 59.94, 366 | Current: (bool) false, 367 | Preferred: (bool) false 368 | } 369 | } 370 | }, 371 | (xrandr.Mode) { 372 | Resolution: (xrandr.Size) { 373 | Width: (float32) 2880, 374 | Height: (float32) 1620 375 | }, 376 | RefreshRates: ([]xrandr.RefreshRate) (len=2 cap=2) { 377 | (xrandr.RefreshRate) { 378 | Value: (xrandr.RefreshRateValue) 59.96, 379 | Current: (bool) false, 380 | Preferred: (bool) false 381 | }, 382 | (xrandr.RefreshRate) { 383 | Value: (xrandr.RefreshRateValue) 59.97, 384 | Current: (bool) false, 385 | Preferred: (bool) false 386 | } 387 | } 388 | }, 389 | (xrandr.Mode) { 390 | Resolution: (xrandr.Size) { 391 | Width: (float32) 2560, 392 | Height: (float32) 1600 393 | }, 394 | RefreshRates: ([]xrandr.RefreshRate) (len=2 cap=2) { 395 | (xrandr.RefreshRate) { 396 | Value: (xrandr.RefreshRateValue) 59.99, 397 | Current: (bool) false, 398 | Preferred: (bool) false 399 | }, 400 | (xrandr.RefreshRate) { 401 | Value: (xrandr.RefreshRateValue) 59.97, 402 | Current: (bool) false, 403 | Preferred: (bool) false 404 | } 405 | } 406 | }, 407 | (xrandr.Mode) { 408 | Resolution: (xrandr.Size) { 409 | Width: (float32) 2560, 410 | Height: (float32) 1440 411 | }, 412 | RefreshRates: ([]xrandr.RefreshRate) (len=4 cap=4) { 413 | (xrandr.RefreshRate) { 414 | Value: (xrandr.RefreshRateValue) 59.99, 415 | Current: (bool) false, 416 | Preferred: (bool) false 417 | }, 418 | (xrandr.RefreshRate) { 419 | Value: (xrandr.RefreshRateValue) 59.99, 420 | Current: (bool) false, 421 | Preferred: (bool) false 422 | }, 423 | (xrandr.RefreshRate) { 424 | Value: (xrandr.RefreshRateValue) 59.96, 425 | Current: (bool) false, 426 | Preferred: (bool) false 427 | }, 428 | (xrandr.RefreshRate) { 429 | Value: (xrandr.RefreshRateValue) 59.95, 430 | Current: (bool) false, 431 | Preferred: (bool) false 432 | } 433 | } 434 | }, 435 | (xrandr.Mode) { 436 | Resolution: (xrandr.Size) { 437 | Width: (float32) 2048, 438 | Height: (float32) 1536 439 | }, 440 | RefreshRates: ([]xrandr.RefreshRate) (len=1 cap=1) { 441 | (xrandr.RefreshRate) { 442 | Value: (xrandr.RefreshRateValue) 60, 443 | Current: (bool) false, 444 | Preferred: (bool) false 445 | } 446 | } 447 | }, 448 | (xrandr.Mode) { 449 | Resolution: (xrandr.Size) { 450 | Width: (float32) 1920, 451 | Height: (float32) 1440 452 | }, 453 | RefreshRates: ([]xrandr.RefreshRate) (len=1 cap=1) { 454 | (xrandr.RefreshRate) { 455 | Value: (xrandr.RefreshRateValue) 60, 456 | Current: (bool) false, 457 | Preferred: (bool) false 458 | } 459 | } 460 | }, 461 | (xrandr.Mode) { 462 | Resolution: (xrandr.Size) { 463 | Width: (float32) 1856, 464 | Height: (float32) 1392 465 | }, 466 | RefreshRates: ([]xrandr.RefreshRate) (len=1 cap=1) { 467 | (xrandr.RefreshRate) { 468 | Value: (xrandr.RefreshRateValue) 60.01, 469 | Current: (bool) false, 470 | Preferred: (bool) false 471 | } 472 | } 473 | }, 474 | (xrandr.Mode) { 475 | Resolution: (xrandr.Size) { 476 | Width: (float32) 1792, 477 | Height: (float32) 1344 478 | }, 479 | RefreshRates: ([]xrandr.RefreshRate) (len=1 cap=1) { 480 | (xrandr.RefreshRate) { 481 | Value: (xrandr.RefreshRateValue) 60.01, 482 | Current: (bool) false, 483 | Preferred: (bool) false 484 | } 485 | } 486 | }, 487 | (xrandr.Mode) { 488 | Resolution: (xrandr.Size) { 489 | Width: (float32) 2048, 490 | Height: (float32) 1152 491 | }, 492 | RefreshRates: ([]xrandr.RefreshRate) (len=4 cap=4) { 493 | (xrandr.RefreshRate) { 494 | Value: (xrandr.RefreshRateValue) 59.99, 495 | Current: (bool) false, 496 | Preferred: (bool) false 497 | }, 498 | (xrandr.RefreshRate) { 499 | Value: (xrandr.RefreshRateValue) 59.98, 500 | Current: (bool) false, 501 | Preferred: (bool) false 502 | }, 503 | (xrandr.RefreshRate) { 504 | Value: (xrandr.RefreshRateValue) 59.9, 505 | Current: (bool) false, 506 | Preferred: (bool) false 507 | }, 508 | (xrandr.RefreshRate) { 509 | Value: (xrandr.RefreshRateValue) 59.91, 510 | Current: (bool) false, 511 | Preferred: (bool) false 512 | } 513 | } 514 | }, 515 | (xrandr.Mode) { 516 | Resolution: (xrandr.Size) { 517 | Width: (float32) 1920, 518 | Height: (float32) 1200 519 | }, 520 | RefreshRates: ([]xrandr.RefreshRate) (len=2 cap=2) { 521 | (xrandr.RefreshRate) { 522 | Value: (xrandr.RefreshRateValue) 59.88, 523 | Current: (bool) false, 524 | Preferred: (bool) false 525 | }, 526 | (xrandr.RefreshRate) { 527 | Value: (xrandr.RefreshRateValue) 59.95, 528 | Current: (bool) false, 529 | Preferred: (bool) false 530 | } 531 | } 532 | }, 533 | (xrandr.Mode) { 534 | Resolution: (xrandr.Size) { 535 | Width: (float32) 1920, 536 | Height: (float32) 1080 537 | }, 538 | RefreshRates: ([]xrandr.RefreshRate) (len=4 cap=4) { 539 | (xrandr.RefreshRate) { 540 | Value: (xrandr.RefreshRateValue) 60.01, 541 | Current: (bool) false, 542 | Preferred: (bool) false 543 | }, 544 | (xrandr.RefreshRate) { 545 | Value: (xrandr.RefreshRateValue) 59.97, 546 | Current: (bool) false, 547 | Preferred: (bool) false 548 | }, 549 | (xrandr.RefreshRate) { 550 | Value: (xrandr.RefreshRateValue) 59.96, 551 | Current: (bool) false, 552 | Preferred: (bool) false 553 | }, 554 | (xrandr.RefreshRate) { 555 | Value: (xrandr.RefreshRateValue) 59.93, 556 | Current: (bool) false, 557 | Preferred: (bool) false 558 | } 559 | } 560 | }, 561 | (xrandr.Mode) { 562 | Resolution: (xrandr.Size) { 563 | Width: (float32) 1600, 564 | Height: (float32) 1200 565 | }, 566 | RefreshRates: ([]xrandr.RefreshRate) (len=1 cap=1) { 567 | (xrandr.RefreshRate) { 568 | Value: (xrandr.RefreshRateValue) 60, 569 | Current: (bool) false, 570 | Preferred: (bool) false 571 | } 572 | } 573 | }, 574 | (xrandr.Mode) { 575 | Resolution: (xrandr.Size) { 576 | Width: (float32) 1680, 577 | Height: (float32) 1050 578 | }, 579 | RefreshRates: ([]xrandr.RefreshRate) (len=2 cap=2) { 580 | (xrandr.RefreshRate) { 581 | Value: (xrandr.RefreshRateValue) 59.95, 582 | Current: (bool) false, 583 | Preferred: (bool) false 584 | }, 585 | (xrandr.RefreshRate) { 586 | Value: (xrandr.RefreshRateValue) 59.88, 587 | Current: (bool) false, 588 | Preferred: (bool) false 589 | } 590 | } 591 | }, 592 | (xrandr.Mode) { 593 | Resolution: (xrandr.Size) { 594 | Width: (float32) 1400, 595 | Height: (float32) 1050 596 | }, 597 | RefreshRates: ([]xrandr.RefreshRate) (len=1 cap=1) { 598 | (xrandr.RefreshRate) { 599 | Value: (xrandr.RefreshRateValue) 59.98, 600 | Current: (bool) false, 601 | Preferred: (bool) false 602 | } 603 | } 604 | }, 605 | (xrandr.Mode) { 606 | Resolution: (xrandr.Size) { 607 | Width: (float32) 1600, 608 | Height: (float32) 900 609 | }, 610 | RefreshRates: ([]xrandr.RefreshRate) (len=4 cap=4) { 611 | (xrandr.RefreshRate) { 612 | Value: (xrandr.RefreshRateValue) 59.99, 613 | Current: (bool) false, 614 | Preferred: (bool) false 615 | }, 616 | (xrandr.RefreshRate) { 617 | Value: (xrandr.RefreshRateValue) 59.94, 618 | Current: (bool) false, 619 | Preferred: (bool) false 620 | }, 621 | (xrandr.RefreshRate) { 622 | Value: (xrandr.RefreshRateValue) 59.95, 623 | Current: (bool) false, 624 | Preferred: (bool) false 625 | }, 626 | (xrandr.RefreshRate) { 627 | Value: (xrandr.RefreshRateValue) 59.82, 628 | Current: (bool) false, 629 | Preferred: (bool) false 630 | } 631 | } 632 | }, 633 | (xrandr.Mode) { 634 | Resolution: (xrandr.Size) { 635 | Width: (float32) 1280, 636 | Height: (float32) 1024 637 | }, 638 | RefreshRates: ([]xrandr.RefreshRate) (len=1 cap=1) { 639 | (xrandr.RefreshRate) { 640 | Value: (xrandr.RefreshRateValue) 60.02, 641 | Current: (bool) false, 642 | Preferred: (bool) false 643 | } 644 | } 645 | }, 646 | (xrandr.Mode) { 647 | Resolution: (xrandr.Size) { 648 | Width: (float32) 1400, 649 | Height: (float32) 900 650 | }, 651 | RefreshRates: ([]xrandr.RefreshRate) (len=2 cap=2) { 652 | (xrandr.RefreshRate) { 653 | Value: (xrandr.RefreshRateValue) 59.96, 654 | Current: (bool) false, 655 | Preferred: (bool) false 656 | }, 657 | (xrandr.RefreshRate) { 658 | Value: (xrandr.RefreshRateValue) 59.88, 659 | Current: (bool) false, 660 | Preferred: (bool) false 661 | } 662 | } 663 | }, 664 | (xrandr.Mode) { 665 | Resolution: (xrandr.Size) { 666 | Width: (float32) 1280, 667 | Height: (float32) 960 668 | }, 669 | RefreshRates: ([]xrandr.RefreshRate) (len=1 cap=1) { 670 | (xrandr.RefreshRate) { 671 | Value: (xrandr.RefreshRateValue) 60, 672 | Current: (bool) false, 673 | Preferred: (bool) false 674 | } 675 | } 676 | }, 677 | (xrandr.Mode) { 678 | Resolution: (xrandr.Size) { 679 | Width: (float32) 1440, 680 | Height: (float32) 810 681 | }, 682 | RefreshRates: ([]xrandr.RefreshRate) (len=2 cap=2) { 683 | (xrandr.RefreshRate) { 684 | Value: (xrandr.RefreshRateValue) 60, 685 | Current: (bool) false, 686 | Preferred: (bool) false 687 | }, 688 | (xrandr.RefreshRate) { 689 | Value: (xrandr.RefreshRateValue) 59.97, 690 | Current: (bool) false, 691 | Preferred: (bool) false 692 | } 693 | } 694 | }, 695 | (xrandr.Mode) { 696 | Resolution: (xrandr.Size) { 697 | Width: (float32) 1368, 698 | Height: (float32) 768 699 | }, 700 | RefreshRates: ([]xrandr.RefreshRate) (len=2 cap=2) { 701 | (xrandr.RefreshRate) { 702 | Value: (xrandr.RefreshRateValue) 59.88, 703 | Current: (bool) false, 704 | Preferred: (bool) false 705 | }, 706 | (xrandr.RefreshRate) { 707 | Value: (xrandr.RefreshRateValue) 59.85, 708 | Current: (bool) false, 709 | Preferred: (bool) false 710 | } 711 | } 712 | }, 713 | (xrandr.Mode) { 714 | Resolution: (xrandr.Size) { 715 | Width: (float32) 1280, 716 | Height: (float32) 800 717 | }, 718 | RefreshRates: ([]xrandr.RefreshRate) (len=4 cap=4) { 719 | (xrandr.RefreshRate) { 720 | Value: (xrandr.RefreshRateValue) 59.99, 721 | Current: (bool) false, 722 | Preferred: (bool) false 723 | }, 724 | (xrandr.RefreshRate) { 725 | Value: (xrandr.RefreshRateValue) 59.97, 726 | Current: (bool) false, 727 | Preferred: (bool) false 728 | }, 729 | (xrandr.RefreshRate) { 730 | Value: (xrandr.RefreshRateValue) 59.81, 731 | Current: (bool) false, 732 | Preferred: (bool) false 733 | }, 734 | (xrandr.RefreshRate) { 735 | Value: (xrandr.RefreshRateValue) 59.91, 736 | Current: (bool) false, 737 | Preferred: (bool) false 738 | } 739 | } 740 | }, 741 | (xrandr.Mode) { 742 | Resolution: (xrandr.Size) { 743 | Width: (float32) 1280, 744 | Height: (float32) 720 745 | }, 746 | RefreshRates: ([]xrandr.RefreshRate) (len=4 cap=4) { 747 | (xrandr.RefreshRate) { 748 | Value: (xrandr.RefreshRateValue) 60, 749 | Current: (bool) false, 750 | Preferred: (bool) false 751 | }, 752 | (xrandr.RefreshRate) { 753 | Value: (xrandr.RefreshRateValue) 59.99, 754 | Current: (bool) false, 755 | Preferred: (bool) false 756 | }, 757 | (xrandr.RefreshRate) { 758 | Value: (xrandr.RefreshRateValue) 59.86, 759 | Current: (bool) false, 760 | Preferred: (bool) false 761 | }, 762 | (xrandr.RefreshRate) { 763 | Value: (xrandr.RefreshRateValue) 59.74, 764 | Current: (bool) false, 765 | Preferred: (bool) false 766 | } 767 | } 768 | }, 769 | (xrandr.Mode) { 770 | Resolution: (xrandr.Size) { 771 | Width: (float32) 1024, 772 | Height: (float32) 768 773 | }, 774 | RefreshRates: ([]xrandr.RefreshRate) (len=2 cap=2) { 775 | (xrandr.RefreshRate) { 776 | Value: (xrandr.RefreshRateValue) 60.04, 777 | Current: (bool) false, 778 | Preferred: (bool) false 779 | }, 780 | (xrandr.RefreshRate) { 781 | Value: (xrandr.RefreshRateValue) 60, 782 | Current: (bool) false, 783 | Preferred: (bool) false 784 | } 785 | } 786 | }, 787 | (xrandr.Mode) { 788 | Resolution: (xrandr.Size) { 789 | Width: (float32) 960, 790 | Height: (float32) 720 791 | }, 792 | RefreshRates: ([]xrandr.RefreshRate) (len=1 cap=1) { 793 | (xrandr.RefreshRate) { 794 | Value: (xrandr.RefreshRateValue) 60, 795 | Current: (bool) false, 796 | Preferred: (bool) false 797 | } 798 | } 799 | }, 800 | (xrandr.Mode) { 801 | Resolution: (xrandr.Size) { 802 | Width: (float32) 928, 803 | Height: (float32) 696 804 | }, 805 | RefreshRates: ([]xrandr.RefreshRate) (len=1 cap=1) { 806 | (xrandr.RefreshRate) { 807 | Value: (xrandr.RefreshRateValue) 60.05, 808 | Current: (bool) false, 809 | Preferred: (bool) false 810 | } 811 | } 812 | }, 813 | (xrandr.Mode) { 814 | Resolution: (xrandr.Size) { 815 | Width: (float32) 896, 816 | Height: (float32) 672 817 | }, 818 | RefreshRates: ([]xrandr.RefreshRate) (len=1 cap=1) { 819 | (xrandr.RefreshRate) { 820 | Value: (xrandr.RefreshRateValue) 60.01, 821 | Current: (bool) false, 822 | Preferred: (bool) false 823 | } 824 | } 825 | }, 826 | (xrandr.Mode) { 827 | Resolution: (xrandr.Size) { 828 | Width: (float32) 1024, 829 | Height: (float32) 576 830 | }, 831 | RefreshRates: ([]xrandr.RefreshRate) (len=4 cap=4) { 832 | (xrandr.RefreshRate) { 833 | Value: (xrandr.RefreshRateValue) 59.95, 834 | Current: (bool) false, 835 | Preferred: (bool) false 836 | }, 837 | (xrandr.RefreshRate) { 838 | Value: (xrandr.RefreshRateValue) 59.96, 839 | Current: (bool) false, 840 | Preferred: (bool) false 841 | }, 842 | (xrandr.RefreshRate) { 843 | Value: (xrandr.RefreshRateValue) 59.9, 844 | Current: (bool) false, 845 | Preferred: (bool) false 846 | }, 847 | (xrandr.RefreshRate) { 848 | Value: (xrandr.RefreshRateValue) 59.82, 849 | Current: (bool) false, 850 | Preferred: (bool) false 851 | } 852 | } 853 | }, 854 | (xrandr.Mode) { 855 | Resolution: (xrandr.Size) { 856 | Width: (float32) 960, 857 | Height: (float32) 600 858 | }, 859 | RefreshRates: ([]xrandr.RefreshRate) (len=2 cap=2) { 860 | (xrandr.RefreshRate) { 861 | Value: (xrandr.RefreshRateValue) 59.93, 862 | Current: (bool) false, 863 | Preferred: (bool) false 864 | }, 865 | (xrandr.RefreshRate) { 866 | Value: (xrandr.RefreshRateValue) 60, 867 | Current: (bool) false, 868 | Preferred: (bool) false 869 | } 870 | } 871 | }, 872 | (xrandr.Mode) { 873 | Resolution: (xrandr.Size) { 874 | Width: (float32) 960, 875 | Height: (float32) 540 876 | }, 877 | RefreshRates: ([]xrandr.RefreshRate) (len=4 cap=4) { 878 | (xrandr.RefreshRate) { 879 | Value: (xrandr.RefreshRateValue) 59.96, 880 | Current: (bool) false, 881 | Preferred: (bool) false 882 | }, 883 | (xrandr.RefreshRate) { 884 | Value: (xrandr.RefreshRateValue) 59.99, 885 | Current: (bool) false, 886 | Preferred: (bool) false 887 | }, 888 | (xrandr.RefreshRate) { 889 | Value: (xrandr.RefreshRateValue) 59.63, 890 | Current: (bool) false, 891 | Preferred: (bool) false 892 | }, 893 | (xrandr.RefreshRate) { 894 | Value: (xrandr.RefreshRateValue) 59.82, 895 | Current: (bool) false, 896 | Preferred: (bool) false 897 | } 898 | } 899 | }, 900 | (xrandr.Mode) { 901 | Resolution: (xrandr.Size) { 902 | Width: (float32) 800, 903 | Height: (float32) 600 904 | }, 905 | RefreshRates: ([]xrandr.RefreshRate) (len=3 cap=4) { 906 | (xrandr.RefreshRate) { 907 | Value: (xrandr.RefreshRateValue) 60, 908 | Current: (bool) false, 909 | Preferred: (bool) false 910 | }, 911 | (xrandr.RefreshRate) { 912 | Value: (xrandr.RefreshRateValue) 60.32, 913 | Current: (bool) false, 914 | Preferred: (bool) false 915 | }, 916 | (xrandr.RefreshRate) { 917 | Value: (xrandr.RefreshRateValue) 56.25, 918 | Current: (bool) false, 919 | Preferred: (bool) false 920 | } 921 | } 922 | }, 923 | (xrandr.Mode) { 924 | Resolution: (xrandr.Size) { 925 | Width: (float32) 840, 926 | Height: (float32) 525 927 | }, 928 | RefreshRates: ([]xrandr.RefreshRate) (len=2 cap=2) { 929 | (xrandr.RefreshRate) { 930 | Value: (xrandr.RefreshRateValue) 60.01, 931 | Current: (bool) false, 932 | Preferred: (bool) false 933 | }, 934 | (xrandr.RefreshRate) { 935 | Value: (xrandr.RefreshRateValue) 59.88, 936 | Current: (bool) false, 937 | Preferred: (bool) false 938 | } 939 | } 940 | }, 941 | (xrandr.Mode) { 942 | Resolution: (xrandr.Size) { 943 | Width: (float32) 864, 944 | Height: (float32) 486 945 | }, 946 | RefreshRates: ([]xrandr.RefreshRate) (len=2 cap=2) { 947 | (xrandr.RefreshRate) { 948 | Value: (xrandr.RefreshRateValue) 59.92, 949 | Current: (bool) false, 950 | Preferred: (bool) false 951 | }, 952 | (xrandr.RefreshRate) { 953 | Value: (xrandr.RefreshRateValue) 59.57, 954 | Current: (bool) false, 955 | Preferred: (bool) false 956 | } 957 | } 958 | }, 959 | (xrandr.Mode) { 960 | Resolution: (xrandr.Size) { 961 | Width: (float32) 700, 962 | Height: (float32) 525 963 | }, 964 | RefreshRates: ([]xrandr.RefreshRate) (len=1 cap=1) { 965 | (xrandr.RefreshRate) { 966 | Value: (xrandr.RefreshRateValue) 59.98, 967 | Current: (bool) false, 968 | Preferred: (bool) false 969 | } 970 | } 971 | }, 972 | (xrandr.Mode) { 973 | Resolution: (xrandr.Size) { 974 | Width: (float32) 800, 975 | Height: (float32) 450 976 | }, 977 | RefreshRates: ([]xrandr.RefreshRate) (len=2 cap=2) { 978 | (xrandr.RefreshRate) { 979 | Value: (xrandr.RefreshRateValue) 59.95, 980 | Current: (bool) false, 981 | Preferred: (bool) false 982 | }, 983 | (xrandr.RefreshRate) { 984 | Value: (xrandr.RefreshRateValue) 59.82, 985 | Current: (bool) false, 986 | Preferred: (bool) false 987 | } 988 | } 989 | }, 990 | (xrandr.Mode) { 991 | Resolution: (xrandr.Size) { 992 | Width: (float32) 640, 993 | Height: (float32) 512 994 | }, 995 | RefreshRates: ([]xrandr.RefreshRate) (len=1 cap=1) { 996 | (xrandr.RefreshRate) { 997 | Value: (xrandr.RefreshRateValue) 60.02, 998 | Current: (bool) false, 999 | Preferred: (bool) false 1000 | } 1001 | } 1002 | }, 1003 | (xrandr.Mode) { 1004 | Resolution: (xrandr.Size) { 1005 | Width: (float32) 700, 1006 | Height: (float32) 450 1007 | }, 1008 | RefreshRates: ([]xrandr.RefreshRate) (len=2 cap=2) { 1009 | (xrandr.RefreshRate) { 1010 | Value: (xrandr.RefreshRateValue) 59.96, 1011 | Current: (bool) false, 1012 | Preferred: (bool) false 1013 | }, 1014 | (xrandr.RefreshRate) { 1015 | Value: (xrandr.RefreshRateValue) 59.88, 1016 | Current: (bool) false, 1017 | Preferred: (bool) false 1018 | } 1019 | } 1020 | }, 1021 | (xrandr.Mode) { 1022 | Resolution: (xrandr.Size) { 1023 | Width: (float32) 640, 1024 | Height: (float32) 480 1025 | }, 1026 | RefreshRates: ([]xrandr.RefreshRate) (len=2 cap=2) { 1027 | (xrandr.RefreshRate) { 1028 | Value: (xrandr.RefreshRateValue) 60, 1029 | Current: (bool) false, 1030 | Preferred: (bool) false 1031 | }, 1032 | (xrandr.RefreshRate) { 1033 | Value: (xrandr.RefreshRateValue) 59.94, 1034 | Current: (bool) false, 1035 | Preferred: (bool) false 1036 | } 1037 | } 1038 | }, 1039 | (xrandr.Mode) { 1040 | Resolution: (xrandr.Size) { 1041 | Width: (float32) 720, 1042 | Height: (float32) 405 1043 | }, 1044 | RefreshRates: ([]xrandr.RefreshRate) (len=2 cap=2) { 1045 | (xrandr.RefreshRate) { 1046 | Value: (xrandr.RefreshRateValue) 59.51, 1047 | Current: (bool) false, 1048 | Preferred: (bool) false 1049 | }, 1050 | (xrandr.RefreshRate) { 1051 | Value: (xrandr.RefreshRateValue) 58.99, 1052 | Current: (bool) false, 1053 | Preferred: (bool) false 1054 | } 1055 | } 1056 | }, 1057 | (xrandr.Mode) { 1058 | Resolution: (xrandr.Size) { 1059 | Width: (float32) 684, 1060 | Height: (float32) 384 1061 | }, 1062 | RefreshRates: ([]xrandr.RefreshRate) (len=2 cap=2) { 1063 | (xrandr.RefreshRate) { 1064 | Value: (xrandr.RefreshRateValue) 59.88, 1065 | Current: (bool) false, 1066 | Preferred: (bool) false 1067 | }, 1068 | (xrandr.RefreshRate) { 1069 | Value: (xrandr.RefreshRateValue) 59.85, 1070 | Current: (bool) false, 1071 | Preferred: (bool) false 1072 | } 1073 | } 1074 | }, 1075 | (xrandr.Mode) { 1076 | Resolution: (xrandr.Size) { 1077 | Width: (float32) 640, 1078 | Height: (float32) 400 1079 | }, 1080 | RefreshRates: ([]xrandr.RefreshRate) (len=2 cap=2) { 1081 | (xrandr.RefreshRate) { 1082 | Value: (xrandr.RefreshRateValue) 59.88, 1083 | Current: (bool) false, 1084 | Preferred: (bool) false 1085 | }, 1086 | (xrandr.RefreshRate) { 1087 | Value: (xrandr.RefreshRateValue) 59.98, 1088 | Current: (bool) false, 1089 | Preferred: (bool) false 1090 | } 1091 | } 1092 | }, 1093 | (xrandr.Mode) { 1094 | Resolution: (xrandr.Size) { 1095 | Width: (float32) 640, 1096 | Height: (float32) 360 1097 | }, 1098 | RefreshRates: ([]xrandr.RefreshRate) (len=4 cap=4) { 1099 | (xrandr.RefreshRate) { 1100 | Value: (xrandr.RefreshRateValue) 59.86, 1101 | Current: (bool) false, 1102 | Preferred: (bool) false 1103 | }, 1104 | (xrandr.RefreshRate) { 1105 | Value: (xrandr.RefreshRateValue) 59.83, 1106 | Current: (bool) false, 1107 | Preferred: (bool) false 1108 | }, 1109 | (xrandr.RefreshRate) { 1110 | Value: (xrandr.RefreshRateValue) 59.84, 1111 | Current: (bool) false, 1112 | Preferred: (bool) false 1113 | }, 1114 | (xrandr.RefreshRate) { 1115 | Value: (xrandr.RefreshRateValue) 59.32, 1116 | Current: (bool) false, 1117 | Preferred: (bool) false 1118 | } 1119 | } 1120 | }, 1121 | (xrandr.Mode) { 1122 | Resolution: (xrandr.Size) { 1123 | Width: (float32) 512, 1124 | Height: (float32) 384 1125 | }, 1126 | RefreshRates: ([]xrandr.RefreshRate) (len=1 cap=1) { 1127 | (xrandr.RefreshRate) { 1128 | Value: (xrandr.RefreshRateValue) 60, 1129 | Current: (bool) false, 1130 | Preferred: (bool) false 1131 | } 1132 | } 1133 | }, 1134 | (xrandr.Mode) { 1135 | Resolution: (xrandr.Size) { 1136 | Width: (float32) 512, 1137 | Height: (float32) 288 1138 | }, 1139 | RefreshRates: ([]xrandr.RefreshRate) (len=2 cap=2) { 1140 | (xrandr.RefreshRate) { 1141 | Value: (xrandr.RefreshRateValue) 60, 1142 | Current: (bool) false, 1143 | Preferred: (bool) false 1144 | }, 1145 | (xrandr.RefreshRate) { 1146 | Value: (xrandr.RefreshRateValue) 59.92, 1147 | Current: (bool) false, 1148 | Preferred: (bool) false 1149 | } 1150 | } 1151 | }, 1152 | (xrandr.Mode) { 1153 | Resolution: (xrandr.Size) { 1154 | Width: (float32) 480, 1155 | Height: (float32) 270 1156 | }, 1157 | RefreshRates: ([]xrandr.RefreshRate) (len=2 cap=2) { 1158 | (xrandr.RefreshRate) { 1159 | Value: (xrandr.RefreshRateValue) 59.63, 1160 | Current: (bool) false, 1161 | Preferred: (bool) false 1162 | }, 1163 | (xrandr.RefreshRate) { 1164 | Value: (xrandr.RefreshRateValue) 59.82, 1165 | Current: (bool) false, 1166 | Preferred: (bool) false 1167 | } 1168 | } 1169 | }, 1170 | (xrandr.Mode) { 1171 | Resolution: (xrandr.Size) { 1172 | Width: (float32) 400, 1173 | Height: (float32) 300 1174 | }, 1175 | RefreshRates: ([]xrandr.RefreshRate) (len=2 cap=2) { 1176 | (xrandr.RefreshRate) { 1177 | Value: (xrandr.RefreshRateValue) 60.32, 1178 | Current: (bool) false, 1179 | Preferred: (bool) false 1180 | }, 1181 | (xrandr.RefreshRate) { 1182 | Value: (xrandr.RefreshRateValue) 56.34, 1183 | Current: (bool) false, 1184 | Preferred: (bool) false 1185 | } 1186 | } 1187 | }, 1188 | (xrandr.Mode) { 1189 | Resolution: (xrandr.Size) { 1190 | Width: (float32) 432, 1191 | Height: (float32) 243 1192 | }, 1193 | RefreshRates: ([]xrandr.RefreshRate) (len=2 cap=2) { 1194 | (xrandr.RefreshRate) { 1195 | Value: (xrandr.RefreshRateValue) 59.92, 1196 | Current: (bool) false, 1197 | Preferred: (bool) false 1198 | }, 1199 | (xrandr.RefreshRate) { 1200 | Value: (xrandr.RefreshRateValue) 59.57, 1201 | Current: (bool) false, 1202 | Preferred: (bool) false 1203 | } 1204 | } 1205 | }, 1206 | (xrandr.Mode) { 1207 | Resolution: (xrandr.Size) { 1208 | Width: (float32) 320, 1209 | Height: (float32) 240 1210 | }, 1211 | RefreshRates: ([]xrandr.RefreshRate) (len=1 cap=1) { 1212 | (xrandr.RefreshRate) { 1213 | Value: (xrandr.RefreshRateValue) 60.05, 1214 | Current: (bool) false, 1215 | Preferred: (bool) false 1216 | } 1217 | } 1218 | }, 1219 | (xrandr.Mode) { 1220 | Resolution: (xrandr.Size) { 1221 | Width: (float32) 360, 1222 | Height: (float32) 202 1223 | }, 1224 | RefreshRates: ([]xrandr.RefreshRate) (len=2 cap=2) { 1225 | (xrandr.RefreshRate) { 1226 | Value: (xrandr.RefreshRateValue) 59.51, 1227 | Current: (bool) false, 1228 | Preferred: (bool) false 1229 | }, 1230 | (xrandr.RefreshRate) { 1231 | Value: (xrandr.RefreshRateValue) 59.13, 1232 | Current: (bool) false, 1233 | Preferred: (bool) false 1234 | } 1235 | } 1236 | }, 1237 | (xrandr.Mode) { 1238 | Resolution: (xrandr.Size) { 1239 | Width: (float32) 320, 1240 | Height: (float32) 180 1241 | }, 1242 | RefreshRates: ([]xrandr.RefreshRate) (len=2 cap=2) { 1243 | (xrandr.RefreshRate) { 1244 | Value: (xrandr.RefreshRateValue) 59.84, 1245 | Current: (bool) false, 1246 | Preferred: (bool) false 1247 | }, 1248 | (xrandr.RefreshRate) { 1249 | Value: (xrandr.RefreshRateValue) 59.32, 1250 | Current: (bool) false, 1251 | Preferred: (bool) false 1252 | } 1253 | } 1254 | } 1255 | }, 1256 | Primary: (bool) false, 1257 | Size: (xrandr.Size) { 1258 | Width: (float32) 346, 1259 | Height: (float32) 194 1260 | }, 1261 | Connected: (bool) true, 1262 | Resolution: (xrandr.Size) { 1263 | Width: (float32) 3840, 1264 | Height: (float32) 2160 1265 | }, 1266 | Position: (xrandr.Position) { 1267 | X: (int) 5119, 1268 | Y: (int) 0 1269 | } 1270 | }, 1271 | (xrandr.Monitor) { 1272 | ID: (string) (len=6) "DP-1-1", 1273 | Modes: ([]xrandr.Mode) , 1274 | Primary: (bool) false, 1275 | Size: (xrandr.Size) { 1276 | Width: (float32) 0, 1277 | Height: (float32) 0 1278 | }, 1279 | Connected: (bool) false, 1280 | Resolution: (xrandr.Size) { 1281 | Width: (float32) 0, 1282 | Height: (float32) 0 1283 | }, 1284 | Position: (xrandr.Position) { 1285 | X: (int) 0, 1286 | Y: (int) 0 1287 | } 1288 | } 1289 | } 1290 | } 1291 | } 1292 | ``` 1293 | -------------------------------------------------------------------------------- /command.go: -------------------------------------------------------------------------------- 1 | package xrandr 2 | 3 | import ( 4 | "fmt" 5 | "os/exec" 6 | "strconv" 7 | ) 8 | 9 | // CommandBuilder xrandr command builder 10 | type CommandBuilder struct { 11 | dpi int 12 | screenSize Size 13 | outputCommandBuilders []OutputCommandBuilder 14 | } 15 | 16 | // OutputCommandBuilder xrandr command builder for flag --output 17 | type OutputCommandBuilder struct { 18 | monitor Monitor 19 | scale float32 20 | rotation Rotation 21 | leftOf *Monitor 22 | primary bool 23 | parent CommandBuilder 24 | } 25 | 26 | // DPI sets the dpi --dpi flag 27 | func (cb CommandBuilder) DPI(dpi int) CommandBuilder { 28 | cb.dpi = dpi 29 | 30 | return cb 31 | } 32 | 33 | // ScreenSize sets the screen size --fb flag 34 | func (cb CommandBuilder) ScreenSize(size Size) CommandBuilder { 35 | cb.screenSize = size 36 | 37 | return cb 38 | } 39 | 40 | // Output starts a new output command 41 | func (cb CommandBuilder) Output(monitor Monitor) OutputCommandBuilder { 42 | ocb := NewOutputCommand(monitor) 43 | ocb.parent = cb 44 | cb.AddOutputCommand(ocb) 45 | 46 | return ocb 47 | } 48 | 49 | // AddOutputCommand adds a new output command to command builder 50 | func (cb CommandBuilder) AddOutputCommand(ocb OutputCommandBuilder) CommandBuilder { 51 | cb.outputCommandBuilders = append(cb.outputCommandBuilders, ocb) 52 | 53 | return cb 54 | } 55 | 56 | // NewOutputCommand output command builder constructor 57 | func NewOutputCommand(monitor Monitor) OutputCommandBuilder { 58 | return OutputCommandBuilder{monitor: monitor} 59 | } 60 | 61 | // Scale sets the --scale flag 62 | func (ocb OutputCommandBuilder) Scale(scale float32) OutputCommandBuilder { 63 | ocb.scale = scale 64 | 65 | return ocb 66 | } 67 | 68 | // Rotate sets the --rotate flag 69 | func (ocb OutputCommandBuilder) Rotate(rotation Rotation) OutputCommandBuilder { 70 | ocb.rotation = rotation 71 | 72 | return ocb 73 | } 74 | 75 | // Position sets --position flag 76 | func (ocb OutputCommandBuilder) Position(position Position) OutputCommandBuilder { 77 | ocb.monitor.Position = position 78 | 79 | return ocb 80 | } 81 | 82 | // LeftOf sets --left-of flag 83 | func (ocb OutputCommandBuilder) LeftOf(monitor Monitor) OutputCommandBuilder { 84 | ocb.leftOf = &monitor 85 | 86 | return ocb 87 | } 88 | 89 | // Resolution sets --mode flag 90 | func (ocb OutputCommandBuilder) Resolution(resolution Size) OutputCommandBuilder { 91 | ocb.monitor.Resolution = resolution 92 | 93 | return ocb 94 | } 95 | 96 | // MakePrimary sets --primary flag 97 | func (ocb OutputCommandBuilder) MakePrimary() OutputCommandBuilder { 98 | ocb.monitor.Primary = true 99 | 100 | return ocb 101 | } 102 | 103 | // SetPrimary sets --primary flag 104 | func (ocb OutputCommandBuilder) SetPrimary(primary bool) OutputCommandBuilder { 105 | ocb.monitor.Primary = primary 106 | 107 | return ocb 108 | } 109 | 110 | // EndOutput ends the output command builder inside command builder 111 | func (ocb OutputCommandBuilder) EndOutput() CommandBuilder { 112 | ocb.parent.outputCommandBuilders = append(ocb.parent.outputCommandBuilders, ocb) 113 | 114 | return ocb.parent 115 | } 116 | 117 | func (ocb OutputCommandBuilder) getCommandArgs() ([]string, error) { 118 | var args []string 119 | 120 | if ocb.monitor.ID == "" { 121 | return args, nil 122 | } 123 | 124 | args = append(args, "--output", ocb.monitor.ID) 125 | if ocb.scale != 0 { 126 | args = append(args, "--scale", fmt.Sprintf("%0.3fx%0.3f", ocb.scale, ocb.scale)) 127 | } 128 | 129 | if ocb.rotation != "" { 130 | args = append(args, "--rotate", string(ocb.rotation)) 131 | } 132 | 133 | args = append(args, "--pos", fmt.Sprintf("%dx%d", ocb.monitor.Position.X, ocb.monitor.Position.Y)) 134 | 135 | mode, ok := ocb.monitor.CurrentMode() 136 | if !ok { 137 | return nil, fmt.Errorf(`cannot determin current mode for output "%s"`, ocb.monitor.ID) 138 | } 139 | 140 | res := mode.Resolution 141 | if res.Width > 0 && res.Height > 0 { 142 | args = append(args, "--mode", fmt.Sprintf("%dx%d", int(res.Width), int(res.Height))) 143 | } 144 | 145 | if ocb.leftOf != nil { 146 | args = append(args, "--left-of", ocb.leftOf.ID) 147 | } 148 | 149 | if ocb.monitor.Primary { 150 | args = append(args, "--primary") 151 | } 152 | 153 | return args, nil 154 | } 155 | 156 | func (cb CommandBuilder) getCommandArgs() []string { 157 | var args []string 158 | 159 | if cb.dpi > 0 { 160 | args = append(args, "--dpi", strconv.Itoa(cb.dpi)) 161 | } 162 | 163 | if cb.screenSize.Width > 0 && cb.screenSize.Height > 0 { 164 | args = append(args, "--fb", fmt.Sprintf("%dx%d", int(cb.screenSize.Width), int(cb.screenSize.Height))) 165 | } 166 | 167 | return args 168 | } 169 | 170 | // RunnableCommands returns all the xrandr commands 171 | func (cb CommandBuilder) RunnableCommands() ([]*exec.Cmd, error) { 172 | cmds := make([]*exec.Cmd, 0) 173 | args := cb.getCommandArgs() 174 | if len(args) > 0 { 175 | cmds = append(cmds, exec.Command("xrandr", cb.getCommandArgs()...)) 176 | } 177 | 178 | for i, ocb := range cb.outputCommandBuilders { 179 | args, err := ocb.getCommandArgs() 180 | if err != nil { 181 | return nil, err 182 | } 183 | 184 | if i == 0 && len(cmds) > 0 { 185 | cmds[0].Args = append(cmds[0].Args, args...) 186 | continue 187 | } 188 | 189 | cmds = append(cmds, exec.Command("xrandr", args...)) 190 | } 191 | 192 | return cmds, nil 193 | } 194 | 195 | // Run runs all the xrandr command returned by RunnableCommands method 196 | func (cb CommandBuilder) Run() error { 197 | cmds, err := cb.RunnableCommands() 198 | if err != nil { 199 | return nil 200 | } 201 | 202 | for _, cmd := range cmds { 203 | if err := cmd.Run(); err != nil { 204 | return err 205 | } 206 | } 207 | 208 | return nil 209 | } 210 | 211 | // Command CommandBuilder constructor 212 | func Command() CommandBuilder { 213 | return CommandBuilder{} 214 | } 215 | -------------------------------------------------------------------------------- /export_test.go: -------------------------------------------------------------------------------- 1 | package xrandr 2 | 3 | var ( 4 | ParseSize = parseSize 5 | ParseSizeWithPosition = parseSizeWithPosition 6 | ParseRate = parseRefreshRate 7 | ParseModeLine = parseModeLine 8 | ParseScreenLine = parseScreenLine 9 | ParseMonitorLine = parseMonitorLine 10 | IsScreenLine = isScreenLine 11 | IsMonitorLine = isMonitorLine 12 | ParseScreens = parseScreens 13 | ) 14 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/vcraescu/go-xrandr 2 | 3 | require ( 4 | github.com/davecgh/go-spew v1.1.1 // indirect 5 | github.com/pmezard/go-difflib v1.0.0 // indirect 6 | github.com/stretchr/testify v1.2.2 7 | ) 8 | 9 | go 1.13 10 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= 6 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 7 | -------------------------------------------------------------------------------- /xrandr.go: -------------------------------------------------------------------------------- 1 | package xrandr 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "math" 7 | "os/exec" 8 | "regexp" 9 | "strconv" 10 | "strings" 11 | ) 12 | 13 | // RefreshRateValue refresh rate value 14 | type RefreshRateValue float32 15 | 16 | // Rotation screen rotation 17 | type Rotation string 18 | 19 | const ( 20 | RotationNormal Rotation = "normal" 21 | RotationLeft Rotation = "left" 22 | RotationRight Rotation = "right" 23 | RotationInvert Rotation = "inverted" 24 | ) 25 | 26 | // Size screen size or resolution 27 | type Size struct { 28 | Width float32 `json:"width"` 29 | Height float32 `json:"height"` 30 | } 31 | 32 | // Position screen position 33 | type Position struct { 34 | X int `json:"x"` 35 | Y int `json:"y"` 36 | } 37 | 38 | // RefreshRate mode refresh rate 39 | type RefreshRate struct { 40 | Value RefreshRateValue `json:"value"` 41 | Current bool `json:"current"` 42 | Preferred bool `json:"preferred"` 43 | } 44 | 45 | // Mode xrandr output mode 46 | type Mode struct { 47 | Resolution Size `json:"resolution"` 48 | RefreshRates []RefreshRate `json:"refresh_rates"` 49 | } 50 | 51 | // Monitor all the info of xrandr output 52 | type Monitor struct { 53 | ID string `json:"id"` 54 | Modes []Mode `json:"modes"` 55 | Primary bool `json:"primary"` 56 | Size Size `json:"size"` 57 | Connected bool `json:"connected"` 58 | Resolution Size `json:"resolution"` 59 | Position Position `json:"position"` 60 | } 61 | 62 | // Screen all the info of xrandr screen 63 | type Screen struct { 64 | No int `json:"no"` 65 | CurrentResolution Size `json:"current_resolution"` 66 | MinResolution Size `json:"min_resolution"` 67 | MaxResolution Size `json:"max_resolution"` 68 | Monitors []Monitor `json:"monitors"` 69 | } 70 | 71 | // Screens slice of screens 72 | type Screens []Screen 73 | 74 | // MonitorByID returns the output with the given ID 75 | func (screens Screens) MonitorByID(ID string) (Monitor, bool) { 76 | m := Monitor{} 77 | 78 | for _, screen := range screens { 79 | m, ok := screen.MonitorByID(ID) 80 | if ok { 81 | return m, true 82 | } 83 | } 84 | 85 | return m, false 86 | } 87 | 88 | // MonitorByID returns the output with the given ID 89 | func (s Screen) MonitorByID(ID string) (Monitor, bool) { 90 | for _, monitor := range s.Monitors { 91 | if monitor.ID == ID { 92 | return monitor, true 93 | } 94 | } 95 | 96 | return Monitor{}, false 97 | } 98 | 99 | // CurrentRefreshRate returns mode current refresh rate 100 | func (md Mode) CurrentRefreshRate() (RefreshRate, bool) { 101 | for _, r := range md.RefreshRates { 102 | if r.Current { 103 | return r, true 104 | } 105 | } 106 | 107 | return RefreshRate{}, false 108 | } 109 | 110 | // CurrentMode returns monitor/output current mode 111 | func (m Monitor) CurrentMode() (Mode, bool) { 112 | for _, mode := range m.Modes { 113 | if _, ok := mode.CurrentRefreshRate(); ok { 114 | return mode, true 115 | } 116 | } 117 | 118 | return Mode{}, false 119 | } 120 | 121 | // SizeMM returns monitor/output size in milimeters 122 | func (m Monitor) SizeMM() Size { 123 | return m.Size 124 | } 125 | 126 | // SizeIn returns monitor/output size in inches 127 | func (m Monitor) SizeIn() Size { 128 | var mmToInch float32 = 0.0393701 129 | 130 | sizeIn := Size{ 131 | Width: m.Size.Width * mmToInch, 132 | Height: m.Size.Height * mmToInch, 133 | } 134 | 135 | return sizeIn 136 | } 137 | 138 | // DPI returns monitor/output dpi 139 | func (m Monitor) DPI() (float32, error) { 140 | hDPI, err := m.VerticalDPI() 141 | if err != nil { 142 | return 0, err 143 | } 144 | 145 | vDPI, err := m.HorizontalDPI() 146 | if err != nil { 147 | return 0, err 148 | } 149 | 150 | return float32(math.Min(float64(hDPI), float64(vDPI))), nil 151 | } 152 | 153 | // HorizontalDPI monitor horizontal dpi 154 | func (m Monitor) HorizontalDPI() (float32, error) { 155 | cm, ok := m.CurrentMode() 156 | if !ok { 157 | return 0, fmt.Errorf(`cannot determine monitor current mode: %s`, m.ID) 158 | } 159 | 160 | size := m.SizeIn() 161 | 162 | return float32(cm.Resolution.Width) / float32(size.Width), nil 163 | } 164 | 165 | // VerticalDPI monitor vertical dpi 166 | func (m Monitor) VerticalDPI() (float32, error) { 167 | cm, ok := m.CurrentMode() 168 | if !ok { 169 | return 0, fmt.Errorf(`cannot determine monitor current mode: %s`, m.ID) 170 | } 171 | 172 | size := m.SizeIn() 173 | 174 | return float32(cm.Resolution.Height) / float32(size.Height), nil 175 | } 176 | 177 | // Rescale recalculate monitor/output resolution 178 | func (s Size) Rescale(scale float32) Size { 179 | s.Width = float32(math.Ceil(float64(s.Width) * float64(scale))) 180 | s.Height = float32(math.Ceil(float64(s.Height) * float64(scale))) 181 | 182 | return s 183 | } 184 | 185 | // GetScreens returns all the screens info from xrandr output 186 | func GetScreens() (Screens, error) { 187 | _, err := exec.LookPath("xrandr") 188 | if err != nil { 189 | return nil, err 190 | } 191 | 192 | cmd := exec.Command("xrandr") 193 | output, err := cmd.Output() 194 | if err != nil { 195 | return nil, err 196 | } 197 | 198 | screens, err := parseScreens(string(output)) 199 | if err != nil { 200 | return nil, err 201 | } 202 | 203 | return screens, nil 204 | } 205 | 206 | func parseScreenLine(line string) (*Screen, error) { 207 | line = strings.TrimSpace(line) 208 | if !strings.HasPrefix(line, "Screen") { 209 | return nil, fmt.Errorf("invalid screen line: %s", line) 210 | } 211 | 212 | re := regexp.MustCompile(`Screen \d+`) 213 | screenStr := re.FindString(line) 214 | if screenStr == "" { 215 | return nil, fmt.Errorf("unexpected screen line format: %s", line) 216 | } 217 | no, err := strconv.Atoi(strings.Split(screenStr, " ")[1]) 218 | if err != nil { 219 | return nil, fmt.Errorf("error parsing screen number: %s", err) 220 | } 221 | 222 | screen := &Screen{ 223 | No: no, 224 | } 225 | 226 | parseScreenResolution := func(s, typ string) (*Size, error) { 227 | if !strings.HasPrefix(s, typ) { 228 | return nil, fmt.Errorf("expected to start with %s", typ) 229 | } 230 | s = strings.Replace(s, typ, "", -1) 231 | s = strings.TrimSpace(s) 232 | resolution, err := parseSize(s) 233 | if err != nil { 234 | return nil, fmt.Errorf("error parsing %s screen resolution: %s", typ, err) 235 | } 236 | 237 | return resolution, nil 238 | } 239 | 240 | for _, typ := range []string{"minimum", "current", "maximum"} { 241 | re = regexp.MustCompile(fmt.Sprintf(`%s \d+\s*x\s*\d+`, typ)) 242 | resStr := re.FindString(line) 243 | if resStr == "" { 244 | return nil, fmt.Errorf("%s resolution could not be found: %s", typ, line) 245 | } 246 | 247 | resolution, err := parseScreenResolution(resStr, typ) 248 | if err != nil { 249 | return nil, err 250 | } 251 | 252 | switch typ { 253 | case "minimum": 254 | screen.MinResolution = *resolution 255 | case "current": 256 | screen.CurrentResolution = *resolution 257 | case "maximum": 258 | screen.MaxResolution = *resolution 259 | } 260 | } 261 | 262 | return screen, nil 263 | } 264 | 265 | func parseMonitorLine(line string) (*Monitor, error) { 266 | line = strings.TrimSpace(line) 267 | tokens := strings.SplitN(line, " ", 2) 268 | if len(tokens) != 2 { 269 | return nil, fmt.Errorf("invalid monitor line format: %s", line) 270 | } 271 | 272 | id := tokens[0] 273 | monitor := Monitor{ 274 | ID: id, 275 | } 276 | 277 | connected := strings.Contains(line, " connected ") 278 | primary := strings.Contains(line, "primary") 279 | 280 | re := regexp.MustCompile(`\d+mm\s*x\s*\d+mm`) 281 | sizeStr := re.FindString(tokens[1]) 282 | 283 | if sizeStr == "" { 284 | if connected { 285 | return nil, fmt.Errorf("could not determine monitor size (mm), expected WWWmm x HHHmm: %s", line) 286 | } 287 | // Disconnected screens might or might not have a size set. Default to a zero one if not present. 288 | sizeStr = "0mm x 0mm" 289 | } 290 | 291 | sizeStr = strings.Replace(sizeStr, "mm", "", 2) 292 | size, err := parseSize(sizeStr) 293 | if err != nil { 294 | return nil, fmt.Errorf("error parsing monitor size: %s", err) 295 | } 296 | 297 | re = regexp.MustCompile(`\d+\s*x\s*\d+\+\d+\+\d+`) 298 | resStr := re.FindString(tokens[1]) 299 | if resStr == "" { 300 | if connected { 301 | return nil, fmt.Errorf("could not determine monitor resolution and position, expected WxH+X+Y: %s", line) 302 | } 303 | // Disconnected screens might or might not have a resolution set. Default to a zero one if not present. 304 | resStr = "0x0+0+0" 305 | } 306 | 307 | resolution, position, err := parseSizeWithPosition(resStr) 308 | if err != nil { 309 | return nil, fmt.Errorf("could not determine monitor resolution and position: %s", err) 310 | } 311 | 312 | monitor.Connected = connected 313 | monitor.Primary = primary 314 | monitor.Size = *size 315 | monitor.Position = *position 316 | monitor.Resolution = *resolution 317 | 318 | return &monitor, nil 319 | } 320 | 321 | func parseModeLine(line string) (*Mode, error) { 322 | line = strings.TrimSpace(line) 323 | mode := Mode{} 324 | 325 | ws := bufio.NewScanner(strings.NewReader(line)) 326 | ws.Split(bufio.ScanWords) 327 | for ws.Scan() { 328 | w := ws.Text() 329 | if strings.Contains(w, "x") { 330 | res, err := parseSize(w) 331 | if err != nil { 332 | return nil, err 333 | } 334 | 335 | mode.Resolution = *res 336 | continue 337 | } 338 | 339 | rate, err := parseRefreshRate(w) 340 | if err != nil { 341 | return nil, err 342 | } 343 | 344 | mode.RefreshRates = append(mode.RefreshRates, *rate) 345 | } 346 | 347 | return &mode, nil 348 | } 349 | 350 | func parseSize(s string) (*Size, error) { 351 | if !strings.Contains(s, "x") { 352 | return nil, fmt.Errorf("invalid size format; expected format WxH but got %s", s) 353 | } 354 | 355 | res := strings.Split(s, "x") 356 | width, err := strconv.Atoi(strings.TrimSpace(res[0])) 357 | if err != nil { 358 | return nil, fmt.Errorf("could not parse mode width size (%s): %s", s, err) 359 | } 360 | 361 | height, err := strconv.Atoi(strings.TrimSpace(res[1])) 362 | if err != nil { 363 | return nil, fmt.Errorf("could not parse mode height size (%s): %s", s, err) 364 | } 365 | 366 | return &Size{ 367 | Width: float32(width), 368 | Height: float32(height), 369 | }, nil 370 | } 371 | 372 | func parseSizeWithPosition(s string) (*Size, *Position, error) { 373 | tokens := strings.SplitN(s, "+", 2) 374 | size, err := parseSize(tokens[0]) 375 | if err != nil { 376 | return nil, nil, fmt.Errorf("invalid resolution with position format; expected WxH+X+Y, got %s: %s", s, err) 377 | } 378 | 379 | tokens = strings.Split(tokens[1], "+") 380 | if len(tokens) != 2 { 381 | return nil, nil, fmt.Errorf("invalid position format; expected X+Y, got %s", tokens) 382 | } 383 | 384 | x, err := strconv.Atoi(strings.TrimSpace(tokens[0])) 385 | if err != nil { 386 | return nil, nil, fmt.Errorf("invalid position X: %s", err) 387 | } 388 | 389 | y, err := strconv.Atoi(strings.TrimSpace(tokens[1])) 390 | if err != nil { 391 | return nil, nil, fmt.Errorf("invalid position Y: %s", err) 392 | } 393 | 394 | position := Position{ 395 | X: x, 396 | Y: y, 397 | } 398 | 399 | return size, &position, nil 400 | } 401 | 402 | func parseRefreshRate(s string) (*RefreshRate, error) { 403 | s = strings.TrimSpace(s) 404 | current := strings.Contains(s, "*") 405 | preferred := strings.Contains(s, "+") 406 | 407 | s = strings.TrimSpace(strings.Trim(s, "*+ ")) 408 | value, err := strconv.ParseFloat(s, 32) 409 | if err != nil { 410 | return nil, fmt.Errorf("invalid rate value (%s): %s", s, err) 411 | } 412 | 413 | return &RefreshRate{ 414 | Value: RefreshRateValue(value), 415 | Current: current, 416 | Preferred: preferred, 417 | }, nil 418 | } 419 | 420 | func isScreenLine(l string) bool { 421 | return strings.HasPrefix(l, "Screen") 422 | } 423 | 424 | func isMonitorLine(l string) bool { 425 | return strings.Contains(l, "connected") || strings.Contains(l, "disconnected") 426 | } 427 | 428 | func parseScreens(s string) ([]Screen, error) { 429 | var screens []Screen 430 | 431 | ls := bufio.NewScanner(strings.NewReader(s)) 432 | ls.Split(bufio.ScanLines) 433 | 434 | var err error 435 | var screen *Screen 436 | var monitor *Monitor 437 | var mode *Mode 438 | 439 | for ls.Scan() { 440 | l := ls.Text() 441 | if isScreenLine(l) { 442 | if screen != nil { 443 | screens = append(screens, *screen) 444 | } 445 | 446 | screen, err = parseScreenLine(l) 447 | if err != nil { 448 | return nil, err 449 | } 450 | 451 | continue 452 | } 453 | 454 | if isMonitorLine(l) { 455 | if monitor != nil { 456 | screen.Monitors = append(screen.Monitors, *monitor) 457 | } 458 | 459 | monitor, err = parseMonitorLine(l) 460 | if err != nil { 461 | return nil, err 462 | } 463 | 464 | continue 465 | } 466 | 467 | if monitor != nil && !monitor.Connected { 468 | continue 469 | } 470 | 471 | mode, err = parseModeLine(l) 472 | if err != nil { 473 | return nil, err 474 | } 475 | 476 | monitor.Modes = append(monitor.Modes, *mode) 477 | } 478 | 479 | screen.Monitors = append(screen.Monitors, *monitor) 480 | screens = append(screens, *screen) 481 | 482 | return screens, nil 483 | } 484 | -------------------------------------------------------------------------------- /xrandr_test.go: -------------------------------------------------------------------------------- 1 | package xrandr_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "github.com/vcraescu/go-xrandr" 8 | ) 9 | 10 | func TestParseSize(t *testing.T) { 11 | r, err := xrandr.ParseSize("3000x1200") 12 | assert.NoError(t, err) 13 | 14 | assert.Equal(t, float32(3000), r.Width) 15 | assert.Equal(t, float32(1200), r.Height) 16 | 17 | r, err = xrandr.ParseSize("\n3000 x 1200 \t") 18 | assert.NoError(t, err) 19 | assert.Equal(t, float32(3000), r.Width) 20 | assert.Equal(t, float32(1200), r.Height) 21 | 22 | r, err = xrandr.ParseSize("\n3000 1200 \t") 23 | assert.Error(t, err) 24 | } 25 | 26 | func TestParseSizeWithPosition(t *testing.T) { 27 | s, p, err := xrandr.ParseSizeWithPosition("3000x1200+1200+400") 28 | assert.NoError(t, err) 29 | 30 | assert.Equal(t, float32(3000), s.Width) 31 | assert.Equal(t, float32(1200), s.Height) 32 | assert.Equal(t, 1200, p.X) 33 | assert.Equal(t, 400, p.Y) 34 | 35 | _, _, err = xrandr.ParseSizeWithPosition("3000x1200 + 1200 + 400") 36 | assert.NoError(t, err) 37 | } 38 | 39 | func TestParseRefreshRate(t *testing.T) { 40 | r, err := xrandr.ParseRate(" 30.12 \t \n") 41 | assert.NoError(t, err) 42 | assert.Equal(t, xrandr.RefreshRateValue(30.12), r.Value) 43 | assert.False(t, r.Current) 44 | assert.False(t, r.Preferred) 45 | 46 | r, err = xrandr.ParseRate(" 30.12+ ") 47 | assert.NoError(t, err) 48 | assert.Equal(t, xrandr.RefreshRateValue(30.12), r.Value) 49 | assert.False(t, r.Current) 50 | assert.True(t, r.Preferred) 51 | 52 | r, err = xrandr.ParseRate(" 30.12* ") 53 | assert.NoError(t, err) 54 | assert.Equal(t, xrandr.RefreshRateValue(30.12), r.Value) 55 | assert.True(t, r.Current) 56 | assert.False(t, r.Preferred) 57 | 58 | r, err = xrandr.ParseRate(" 30.12+* ") 59 | assert.NoError(t, err) 60 | assert.Equal(t, xrandr.RefreshRateValue(30.12), r.Value) 61 | assert.True(t, r.Current) 62 | assert.True(t, r.Preferred) 63 | 64 | r, err = xrandr.ParseRate(" 30.12 + * ") 65 | assert.NoError(t, err) 66 | assert.Equal(t, xrandr.RefreshRateValue(30.12), r.Value) 67 | assert.True(t, r.Current) 68 | assert.True(t, r.Preferred) 69 | 70 | r, err = xrandr.ParseRate("60.05") 71 | assert.NoError(t, err) 72 | assert.Equal(t, xrandr.RefreshRateValue(60.05), r.Value) 73 | assert.False(t, r.Current) 74 | assert.False(t, r.Preferred) 75 | 76 | r, err = xrandr.ParseRate("23.98+") 77 | assert.NoError(t, err) 78 | assert.Equal(t, xrandr.RefreshRateValue(23.98), r.Value) 79 | assert.False(t, r.Current) 80 | assert.True(t, r.Preferred) 81 | } 82 | 83 | func TestParseModeLine(t *testing.T) { 84 | m, err := xrandr.ParseModeLine(" \t 1920x1080 60.00 59.94 50.00 23.98 60.05 60.00 50.04 \t") 85 | assert.NoError(t, err) 86 | assert.Equal(t, float32(1920), m.Resolution.Width) 87 | assert.Equal(t, float32(1080), m.Resolution.Height) 88 | assert.Len(t, m.RefreshRates, 7) 89 | assert.Equal(t, xrandr.RefreshRateValue(60), m.RefreshRates[0].Value) 90 | assert.Equal(t, xrandr.RefreshRateValue(59.94), m.RefreshRates[1].Value) 91 | assert.Equal(t, xrandr.RefreshRateValue(50.00), m.RefreshRates[2].Value) 92 | assert.Equal(t, xrandr.RefreshRateValue(23.98), m.RefreshRates[3].Value) 93 | assert.Equal(t, xrandr.RefreshRateValue(60.05), m.RefreshRates[4].Value) 94 | assert.Equal(t, xrandr.RefreshRateValue(60.00), m.RefreshRates[5].Value) 95 | assert.Equal(t, xrandr.RefreshRateValue(50.04), m.RefreshRates[6].Value) 96 | 97 | m, err = xrandr.ParseModeLine(" \t 1920x1080 60.00 59.94* 50.00 23.98+ 60.05 60.00 50.04 \t") 98 | assert.NoError(t, err) 99 | assert.Equal(t, float32(1920), m.Resolution.Width) 100 | assert.Equal(t, float32(1080), m.Resolution.Height) 101 | assert.Equal(t, xrandr.RefreshRateValue(59.94), m.RefreshRates[1].Value) 102 | assert.True(t, m.RefreshRates[1].Current) 103 | 104 | assert.Equal(t, xrandr.RefreshRateValue(23.98), m.RefreshRates[3].Value) 105 | assert.True(t, m.RefreshRates[3].Preferred) 106 | } 107 | 108 | func TestParseScreenLine(t *testing.T) { 109 | s, err := xrandr.ParseScreenLine(" Screen 3: minimum 8 x 8, current 9600 x 3240, maximum 32767 x 32767 ") 110 | assert.NoError(t, err) 111 | assert.Equal(t, 3, s.No) 112 | assert.Equal(t, float32(8), s.MinResolution.Width) 113 | assert.Equal(t, float32(8), s.MinResolution.Height) 114 | 115 | assert.Equal(t, float32(9600), s.CurrentResolution.Width) 116 | assert.Equal(t, float32(3240), s.CurrentResolution.Height) 117 | 118 | assert.Equal(t, float32(32767), s.MaxResolution.Width) 119 | assert.Equal(t, float32(32767), s.MaxResolution.Height) 120 | } 121 | 122 | func TestParseMonitorLine(t *testing.T) { 123 | m, err := xrandr.ParseMonitorLine(" HDMI-0 connected primary 5760x3240+1000+500 (normal left inverted right x axis y axis) 597mm x 336mm") 124 | assert.NoError(t, err) 125 | assert.Equal(t, "HDMI-0", m.ID) 126 | assert.True(t, m.Connected) 127 | assert.True(t, m.Primary) 128 | assert.Equal(t, float32(597), m.Size.Width) 129 | assert.Equal(t, float32(336), m.Size.Height) 130 | assert.Equal(t, float32(5760), m.Resolution.Width) 131 | assert.Equal(t, float32(3240), m.Resolution.Height) 132 | assert.Equal(t, 1000, m.Position.X) 133 | assert.Equal(t, 500, m.Position.Y) 134 | 135 | m, err = xrandr.ParseMonitorLine("DP-1-1 disconnected (normal left inverted right x axis y axis)") 136 | assert.NoError(t, err) 137 | assert.Equal(t, "DP-1-1", m.ID) 138 | } 139 | 140 | func TestIsScreenLine(t *testing.T) { 141 | b := xrandr.IsScreenLine("Screen 0: minimum 8 x 8, current 9408 x 3132, maximum 32767 x 32767") 142 | assert.True(t, b) 143 | 144 | b = xrandr.IsScreenLine(" minimum 8 x 8, Screen 0, current 9408 x 3132, maximum 32767 x 32767") 145 | assert.False(t, b) 146 | 147 | b = xrandr.IsScreenLine(" minimum 8 x 8, current 9408 x 3132, maximum 32767 x 32767") 148 | assert.False(t, b) 149 | } 150 | 151 | func TestIsMonitorLine(t *testing.T) { 152 | b := xrandr.IsMonitorLine("HDMI-0 connected primary 5568x3132+0+0 (normal left inverted right x axis y axis) 597mm x 336mm") 153 | assert.True(t, b) 154 | 155 | b = xrandr.IsMonitorLine("HDMI-0 disconnected primary 5568x3132+0+0 (normal left inverted right x axis y axis) 597mm x 336mm") 156 | assert.True(t, b) 157 | 158 | b = xrandr.IsMonitorLine(" minimum 8 x 8, current 9408 x 3132, maximum 32767 x 32767") 159 | assert.False(t, b) 160 | } 161 | 162 | func TestParseScreens(t *testing.T) { 163 | screens, err := xrandr.ParseScreens(`Screen 0: minimum 8 x 8, current 9408 x 3132, maximum 32767 x 32767 164 | HDMI-0 connected primary 5568x3132+0+0 (normal left inverted right x axis y axis) 597mm x 336mm 165 | 3840x2160 30.00*+ 29.97 25.00 23.98 166 | 1920x1200 59.88 167 | 1920x1080 60.00 59.94 50.00 23.98 60.05 60.00 50.04 168 | 1680x1050 59.95 169 | 1600x1200 60.00 170 | 1280x1024 75.02 60.02 171 | 1280x800 59.81 172 | 1280x720 60.00 59.94 50.00 173 | 1152x864 75.00 174 | 1024x768 75.03 60.00 175 | 800x600 75.00 60.32 176 | 720x576 50.00 177 | 720x480 59.94 178 | 640x480 75.00 59.94 59.93 179 | HDMI-1 disconnected primary 5568x3132+0+0 (normal left inverted right x axis y axis) 597mm x 336mm 180 | eDP-1-1 connected 3840x2160+5568+0 (normal left inverted right x axis y axis) 346mm x 194mm 181 | 3840x2160 60.00*+ 59.98 48.02 59.97 182 | 3200x1800 59.96 59.94 183 | 2880x1620 59.96 59.97 184 | 2560x1600 59.99 59.97 185 | 2560x1440 59.99 59.99 59.96 59.95 186 | 2048x1536 60.00 187 | 1920x1440 60.00 188 | 1856x1392 60.01 189 | 1792x1344 60.01 190 | 2048x1152 59.99 59.98 59.90 59.91 191 | 1920x1200 59.88 59.95 192 | 1920x1080 60.01 59.97 59.96 59.93 193 | DP-1-1 disconnected (normal left inverted right x axis y axis) 194 | 1920x1200 (0x5b) 193.250MHz -HSync +VSync 195 | h: width 1920 start 2056 end 2256 total 2592 skew 0 clock 74.56KHz 196 | v: height 1200 start 1203 end 1209 total 1245 clock 59.88Hz 197 | 1600x1200 (0x61) 162.000MHz +HSync +VSync 198 | h: width 1600 start 1664 end 1856 total 2160 skew 0 clock 75.00KHz 199 | v: height 1200 start 1201 end 1204 total 1250 clock 60.00Hz 200 | 1680x1050 (0x62) 146.250MHz -HSync +VSync 201 | h: width 1680 start 1784 end 1960 total 2240 skew 0 clock 65.29KHz 202 | v: height 1050 start 1053 end 1059 total 1089 clock 59.95Hz 203 | 1280x1024 (0x69) 108.000MHz +HSync +VSync 204 | h: width 1280 start 1328 end 1440 total 1688 skew 0 clock 63.98KHz 205 | v: height 1024 start 1025 end 1028 total 1066 clock 60.02Hz 206 | 1280x800 (0x73) 83.500MHz -HSync +VSync 207 | h: width 1280 start 1352 end 1480 total 1680 skew 0 clock 49.70KHz 208 | v: height 800 start 803 end 809 total 831 clock 59.81Hz 209 | 1024x768 (0x7a) 65.000MHz -HSync -VSync 210 | h: width 1024 start 1048 end 1184 total 1344 skew 0 clock 48.36KHz 211 | v: height 768 start 771 end 777 total 806 clock 60.00Hz 212 | 800x600 (0x89) 40.000MHz +HSync +VSync 213 | h: width 800 start 840 end 968 total 1056 skew 0 clock 37.88KHz 214 | v: height 600 start 601 end 605 total 628 clock 60.32Hz 215 | 640x480 (0x96) 25.175MHz -HSync -VSync 216 | h: width 640 start 656 end 752 total 800 skew 0 clock 31.47KHz 217 | v: height 480 start 490 end 492 total 525 clock 59.94Hz 218 | eDP-2-2 connected 3840x2160+5568+0 (normal left inverted right x axis y axis) 346mm x 194mm 219 | 2048x1152 59.99 59.98 59.90 59.91 220 | 1920x1200 59.88 59.95 221 | 1920x1080 60.01 59.97 59.96 59.93 222 | `) 223 | 224 | assert.NoError(t, err) 225 | assert.Len(t, screens, 1) 226 | screen := screens[0] 227 | assert.Equal(t, 0, screen.No) 228 | assert.Equal(t, xrandr.Size{ 229 | Width: 8, 230 | Height: 8, 231 | }, screens[0].MinResolution) 232 | assert.Equal(t, xrandr.Size{ 233 | Width: 9408, 234 | Height: 3132, 235 | }, screen.CurrentResolution) 236 | assert.Equal(t, xrandr.Size{ 237 | Width: 32767, 238 | Height: 32767, 239 | }, screen.MaxResolution) 240 | assert.Len(t, screen.Monitors, 5) 241 | 242 | monitor := screen.Monitors[0] 243 | assert.Equal(t, "HDMI-0", monitor.ID) 244 | assert.True(t, monitor.Connected) 245 | assert.True(t, monitor.Primary) 246 | 247 | assert.Equal(t, xrandr.Size{ 248 | Width: 5568, 249 | Height: 3132, 250 | }, monitor.Resolution) 251 | assert.Equal(t, xrandr.Position{ 252 | X: 0, 253 | Y: 0, 254 | }, monitor.Position) 255 | assert.Equal(t, xrandr.Size{ 256 | Width: 597, 257 | Height: 336, 258 | }, monitor.Size) 259 | assert.Len(t, monitor.Modes, 14) 260 | assert.Equal(t, xrandr.Size{ 261 | Width: 3840, 262 | Height: 2160, 263 | }, monitor.Modes[0].Resolution) 264 | assert.Equal(t, xrandr.RefreshRate{ 265 | Value: 30, 266 | Current: true, 267 | Preferred: true, 268 | }, monitor.Modes[0].RefreshRates[0]) 269 | assert.Equal(t, xrandr.RefreshRate{ 270 | Value: 23.98, 271 | Current: false, 272 | Preferred: false, 273 | }, monitor.Modes[0].RefreshRates[3]) 274 | 275 | assert.Equal(t, xrandr.Size{ 276 | Width: 640, 277 | Height: 480, 278 | }, monitor.Modes[13].Resolution) 279 | assert.Equal(t, xrandr.RefreshRate{ 280 | Value: 75, 281 | Current: false, 282 | Preferred: false, 283 | }, monitor.Modes[13].RefreshRates[0]) 284 | assert.Equal(t, xrandr.RefreshRate{ 285 | Value: 59.93, 286 | Current: false, 287 | Preferred: false, 288 | }, monitor.Modes[13].RefreshRates[2]) 289 | 290 | monitor = screen.Monitors[1] 291 | assert.Equal(t, "HDMI-1", monitor.ID) 292 | assert.False(t, monitor.Connected) 293 | assert.Equal(t, xrandr.Size{ 294 | Width: 5568, 295 | Height: 3132, 296 | }, monitor.Resolution) 297 | assert.Equal(t, xrandr.Position{ 298 | X: 0, 299 | Y: 0, 300 | }, monitor.Position) 301 | assert.Equal(t, xrandr.Size{ 302 | Width: 597, 303 | Height: 336, 304 | }, monitor.Size) 305 | assert.Len(t, monitor.Modes, 0) 306 | 307 | monitor = screen.Monitors[2] 308 | assert.Equal(t, "eDP-1-1", monitor.ID) 309 | assert.True(t, monitor.Connected) 310 | assert.False(t, monitor.Primary) 311 | assert.Equal(t, xrandr.Size{ 312 | Width: 3840, 313 | Height: 2160, 314 | }, monitor.Resolution) 315 | assert.Equal(t, xrandr.Position{ 316 | X: 5568, 317 | Y: 0, 318 | }, monitor.Position) 319 | assert.Equal(t, xrandr.Size{ 320 | Width: 346, 321 | Height: 194, 322 | }, monitor.Size) 323 | assert.Len(t, monitor.Modes, 12) 324 | 325 | assert.Equal(t, xrandr.Size{ 326 | Width: 3840, 327 | Height: 2160, 328 | }, monitor.Modes[0].Resolution) 329 | assert.Equal(t, xrandr.RefreshRate{ 330 | Value: 60, 331 | Current: true, 332 | Preferred: true, 333 | }, monitor.Modes[0].RefreshRates[0]) 334 | 335 | monitor = screen.Monitors[3] 336 | assert.Equal(t, "DP-1-1", monitor.ID) 337 | assert.False(t, monitor.Connected) 338 | assert.False(t, monitor.Primary) 339 | assert.Len(t, monitor.Modes, 0) 340 | 341 | monitor = screen.Monitors[4] 342 | assert.Equal(t, "eDP-2-2", monitor.ID) 343 | assert.True(t, monitor.Connected) 344 | assert.False(t, monitor.Primary) 345 | assert.Len(t, monitor.Modes, 3) 346 | } 347 | 348 | func TestScreen_GeMonitorByID(t *testing.T) { 349 | monitors := []xrandr.Monitor{ 350 | {ID: "Monitor1"}, 351 | {ID: "Monitor2"}, 352 | {ID: "Monitor3"}, 353 | } 354 | 355 | screen := xrandr.Screen{Monitors: monitors} 356 | m, ok := screen.MonitorByID("Monitor2") 357 | assert.True(t, ok) 358 | assert.Equal(t, "Monitor2", m.ID) 359 | } 360 | 361 | func TestScreens_GeMonitorByID(t *testing.T) { 362 | monitors := []xrandr.Monitor{ 363 | {ID: "Monitor1"}, 364 | {ID: "Monitor2"}, 365 | {ID: "Monitor3"}, 366 | } 367 | 368 | screen := xrandr.Screen{Monitors: monitors} 369 | screens := xrandr.Screens{screen} 370 | m, ok := screens.MonitorByID("Monitor3") 371 | assert.True(t, ok) 372 | assert.Equal(t, "Monitor3", m.ID) 373 | } 374 | 375 | func TestMode_CurrentRefreshRate(t *testing.T) { 376 | refreshRates := []xrandr.RefreshRate{ 377 | {Current: false, Value: xrandr.RefreshRateValue(60.0)}, 378 | {Current: false, Value: xrandr.RefreshRateValue(30.0)}, 379 | {Current: true, Value: xrandr.RefreshRateValue(144)}, 380 | } 381 | 382 | mode := xrandr.Mode{RefreshRates: refreshRates} 383 | 384 | crr, ok := mode.CurrentRefreshRate() 385 | assert.True(t, ok) 386 | assert.Equal(t, xrandr.RefreshRateValue(144.0), crr.Value) 387 | } 388 | 389 | func TestMonitor_CurrentMode(t *testing.T) { 390 | refreshRates1 := []xrandr.RefreshRate{ 391 | {Current: false, Value: xrandr.RefreshRateValue(60)}, 392 | {Current: false, Value: xrandr.RefreshRateValue(30)}, 393 | {Current: false, Value: xrandr.RefreshRateValue(144)}, 394 | } 395 | refreshRates2 := []xrandr.RefreshRate{ 396 | {Current: true, Value: xrandr.RefreshRateValue(30)}, 397 | {Current: false, Value: xrandr.RefreshRateValue(54)}, 398 | } 399 | 400 | monitor := xrandr.Monitor{ 401 | Modes: []xrandr.Mode{ 402 | {RefreshRates: refreshRates1}, 403 | {RefreshRates: refreshRates2}, 404 | }, 405 | } 406 | 407 | _, ok := monitor.CurrentMode() 408 | assert.True(t, ok) 409 | } 410 | 411 | func TestMonitor_DPI(t *testing.T) { 412 | monitor := xrandr.Monitor{ 413 | Modes: []xrandr.Mode{ 414 | { 415 | RefreshRates: []xrandr.RefreshRate{ 416 | {Current: true, Value: xrandr.RefreshRateValue(30)}, 417 | {Current: false, Value: xrandr.RefreshRateValue(54)}, 418 | }, 419 | Resolution: xrandr.Size{ 420 | Width: 3840, 421 | Height: 2160, 422 | }, 423 | }, 424 | }, 425 | 426 | Size: xrandr.Size{ 427 | Width: 487, 428 | Height: 247, 429 | }, 430 | } 431 | 432 | dpi, err := monitor.DPI() 433 | assert.Nil(t, err) 434 | assert.Equal(t, 200, int(dpi)) 435 | } 436 | 437 | func TestSize_Rescale(t *testing.T) { 438 | s := xrandr.Size{Width: 1000, Height: 2000} 439 | s = s.Rescale(1.333333) 440 | 441 | assert.Equal(t, float32(1334), s.Width) 442 | assert.Equal(t, float32(2667), s.Height) 443 | } 444 | --------------------------------------------------------------------------------