1# This file is part of Xpra. 2# Copyright (C) 2020 Antoine Martin <antoine@xpra.org> 3# Xpra is released under the terms of the GNU GPL v2, or, at your option, any 4# later version. See the file COPYING for details. 5 6import os 7import struct 8 9from xpra.platform.win32 import get_common_startmenu_dir, get_startmenu_dir 10from xpra.log import Logger 11from xpra.util import print_nested_dict 12 13log = Logger("exec") 14 15 16def parse_link(content): 17 # skip first 20 bytes (HeaderSize and LinkCLSID) 18 # read the LinkFlags structure (4 bytes) 19 lflags = struct.unpack('I', content[0x14:0x18])[0] 20 position = 0x18 21 # if the HasLinkTargetIDList bit is set then skip the stored IDList 22 # structure and header 23 if (lflags & 0x01) == 1: 24 position = struct.unpack('H', content[0x4C:0x4E])[0] + 0x4E 25 last_pos = position 26 position += 0x04 27 # get how long the file information is (LinkInfoSize) 28 length = struct.unpack('I', content[last_pos:position])[0] 29 # skip 12 bytes (LinkInfoHeaderSize, LinkInfoFlags, and VolumeIDOffset) 30 position += 0x0C 31 # go to the LocalBasePath position 32 lbpos = struct.unpack('I', content[position:position+0x04])[0] 33 position = last_pos + lbpos 34 # read the string at the given position of the determined length 35 size= (length + last_pos) - position - 0x02 36 temp = struct.unpack('c' * size, content[position:position+size]) 37 return ''.join([chr(ord(a)) for a in temp]) 38 39def read_link(path): 40 try: 41 with open(path, 'rb') as stream: 42 content = stream.read() 43 return parse_link(content) 44 except Exception as e: 45 #log("error parsing '%s'", path, exc_info=True) 46 log("error parsing '%s': %s", path, e) 47 return None 48 49def listdir(d): 50 try: 51 return os.listdir(d) 52 except PermissionError as e: 53 log("listdir(%s)", d, exc_info=True) 54 log.warn("Warning: cannot access directory '%s':", d) 55 log.warn(" %s", e) 56 return () 57 58def load_subdir(d): 59 #recurse down directories 60 #and return a dictionary of entries 61 menu = {} 62 for x in listdir(d): 63 if x.endswith(".ini"): 64 continue 65 name = os.path.join(d, x) 66 if os.path.isdir(name): 67 menu.update(load_subdir(name)) 68 elif name.endswith(".lnk"): 69 exe = read_link(name) 70 if exe: 71 menu[x[:-4]] = { 72 "command" : exe, 73 } 74 return menu 75 76def load_dir(d): 77 log("load_dir(%s)", d) 78 menu = {} 79 for x in listdir(d): 80 log(" %s" % (x)) 81 if x.endswith(".ini"): 82 continue 83 name = os.path.join(d, x) 84 if os.path.isdir(name): 85 subdirmenu = load_subdir(name) 86 if subdirmenu: 87 menu[x] = { 88 "Entries" : subdirmenu, 89 } 90 elif name.endswith(".lnk"): 91 #add them to the "Shortcuts" submenu: 92 exe = read_link(name) 93 if exe: 94 menu.setdefault("Shortcuts", {}).setdefault("Entries", {})[x[:-4]] = { 95 "command" : exe, 96 } 97 return menu 98 99def load_menu(): 100 menu = {} 101 for d_fn in (get_common_startmenu_dir, get_startmenu_dir): 102 d = d_fn() 103 if not d: 104 continue 105 #ie: "C:\ProgramData\Microsoft\Windows\Start Menu" 106 for x in listdir(d): 107 subdir = os.path.join(d, x) 108 if os.path.isdir(subdir): 109 #ie: "C:\ProgramData\Microsoft\Windows\Start Menu\Programs" 110 m = load_dir(subdir) 111 if not m: 112 continue 113 #TODO: recursive merge 114 for k,v in m.items(): 115 ev = menu.get(k) 116 if isinstance(ev, dict): 117 ev.update(v) 118 else: 119 menu[k] = v 120 return menu 121 122 123def main(): 124 from xpra.platform import program_context 125 with program_context("menu-helper", "Menu Helper"): 126 menu = load_menu() 127 print_nested_dict(menu) 128 129 130if __name__ == "__main__": 131 main() 132