Meson: Avoid UAC on 32-bit Windows for gtk-update-icon-cache

As the program executable name has 'update' in its filename,
gtk-update-icon-cache.exe is considered to be an installer program on 32-bit
Windows [1], which will cause the program to fail to run unless it is running
with elevated privileges (i.e. UAC).

Avoid this situation by embedding a manifest file into the final executable
that tells Windows that this is not a program that requires elevation.

Also make the autotools build files dist the new script and use the new script
to generate the manifest and rc files, instead of hardcoding the generating
bits in gtk/Makefile.am

Fixes issue #3632.

[1]: https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-vista/cc709628(v=ws.10)?redirectedfrom=MSDN,
under section "Installer Detection  Technology"
This commit is contained in:
Chun-wei Fan
2021-02-03 11:30:15 +08:00
parent ddb9bae3d4
commit a612a42c11
3 changed files with 137 additions and 20 deletions

View File

@ -1710,27 +1710,10 @@ GTK_UPDATE_ICON_CACHE_MANIFEST = gtk-update-icon-cache.exe.manifest
GTK_UPDATE_ICON_CACHE_RC = gtk-update-icon-cache.rc
GTK_UPDATE_ICON_CACHE_MANIFEST_OBJECT = gtk-update-icon-cache_manifest.o
$(GTK_UPDATE_ICON_CACHE_MANIFEST):
(echo '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' ; \
echo '<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">' ; \
echo ' <assemblyIdentity version="1.0.0.0"' ; \
echo ' processorArchitecture="'$(EXE_MANIFEST_ARCHITECTURE)'"' ; \
echo ' name="gtk-update-icon-cache.exe"' ; \
echo ' type="win32"/>' ; \
echo ' <!-- Identify the application security requirements. -->' ; \
echo ' <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">' ; \
echo ' <security>' ; \
echo ' <requestedPrivileges>' ; \
echo ' <requestedExecutionLevel' ; \
echo ' level="asInvoker"' ; \
echo ' uiAccess="false"/>' ; \
echo ' </requestedPrivileges>' ; \
echo ' </security>' ; \
echo ' </trustInfo>' ; \
echo '</assembly>' ) >$@
$(GTK_UPDATE_ICON_CACHE_MANIFEST): Makefile generate-uac-manifest.py
$(PYTHON) $(srcdir)/generate-uac-manifest.py -p=gtk3 -n=gtk-update-icon-cache --pkg-version=$(GTK_VERSION) --output-dir=$(builddir)
$(GTK_UPDATE_ICON_CACHE_RC):
(echo -e '#include <winuser.h>\nCREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST '$(GTK_UPDATE_ICON_CACHE_MANIFEST)) >$@
$(GTK_UPDATE_ICON_CACHE_RC): $(GTK_UPDATE_ICON_CACHE_MANIFEST)
$(GTK_UPDATE_ICON_CACHE_MANIFEST_OBJECT): $(GTK_UPDATE_ICON_CACHE_RC) $(GTK_UPDATE_ICON_CACHE_MANIFEST)
$(WINDRES) --input $< --output $@ --output-format=coff
@ -1793,6 +1776,7 @@ EXTRA_DIST += \
meson.build \
gen-gtk-gresources-xml.py \
gen-rc.py \
generate-uac-manifest.py \
gentypefuncs.py \
a11y/meson.build \
deprecated/meson.build \

View File

