1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
|
#!/usr/bin/python3
import argparse
import glob
import re
import shlex
import shutil
import subprocess
import sys
from typing import cast
from os import path
from xdg import BaseDirectory
from xdg.DesktopEntry import DesktopEntry
# allow matching empty envs with .*
env_re = re.compile(r"\w+=.*")
# installed gapps
gapps = []
def strip_command_parent(cmd_args: list[str], is_first: bool = True) -> list[str]:
while cmd_args and env_re.match(cmd_args[0]):
cmd_args = cmd_args[1:]
try:
cmd = cmd_args[0]
except IndexError:
cmd = ""
if is_first and cmd == "exec":
return strip_command_parent(cmd_args[1:], is_first=False)
if cmd == "env" or cmd.endswith("/env"):
return strip_command_parent(cmd_args[1:], is_first=False)
return cmd_args
def is_valid_gapp_cmd(cmd: str):
app_id = cmd
if not gapps:
try:
output = subprocess.check_output(["gapplication", "list-apps"], text=True)
gapps.extend((output or "").split("\n"))
except subprocess.CalledProcessError:
gapps.append("")
return app_id in gapps
def is_gapp_cmd(cmd_args: list[str]):
return (
len(cmd_args) > 2
and (cmd_args[0] == "gapplication" or cmd_args[0].endswith("/gapplication"))
and cmd_args[1] == "launch"
)
def find_missing_desktop_files(desktop_dir: str, show_all: bool):
for df in glob.iglob("*.desktop", root_dir=desktop_dir):
file_path = path.join(desktop_dir, df)
de = DesktopEntry(file_path)
file_name = shlex.quote(de.getFileName())
if de.getHidden():
yield file_name
continue
if show_all or not de.getNoDisplay():
if exc := cast(str | None, (de.getExec() or de.getTryExec())):
try:
cmd = shlex.split(exc)
cmd = strip_command_parent(cmd)
if is_gapp_cmd(cmd):
if not is_valid_gapp_cmd(cmd[2]):
yield file_name
elif not (cmd and shutil.which(cmd[0])):
yield file_name
except ValueError as err:
print(f"Error parsing '{file_path}': {err}", file=sys.stderr)
def find_desktop_directories():
"""
https://wiki.archlinux.org/title/desktop_entries#Modify_desktop_files
https://wiki.archlinux.org/title/XDG_Autostart#Directories
"""
yield from BaseDirectory.load_data_paths("applications")
yield from BaseDirectory.load_config_paths("autostart")
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Find desktop entries files with broken executables"
)
parser.add_argument(
"-a",
"--all",
action="store_true",
help='show all desktop entries regardless of "NoDisplay" value',
default=False,
)
args = parser.parse_args()
for d in find_desktop_directories():
for df in find_missing_desktop_files(d, args.all):
print(df)
|