1#!/usr/bin/env python3

2# ========================================================================

3#

4# based on xlogparse

5#

6# DESCRIPTION

7#

8# Parses Xlog.*.log format files and allows looking up data from it

9#

10# AUTHOR

11# Bryce W. Harrington <bryce@canonical.com>

12#

13# COPYRIGHT

14# Copyright (C) 2010-2012 Bryce W. Harrington

15# All Rights Reserved.

16#

17# Permission is hereby granted, free of charge, to any person obtaining

18# a copy of this software and associated documentation files (the

19# "Software"), to deal in the Software without restriction, including

20# without limitation the rights to use, copy, modify, merge, publish,

21# distribute, sublicense, and/or sell copies of the Software, and to

22# permit persons to whom the Software is furnished to do so, subject to

23# the following conditions:

24#

25# The above copyright notice and this permission notice shall be included

26# in all copies or substantial portions of the Software.

27#

28# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,

29# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF

30# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.

31# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY

32# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,

33# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE

34# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

35#

36# ========================================================================

37 import re

38 import sys

39 import os

40 import glob

41

42 from subprocess import Popen, PIPE, check_output, CalledProcessError

43

44

45 class XorgLog(object):

46

47 def __init__(self, logfile=None):

48 self.modules = []

49 self.errors = []

50 self.warnings = []

51 self.info = []

52 self.notimpl = []

53 self.notices = []

54 self.cards = []

55 self.displays = {}

56 self.xserver_version = None

57 self.boot_time = None

58 self.boot_logfile = None

59 self.kernel_version = None

60 self.video_driver = None

61 self.xorg_conf_path = None

62 self.logfile = logfile

63

64 if logfile:

65 self.parse(logfile)

66

67 def parse(self, filename):

68 self.displays = {}

69 display = {}

70 display_name = "Unknown"

71 in_file = open(filename, errors="ignore")

72 gathering_module = False

73 found_ddx = False

74 module = None

75 for line in in_file.readlines():

76

77 # Errors and Warnings

78 m = re.search(r"\(WW\) (.*)$", line)

79 if m:

80 self.warnings.append(m.group(1))

81 continue

82

83 m = re.search(r"\(EE\) (.*)$", line)

84 if m:

85 self.errors.append(m.group(1))

86 continue

87

88 # General details

89 m = re.search(r"Current Operating System: (.*)$", line)

90 if m:

91 uname = m.group(1)

92 self.kernel_version = uname.split()[2]

93 continue

94

95 m = re.search(r"Kernel command line: (.*)$", line)

96 if m:

97 self.kernel_command_line = m.group(1)

98 continue

99

100 m = re.search(r"Build Date: (.*)$", line)

101 if m:

102 self.kernel_command_line = m.group(1)

103 continue

104

105 m = re.search(r'Log file: "(.*)", Time: (.*)$', line)

106 if m:

107 self.boot_logfile = m.group(1)

108 self.boot_time = m.group(2)

109

110 m = re.search(r"xorg-server ([^ ]+) .*$", line)

111 if m:

112 self.xserver_version = m.group(1)

113 continue

114

115 m = re.search(r"Using a default monitor configuration.", line)

116 if m and self.xorg_conf_path is None:

117 self.xorg_conf_path = "default"

118 continue

119

120 m = re.search(r'Using config file: "(.*)"', line)

121 if m:

122 self.xorg_conf_path = m.group(1)

123 continue

124

125 # Driver related information

126 m = re.search(r"\(..\)", line)

127 if m:

128 if gathering_module and module is not None:

129 self.modules.append(module)

130 gathering_module = False

131 module = None

132 m = re.search(

133 r"\(II\) Loading.*modules\/drivers\/(.+)_drv\.so", line

134 )

135 if m:

136 found_ddx = True

137 continue

138 m = re.search(r"\(II\) Module (\w+):", line)

139 if m:

140 module = {

141 "name": m.group(1),

142 "vendor": None,

143 "version": None,

144 "class": None,

145 "abi_name": None,

146 "abi_version": None,

147 "ddx": found_ddx,

148 }

149 found_ddx = False

150 gathering_module = True

151

152 if gathering_module:

153 m = re.search(r'vendor="(.*:?)"', line)

154 if m:

155 module["vendor"] = m.group(1)

156

157 m = re.search(r"module version = (.*)", line)

158 if m:

159 module["version"] = m.group(1)

160

161 if module["name"] == "nvidia":

162 try:

163 version = check_output(

164 "nvidia-settings -v",

165 shell=True,

166 universal_newlines=True,

167 )