@ -0,0 +1,110 @@
#!/usr/bin/env python3
"""
This script generates a Windows manifest file and optionally a resource file to
determine whether a specified program requires UAC elevation
"""
import os
import argparse
DOMAIN_NAME='gnome'
def main():
parser = argparse.ArgumentParser(
description=__doc__)
parser.add_argument('-p', '--package', required=True,
help='package name of the executable')
parser.add_argument('-n', '--name', required=True,
help='name of executable')
parser.add_argument('--pkg-version', required=True, dest='version',
help='version of package')
parser.add_argument('--require-admin', action='store_true', dest='admin',
default=False,
help='require admin access to application')
parser.add_argument('--input-resource-file', dest='resource',
default=None,
help='existing .rc file to embed UAC manifest (do not generate a new .rc file), must have included winuser.h in it')
parser.add_argument('--output-dir', dest='outdir',
default=None,
help='directory to output resulting files')
args = parser.parse_args()
if args.resource is not None:
if not os.path.isfile(args.resource):
raise FileNotFoundError("Specified resource file '%s' does not exist" % args.resource)
generate_manifest(args.package, args.name, args.version, args.admin, args.outdir)
write_rc_file(args.name, args.resource, args.outdir)
def generate_manifest(package, name, version, admin, outdir):
if version.count('.') == 0:
manifest_package_version = version + '.0.0.0'
elif version.count('.') == 1:
manifest_package_version = version + '.0.0'
elif version.count('.') == 2:
manifest_package_version = version + '.0'
elif version.count('.') == 3:
manifest_package_version = version
else:
parts = version.split('.')
manifest_package_version = ''
for x in (0, 1, 2, 3):
if x == 0:
manifest_package_version += parts[x]
else:
manifest_package_version += '.' + parts[x]
if outdir is not None:
output_file_base_name = os.path.join(outdir, name)
else:
output_file_base_name = name
outfile = open(output_file_base_name + '.exe.manifest', 'w+')
outfile.write('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n')
outfile.write('<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">\n')
outfile.write(' <assemblyIdentity version="%s"\n' % manifest_package_version)
outfile.write(' processorArchitecture="*"\n')
outfile.write(' name="%s.%s.%s.exe"\n' % (DOMAIN_NAME, package, name))
outfile.write(' type="win32" />\n')
outfile.write(' <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">\n')
outfile.write(' <security>\n')
outfile.write(' <requestedPrivileges>\n')
outfile.write(' <requestedExecutionLevel\n')
if admin:
outfile.write(' level="requireAdministrator"\n')
else:
outfile.write(' level="asInvoker"\n')
outfile.write(' uiAccess="false" />\n')
outfile.write(' </requestedPrivileges>\n')
outfile.write(' </security>\n')
outfile.write(' </trustInfo>\n')
outfile.write('</assembly>\n')
outfile.close()
def write_rc_file(name, resource, outdir):
if outdir is not None:
output_file_base_name = os.path.join(outdir, name)
else:
output_file_base_name = name
if resource is None:
outfile = open(output_file_base_name + '.rc', 'w+')
outfile.write('#include <winuser.h>')
else:
if resource != output_file_base_name + '.rc':
outfile = open(output_file_base_name + '.rc', 'w+')
else:
outfile = open(output_file_base_name + '.final.rc', 'w+')
srcfile = open(resource, 'r')
outfile.write(srcfile.read())
srcfile.close()
outfile.write('\n')
outfile.write('CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "%s.exe.manifest"' % name)
outfile.close()
if __name__ == '__main__':
main()

View File

@ -1065,9 +1065,32 @@ gtk_builder_tool = executable(
install: true
)
extra_update_icon_cache_objs = []
if win32_enabled
gen_uac_manifest = find_program('generate-uac-manifest.py')
uac_exe_pkg = 'gtk3'
uac_exe_name = 'gtk-update-icon-cache'
# Well, we have to forgo the xxx.exe.manifest in the output listing, since
# compile_resources doesn't like to consume targets with multiple outputs,
# and the xxx.exe.manifest and xxx.rc are tied together
uac_rc = custom_target(
'gtk/@0@.rc'.format(uac_exe_name),
output: ['@0@.rc'.format(uac_exe_name)],
command: [gen_uac_manifest,
'-p=@0@'.format(uac_exe_pkg),
'-n=@0@'.format(uac_exe_name),
'--pkg-version=@0@'.format(meson.project_version()),
'--output-dir=@OUTDIR@'],
)
extra_update_icon_cache_objs = import('windows').compile_resources(uac_rc)
endif
gtk_update_icon_cache = executable(
'gtk-update-icon-cache',
'updateiconcache.c',
extra_update_icon_cache_objs,
c_args: gtk_cargs,
dependencies: libgtk_dep,
install: true