├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── AUTHORS ├── ChangeLog ├── LICENSE ├── README.md ├── bump-version.py ├── com.system76.pkexec.system76-driver.policy ├── debian ├── changelog ├── compat ├── conffiles ├── control ├── copyright ├── rules ├── source │ └── format ├── source_system76-driver.py ├── system76-driver-nvidia.install ├── system76-driver-nvidia.postinst ├── system76-driver.gsettings-override ├── system76-driver.install ├── system76-driver.postinst ├── system76-driver.service └── system76-driver.upstart ├── dmi-test ├── lib └── systemd │ └── system-sleep │ └── system76-driver_bluetooth-suspend ├── make-release.py ├── po ├── ChangeLog └── POTFILES.in ├── quirks ├── system76-nvidia-quirks-bonw11 ├── system76-nvidia-quirks-oryp2 ├── system76-nvidia-quirks-oryp2-ess ├── system76-nvidia-quirks-oryp3 ├── system76-nvidia-quirks-oryp3-b ├── system76-nvidia-quirks-oryp3-ess ├── system76-nvidia-quirks-serw10 └── system76-nvidia-quirks-thelio-massive-b1 ├── setup.py ├── system76-apt-preferences ├── system76-daemon ├── system76-driver ├── system76-driver-cli ├── system76-driver.desktop ├── system76-driver.svg ├── system76-nm-restart ├── system76-third-party-mirrors.cfg ├── system76-thunderbolt-reload ├── system76-user-daemon ├── system76-user-daemon.desktop ├── system76-virtual-hub └── system76driver ├── __init__.py ├── actions.py ├── daemon.py ├── data ├── 76icon.svg ├── 76icon_primary.svg ├── analog-input-internal-mic.conf ├── gtk3.glade ├── iec958-stereo-output.conf ├── system76-switch-internal-speakers.conf ├── system76_logo_primary.svg └── system76_logo_white.svg ├── gtk.py ├── mockable.py ├── model.py ├── products.py ├── tests ├── __init__.py ├── helpers.py ├── run.py ├── test_actions.py ├── test_daemon.py ├── test_gtk.py ├── test_mockable.py ├── test_model.py ├── test_products.py └── test_util.py ├── userdaemon.py └── util.py /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | **Distribution (run `cat /etc/os-release`):** 9 | 10 | 11 | 12 | **Related Application and/or Package Version (run `apt policy $PACKAGE NAME`):** 13 | 14 | 15 | 16 | **Issue/Bug Description:** 17 | 18 | 19 | 20 | **Steps to reproduce (if you know):** 21 | 22 | 23 | 24 | **Expected behavior:** 25 | 26 | 27 | 28 | **Other Notes:** 29 | 30 | 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.pybuild 2 | /build/ 3 | /debian/files 4 | /debian/system76-driver/ 5 | /debian/system76-driver-nvidia/ 6 | /debian/*debhelper* 7 | /debian/*.substvars 8 | __pycache__ 9 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Code 2 | ====== 3 | Carl Richell - carl(at)system76(dot)com 4 | Tom Aaron - tom(at)system76(dot)com 5 | Ian Santopietro - ian(at)system76(dot)com 6 | 7 | UI 8 | ====== 9 | Carl Richell - carl(at)system76(dot)com 10 | Ian Santopietro - ian(at)system76(dot)com 11 | 12 | Documentation 13 | ============= 14 | Carl Richell - carl(at)system76(dot)com 15 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | System76 Driver 2 | 3 | System76, Inc. 4 | Copyright System76, Inc. 5 | Released under the GNU General Public License version 2 (See LICENSE) 6 | 7 | Version 3.2.7 8 | 9 | 1.) Fix wireless and bluetooth hotkey bug on the lemu4 10 | panp9, gazp7, gazp8 in Ubuntu 13.04 11 | 12 | -------------------------------------- 13 | 14 | Version 3.2.6 15 | 16 | 1.) Replace gksu with gksudo for compatibility 17 | 2.) driverscontrol.py - Remove brightness key fix for 13.04 - No longer necessary. 18 | misc.py - Update WiFi performance fix with better description. 19 | 20 | -------------------------------------- 21 | 22 | Version 3.2.5 23 | 24 | 1.) base_system.py - Remove horizontal scrolling from 13.04 25 | Remove Inline Debian packaging to simplfy package creation. 26 | 27 | -------------------------------------- 28 | 29 | Version 3.2.4 30 | 31 | 1.) Add Ubuntu 13.04 support 32 | 33 | -------------------------------------- 34 | 35 | Version 3.2.3 36 | 37 | 1.) Add new Bonobo Extreme Model (bonx6) 38 | 39 | -------------------------------------- 40 | 41 | Version 3.2.2 42 | 43 | 1.) Add new Sable Complete model sabc1 44 | 2.) Install linux-headers on all systems in Ubuntu 12.10 (expect netbooks). This enables, 45 | installation of nVidia drivers, card readers, finger print readers, vmware and other 46 | dependent modules. 47 | 48 | -------------------------------------- 49 | 50 | Version 3.2.1 51 | 52 | 1.) Fix lightdm startup race condition on ratp1, wilp9, leox3 53 | lemu4, gazp7, gazp8, panp9. Applies to Ubuntu 12.04 and 12.10. 54 | 2.) Add gazp8 model 55 | 56 | -------------------------------------- 57 | 58 | Version 3.2.0 59 | 60 | 1.) Add Ubuntu 12.10 support 61 | 2.) Fix minor bug throwing apport errors 62 | 63 | -------------------------------------- 64 | 65 | Version 3.0.0 66 | 67 | 1.) interface on Ubuntu 11.10 and later to use GTK3 68 | 2.) Clean up core logic for GTK3 version 69 | 3.) Add details pane showing what drivers will be installed. 70 | 71 | -------------------------------------- 72 | 73 | Version 2.7.6 74 | 75 | 1.) Add new Gazelle Professional (gazp7) 76 | 77 | -------------------------------------- 78 | 79 | Version 2.7.5 80 | 81 | 1.) Add new Ratel Performance ratp1 82 | 2.) Add new Wild Dog Performance 83 | 3.) Move rts_bpp realtek module install directory to /usr/src 84 | 85 | -------------------------------------- 86 | 87 | Version 2.7.4 88 | 89 | 1.) Fix Lemur Ultra (lemu1) brightness keys in 12.04 90 | 91 | -------------------------------------- 92 | 93 | Version 2.7.3 94 | 95 | 1.) Add new Lemur Ultra (lemu4) and Pangolin Performance (panp9) 96 | 2.) Add Ubuntu 12.04 support 97 | 98 | -------------------------------------- 99 | Version 2.7.2 100 | 101 | 1.) Add new Leopard Extreme model - leox3 102 | 103 | -------------------------------------- 104 | 105 | Version 2.7.1 106 | 107 | 1.) Fix Starling Netbook (star1) Ubuntu 11.10 support 108 | 109 | -------------------------------------- 110 | 111 | Version 2.7.0 112 | 113 | 1.) Fix Lemur Ultra (lemu2) runtime error (bug defining systemname) 114 | 115 | -------------------------------------- 116 | 117 | Version 2.6.9 118 | 119 | 1.) Add Ubuntu 11.10 support 120 | 2.) Add Lemur Ultra (lemu3) 121 | 122 | -------------------------------------- 123 | 124 | Version 2.6.8 125 | 126 | 1.) Add new Meerkat Ion model (ment5) 127 | 128 | -------------------------------------- 129 | 130 | Version 2.6.7 131 | 132 | 1.) Add new Wildebeest Performance (wilb2) model 133 | 2.) Add new Ratel Ultra (ratu2) model (unreleased) 134 | 135 | -------------------------------------- 136 | 137 | Version 2.6.6 138 | 139 | 1.) Add new Pangolin Performance (panp8) model 140 | 141 | -------------------------------------- 142 | 143 | Version 2.6.5 144 | 145 | 1.) Fix Bonobo (bonp5) headphone detection and speaker mute 146 | 147 | -------------------------------------- 148 | Version 2.6.4 149 | 150 | 1.) Change app icon to white "76" instead of alpha 151 | 2.) Add Bonobo pro (bonp5) model 152 | 3.) In Ubuntu 11.04 configure two finger scrolling on supported touchpads 153 | 154 | -------------------------------------- 155 | 156 | Version 2.6.3 157 | 158 | 1.) Add initial Ubuntu 11.04 support 159 | 2.) Change app icon to .svg 160 | 3.) Fix PCIe ASPM bug on serp7 and gazp6 (Ethernet connection 161 | problems when AC is unplugged. Potential system freeze 162 | when AC is plugged in while Ethernet is plugged in) 163 | 164 | -------------------------------------- 165 | 166 | Version 2.6.2 167 | 168 | 1.) Add new Wild Dog (wilp8), Leopard Extreme (leox2), and Starling Netbook (star5) 169 | 170 | -------------------------------------- 171 | 172 | Version 2.6.1 173 | 174 | 1.) Add Gnome theme race condition fix to the serp6 and wilb1 models 175 | 176 | -------------------------------------- 177 | 178 | Version 2.6.0 179 | 180 | 1.) Add new serp7 and gazp6 models 181 | 182 | -------------------------------------- 183 | 184 | Version 2.5.9 185 | 186 | 1.) Complete new 17" Bonobo Pro (bonp4) 187 | 2.) Fix suspend and hibernate on the Serval Pro (serp6) 188 | 189 | -------------------------------------- 190 | 191 | Version 2.5.8 192 | 193 | 1.) Complete new 17" Bonobo Pro (bonp4) 194 | 2.) Fix suspend and hibernate on the Serval Pro (serp6) 195 | 196 | -------------------------------------- 197 | 198 | Version 2.5.8 199 | 200 | 1.) Add new Starling Netbook (star4) 201 | 2.) Add initial support for the new 17" Bonobo Pro (bonp4) 202 | 203 | -------------------------------------- 204 | 205 | Version 2.5.7 206 | 207 | 1.) Remove uvc code update for Ubuntu 10.10 on the lemu1, lemu2 208 | and serp6. Code is now included in 10.10. 209 | 2.) Removed SD Card + Suspend patch for Ubuntu 10.10. Fixed in 210 | distro. 211 | 212 | -------------------------------------- 213 | 214 | Version 2.5.6 215 | 216 | 1.) Add initial Ubuntu 10.10 support 217 | 2.) Fix Starling Edubook BIOS recognition 218 | 219 | -------------------------------------- 220 | 221 | Version 2.5.5 222 | 223 | 1.) Fix typo in driverscontrol.py. Typo cause a drivers install 224 | error on some models 225 | 226 | -------------------------------------- 227 | 228 | Version 2.5.4 229 | 230 | 1.) Fix typo in driverscontrol.py. Typo cause a drivers install 231 | error on the lemu1 232 | 233 | -------------------------------------- 234 | 235 | Version 2.5.3 236 | 237 | 1.) Add 2nd Gen Lemur UltraThin (lemu2) 238 | 2.) Change Lemur series camera driver installation 239 | so that the driver is automatically reinstalled 240 | whenever a new kernel-headers package is installed 241 | 242 | -------------------------------------- 243 | 244 | Version 2.5.2 245 | 246 | 1.) Add 2nd Gen Starling Netbook (star3) 247 | 248 | -------------------------------------- 249 | 250 | Version 2.5.1 251 | 252 | 1.) Fix model designation problem on star2 (stopped driver 253 | from running with certain BIOS's) 254 | 255 | -------------------------------------- 256 | 257 | Version 2.5.0 258 | 259 | 1.) Fix dependency problem (build-essential) when installing wireless 260 | drivers on the Starling Netbook (model star1) 261 | 2.) Fix SD Card reader in Ubuntu 10.04 on star1 262 | 3.) Add patch to wake up Starling Edubook (model star2) 263 | Synaptic touchpad when resuming from suspend 264 | 265 | -------------------------------------- 266 | 267 | Version 2.4.9 268 | 269 | 1.) Fix missing cheese and repository install on lemu1 restore 270 | 271 | -------------------------------------- 272 | 273 | Version 2.4.8 274 | 275 | 1.) Add Ratel Ultra (ratu1) 276 | 277 | -------------------------------------- 278 | 279 | Version 2.4.7 280 | 281 | 1.) Add Ubuntu 10.04 support 282 | 283 | -------------------------------------- 284 | Version 2.4.6 285 | 286 | 1.) Add new Meerkat NetTop (ment3) 287 | 2.) Add jme patch for panp7 and bonp3. Installs jme module 288 | version 1.0.5 and scripts to update when new kernels 289 | are installed. Effects panp7 and bonp3 with quad core 290 | CPUs 291 | 292 | -------------------------------------- 293 | 294 | Version 2.4.5 295 | 296 | 1.) Add new Pangolin Performance (panp7) 297 | 2.) Add new Bonobo Performance (bonp3) 298 | 299 | -------------------------------------- 300 | 301 | Version 2.4.4 302 | 303 | 1.) Fix Starling Ultra (star1) wireless in Ubuntu 9.10 304 | 305 | -------------------------------------- 306 | 307 | Version 2.4.3 308 | 309 | 1.) Add new serp6 model 310 | 2.) Add new wilp7 model 311 | 312 | -------------------------------------- 313 | 314 | Version 2.4.2 315 | 316 | 1.) Fix Lemur NIC on Ubuntu 64bit w 4GB 317 | 2.) Remove brightness hotkey fix for bonp2 in 9.10 318 | 319 | -------------------------------------- 320 | 321 | Version 2.4.0 322 | 323 | 1.) Starling Netbook (star1) Ubuntu 9.10 (Karmic) patches 324 | 2.) Lemur UltraThin webcam fix 325 | 326 | -------------------------------------- 327 | 328 | Version 2.3.9 329 | 330 | 1.) Add Pangolin Performance 6 (panp6) 331 | 2.) Add Karmic support 332 | 3.) Add Wildebeest Performance desktop (wilb1) 333 | 4.) Add Lemur Ultrathin laptop (lemu1) 334 | 335 | -------------------------------------- 336 | 337 | Version 2.3.7 338 | 339 | 1.) Extend wireless range for the Starling Netbook (star1) 340 | 2.) Fix wireless LED on Starling Netbook (star1) 341 | 3.) Add Leopard Extreme info 342 | 343 | -------------------------------------- 344 | 345 | Version 2.3.6 346 | 347 | 1.) Add --assume-yes to fix linux-backports-modules installation 348 | 349 | -------------------------------------- 350 | 351 | Version 2.3.5 352 | 353 | 1.) Fix Bonobo (bonp2) brightness hot keys 354 | 2.) Add linux-backports to certain nVidia based laptops on 9.04 355 | (panp4n, panp5, bonp2, serp5) 356 | (fixes shutdown freeze bug using nVidia 180 driver) 357 | 358 | -------------------------------------- 359 | 360 | Version 2.3.4 361 | 362 | 1.) Add Starling Netbook (star1) 363 | 364 | -------------------------------------- 365 | 366 | Version 2.3.3 367 | 368 | 1.) Add command line restore and install driver options 369 | 2.) Remove some info from system info tab (CPU, HD, Mem) 370 | 3.) Change startup script from bash to python 371 | 4.) Add syslog to support archive 372 | 5.) Add Jaunty support 373 | 374 | -------------------------------------- 375 | 376 | Version 2.3.2 377 | 378 | 1.) Add new Meerkat NetTop system 379 | 2.) Add Pangolin update (panp5) 380 | 3.) Add new Bonobo 17" 381 | 382 | -------------------------------------- 383 | 384 | Version 2.3.1 385 | 386 | 1.) Fix daru1 touchpad on/off hotkey 387 | 2.) Re-add cheese during restore. Fixed upstream 388 | 3.) Add new Wild Dog Performance wilp6 389 | 390 | -------------------------------------- 391 | 392 | Version 2.3.0 393 | 394 | 1.) Reverse Direct Rendering w/ Suspend on fix daru3 (2.2.9) - fixed in Ubuntu 395 | 2.) Add quirks=2 to uvcvideo load on gazu3 (fixes webcam) 396 | 397 | -------------------------------------- 398 | 399 | Version 2.2.9 400 | 401 | 1.) Fix Suspend with Direct Rendering on daru3 402 | 403 | -------------------------------------- 404 | 405 | Version 2.2.8 406 | 407 | 1.) Support ATI card video playback on wilp5 408 | 2.) Add proposed gazu1 model 409 | 410 | -------------------------------------- 411 | 412 | Version 2.2.7 413 | 414 | 1.) Manufacturing Update 415 | 416 | -------------------------------------- 417 | 418 | Version 2.2.6 419 | 420 | 1.) Add System76 Bonobo Pro (bonp2) 421 | 2.) Add System76 Serval Pro (serp5) 422 | 3.) Fix system designation for one Pangolin panv2 where the MB had been replaced 423 | 4.) Update Linux UVC code 424 | 5.) Add Ubuntu Intrepid to System76Driver.py (no system patches applied yet) 425 | 426 | -------------------------------------- 427 | 428 | Version 2.2.5 429 | 430 | 1.) Fix panp4 mic and sound after resume LP #264516 431 | 432 | -------------------------------------- 433 | 434 | Version 2.2.4 435 | 436 | 1.) Add new Pangolin Performance (panp4) and Darter Ultra (daru3) 437 | 438 | -------------------------------------- 439 | 440 | Version 2.2.3 441 | 442 | 1.) Add new Ratel Value (ratv6) 443 | 444 | -------------------------------------- 445 | 446 | Version 2.2.2 447 | 448 | 1.) Fix missing () in driverscontrol 449 | 2.) Update Ubuntu version detection for upcoming 8.04.1 release 450 | 451 | -------------------------------------- 452 | 453 | Version 2.2.1 454 | 455 | 1.) [hardy] fix sound on resume from suspend LP Bug #223742 456 | (serp3) (serp4) (gazp5) (gazv5) (panv3) 457 | 2.) Install linux-backports-modules-hardy to fix the wireless 458 | LED on Intel based cards LP Bug #223874 459 | 460 | -------------------------------------- 461 | 462 | Version 2.2.0 463 | 464 | 1.) Version bump for Hardy release 465 | 466 | -------------------------------------- 467 | 468 | Version 2.1.9 469 | 470 | 1.) Fix nVidia installation on restore (command changed in Hardy) 471 | 472 | -------------------------------------- 473 | 474 | Version 2.1.8 475 | 476 | 1.) Add Hardy support. 477 | 2.) Restore adds gnucash, gnucash-docs, and system76-driver. nVidia 478 | and gsynaptics are installed if appropriate. 479 | 3.) Remove system76 repo from sources.list and add via sources.list.d/system76.list 480 | 481 | -------------------------------------- 482 | 483 | Version 2.1.7 484 | 485 | 1.) Add kernel line parameter ec_intr=0 to menu.lst on daru2 under 486 | Ubuntu 7.10. Fixes acpi battery indication. 487 | 2.) Update acpi-support on Ubuntu 7.10 for models daru1, daru2, 488 | gazp1, gazp2, gazp3, gazp5, gazv2, gazv3, gazv4. 489 | 3.) Fix usplash bug after restoring a nVidia based system with Ubuntu 490 | 7.10 64 bit from standard Ubuntu disk. This fix was built into 491 | manufacturing images but not applied when customer restored from disk. 492 | 493 | -------------------------------------- 494 | 495 | Version 2.1.6 496 | 497 | 1.) Add new system76 serval model serp4 498 | 499 | -------------------------------------- 500 | 501 | Version 2.1.5 502 | 503 | 1.) Add new system76 model ratv5 504 | 505 | -------------------------------------- 506 | 507 | Version 2.1.4 508 | 509 | 1.) Add new system76 models wilp5, sabv3, ratv4 510 | 511 | -------------------------------------- 512 | 513 | Version 2.1.3 514 | 515 | 1.) Clean out legacy unnecessary drivers 516 | 2.) Add checks for running package managers 517 | 3.) Fix Darter (daru2) brightness increase/decrease 518 | 4.) Sound imporovments: 519 | a.) sound drivers are now provided by alsa-modules package 520 | rather than compiled into kernel (kernel update doesn't break sound) 521 | b.) provide a clean /etc/modprobe.d/alsa-base file where necessary 522 | 523 | -------------------------------------- 524 | 525 | Version 2.1.2 526 | 527 | 1.) Fix bug effecting serp3 sound 528 | 2.) Fix DSDT tables on Darter (better power managment) 529 | 3.) Do not install Alsa on daru2 - only add targa-dig for headphone jacksense 530 | 531 | -------------------------------------- 532 | 533 | Version 2.1.1 534 | 535 | 1.) Fix Gutsy upgrade bug on Gazelle Value (gazv3) 536 | 537 | -------------------------------------- 538 | 539 | Version 2.1.0 540 | 541 | 1.) Complete Ubuntu 7.10 Gutsy support for all computers 542 | 2.) Add support tab and create log archive 543 | 544 | -------------------------------------- 545 | 546 | Version 2.0.10 547 | 548 | 1.) Add new Gazelle Value (gazv5) model 549 | 2.) Add uvc camera driver for new Gazelle Value (gazv5) 550 | 3.) Fix some file permissions problems (menu icon) 551 | 4.) Add SD card reader support for Pangolin (panv3) and Gazelle (gazv5) 552 | 553 | -------------------------------------- 554 | 555 | Version 2.0.9 556 | 557 | 1.) Fix Darter Headphone Sensing Bug #130669 558 | 2.) Improve Darter (daru2) battery monitor Bug #130739 559 | Fixes buggy DSDT table in BIOS - installs via initrd.img 560 | 561 | -------------------------------------- 562 | 563 | Version 2.0.8 564 | 565 | 1.) Add new Serval Performance (serp3) (incomplete) 566 | 2.) Add initial gutsy support 567 | 568 | -------------------------------------- 569 | 570 | Version 2.0.7 571 | 572 | 1.) Add new Pangolin Value (panv3) 573 | 574 | -------------------------------------- 575 | 576 | Version 2.0.6 577 | 578 | 1.) Add new Darter Ultra (daru2) 579 | 580 | -------------------------------------- 581 | 582 | Version 2.0.5 583 | 584 | 1.) Fix Suspend on all laptops (gazv4 still buggy) 585 | 2.) Fix gazv3 suspend Bug #108253 586 | 3.) Fix nVidia rotation Bug #118854 587 | 4.) Fix Darter Feisty Suspend and LCD Brightness on Resume Bug #114675 588 | 5.) Fix network-manager wireless interface recognition after resume 589 | 6.) Fix Darter (daru1) FN+F8 monitor switching Bug #116107 590 | 7.) Remove build-essential dependency fixing bug #121405 591 | 592 | -------------------------------------- 593 | 594 | Version 2.0.4 595 | 596 | 1.) Apply update for gazp3 motherboard change 597 | 598 | -------------------------------------- 599 | 600 | Version 2.0.3 601 | 602 | 1.) Add usb audio device support to systems requiring sound 603 | Fixes bug #110321 604 | 605 | -------------------------------------- 606 | 607 | Version 2.0.2 608 | 609 | 1.) Fix ata driver problem with daru1, gazv3, gazv4 610 | 611 | -------------------------------------- 612 | 613 | Version 2.0.1 614 | 615 | 1.) Add Feisty sound support to gazv3 616 | 2.) Add Feisty sound support to gazp1 617 | 3.) Fix variable bug drivers.py/driverscontrol.py 618 | 4.) Fix Suspend in Gazelle Value 619 | 620 | -------------------------------------- 621 | 622 | Version 2.0.0 623 | 624 | 1.) Add Feisty sound support for gazp2 & gazp3 front speakers 625 | 626 | -------------------------------------- 627 | 628 | Version 1.9.95 629 | 630 | 1.) Add support for Kubuntu 631 | 2.) Add new Wild Dog Professional (wilp3) 632 | 633 | -------------------------------------- 634 | 635 | Version 1.9.94 636 | 637 | 1.) Major re-write 638 | 2.) Implement System Information tab 639 | 3.) Separate drivers and restore task 640 | 4.) Bug fixes 641 | 5.) Move to GLADE 642 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 2007 System76, Inc. 5 | 6 | Preamble 7 | 8 | The licenses for most software are designed to take away your 9 | freedom to share and change it. By contrast, the GNU General Public 10 | License is intended to guarantee your freedom to share and change free 11 | software--to make sure the software is free for all its users. This 12 | General Public License applies to most of the Free Software 13 | Foundation's software and to any other program whose authors commit to 14 | using it. (Some other Free Software Foundation software is covered by 15 | the GNU Lesser General Public License instead.) You can apply it to 16 | your programs, too. 17 | 18 | When we speak of free software, we are referring to freedom, not 19 | price. Our General Public Licenses are designed to make sure that you 20 | have the freedom to distribute copies of free software (and charge for 21 | this service if you wish), that you receive source code or can get it 22 | if you want it, that you can change the software or use pieces of it 23 | in new free programs; and that you know you can do these things. 24 | 25 | To protect your rights, we need to make restrictions that forbid 26 | anyone to deny you these rights or to ask you to surrender the rights. 27 | These restrictions translate to certain responsibilities for you if you 28 | distribute copies of the software, or if you modify it. 29 | 30 | For example, if you distribute copies of such a program, whether 31 | gratis or for a fee, you must give the recipients all the rights that 32 | you have. You must make sure that they, too, receive or can get the 33 | source code. And you must show them these terms so they know their 34 | rights. 35 | 36 | We protect your rights with two steps: (1) copyright the software, and 37 | (2) offer you this license which gives you legal permission to copy, 38 | distribute and/or modify the software. 39 | 40 | Also, for each author's protection and ours, we want to make certain 41 | that everyone understands that there is no warranty for this free 42 | software. If the software is modified by someone else and passed on, we 43 | want its recipients to know that what they have is not the original, so 44 | that any problems introduced by others will not reflect on the original 45 | authors' reputations. 46 | 47 | Finally, any free program is threatened constantly by software 48 | patents. We wish to avoid the danger that redistributors of a free 49 | program will individually obtain patent licenses, in effect making the 50 | program proprietary. To prevent this, we have made it clear that any 51 | patent must be licensed for everyone's free use or not licensed at all. 52 | 53 | The precise terms and conditions for copying, distribution and 54 | modification follow. 55 | 56 | GNU GENERAL PUBLIC LICENSE 57 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 58 | 59 | 0. This License applies to any program or other work which contains 60 | a notice placed by the copyright holder saying it may be distributed 61 | under the terms of this General Public License. The "Program", below, 62 | refers to any such program or work, and a "work based on the Program" 63 | means either the Program or any derivative work under copyright law: 64 | that is to say, a work containing the Program or a portion of it, 65 | either verbatim or with modifications and/or translated into another 66 | language. (Hereinafter, translation is included without limitation in 67 | the term "modification".) Each licensee is addressed as "you". 68 | 69 | Activities other than copying, distribution and modification are not 70 | covered by this License; they are outside its scope. The act of 71 | running the Program is not restricted, and the output from the Program 72 | is covered only if its contents constitute a work based on the 73 | Program (independent of having been made by running the Program). 74 | Whether that is true depends on what the Program does. 75 | 76 | 1. You may copy and distribute verbatim copies of the Program's 77 | source code as you receive it, in any medium, provided that you 78 | conspicuously and appropriately publish on each copy an appropriate 79 | copyright notice and disclaimer of warranty; keep intact all the 80 | notices that refer to this License and to the absence of any warranty; 81 | and give any other recipients of the Program a copy of this License 82 | along with the Program. 83 | 84 | You may charge a fee for the physical act of transferring a copy, and 85 | you may at your option offer warranty protection in exchange for a fee. 86 | 87 | 2. You may modify your copy or copies of the Program or any portion 88 | of it, thus forming a work based on the Program, and copy and 89 | distribute such modifications or work under the terms of Section 1 90 | above, provided that you also meet all of these conditions: 91 | 92 | a) You must cause the modified files to carry prominent notices 93 | stating that you changed the files and the date of any change. 94 | 95 | b) You must cause any work that you distribute or publish, that in 96 | whole or in part contains or is derived from the Program or any 97 | part thereof, to be licensed as a whole at no charge to all third 98 | parties under the terms of this License. 99 | 100 | c) If the modified program normally reads commands interactively 101 | when run, you must cause it, when started running for such 102 | interactive use in the most ordinary way, to print or display an 103 | announcement including an appropriate copyright notice and a 104 | notice that there is no warranty (or else, saying that you provide 105 | a warranty) and that users may redistribute the program under 106 | these conditions, and telling the user how to view a copy of this 107 | License. (Exception: if the Program itself is interactive but 108 | does not normally print such an announcement, your work based on 109 | the Program is not required to print an announcement.) 110 | 111 | These requirements apply to the modified work as a whole. If 112 | identifiable sections of that work are not derived from the Program, 113 | and can be reasonably considered independent and separate works in 114 | themselves, then this License, and its terms, do not apply to those 115 | sections when you distribute them as separate works. But when you 116 | distribute the same sections as part of a whole which is a work based 117 | on the Program, the distribution of the whole must be on the terms of 118 | this License, whose permissions for other licensees extend to the 119 | entire whole, and thus to each and every part regardless of who wrote it. 120 | 121 | Thus, it is not the intent of this section to claim rights or contest 122 | your rights to work written entirely by you; rather, the intent is to 123 | exercise the right to control the distribution of derivative or 124 | collective works based on the Program. 125 | 126 | In addition, mere aggregation of another work not based on the Program 127 | with the Program (or with a work based on the Program) on a volume of 128 | a storage or distribution medium does not bring the other work under 129 | the scope of this License. 130 | 131 | 3. You may copy and distribute the Program (or a work based on it, 132 | under Section 2) in object code or executable form under the terms of 133 | Sections 1 and 2 above provided that you also do one of the following: 134 | 135 | a) Accompany it with the complete corresponding machine-readable 136 | source code, which must be distributed under the terms of Sections 137 | 1 and 2 above on a medium customarily used for software interchange; or, 138 | 139 | b) Accompany it with a written offer, valid for at least three 140 | years, to give any third party, for a charge no more than your 141 | cost of physically performing source distribution, a complete 142 | machine-readable copy of the corresponding source code, to be 143 | distributed under the terms of Sections 1 and 2 above on a medium 144 | customarily used for software interchange; or, 145 | 146 | c) Accompany it with the information you received as to the offer 147 | to distribute corresponding source code. (This alternative is 148 | allowed only for noncommercial distribution and only if you 149 | received the program in object code or executable form with such 150 | an offer, in accord with Subsection b above.) 151 | 152 | The source code for a work means the preferred form of the work for 153 | making modifications to it. For an executable work, complete source 154 | code means all the source code for all modules it contains, plus any 155 | associated interface definition files, plus the scripts used to 156 | control compilation and installation of the executable. However, as a 157 | special exception, the source code distributed need not include 158 | anything that is normally distributed (in either source or binary 159 | form) with the major components (compiler, kernel, and so on) of the 160 | operating system on which the executable runs, unless that component 161 | itself accompanies the executable. 162 | 163 | If distribution of executable or object code is made by offering 164 | access to copy from a designated place, then offering equivalent 165 | access to copy the source code from the same place counts as 166 | distribution of the source code, even though third parties are not 167 | compelled to copy the source along with the object code. 168 | 169 | 4. You may not copy, modify, sublicense, or distribute the Program 170 | except as expressly provided under this License. Any attempt 171 | otherwise to copy, modify, sublicense or distribute the Program is 172 | void, and will automatically terminate your rights under this License. 173 | However, parties who have received copies, or rights, from you under 174 | this License will not have their licenses terminated so long as such 175 | parties remain in full compliance. 176 | 177 | 5. You are not required to accept this License, since you have not 178 | signed it. However, nothing else grants you permission to modify or 179 | distribute the Program or its derivative works. These actions are 180 | prohibited by law if you do not accept this License. Therefore, by 181 | modifying or distributing the Program (or any work based on the 182 | Program), you indicate your acceptance of this License to do so, and 183 | all its terms and conditions for copying, distributing or modifying 184 | the Program or works based on it. 185 | 186 | 6. Each time you redistribute the Program (or any work based on the 187 | Program), the recipient automatically receives a license from the 188 | original licensor to copy, distribute or modify the Program subject to 189 | these terms and conditions. You may not impose any further 190 | restrictions on the recipients' exercise of the rights granted herein. 191 | You are not responsible for enforcing compliance by third parties to 192 | this License. 193 | 194 | 7. If, as a consequence of a court judgment or allegation of patent 195 | infringement or for any other reason (not limited to patent issues), 196 | conditions are imposed on you (whether by court order, agreement or 197 | otherwise) that contradict the conditions of this License, they do not 198 | excuse you from the conditions of this License. If you cannot 199 | distribute so as to satisfy simultaneously your obligations under this 200 | License and any other pertinent obligations, then as a consequence you 201 | may not distribute the Program at all. For example, if a patent 202 | license would not permit royalty-free redistribution of the Program by 203 | all those who receive copies directly or indirectly through you, then 204 | the only way you could satisfy both it and this License would be to 205 | refrain entirely from distribution of the Program. 206 | 207 | If any portion of this section is held invalid or unenforceable under 208 | any particular circumstance, the balance of the section is intended to 209 | apply and the section as a whole is intended to apply in other 210 | circumstances. 211 | 212 | It is not the purpose of this section to induce you to infringe any 213 | patents or other property right claims or to contest validity of any 214 | such claims; this section has the sole purpose of protecting the 215 | integrity of the free software distribution system, which is 216 | implemented by public license practices. Many people have made 217 | generous contributions to the wide range of software distributed 218 | through that system in reliance on consistent application of that 219 | system; it is up to the author/donor to decide if he or she is willing 220 | to distribute software through any other system and a licensee cannot 221 | impose that choice. 222 | 223 | This section is intended to make thoroughly clear what is believed to 224 | be a consequence of the rest of this License. 225 | 226 | 8. If the distribution and/or use of the Program is restricted in 227 | certain countries either by patents or by copyrighted interfaces, the 228 | original copyright holder who places the Program under this License 229 | may add an explicit geographical distribution limitation excluding 230 | those countries, so that distribution is permitted only in or among 231 | countries not thus excluded. In such case, this License incorporates 232 | the limitation as if written in the body of this License. 233 | 234 | 9. The Free Software Foundation may publish revised and/or new versions 235 | of the General Public License from time to time. Such new versions will 236 | be similar in spirit to the present version, but may differ in detail to 237 | address new problems or concerns. 238 | 239 | Each version is given a distinguishing version number. If the Program 240 | specifies a version number of this License which applies to it and "any 241 | later version", you have the option of following the terms and conditions 242 | either of that version or of any later version published by the Free 243 | Software Foundation. If the Program does not specify a version number of 244 | this License, you may choose any version ever published by the Free Software 245 | Foundation. 246 | 247 | 10. If you wish to incorporate parts of the Program into other free 248 | programs whose distribution conditions are different, write to the author 249 | to ask for permission. For software which is copyrighted by the Free 250 | Software Foundation, write to the Free Software Foundation; we sometimes 251 | make exceptions for this. Our decision will be guided by the two goals 252 | of preserving the free status of all derivatives of our free software and 253 | of promoting the sharing and reuse of software generally. 254 | 255 | NO WARRANTY 256 | 257 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 258 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 259 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 260 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 261 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 262 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 263 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 264 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 265 | REPAIR OR CORRECTION. 266 | 267 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 268 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 269 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 270 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 271 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 272 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 273 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 274 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 275 | POSSIBILITY OF SUCH DAMAGES. 276 | 277 | END OF TERMS AND CONDITIONS 278 | 279 | How to Apply These Terms to Your New Programs 280 | 281 | If you develop a new program, and you want it to be of the greatest 282 | possible use to the public, the best way to achieve this is to make it 283 | free software which everyone can redistribute and change under these terms. 284 | 285 | To do so, attach the following notices to the program. It is safest 286 | to attach them to the start of each source file to most effectively 287 | convey the exclusion of warranty; and each file should have at least 288 | the "copyright" line and a pointer to where the full notice is found. 289 | 290 | 291 | Copyright (C) 292 | 293 | This program is free software; you can redistribute it and/or modify 294 | it under the terms of the GNU General Public License as published by 295 | the Free Software Foundation; either version 2 of the License, or 296 | (at your option) any later version. 297 | 298 | This program is distributed in the hope that it will be useful, 299 | but WITHOUT ANY WARRANTY; without even the implied warranty of 300 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 301 | GNU General Public License for more details. 302 | 303 | You should have received a copy of the GNU General Public License along 304 | with this program; if not, write to the Free Software Foundation, Inc., 305 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 306 | 307 | Also add information on how to contact you by electronic and paper mail. 308 | 309 | If the program is interactive, make it output a short notice like this 310 | when it starts in an interactive mode: 311 | 312 | Gnomovision version 69, Copyright (C) year name of author 313 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 314 | This is free software, and you are welcome to redistribute it 315 | under certain conditions; type `show c' for details. 316 | 317 | The hypothetical commands `show w' and `show c' should show the appropriate 318 | parts of the General Public License. Of course, the commands you use may 319 | be called something other than `show w' and `show c'; they could even be 320 | mouse-clicks or menu items--whatever suits your program. 321 | 322 | You should also get your employer (if you work as a programmer) or your 323 | school, if any, to sign a "copyright disclaimer" for the program, if 324 | necessary. Here is a sample; alter the names: 325 | 326 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 327 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 328 | 329 | , 1 April 1989 330 | Ty Coon, President of Vice 331 | 332 | This General Public License does not permit incorporating your program into 333 | proprietary programs. If your program is a subroutine library, you may 334 | consider it more useful to permit linking proprietary applications with the 335 | library. If this is what you want to do, use the GNU Lesser General 336 | Public License instead of this License. 337 | 338 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # System76 Driver 2 | 3 | This program installs drivers and provides restore functionality for System76 4 | machines. 5 | 6 | Open Activities button on the top left or use the Ubuntu/Pop!\_OS/Super key and 7 | search for 'system76' then click the icon and enter your password to open the 8 | application. 9 | 10 | ## Making changes 11 | 12 | 1. Checkout new branch 13 | 2. Push new branch 14 | 3. Bump the version (`./bump-version.py`) 15 | 4. Make changes 16 | 5. Make pull request 17 | 6. Get PR approved and merged 18 | 7. Make a release from master branch (`./make-release.py`) 19 | 20 | ## License 21 | 22 | This software is made available under the terms of the GNU General Public 23 | License; either version 2 of the License, or (at your option) any later 24 | version. See [LICENSE](LICENSE) for details. 25 | -------------------------------------------------------------------------------- /bump-version.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # system76-driver: Universal driver for System76 computers 4 | # Copyright (C) 2005-2016 System76, Inc. 5 | # 6 | # This file is part of `system76-driver`. 7 | # 8 | # `system76-driver` is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; either version 2 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # `system76-driver` is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License along 19 | # with `system76-driver`; if not, write to the Free Software Foundation, Inc., 20 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 21 | 22 | """ 23 | Bump version of system76-driver to start work on next release. 24 | """ 25 | 26 | import sys 27 | import os 28 | from os import path 29 | import re 30 | import time 31 | from subprocess import check_call, check_output, call 32 | 33 | from system76driver import __version__ 34 | from system76driver.tests.helpers import TempDir 35 | 36 | 37 | DISTROS = ('trusty', 'xenial', 'yakkety', 'zesty', 'artful', 'bionic', 'cosmic', 'disco', 'eoan', 'focal', 'noble') 38 | ALPHA = '~~alpha' 39 | 40 | TREE = path.dirname(path.abspath(__file__)) 41 | assert TREE == sys.path[0] 42 | assert os.getcwd() == TREE 43 | 44 | CHANGELOG = path.join(TREE, 'debian', 'changelog') 45 | INIT = path.join(TREE, 'system76driver', '__init__.py') 46 | SETUP = path.join(TREE, 'setup.py') 47 | 48 | assert path.isfile(CHANGELOG) 49 | assert path.isfile(INIT) 50 | assert path.isfile(SETUP) 51 | 52 | 53 | def confirm(): 54 | while True: 55 | response = input(' Okay? yes/NO: ').lower() 56 | if response == 'yes': 57 | return True 58 | if response == 'no': 59 | return False 60 | print("Please enter 'yes' or 'no'") 61 | 62 | 63 | def check_for_uncommitted_changes(): 64 | if check_output(['git', 'diff']).decode() != '': 65 | sys.exit('ERROR: unstaged changes!') 66 | if check_output(['git', 'diff', '--cached']).decode() != '': 67 | sys.exit('ERROR: uncommited changes!') 68 | 69 | 70 | def parse_version_line(line): 71 | if ALPHA in line: 72 | raise ValueError('{!r} in current version:\n{!r}'.format(ALPHA, line)) 73 | m = re.match( 74 | '^system76-driver \(([\.0-9]+)\) ([a-z]+); urgency=(low|medium|high|emergency|critical)$', line 75 | ) 76 | if m is None: 77 | raise ValueError('bad version line[0]:\n{!r}'.format(line)) 78 | ver = m.group(1) 79 | if ver != __version__: 80 | raise ValueError( 81 | 'changelog != __version: {!r} != {!r}'.format(ver, __version__) 82 | ) 83 | distro = m.group(2) 84 | if distro not in DISTROS: 85 | raise ValueError('bad distro {!r} not in {!r}'.format(distro, DISTROS)) 86 | return (ver, distro) 87 | 88 | 89 | def bump_version(current): 90 | (year, month, rev) = current.split('.') 91 | assert str(int(year)) == year and int(year) >= 14 92 | assert month in ('04', '10') 93 | assert str(int(rev)) == rev and int(rev) >= 0 94 | return '{}.{}.{}'.format(year, month, int(rev) + 1) 95 | 96 | 97 | def build_author_line(): 98 | user = check_output(['git', 'config', '--get', 'user.name']).decode().strip() 99 | email = check_output(['git', 'config', '--get', 'user.email']).decode().strip() 100 | author = ' '.join((user, '<' + email + '>')) 101 | ts = time.strftime('%a, %d %b %Y %H:%M:%S %z', time.localtime()) 102 | return ' -- {} {}\n'.format(author, ts) 103 | 104 | 105 | def iter_new_changelog_lines(new, newdeb, distro): 106 | yield 'system76-driver ({}) {}; urgency=low\n'.format(newdeb, distro) 107 | yield '\n' 108 | yield ' * Daily WIP for {}\n'.format(new) 109 | yield '\n' 110 | yield build_author_line() 111 | yield '\n' 112 | 113 | 114 | def iter_new_init_lines(new, init_lines): 115 | found_version = False 116 | for line in init_lines: 117 | if line.startswith('__version__'): 118 | assert found_version is False 119 | found_version = True 120 | yield '__version__ = {!r}\n'.format(new) 121 | else: 122 | yield line 123 | 124 | 125 | # Make sure there are no uncommited changes in the tree: 126 | check_for_uncommitted_changes() 127 | 128 | # Read existing changelog and __init__.py lines: 129 | with open(CHANGELOG, 'r') as fp: 130 | changelog_lines = fp.readlines() 131 | with open(INIT, 'r') as fp: 132 | init_lines = fp.readlines() 133 | 134 | # Parse out current version and distro, bump version: 135 | (current, distro) = parse_version_line(changelog_lines[0]) 136 | assert current == __version__ 137 | new = bump_version(current) 138 | assert new != __version__ 139 | newdeb = new + ALPHA 140 | 141 | # Build new changelog and __init__.py lines: 142 | new_changelog_lines = list(iter_new_changelog_lines(new, newdeb, distro)) 143 | new_init_lines = list(iter_new_init_lines(new, init_lines)) 144 | assert len(new_changelog_lines) == 6 145 | assert len(new_init_lines) == len(init_lines) 146 | 147 | # Again, make sure there are no uncommited changes in the tree: 148 | check_for_uncommitted_changes() 149 | 150 | # Write new changelog and __init__.py lines: 151 | with open(CHANGELOG, 'w') as fp: 152 | fp.writelines(new_changelog_lines + changelog_lines) 153 | with open(INIT, 'w') as fp: 154 | fp.writelines(new_init_lines) 155 | 156 | # Confirm before we make the commit: 157 | print('-' * 80) 158 | call(['git', 'diff']) 159 | print('-' * 80) 160 | print('Source tree is {!r}'.format(TREE)) 161 | print( 162 | 'Will bump {!r} version from {!r} to {!r}'.format(distro, current, newdeb) 163 | ) 164 | if not confirm(): 165 | print('') 166 | print('Version bump not committed, reverting changes...') 167 | check_call(['git', 'checkout', '--', CHANGELOG, INIT]) 168 | print('Goodbye.') 169 | sys.exit(0) 170 | 171 | # Make the commit: 172 | check_call(['git', 'commit', CHANGELOG, INIT, '-m', 'Bump version to {}'.format(newdeb)]) 173 | check_call(['git', 'push']) 174 | print('-' * 80) 175 | print('{!r} is now at version {!r}'.format(distro, newdeb)) 176 | -------------------------------------------------------------------------------- /com.system76.pkexec.system76-driver.policy: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | Authentication is required to run the System76 Driver 9 | system76-driver 10 | 11 | auth_admin 12 | auth_admin 13 | auth_admin 14 | 15 | /usr/bin/system76-driver-cli 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /debian/conffiles: -------------------------------------------------------------------------------- 1 | /etc/apt/preferences.d/system76-apt-preferences 2 | /etc/init/system76-driver.conf 3 | /etc/update-manager/release-upgrades.d/system76-third-party-mirrors.cfg 4 | /etc/xdg/autostart/system76-user-daemon.desktop 5 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: system76-driver 2 | Section: utils 3 | Priority: extra 4 | Maintainer: System76, Inc. 5 | Build-Depends: debhelper (>= 9.20160709), 6 | dh-python, 7 | gir1.2-gtk-3.0, 8 | gir1.2-notify-0.7, 9 | pyflakes3, 10 | python3-all (>= 3.6), 11 | python3-dbus, 12 | python3-evdev, 13 | python3-gi, 14 | python3-distro, 15 | python3-systemd, 16 | xbacklight 17 | Standards-Version: 4.5.0 18 | X-Python3-Version: >= 3.6 19 | Homepage: https://launchpad.net/system76-driver 20 | 21 | Package: system76-driver 22 | Architecture: amd64 arm64 23 | Suggests: gnome-color-manager 24 | Depends: ${python3:Depends}, ${misc:Depends}, 25 | at, 26 | firmware-manager-virtual [amd64], 27 | gir1.2-gtk-3.0, 28 | gir1.2-notify-0.7, 29 | ifupdown, 30 | linux-system76, 31 | pm-utils, 32 | python3-dbus, 33 | python3-evdev, 34 | python3-gi, 35 | python3-distro, 36 | python3-systemd, 37 | system76-acpi-dkms [amd64], 38 | system76-dkms [amd64], 39 | system76-firmware-daemon [amd64], 40 | system76-io-dkms, 41 | system76-oled [amd64], 42 | system76-power, 43 | system76-power-applet-virtual [amd64], 44 | xbacklight, 45 | usbutils, 46 | Recommends: hidpi-daemon [amd64], 47 | lm-sensors, 48 | system76-wallpapers 49 | Description: Universal driver for System76 computers 50 | System76 Driver provides drivers, restore, and regression support for System76 51 | computers running Ubuntu. Click the Device Menu (power icon at the top right 52 | of your screen) and choose System Settings. Click System76 Driver to install 53 | drivers or restore your computer. 54 | 55 | Package: system76-driver-nvidia 56 | Architecture: amd64 arm64 57 | Depends: ${misc:Depends}, 58 | nvidia-driver-570-open | nvidia-driver-570 | nvidia-driver-550 | nvidia-driver-470, 59 | system76-driver (>= ${binary:Version}), 60 | ubuntu-drivers-common, 61 | Recommends: amd-ppt-bin [amd64] 62 | Description: Latest nvidia driver for System76 computers 63 | This dummy package depends on the latest driver tested with and recommended for 64 | System76 products with an nvidia GPU. 65 | . 66 | When this package is installed, you will automatically be upgraded to newer 67 | nvidia driver versions after System76 has thouroughly tested them. 68 | . 69 | This driver will generally depend on a newer nvidia driver than the official 70 | nvidia-current-updates Ubuntu package. 71 | . 72 | If you don't want to be automatically upgraded to newer nvidia drivers, simply 73 | remove this package. 74 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Copyright and license: 2 | 3 | | Copyright (C) 2005-2016 System76, Inc. 4 | | 5 | | `system76-driver` is free software; you can redistribute it and/or modify 6 | | it under the terms of the GNU General Public License as published by 7 | | the Free Software Foundation; either version 2 of the License, or 8 | | (at your option) any later version. 9 | | 10 | | `system76-driver` is distributed in the hope that it will be useful, 11 | | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | | GNU General Public License for more details. 14 | | 15 | | You should have received a copy of the GNU General Public License along 16 | | with `system76-driver`; if not, write to the Free Software Foundation, Inc., 17 | | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | 19 | On Debian systems, the full text of the GNU General Public License is available 20 | in /usr/share/common-licenses/LGPL-2. 21 | 22 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | %: 4 | dh $@ --with=python3,systemd --buildsystem=pybuild 5 | 6 | override_dh_auto_clean: 7 | set -ex; for python in $(shell py3versions -r); do \ 8 | $$python setup.py clean; \ 9 | done 10 | 11 | override_dh_auto_build: 12 | set -ex; for python in $(shell py3versions -r); do \ 13 | $$python setup.py build \ 14 | --executable=/usr/bin/python3; \ 15 | done 16 | 17 | override_dh_auto_test: 18 | set -ex; for python in $(shell py3versions -r); do \ 19 | LC_ALL=C.UTF-8 $$python setup.py test --skip-gtk; \ 20 | done 21 | 22 | override_dh_auto_install: 23 | set -ex; for python in $(shell py3versions -r); do \ 24 | $$python setup.py install \ 25 | --install-layout=deb \ 26 | --root=$(CURDIR)/debian/system76-driver; \ 27 | done 28 | mkdir -p $(CURDIR)/debian/system76-driver/var/lib/system76-driver 29 | 30 | override_dh_installgsettings: 31 | dh_installgsettings --priority=40 32 | 33 | override_dh_installdeb: 34 | dh_installdeb 35 | cp debian/conffiles debian/system76-driver/DEBIAN/conffiles 36 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /debian/source_system76-driver.py: -------------------------------------------------------------------------------- 1 | """ 2 | Apport package hook for system76-driver (requires Apport 2.5 or newer). 3 | 4 | Copyright (C) 2005-2013 System76, Inc. 5 | """ 6 | 7 | from apport.hookutils import attach_file_if_exists 8 | 9 | LOGS = ( 10 | ('DriverLog', '/var/log/system76-driver.log'), 11 | ('DaemonLog', '/var/log/upstart/system76-driver.log'), 12 | ) 13 | 14 | def add_info(report): 15 | report['CrashDB'] = "{'impl': 'launchpad', 'project': 'system76-driver'}" 16 | for (key, filename) in LOGS: 17 | attach_file_if_exists(report, filename, key) 18 | 19 | -------------------------------------------------------------------------------- /debian/system76-driver-nvidia.install: -------------------------------------------------------------------------------- 1 | quirks/* usr/share/ubuntu-drivers-common/quirks/ 2 | -------------------------------------------------------------------------------- /debian/system76-driver-nvidia.postinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Note: deliberately not called with /bin/sh -e 4 | 5 | # system76-driver: Universal driver for System76 computers 6 | # Copyright (C) 2005-2014 System76, Inc. 7 | # 8 | # This file is part of `system76-driver`. 9 | # 10 | # `system76-driver` is free software; you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation; either version 2 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # `system76-driver` is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License along 21 | # with `system76-driver`; if not, write to the Free Software Foundation, Inc., 22 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 23 | 24 | case $1 in 25 | configure) 26 | if [ -x /usr/bin/quirks-handler -a -d /sys/module/nvidia ]; then 27 | /usr/bin/quirks-handler -e system76-driver-nvidia 28 | fi 29 | esac 30 | 31 | #DEBHELPER# 32 | -------------------------------------------------------------------------------- /debian/system76-driver.gsettings-override: -------------------------------------------------------------------------------- 1 | [org.gnome.desktop.peripherals.touchpad] 2 | tap-to-click = false 3 | 4 | [org.gnome.gedit.preferences.editor] 5 | tabs-size = 4 6 | insert-spaces = true 7 | auto-indent = true 8 | display-line-numbers = true 9 | display-right-margin = true 10 | highlight-current-line = true 11 | 12 | [com.canonical.Unity.Lenses] 13 | remote-content-search = 'none' 14 | 15 | -------------------------------------------------------------------------------- /debian/system76-driver.install: -------------------------------------------------------------------------------- 1 | com.system76.pkexec.system76-driver.policy usr/share/polkit-1/actions/ 2 | lib 3 | system76-nm-restart /lib/systemd/system-sleep/ 4 | system76-thunderbolt-reload /lib/systemd/system-sleep/ 5 | system76-virtual-hub /lib/systemd/system-sleep/ 6 | system76-apt-preferences /etc/apt/preferences.d/ 7 | system76-third-party-mirrors.cfg /etc/update-manager/release-upgrades.d/ 8 | system76-daemon usr/lib/system76-driver/ 9 | system76-user-daemon usr/lib/system76-driver/ 10 | system76-user-daemon.desktop etc/xdg/autostart/ 11 | debian/source_system76-driver.py usr/share/apport/package-hooks/ 12 | -------------------------------------------------------------------------------- /debian/system76-driver.postinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Note: deliberately not called with /bin/sh -e 4 | 5 | # system76-driver: Universal driver for System76 computers 6 | # Copyright (C) 2005-2014 System76, Inc. 7 | # 8 | # This file is part of `system76-driver`. 9 | # 10 | # `system76-driver` is free software; you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation; either version 2 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # `system76-driver` is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License along 21 | # with `system76-driver`; if not, write to the Free Software Foundation, Inc., 22 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 23 | #gsettings set org.gnome.shell enabled-extensions "['system76-power@system76.com']" ;; 24 | 25 | POWER_GSETTINGS_OVERRIDE="[org.gnome.shell]\nenabled-extensions = ['system76-power@system76.com']" 26 | gsetting_dir="/usr/share/glib-2.0/schemas" 27 | gsetting_power="/50_system76-power.gschema.override" 28 | 29 | lsb_description="$(grep DESCRIPTION /etc/lsb-release)" 30 | ubuntu="Ubuntu" 31 | 32 | case "$lsb_description" in 33 | *"$ubuntu"*) touch "$gsetting_dir$gsetting_power" && echo $POWER_GSETTINGS_OVERRIDE > $gsetting_dir$gsetting_power && glib-compile-schemas $gsetting_dir ;; 34 | *);; 35 | esac 36 | 37 | lsb_description="$(grep DESCRIPTION /etc/lsb-release)" 38 | ubuntu="Ubuntu" 39 | 40 | case "$lsb_description" in 41 | *"$ubuntu"*) gsettings set org.gnome.shell enabled-extensions "['system76-power@system76.com']" ;; 42 | *);; 43 | esac 44 | 45 | case $1 in 46 | configure) 47 | if [ -e /usr/bin/system76-driver-cli ]; then 48 | /usr/bin/system76-driver-cli 49 | fi 50 | esac 51 | 52 | if [ -f /lib/systemd/system-sleep/system76-atlantic-reload ]; then 53 | rm -f /lib/systemd/system-sleep/system76-atlantic-reload 54 | fi 55 | 56 | #DEBHELPER# 57 | -------------------------------------------------------------------------------- /debian/system76-driver.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=System76 airplane-mode hotkey and LED support 3 | 4 | [Service] 5 | ExecStart=/usr/lib/system76-driver/system76-daemon 6 | Restart=on-failure 7 | 8 | [Install] 9 | WantedBy=multi-user.target 10 | 11 | -------------------------------------------------------------------------------- /debian/system76-driver.upstart: -------------------------------------------------------------------------------- 1 | # system76-daemon - Support for Airplane Mode hotkey on System76 Laptops 2 | # 3 | # 4 | 5 | description "System76 airplane-mode hotkey and LED support" 6 | 7 | start on (login-session-start or desktop-session-start) 8 | stop on desktop-shutdown 9 | 10 | respawn 11 | 12 | exec /usr/lib/system76-driver/system76-daemon 13 | 14 | -------------------------------------------------------------------------------- /dmi-test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # system76-driver: Universal driver for System76 computers 4 | # Copyright (C) 2005-2016 System76, Inc. 5 | # 6 | # This file is part of `system76-driver`. 7 | # 8 | # `system76-driver` is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; either version 2 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # `system76-driver` is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License along 19 | # with `system76-driver`; if not, write to the Free Software Foundation, Inc., 20 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 21 | 22 | import json 23 | 24 | from system76driver.model import get_all_dmi_info 25 | 26 | 27 | info = get_all_dmi_info() 28 | print(json.dumps(info, sort_keys=True, indent=4)) 29 | -------------------------------------------------------------------------------- /lib/systemd/system-sleep/system76-driver_bluetooth-suspend: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | # Do not run if pop-default-settings patch is present 6 | if [ -x /lib/systemd/system-sleep/pop-default-settings_bluetooth-suspend ] 7 | then 8 | exit 0 9 | fi 10 | 11 | BT_BLOCK_PATH=/run/bluetooth.blocked 12 | BT_STATES_PATH=/var/lib/systemd/rfkill/ 13 | BT_TMP_PATH=/tmp/ 14 | 15 | case "$2" in 16 | suspend | hybrid-sleep) 17 | case "$1" in 18 | pre) 19 | if rfkill -o ID,TYPE,SOFT | grep -q -E 'bluetooth\s+unblocked'; then 20 | cp "$BT_STATES_PATH"*bluetooth "$BT_TMP_PATH" 21 | rfkill block bluetooth 22 | else 23 | > "$BT_BLOCK_PATH" 24 | fi 25 | ;; 26 | post) 27 | cp -f "$BT_TMP_PATH"*bluetooth "$BT_STATES_PATH" 2> /dev/null 28 | [ ! -f "$BT_BLOCK_PATH" ] && rfkill unblock bluetooth 29 | rm -f "$BT_BLOCK_PATH" 2> /dev/null 30 | rm -f "$BT_TMP_PATH"*bluetooth 2> /dev/null 31 | ;; 32 | esac 33 | ;; 34 | esac 35 | -------------------------------------------------------------------------------- /make-release.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # system76-driver: Universal driver for System76 computers 4 | # Copyright (C) 2005-2016 System76, Inc. 5 | # 6 | # This file is part of `system76-driver`. 7 | # 8 | # `system76-driver` is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; either version 2 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # `system76-driver` is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License along 19 | # with `system76-driver`; if not, write to the Free Software Foundation, Inc., 20 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 21 | 22 | """ 23 | Make a stable system76-driver release. 24 | """ 25 | 26 | import sys 27 | import os 28 | from os import path 29 | import re 30 | import time 31 | from subprocess import check_call, check_output, call 32 | 33 | from system76driver import __version__ 34 | from system76driver.tests.helpers import TempDir 35 | 36 | 37 | DISTROS = ('trusty', 'xenial', 'yakkety', 'zesty', 'artful', 'bionic', 'cosmic', 'disco', 'eoan', 'focal', 'noble') 38 | PPA = 'ppa:system76-dev/pre-stable' 39 | ALPHA = '~~alpha' 40 | 41 | TREE = path.dirname(path.abspath(__file__)) 42 | assert TREE == sys.path[0] 43 | assert os.getcwd() == TREE 44 | 45 | CHANGELOG = path.join(TREE, 'debian', 'changelog') 46 | SETUP = path.join(TREE, 'setup.py') 47 | INIT = path.join(TREE, 'system76driver', '__init__.py') 48 | DSC_NAME = 'system76-driver_{}.dsc'.format(__version__) 49 | 50 | assert path.isfile(CHANGELOG) 51 | assert path.isfile(SETUP) 52 | assert path.isfile(INIT) 53 | 54 | 55 | def confirm(): 56 | while True: 57 | response = input(' Okay? yes/NO: ').lower() 58 | if response == 'yes': 59 | return True 60 | if response == 'no': 61 | return False 62 | print("Please enter 'yes' or 'no'") 63 | 64 | 65 | def check_for_uncommitted_changes(): 66 | if check_output(['git', 'diff']).decode() != '': 67 | sys.exit('ERROR: unstaged changes!') 68 | if check_output(['git', 'diff', '--cached']).decode() != '': 69 | sys.exit('ERROR: uncommited changes!') 70 | 71 | 72 | def iter_input_lines(fp): 73 | yield fp.readline() 74 | 75 | line = fp.readline() 76 | if line != '\n': 77 | raise ValueError('bad empty line[1]:\n{!r}'.format(line)) 78 | yield line 79 | 80 | line = fp.readline() 81 | if not line.startswith(' * Daily WIP for '): 82 | raise ValueError('bad first item line[2]:\n{!r}'.format(line)) 83 | 84 | line = fp.readline() 85 | if line[:4] != ' * ': 86 | raise ValueError('bad second item line[3]:\n{!r}'.format(line)) 87 | yield line 88 | 89 | i = 4 90 | while True: 91 | line = fp.readline() 92 | if line[:4] not in (' * ', ' ', '\n'): 93 | raise ValueError('bad item line[{}]:\n{!r}'.format(i, line)) 94 | yield line 95 | i += 1 96 | if line == '\n': 97 | break 98 | 99 | line = fp.readline() 100 | if line[:4] != ' -- ': 101 | raise ValueError('bad author line[{}]:\n{!r}'.format(i, line)) 102 | yield line 103 | 104 | 105 | def parse_version_line(line): 106 | if ALPHA not in line: 107 | raise ValueError('Missing {!r} in version:\n{!r}'.format(ALPHA, line)) 108 | m = re.match( 109 | '^system76-driver \(([\.0-9]+)' + ALPHA + '\) ([a-z]+); urgency=(low|medium|high|emergency|critical)$', line 110 | ) 111 | if m is None: 112 | raise ValueError('bad version line[0]:\n{!r}'.format(line)) 113 | ver = m.group(1) 114 | if ver != __version__: 115 | raise ValueError( 116 | 'changelog != __version: {!r} != {!r}'.format(ver, __version__) 117 | ) 118 | distro = m.group(2) 119 | if distro not in DISTROS: 120 | raise ValueError('bad distro {!r} not in {!r}'.format(distro, DISTROS)) 121 | return (ver, distro) 122 | 123 | 124 | def build_version_line(line): 125 | parse_version_line(line) 126 | return line.replace(ALPHA, '') 127 | 128 | 129 | def build_author_line(): 130 | user = check_output(['git', 'config', '--get', 'user.name']).decode().strip() 131 | email = check_output(['git', 'config', '--get', 'user.email']).decode().strip() 132 | author = ' '.join((user, '<' + email + '>')) 133 | ts = time.strftime('%a, %d %b %Y %H:%M:%S %z', time.localtime()) 134 | return ' -- {} {}\n'.format(author, ts) 135 | 136 | 137 | def iter_output_lines(input_lines): 138 | yield build_version_line(input_lines[0]) 139 | yield from input_lines[1:-1] 140 | yield build_author_line() 141 | 142 | 143 | # Make sure there are no uncommited changes in the tree: 144 | check_for_uncommitted_changes() 145 | 146 | # Read lines from current debian/changelog file: 147 | with open(CHANGELOG, 'r') as fp: 148 | input_lines = list(iter_input_lines(fp)) 149 | remaining_lines = fp.readlines() 150 | 151 | # Parse and validate, then build lines for new changelog files: 152 | (version, distro) = parse_version_line(input_lines[0]) 153 | assert version == __version__ 154 | output_lines = list(iter_output_lines(input_lines)) 155 | assert len(output_lines) == len(input_lines) 156 | assert output_lines[1:-1] == input_lines[1:-1] 157 | 158 | # Again, make sure there are no uncommited changes in the tree: 159 | check_for_uncommitted_changes() 160 | 161 | # Write the new debian/changelog file: 162 | with open(CHANGELOG, 'w') as fp: 163 | fp.writelines(output_lines + remaining_lines) 164 | 165 | # Make sure the unit tests pass in-tree: 166 | check_call([SETUP, 'test']) 167 | 168 | # Make sure package builds okay locally using pbuilder-dist: 169 | check_call(['pbuilder-dist', distro, 'update']) 170 | tmp = TempDir() 171 | os.mkdir(tmp.join('result')) 172 | check_call(['dpkg-source', '-b', TREE], cwd=tmp.join('result')) 173 | check_call(['pbuilder-dist', distro, 'build', tmp.join('result', DSC_NAME)]) 174 | del tmp 175 | 176 | def abort(msg=None): 177 | if msg is not None: 178 | print('\nERROR: ' + msg) 179 | print('') 180 | print('Release not made, reverting changes...') 181 | check_call(['git', 'checkout', '--', CHANGELOG, INIT]) 182 | print('Goodbye.') 183 | status = (0 if msg is None else 2) 184 | sys.exit(status) 185 | 186 | # Confirm before we make the commit: 187 | print('-' * 80) 188 | call(['git', 'diff']) 189 | print('-' * 80) 190 | print('Source tree is {!r}'.format(TREE)) 191 | print('Will release {!r} for {!r}'.format(version, distro)) 192 | if not confirm(): 193 | abort() 194 | 195 | # Commit and tag: 196 | check_call(['git', 'commit', CHANGELOG, '-m', 'Release {}'.format(version)]) 197 | check_call(['git', 'push']) 198 | check_call(['git', 'tag', version]) 199 | check_call(['git', 'push', 'origin', 'tag', version]) 200 | 201 | # We're done: 202 | print('-' * 80) 203 | print('Released {!r} for {!r}'.format(version, distro)) 204 | -------------------------------------------------------------------------------- /po/ChangeLog: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pop-os/system76-driver/6e6b8065595acfb97dc8ff351b17e67d5f89ad46/po/ChangeLog -------------------------------------------------------------------------------- /po/POTFILES.in: -------------------------------------------------------------------------------- 1 | # List of source files containing translatable strings. 2 | 3 | src/main.c 4 | src/interface.c 5 | src/callbacks.c 6 | src/support.c 7 | -------------------------------------------------------------------------------- /quirks/system76-nvidia-quirks-bonw11: -------------------------------------------------------------------------------- 1 | Section "Quirk" 2 | Identifier "system76-0-bonw11" 3 | Handler "nvidia-390" 4 | Match "sys_vendor" "System76, Inc.|System76" 5 | Match "product_version" "bonw11" 6 | XorgSnippet 7 | Section "Device" 8 | Identifier "System76 nVidia Card" 9 | Driver "nvidia" 10 | Option "FlatPanelProperties" "DFP-1: Dithering = Enabled" 11 | EndSection 12 | EndXorgSnippet 13 | EndSection 14 | -------------------------------------------------------------------------------- /quirks/system76-nvidia-quirks-oryp2: -------------------------------------------------------------------------------- 1 | Section "Quirk" 2 | Identifier "system76-0-oryp2" 3 | Handler "nvidia-390" 4 | Match "sys_vendor" "System76, Inc." 5 | Match "product_version" "oryp2" 6 | XorgSnippet 7 | Section "Device" 8 | Identifier "System76 nVidia Card" 9 | Driver "nvidia" 10 | Option "FlatPanelProperties" "DFP-1: Dithering = Enabled" 11 | EndSection 12 | EndXorgSnippet 13 | EndSection 14 | -------------------------------------------------------------------------------- /quirks/system76-nvidia-quirks-oryp2-ess: -------------------------------------------------------------------------------- 1 | Section "Quirk" 2 | Identifier "system76-0-oryp2-ess" 3 | Handler "nvidia-390" 4 | Match "sys_vendor" "System76, Inc." 5 | Match "product_version" "oryp2-ess" 6 | XorgSnippet 7 | Section "Device" 8 | Identifier "System76 nVidia Card" 9 | Driver "nvidia" 10 | Option "FlatPanelProperties" "DFP-1: Dithering = Enabled" 11 | EndSection 12 | EndXorgSnippet 13 | EndSection 14 | -------------------------------------------------------------------------------- /quirks/system76-nvidia-quirks-oryp3: -------------------------------------------------------------------------------- 1 | Section "Quirk" 2 | Identifier "system76-0-oryp3" 3 | Handler "nvidia-390" 4 | Match "sys_vendor" "System76" 5 | Match "product_version" "oryp3" 6 | XorgSnippet 7 | Section "Device" 8 | Identifier "System76 nVidia Card" 9 | Driver "nvidia" 10 | Option "FlatPanelProperties" "DFP-1: Dithering = Enabled" 11 | EndSection 12 | EndXorgSnippet 13 | EndSection 14 | -------------------------------------------------------------------------------- /quirks/system76-nvidia-quirks-oryp3-b: -------------------------------------------------------------------------------- 1 | Section "Quirk" 2 | Identifier "system76-0-oryp3-b" 3 | Handler "nvidia-390" 4 | Match "sys_vendor" "System76" 5 | Match "product_version" "oryp3-b" 6 | XorgSnippet 7 | Section "Device" 8 | Identifier "System76 nVidia Card" 9 | Driver "nvidia" 10 | Option "FlatPanelProperties" "DFP-1: Dithering = Enabled" 11 | EndSection 12 | EndXorgSnippet 13 | EndSection 14 | -------------------------------------------------------------------------------- /quirks/system76-nvidia-quirks-oryp3-ess: -------------------------------------------------------------------------------- 1 | Section "Quirk" 2 | Identifier "system76-0-oryp3-ess" 3 | Handler "nvidia-390" 4 | Match "sys_vendor" "System76" 5 | Match "product_version" "oryp3-ess" 6 | XorgSnippet 7 | Section "Device" 8 | Identifier "System76 nVidia Card" 9 | Driver "nvidia" 10 | Option "FlatPanelProperties" "DFP-1: Dithering = Enabled" 11 | EndSection 12 | EndXorgSnippet 13 | EndSection 14 | -------------------------------------------------------------------------------- /quirks/system76-nvidia-quirks-serw10: -------------------------------------------------------------------------------- 1 | Section "Quirk" 2 | Identifier "system76-0-serw10" 3 | Handler "nvidia-390" 4 | Match "sys_vendor" "System76, Inc.|System76" 5 | Match "product_version" "serw10" 6 | XorgSnippet 7 | Section "Device" 8 | Identifier "System76 nVidia Card" 9 | Driver "nvidia" 10 | Option "FlatPanelProperties" "DFP-1: Dithering = Enabled" 11 | EndSection 12 | EndXorgSnippet 13 | EndSection 14 | -------------------------------------------------------------------------------- /quirks/system76-nvidia-quirks-thelio-massive-b1: -------------------------------------------------------------------------------- 1 | Section "Quirk" 2 | Identifier "system76-0-thelio-massive-b1" 3 | Handler "system76-driver-nvidia" 4 | Match "sys_vendor" "System76" 5 | Match "product_version" "thelio-massive-b1" 6 | XorgSnippet 7 | Section "Device" 8 | Identifier "Device0" 9 | Driver "nvidia" 10 | EndSection 11 | EndXorgSnippet 12 | EndSection 13 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # system76-driver: Universal driver for System76 computers 4 | # Copyright (C) 2005-2016 System76, Inc. 5 | # 6 | # This file is part of `system76-driver`. 7 | # 8 | # `system76-driver` is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; either version 2 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # `system76-driver` is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License along 19 | # with `system76-driver`; if not, write to the Free Software Foundation, Inc., 20 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 21 | 22 | 23 | """ 24 | Install `system76driver`. 25 | """ 26 | 27 | import sys 28 | if sys.version_info < (3, 4): 29 | sys.exit('ERROR: `system76driver` requires Python 3.4 or newer') 30 | 31 | import os 32 | from os import path 33 | import subprocess 34 | from distutils.core import setup 35 | from distutils.cmd import Command 36 | 37 | import system76driver 38 | from system76driver.tests.run import run_tests 39 | 40 | 41 | SCRIPTS = [ 42 | 'system76-driver', 43 | 'system76-driver-cli', 44 | ] 45 | 46 | 47 | def run_pyflakes3(): 48 | pyflakes3 = '/usr/bin/pyflakes3' 49 | if not os.access(pyflakes3, os.R_OK | os.X_OK): 50 | print('WARNING: cannot read and execute: {!r}'.format(pyflakes3)) 51 | return 52 | tree = path.dirname(path.abspath(__file__)) 53 | names = [ 54 | 'system76driver', 55 | 'setup.py', 56 | 'system76-daemon', 57 | ] + SCRIPTS 58 | cmd = [pyflakes3] + [path.join(tree, name) for name in names] 59 | print('check_call:', cmd) 60 | subprocess.check_call(cmd) 61 | print('[pyflakes3 checks passed]') 62 | 63 | 64 | class Test(Command): 65 | description = 'run unit tests and doc tests' 66 | 67 | user_options = [ 68 | ('skip-gtk', None, 'Skip GTK related tests'), 69 | ] 70 | 71 | def initialize_options(self): 72 | self.skip_gtk = 0 73 | 74 | def finalize_options(self): 75 | pass 76 | 77 | def run(self): 78 | if not run_tests(self.skip_gtk): 79 | raise SystemExit(2) 80 | run_pyflakes3() 81 | 82 | 83 | setup( 84 | name='system76driver', 85 | version=system76driver.__version__, 86 | description='hardware-specific enhancements for System76 products', 87 | url='https://launchpad.net/system76-driver', 88 | author='System76, Inc.', 89 | author_email='dev@system76.com', 90 | license='GPLv2+', 91 | cmdclass={'test': Test}, 92 | packages=[ 93 | 'system76driver', 94 | 'system76driver.tests' 95 | ], 96 | scripts=SCRIPTS, 97 | package_data={ 98 | 'system76driver': ['data/*'], 99 | }, 100 | data_files=[ 101 | ('share/applications', ['system76-driver.desktop']), 102 | ('share/icons/hicolor/scalable/apps', ['system76-driver.svg']), 103 | ], 104 | ) 105 | -------------------------------------------------------------------------------- /system76-apt-preferences: -------------------------------------------------------------------------------- 1 | Package: * 2 | Pin: release o=LP-PPA-system76-dev-stable 3 | Pin-Priority: 1001 4 | 5 | Package: * 6 | Pin: release o=LP-PPA-system76-dev-pre-stable 7 | Pin-Priority: 1001 8 | -------------------------------------------------------------------------------- /system76-daemon: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # system76-driver: Universal driver for System76 computers 4 | # Copyright (C) 2005-2016 System76, Inc. 5 | # 6 | # This file is part of `system76-driver`. 7 | # 8 | # `system76-driver` is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; either version 2 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # `system76-driver` is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License along 19 | # with `system76-driver`; if not, write to the Free Software Foundation, Inc., 20 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 21 | 22 | """ 23 | Test Airplane Mode workaround. 24 | """ 25 | 26 | import time 27 | import argparse 28 | import os 29 | import sys 30 | import logging 31 | 32 | from gi.repository import GLib 33 | 34 | import system76driver 35 | from system76driver import daemon 36 | 37 | start_time = time.monotonic() 38 | 39 | logging.basicConfig( 40 | level=logging.DEBUG, 41 | style='{', 42 | format='{asctime} {levelname} {message}', 43 | ) 44 | log = logging.getLogger() 45 | 46 | parser = argparse.ArgumentParser() 47 | parser.add_argument('--model', help='force model rather than detecting it') 48 | parser.add_argument('--debug', action='store_true', default=False, 49 | help='print loaded modules', 50 | ) 51 | args = parser.parse_args() 52 | 53 | if os.getuid() != 0: 54 | sys.exit('Error: system76-daemon must be run as root') 55 | log.info('**** Process start at monotonic time %r', start_time) 56 | 57 | if not args.model: 58 | model = daemon.load_json_conf('/etc/system76-daemon.json').get('model') 59 | args.model = (model or system76driver.get_product_version()) 60 | log.info('model: %r', args.model) 61 | brightness = daemon.run_brightness(args.model) 62 | airplane = daemon.run_airplane(args.model) 63 | acpi = daemon.run_firmware_acpi_interrupt(args.model) 64 | ess_dac_autoswitch = daemon.run_ess_dac_autoswitch(args.model) 65 | daemon.run_headphone_volume_adjust(args.model) 66 | daemon.run_dpcd_pwm(args.model) 67 | tdp = daemon.run_limit_power_draw(args.model) 68 | 69 | mainloop = GLib.MainLoop() 70 | if args.debug: 71 | names = sorted(sys.modules) 72 | for name in names: 73 | print(name) 74 | print(len(names)) 75 | mainloop.run() 76 | -------------------------------------------------------------------------------- /system76-driver: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # system76-driver: Universal driver for System76 computers 4 | # Copyright (C) 2005-2016 System76, Inc. 5 | # 6 | # This file is part of `system76-driver`. 7 | # 8 | # `system76-driver` is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; either version 2 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # `system76-driver` is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License along 19 | # with `system76-driver`; if not, write to the Free Software Foundation, Inc., 20 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 21 | 22 | import argparse 23 | import os 24 | import time 25 | import logging 26 | import distro 27 | 28 | import system76driver 29 | from system76driver.model import determine_model_new 30 | from system76driver.products import PRODUCTS 31 | from system76driver.gtk import UI 32 | 33 | 34 | parser = argparse.ArgumentParser() 35 | parser.add_argument('--home', help='specify home directory') 36 | parser.add_argument('--model', help='force model rather than detecting it') 37 | parser.add_argument('--dry', action='store_true', default=False, 38 | help='print what would be done but without calling Action.perform()', 39 | ) 40 | args = parser.parse_args() 41 | 42 | logging.basicConfig( 43 | level=logging.DEBUG, 44 | style='{', 45 | format='{asctime} {levelname} {message}', 46 | ) 47 | log = logging.getLogger() 48 | 49 | if args.home is None: 50 | args.home = os.environ['HOME'] 51 | if args.model is None: 52 | args.model = determine_model_new() 53 | product = PRODUCTS.get(args.model) 54 | 55 | log.info('** Process start at monotonic time %r', time.monotonic()) 56 | log.info('system76driver.__version__: %r', system76driver.__version__) 57 | log.info('OS: %r', distro.name()) 58 | log.info('kernel: %r', os.uname().release) 59 | log.info('model: %r', args.model) 60 | 61 | ui = UI(args.model, product, args) 62 | ui.run() 63 | 64 | -------------------------------------------------------------------------------- /system76-driver-cli: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # system76-driver: Universal driver for System76 computers 4 | # Copyright (C) 2005-2016 System76, Inc. 5 | # 6 | # This file is part of `system76-driver`. 7 | # 8 | # `system76-driver` is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; either version 2 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # `system76-driver` is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License along 19 | # with `system76-driver`; if not, write to the Free Software Foundation, Inc., 20 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 21 | 22 | 23 | import argparse 24 | import os 25 | from os import path 26 | import sys 27 | import logging 28 | 29 | import system76driver 30 | from system76driver.products import PRODUCTS 31 | from system76driver.actions import ActionRunner 32 | from system76driver.util import create_logs 33 | 34 | parser = argparse.ArgumentParser() 35 | parser.add_argument('--strict', action='store_true', default=False, 36 | help='Exit with a non-zero status if not a valid system76 product', 37 | ) 38 | parser.add_argument('--model', help='force model rather than detecting it') 39 | parser.add_argument('--logs', help='Store logs to specified home directory') 40 | args = parser.parse_args() 41 | exitcode = (2 if args.strict else 0) 42 | 43 | # Because system76-driver-cli will be called automatically from the postinst 44 | # script, we don't log to /var/log/system76-driver.log 45 | logging.basicConfig( 46 | level=logging.DEBUG, 47 | format='%(levelname)s\t%(message)s', 48 | ) 49 | log = logging.getLogger() 50 | 51 | # This is a hack so that the driver doesn't get run in the VM when mastering 52 | # and maintaining the golden images: 53 | MARKER = '/var/cache/system76-pre-master.marker' 54 | if path.exists(MARKER): 55 | log.warning('Pre-master marker exists: %r', MARKER) 56 | sys.exit(0) 57 | 58 | 59 | product_version = args.model or system76driver.get_product_version() 60 | try: 61 | product = PRODUCTS[product_version] 62 | log.info('product_version: %r', product_version) 63 | except KeyError: 64 | log.warning('invalid product_version: %r', product_version) 65 | sys.exit(exitcode) 66 | 67 | if os.getuid() != 0: 68 | log.error('must be run as root') 69 | sys.exit(exitcode) 70 | 71 | try: 72 | if args.logs is not None: 73 | tgz = create_logs(args.logs) 74 | log.info('logs stored in %s', tgz) 75 | else: 76 | action_runner = ActionRunner(product['drivers']) 77 | for msg in action_runner.run_iter(): 78 | log.info('* %s', msg) 79 | except Exception: 80 | log.exception('Error running actions:') 81 | sys.exit(exitcode) 82 | 83 | -------------------------------------------------------------------------------- /system76-driver.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Version=1.1 3 | Encoding=UTF-8 4 | Name=System76 Driver 5 | GenericName=System76 Driver 6 | Comment=Installs Drivers for System76 Systems 7 | Exec=system76-driver 8 | Terminal=false 9 | Type=Application 10 | Icon=system76-driver 11 | Categories=GNOME;GTK;Settings;X-GNOME-SystemSettings;X-GNOME-Settings-Panel;X-Unity-Settings-Panel; 12 | GenericName[en_US]=System76 Driver 13 | X-Unity-Settings-Panel=system76-driver 14 | X-GNOME-Settings-Panel=system76-driver 15 | -------------------------------------------------------------------------------- /system76-driver.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 25 | 27 | 31 | 39 | 40 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /system76-nm-restart: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # system76-driver: Universal driver for System76 computers 4 | # Copyright (C) 2005-2016 System76, Inc. 5 | # 6 | # This file is part of `system76-driver`. 7 | # 8 | # `system76-driver` is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; either version 2 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # `system76-driver` is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License along 19 | # with `system76-driver`; if not, write to the Free Software Foundation, Inc., 20 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 21 | 22 | # Starting with 16.04, there are WiFi reliability issues when resuming from 23 | # suspend, at least with certain WiFi cards. In particular, this problem seems 24 | # to effect Haswell generation cards like the 7260. It does not seem to effect 25 | # Skylake generation cards like the 3165, 8260. 26 | # 27 | # This seems fundamentally a user-space issue, not a kernel issue. 28 | # 29 | # This script is installed at /lib/systemd/system-sleep/system76-nm-restart. 30 | 31 | set -e 32 | 33 | if [ "$2" = "suspend" ] || [ "$2" = "hybrid-sleep" ]; then 34 | case "$1" in 35 | pre) true ;; 36 | post) sleep 1 && sudo systemctl restart NetworkManager ;; 37 | esac 38 | fi 39 | 40 | -------------------------------------------------------------------------------- /system76-third-party-mirrors.cfg: -------------------------------------------------------------------------------- 1 | [ThirdPartyMirrors] 2 | system76-dev = http://ppa.launchpad.net/system76-dev/stable/ubuntu/ 3 | -------------------------------------------------------------------------------- /system76-thunderbolt-reload: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # system76-driver: Universal driver for System76 computers 4 | # Copyright (C) 2005-2019 System76, Inc. 5 | # 6 | # This file is part of `system76-driver`. 7 | # 8 | # `system76-driver` is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; either version 2 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # `system76-driver` is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License along 19 | # with `system76-driver`; if not, write to the Free Software Foundation, Inc., 20 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 21 | 22 | # This script removes the thunderbolt PCI bridge on suspend and rescans it 23 | # on resume in order to prevent hangs on resume. 24 | 25 | set -e 26 | 27 | case "$(cat /sys/class/dmi/id/product_version)" in 28 | darp6 | galp4) 29 | case "$2" in 30 | suspend | hybrid-sleep) 31 | case "$1" in 32 | pre) 33 | echo 1 > '/sys/devices/pci0000:00/0000:00:1c.0/remove' 34 | ;; 35 | post) 36 | echo 1 > '/sys/devices/pci0000:00/0000:00:00.0/rescan' 37 | ;; 38 | esac 39 | ;; 40 | esac 41 | ;; 42 | esac 43 | -------------------------------------------------------------------------------- /system76-user-daemon: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # system76-driver: Universal driver for System76 computers 4 | # Copyright (C) 2005-2017 System76, Inc. 5 | # 6 | # This file is part of `system76-driver`. 7 | # 8 | # `system76-driver` is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; either version 2 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # `system76-driver` is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License along 19 | # with `system76-driver`; if not, write to the Free Software Foundation, Inc., 20 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 21 | 22 | """ 23 | Backlight daemon to workaround incompatibility between GNOME and NVIDIA 9 Series. 24 | """ 25 | 26 | import time 27 | start_time = time.monotonic() 28 | import argparse 29 | import os 30 | import sys 31 | import logging 32 | 33 | from gi.repository import GLib 34 | from gi.repository import GObject 35 | 36 | import system76driver 37 | from system76driver import daemon, userdaemon 38 | from system76driver.daemon import load_json_conf 39 | 40 | 41 | logging.basicConfig( 42 | level=logging.DEBUG, 43 | style='{', 44 | format='{asctime} {levelname} {message}', 45 | ) 46 | log = logging.getLogger() 47 | 48 | parser = argparse.ArgumentParser() 49 | parser.add_argument('--model', help='force model rather than detecting it') 50 | parser.add_argument('--debug', action='store_true', default=False, 51 | help='print loaded modules', 52 | ) 53 | args = parser.parse_args() 54 | 55 | if os.getuid() == 0: 56 | sys.exit('Error: system76-daemon must be run as user') 57 | log.info('**** Process start at monotonic time %r', start_time) 58 | 59 | if not args.model: 60 | model = daemon.load_json_conf('/etc/system76-daemon.json').get('model') 61 | args.model = (model or system76driver.get_product_version()) 62 | log.info('model: %r', args.model) 63 | userdaemon.run_backlight(args.model) 64 | 65 | mainloop = GLib.MainLoop() 66 | if args.debug: 67 | names = sorted(sys.modules) 68 | for name in names: 69 | print(name) 70 | print(len(names)) 71 | mainloop.run() 72 | -------------------------------------------------------------------------------- /system76-user-daemon.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Type=Application 3 | Encoding=UTF-8 4 | Name=System76 User Daemon 5 | Comment=Make backlight work on GNOME with NVIDIA 9-Series hardware 6 | Exec=/usr/lib/system76-driver/system76-user-daemon 7 | Terminal=false 8 | Icon=folder 9 | NoDisplay=true 10 | StartupNotify=false 11 | Categories=System;Settings; 12 | -------------------------------------------------------------------------------- /system76-virtual-hub: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # system76-driver: Universal driver for System76 computers 4 | # Copyright (C) 2005-2019 System76, Inc. 5 | # 6 | # This file is part of `system76-driver`. 7 | # 8 | # `system76-driver` is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; either version 2 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # `system76-driver` is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License along 19 | # with `system76-driver`; if not, write to the Free Software Foundation, Inc., 20 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 21 | 22 | # This script removes some USB virtual devices on suspend and rescans 23 | # them on resume in order to prevent the system from spontaneously 24 | # waking from suspend after a few seconds. 25 | 26 | set -e 27 | 28 | vendor_id="046b" 29 | device_id="ffb0" 30 | bus_number="" 31 | remove_file="" 32 | 33 | case "$(cat /sys/class/dmi/id/product_version)" in 34 | thelio-mega-r3) 35 | case "$2" in 36 | suspend | hybrid-sleep) 37 | case "$1" in 38 | pre) 39 | if [ -f /tmp/system76-virtual-hub ]; then 40 | rm /tmp/system76-virtual-hub 41 | fi 42 | 43 | device_info=$(lsusb | grep "$vendor_id" | grep "$device_id") 44 | bus_number=$(echo "$device_info" | awk '{print $2}' | sed 's/^0*//') 45 | 46 | # Exit if bus_number is empty 47 | if [ -z "$bus_number" ]; then 48 | exit 49 | fi 50 | 51 | # Iterate through idVendor paths and directories 52 | for path in /sys/bus/usb/devices/usb${bus_number}/${bus_number}-*/idVendor; do 53 | dir=$(dirname "$path") 54 | id_vendor=$(cat "$path") 55 | if [ "$id_vendor" = "$vendor_id" ]; then 56 | remove_file="${dir}/remove" 57 | break 58 | fi 59 | done 60 | 61 | # Check if a matching directory was found 62 | if [ ! -z "$remove_file" ]; then 63 | echo "$bus_number" > /tmp/system76-virtual-hub 64 | if [ -e "$remove_file" ]; then 65 | echo 1 > "$remove_file" 66 | fi 67 | fi 68 | ;; 69 | post) 70 | if [ -f /tmp/system76-virtual-hub ]; then 71 | bus_number=$(cat /tmp/system76-virtual-hub) 72 | rm /tmp/system76-virtual-hub 73 | fi 74 | cd $(readlink -f /sys/bus/usb/devices/usb${bus_number}) 75 | echo 1 > ../rescan 76 | ;; 77 | esac 78 | ;; 79 | esac 80 | ;; 81 | esac 82 | -------------------------------------------------------------------------------- /system76driver/__init__.py: -------------------------------------------------------------------------------- 1 | # system76-driver: Universal driver for System76 computers 2 | # Copyright (C) 2005-2016 System76, Inc. 3 | # 4 | # This file is part of `system76-driver`. 5 | # 6 | # `system76-driver` is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # `system76-driver` is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along 17 | # with `system76-driver`; if not, write to the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 19 | 20 | """ 21 | Universal driver for System76 computers 22 | """ 23 | 24 | from os import path 25 | import logging 26 | 27 | 28 | __version__ = '24.04.5' 29 | 30 | datadir = path.join(path.dirname(path.abspath(__file__)), 'data') 31 | log = logging.getLogger(__name__) 32 | 33 | 34 | # Unfortunately, we need to accomidate some typos and goofs in sys_vendor: 35 | VALID_SYS_VENDOR = ( 36 | 'System76', # Current standard 37 | 'System76, Inc.', # Previous standard 38 | 'System76, Inc', 39 | 'System76, Inc .', 40 | 'Notebook', 41 | ) 42 | 43 | 44 | def get_datafile(name): 45 | return path.join(datadir, name) 46 | 47 | 48 | def read_dmi_id(key, sysdir='/sys'): 49 | if key not in ('sys_vendor', 'product_version'): 50 | raise ValueError('bad dmi/id key: {!r}'.format(key)) 51 | filename = path.join(sysdir, 'class', 'dmi', 'id', key) 52 | try: 53 | with open(filename, 'r') as fp: 54 | return fp.read(256).strip() 55 | except (FileNotFoundError, UnicodeDecodeError): 56 | pass 57 | 58 | 59 | def get_sys_vendor(sysdir='/sys'): 60 | sys_vendor = read_dmi_id('sys_vendor', sysdir) 61 | if sys_vendor in VALID_SYS_VENDOR: 62 | return sys_vendor 63 | log.warning('invalid sys_vendor: %r', sys_vendor) 64 | 65 | 66 | def get_product_version(sysdir='/sys'): 67 | if get_sys_vendor(sysdir) is not None: 68 | return read_dmi_id('product_version', sysdir) 69 | -------------------------------------------------------------------------------- /system76driver/data/76icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml -------------------------------------------------------------------------------- /system76driver/data/76icon_primary.svg: -------------------------------------------------------------------------------- 1 | 2 | image/svg+xml 21 | 24 | 25 | 28 | 30 | 34 | 38 | 42 | 46 | 47 | 51 | 52 | 55 | 56 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /system76driver/data/analog-input-internal-mic.conf: -------------------------------------------------------------------------------- 1 | # This file is part of PulseAudio. 2 | # 3 | # PulseAudio is free software; you can redistribute it and/or modify 4 | # it under the terms of the GNU Lesser General Public License as 5 | # published by the Free Software Foundation; either version 2.1 of the 6 | # License, or (at your option) any later version. 7 | # 8 | # PulseAudio is distributed in the hope that it will be useful, but 9 | # WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public License 14 | # along with PulseAudio; if not, see . 15 | 16 | ; For devices where a 'Internal Mic' or 'Internal Mic Boost' element exists 17 | ; 'Int Mic' and 'Int Mic Boost' are for compatibility with kernels < 2.6.38 18 | ; 19 | ; See analog-output.conf.common for an explanation on the directives 20 | 21 | [General] 22 | priority = 89 23 | description-key = analog-input-microphone-internal 24 | 25 | [Jack Mic] 26 | state.plugged = no 27 | state.unplugged = unknown 28 | 29 | [Jack Dock Mic] 30 | state.plugged = no 31 | state.unplugged = unknown 32 | 33 | [Jack Front Mic] 34 | state.plugged = no 35 | state.unplugged = unknown 36 | 37 | [Jack Rear Mic] 38 | state.plugged = no 39 | state.unplugged = unknown 40 | 41 | [Jack Internal Mic Phantom] 42 | state.plugged = unknown 43 | state.unplugged = unknown 44 | required-any = any 45 | 46 | [Element Capture] 47 | switch = mute 48 | volume = merge 49 | override-map.1 = all 50 | override-map.2 = all-left,all-right 51 | 52 | [Element Internal Mic Boost] 53 | required-any = any 54 | switch = select 55 | volume = zero 56 | override-map.1 = all 57 | override-map.2 = all-left,all-right 58 | 59 | [Option Internal Mic Boost:on] 60 | name = input-boost-on 61 | 62 | [Option Internal Mic Boost:off] 63 | name = input-boost-off 64 | 65 | [Element Int Mic Boost] 66 | required-any = any 67 | switch = select 68 | volume = merge 69 | override-map.1 = all 70 | override-map.2 = all-left,all-right 71 | 72 | [Option Int Mic Boost:on] 73 | name = input-boost-on 74 | 75 | [Option Int Mic Boost:off] 76 | name = input-boost-off 77 | 78 | [Element Internal Mic] 79 | required-any = any 80 | switch = mute 81 | volume = merge 82 | override-map.1 = all 83 | override-map.2 = all-left,all-right 84 | 85 | [Element Int Mic] 86 | required-any = any 87 | switch = mute 88 | volume = merge 89 | override-map.1 = all 90 | override-map.2 = all-left,all-right 91 | 92 | [Element Input Source] 93 | enumeration = select 94 | 95 | [Option Input Source:Internal Mic] 96 | name = analog-input-microphone-internal 97 | required-any = any 98 | 99 | [Option Input Source:Int Mic] 100 | name = analog-input-microphone-internal 101 | required-any = any 102 | 103 | [Element Capture Source] 104 | enumeration = select 105 | 106 | [Option Capture Source:Internal Mic] 107 | name = analog-input-microphone-internal 108 | required-any = any 109 | 110 | [Option Capture Source:Int Mic] 111 | name = analog-input-microphone-internal 112 | required-any = any 113 | 114 | [Element Mic] 115 | switch = off 116 | volume = off 117 | 118 | [Element Dock Mic] 119 | switch = off 120 | volume = off 121 | 122 | [Element Front Mic] 123 | switch = off 124 | volume = off 125 | 126 | [Element Rear Mic] 127 | switch = off 128 | volume = off 129 | 130 | [Element Headphone Mic] 131 | switch = off 132 | volume = off 133 | 134 | [Element Headphone Mic Boost] 135 | switch = off 136 | volume = off 137 | 138 | [Element Mic Boost] 139 | switch = off 140 | volume = off 141 | 142 | [Element Dock Mic Boost] 143 | switch = off 144 | volume = off 145 | 146 | [Element Front Mic Boost] 147 | switch = off 148 | volume = off 149 | 150 | [Element Rear Mic Boost] 151 | switch = off 152 | volume = off 153 | 154 | .include analog-input-mic.conf.common 155 | -------------------------------------------------------------------------------- /system76driver/data/iec958-stereo-output.conf: -------------------------------------------------------------------------------- 1 | # This file is part of PulseAudio. 2 | # 3 | # PulseAudio is free software; you can redistribute it and/or modify 4 | # it under the terms of the GNU Lesser General Public License as 5 | # published by the Free Software Foundation; either version 2.1 of the 6 | # License, or (at your option) any later version. 7 | # 8 | # PulseAudio is distributed in the hope that it will be useful, but 9 | # WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public License 14 | # along with PulseAudio; if not, see . 15 | 16 | 17 | [General] 18 | description = Headphones + Digital Output (S/PDIF) 19 | 20 | [Element IEC958] 21 | switch = mute 22 | -------------------------------------------------------------------------------- /system76driver/data/system76-switch-internal-speakers.conf: -------------------------------------------------------------------------------- 1 | # This file is part of PulseAudio. 2 | # 3 | # PulseAudio is free software; you can redistribute it and/or modify 4 | # it under the terms of the GNU Lesser General Public License as 5 | # published by the Free Software Foundation; either version 2.1 of the 6 | # License, or (at your option) any later version. 7 | # 8 | # PulseAudio is distributed in the hope that it will be useful, but 9 | # WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public License 14 | # along with PulseAudio; if not, see . 15 | 16 | ; This profile switches the left and right internal speaker outputs. 17 | ; See default.conf for explanations. 18 | 19 | [General] 20 | auto-profiles = yes 21 | 22 | [Mapping analog-mono] 23 | device-strings = hw:%f 24 | channel-map = mono 25 | paths-output = analog-output analog-output-lineout analog-output-speaker analog-output-headphones analog-output-headphones-2 analog-output-mono 26 | paths-input = analog-input-front-mic analog-input-rear-mic analog-input-internal-mic analog-input-dock-mic analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line analog-input-headset-mic 27 | priority = 2 28 | 29 | [Mapping speakers-analog-stereo] 30 | device-strings = front:%f 31 | channel-map = right,left 32 | description = Analog Stereo 33 | paths-output = analog-output-speaker 34 | priority = 100 35 | 36 | [Mapping analog-stereo] 37 | device-strings = front:%f 38 | channel-map = left,right 39 | paths-output = analog-output analog-output-lineout analog-output-headphones analog-output-headphones-2 40 | paths-input = analog-input-front-mic analog-input-rear-mic analog-input-internal-mic analog-input-dock-mic analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line analog-input-headphone-mic analog-input-headset-mic 41 | priority = 10 42 | 43 | [Mapping analog-surround-21] 44 | device-strings = surround21:%f 45 | channel-map = front-left,front-right,lfe 46 | paths-output = analog-output analog-output-lineout analog-output-speaker 47 | priority = 8 48 | direction = output 49 | 50 | [Mapping analog-surround-40] 51 | device-strings = surround40:%f 52 | channel-map = front-left,front-right,rear-left,rear-right 53 | paths-output = analog-output analog-output-lineout analog-output-speaker 54 | priority = 7 55 | direction = output 56 | 57 | [Mapping analog-surround-41] 58 | device-strings = surround41:%f 59 | channel-map = front-left,front-right,rear-left,rear-right,lfe 60 | paths-output = analog-output analog-output-lineout analog-output-speaker 61 | priority = 8 62 | direction = output 63 | 64 | [Mapping analog-surround-50] 65 | device-strings = surround50:%f 66 | channel-map = front-left,front-right,rear-left,rear-right,front-center 67 | paths-output = analog-output analog-output-lineout analog-output-speaker 68 | priority = 7 69 | direction = output 70 | 71 | [Mapping analog-surround-51] 72 | device-strings = surround51:%f 73 | channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe 74 | paths-output = analog-output analog-output-lineout analog-output-speaker 75 | priority = 8 76 | direction = output 77 | 78 | [Mapping analog-surround-71] 79 | device-strings = surround71:%f 80 | channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe,side-left,side-right 81 | description = Analog Surround 7.1 82 | paths-output = analog-output analog-output-lineout analog-output-speaker 83 | priority = 7 84 | direction = output 85 | 86 | [Mapping iec958-stereo] 87 | device-strings = iec958:%f 88 | channel-map = left,right 89 | paths-input = iec958-stereo-input 90 | paths-output = iec958-stereo-output 91 | priority = 5 92 | 93 | [Mapping iec958-ac3-surround-40] 94 | device-strings = a52:%f 95 | channel-map = front-left,front-right,rear-left,rear-right 96 | paths-output = iec958-stereo-output 97 | priority = 2 98 | direction = output 99 | 100 | [Mapping iec958-ac3-surround-51] 101 | device-strings = a52:%f 102 | channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe 103 | paths-output = iec958-stereo-output 104 | priority = 3 105 | direction = output 106 | 107 | [Mapping iec958-dts-surround-51] 108 | device-strings = dca:%f 109 | channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe 110 | paths-output = iec958-stereo-output 111 | priority = 3 112 | direction = output 113 | 114 | [Mapping hdmi-stereo] 115 | description = Digital Stereo (HDMI) 116 | device-strings = hdmi:%f 117 | paths-output = hdmi-output-0 118 | channel-map = left,right 119 | priority = 4 120 | direction = output 121 | 122 | [Mapping hdmi-surround] 123 | description = Digital Surround 5.1 (HDMI) 124 | device-strings = hdmi:%f 125 | paths-output = hdmi-output-0 126 | channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe 127 | priority = 3 128 | direction = output 129 | 130 | [Mapping hdmi-surround71] 131 | description = Digital Surround 7.1 (HDMI) 132 | device-strings = hdmi:%f 133 | paths-output = hdmi-output-0 134 | channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe,side-left,side-right 135 | priority = 3 136 | direction = output 137 | 138 | [Mapping hdmi-dts-surround] 139 | description = Digital Surround 5.1 (HDMI/DTS) 140 | device-strings = dcahdmi:%f 141 | paths-output = hdmi-output-0 142 | channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe 143 | priority = 1 144 | direction = output 145 | 146 | ; An example for defining multiple-sink profiles 147 | #[Profile output:analog-stereo+output:iec958-stereo+input:analog-stereo] 148 | #description = Foobar 149 | #output-mappings = analog-stereo iec958-stereo 150 | #input-mappings = analog-stereo 151 | -------------------------------------------------------------------------------- /system76driver/data/system76_logo_primary.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml -------------------------------------------------------------------------------- /system76driver/data/system76_logo_white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml -------------------------------------------------------------------------------- /system76driver/gtk.py: -------------------------------------------------------------------------------- 1 | # system76-driver: Universal driver for System76 computers 2 | # Copyright (C) 2005-2016 System76, Inc. 3 | # 4 | # This file is part of `system76-driver`. 5 | # 6 | # `system76-driver` is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # `system76-driver` is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along 17 | # with `system76-driver`; if not, write to the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 19 | 20 | """ 21 | Gtk UI. 22 | """ 23 | 24 | import distro 25 | import threading 26 | from gettext import gettext as _ 27 | 28 | import gi 29 | gi.require_version('Gtk', '3.0') 30 | from gi.repository import GLib, Gtk 31 | 32 | from . import __version__, get_datafile 33 | from .mockable import SubProcess 34 | from .actions import ActionRunner 35 | 36 | 37 | GLib.threads_init() 38 | 39 | 40 | class UI: 41 | def __init__(self, model, product, args): 42 | assert isinstance(model, str) 43 | assert product is None or isinstance(product, dict) 44 | assert isinstance(args.dry, bool) 45 | self.thread = None 46 | self.model = model 47 | self.product = product 48 | self.args = args 49 | self.builder = Gtk.Builder() 50 | self.builder.add_from_file(get_datafile('gtk3.glade')) 51 | self.window = self.builder.get_object('mainWindow') 52 | self.notify_icon = self.builder.get_object('notifyImage') 53 | self.notify_text = self.builder.get_object('notifyLabel') 54 | self.details = self.builder.get_object('detailsText') 55 | self.builder.get_object('sysModel').set_text(model) 56 | self.builder.get_object('ubuntuVersion').set_text( 57 | '{} {} ({})'.format(*distro.linux_distribution()) 58 | ) 59 | self.builder.get_object('driverVersion').set_text(__version__) 60 | 61 | self.builder.connect_signals({ 62 | 'onDeleteWindow': Gtk.main_quit, 63 | 'onCloseClicked': Gtk.main_quit, 64 | 'onInstallClicked': self.onInstallClicked, 65 | 'onRestoreClicked': self.onRestoreClicked, 66 | 'onCreateClicked': self.onCreateClicked, 67 | 'onAboutClicked': self.onAboutClicked, 68 | }) 69 | 70 | self.buttons = dict( 71 | (key, self.builder.get_object(key)) 72 | for key in ['driverInstall', 'driverRestore', 'driverCreate'] 73 | ) 74 | self.enabled = { 75 | 'driverInstall': False, 76 | 'driverRestore': False, 77 | 'driverCreate': False, 78 | } 79 | self.set_sensitive(False) 80 | 81 | if product: 82 | name = product['name'] 83 | else: 84 | name = _('Non System76 Product') 85 | self.set_notify('gtk-dialog-error', 86 | _('Not a System76 product, nothing to do!') 87 | ) 88 | self.builder.get_object('sysName').set_text(name) 89 | 90 | def prepare_action_runner(self): 91 | self.enabled['driverCreate'] = True 92 | self.action_runner = ActionRunner(self.product['drivers']) 93 | if not self.action_runner.actions: 94 | msg = _('All of the drivers for this system are provided by Ubuntu.') 95 | self.set_notify('gtk-ok', msg) 96 | self.details.set_text(msg) 97 | else: 98 | lines = [] 99 | for action in self.action_runner.actions: 100 | template = ('+ {}' if action.isneeded else '* {}') 101 | lines.append(template.format(action.description)) 102 | self.details.set_text('\n'.join(lines)) 103 | if self.action_runner.needed: 104 | self.enabled['driverInstall'] = True 105 | self.enabled['driverRestore'] = True 106 | else: 107 | msg = _('All drivers have been applied, nothing to do.') 108 | self.set_notify('gtk-ok', msg) 109 | self.set_sensitive(True) 110 | 111 | def set_sensitive(self, sensitive): 112 | for (key, button) in self.buttons.items(): 113 | button.set_sensitive(sensitive and self.enabled[key]) 114 | 115 | def set_notify(self, icon, text): 116 | self.notify_text.show() 117 | self.notify_icon.show() 118 | self.notify_text.set_text(text) 119 | self.notify_icon.set_from_stock(icon, 4) 120 | 121 | def run(self): 122 | self.window.show() 123 | if self.product: 124 | GLib.idle_add(self.prepare_action_runner) 125 | Gtk.main() 126 | 127 | def worker_thread(self): 128 | SubProcess.check_call(['pkexec', 'system76-driver-cli', '--model', self.model]) 129 | GLib.idle_add(self.on_worker_complete) 130 | 131 | def on_worker_complete(self): 132 | self.thread.join() 133 | self.thread = None 134 | self.set_notify('gtk-apply', 135 | 'Installation is complete! Please reboot for changes to take effect.' 136 | ) 137 | self.set_sensitive(True) 138 | 139 | def start_worker(self): 140 | if self.thread is None: 141 | self.set_sensitive(False) 142 | self.set_notify('gtk-execute', 143 | _('Now installing drivers. This may take a while...') 144 | ) 145 | self.thread = threading.Thread( 146 | target=self.worker_thread, 147 | args=(), 148 | daemon=True, 149 | ) 150 | self.thread.start() 151 | 152 | def onInstallClicked(self, button): 153 | print('onInstallClicked') 154 | self.start_worker() 155 | 156 | def onRestoreClicked(self, button): 157 | print('onRestoreClicked') 158 | self.start_worker() 159 | 160 | def create_worker(self): 161 | SubProcess.check_call(['pkexec', 'system76-driver-cli', '--logs', self.args.home]) 162 | GLib.idle_add(self.on_create_complete) 163 | 164 | def on_create_complete(self): 165 | self.thread.join() 166 | self.thread = None 167 | self.set_sensitive(True) 168 | self.set_notify('gtk-ok', 169 | _('A log file (system76-logs.tgz) was created in your home folder.\nPlease send it to support via www.system76.com/support') 170 | ) 171 | 172 | def onCreateClicked(self, button): 173 | if self.thread is None: 174 | self.set_sensitive(False) 175 | self.set_notify('gtk-execute', _('Creating logs...')) 176 | self.thread = threading.Thread( 177 | target=self.create_worker, 178 | daemon=True, 179 | ) 180 | self.thread.start() 181 | 182 | def onAboutClicked(self, button): 183 | aboutDialog = self.builder.get_object('aboutDialog') 184 | aboutDialog.set_version(__version__) 185 | aboutDialog.run() 186 | aboutDialog.hide() 187 | -------------------------------------------------------------------------------- /system76driver/mockable.py: -------------------------------------------------------------------------------- 1 | # system76-driver: Universal driver for System76 computers 2 | # Copyright (C) 2005-2016 System76, Inc. 3 | # 4 | # This file is part of `system76-driver`. 5 | # 6 | # `system76-driver` is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # `system76-driver` is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along 17 | # with `system76-driver`; if not, write to the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 19 | 20 | """ 21 | Mockable subprocess calls. 22 | """ 23 | 24 | import subprocess 25 | 26 | 27 | class SubProcess: 28 | mocking = False 29 | calls = [] 30 | outputs = [] 31 | 32 | @classmethod 33 | def reset(cls, mocking=False, outputs=None): 34 | assert isinstance(mocking, bool) 35 | cls.mocking = mocking 36 | cls.calls.clear() 37 | cls.outputs.clear() 38 | if outputs: 39 | assert mocking is True 40 | for value in outputs: 41 | assert isinstance(value, bytes) 42 | cls.outputs.append(value) 43 | 44 | @classmethod 45 | def check_call(cls, cmd, **kw): 46 | assert isinstance(cmd, list) 47 | if cls.mocking: 48 | cls.calls.append(('check_call', cmd, kw)) 49 | else: 50 | return subprocess.check_call(cmd, **kw) 51 | 52 | @classmethod 53 | def check_output(cls, cmd, **kw): 54 | assert isinstance(cmd, list) 55 | if cls.mocking: 56 | cls.calls.append(('check_output', cmd, kw)) 57 | return cls.outputs.pop(0) 58 | else: 59 | return subprocess.check_output(cmd, **kw) 60 | 61 | -------------------------------------------------------------------------------- /system76driver/model.py: -------------------------------------------------------------------------------- 1 | # system76-driver: Universal driver for System76 computers 2 | # Copyright (C) 2005-2016 System76, Inc. 3 | # 4 | # This file is part of `system76-driver`. 5 | # 6 | # `system76-driver` is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # `system76-driver` is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along 17 | # with `system76-driver`; if not, write to the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 19 | 20 | """ 21 | Determine model of System76 product. 22 | """ 23 | 24 | from . import read_dmi_id 25 | from .mockable import SubProcess 26 | 27 | 28 | KEYWORDS = ( 29 | 'system-uuid', 30 | 'baseboard-product-name', 31 | 'system-product-name', 32 | 'system-version', 33 | ) 34 | 35 | ALL_KEYWORDS = ( 36 | 'baseboard-asset-tag', 37 | 'baseboard-manufacturer', 38 | 'baseboard-product-name', 39 | 'baseboard-serial-number', 40 | 'baseboard-version', 41 | 'bios-release-date', 42 | 'bios-vendor', 43 | 'bios-version', 44 | 'chassis-asset-tag', 45 | 'chassis-manufacturer', 46 | 'chassis-serial-number', 47 | 'chassis-type', 48 | 'chassis-version', 49 | 'processor-family', 50 | 'processor-frequency', 51 | 'processor-manufacturer', 52 | 'processor-version', 53 | 'system-manufacturer', 54 | 'system-product-name', 55 | 'system-serial-number', 56 | 'system-uuid', 57 | 'system-version', 58 | ) 59 | 60 | TABLES = { 61 | 'system-uuid': { 62 | '00000000-0000-0000-0000-000000000001': 'koap1', 63 | }, 64 | 'baseboard-product-name': { 65 | 'Z35FM': 'daru1', 66 | 'Z35F': 'daru1', 67 | 'MS-1221': 'daru2', 68 | 'IFL91': 'panv3', 69 | 'IFT01': 'gazv5', 70 | 'IFT00': 'gazp5', 71 | 'A8N8L': 'sabv1', 72 | 'M2N8L': 'sabv2', 73 | 'P5K-VM': 'sabv3', 74 | 'IFL90': 'serp3', 75 | 'JFL92': 'serp4', 76 | 'MS-7250': 'wilp1', 77 | 'A8V-MQ': 'ratv1', 78 | 'P5VD2-MX': 'ratv2', 79 | 'P5VD2-VM': 'ratv3', 80 | 'D945GCPE': 'ratv4', 81 | 'P5GC-MX/1333': 'ratv5', 82 | 'MPAD-MSAE Customer Reference Boards': 'gazv2', 83 | 'K8N-DL': 'wilp2', 84 | 'KFN5-D SLI': 'wilp3', 85 | 'DP35DP': 'wilp5', 86 | }, 87 | 'system-product-name': { 88 | 'MS-1012': 'gazv1', 89 | 'Z62FP': 'gazv3', 90 | 'Z62FM': 'gazv4', 91 | 'Z62F': 'gazp1', 92 | 'Z62J': 'gazp2', 93 | 'Z62JM': 'gazp3', 94 | 'Z62JP': 'gazp3', 95 | 'U-100': 'meec1', 96 | 'Z96F': 'panv2', 97 | 'Centoris V661': 'panv2', 98 | 'Z96FM': 'panv2', 99 | 'HEL80I': 'serp1', 100 | 'HEL8X': 'serp1', 101 | 'HEL80C': 'serp2', 102 | 'UW1': 'star1', 103 | 'star1': 'star1', 104 | 'E10IS': 'star2', 105 | 'E10IS2': 'star2', 106 | 'Star2': 'star2', 107 | 'A7V': 'bonp1', 108 | 'M570TU': 'bonp2', 109 | 'M720T/M730T': 'daru3', 110 | 'M740T/M760T': 'panp4i', 111 | 'M740TU/M760TU': 'panp4n', 112 | 'M860TU': 'serp5', 113 | }, 114 | 'system-version': { 115 | 'addw1': 'addw1', 116 | 'addw2': 'addw2', 117 | 'addw3': 'addw3', 118 | 'addw4': 'addw4', 119 | 'bonp2': 'bonp2', 120 | 'bonp3': 'bonp3', 121 | 'bonp4': 'bonp4', 122 | 'bonp5': 'bonp5', 123 | 'bonx6': 'bonx6', 124 | 'bonx7': 'bonx7', 125 | 'bonx8': 'bonx8', 126 | 'bonw9': 'bonw9', 127 | 'bonw10': 'bonw10', 128 | 'bonw11': 'bonw11', 129 | 'bonw12': 'bonw12', 130 | 'bonw13': 'bonw13', 131 | 'bonw14': 'bonw14', 132 | 'bonw15': 'bonw15', 133 | 'bonw15-b': 'bonw15-b', 134 | 'bonw16': 'bonw16', 135 | 'darp5': 'darp5', 136 | 'darp6': 'darp6', 137 | 'darp7': 'darp7', 138 | 'darp8': 'darp8', 139 | 'darp9': 'darp9', 140 | 'darp10': 'darp10', 141 | 'darp10-b': 'darp10-b', 142 | 'darp11': 'darp11', 143 | 'darp11-b': 'darp11-b', 144 | 'galu1': 'galu1', 145 | 'galp2': 'galp2', 146 | 'galp3': 'galp3', 147 | 'galp3-b': 'galp3-b', 148 | 'galp3-c': 'galp3-c', 149 | 'galp4': 'galp4', 150 | 'galp5': 'galp5', 151 | 'galp6': 'galp6', 152 | 'galp7': 'galp7', 153 | 'gazu1': 'gazu1', 154 | 'gazp6': 'gazp6', 155 | 'gazp7': 'gazp7', 156 | 'gazp8': 'gazp8', 157 | 'gazp9': 'gazp9', 158 | 'gazp9b': 'gazp9b', 159 | 'gazp9c': 'gazp9c', 160 | 'gaze10': 'gaze10', 161 | 'gaze11': 'gaze11', 162 | 'gaze12': 'gaze12', 163 | 'gaze13': 'gaze13', 164 | 'gaze14': 'gaze14', 165 | 'gaze15': 'gaze15', 166 | 'gaze16-3050': 'gaze16-3050', 167 | 'gaze16-3060': 'gaze16-3060', 168 | 'gaze16-3060-b': 'gaze16-3060-b', 169 | 'gaze17-3050': 'gaze17-3050', 170 | 'gaze17-3060-b': 'gaze17-3060-b', 171 | 'gaze18': 'gaze18', 172 | 'daru3': 'daru3', 173 | 'daru4': 'daru4', 174 | 'kudp1': 'kudp1', 175 | 'kudp1b': 'kudp1b', 176 | 'kudp1c': 'kudp1c', 177 | 'kudu2': 'kudu2', 178 | 'kudu3': 'kudu3', 179 | 'kudu4': 'kudu4', 180 | 'kudu5': 'kudu5', 181 | 'kudu6': 'kudu6', 182 | 'panp4n': 'panp4n', 183 | 'panp5': 'panp5', 184 | 'panp6': 'panp6', 185 | 'panp7': 'panp7', 186 | 'panp8': 'panp8', 187 | 'panp9': 'panp9', 188 | 'pang10': 'pang10', 189 | 'pang11': 'pang11', 190 | 'pang12': 'pang12', 191 | 'pang13': 'pang13', 192 | 'pang14': 'pang14', 193 | 'pang15': 'pang15', 194 | 'lemu1': 'lemu1', 195 | 'lemu2': 'lemu2', 196 | 'lemu3': 'lemu3', 197 | 'lemu4': 'lemu4', 198 | 'lemu5': 'lemu5', 199 | 'lemu6': 'lemu6', 200 | 'lemu7': 'lemu7', 201 | 'lemu8': 'lemu8', 202 | 'lemp9': 'lemp9', 203 | 'lemp10': 'lemp10', 204 | 'lemp11': 'lemp11', 205 | 'lemp12': 'lemp12', 206 | 'lemp13': 'lemp13', 207 | 'lemp13-b': 'lemp13-b', 208 | 'leo1': 'leo1', 209 | 'leox2': 'leox2', 210 | 'leox3': 'leox3', 211 | 'leox4': 'leox4', 212 | 'leox5': 'leox5', 213 | 'leow6': 'leow6', 214 | 'leow7': 'leow7', 215 | 'leow8': 'leow8', 216 | 'leow9': 'leow9', 217 | 'leow9-b': 'leow9-b', 218 | 'leow9-w': 'leow9-w', 219 | 'meer1': 'meer1', 220 | 'meer2': 'meer2', 221 | 'meer3': 'meer3', 222 | 'meer4': 'meer4', 223 | 'meer5': 'meer5', 224 | 'meer6': 'meer6', 225 | 'meer7': 'meer7', 226 | 'meer8': 'meer8', 227 | 'meer8-b': 'meer8-b', 228 | 'meer9': 'meer9', 229 | 'ment1': 'ment1', 230 | 'ment2': 'ment2', 231 | 'ment3': 'ment3', 232 | 'ment5': 'ment5', 233 | 'orxp1': 'orxp1', 234 | 'oryp2': 'oryp2', 235 | 'oryp2-ess': 'oryp2-ess', 236 | 'oryp3': 'oryp3', 237 | 'oryp3-ess': 'oryp3-ess', 238 | 'oryp3-b': 'oryp3-b', 239 | 'oryp4': 'oryp4', 240 | 'oryp4-b': 'oryp4-b', 241 | 'oryp5': 'oryp5', 242 | 'oryp6': 'oryp6', 243 | 'oryp7': 'oryp7', 244 | 'oryp8': 'oryp8', 245 | 'oryp9': 'oryp9', 246 | 'oryp10': 'oryp10', 247 | 'oryp11': 'oryp11', 248 | 'oryp12': 'oryp12', 249 | 'ratv6': 'ratv6', 250 | 'ratu1': 'ratu1', 251 | 'ratu2': 'ratu2', 252 | 'ratp1': 'ratp1', 253 | 'ratp2': 'ratp2', 254 | 'ratp3': 'ratp3', 255 | 'ratp4': 'ratp4', 256 | 'ratp5': 'ratp5', 257 | 'star3': 'star3', 258 | 'star4': 'star4', 259 | 'star5': 'star5', 260 | 'thelio-astra-a1': 'thelio-astra-a1', 261 | 'thelio-astra-a1.1': 'thelio-astra-a1.1', 262 | 'thelio-b1': 'thelio-b1', 263 | 'thelio-b2': 'thelio-b2', 264 | 'thelio-b3': 'thelio-b3', 265 | 'thelio-b4': 'thelio-b4', 266 | 'thelio-b5': 'thelio-b5', 267 | 'thelio-r1': 'thelio-r1', 268 | 'thelio-r2': 'thelio-r2', 269 | 'thelio-r3': 'thelio-r3', 270 | 'thelio-r4': 'thelio-r4', 271 | 'thelio-r5': 'thelio-r5', 272 | 'thelio-major-b1': 'thelio-major-b1', 273 | 'thelio-major-b1.1': 'thelio-major-b1.1', 274 | 'thelio-major-b2': 'thelio-major-b2', 275 | 'thelio-major-b3': 'thelio-major-b3', 276 | 'thelio-major-b4': 'thelio-major-b4', 277 | 'thelio-major-r1': 'thelio-major-r1', 278 | 'thelio-major-r2': 'thelio-major-r2', 279 | 'thelio-major-r2.1': 'thelio-major-r2.1', 280 | 'thelio-major-r3': 'thelio-major-r3', 281 | 'thelio-major-r4': 'thelio-major-r4', 282 | 'thelio-major-r5': 'thelio-major-r5', 283 | 'thelio-massive-b1': 'thelio-massive-b1', 284 | 'thelio-mega-b1': 'thelio-mega-b1', 285 | 'thelio-mega-r1': 'thelio-mega-r1', 286 | 'thelio-mega-r1.1': 'thelio-mega-r1.1', 287 | 'thelio-mega-r2': 'thelio-mega-r2', 288 | 'thelio-mega-r3': 'thelio-mega-r3', 289 | 'thelio-mega-r4': 'thelio-mega-r4', 290 | 'thelio-mira-b1': 'thelio-mira-b1', 291 | 'thelio-mira-b2': 'thelio-mira-b2', 292 | 'thelio-mira-b3': 'thelio-mira-b3', 293 | 'thelio-mira-b4': 'thelio-mira-b4', 294 | 'thelio-mira-b4.1': 'thelio-mira-b4.1', 295 | 'thelio-mira-r1': 'thelio-mira-r1', 296 | 'thelio-mira-r2': 'thelio-mira-r2', 297 | 'thelio-mira-r3': 'thelio-mira-r3', 298 | 'thelio-mira-r4': 'thelio-mira-r4', 299 | 'thelio-spark-b1': 'thelio-spark-b1', 300 | 'thelio-spark-r1': 'thelio-spark-r1', 301 | 'thelio-spark-r2': 'thelio-spark-r2', 302 | 'wilb1': 'wilb1', 303 | 'wilb2': 'wilb2', 304 | 'wilp6': 'wilp6', 305 | 'wilp7': 'wilp7', 306 | 'wilp8': 'wilp8', 307 | 'wilp9': 'wilp9', 308 | 'wilp10': 'wilp10', 309 | 'wilp11': 'wilp11', 310 | 'wilp12': 'wilp12', 311 | 'wilp13': 'wilp13', 312 | 'wilp14': 'wilp14', 313 | 'serp5': 'serp5', 314 | 'serp6': 'serp6', 315 | 'serp7': 'serp7', 316 | 'serw8-15': 'serw8-15', 317 | 'serw8-17': 'serw8-17', 318 | 'serw8-17g': 'serw8-17g', 319 | 'serw9': 'serw9', 320 | 'serw10': 'serw10', 321 | 'serw11': 'serw11', 322 | 'serw11-b': 'serw11-b', 323 | 'serw12': 'serw12', 324 | 'serw13': 'serw13', 325 | 'serw14': 'serw14', 326 | 'silw1': 'silw1', 327 | 'silw2': 'silw2', 328 | 'silw3': 'silw3', 329 | 'sabc1': 'sabc1', 330 | 'sabc2': 'sabc2', 331 | 'sabc3': 'sabc3', 332 | 'sabl4': 'sabl4', 333 | 'sabl5': 'sabl5', 334 | 'sabl6': 'sabl6', 335 | 'sabt1': 'sabt1', 336 | 'sabt2': 'sabt2', 337 | 'sabt3': 'sabt3', 338 | }, 339 | } 340 | 341 | 342 | def dmidecode(keyword): 343 | #TODO: will fail if used by GUI when not running as root 344 | cmd = ['dmidecode', '-s', keyword] 345 | return SubProcess.check_output(cmd).decode('utf-8').strip() 346 | 347 | 348 | def get_dmi_info(): 349 | return dict( 350 | (keyword, dmidecode(keyword)) for keyword in KEYWORDS 351 | ) 352 | 353 | 354 | def get_all_dmi_info(): 355 | return dict( 356 | (keyword, dmidecode(keyword)) for keyword in ALL_KEYWORDS 357 | ) 358 | 359 | 360 | def determine_model(info=None): 361 | """ 362 | Determine the System76 model number. 363 | """ 364 | if info is None: 365 | info = get_dmi_info() 366 | for keyword in KEYWORDS: 367 | value = info[keyword] 368 | table = TABLES[keyword] 369 | if value in table: 370 | return table[value] 371 | return 'nonsystem76' 372 | 373 | 374 | def determine_model_new(sysdir='/sys', info=None): 375 | model = read_dmi_id('product_version', sysdir) 376 | if model in TABLES['system-version']: 377 | return model 378 | return determine_model(info) 379 | -------------------------------------------------------------------------------- /system76driver/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # system76-driver: Universal driver for System76 computers 2 | # Copyright (C) 2005-2016 System76, Inc. 3 | # 4 | # This file is part of `system76-driver`. 5 | # 6 | # `system76-driver` is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # `system76-driver` is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along 17 | # with `system76-driver`; if not, write to the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 19 | 20 | """ 21 | Unit test for the `system76driver` package. 22 | """ 23 | 24 | from unittest import TestCase 25 | import os 26 | from os import path 27 | from subprocess import check_call 28 | 29 | from .helpers import TempDir 30 | from system76driver.products import PRODUCTS 31 | import system76driver 32 | 33 | 34 | TREE = path.dirname(path.dirname(path.dirname(path.abspath(__file__)))) 35 | IN_TREE = path.isfile(path.join(TREE, 'setup.py')) 36 | 37 | 38 | class TestConstants(TestCase): 39 | def test_version(self): 40 | self.assertIsInstance(system76driver.__version__, str) 41 | (year, month, rev) = system76driver.__version__.split('.') 42 | self.assertEqual(year, str(int(year))) 43 | self.assertGreaterEqual(int(year), 13) 44 | self.assertIn(month, ['04', '10']) 45 | self.assertEqual(rev, str(int(rev))) 46 | self.assertGreaterEqual(int(rev), 0) 47 | 48 | def test_VALID_SYS_VENDOR(self): 49 | self.assertIsInstance(system76driver.VALID_SYS_VENDOR, tuple) 50 | self.assertIn('System76, Inc.', system76driver.VALID_SYS_VENDOR) 51 | for value in system76driver.VALID_SYS_VENDOR: 52 | self.assertIsInstance(value, str) 53 | 54 | 55 | class TestScripts(TestCase): 56 | def setUp(self): 57 | if not IN_TREE: 58 | self.skipTest('not running tests in-tree') 59 | 60 | def check_script(self, name): 61 | script = path.join(TREE, name) 62 | self.assertTrue(path.isfile(script)) 63 | # All the scripts need to be run as root, but you should always be able 64 | # to do a -h as a normal user: 65 | check_call([script, '-h']) 66 | 67 | def test_system76_driver(self): 68 | """ 69 | Test the `system76-driver` Gtk UI script. 70 | """ 71 | self.check_script('system76-driver') 72 | 73 | def test_system76_driver_cli(self): 74 | """ 75 | Test the `system76-driver-cli` CLI script. 76 | """ 77 | self.check_script('system76-driver-cli') 78 | 79 | def test_system76_daemon(self): 80 | """ 81 | Test the `system76-daemon` CLI script. 82 | """ 83 | self.check_script('system76-daemon') 84 | 85 | 86 | class TestDataFiles(TestCase): 87 | def iter_data_files(self, callback): 88 | for name in sorted(os.listdir(system76driver.datadir)): 89 | fullname = path.join(system76driver.datadir, name) 90 | self.assertTrue(path.isfile(fullname)) 91 | if callback(name): 92 | yield (name, fullname) 93 | 94 | def test_icc(self): 95 | for (name, fullname) in self.iter_data_files(lambda n: n.endswith('.icc')): 96 | self.assertTrue(name.endswith('.icc')) 97 | (prefix, model, *rest) = name.split('-') 98 | self.assertEqual(prefix, 'system76') 99 | self.assertIn(model, PRODUCTS) 100 | 101 | 102 | class TestFunctions(TestCase): 103 | def test_read_dmi_id(self): 104 | tmp = TempDir() 105 | KEYS = ('sys_vendor', 'product_version') 106 | VALS = ('System76, Inc.', 'kudp1') 107 | 108 | # Bad dmi/id key: 109 | bad_keys = tuple(k.upper() for k in KEYS) + ('product_serial', 'product_name') 110 | for bad in bad_keys: 111 | with self.assertRaises(ValueError) as cm: 112 | system76driver.read_dmi_id(bad, sysdir=tmp.dir) 113 | self.assertEqual(str(cm.exception), 114 | 'bad dmi/id key: {!r}'.format(bad) 115 | ) 116 | 117 | # class/dmi/id dir missing: 118 | for key in KEYS: 119 | self.assertIsNone( 120 | system76driver.read_dmi_id(key, sysdir=tmp.dir) 121 | ) 122 | self.assertEqual(tmp.listdir(), []) 123 | 124 | # sys_vendor, product_version files misssing: 125 | tmp.makedirs('class', 'dmi', 'id') 126 | for key in KEYS: 127 | self.assertIsNone( 128 | system76driver.read_dmi_id(key, sysdir=tmp.dir) 129 | ) 130 | self.assertEqual(tmp.listdir(), ['class']) 131 | self.assertEqual(tmp.listdir('class'), ['dmi']) 132 | self.assertEqual(tmp.listdir('class', 'dmi'), ['id']) 133 | self.assertEqual(tmp.listdir('class', 'dmi', 'id'), []) 134 | 135 | # sys_vendor, product_version files exist: 136 | for (key, val) in zip(KEYS, VALS): 137 | tmp.write(val.encode() + b'\n', 'class', 'dmi', 'id', key) 138 | self.assertEqual( 139 | system76driver.read_dmi_id(key, sysdir=tmp.dir), 140 | val 141 | ) 142 | self.assertEqual(tmp.listdir(), ['class']) 143 | self.assertEqual(tmp.listdir('class'), ['dmi']) 144 | self.assertEqual(tmp.listdir('class', 'dmi'), ['id']) 145 | self.assertEqual(tmp.listdir('class', 'dmi', 'id'), sorted(KEYS)) 146 | 147 | # sys_vendor, product_version do not contain valid UTF-8: 148 | tmp = TempDir() 149 | tmp.makedirs('class', 'dmi', 'id') 150 | for (key, val) in zip(KEYS, VALS): 151 | badval = b'\xff' + val.encode() + b'\n' 152 | with self.assertRaises(UnicodeDecodeError): 153 | badval.decode() 154 | tmp.write(badval, 'class', 'dmi', 'id', key) 155 | self.assertIsNone(system76driver.read_dmi_id(key, sysdir=tmp.dir)) 156 | self.assertEqual(tmp.listdir(), ['class']) 157 | self.assertEqual(tmp.listdir('class'), ['dmi']) 158 | self.assertEqual(tmp.listdir('class', 'dmi'), ['id']) 159 | self.assertEqual(tmp.listdir('class', 'dmi', 'id'), sorted(KEYS)) 160 | 161 | # Non-mocked test, as this can still pass in the build environment: 162 | for key in KEYS: 163 | val = system76driver.read_dmi_id(key) 164 | self.assertIsInstance(val, (type(None), str)) 165 | if isinstance(val, str): 166 | self.assertEqual(val.strip(), val) 167 | 168 | def test_get_sys_vendor(self): 169 | get_sys_vendor = system76driver.get_sys_vendor 170 | tmp = TempDir() 171 | 172 | # class/dmi/id/ dir missing: 173 | self.assertIsNone(get_sys_vendor(sysdir=tmp.dir)) 174 | self.assertEqual(tmp.listdir(), []) 175 | 176 | # sys_vendor file misssing: 177 | tmp.makedirs('class', 'dmi', 'id') 178 | self.assertIsNone(get_sys_vendor(sysdir=tmp.dir)) 179 | self.assertEqual(tmp.listdir(), ['class']) 180 | self.assertEqual(tmp.listdir('class'), ['dmi']) 181 | self.assertEqual(tmp.listdir('class', 'dmi'), ['id']) 182 | self.assertEqual(tmp.listdir('class', 'dmi', 'id'), []) 183 | 184 | # sys_vendor file exists, contains good value: 185 | for value in system76driver.VALID_SYS_VENDOR: 186 | tmp = TempDir() 187 | tmp.makedirs('class', 'dmi', 'id') 188 | value_b = value.encode() + b'\n' 189 | tmp.write(value_b, 'class', 'dmi', 'id', 'sys_vendor') 190 | self.assertEqual(get_sys_vendor(sysdir=tmp.dir), value) 191 | self.assertEqual(tmp.listdir(), ['class']) 192 | self.assertEqual(tmp.listdir('class'), ['dmi']) 193 | self.assertEqual(tmp.listdir('class', 'dmi'), ['id']) 194 | self.assertEqual(tmp.listdir('class', 'dmi', 'id'), ['sys_vendor']) 195 | 196 | # sys_vendor file exists, contains bad value: 197 | for value in system76driver.VALID_SYS_VENDOR: 198 | tmp = TempDir() 199 | tmp.makedirs('class', 'dmi', 'id') 200 | bad_value_b = value.upper().encode() + b'\n' 201 | tmp.write(bad_value_b, 'class', 'dmi', 'id', 'sys_vendor') 202 | self.assertIsNone(get_sys_vendor(sysdir=tmp.dir)) 203 | self.assertEqual(tmp.listdir(), ['class']) 204 | self.assertEqual(tmp.listdir('class'), ['dmi']) 205 | self.assertEqual(tmp.listdir('class', 'dmi'), ['id']) 206 | self.assertEqual(tmp.listdir('class', 'dmi', 'id'), ['sys_vendor']) 207 | 208 | # Non-mocked test, as this can still pass in the build environment: 209 | value = get_sys_vendor() 210 | self.assertIsInstance(value, (type(None), str)) 211 | if isinstance(value, str): 212 | self.assertEqual(value.strip(), value) 213 | 214 | def test_get_product_version(self): 215 | get_product_version = system76driver.get_product_version 216 | tmp = TempDir() 217 | DPARTS = ('class', 'dmi', 'id') 218 | SYS_PARTS = DPARTS + ('sys_vendor',) 219 | VER_PARTS = DPARTS + ('product_version',) 220 | 221 | # class/dmi/id/ dir missing: 222 | self.assertIsNone(get_product_version(sysdir=tmp.dir)) 223 | self.assertEqual(tmp.listdir(), []) 224 | 225 | # sys_vendor and product_version files misssing: 226 | tmp.makedirs(*DPARTS) 227 | self.assertIsNone(get_product_version(sysdir=tmp.dir)) 228 | self.assertEqual(tmp.listdir(), ['class']) 229 | self.assertEqual(tmp.listdir('class'), ['dmi']) 230 | self.assertEqual(tmp.listdir('class', 'dmi'), ['id']) 231 | self.assertEqual(tmp.listdir('class', 'dmi', 'id'), []) 232 | 233 | # product_version file exists, but sys_vendor is missing: 234 | tmp.write(b'kudu2\n', *VER_PARTS) 235 | self.assertIsNone(get_product_version(sysdir=tmp.dir)) 236 | 237 | # sys_vendor exists but contains a bad value: 238 | tmp.write(b'Nope\n', *SYS_PARTS) 239 | self.assertIsNone(get_product_version(sysdir=tmp.dir)) 240 | 241 | # sys_vendor file exists and contains a good value: 242 | for value in system76driver.VALID_SYS_VENDOR: 243 | tmp.remove(*SYS_PARTS) 244 | value_b = value.encode() + b'\n' 245 | tmp.write(value_b, *SYS_PARTS) 246 | self.assertEqual(get_product_version(sysdir=tmp.dir), 'kudu2') 247 | 248 | # Non-mocked test, as this can still pass in the build environment: 249 | value = get_product_version() 250 | self.assertIsInstance(value, (type(None), str)) 251 | if isinstance(value, str): 252 | self.assertEqual(value.strip(), value) 253 | 254 | -------------------------------------------------------------------------------- /system76driver/tests/helpers.py: -------------------------------------------------------------------------------- 1 | # system76-driver: Universal driver for System76 computers 2 | # Copyright (C) 2005-2016 System76, Inc. 3 | # 4 | # This file is part of `system76-driver`. 5 | # 6 | # `system76-driver` is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # `system76-driver` is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along 17 | # with `system76-driver`; if not, write to the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 19 | 20 | """ 21 | Base classes and helpers for unit tests. 22 | """ 23 | 24 | import os 25 | from os import path 26 | import tempfile 27 | import shutil 28 | 29 | 30 | class TempDir: 31 | def __init__(self, prefix='unittest.'): 32 | self.dir = tempfile.mkdtemp(prefix=prefix) 33 | 34 | def __del__(self): 35 | shutil.rmtree(self.dir) 36 | 37 | def join(self, *parts): 38 | return path.join(self.dir, *parts) 39 | 40 | def listdir(self, *parts): 41 | return sorted(os.listdir(self.join(*parts))) 42 | 43 | def mkdir(self, *parts): 44 | dirname = self.join(*parts) 45 | os.mkdir(dirname) 46 | return dirname 47 | 48 | def makedirs(self, *parts): 49 | dirname = self.join(*parts) 50 | os.makedirs(dirname) 51 | return dirname 52 | 53 | def touch(self, *parts): 54 | filename = self.join(*parts) 55 | open(filename, 'xb').close() 56 | return filename 57 | 58 | def write(self, content, *parts): 59 | filename = self.join(*parts) 60 | open(filename, 'xb').write(content) 61 | return filename 62 | 63 | def remove(self, *parts): 64 | os.remove(self.join(*parts)) 65 | 66 | -------------------------------------------------------------------------------- /system76driver/tests/run.py: -------------------------------------------------------------------------------- 1 | # system76-driver: Universal driver for System76 computers 2 | # Copyright (C) 2005-2016 System76, Inc. 3 | # 4 | # This file is part of `system76-driver`. 5 | # 6 | # `system76-driver` is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # `system76-driver` is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along 17 | # with `system76-driver`; if not, write to the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 19 | 20 | """ 21 | Run the `system76driver` unit tests. 22 | """ 23 | 24 | import sys 25 | import os 26 | from os import path 27 | import stat 28 | from unittest import TestLoader, TextTestRunner 29 | from doctest import DocTestSuite 30 | 31 | import system76driver 32 | 33 | 34 | packagedir = path.dirname(path.abspath(system76driver.__file__)) 35 | 36 | 37 | def pynames_iter(pkdir=packagedir, pkname=None): 38 | """ 39 | Recursively yield dotted names for *.py files in directory *pydir*. 40 | """ 41 | if not path.isfile(path.join(pkdir, '__init__.py')): 42 | return 43 | if pkname is None: 44 | pkname = path.basename(pkdir) 45 | yield pkname 46 | dirs = [] 47 | for name in sorted(os.listdir(pkdir)): 48 | if name in ('__init__.py', '__pycache__'): 49 | continue 50 | if name.startswith('.') or name.endswith('~'): 51 | continue 52 | fullname = path.join(pkdir, name) 53 | st = os.lstat(fullname) 54 | if stat.S_ISREG(st.st_mode) and name.endswith('.py'): 55 | parts = name.split('.') 56 | if len(parts) == 2: 57 | yield '.'.join([pkname, parts[0]]) 58 | elif stat.S_ISDIR(st.st_mode): 59 | dirs.append((fullname, name)) 60 | for (fullname, name) in dirs: 61 | subpkg = '.'.join([pkname, name]) 62 | for n in pynames_iter(fullname, subpkg): 63 | yield n 64 | 65 | 66 | def run_tests(skip_gtk=False): 67 | pynames = tuple(pynames_iter()) 68 | if skip_gtk: 69 | pynames = tuple(filter(lambda name: 'gtk' not in name, pynames)) 70 | 71 | # Add unit-tests: 72 | loader = TestLoader() 73 | suite = loader.loadTestsFromNames(pynames) 74 | 75 | # Add doc-tests: 76 | for name in pynames: 77 | suite.addTest(DocTestSuite(name)) 78 | 79 | # Run the tests: 80 | runner = TextTestRunner(verbosity=2) 81 | result = runner.run(suite) 82 | print( 83 | 'system76driver: {!r}'.format(path.abspath(system76driver.__file__)), 84 | file=sys.stderr 85 | ) 86 | print('-' * 70, file=sys.stderr) 87 | return result.wasSuccessful() 88 | 89 | 90 | if __name__ == '__main__': 91 | import argparse 92 | parser = argparse.ArgumentParser() 93 | parser.add_argument('--skip-gtk', action='store_true', default=False, 94 | help='Skip GTK related tests', 95 | ) 96 | args = parser.parse_args() 97 | if not run_tests(args.skip_gtk): 98 | raise SystemExit('2') 99 | 100 | -------------------------------------------------------------------------------- /system76driver/tests/test_gtk.py: -------------------------------------------------------------------------------- 1 | # system76-driver: Universal driver for System76 computers 2 | # Copyright (C) 2005-2016 System76, Inc. 3 | # 4 | # This file is part of `system76-driver`. 5 | # 6 | # `system76-driver` is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # `system76-driver` is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along 17 | # with `system76-driver`; if not, write to the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 19 | 20 | """ 21 | Unit tests for `system76driver.gtk` module. 22 | """ 23 | 24 | from unittest import TestCase 25 | import distro 26 | from collections import namedtuple 27 | 28 | from gi.repository import Gtk 29 | 30 | import system76driver 31 | from system76driver.actions import random_id 32 | from system76driver import gtk 33 | 34 | 35 | DummyArgs = namedtuple('DummyArgs', 'home dry') 36 | 37 | 38 | class TestUI(TestCase): 39 | def test_init(self): 40 | args = DummyArgs('/home/oem', False) 41 | product = { 42 | 'name': 'Gazelle Professional', 43 | 'drivers': [], 44 | 'prefs': [], 45 | } 46 | ui = gtk.UI('gazp9', product, args) 47 | self.assertIsNone(ui.thread) 48 | self.assertEqual(ui.model, 'gazp9') 49 | self.assertIs(ui.product, product) 50 | self.assertIs(ui.args, args) 51 | self.assertIsInstance(ui.builder, Gtk.Builder) 52 | self.assertIsInstance(ui.window, Gtk.Window) 53 | self.assertEqual( 54 | ui.builder.get_object('sysName').get_text(), 55 | 'Gazelle Professional' 56 | ) 57 | self.assertEqual( 58 | ui.builder.get_object('sysModel').get_text(), 59 | 'gazp9' 60 | ) 61 | self.assertEqual( 62 | ui.builder.get_object('ubuntuVersion').get_text(), 63 | '{} {} ({})'.format(*distro.linux_distribution()) 64 | ) 65 | self.assertEqual( 66 | ui.builder.get_object('driverVersion').get_text(), 67 | system76driver.__version__ 68 | ) 69 | 70 | model = random_id() 71 | name = random_id() 72 | product = {'name': name} 73 | ui = gtk.UI(model, product, args) 74 | self.assertIsNone(ui.thread) 75 | self.assertEqual(ui.model, model) 76 | self.assertIs(ui.product, product) 77 | self.assertEqual(ui.product, {'name': name}) 78 | self.assertIs(ui.args, args) 79 | self.assertIsInstance(ui.builder, Gtk.Builder) 80 | self.assertIsInstance(ui.window, Gtk.Window) 81 | self.assertEqual(ui.builder.get_object('sysName').get_text(), name) 82 | self.assertEqual(ui.builder.get_object('sysModel').get_text(), model) 83 | self.assertEqual( 84 | ui.builder.get_object('ubuntuVersion').get_text(), 85 | '{} {} ({})'.format(*distro.linux_distribution()) 86 | ) 87 | self.assertEqual( 88 | ui.builder.get_object('driverVersion').get_text(), 89 | system76driver.__version__ 90 | ) 91 | 92 | def test_set_notify(self): 93 | args = DummyArgs('/home/oem', False) 94 | ui = gtk.UI('gazp9', {'name': 'Gazelle Professional'}, args) 95 | text = random_id() 96 | self.assertIsNone(ui.set_notify('gtk-execute', text)) 97 | self.assertEqual(ui.notify_text.get_text(), text) 98 | -------------------------------------------------------------------------------- /system76driver/tests/test_mockable.py: -------------------------------------------------------------------------------- 1 | # system76-driver: Universal driver for System76 computers 2 | # Copyright (C) 2005-2016 System76, Inc. 3 | # 4 | # This file is part of `system76-driver`. 5 | # 6 | # `system76-driver` is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # `system76-driver` is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along 17 | # with `system76-driver`; if not, write to the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 19 | 20 | """ 21 | Unit tests for `system76driver.mockable` module. 22 | """ 23 | 24 | from unittest import TestCase 25 | import subprocess 26 | 27 | from system76driver.mockable import SubProcess 28 | 29 | 30 | class TestSubProcess(TestCase): 31 | def test_reset(self): 32 | calls = SubProcess.calls 33 | outputs = SubProcess.outputs 34 | 35 | SubProcess.calls.extend(['foo', 'bar']) 36 | SubProcess.outputs.append(b'baz') 37 | self.assertIsNone(SubProcess.reset()) 38 | self.assertIs(SubProcess.mocking, False) 39 | self.assertEqual(SubProcess.calls, []) 40 | self.assertIs(SubProcess.calls, calls) 41 | self.assertEqual(SubProcess.outputs, []) 42 | self.assertIs(SubProcess.outputs, outputs) 43 | 44 | SubProcess.calls.extend(['foo', 'bar']) 45 | SubProcess.outputs.append(b'baz') 46 | self.assertIsNone(SubProcess.reset(True)) 47 | self.assertIs(SubProcess.mocking, True) 48 | self.assertEqual(SubProcess.calls, []) 49 | self.assertIs(SubProcess.calls, calls) 50 | self.assertEqual(SubProcess.outputs, []) 51 | self.assertIs(SubProcess.outputs, outputs) 52 | 53 | SubProcess.calls.extend(['foo', 'bar']) 54 | SubProcess.outputs.append(b'baz') 55 | self.assertIsNone(SubProcess.reset(True, [b'stuff', b'junk'])) 56 | self.assertIs(SubProcess.mocking, True) 57 | self.assertEqual(SubProcess.calls, []) 58 | self.assertIs(SubProcess.calls, calls) 59 | self.assertEqual(SubProcess.outputs, [b'stuff', b'junk']) 60 | self.assertIs(SubProcess.outputs, outputs) 61 | 62 | def test_check_call(self): 63 | SubProcess.reset(mocking=True) 64 | self.assertIsNone(SubProcess.check_call(['/no/such', 'thing'])) 65 | self.assertEqual(SubProcess.calls, [ 66 | ('check_call', ['/no/such', 'thing'], {}), 67 | ]) 68 | self.assertEqual(SubProcess.outputs, []) 69 | self.assertIsNone(SubProcess.check_call(['/nope'], foo='bar')) 70 | self.assertEqual(SubProcess.calls, [ 71 | ('check_call', ['/no/such', 'thing'], {}), 72 | ('check_call', ['/nope'], {'foo': 'bar'}), 73 | ]) 74 | self.assertEqual(SubProcess.outputs, []) 75 | 76 | SubProcess.reset(mocking=False) 77 | self.assertEqual(SubProcess.check_call(['/bin/true']), 0) 78 | self.assertEqual(SubProcess.calls, []) 79 | self.assertEqual(SubProcess.outputs, []) 80 | 81 | with self.assertRaises(subprocess.CalledProcessError) as cm: 82 | SubProcess.check_call(['/bin/false']) 83 | self.assertEqual(cm.exception.cmd, ['/bin/false']) 84 | self.assertEqual(cm.exception.returncode, 1) 85 | self.assertEqual(SubProcess.calls, []) 86 | self.assertEqual(SubProcess.outputs, []) 87 | 88 | with self.assertRaises(subprocess.TimeoutExpired) as cm: 89 | SubProcess.check_call(['/bin/sleep', '2'], timeout=0.5) 90 | self.assertEqual(cm.exception.cmd, ['/bin/sleep', '2']) 91 | self.assertEqual(cm.exception.timeout, 0.5) 92 | self.assertEqual(SubProcess.calls, []) 93 | self.assertEqual(SubProcess.outputs, []) 94 | 95 | def test_check_output(self): 96 | SubProcess.reset(mocking=True, outputs=[b'one', b'two']) 97 | self.assertEqual(SubProcess.check_output(['/no/such', 'thing']), b'one') 98 | self.assertEqual(SubProcess.calls, [ 99 | ('check_output', ['/no/such', 'thing'], {}), 100 | ]) 101 | self.assertEqual(SubProcess.outputs, [b'two']) 102 | self.assertEqual(SubProcess.check_output(['/nope'], foo='bar'), b'two') 103 | self.assertEqual(SubProcess.calls, [ 104 | ('check_output', ['/no/such', 'thing'], {}), 105 | ('check_output', ['/nope'], {'foo': 'bar'}), 106 | ]) 107 | self.assertEqual(SubProcess.outputs, []) 108 | 109 | SubProcess.reset(mocking=False) 110 | self.assertEqual( 111 | SubProcess.check_output(['/bin/echo', 'hello world']), 112 | b'hello world\n' 113 | ) 114 | self.assertEqual(SubProcess.calls, []) 115 | self.assertEqual(SubProcess.outputs, []) 116 | 117 | with self.assertRaises(subprocess.CalledProcessError) as cm: 118 | SubProcess.check_output(['/bin/false']) 119 | self.assertEqual(cm.exception.cmd, ['/bin/false']) 120 | self.assertEqual(cm.exception.returncode, 1) 121 | self.assertEqual(SubProcess.calls, []) 122 | self.assertEqual(SubProcess.outputs, []) 123 | 124 | with self.assertRaises(subprocess.TimeoutExpired) as cm: 125 | SubProcess.check_output(['/bin/sleep', '2'], timeout=0.5) 126 | self.assertEqual(cm.exception.cmd, ['/bin/sleep', '2']) 127 | self.assertEqual(cm.exception.timeout, 0.5) 128 | self.assertEqual(SubProcess.calls, []) 129 | self.assertEqual(SubProcess.outputs, []) 130 | -------------------------------------------------------------------------------- /system76driver/tests/test_model.py: -------------------------------------------------------------------------------- 1 | # system76-driver: Universal driver for System76 computers 2 | # Copyright (C) 2005-2016 System76, Inc. 3 | # 4 | # This file is part of `system76-driver`. 5 | # 6 | # `system76-driver` is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # `system76-driver` is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along 17 | # with `system76-driver`; if not, write to the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 19 | 20 | """ 21 | Unit tests for `system76driver.model` module. 22 | """ 23 | 24 | from unittest import TestCase 25 | 26 | from .helpers import TempDir 27 | from system76driver.mockable import SubProcess 28 | from system76driver import products, model 29 | 30 | 31 | OUTPUTS = ( 32 | b'CCF59000-5FBA-0000-0000-000000000000\n', 33 | b'Gazelle Professional\n', 34 | b'Gazelle Professional\n', 35 | b'gazp7\n', 36 | ) 37 | 38 | EXPECTED = ( 39 | ('system-uuid', 'CCF59000-5FBA-0000-0000-000000000000'), 40 | ('baseboard-product-name', 'Gazelle Professional'), 41 | ('system-product-name', 'Gazelle Professional'), 42 | ('system-version', 'gazp7'), 43 | ) 44 | 45 | 46 | class TestConstants(TestCase): 47 | def test_KEYWORDS(self): 48 | self.assertIsInstance(model.KEYWORDS, tuple) 49 | self.assertEqual(len(model.KEYWORDS), 4) 50 | for keyword in model.KEYWORDS: 51 | self.assertIsInstance(keyword, str) 52 | 53 | def test_TABLES(self): 54 | self.assertIsInstance(model.TABLES, dict) 55 | self.assertEqual(len(model.TABLES), 4) 56 | self.assertEqual(set(model.TABLES), set(model.KEYWORDS)) 57 | for (keyword, table) in model.TABLES.items(): 58 | self.assertIsInstance(keyword, str) 59 | self.assertIsInstance(table, dict) 60 | self.assertGreater(len(table), 0) 61 | for (key, value) in table.items(): 62 | self.assertIsInstance(key, str) 63 | self.assertIsInstance(value, str) 64 | # 'system-version' is currently always the same: 65 | for (key, value) in model.TABLES['system-version'].items(): 66 | self.assertEqual(key, value) 67 | 68 | # Extra careful checks for models that occur more than once in TABLES: 69 | reverse = {} 70 | for (keyword, table) in model.TABLES.items(): 71 | for (key, value) in table.items(): 72 | if value not in reverse: 73 | reverse[value] = set() 74 | reverse[value].add((keyword, key)) 75 | self.assertEqual(set(reverse), set(products.PRODUCTS)) 76 | 77 | multi = {} 78 | for (value, occurances) in reverse.items(): 79 | if len(occurances) > 1: 80 | multi[value] = occurances 81 | expected = { 82 | 'daru1': set([ 83 | ('baseboard-product-name', 'Z35FM'), 84 | ('baseboard-product-name', 'Z35F'), 85 | ]), 86 | 'gazp3': set([ 87 | ('system-product-name', 'Z62JM'), 88 | ('system-product-name', 'Z62JP'), 89 | 90 | ]), 91 | 'panv2': set([ 92 | ('system-product-name', 'Z96F'), 93 | ('system-product-name', 'Centoris V661'), 94 | ('system-product-name', 'Z96FM'), 95 | ]), 96 | 'serp1': set([ 97 | ('system-product-name', 'HEL80I'), 98 | ('system-product-name', 'HEL8X'), 99 | ]), 100 | 'star1': set([ 101 | ('system-product-name', 'UW1'), 102 | ('system-product-name', 'star1'), 103 | ]), 104 | 'star2': set([ 105 | ('system-product-name', 'E10IS'), 106 | ('system-product-name', 'E10IS2'), 107 | ('system-product-name', 'Star2'), 108 | ]), 109 | 'bonp2': set([ 110 | ('system-product-name', 'M570TU'), 111 | ('system-version', 'bonp2'), 112 | ]), 113 | 'daru3': set([ 114 | ('system-product-name', 'M720T/M730T'), 115 | ('system-version', 'daru3'), 116 | ]), 117 | 'panp4n': set([ 118 | ('system-product-name', 'M740TU/M760TU'), 119 | ('system-version', 'panp4n'), 120 | ]), 121 | 'serp5': set([ 122 | ('system-product-name', 'M860TU'), 123 | ('system-version', 'serp5'), 124 | ]), 125 | } 126 | self.assertEqual(set(multi), set(expected)) 127 | for key in sorted(multi): 128 | self.assertEqual(multi[key], expected[key], key) 129 | self.assertEqual(multi, expected) 130 | 131 | 132 | class TestFunctions(TestCase): 133 | def test_dmidecode(self): 134 | SubProcess.reset(True, [b'bar\n']) 135 | self.assertEqual(model.dmidecode('foo'), 'bar') 136 | self.assertEqual(SubProcess.calls, [ 137 | ('check_output', ['dmidecode', '-s', 'foo'], {}), 138 | ]) 139 | self.assertEqual(SubProcess.outputs, []) 140 | 141 | def test_get_dmi_info(self): 142 | SubProcess.reset(True, OUTPUTS) 143 | self.assertEqual(model.get_dmi_info(), dict(EXPECTED)) 144 | self.assertEqual(SubProcess.calls, [ 145 | ('check_output', ['dmidecode', '-s', 'system-uuid'], {}), 146 | ('check_output', ['dmidecode', '-s', 'baseboard-product-name'], {}), 147 | ('check_output', ['dmidecode', '-s', 'system-product-name'], {}), 148 | ('check_output', ['dmidecode', '-s', 'system-version'], {}), 149 | ]) 150 | self.assertEqual(SubProcess.outputs, []) 151 | 152 | def test_determine_model_1(self): 153 | """ 154 | Test `determine_model()` when *info* is not provided. 155 | """ 156 | SubProcess.reset(True, OUTPUTS) 157 | self.assertEqual(model.determine_model(), 'gazp7') 158 | self.assertEqual(SubProcess.calls, [ 159 | ('check_output', ['dmidecode', '-s', 'system-uuid'], {}), 160 | ('check_output', ['dmidecode', '-s', 'baseboard-product-name'], {}), 161 | ('check_output', ['dmidecode', '-s', 'system-product-name'], {}), 162 | ('check_output', ['dmidecode', '-s', 'system-version'], {}), 163 | ]) 164 | self.assertEqual(SubProcess.outputs, []) 165 | 166 | def test_determine_model_2(self): 167 | """ 168 | Test `determine_model()` when *info* is provided. 169 | """ 170 | SubProcess.reset(True) 171 | 172 | # system-uuid: 173 | info = {'system-uuid': '00000000-0000-0000-0000-000000000001'} 174 | self.assertEqual(model.determine_model(info), 'koap1') 175 | 176 | # baseboard-product-name: 177 | info = {'system-uuid': 'nope', 'baseboard-product-name': 'Z35FM'} 178 | self.assertEqual(model.determine_model(info), 'daru1') 179 | 180 | info = {'system-uuid': 'nope', 'baseboard-product-name': 'Z35F'} 181 | self.assertEqual(model.determine_model(info), 'daru1') 182 | 183 | info = {'system-uuid': 'nope', 'baseboard-product-name': 'MS-1221'} 184 | self.assertEqual(model.determine_model(info), 'daru2') 185 | 186 | info = {'system-uuid': 'nope', 'baseboard-product-name': 'MS-1221'} 187 | self.assertEqual(model.determine_model(info), 'daru2') 188 | 189 | # system-uuid: 190 | for (key, value) in model.TABLES['system-uuid'].items(): 191 | info = {'system-uuid': key} 192 | self.assertEqual(model.determine_model(info), value) 193 | 194 | # baseboard-product-name: 195 | for (key, value) in model.TABLES['baseboard-product-name'].items(): 196 | info = { 197 | 'system-uuid': 'nope', 198 | 'baseboard-product-name': key, 199 | } 200 | self.assertEqual(model.determine_model(info), value) 201 | 202 | # system-product-name: 203 | for (key, value) in model.TABLES['system-product-name'].items(): 204 | info = { 205 | 'system-uuid': 'nope', 206 | 'baseboard-product-name': 'nope', 207 | 'system-product-name': key, 208 | } 209 | self.assertEqual(model.determine_model(info), value) 210 | 211 | # system-version: 212 | for (key, value) in model.TABLES['system-version'].items(): 213 | info = { 214 | 'system-uuid': 'nope', 215 | 'baseboard-product-name': 'nope', 216 | 'system-product-name': 'nope', 217 | 'system-version': key, 218 | } 219 | self.assertEqual(model.determine_model(info), value) 220 | 221 | # non-System76: 222 | info = { 223 | 'system-uuid': 'nope', 224 | 'baseboard-product-name': 'nope', 225 | 'system-product-name': 'nope', 226 | 'system-version': 'nope', 227 | } 228 | self.assertEqual(model.determine_model(info), 'nonsystem76') 229 | 230 | # No calls should have resulted: 231 | self.assertEqual(SubProcess.calls, []) 232 | self.assertEqual(SubProcess.outputs, []) 233 | 234 | def test_determine_model_new(self): 235 | for val in model.TABLES['system-version']: 236 | tmp = TempDir() 237 | tmp.makedirs('class', 'dmi', 'id') 238 | tmp.write(val.encode(), 'class', 'dmi', 'id', 'product_version') 239 | self.assertEqual(model.determine_model_new(sysdir=tmp.dir), val) 240 | tmp = TempDir() 241 | tmp.makedirs('class', 'dmi', 'id') 242 | tmp.write(b'nope', 'class', 'dmi', 'id', 'product_version') 243 | SubProcess.reset(True, OUTPUTS) 244 | self.assertEqual(model.determine_model_new(sysdir=tmp.dir), 'gazp7') 245 | self.assertEqual(SubProcess.calls, [ 246 | ('check_output', ['dmidecode', '-s', 'system-uuid'], {}), 247 | ('check_output', ['dmidecode', '-s', 'baseboard-product-name'], {}), 248 | ('check_output', ['dmidecode', '-s', 'system-product-name'], {}), 249 | ('check_output', ['dmidecode', '-s', 'system-version'], {}), 250 | ]) 251 | self.assertEqual(SubProcess.outputs, []) 252 | 253 | -------------------------------------------------------------------------------- /system76driver/tests/test_products.py: -------------------------------------------------------------------------------- 1 | # system76-driver: Universal driver for System76 computers 2 | # Copyright (C) 2005-2016 System76, Inc. 3 | # 4 | # This file is part of `system76-driver`. 5 | # 6 | # `system76-driver` is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # `system76-driver` is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along 17 | # with `system76-driver`; if not, write to the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 19 | 20 | """ 21 | Unit tests for `system76driver.products` module. 22 | """ 23 | 24 | from unittest import TestCase 25 | 26 | from system76driver.mockable import SubProcess 27 | from system76driver import actions, products 28 | 29 | 30 | OUTPUTS = ( 31 | b'CCF59000-5FBA-0000-0000-000000000000\n', 32 | b'Gazelle Professional\n', 33 | b'Gazelle Professional\n', 34 | b'gazp7\n', 35 | ) 36 | 37 | 38 | class TestConstants(TestCase): 39 | def test_PRODUCTS(self): 40 | self.assertIsInstance(products.PRODUCTS, dict) 41 | for (key, value) in products.PRODUCTS.items(): 42 | SubProcess.reset(True, OUTPUTS) 43 | 44 | self.assertIsInstance(key, str) 45 | self.assertIsInstance(value, dict) 46 | self.assertIn('name', value) 47 | self.assertIsInstance(value['name'], str) 48 | self.assertTrue(value['name']) 49 | 50 | self.assertIsInstance(value['drivers'], list) 51 | for action in value['drivers']: 52 | self.assertTrue(issubclass(action, actions.Action)) 53 | inst = action() 54 | text = inst.describe() 55 | self.assertIsInstance(text, str) 56 | self.assertTrue(text, text) 57 | 58 | if 'screens' in value: 59 | screens = value['screens'] 60 | self.assertIsInstance(screens, dict) 61 | for (edid_md5, description) in screens.items(): 62 | self.assertIsInstance(edid_md5, str) 63 | self.assertIsInstance(description, str) 64 | -------------------------------------------------------------------------------- /system76driver/tests/test_util.py: -------------------------------------------------------------------------------- 1 | # system76-driver: Universal driver for System76 computers 2 | # Copyright (C) 2005-2016 System76, Inc. 3 | # 4 | # This file is part of `system76-driver`. 5 | # 6 | # `system76-driver` is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # `system76-driver` is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along 17 | # with `system76-driver`; if not, write to the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 19 | 20 | """ 21 | Unit tests for `system76driver.util` module. 22 | """ 23 | 24 | from unittest import TestCase 25 | import os 26 | from os import path 27 | import shutil 28 | 29 | from .helpers import TempDir 30 | from system76driver.mockable import SubProcess 31 | from system76driver import util 32 | 33 | 34 | class TestFunctions(TestCase): 35 | def test_create_tmp_logs(self): 36 | SubProcess.reset(mocking=False) 37 | (tmp, tgz) = util.create_tmp_logs(func=None) 38 | self.assertTrue(path.isdir(tmp)) 39 | self.assertTrue(tmp.startswith('/tmp/logs.')) 40 | self.assertEqual( 41 | sorted(os.listdir(tmp)), 42 | ['system76-logs', 'system76-logs.tgz'], 43 | ) 44 | self.assertEqual(tgz, path.join(tmp, 'system76-logs.tgz')) 45 | self.assertTrue(path.isfile(tgz)) 46 | self.assertTrue(path.isdir(path.join(tmp, 'system76-logs'))) 47 | shutil.rmtree(tmp) 48 | 49 | def test_create_logs(self): 50 | SubProcess.reset(mocking=False) 51 | tmp = TempDir() 52 | tgz = util.create_logs(tmp.dir, func=None) 53 | self.assertEqual(tgz, tmp.join('system76-logs.tgz')) 54 | self.assertTrue(path.isfile(tgz)) 55 | 56 | -------------------------------------------------------------------------------- /system76driver/userdaemon.py: -------------------------------------------------------------------------------- 1 | # system76-driver: Universal driver for System76 computers 2 | # Copyright (C) 2005-2016 System76, Inc. 3 | # 4 | # This file is part of `system76-driver`. 5 | # 6 | # `system76-driver` is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # `system76-driver` is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along 17 | # with `system76-driver`; if not, write to the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 19 | 20 | """ 21 | User-space work-around for Airplane Mode hotkey (Fn+F11). 22 | 23 | In the near future this will be replaced with a kernel driver to do the same. 24 | 25 | ACPI virtual device for Fn+F11:: 26 | 27 | Name (_HID, EisaId ("PNPC000")) 28 | """ 29 | 30 | import logging 31 | from os import path 32 | import subprocess 33 | import time 34 | 35 | from gi.repository import GLib 36 | 37 | 38 | log = logging.getLogger(__name__) 39 | 40 | # These products use 'acpi_video0' instead of 'intel_backlight': 41 | NEEDS_BACKLIGHT = ( 42 | 'bonx7', 43 | 'bonx8', 44 | 'bonw9', 45 | 'bonw10', 46 | 'serw8-15', 47 | 'serw8-17', 48 | 'serw8-17g', 49 | 'serw9', 50 | 'orxp1', 51 | ) 52 | 53 | class Backlight: 54 | def __init__(self, model, name, rootdir='/'): 55 | assert name in ('acpi_video0') 56 | self.model = model 57 | self.name = name 58 | self.current = None 59 | self.backlight_dir = path.join(rootdir, 60 | 'sys', 'class', 'backlight', name 61 | ) 62 | self.max_brightness_file = path.join(self.backlight_dir, 'max_brightness') 63 | self.brightness_file = path.join(self.backlight_dir, 'brightness') 64 | self.xbacklight_max_brightness = 10 65 | 66 | def read_max_brightness(self): 67 | with open(self.max_brightness_file, 'rb', 0) as fp: 68 | return int(fp.read(11)) 69 | 70 | def read_brightness(self): 71 | with open(self.brightness_file, 'rb', 0) as fp: 72 | return int(fp.read(11)) 73 | 74 | def set_xbacklight(self, brightness): 75 | xbrightness = int(100 * brightness / self.xbacklight_max_brightness) 76 | if xbrightness == 0: 77 | xbrightness = 1 78 | if xbrightness <= 100: 79 | xbrightness_cmd = ['xbacklight', 80 | '-set', 81 | str(xbrightness) 82 | ] 83 | try: 84 | subprocess.check_output(xbrightness_cmd) 85 | except: 86 | time.sleep(1) 87 | 88 | def run(self): 89 | self.xbacklight_max_brightness = self.read_max_brightness() 90 | self.timeout_id = GLib.timeout_add(100, self.on_timeout) 91 | 92 | def on_timeout(self): 93 | try: 94 | self.update() 95 | return True 96 | except Exception: 97 | log.exception('Error calling Backlight.update():') 98 | return False 99 | 100 | def update(self): 101 | brightness = self.read_brightness() 102 | if self.current != brightness: 103 | self.current = brightness 104 | if brightness >= 0: 105 | self.set_xbacklight(brightness) 106 | return True 107 | 108 | def _run_backlight(model): 109 | if model not in NEEDS_BACKLIGHT: 110 | log.info('Backlight hack not needed for %r', model) 111 | return 112 | log.info('Enabling backlight hack for %r', model) 113 | name = 'acpi_video0' 114 | backlight = Backlight(model, name) 115 | backlight.run() 116 | return backlight 117 | 118 | def run_backlight(model): 119 | try: 120 | return _run_backlight(model) 121 | except Exception: 122 | log.exception('Error calling _run_brightness(%r):', model) 123 | -------------------------------------------------------------------------------- /system76driver/util.py: -------------------------------------------------------------------------------- 1 | # system76-driver: Universal driver for System76 computers 2 | # Copyright (C) 2005-2016 System76, Inc. 3 | # 4 | # This file is part of `system76-driver`. 5 | # 6 | # `system76-driver` is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # `system76-driver` is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along 17 | # with `system76-driver`; if not, write to the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 19 | 20 | """ 21 | Collect logs and other info for support. 22 | """ 23 | 24 | import os 25 | from os import path 26 | import shutil 27 | import tempfile 28 | import distro 29 | import subprocess 30 | 31 | from .model import determine_model_new 32 | 33 | def dump_command(base, name, args): 34 | fp = open(path.join(base, name), 'xt') 35 | output = subprocess.run(" ".join(args), capture_output=True, shell=True, text=True) 36 | fp.write(output.stdout + "\n" + output.stderr) 37 | 38 | def dump_path(base, name, src): 39 | if path.exists(src): 40 | dst = path.join(base, name) 41 | dst_dir = path.dirname(dst) 42 | if not path.isdir(dst_dir): 43 | os.makedirs(dst_dir) 44 | assert not path.exists(dst) 45 | if path.isdir(src): 46 | shutil.copytree(src, dst) 47 | else: 48 | shutil.copy(src, dst) 49 | 50 | def dump_logs(base): 51 | fp = open(path.join(base, 'systeminfo.txt'), 'x') 52 | fp.write('System76 Model: {}\n'.format(determine_model_new())) 53 | fp.write('OS Version: {}\n'.format(distro.name(pretty=True))) 54 | fp.write('Kernel Version: {}\n'.format(os.uname().release)) 55 | fp.write('Kernel Revision: {}\n'.format(os.uname().version)) 56 | 57 | dump_command(base, "boot-process-times", ["systemd-analyze", "blame"]) 58 | dump_command(base, "free-disk-space", ["df", "-h"]) 59 | dump_command(base, "dmesg", ["dmesg"]) 60 | dump_command(base, "dmidecode", ["dmidecode"]) 61 | dump_command(base, "efibootmgr", ["efibootmgr", "-v"]) 62 | dump_command(base, "journalctl", ["journalctl", "--since", "yesterday"]) 63 | dump_command(base, "lsblk", ["lsblk", "-o", "NAME,MODEL,FSTYPE,FSVER,SIZE,FSUSE%,MOUNTPOINTS,LABEL,UUID"]) 64 | dump_command(base, "lsmod", ["lsmod"]) 65 | dump_command(base, "lspci", ["lspci", "-vv"]) 66 | dump_command(base, "lsusb", ["lsusb", "-vv"]) 67 | dump_command(base, "reboot-history", ["last"]) 68 | dump_command(base, "sensors", ["sensors"]) 69 | dump_command(base, "upower", ["upower"]) 70 | dump_command(base, "uptime", ["uptime"]) 71 | dump_command(base, "xinput", ["xinput"]) 72 | dump_path(base, "crypttab", "/etc/crypttab") 73 | dump_path(base, "kernelstub", "/etc/kernelstub/configuration") 74 | dump_path(base, "fstab", "/etc/fstab") 75 | dump_path(base, "syslog", "/var/log/syslog") 76 | dump_path(base, "Xorg.log", "/var/log/Xorg.0.log") 77 | dump_path(base, "apt/sources.list", "/etc/apt/sources.list") 78 | dump_path(base, "apt/sources.list.d", "/etc/apt/sources.list.d") 79 | dump_path(base, "apt/history", "/var/log/apt/history.log") 80 | dump_path(base, "apt/history-rotated.gz", "/var/log/apt/history.log.1.gz") 81 | dump_path(base, "apt/term", "/var/log/apt/term.log") 82 | dump_path(base, "apt/term-rotated.gz", "/var/log/apt/term.log.1.gz") 83 | 84 | def create_tmp_logs(func=dump_logs): 85 | tmp = tempfile.mkdtemp(prefix='logs.') 86 | base = path.join(tmp, 'system76-logs') 87 | os.mkdir(base) 88 | if func is not None: 89 | func(base) 90 | tgz = path.join(tmp, 'system76-logs.tgz') 91 | cmd = [ 92 | 'tar', '-czv', 93 | '-f', tgz, 94 | '-C', tmp, 95 | 'system76-logs', 96 | ] 97 | subprocess.run(cmd) 98 | return (tmp, tgz) 99 | 100 | def create_logs(homedir, func=dump_logs): 101 | (tmp, src) = create_tmp_logs(func) 102 | assert path.isdir(homedir) 103 | dst = path.join(homedir, path.basename(src)) 104 | shutil.copy(src, dst) 105 | shutil.rmtree(tmp) 106 | return dst 107 | --------------------------------------------------------------------------------