168 m = re.search(r".*version\s+([0-9\.]+).*", version)

169 if m:

170 module["version"] = m.group(1)

171 except CalledProcessError:

172 pass

173

174 m = re.search(r"class: (.*)", line)

175 if m:

176 module["class"] = m.group(1)

177

178 m = re.search(r"ABI class:\s+(.*:?), version\s+(.*:?)", line)

179 if m:

180 if m.group(1)[:5] == "X.Org":

181 module["abi_name"] = m.group(1)[6:]

182 else:

183 module["abi_name"] = m.group(1)

184 module["abi_version"] = m.group(2)

185 continue

186

187 # EDID and Modelines

188 # We use this part to determine which driver is in use

189 # For Intel / RADEON / Matrox (using modesetting)

190 m = re.search(r"\(II\) (.*)\(\d+\): EDID for output (.*)", line)

191 if m:

192 self.displays[display_name] = display

193 if m.group(1) == "modeset":

194 self.video_driver = "modesetting"

195 else:

196 self.video_driver = m.group(1)

197 display_name = m.group(2)

198 display = {"Output": display_name}

199 continue

200

201 m = re.search(

202 r"\(II\) (.*)\(\d+\): Assigned Display Device: (.*)$", line

203 )

204 if m:

205 self.displays[display_name] = display

206 self.video_driver = m.group(1)

207 display_name = m.group(2)

208 display = {"Output": display_name}

209 continue

210

211 # For NVIDIA

212 m = re.search(r'\(II\) (.*)\(\d+\): Setting mode "(.*?):', line)

213 if not m:

214 m = re.search(

215 r'\(II\) (.*)\(\d+\): Setting mode "(NULL)"', line

216 )

217 if m:

218 self.displays[display_name] = display

219 self.video_driver = m.group(1)

220 display_name = m.group(2)

221 display = {"Output": display_name}

222 continue

223

224 # For 4th Intel after 3.11

225 m = re.search(

226 r"\(II\) (.*)\(\d+\): switch to mode .* using (.*),", line

227 )

228 if m:

229 self.displays[display_name] = display

230 self.video_driver = "intel" # 'intel' is what we expect to see

231 display_name = m.group(2)

232 display = {"Output": display_name}

233 continue

234

235 m = re.search(

236 r"Manufacturer: (.*) *Model: (.*) *Serial#: (.*)", line

237 )

238 if m:

239 display["display manufacturer"] = m.group(1)

240 display["display model"] = m.group(2)

241 display["display serial no."] = m.group(3)

242

243 m = re.search(r"EDID Version: (.*)", line)

244 if m:

245 display["display edid version"] = m.group(1)

246

247 m = re.search(r"EDID vendor \"(.*)\", prod id (.*)", line)

248 if m:

249 display["vendor"] = m.group(1)

250 display["product id"] = m.group(2)

251

252 m = re.search(

253 r"Max Image Size \[(.*)\]: *horiz.: (.*) *vert.: (.*)", line

254 )

255 if m:

256 display["size max horizontal"] = "%s %s" % (

257 m.group(2),

258 m.group(1),

259 )

260 display["size max vertical"] = "%s %s" % (

261 m.group(3),

262 m.group(1),

263 )

264

265 m = re.search(r"Image Size: *(.*) x (.*) (.*)", line)

266 if m:

267 display["size horizontal"] = "%s %s" % (m.group(1), m.group(3))

268 display["size vertical"] = "%s %s" % (m.group(2), m.group(3))

269

270 m = re.search(r"(.*) is preferred mode", line)

271 if m:

272 display["mode preferred"] = m.group(1)

273

274 m = re.search(r"Modeline \"(\d+)x(\d+)\"x([0-9\.]+) *(.*)$", line)

275 if m:

276 key = "mode %sx%s@%s" % (m.group(1), m.group(2), m.group(3))

277 display[key] = m.group(4)

278 continue

279

280 if display_name not in self.displays.keys():

281 self.displays[display_name] = display

282 in_file.close()

283

284 def errors_filtered(self):

285 excludes = set(

286 [

287 "error, (NI) not implemented, (??) unknown.",

288 'Failed to load module "fglrx" (module does not exist, 0)',

289 'Failed to load module "nv" (module does not exist, 0)',

290 ]

291 )

292 return [err for err in self.errors if err not in excludes]

293

294 def warnings_filtered(self):

295 excludes = set(

296 [

297 "warning, (EE) error, (NI) not implemented, (??) unknown.",

298 'The directory "/usr/share/fonts/X11/cyrillic" does not exist.', # noqa: E501

299 'The directory "/usr/share/fonts/X11/100dpi/" does not exist.',

300 'The directory "/usr/share/fonts/X11/75dpi/" does not exist.',

301 'The directory "/usr/share/fonts/X11/100dpi" does not exist.',

302 'The directory "/usr/share/fonts/X11/75dpi" does not exist.',

303 "Warning, couldn't open module nv",

304 "Warning, couldn't open module fglrx",

305 "Falling back to old probe method for vesa",

306 "Falling back to old probe method for fbdev",

307 ]

308 )

309 return [err for err in self.warnings if err not in excludes]

310

311

312 def get_driver_info(xlog):

313 """Return the running driver and version"""

314 print("-" * 13, "VIDEO DRIVER INFORMATION", "-" * 13)

315 if xlog.video_driver:

316 for module in xlog.modules:

317 if module["name"] == xlog.video_driver.lower():

318 print("Video Driver: %s" % module["name"])

319 print("Driver Version: %s" % module["version"])

320 print("\n")

321 return 0

322 else:

323 print(

324 "ERROR: No video driver loaded! Possibly in failsafe mode!",

325 file=sys.stderr,

326 )

327 return 1

328

329

330 def is_laptop():

331 return os.path.isdir("/proc/acpi/button/lid")

332

333

334 def hybrid_graphics_check(xlog):

335 """Check for Hybrid Graphics"""

336 card_id1 = re.compile(r".*0300: *(.+):(.+) \(.+\)")

337 card_id2 = re.compile(r".*03..: *(.+):(.+)")

338 cards_dict = {"8086": "Intel", "10de": "NVIDIA", "1002": "AMD"}

339 cards = []

340 drivers = []

341 formatted_cards = []

342

343 output = Popen(["lspci", "-n"], stdout=PIPE, universal_newlines=True)

344 card_list = output.communicate()[0].split("\n")

345

346 # List of discovered cards

347 for line in card_list:

348 m1 = card_id1.match(line)

349 m2 = card_id2.match(line)

350 if m1:

351 id1 = m1.group(1).strip().lower()

352 id2 = m1.group(2).strip().lower()

353 id = id1 + ":" + id2

354 cards.append(id)

355 elif m2:

356 id1 = m2.group(1).strip().lower()

357 id2 = m2.group(2).strip().lower()

358 id = id1 + ":" + id2

359 cards.append(id)

360

361 print("-" * 13, "HYBRID GRAPHICS CHECK", "-" * 16)

362 for card in cards:

363 formatted_name = cards_dict.get(card.split(":")[0], "Unknown")

364 formatted_cards.append(formatted_name)

365 print("Graphics Chipset: %s (%s)" % (formatted_name, card))

366

367 for module in xlog.modules:

368 if module["ddx"] and module["name"] not in drivers:

369 drivers.append(module["name"])

370 print("Loaded DDX Drivers: %s" % ", ".join(drivers))

371

372 has_hybrid_graphics = (

373 len(cards) > 1

374 and is_laptop()

375 and (

376 cards_dict.get("8086") in formatted_cards

377 or cards_dict.get("1002") in formatted_cards

378 )

379 )

380

381 print("Hybrid Graphics: %s" % (has_hybrid_graphics and "yes" or "no"))

382

383 return 0

384

385

386 def main():

387 usr_xorg_dir = os.path.expanduser("~/.local/share/xorg/")

388 root_xorg_dir = "/var/log/"

389 xlog = None

390 xorg_owner = []

391 tgt_dir = ""

392

393 # Output the Xorg owner

394 xorg_owner = check_output(

395 "ps -o user= -p $(pidof Xorg)", shell=True, universal_newlines=True

396 ).split()

397

398 # Check the Xorg owner and then judge the Xorg log location

399 if "root" in xorg_owner:

400 tgt_dir = root_xorg_dir

401 elif xorg_owner:

402 tgt_dir = usr_xorg_dir

403 else:

404 print("ERROR: No Xorg process found!", file=sys.stderr)

405

406 if tgt_dir:

407 xorg_file_paths = list(glob.iglob(tgt_dir + "Xorg.*.log"))

408 target_file = xorg_file_paths[0]

409 xlog = XorgLog(target_file)

410

411 results = []

412

413 results.append(get_driver_info(xlog))

414 results.append(hybrid_graphics_check(xlog))

415

416 return 1 if 1 in results else 0

417

418

419 if __name__ == "__main__":

420 sys.exit(main())