Initial commit: RustDesk 1.4.7 macOS desktop port
- Filled empty libs/hbb_common/ submodule (cloned from rustdesk/hbb_common) - Patched Flutter 3.44 / Dart 3.12 compatibility: * flutter/lib/generated_bridge.dart: asTypedList with cast<>, DartPort=Int64 * flutter/lib/common.dart: DialogTheme->DialogThemeData, TabBarTheme->TabBarThemeData * flutter/pubspec.yaml: extended_text 14.0.0->15.0.2, google_fonts override 5.0.0 * flutter/macos/Runner/Configs/Release.xcconfig: EXCLUDED_ARCHS=x86_64 - Build verified: cargo check + cargo build --features flutter + cargo build --release --features flutter - Verified flutter build macos --debug and --release both produce working .app - Verified .dmg installer (27MB arm64) created via hdiutil - Build deps: Xcode 26.5, CocoaPods 1.16.2, Flutter 3.44, VCPKG arm64-osx
This commit is contained in:
Executable
+27
@@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
if [ "$1" = configure ]; then
|
||||
|
||||
INITSYS=$(ls -al /proc/1/exe | awk -F' ' '{print $NF}' | awk -F'/' '{print $NF}')
|
||||
ln -f -s /usr/share/rustdesk/rustdesk /usr/bin/rustdesk
|
||||
|
||||
if [ "systemd" == "$INITSYS" ]; then
|
||||
|
||||
if [ -e /etc/systemd/system/rustdesk.service ]; then
|
||||
rm /etc/systemd/system/rustdesk.service /usr/lib/systemd/system/rustdesk.service /usr/lib/systemd/user/rustdesk.service >/dev/null 2>&1
|
||||
fi
|
||||
mkdir -p /usr/lib/systemd/system/
|
||||
cp /usr/share/rustdesk/files/systemd/rustdesk.service /usr/lib/systemd/system/rustdesk.service
|
||||
# try fix error in Ubuntu 18.04
|
||||
# Failed to reload rustdesk.service: Unit rustdesk.service is not loaded properly: Exec format error.
|
||||
# /usr/lib/systemd/system/rustdesk.service:10: Executable path is not absolute: pkill -f "rustdesk --"
|
||||
if [ -e /usr/bin/pkill ]; then
|
||||
sed -i "s|pkill|/usr/bin/pkill|g" /usr/lib/systemd/system/rustdesk.service
|
||||
fi
|
||||
systemctl daemon-reload
|
||||
systemctl enable rustdesk
|
||||
systemctl start rustdesk
|
||||
fi
|
||||
fi
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
case $1 in
|
||||
purge)
|
||||
rm -rf /root/.config/rustdesk || true
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
case $1 in
|
||||
install|upgrade)
|
||||
INITSYS=$(ls -al /proc/1/exe | awk -F' ' '{print $NF}' | awk -F'/' '{print $NF}')
|
||||
if [ "systemd" == "${INITSYS}" ]; then
|
||||
service rustdesk stop || true
|
||||
sleep 1
|
||||
rm -rf /usr/bin/libsciter-gtk.so
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
case $1 in
|
||||
remove|upgrade)
|
||||
INITSYS=$(ls -al /proc/1/exe | awk -F' ' '{print $NF}' | awk -F'/' '{print $NF}')
|
||||
rm -f /usr/bin/rustdesk
|
||||
|
||||
if [ "systemd" == "${INITSYS}" ]; then
|
||||
|
||||
systemctl stop rustdesk || true
|
||||
systemctl disable rustdesk || true
|
||||
rm /etc/systemd/system/rustdesk.service /usr/lib/systemd/system/rustdesk.service || true
|
||||
|
||||
# workaround temp dev build between 1.1.9 and 1.2.0
|
||||
serverUser=$(ps -ef | grep -E 'rustdesk +--server' | grep -v 'sudo ' | awk '{print $1}' | head -1)
|
||||
if [ "$serverUser" != "" ] && [ "$serverUser" != "root" ]
|
||||
then
|
||||
systemctl --machine=${serverUser}@.host --user stop rustdesk >/dev/null 2>&1 || true
|
||||
fi
|
||||
rm /usr/lib/systemd/user/rustdesk.service >/dev/null 2>&1 || true
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
@@ -0,0 +1,35 @@
|
||||
pkgname=rustdesk
|
||||
pkgver=1.4.7
|
||||
pkgrel=0
|
||||
epoch=
|
||||
pkgdesc=""
|
||||
arch=('x86_64')
|
||||
url=""
|
||||
license=('AGPL-3.0')
|
||||
groups=()
|
||||
depends=('gtk3' 'xdotool' 'libxcb' 'libxfixes' 'alsa-lib' 'libva' 'libappindicator-gtk3' 'pam' 'gst-plugins-base' 'gst-plugin-pipewire')
|
||||
makedepends=()
|
||||
checkdepends=()
|
||||
optdepends=()
|
||||
provides=()
|
||||
conflicts=()
|
||||
replaces=()
|
||||
backup=()
|
||||
options=()
|
||||
install=pacman_install
|
||||
changelog=
|
||||
noextract=()
|
||||
md5sums=() #generate with 'makepkg -g'
|
||||
|
||||
package() {
|
||||
if [[ ${FLUTTER} ]]; then
|
||||
mkdir -p "${pkgdir}/usr/share/rustdesk" && cp -r ${HBB}/flutter/build/linux/x64/release/bundle/* -t "${pkgdir}/usr/share/rustdesk"
|
||||
fi
|
||||
mkdir -p "${pkgdir}/usr/bin"
|
||||
pushd ${pkgdir} && ln -s /usr/share/rustdesk/rustdesk usr/bin/rustdesk && popd
|
||||
install -Dm 644 $HBB/res/rustdesk.service -t "${pkgdir}/usr/share/rustdesk/files"
|
||||
install -Dm 644 $HBB/res/rustdesk.desktop -t "${pkgdir}/usr/share/rustdesk/files"
|
||||
install -Dm 644 $HBB/res/rustdesk-link.desktop -t "${pkgdir}/usr/share/rustdesk/files"
|
||||
install -Dm 644 $HBB/res/128x128@2x.png "${pkgdir}/usr/share/icons/hicolor/256x256/apps/rustdesk.png"
|
||||
install -Dm 644 $HBB/res/scalable.svg "${pkgdir}/usr/share/icons/hicolor/scalable/apps/rustdesk.svg"
|
||||
}
|
||||
@@ -0,0 +1,791 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import requests
|
||||
import argparse
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
|
||||
def get_personal_ab(url, token):
|
||||
"""Get personal address book GUID"""
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
response = requests.get(f"{url}/api/ab/personal", headers=headers)
|
||||
|
||||
if response.status_code != 200:
|
||||
return f"Error: {response.status_code} - {response.text}"
|
||||
|
||||
return response.json()
|
||||
|
||||
|
||||
def view_shared_abs(url, token, name=None):
|
||||
"""View all shared address books (excluding personal ones)"""
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
pageSize = 30
|
||||
params = {
|
||||
"name": name,
|
||||
}
|
||||
|
||||
filtered_params = {
|
||||
k: "%" + v + "%" if (v != "-" and "%" not in v and k != "name") else v
|
||||
for k, v in params.items()
|
||||
if v is not None
|
||||
}
|
||||
filtered_params["pageSize"] = pageSize
|
||||
|
||||
abs = []
|
||||
current = 0
|
||||
|
||||
while True:
|
||||
current += 1
|
||||
filtered_params["current"] = current
|
||||
response = requests.get(f"{url}/api/ab/shared/profiles", headers=headers, params=filtered_params)
|
||||
if response.status_code != 200:
|
||||
print(f"Error: HTTP {response.status_code} - {response.text}")
|
||||
exit(1)
|
||||
|
||||
response_json = response.json()
|
||||
if "error" in response_json:
|
||||
print(f"Error: {response_json['error']}")
|
||||
exit(1)
|
||||
|
||||
data = response_json.get("data", [])
|
||||
abs.extend(data)
|
||||
|
||||
total = response_json.get("total", 0)
|
||||
if len(data) < pageSize or current * pageSize >= total:
|
||||
break
|
||||
|
||||
return abs
|
||||
|
||||
|
||||
def get_ab_by_name(url, token, ab_name):
|
||||
"""Get address book by name"""
|
||||
abs = view_shared_abs(url, token, ab_name)
|
||||
for ab in abs:
|
||||
if ab["name"] == ab_name:
|
||||
return ab
|
||||
return None
|
||||
|
||||
|
||||
def view_ab_peers(url, token, ab_guid, peer_id=None, alias=None):
|
||||
"""View peers in an address book"""
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
pageSize = 30
|
||||
params = {
|
||||
"ab": ab_guid,
|
||||
"id": peer_id,
|
||||
"alias": alias,
|
||||
}
|
||||
|
||||
filtered_params = {
|
||||
k: "%" + v + "%" if (v != "-" and "%" not in v and k not in ["ab"]) else v
|
||||
for k, v in params.items()
|
||||
if v is not None
|
||||
}
|
||||
filtered_params["pageSize"] = pageSize
|
||||
|
||||
peers = []
|
||||
current = 0
|
||||
|
||||
while True:
|
||||
current += 1
|
||||
filtered_params["current"] = current
|
||||
response = requests.get(f"{url}/api/ab/peers", headers=headers, params=filtered_params)
|
||||
if response.status_code != 200:
|
||||
print(f"Error: HTTP {response.status_code} - {response.text}")
|
||||
exit(1)
|
||||
|
||||
response_json = response.json()
|
||||
if "error" in response_json:
|
||||
print(f"Error: {response_json['error']}")
|
||||
exit(1)
|
||||
|
||||
data = response_json.get("data", [])
|
||||
peers.extend(data)
|
||||
|
||||
total = response_json.get("total", 0)
|
||||
if len(data) < pageSize or current * pageSize >= total:
|
||||
break
|
||||
|
||||
return peers
|
||||
|
||||
|
||||
def view_ab_tags(url, token, ab_guid):
|
||||
"""View tags in an address book"""
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.get(f"{url}/api/ab/tags/{ab_guid}", headers=headers)
|
||||
response_json = check_response(response)
|
||||
|
||||
# Format color values as hex
|
||||
if response_json:
|
||||
for tag in response_json:
|
||||
if "color" in tag and tag["color"] is not None:
|
||||
# Convert color to hex format
|
||||
color_value = tag["color"]
|
||||
if isinstance(color_value, int):
|
||||
tag["color"] = f"0x{color_value:08X}"
|
||||
|
||||
return response_json if response_json else []
|
||||
|
||||
|
||||
def check_response(response):
|
||||
"""Check API response and return result"""
|
||||
if response.status_code != 200:
|
||||
print(f"Error: HTTP {response.status_code} - {response.text}")
|
||||
exit(1)
|
||||
|
||||
try:
|
||||
response_json = response.json()
|
||||
if "error" in response_json:
|
||||
print(f"Error: {response_json['error']}")
|
||||
exit(1)
|
||||
return response_json
|
||||
except ValueError:
|
||||
return response.text or "Success"
|
||||
|
||||
|
||||
def add_peer(url, token, ab_guid, peer_id, alias=None, note=None, tags=None, password=None):
|
||||
"""Add a peer to address book"""
|
||||
print(f"Adding peer {peer_id} to address book")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
payload = {
|
||||
"id": peer_id,
|
||||
"note": note,
|
||||
}
|
||||
|
||||
# Add peer info if provided
|
||||
info = {}
|
||||
if alias:
|
||||
info["alias"] = alias
|
||||
if tags:
|
||||
info["tags"] = tags if isinstance(tags, list) else [tags]
|
||||
if password:
|
||||
info["password"] = password
|
||||
|
||||
if info:
|
||||
payload.update(info)
|
||||
|
||||
response = requests.post(f"{url}/api/ab/peer/add/{ab_guid}", headers=headers, json=payload)
|
||||
return check_response(response)
|
||||
|
||||
|
||||
def delete_peer(url, token, ab_guid, peer_ids):
|
||||
"""Delete peers from address book by IDs"""
|
||||
if isinstance(peer_ids, str):
|
||||
peer_ids = [peer_ids]
|
||||
|
||||
print(f"Deleting peers {peer_ids} from address book")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.delete(f"{url}/api/ab/peer/{ab_guid}", headers=headers, json=peer_ids)
|
||||
return check_response(response)
|
||||
|
||||
def update_peer(url, token, ab_guid, peer_id, alias=None, note=None, tags=None, password=None):
|
||||
"""Update a peer in address book"""
|
||||
print(f"Updating peer {peer_id} in address book")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
# Check if at least one parameter is provided for update
|
||||
update_params = [alias, note, tags, password]
|
||||
if all(param is None for param in update_params):
|
||||
return "Error: At least one parameter must be specified for update"
|
||||
|
||||
payload = {
|
||||
"id": peer_id,
|
||||
}
|
||||
|
||||
# Add fields to update
|
||||
info = {}
|
||||
if alias is not None:
|
||||
info["alias"] = alias
|
||||
if tags is not None:
|
||||
info["tags"] = tags if isinstance(tags, list) else [tags]
|
||||
if password is not None:
|
||||
info["password"] = password
|
||||
|
||||
if info:
|
||||
payload.update(info)
|
||||
|
||||
if note is not None:
|
||||
payload["note"] = note
|
||||
|
||||
response = requests.put(f"{url}/api/ab/peer/update/{ab_guid}", headers=headers, json=payload)
|
||||
return check_response(response)
|
||||
|
||||
|
||||
def str2color(tag_name, existing_colors=None):
|
||||
"""Generate color for tag name similar to str2color2 function"""
|
||||
if existing_colors is None:
|
||||
existing_colors = []
|
||||
|
||||
color_map = {
|
||||
"red": 0xFFFF0000,
|
||||
"green": 0xFF008000,
|
||||
"blue": 0xFF0000FF,
|
||||
"orange": 0xFFFF9800,
|
||||
"purple": 0xFF9C27B0,
|
||||
"grey": 0xFF9E9E9E,
|
||||
"cyan": 0xFF00BCD4,
|
||||
"lime": 0xFFCDDC39,
|
||||
"teal": 0xFF009688,
|
||||
"pink": 0xFFF48FB1,
|
||||
"indigo": 0xFF3F51B5,
|
||||
"brown": 0xFF795548,
|
||||
}
|
||||
|
||||
lower_name = tag_name.lower()
|
||||
|
||||
# Check if tag name matches a predefined color
|
||||
if lower_name in color_map:
|
||||
return color_map[lower_name]
|
||||
|
||||
# Special case for yellow
|
||||
if lower_name == "yellow":
|
||||
return 0xFFFFFF00
|
||||
|
||||
# Generate hash-based color
|
||||
hash_value = 0
|
||||
for char in tag_name:
|
||||
hash_value += ord(char)
|
||||
|
||||
color_list = list(color_map.values())
|
||||
hash_value = hash_value % len(color_list)
|
||||
result = color_list[hash_value]
|
||||
|
||||
# If color is already used, try to find an unused one
|
||||
if result in existing_colors:
|
||||
for color in color_list:
|
||||
if color not in existing_colors:
|
||||
result = color
|
||||
break
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def add_tag(url, token, ab_guid, tag_name, color=None):
|
||||
"""Add a tag to address book"""
|
||||
print(f"Adding tag '{tag_name}' to address book")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
# If no color specified, generate one based on tag name
|
||||
if color is None:
|
||||
# Get existing tags to avoid color conflicts
|
||||
try:
|
||||
existing_tags = view_ab_tags(url, token, ab_guid)
|
||||
existing_colors = [tag.get("color", 0) for tag in existing_tags]
|
||||
color = str2color(tag_name, existing_colors)
|
||||
except:
|
||||
# Fallback to default color if we can't get existing tags
|
||||
color = str2color(tag_name)
|
||||
|
||||
payload = {
|
||||
"name": tag_name,
|
||||
"color": color,
|
||||
}
|
||||
|
||||
response = requests.post(f"{url}/api/ab/tag/add/{ab_guid}", headers=headers, json=payload)
|
||||
return check_response(response)
|
||||
|
||||
|
||||
def update_tag(url, token, ab_guid, tag_name, color):
|
||||
"""Update a tag in address book"""
|
||||
print(f"Updating tag '{tag_name}' in address book")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
payload = {
|
||||
"name": tag_name,
|
||||
"color": color,
|
||||
}
|
||||
|
||||
response = requests.put(f"{url}/api/ab/tag/update/{ab_guid}", headers=headers, json=payload)
|
||||
return check_response(response)
|
||||
|
||||
|
||||
def delete_tags(url, token, ab_guid, tag_names):
|
||||
"""Delete tags from address book"""
|
||||
if isinstance(tag_names, str):
|
||||
tag_names = [tag_names]
|
||||
|
||||
print(f"Deleting tags {tag_names} from address book")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.delete(f"{url}/api/ab/tag/{ab_guid}", headers=headers, json=tag_names)
|
||||
return check_response(response)
|
||||
|
||||
|
||||
def add_shared_ab(url, token, name, note=None, password=None):
|
||||
"""Add a new shared address book"""
|
||||
print(f"Adding shared address book '{name}'")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
payload = {
|
||||
"name": name,
|
||||
"note": note,
|
||||
}
|
||||
|
||||
# Add info if password is provided
|
||||
if password:
|
||||
payload["info"] = {
|
||||
"password": password
|
||||
}
|
||||
|
||||
response = requests.post(f"{url}/api/ab/shared/add", headers=headers, json=payload)
|
||||
return check_response(response)
|
||||
|
||||
|
||||
def update_shared_ab(url, token, ab_guid, name=None, note=None, owner=None, password=None):
|
||||
"""Update a shared address book"""
|
||||
print(f"Updating shared address book {ab_guid}")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
# Check if at least one parameter is provided for update
|
||||
update_params = [name, note, owner, password]
|
||||
if all(param is None for param in update_params):
|
||||
return "Error: At least one parameter must be specified for update"
|
||||
|
||||
payload = {
|
||||
"guid": ab_guid,
|
||||
}
|
||||
|
||||
if name is not None:
|
||||
payload["name"] = name
|
||||
if note is not None:
|
||||
payload["note"] = note
|
||||
if owner is not None:
|
||||
payload["owner"] = owner
|
||||
if password is not None:
|
||||
payload["info"] = {
|
||||
"password": password
|
||||
}
|
||||
|
||||
response = requests.put(f"{url}/api/ab/shared/update/profile", headers=headers, json=payload)
|
||||
return check_response(response)
|
||||
|
||||
|
||||
def delete_shared_abs(url, token, ab_guids):
|
||||
"""Delete shared address books"""
|
||||
if isinstance(ab_guids, str):
|
||||
ab_guids = [ab_guids]
|
||||
|
||||
print(f"Deleting shared address books {ab_guids}")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.delete(f"{url}/api/ab/shared", headers=headers, json=ab_guids)
|
||||
return check_response(response)
|
||||
|
||||
|
||||
def permission_to_string(permission):
|
||||
"""Convert numeric permission to string representation"""
|
||||
permission_map = {
|
||||
1: "ro", # Read
|
||||
2: "rw", # ReadWrite
|
||||
3: "full" # FullControl
|
||||
}
|
||||
return permission_map.get(permission, str(permission))
|
||||
|
||||
|
||||
def string_to_permission(permission_str):
|
||||
"""Convert string permission to numeric representation"""
|
||||
permission_map = {
|
||||
"ro": 1, # Read
|
||||
"rw": 2, # ReadWrite
|
||||
"full": 3 # FullControl
|
||||
}
|
||||
return permission_map.get(permission_str.lower(), None)
|
||||
|
||||
|
||||
def view_ab_rules(url, token, ab_guid):
|
||||
"""View rules in an address book"""
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
pageSize = 30
|
||||
params = {
|
||||
"ab": ab_guid,
|
||||
"pageSize": pageSize,
|
||||
}
|
||||
|
||||
rules = []
|
||||
current = 0
|
||||
|
||||
while True:
|
||||
current += 1
|
||||
params["current"] = current
|
||||
response = requests.get(f"{url}/api/ab/rules", headers=headers, params=params)
|
||||
if response.status_code != 200:
|
||||
print(f"Error: HTTP {response.status_code} - {response.text}")
|
||||
exit(1)
|
||||
|
||||
response_json = response.json()
|
||||
if "error" in response_json:
|
||||
print(f"Error: {response_json['error']}")
|
||||
exit(1)
|
||||
|
||||
data = response_json.get("data", [])
|
||||
rules.extend(data)
|
||||
|
||||
total = response_json.get("total", 0)
|
||||
if len(data) < pageSize or current * pageSize >= total:
|
||||
break
|
||||
|
||||
# Convert numeric permissions to string format
|
||||
for rule in rules:
|
||||
if "rule" in rule:
|
||||
rule["rule"] = permission_to_string(rule["rule"])
|
||||
|
||||
return rules
|
||||
|
||||
|
||||
def add_ab_rule(url, token, ab_guid, rule_type, user=None, group=None, rule=1):
|
||||
"""Add a rule to address book"""
|
||||
print(f"Adding {rule_type} rule to address book")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
payload = {
|
||||
"guid": ab_guid,
|
||||
"rule": rule,
|
||||
}
|
||||
|
||||
if rule_type == "user" and user:
|
||||
payload["user"] = user
|
||||
elif rule_type == "group" and group:
|
||||
payload["group"] = group
|
||||
elif rule_type == "everyone":
|
||||
# For everyone, both user and group are None (not included in payload)
|
||||
pass
|
||||
|
||||
response = requests.post(f"{url}/api/ab/rule", headers=headers, json=payload)
|
||||
return check_response(response)
|
||||
|
||||
|
||||
def update_ab_rule(url, token, rule_guid, rule):
|
||||
"""Update an address book rule"""
|
||||
print(f"Updating rule {rule_guid}")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
payload = {
|
||||
"guid": rule_guid,
|
||||
"rule": rule,
|
||||
}
|
||||
|
||||
response = requests.patch(f"{url}/api/ab/rule", headers=headers, json=payload)
|
||||
return check_response(response)
|
||||
|
||||
|
||||
def delete_ab_rules(url, token, rule_guids):
|
||||
"""Delete address book rules"""
|
||||
if isinstance(rule_guids, str):
|
||||
rule_guids = [rule_guids]
|
||||
|
||||
print(f"Deleting rules {rule_guids}")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.delete(f"{url}/api/ab/rules", headers=headers, json=rule_guids)
|
||||
return check_response(response)
|
||||
|
||||
|
||||
def main():
|
||||
def parse_color(value):
|
||||
"""Parse color value - supports both hex (0xFF00FF00) and decimal"""
|
||||
if value.startswith('0x') or value.startswith('0X'):
|
||||
return int(value, 16)
|
||||
else:
|
||||
return int(value)
|
||||
|
||||
def parse_permission(value):
|
||||
"""Parse permission value - supports both string (ro/rw/full) and numeric (1/2/3)"""
|
||||
# Try to parse as string first
|
||||
permission_num = string_to_permission(value)
|
||||
if permission_num is not None:
|
||||
return permission_num
|
||||
|
||||
# Try to parse as integer for backward compatibility
|
||||
try:
|
||||
num_value = int(value)
|
||||
if num_value in [1, 2, 3]:
|
||||
return num_value
|
||||
else:
|
||||
raise argparse.ArgumentTypeError(f"Invalid permission value: {value}. Must be one of: ro, rw, full, 1, 2, 3")
|
||||
except ValueError:
|
||||
raise argparse.ArgumentTypeError(f"Invalid permission value: {value}. Must be one of: ro, rw, full, 1, 2, 3")
|
||||
|
||||
parser = argparse.ArgumentParser(description="Address Book manager")
|
||||
|
||||
# Required arguments
|
||||
parser.add_argument(
|
||||
"command",
|
||||
choices=["view-ab", "add-ab", "update-ab", "delete-ab", "get-personal-ab",
|
||||
"view-peer", "add-peer", "update-peer", "delete-peer",
|
||||
"view-tag", "add-tag", "update-tag", "delete-tag",
|
||||
"view-rule", "add-rule", "update-rule", "delete-rule"],
|
||||
help="Command to execute",
|
||||
)
|
||||
|
||||
# Global arguments (used by all commands)
|
||||
parser.add_argument("--url", required=True, help="URL of the API")
|
||||
parser.add_argument("--token", required=True, help="Bearer token for authentication")
|
||||
|
||||
# Address book identification (used by most commands except get-personal-ab)
|
||||
parser.add_argument("--ab-name", help="Address book name (for identification)")
|
||||
parser.add_argument("--ab-guid", help="Address book GUID (alternative to ab-name)")
|
||||
|
||||
# Address book management arguments
|
||||
parser.add_argument("--ab-update-name", help="New address book name (for update)")
|
||||
parser.add_argument("--note", help="Note field")
|
||||
parser.add_argument("--password", help="Password field")
|
||||
parser.add_argument("--owner", help="Address book owner (username)")
|
||||
|
||||
# Peer management arguments
|
||||
parser.add_argument("--peer-id", help="Peer ID")
|
||||
parser.add_argument("--alias", help="Peer alias")
|
||||
parser.add_argument("--tags", help="Peer tags (supports both 'tag1,tag2' and '[tag1,tag2]' formats, use '[]' to clear tags)")
|
||||
|
||||
# Tag management arguments
|
||||
parser.add_argument("--tag-name", help="Tag name")
|
||||
parser.add_argument("--tag-color", type=parse_color, help="Tag color (hex number like 0xFF00FF00 or decimal, auto-generated if not specified)")
|
||||
|
||||
# Rule management arguments
|
||||
parser.add_argument("--rule-type", choices=["user", "group", "everyone"], help="Rule type (auto-detected if not specified)")
|
||||
parser.add_argument("--rule-user", help="Rule target user name (auto-sets rule-type=user)")
|
||||
parser.add_argument("--rule-group", help="Rule target group name (auto-sets rule-type=group)")
|
||||
parser.add_argument("--rule-permission", type=parse_permission, help="Rule permission (ro=Read, rw=ReadWrite, full=FullControl, or numeric 1/2/3)")
|
||||
parser.add_argument("--rule-guid", help="Rule GUID (for update/delete)")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Remove trailing slashes from URL
|
||||
while args.url.endswith("/"):
|
||||
args.url = args.url[:-1]
|
||||
|
||||
if args.command == "view-ab":
|
||||
# View all shared address books
|
||||
abs = view_shared_abs(args.url, args.token, args.ab_name)
|
||||
print(json.dumps(abs, indent=2))
|
||||
|
||||
elif args.command == "get-personal-ab":
|
||||
# Get personal address book GUID
|
||||
personal_ab = get_personal_ab(args.url, args.token)
|
||||
print(json.dumps(personal_ab, indent=2))
|
||||
|
||||
elif args.command in ["add-ab", "update-ab", "delete-ab"]:
|
||||
# Address book management commands
|
||||
if args.command == "add-ab":
|
||||
if not args.ab_name:
|
||||
print("Error: --ab-name is required for add-ab command")
|
||||
return
|
||||
|
||||
result = add_shared_ab(args.url, args.token, args.ab_name, args.note, args.password)
|
||||
print(f"Result: {result}")
|
||||
|
||||
elif args.command in ["update-ab", "delete-ab"]:
|
||||
# Commands that need ab-name or ab-guid
|
||||
if not args.ab_name and not args.ab_guid:
|
||||
print("Error: --ab-name or --ab-guid is required for this command")
|
||||
return
|
||||
|
||||
if args.ab_name and args.ab_guid:
|
||||
print("Error: Cannot specify both --ab-name and --ab-guid")
|
||||
return
|
||||
|
||||
if args.ab_guid:
|
||||
ab_guid = args.ab_guid
|
||||
print(f"Working with address book GUID: {ab_guid}")
|
||||
else:
|
||||
# Get address book by name
|
||||
ab = get_ab_by_name(args.url, args.token, args.ab_name)
|
||||
if not ab:
|
||||
print(f"Error: Address book '{args.ab_name}' not found")
|
||||
return
|
||||
ab_guid = ab["guid"]
|
||||
print(f"Working with address book: {args.ab_name} (GUID: {ab_guid})")
|
||||
|
||||
if args.command == "update-ab":
|
||||
result = update_shared_ab(args.url, args.token, ab_guid, args.ab_update_name, args.note, args.owner, args.password)
|
||||
print(f"Result: {result}")
|
||||
|
||||
elif args.command == "delete-ab":
|
||||
result = delete_shared_abs(args.url, args.token, ab_guid)
|
||||
print(f"Result: {result}")
|
||||
|
||||
elif args.command in ["view-peer", "add-peer", "update-peer", "delete-peer", "view-tag", "add-tag", "update-tag", "delete-tag", "view-rule", "add-rule", "update-rule", "delete-rule"]:
|
||||
if not args.ab_name and not args.ab_guid:
|
||||
print("Error: --ab-name or --ab-guid is required for this command")
|
||||
return
|
||||
|
||||
if args.ab_name and args.ab_guid:
|
||||
print("Error: Cannot specify both --ab-name and --ab-guid")
|
||||
return
|
||||
|
||||
if args.ab_guid:
|
||||
ab_guid = args.ab_guid
|
||||
print(f"Working with address book GUID: {ab_guid}")
|
||||
else:
|
||||
# Get address book by name
|
||||
ab = get_ab_by_name(args.url, args.token, args.ab_name)
|
||||
if not ab:
|
||||
print(f"Error: Address book '{args.ab_name}' not found")
|
||||
return
|
||||
|
||||
ab_guid = ab["guid"]
|
||||
print(f"Working with address book: {args.ab_name} (GUID: {ab_guid})")
|
||||
|
||||
if args.command == "view-peer":
|
||||
peers = view_ab_peers(args.url, args.token, ab_guid, args.peer_id, args.alias)
|
||||
print(json.dumps(peers, indent=2))
|
||||
|
||||
elif args.command == "add-peer":
|
||||
if not args.peer_id:
|
||||
print("Error: --peer-id is required for add-peer command")
|
||||
return
|
||||
|
||||
# Handle tags parsing - support both [tag1,tag2] and tag1,tag2 formats
|
||||
tags = None
|
||||
if args.tags is not None:
|
||||
if args.tags == "[]":
|
||||
tags = [] # Empty list to clear tags
|
||||
else:
|
||||
# Remove brackets if present and split by comma
|
||||
tags_str = args.tags.strip()
|
||||
if tags_str.startswith('[') and tags_str.endswith(']'):
|
||||
tags_str = tags_str[1:-1] # Remove brackets
|
||||
tags = [tag.strip() for tag in tags_str.split(",") if tag.strip()]
|
||||
|
||||
result = add_peer(
|
||||
args.url,
|
||||
args.token,
|
||||
ab_guid,
|
||||
args.peer_id,
|
||||
args.alias,
|
||||
args.note,
|
||||
tags,
|
||||
args.password
|
||||
)
|
||||
print(f"Result: {result}")
|
||||
|
||||
elif args.command == "update-peer":
|
||||
if not args.peer_id:
|
||||
print("Error: --peer-id is required for update-peer command")
|
||||
return
|
||||
|
||||
# Handle tags parsing - support both [tag1,tag2] and tag1,tag2 formats
|
||||
tags = None
|
||||
if args.tags is not None:
|
||||
if args.tags == "[]":
|
||||
tags = [] # Empty list to clear tags
|
||||
else:
|
||||
# Remove brackets if present and split by comma
|
||||
tags_str = args.tags.strip()
|
||||
if tags_str.startswith('[') and tags_str.endswith(']'):
|
||||
tags_str = tags_str[1:-1] # Remove brackets
|
||||
tags = [tag.strip() for tag in tags_str.split(",") if tag.strip()]
|
||||
|
||||
result = update_peer(
|
||||
args.url,
|
||||
args.token,
|
||||
ab_guid,
|
||||
args.peer_id,
|
||||
args.alias,
|
||||
args.note,
|
||||
tags,
|
||||
args.password
|
||||
)
|
||||
print(f"Result: {result}")
|
||||
|
||||
elif args.command == "delete-peer":
|
||||
if not args.peer_id:
|
||||
print("Error: --peer-id is required for delete-peer command")
|
||||
return
|
||||
|
||||
result = delete_peer(args.url, args.token, ab_guid, args.peer_id)
|
||||
print(f"Result: {result}")
|
||||
|
||||
elif args.command == "view-tag":
|
||||
tags = view_ab_tags(args.url, args.token, ab_guid)
|
||||
print(json.dumps(tags, indent=2))
|
||||
|
||||
elif args.command == "add-tag":
|
||||
if not args.tag_name:
|
||||
print("Error: --tag-name is required for add-tag command")
|
||||
return
|
||||
|
||||
result = add_tag(args.url, args.token, ab_guid, args.tag_name, args.tag_color)
|
||||
print(f"Result: {result}")
|
||||
|
||||
elif args.command == "update-tag":
|
||||
if not args.tag_name:
|
||||
print("Error: --tag-name is required for update-tag command")
|
||||
return
|
||||
|
||||
result = update_tag(args.url, args.token, ab_guid, args.tag_name, args.tag_color)
|
||||
print(f"Result: {result}")
|
||||
|
||||
elif args.command == "delete-tag":
|
||||
if not args.tag_name:
|
||||
print("Error: --tag-name is required for delete-tag command")
|
||||
return
|
||||
|
||||
result = delete_tags(args.url, args.token, ab_guid, args.tag_name)
|
||||
print(f"Result: {result}")
|
||||
|
||||
elif args.command == "view-rule":
|
||||
rules = view_ab_rules(args.url, args.token, ab_guid)
|
||||
print(json.dumps(rules, indent=2))
|
||||
|
||||
elif args.command == "add-rule":
|
||||
if not args.rule_permission:
|
||||
print("Error: --rule-permission is required for add-rule command")
|
||||
return
|
||||
|
||||
# Auto-detect rule type if not explicitly specified
|
||||
if not args.rule_type:
|
||||
if args.rule_user and args.rule_group:
|
||||
print("Error: Cannot specify both --rule-user and --rule-group")
|
||||
return
|
||||
elif args.rule_user:
|
||||
rule_type = "user"
|
||||
elif args.rule_group:
|
||||
rule_type = "group"
|
||||
else:
|
||||
print("Error: Must specify --rule-type=everyone, --rule-user, or --rule-group")
|
||||
return
|
||||
else:
|
||||
rule_type = args.rule_type
|
||||
|
||||
# Validate explicit rule type with parameters
|
||||
if rule_type == "user" and not args.rule_user:
|
||||
print("Error: --rule-user is required when rule-type=user")
|
||||
return
|
||||
elif rule_type == "group" and not args.rule_group:
|
||||
print("Error: --rule-group is required when rule-type=group")
|
||||
return
|
||||
elif rule_type == "user" and args.rule_group:
|
||||
print("Error: Cannot specify --rule-group when rule-type=user")
|
||||
return
|
||||
elif rule_type == "group" and args.rule_user:
|
||||
print("Error: Cannot specify --rule-user when rule-type=group")
|
||||
return
|
||||
elif rule_type == "everyone" and (args.rule_user or args.rule_group):
|
||||
print("Error: Cannot specify --rule-user or --rule-group when rule-type=everyone")
|
||||
return
|
||||
|
||||
result = add_ab_rule(args.url, args.token, ab_guid, rule_type, args.rule_user, args.rule_group, args.rule_permission)
|
||||
print(f"Result: {result}")
|
||||
|
||||
elif args.command == "update-rule":
|
||||
if not args.rule_guid:
|
||||
print("Error: --rule-guid is required for update-rule command")
|
||||
return
|
||||
if not args.rule_permission:
|
||||
print("Error: --rule-permission is required for update-rule command")
|
||||
return
|
||||
|
||||
result = update_ab_rule(args.url, args.token, args.rule_guid, args.rule_permission)
|
||||
print(f"Result: {result}")
|
||||
|
||||
elif args.command == "delete-rule":
|
||||
if not args.rule_guid:
|
||||
print("Error: --rule-guid is required for delete-rule command")
|
||||
return
|
||||
|
||||
result = delete_ab_rules(args.url, args.token, args.rule_guid)
|
||||
print(f"Result: {result}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Executable
+374
@@ -0,0 +1,374 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import requests
|
||||
import argparse
|
||||
import json
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
|
||||
def format_timestamp(timestamp):
|
||||
"""Convert Unix timestamp to readable local datetime"""
|
||||
if timestamp is None:
|
||||
return None
|
||||
try:
|
||||
# Convert to local time
|
||||
local_dt = datetime.fromtimestamp(timestamp)
|
||||
return local_dt.strftime("%Y-%m-%d %H:%M:%S")
|
||||
except (ValueError, TypeError):
|
||||
return timestamp
|
||||
|
||||
|
||||
def parse_local_time_to_utc_string(time_str):
|
||||
"""Parse local time string to UTC time string for API filtering"""
|
||||
try:
|
||||
# Parse the local time string
|
||||
local_dt = datetime.strptime(time_str, "%Y-%m-%d %H:%M:%S.%f")
|
||||
# Make the datetime object timezone-aware using system's local timezone
|
||||
local_dt = local_dt.replace(tzinfo=datetime.now().astimezone().tzinfo)
|
||||
utc_dt = local_dt.astimezone(timezone.utc)
|
||||
return utc_dt.strftime("%Y-%m-%d %H:%M:%S.000")
|
||||
except ValueError:
|
||||
try:
|
||||
# Try without microseconds
|
||||
local_dt = datetime.strptime(time_str, "%Y-%m-%d %H:%M:%S")
|
||||
# Make the datetime object timezone-aware using system's local timezone
|
||||
local_dt = local_dt.replace(tzinfo=datetime.now().astimezone().tzinfo)
|
||||
utc_dt = local_dt.astimezone(timezone.utc)
|
||||
return utc_dt.strftime("%Y-%m-%d %H:%M:%S.000")
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
def get_connection_type_name(conn_type):
|
||||
"""Convert connection type number to readable name"""
|
||||
type_map = {
|
||||
0: "Remote Desktop",
|
||||
1: "File Transfer",
|
||||
2: "Port Transfer",
|
||||
3: "View Camera",
|
||||
4: "Terminal"
|
||||
}
|
||||
return type_map.get(conn_type, f"Unknown ({conn_type})")
|
||||
|
||||
|
||||
def get_console_type_name(console_type):
|
||||
"""Convert console audit type number to readable name"""
|
||||
type_map = {
|
||||
0: "Group Management",
|
||||
1: "User Management",
|
||||
2: "Device Management",
|
||||
3: "Address Book Management"
|
||||
}
|
||||
return type_map.get(console_type, f"Unknown ({console_type})")
|
||||
|
||||
|
||||
def get_console_operation_name(operation_code):
|
||||
"""Convert console operation code to readable name"""
|
||||
operation_map = {
|
||||
0: "User Login",
|
||||
1: "Add Group",
|
||||
2: "Add User",
|
||||
3: "Add Device",
|
||||
4: "Delete Groups",
|
||||
5: "Disconnect Device",
|
||||
6: "Enable Users",
|
||||
7: "Disable Users",
|
||||
8: "Enable Devices",
|
||||
9: "Disable Devices",
|
||||
10: "Update Group",
|
||||
11: "Update User",
|
||||
12: "Update Device",
|
||||
13: "Delete User",
|
||||
14: "Delete Device",
|
||||
15: "Add Address Book",
|
||||
16: "Delete Address Book",
|
||||
17: "Change Address Book Name",
|
||||
18: "Delete Devices in the Address Book Recycle Bin",
|
||||
19: "Empty Address Book Recycle Bin",
|
||||
20: "Add Address Book Permission",
|
||||
21: "Delete Address Book Permission",
|
||||
22: "Update Address Book Permission"
|
||||
}
|
||||
return operation_map.get(operation_code, f"Unknown ({operation_code})")
|
||||
|
||||
|
||||
def get_alarm_type_name(alarm_type):
|
||||
"""Convert alarm type number to readable name"""
|
||||
type_map = {
|
||||
0: "Access attempt outside the IP whitelist",
|
||||
1: "Over 30 consecutive access attempts",
|
||||
2: "Multiple access attempts within one minute",
|
||||
3: "Over 30 consecutive login attempts",
|
||||
4: "Multiple login attempts within one minute",
|
||||
5: "Multiple login attempts within one hour"
|
||||
}
|
||||
return type_map.get(alarm_type, f"Unknown ({alarm_type})")
|
||||
|
||||
|
||||
def enhance_audit_data(data, audit_type):
|
||||
"""Enhance audit data with readable formats"""
|
||||
if not data:
|
||||
return data
|
||||
|
||||
enhanced_data = []
|
||||
for item in data:
|
||||
enhanced_item = item.copy()
|
||||
|
||||
# Convert timestamps - replace original values
|
||||
if 'created_at' in enhanced_item:
|
||||
enhanced_item['created_at'] = format_timestamp(enhanced_item['created_at'])
|
||||
if 'end_time' in enhanced_item:
|
||||
enhanced_item['end_time'] = format_timestamp(enhanced_item['end_time'])
|
||||
|
||||
# Add type-specific enhancements - replace original values
|
||||
if audit_type == 'conn':
|
||||
if 'conn_type' in enhanced_item:
|
||||
enhanced_item['conn_type'] = get_connection_type_name(enhanced_item['conn_type'])
|
||||
else:
|
||||
enhanced_item['conn_type'] = "Not Logged In"
|
||||
|
||||
elif audit_type == 'console':
|
||||
if 'typ' in enhanced_item:
|
||||
# Replace typ field with type and convert to readable name
|
||||
enhanced_item['type'] = get_console_type_name(enhanced_item['typ'])
|
||||
del enhanced_item['typ']
|
||||
if 'iop' in enhanced_item:
|
||||
# Replace iop field with operation and convert to readable name
|
||||
enhanced_item['operation'] = get_console_operation_name(enhanced_item['iop'])
|
||||
del enhanced_item['iop']
|
||||
|
||||
elif audit_type == 'alarm' and 'typ' in enhanced_item:
|
||||
# Replace typ field with type and convert to readable name
|
||||
enhanced_item['type'] = get_alarm_type_name(enhanced_item['typ'])
|
||||
del enhanced_item['typ']
|
||||
|
||||
enhanced_data.append(enhanced_item)
|
||||
|
||||
return enhanced_data
|
||||
|
||||
|
||||
def check_response(response):
|
||||
"""Check API response and return result"""
|
||||
if response.status_code != 200:
|
||||
print(f"Error: HTTP {response.status_code} - {response.text}")
|
||||
exit(1)
|
||||
|
||||
try:
|
||||
response_json = response.json()
|
||||
if "error" in response_json:
|
||||
print(f"Error: {response_json['error']}")
|
||||
exit(1)
|
||||
return response_json
|
||||
except ValueError:
|
||||
return response.text or "Success"
|
||||
|
||||
|
||||
def view_audits_common(url, token, endpoint, filters=None, page_size=None, current=None,
|
||||
created_at=None, days_ago=None, non_wildcard_fields=None):
|
||||
"""Common function for viewing audits"""
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
# Set default page size and current page
|
||||
if page_size is None:
|
||||
page_size = 10
|
||||
if current is None:
|
||||
current = 1
|
||||
|
||||
params = {
|
||||
"pageSize": page_size,
|
||||
"current": current
|
||||
}
|
||||
|
||||
# Add filter parameters if provided
|
||||
if filters:
|
||||
for key, value in filters.items():
|
||||
if value is not None:
|
||||
params[key] = value
|
||||
|
||||
# Handle time filters
|
||||
if days_ago is not None:
|
||||
# Calculate datetime from days ago
|
||||
target_time = datetime.now() - timedelta(days=days_ago)
|
||||
# Convert to UTC time string using system timezone
|
||||
utc_timestamp = target_time.timestamp()
|
||||
utc_dt = datetime.fromtimestamp(utc_timestamp, timezone.utc)
|
||||
params["created_at"] = utc_dt.strftime("%Y-%m-%d %H:%M:%S.000")
|
||||
elif created_at:
|
||||
# Parse local time string and convert to UTC time string
|
||||
utc_time_str = parse_local_time_to_utc_string(created_at)
|
||||
if utc_time_str is not None:
|
||||
params["created_at"] = utc_time_str
|
||||
else:
|
||||
# If parsing fails, pass the original value
|
||||
params["created_at"] = created_at
|
||||
|
||||
# Apply wildcard patterns for string fields (excluding specific fields)
|
||||
if non_wildcard_fields is None:
|
||||
non_wildcard_fields = set()
|
||||
|
||||
# Always exclude these fields from wildcard treatment
|
||||
non_wildcard_fields.update(["created_at", "pageSize", "current"])
|
||||
|
||||
string_params = {}
|
||||
for k, v in params.items():
|
||||
if isinstance(v, str) and k not in non_wildcard_fields:
|
||||
if v != "-" and "%" not in v:
|
||||
string_params[k] = "%" + v + "%"
|
||||
else:
|
||||
string_params[k] = v
|
||||
else:
|
||||
string_params[k] = v
|
||||
|
||||
response = requests.get(f"{url}/api/audits/{endpoint}", headers=headers, params=string_params)
|
||||
response_json = check_response(response)
|
||||
|
||||
# Enhance the data with readable formats
|
||||
data = enhance_audit_data(response_json.get("data", []), endpoint)
|
||||
|
||||
return {
|
||||
"data": data,
|
||||
"total": response_json.get("total", 0),
|
||||
"current": current,
|
||||
"pageSize": page_size
|
||||
}
|
||||
|
||||
|
||||
def view_conn_audits(url, token, remote=None, conn_type=None,
|
||||
page_size=None, current=None, created_at=None, days_ago=None):
|
||||
"""View connection audits"""
|
||||
filters = {
|
||||
"remote": remote,
|
||||
"conn_type": conn_type
|
||||
}
|
||||
non_wildcard_fields = {"conn_type"}
|
||||
|
||||
return view_audits_common(
|
||||
url, token, "conn", filters, page_size, current, created_at, days_ago, non_wildcard_fields
|
||||
)
|
||||
|
||||
|
||||
def view_file_audits(url, token, remote=None,
|
||||
page_size=None, current=None, created_at=None, days_ago=None):
|
||||
"""View file audits"""
|
||||
filters = {
|
||||
"remote": remote
|
||||
}
|
||||
non_wildcard_fields = set()
|
||||
|
||||
return view_audits_common(
|
||||
url, token, "file", filters, page_size, current, created_at, days_ago, non_wildcard_fields
|
||||
)
|
||||
|
||||
|
||||
def view_alarm_audits(url, token, device=None,
|
||||
page_size=None, current=None, created_at=None, days_ago=None):
|
||||
"""View alarm audits"""
|
||||
filters = {
|
||||
"device": device
|
||||
}
|
||||
non_wildcard_fields = set()
|
||||
|
||||
return view_audits_common(
|
||||
url, token, "alarm", filters, page_size, current, created_at, days_ago, non_wildcard_fields
|
||||
)
|
||||
|
||||
|
||||
def view_console_audits(url, token, operator=None,
|
||||
page_size=None, current=None, created_at=None, days_ago=None):
|
||||
"""View console audits"""
|
||||
filters = {
|
||||
"operator": operator
|
||||
}
|
||||
non_wildcard_fields = set()
|
||||
|
||||
return view_audits_common(
|
||||
url, token, "console", filters, page_size, current, created_at, days_ago, non_wildcard_fields
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Audits manager")
|
||||
parser.add_argument(
|
||||
"command",
|
||||
choices=["view-conn", "view-file", "view-alarm", "view-console"],
|
||||
help="Command to execute",
|
||||
)
|
||||
parser.add_argument("--url", required=True, help="URL of the API")
|
||||
parser.add_argument("--token", required=True, help="Bearer token for authentication")
|
||||
|
||||
# Pagination parameters
|
||||
parser.add_argument("--page-size", type=int, default=10, help="Number of records per page (default: 10)")
|
||||
parser.add_argument("--current", type=int, default=1, help="Current page number (default: 1)")
|
||||
|
||||
# Time filtering parameters
|
||||
parser.add_argument("--created-at", help="Filter by creation time in local time (format: 2025-09-16 14:15:57 or 2025-09-16 14:15:57.000)")
|
||||
parser.add_argument("--days-ago", type=int, help="Filter by days ago (e.g., 7 for last 7 days)")
|
||||
|
||||
# Audit filters (simplified)
|
||||
parser.add_argument("--remote", help="Remote peer ID filter (for conn/file audits)")
|
||||
parser.add_argument("--device", help="Device ID filter (for alarm audits)")
|
||||
parser.add_argument("--conn-type", type=int, help="Connection type filter (for conn audits only): 0=Remote Desktop, 1=File Transfer, 2=Port Transfer, 3=View Camera, 4=Terminal")
|
||||
parser.add_argument("--operator", help="Operator filter (for console audits only)")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Remove trailing slashes from URL
|
||||
while args.url.endswith("/"):
|
||||
args.url = args.url[:-1]
|
||||
|
||||
if args.command == "view-conn":
|
||||
# View connection audits
|
||||
result = view_conn_audits(
|
||||
args.url,
|
||||
args.token,
|
||||
args.remote,
|
||||
args.conn_type,
|
||||
args.page_size,
|
||||
args.current,
|
||||
args.created_at,
|
||||
args.days_ago
|
||||
)
|
||||
print(json.dumps(result, indent=2))
|
||||
|
||||
elif args.command == "view-file":
|
||||
# View file audits
|
||||
result = view_file_audits(
|
||||
args.url,
|
||||
args.token,
|
||||
args.remote,
|
||||
args.page_size,
|
||||
args.current,
|
||||
args.created_at,
|
||||
args.days_ago
|
||||
)
|
||||
print(json.dumps(result, indent=2))
|
||||
|
||||
elif args.command == "view-alarm":
|
||||
# View alarm audits
|
||||
result = view_alarm_audits(
|
||||
args.url,
|
||||
args.token,
|
||||
args.device,
|
||||
args.page_size,
|
||||
args.current,
|
||||
args.created_at,
|
||||
args.days_ago
|
||||
)
|
||||
print(json.dumps(result, indent=2))
|
||||
|
||||
elif args.command == "view-console":
|
||||
# View console audits
|
||||
result = view_console_audits(
|
||||
args.url,
|
||||
args.token,
|
||||
args.operator,
|
||||
args.page_size,
|
||||
args.current,
|
||||
args.created_at,
|
||||
args.days_ago
|
||||
)
|
||||
print(json.dumps(result, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,3 @@
|
||||
#! /usr/bin/env bash
|
||||
sed -i "s/$1/$2/g" res/*spec res/PKGBUILD flutter/pubspec.yaml Cargo.toml .github/workflows/*yml flatpak/*json appimage/*yml libs/portable/Cargo.toml
|
||||
cargo run # to bump version in cargo lock
|
||||
Executable
+274
@@ -0,0 +1,274 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import requests
|
||||
import argparse
|
||||
import json
|
||||
|
||||
|
||||
def check_response(response):
|
||||
"""
|
||||
Check API response and handle errors.
|
||||
|
||||
Two error cases:
|
||||
1. Status code is not 200 -> exit with error
|
||||
2. Response contains {"error": "xxx"} -> exit with error
|
||||
"""
|
||||
if response.status_code != 200:
|
||||
print(f"Error: HTTP {response.status_code}: {response.text}")
|
||||
exit(1)
|
||||
|
||||
# Check for {"error": "xxx"} in response
|
||||
if response.text and response.text.strip():
|
||||
try:
|
||||
json_data = response.json()
|
||||
if isinstance(json_data, dict) and "error" in json_data:
|
||||
print(f"Error: {json_data['error']}")
|
||||
exit(1)
|
||||
return json_data
|
||||
except ValueError:
|
||||
return response.text
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def headers_with(token):
|
||||
return {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
|
||||
|
||||
|
||||
# ---------- Device Group APIs ----------
|
||||
|
||||
def list_groups(url, token, name=None, page_size=50):
|
||||
headers = headers_with(token)
|
||||
params = {"pageSize": page_size}
|
||||
if name:
|
||||
params["name"] = name
|
||||
data, current = [], 0
|
||||
while True:
|
||||
current += 1
|
||||
params["current"] = current
|
||||
r = requests.get(f"{url}/api/device-groups", headers=headers, params=params)
|
||||
if r.status_code != 200:
|
||||
print(f"Error: HTTP {r.status_code} - {r.text}")
|
||||
exit(1)
|
||||
res = r.json()
|
||||
if "error" in res:
|
||||
print(f"Error: {res['error']}")
|
||||
exit(1)
|
||||
rows = res.get("data", [])
|
||||
data.extend(rows)
|
||||
total = res.get("total", 0)
|
||||
if len(rows) < page_size or current * page_size >= total:
|
||||
break
|
||||
return data
|
||||
|
||||
|
||||
def get_group_by_name(url, token, name):
|
||||
groups = list_groups(url, token, name)
|
||||
for g in groups:
|
||||
if str(g.get("name")) == name:
|
||||
return g
|
||||
return None
|
||||
|
||||
|
||||
def create_group(url, token, name, note=None, accessed_from=None):
|
||||
headers = headers_with(token)
|
||||
payload = {"name": name}
|
||||
if note:
|
||||
payload["note"] = note
|
||||
if accessed_from:
|
||||
payload["allowed_incomings"] = accessed_from
|
||||
r = requests.post(f"{url}/api/device-groups", headers=headers, json=payload)
|
||||
return check_response(r)
|
||||
|
||||
|
||||
def update_group(url, token, name, new_name=None, note=None, accessed_from=None):
|
||||
headers = headers_with(token)
|
||||
g = get_group_by_name(url, token, name)
|
||||
if not g:
|
||||
print(f"Error: Group '{name}' not found")
|
||||
exit(1)
|
||||
guid = g.get("guid")
|
||||
payload = {}
|
||||
if new_name is not None:
|
||||
payload["name"] = new_name
|
||||
if note is not None:
|
||||
payload["note"] = note
|
||||
if accessed_from is not None:
|
||||
payload["allowed_incomings"] = accessed_from
|
||||
r = requests.patch(f"{url}/api/device-groups/{guid}", headers=headers, json=payload)
|
||||
check_response(r)
|
||||
return "Success"
|
||||
|
||||
|
||||
def delete_groups(url, token, names):
|
||||
headers = headers_with(token)
|
||||
if isinstance(names, str):
|
||||
names = [names]
|
||||
for n in names:
|
||||
g = get_group_by_name(url, token, n)
|
||||
if not g:
|
||||
print(f"Error: Group '{n}' not found")
|
||||
exit(1)
|
||||
guid = g.get("guid")
|
||||
r = requests.delete(f"{url}/api/device-groups/{guid}", headers=headers)
|
||||
check_response(r)
|
||||
return "Success"
|
||||
|
||||
|
||||
# ---------- Device group assign APIs (name -> guid) ----------
|
||||
|
||||
def view_devices(url, token, group_name=None, id=None, device_name=None,
|
||||
user_name=None, device_username=None, page_size=50):
|
||||
"""View devices in a device group with filters"""
|
||||
headers = headers_with(token)
|
||||
|
||||
# Separate exact match and fuzzy match params
|
||||
params = {}
|
||||
fuzzy_params = {
|
||||
"id": id,
|
||||
"device_name": device_name,
|
||||
"user_name": user_name,
|
||||
"device_username": device_username,
|
||||
}
|
||||
|
||||
# Add device_group_name without wildcard (exact match)
|
||||
if group_name:
|
||||
params["device_group_name"] = group_name
|
||||
|
||||
# Add wildcard for fuzzy search to other params
|
||||
for k, v in fuzzy_params.items():
|
||||
if v is not None:
|
||||
params[k] = "%" + v + "%" if (v != "-" and "%" not in v) else v
|
||||
|
||||
params["pageSize"] = page_size
|
||||
|
||||
data, current = [], 0
|
||||
while True:
|
||||
current += 1
|
||||
params["current"] = current
|
||||
r = requests.get(f"{url}/api/devices", headers=headers, params=params)
|
||||
if r.status_code != 200:
|
||||
return check_response(r)
|
||||
res = r.json()
|
||||
rows = res.get("data", [])
|
||||
data.extend(rows)
|
||||
total = res.get("total", 0)
|
||||
if len(rows) < page_size or current * page_size >= total:
|
||||
break
|
||||
return data
|
||||
|
||||
|
||||
def add_devices(url, token, group_name, device_ids):
|
||||
headers = headers_with(token)
|
||||
g = get_group_by_name(url, token, group_name)
|
||||
if not g:
|
||||
return f"Group '{group_name}' not found"
|
||||
guid = g.get("guid")
|
||||
payload = device_ids if isinstance(device_ids, list) else [device_ids]
|
||||
r = requests.post(f"{url}/api/device-groups/{guid}", headers=headers, json=payload)
|
||||
return check_response(r)
|
||||
|
||||
|
||||
def remove_devices(url, token, group_name, device_ids):
|
||||
headers = headers_with(token)
|
||||
g = get_group_by_name(url, token, group_name)
|
||||
if not g:
|
||||
return f"Group '{group_name}' not found"
|
||||
guid = g.get("guid")
|
||||
payload = device_ids if isinstance(device_ids, list) else [device_ids]
|
||||
r = requests.delete(f"{url}/api/device-groups/{guid}/devices", headers=headers, json=payload)
|
||||
return check_response(r)
|
||||
|
||||
|
||||
def parse_rules(s):
|
||||
if not s:
|
||||
return None
|
||||
try:
|
||||
v = json.loads(s)
|
||||
if isinstance(v, list):
|
||||
# expect list of {"type": number, "name": string}
|
||||
return v
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Device Group manager")
|
||||
parser.add_argument("command", choices=[
|
||||
"view", "add", "update", "delete",
|
||||
"view-devices", "add-devices", "remove-devices"
|
||||
], help=(
|
||||
"Command to execute. "
|
||||
"[view/add/update/delete/add-devices/remove-devices: require Device Group Permission] "
|
||||
"[view-devices: require Device Permission]"
|
||||
))
|
||||
parser.add_argument("--url", required=True)
|
||||
parser.add_argument("--token", required=True)
|
||||
|
||||
parser.add_argument("--name", help="Device group name (exact match)")
|
||||
parser.add_argument("--new-name", help="New device group name (for update)")
|
||||
parser.add_argument("--note", help="Note")
|
||||
|
||||
parser.add_argument("--accessed-from", help="JSON array: '[{\"type\":0|2,\"name\":\"...\"}]' (0=User Group, 2=User)")
|
||||
|
||||
parser.add_argument("--ids", help="Comma separated device IDs for add-devices/remove-devices")
|
||||
|
||||
# Filters for view-devices command
|
||||
parser.add_argument("--id", help="Device ID filter (for view-devices)")
|
||||
parser.add_argument("--device-name", help="Device name filter (for view-devices)")
|
||||
parser.add_argument("--user-name", help="User name filter (owner of device, for view-devices)")
|
||||
parser.add_argument("--device-username", help="Device username filter (logged in user on device, for view-devices)")
|
||||
|
||||
args = parser.parse_args()
|
||||
while args.url.endswith("/"): args.url = args.url[:-1]
|
||||
|
||||
if args.command == "view":
|
||||
res = list_groups(args.url, args.token, args.name)
|
||||
print(json.dumps(res, indent=2))
|
||||
elif args.command == "add":
|
||||
if not args.name:
|
||||
print("Error: --name is required")
|
||||
exit(1)
|
||||
print(create_group(
|
||||
args.url, args.token, args.name, args.note,
|
||||
parse_rules(args.accessed_from)
|
||||
))
|
||||
elif args.command == "update":
|
||||
if not args.name:
|
||||
print("Error: --name is required")
|
||||
exit(1)
|
||||
print(update_group(
|
||||
args.url, args.token, args.name, args.new_name, args.note,
|
||||
parse_rules(args.accessed_from)
|
||||
))
|
||||
elif args.command == "delete":
|
||||
if not args.name:
|
||||
print("Error: --name is required (supports comma separated)")
|
||||
exit(1)
|
||||
names = [x.strip() for x in args.name.split(",") if x.strip()]
|
||||
print(delete_groups(args.url, args.token, names))
|
||||
elif args.command == "view-devices":
|
||||
res = view_devices(
|
||||
args.url,
|
||||
args.token,
|
||||
group_name=args.name,
|
||||
id=args.id,
|
||||
device_name=args.device_name,
|
||||
user_name=args.user_name,
|
||||
device_username=args.device_username
|
||||
)
|
||||
print(json.dumps(res, indent=2))
|
||||
elif args.command in ("add-devices", "remove-devices"):
|
||||
if not args.name or not args.ids:
|
||||
print("Error: --name and --ids are required for add/remove devices")
|
||||
exit(1)
|
||||
ids = [x.strip() for x in args.ids.split(",") if x.strip()]
|
||||
if args.command == "add-devices":
|
||||
print(add_devices(args.url, args.token, args.name, ids))
|
||||
else:
|
||||
print(remove_devices(args.url, args.token, args.name, ids))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Executable
+205
@@ -0,0 +1,205 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import requests
|
||||
import argparse
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
|
||||
def view(
|
||||
url,
|
||||
token,
|
||||
id=None,
|
||||
device_name=None,
|
||||
user_name=None,
|
||||
group_name=None,
|
||||
device_group_name=None,
|
||||
offline_days=None,
|
||||
):
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
pageSize = 30
|
||||
params = {
|
||||
"id": id,
|
||||
"device_name": device_name,
|
||||
"user_name": user_name,
|
||||
"group_name": group_name,
|
||||
"device_group_name": device_group_name,
|
||||
}
|
||||
|
||||
params = {
|
||||
k: "%" + v + "%" if (v != "-" and "%" not in v) else v
|
||||
for k, v in params.items()
|
||||
if v is not None
|
||||
}
|
||||
params["pageSize"] = pageSize
|
||||
|
||||
devices = []
|
||||
|
||||
current = 0
|
||||
|
||||
while True:
|
||||
current += 1
|
||||
params["current"] = current
|
||||
response = requests.get(f"{url}/api/devices", headers=headers, params=params)
|
||||
if response.status_code != 200:
|
||||
print(f"Error: HTTP {response.status_code} - {response.text}")
|
||||
exit(1)
|
||||
|
||||
response_json = response.json()
|
||||
if "error" in response_json:
|
||||
print(f"Error: {response_json['error']}")
|
||||
exit(1)
|
||||
|
||||
data = response_json.get("data", [])
|
||||
|
||||
for device in data:
|
||||
if offline_days is None:
|
||||
devices.append(device)
|
||||
continue
|
||||
last_online = datetime.strptime(
|
||||
device["last_online"].split(".")[0], "%Y-%m-%dT%H:%M:%S"
|
||||
) # assuming date is in this format
|
||||
if (datetime.utcnow() - last_online).days >= offline_days:
|
||||
devices.append(device)
|
||||
|
||||
total = response_json.get("total", 0)
|
||||
if len(data) < pageSize or current * pageSize >= total:
|
||||
break
|
||||
|
||||
return devices
|
||||
|
||||
|
||||
def check(response):
|
||||
if response.status_code != 200:
|
||||
print(f"Error: HTTP {response.status_code} - {response.text}")
|
||||
exit(1)
|
||||
|
||||
try:
|
||||
response_json = response.json()
|
||||
if "error" in response_json:
|
||||
print(f"Error: {response_json['error']}")
|
||||
exit(1)
|
||||
return response_json
|
||||
except ValueError:
|
||||
return response.text or "Success"
|
||||
|
||||
|
||||
def disable(url, token, guid, id):
|
||||
print("Disable", id)
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.post(f"{url}/api/devices/{guid}/disable", headers=headers)
|
||||
return check(response)
|
||||
|
||||
|
||||
def enable(url, token, guid, id):
|
||||
print("Enable", id)
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.post(f"{url}/api/devices/{guid}/enable", headers=headers)
|
||||
return check(response)
|
||||
|
||||
|
||||
def delete(url, token, guid, id):
|
||||
print("Delete", id)
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.delete(f"{url}/api/devices/{guid}", headers=headers)
|
||||
return check(response)
|
||||
|
||||
|
||||
def assign(url, token, guid, id, type, value):
|
||||
print("assign", id, type, value)
|
||||
valid_types = [
|
||||
"ab",
|
||||
"strategy_name",
|
||||
"user_name",
|
||||
"device_group_name",
|
||||
"note",
|
||||
"device_username",
|
||||
"device_name",
|
||||
]
|
||||
if type not in valid_types:
|
||||
print(f"Invalid type, it must be one of: {', '.join(valid_types)}")
|
||||
return
|
||||
data = {"type": type, "value": value}
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.post(
|
||||
f"{url}/api/devices/{guid}/assign", headers=headers, json=data
|
||||
)
|
||||
return check(response)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Device manager")
|
||||
parser.add_argument(
|
||||
"command",
|
||||
choices=["view", "disable", "enable", "delete", "assign"],
|
||||
help="Command to execute",
|
||||
)
|
||||
parser.add_argument("--url", required=True, help="URL of the API")
|
||||
parser.add_argument(
|
||||
"--token", required=True, help="Bearer token for authentication"
|
||||
)
|
||||
parser.add_argument("--id", help="Device ID")
|
||||
parser.add_argument("--device_name", help="Device name")
|
||||
parser.add_argument("--user_name", help="User name")
|
||||
parser.add_argument("--group_name", help="User group name")
|
||||
parser.add_argument("--device_group_name", help="Device group name")
|
||||
parser.add_argument(
|
||||
"--assign_to",
|
||||
help="<type>=<value>, e.g. user_name=mike, strategy_name=test, device_group_name=group1, note=note1, device_username=username1, device_name=name1, ab=ab1, ab=ab1,tag1,alias1,password1,note1"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--offline_days", type=int, help="Offline duration in days, e.g., 7"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
while args.url.endswith("/"): args.url = args.url[:-1]
|
||||
|
||||
devices = view(
|
||||
args.url,
|
||||
args.token,
|
||||
args.id,
|
||||
args.device_name,
|
||||
args.user_name,
|
||||
args.group_name,
|
||||
args.device_group_name,
|
||||
args.offline_days,
|
||||
)
|
||||
|
||||
if args.command == "view":
|
||||
for device in devices:
|
||||
print(device)
|
||||
elif args.command in ["disable", "enable", "delete", "assign"]:
|
||||
# Check if we need user confirmation for multiple devices
|
||||
if len(devices) > 1:
|
||||
print(f"Found {len(devices)} devices. Do you want to proceed with {args.command} operation on the devices? (Y/N)")
|
||||
confirmation = input("Type 'Y' to confirm: ").strip()
|
||||
if confirmation.upper() != 'Y':
|
||||
print("Operation cancelled.")
|
||||
return
|
||||
|
||||
if args.command == "disable":
|
||||
for device in devices:
|
||||
response = disable(args.url, args.token, device["guid"], device["id"])
|
||||
print(response)
|
||||
elif args.command == "enable":
|
||||
for device in devices:
|
||||
response = enable(args.url, args.token, device["guid"], device["id"])
|
||||
print(response)
|
||||
elif args.command == "delete":
|
||||
for device in devices:
|
||||
response = delete(args.url, args.token, device["guid"], device["id"])
|
||||
print(response)
|
||||
elif args.command == "assign":
|
||||
if "=" not in args.assign_to:
|
||||
print("Invalid assign_to format, it must be <type>=<value>")
|
||||
return
|
||||
type, value = args.assign_to.split("=", 1)
|
||||
for device in devices:
|
||||
response = assign(
|
||||
args.url, args.token, device["guid"], device["id"], type, value
|
||||
)
|
||||
print(response)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,16 @@
|
||||
diff --git a/flutter-sdk/.gclient b/flutter-sdk/.gclient
|
||||
new file mode 100644
|
||||
index 0000000..fd12886
|
||||
--- /dev/null
|
||||
+++ b/flutter-sdk/.gclient
|
||||
@@ -0,0 +1,10 @@
|
||||
+solutions = [
|
||||
+ {
|
||||
+ "managed": False,
|
||||
+ "name": "src/flutter",
|
||||
+ "url": "https://github.com/flutter/engine.git@FLUTTER_VERSION_PLACEHOLDER",
|
||||
+ "custom_deps": {},
|
||||
+ "deps_file": "DEPS",
|
||||
+ "safesync_url": "",
|
||||
+ },
|
||||
+]
|
||||
@@ -0,0 +1,24 @@
|
||||
diff --git a/flutter/android/app/build.gradle b/flutter/android/app/build.gradle
|
||||
index f4dc69e..6b835fd 100644
|
||||
--- a/flutter/android/app/build.gradle
|
||||
+++ b/flutter/android/app/build.gradle
|
||||
@@ -67,6 +67,19 @@ android {
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules'
|
||||
}
|
||||
}
|
||||
+
|
||||
+ applicationVariants.all { variant ->
|
||||
+ variant.outputs.each { output ->
|
||||
+ output.processManifest.doLast { task ->
|
||||
+ def outputDir = multiApkManifestOutputDirectory.asFile.get()
|
||||
+ File manifestOutFile = new File(outputDir, "AndroidManifest.xml")
|
||||
+ if (manifestOutFile.exists()) {
|
||||
+ def newFileContents = manifestOutFile.getText('UTF-8').replace("android:debuggable=\"true\"", "")
|
||||
+ manifestOutFile.write(newFileContents, 'UTF-8')
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
|
||||
flutter {
|
||||
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
for size in 16 32 64 128 256 512 1024; do
|
||||
#inkscape -z -o $size.png -w $size -h $size icon.svg >/dev/null 2>/dev/null
|
||||
convert icon.png -resize ${size}x${size} app_icon_$size.png
|
||||
done
|
||||
# from ImageMagick
|
||||
convert 16.png 32.png 48.png 128.png 256.png -colors 256 icon.ico
|
||||
#/bin/rm 16.png 32.png 48.png 128.png 256.png
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 97 KiB |
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import re
|
||||
|
||||
|
||||
def strip(s): return re.sub(r'\s+\n', '\n', re.sub(r'\n\s+', '\n', s))
|
||||
|
||||
common_css = open('src/ui/common.css').read()
|
||||
common_tis = open('src/ui/common.tis', encoding='UTF8').read()
|
||||
|
||||
index = open('src/ui/index.html').read() \
|
||||
.replace('@import url(index.css);', open('src/ui/index.css').read()) \
|
||||
.replace('include "index.tis";', open('src/ui/index.tis').read()) \
|
||||
.replace('include "msgbox.tis";', open('src/ui/msgbox.tis').read()) \
|
||||
.replace('include "ab.tis";', open('src/ui/ab.tis').read())
|
||||
|
||||
remote = open('src/ui/remote.html').read() \
|
||||
.replace('@import url(remote.css);', open('src/ui/remote.css').read()) \
|
||||
.replace('@import url(header.css);', open('src/ui/header.css').read()) \
|
||||
.replace('@import url(file_transfer.css);', open('src/ui/file_transfer.css').read()) \
|
||||
.replace('include "remote.tis";', open('src/ui/remote.tis').read()) \
|
||||
.replace('include "msgbox.tis";', open('src/ui/msgbox.tis').read()) \
|
||||
.replace('include "grid.tis";', open('src/ui/grid.tis').read()) \
|
||||
.replace('include "header.tis";', open('src/ui/header.tis').read()) \
|
||||
.replace('include "file_transfer.tis";', open('src/ui/file_transfer.tis').read()) \
|
||||
.replace('include "port_forward.tis";', open('src/ui/port_forward.tis').read()) \
|
||||
.replace('include "printer.tis";', open('src/ui/printer.tis').read())
|
||||
|
||||
chatbox = open('src/ui/chatbox.html').read()
|
||||
install = open('src/ui/install.html').read().replace('include "install.tis";', open('src/ui/install.tis').read())
|
||||
|
||||
cm = open('src/ui/cm.html').read() \
|
||||
.replace('@import url(cm.css);', open('src/ui/cm.css').read()) \
|
||||
.replace('include "cm.tis";', open('src/ui/cm.tis').read())
|
||||
|
||||
|
||||
def compress(s):
|
||||
s = s.replace("\r\n", "\n")
|
||||
x = bytes(s, encoding='utf-8')
|
||||
return '&[u8; ' + str(len(x)) + '] = b"' + str(x)[2:-1].replace(r"\'", "'").replace(r'"',
|
||||
r'\"') + '"'
|
||||
|
||||
|
||||
with open('src/ui/inline.rs', 'wt') as fh:
|
||||
fh.write('const _COMMON_CSS: ' + compress(strip(common_css)) + ';\n')
|
||||
fh.write('const _COMMON_TIS: ' + compress(strip(common_tis)) + ';\n')
|
||||
fh.write('const _INDEX: ' + compress(strip(index)) + ';\n')
|
||||
fh.write('const _REMOTE: ' + compress(strip(remote)) + ';\n')
|
||||
fh.write('const _CHATBOX: ' + compress(strip(chatbox)) + ';\n')
|
||||
fh.write('const _INSTALL: ' + compress(strip(install)) + ';\n')
|
||||
fh.write('const _CONNECTION_MANAGER: ' + compress(strip(cm)) + ';\n')
|
||||
fh.write('''
|
||||
fn get(data: &[u8]) -> String {
|
||||
String::from_utf8_lossy(data).to_string()
|
||||
}
|
||||
fn replace(data: &[u8]) -> String {
|
||||
let css = get(&_COMMON_CSS[..]);
|
||||
let res = get(data).replace("@import url(common.css);", &css);
|
||||
let tis = get(&_COMMON_TIS[..]);
|
||||
res.replace("include \\\"common.tis\\\";", &tis)
|
||||
}
|
||||
#[inline]
|
||||
pub fn get_index() -> String {
|
||||
replace(&_INDEX[..])
|
||||
}
|
||||
#[inline]
|
||||
pub fn get_remote() -> String {
|
||||
replace(&_REMOTE[..])
|
||||
}
|
||||
#[inline]
|
||||
pub fn get_install() -> String {
|
||||
replace(&_INSTALL[..])
|
||||
}
|
||||
#[inline]
|
||||
pub fn get_chatbox() -> String {
|
||||
replace(&_CHATBOX[..])
|
||||
}
|
||||
#[inline]
|
||||
pub fn get_cm() -> String {
|
||||
replace(&_CONNECTION_MANAGER[..])
|
||||
}
|
||||
''')
|
||||
Executable
+321
@@ -0,0 +1,321 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import requests
|
||||
import os
|
||||
import time
|
||||
import argparse
|
||||
import logging
|
||||
import shutil
|
||||
import zipfile
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s [%(levelname)s] %(message)s [%(filename)s:%(lineno)d]",
|
||||
handlers=[logging.StreamHandler()],
|
||||
)
|
||||
|
||||
# The URL of your Flask server
|
||||
BASE_URL = os.getenv("BASE_URL") or "http://localhost:5000"
|
||||
|
||||
# The secret key for API authentication
|
||||
SECRET_KEY = os.getenv("SECRET_KEY") or "worldpeace2024"
|
||||
|
||||
# The headers for API requests
|
||||
HEADERS = {"Authorization": f"Bearer {SECRET_KEY}"}
|
||||
|
||||
SIGN_TIMEOUT = int(os.getenv("SIGN_TIMEOUT") or "30")
|
||||
TIMEOUT = float(os.getenv("TIMEOUT") or "900")
|
||||
|
||||
|
||||
def create(task_name, file_path=None):
|
||||
if file_path is None:
|
||||
response = requests.post(
|
||||
f"{BASE_URL}/tasks/{task_name}", timeout=TIMEOUT, headers=HEADERS
|
||||
)
|
||||
else:
|
||||
with open(file_path, "rb") as f:
|
||||
files = {"file": f}
|
||||
response = requests.post(
|
||||
f"{BASE_URL}/tasks/{task_name}",
|
||||
timeout=TIMEOUT,
|
||||
headers=HEADERS,
|
||||
files=files,
|
||||
)
|
||||
return get_json(response)
|
||||
|
||||
|
||||
def upload_file(task_id, file_path):
|
||||
with open(file_path, "rb") as f:
|
||||
files = {"file": f}
|
||||
response = requests.post(
|
||||
f"{BASE_URL}/tasks/{task_id}/files",
|
||||
timeout=TIMEOUT,
|
||||
headers=HEADERS,
|
||||
files=files,
|
||||
)
|
||||
return get_json(response)
|
||||
|
||||
|
||||
def get_status(task_id):
|
||||
response = requests.get(
|
||||
f"{BASE_URL}/tasks/{task_id}/status", timeout=TIMEOUT, headers=HEADERS
|
||||
)
|
||||
return get_json(response)
|
||||
|
||||
|
||||
def download_files(task_id, output_dir, fn=None):
|
||||
response = requests.get(
|
||||
f"{BASE_URL}/tasks/{task_id}/files",
|
||||
timeout=TIMEOUT,
|
||||
headers=HEADERS,
|
||||
stream=True,
|
||||
)
|
||||
|
||||
# Check if the request was successful
|
||||
if fn is None:
|
||||
fn = f"task_{task_id}_files.zip"
|
||||
if response.status_code == 200:
|
||||
# Save the file to the output directory
|
||||
with open(os.path.join(output_dir, fn), "wb") as f:
|
||||
for chunk in response.iter_content(chunk_size=1024):
|
||||
if chunk:
|
||||
f.write(chunk)
|
||||
return response.ok
|
||||
|
||||
|
||||
def download_one_file(task_id, file_id, output_dir):
|
||||
response = requests.get(
|
||||
f"{BASE_URL}/tasks/{task_id}/files/{file_id}",
|
||||
timeout=TIMEOUT,
|
||||
headers=HEADERS,
|
||||
stream=True,
|
||||
)
|
||||
|
||||
# Check if the request was successful
|
||||
if response.status_code == 200:
|
||||
# Save the file to the output directory
|
||||
with open(os.path.join(output_dir, file_id), "wb") as f:
|
||||
for chunk in response.iter_content(chunk_size=1024):
|
||||
if chunk:
|
||||
f.write(chunk)
|
||||
return response.ok
|
||||
|
||||
|
||||
def fetch(tag=None):
|
||||
response = requests.get(
|
||||
f"{BASE_URL}/tasks/fetch_task" + ("?tag=%s" % tag if tag else ""),
|
||||
timeout=TIMEOUT,
|
||||
headers=HEADERS,
|
||||
)
|
||||
return get_json(response)
|
||||
|
||||
|
||||
def update_status(task_id, status):
|
||||
response = requests.patch(
|
||||
f"{BASE_URL}/tasks/{task_id}/status",
|
||||
timeout=TIMEOUT,
|
||||
headers=HEADERS,
|
||||
json=status,
|
||||
)
|
||||
return get_json(response)
|
||||
|
||||
|
||||
def delete_task(task_id):
|
||||
response = requests.delete(
|
||||
f"{BASE_URL}/tasks/{task_id}",
|
||||
timeout=TIMEOUT,
|
||||
headers=HEADERS,
|
||||
)
|
||||
return get_json(response)
|
||||
|
||||
|
||||
def sign(file_path):
|
||||
res = create("sign", file_path)
|
||||
if res.ok:
|
||||
task_id = res.task_id
|
||||
|
||||
# Poll the status every second
|
||||
while True:
|
||||
status = get_status(task_id)
|
||||
if status["status"] == "done":
|
||||
# Download the files
|
||||
download_files(task_id, "output")
|
||||
|
||||
# Delete the task
|
||||
delete_task(task_id)
|
||||
|
||||
break
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
def sign_one_file(file_path):
|
||||
logging.info(f"Signing {file_path}")
|
||||
res = create("sign", file_path)
|
||||
logging.info(f"Uploaded {file_path}")
|
||||
task_id = res["id"]
|
||||
n = 0
|
||||
while True:
|
||||
if n >= SIGN_TIMEOUT:
|
||||
delete_task(task_id)
|
||||
logging.error(f"Failed to sign {file_path}")
|
||||
break
|
||||
time.sleep(6)
|
||||
n += 1
|
||||
status = get_status(task_id)
|
||||
if status and status.get("state") == "done":
|
||||
download_one_file(
|
||||
task_id, os.path.basename(file_path), os.path.dirname(file_path)
|
||||
)
|
||||
delete_task(task_id)
|
||||
logging.info(f"Signed {file_path}")
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def get_json(response):
|
||||
try:
|
||||
return response.json()
|
||||
except Exception as e:
|
||||
raise Exception(response.text)
|
||||
|
||||
|
||||
SIGN_EXTENSIONS = [
|
||||
".dll",
|
||||
".exe",
|
||||
".sys",
|
||||
".vxd",
|
||||
".msix",
|
||||
".msixbundle",
|
||||
".appx",
|
||||
".appxbundle",
|
||||
".msi",
|
||||
".msp",
|
||||
".msm",
|
||||
".cab",
|
||||
".ps1",
|
||||
".psm1",
|
||||
]
|
||||
|
||||
|
||||
def sign_files(dir_path, only_ext=None):
|
||||
if only_ext:
|
||||
only_ext = only_ext.split(",")
|
||||
for i in range(len(only_ext)):
|
||||
if not only_ext[i].startswith("."):
|
||||
only_ext[i] = "." + only_ext[i]
|
||||
for root, dirs, files in os.walk(dir_path):
|
||||
is_signed_dir = "RustDeskPrinterDriver" in root or "usbmmidd_v2" in root
|
||||
for file in files:
|
||||
file_path = os.path.join(root, file)
|
||||
_, ext = os.path.splitext(file_path)
|
||||
# only sign the exe files in signed dirs
|
||||
if is_signed_dir and ext not in [".exe"]:
|
||||
continue
|
||||
if only_ext and ext not in only_ext:
|
||||
continue
|
||||
if ext in SIGN_EXTENSIONS:
|
||||
if not sign_one_file(file_path):
|
||||
logging.error(f"Failed to sign {file_path}")
|
||||
break
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Command line interface for task operations."
|
||||
)
|
||||
subparsers = parser.add_subparsers(dest="command")
|
||||
|
||||
# Create a parser for the "sign_one_file" command
|
||||
sign_one_file_parser = subparsers.add_parser(
|
||||
"sign_one_file", help="Sign a single file."
|
||||
)
|
||||
sign_one_file_parser.add_argument("file_path", help="The path of the file to sign.")
|
||||
|
||||
# Create a parser for the "sign_files" command
|
||||
sign_files_parser = subparsers.add_parser(
|
||||
"sign_files", help="Sign all files in a directory."
|
||||
)
|
||||
sign_files_parser.add_argument(
|
||||
"dir_path", help="The path of the directory containing the files to sign."
|
||||
)
|
||||
sign_files_parser.add_argument(
|
||||
"only_ext", help="The file extension to sign.", default=None, nargs="?"
|
||||
)
|
||||
|
||||
# Create a parser for the "fetch" command
|
||||
fetch_parser = subparsers.add_parser("fetch", help="Fetch a task.")
|
||||
|
||||
# Create a parser for the "update_status" command
|
||||
update_status_parser = subparsers.add_parser(
|
||||
"update_status", help="Update the status of a task."
|
||||
)
|
||||
update_status_parser.add_argument("task_id", help="The ID of the task to update.")
|
||||
update_status_parser.add_argument("status", help="The new status of the task.")
|
||||
|
||||
# Create a parser for the "delete_task" command
|
||||
delete_task_parser = subparsers.add_parser("delete_task", help="Delete a task.")
|
||||
delete_task_parser.add_argument("task_id", help="The ID of the task to delete.")
|
||||
|
||||
# Create a parser for the "create" command
|
||||
create_parser = subparsers.add_parser("create", help="Create a task.")
|
||||
create_parser.add_argument("task_name", help="The name of the task to create.")
|
||||
create_parser.add_argument(
|
||||
"file_path",
|
||||
help="The path of the file for the task.",
|
||||
default=None,
|
||||
nargs="?",
|
||||
)
|
||||
|
||||
# Create a parser for the "upload_file" command
|
||||
upload_file_parser = subparsers.add_parser(
|
||||
"upload_file", help="Upload a file to a task."
|
||||
)
|
||||
upload_file_parser.add_argument(
|
||||
"task_id", help="The ID of the task to upload the file to."
|
||||
)
|
||||
upload_file_parser.add_argument("file_path", help="The path of the file to upload.")
|
||||
|
||||
# Create a parser for the "get_status" command
|
||||
get_status_parser = subparsers.add_parser(
|
||||
"get_status", help="Get the status of a task."
|
||||
)
|
||||
get_status_parser.add_argument(
|
||||
"task_id", help="The ID of the task to get the status of."
|
||||
)
|
||||
|
||||
# Create a parser for the "download_files" command
|
||||
download_files_parser = subparsers.add_parser(
|
||||
"download_files", help="Download files from a task."
|
||||
)
|
||||
download_files_parser.add_argument(
|
||||
"task_id", help="The ID of the task to download files from."
|
||||
)
|
||||
download_files_parser.add_argument(
|
||||
"output_dir", help="The directory to save the downloaded files to."
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.command == "sign_one_file":
|
||||
sign_one_file(args.file_path)
|
||||
elif args.command == "sign_files":
|
||||
sign_files(args.dir_path, args.only_ext)
|
||||
elif args.command == "fetch":
|
||||
print(fetch())
|
||||
elif args.command == "update_status":
|
||||
print(update_status(args.task_id, args.status))
|
||||
elif args.command == "delete_task":
|
||||
print(delete_task(args.task_id))
|
||||
elif args.command == "create":
|
||||
print(create(args.task_name, args.file_path))
|
||||
elif args.command == "upload_file":
|
||||
print(upload_file(args.task_id, args.file_path))
|
||||
elif args.command == "get_status":
|
||||
print(get_status(args.task_id))
|
||||
elif args.command == "download_files":
|
||||
print(download_files(args.task_id, args.output_dir))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import glob
|
||||
import sys
|
||||
import csv
|
||||
|
||||
|
||||
def get_lang(lang):
|
||||
out = {}
|
||||
for ln in open('./src/lang/%s.rs' % lang, encoding='utf8'):
|
||||
ln = ln.strip()
|
||||
if ln.startswith('("'):
|
||||
k, v = line_split(ln)
|
||||
out[k] = v
|
||||
return out
|
||||
|
||||
|
||||
def line_split(line):
|
||||
toks = line.split('", "')
|
||||
if len(toks) != 2:
|
||||
print(line)
|
||||
assert 0
|
||||
# Replace fixed position.
|
||||
# Because toks[1] may be v") or v"),
|
||||
k = toks[0][toks[0].find('"') + 1:]
|
||||
v = toks[1][:toks[1].rfind('"')]
|
||||
return k, v
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) == 1:
|
||||
expand()
|
||||
elif sys.argv[1] == '1':
|
||||
to_csv()
|
||||
else:
|
||||
to_rs(sys.argv[1])
|
||||
|
||||
|
||||
def expand():
|
||||
for fn in glob.glob('./src/lang/*.rs'):
|
||||
lang = os.path.basename(fn)[:-3]
|
||||
if lang in ['en', 'template']: continue
|
||||
print(lang)
|
||||
dict = get_lang(lang)
|
||||
fw = open("./src/lang/%s.rs" % lang, "wt", encoding='utf8')
|
||||
for line in open('./src/lang/template.rs', encoding='utf8'):
|
||||
line_strip = line.strip()
|
||||
if line_strip.startswith('("'):
|
||||
k, v = line_split(line_strip)
|
||||
if k in dict:
|
||||
# embraced with " to avoid empty v
|
||||
line = line.replace('"%s"' % v, '"%s"' % dict[k])
|
||||
else:
|
||||
line = line.replace(v, "")
|
||||
fw.write(line)
|
||||
else:
|
||||
fw.write(line)
|
||||
fw.close()
|
||||
|
||||
|
||||
def to_csv():
|
||||
for fn in glob.glob('./src/lang/*.rs'):
|
||||
lang = os.path.basename(fn)[:-3]
|
||||
csvfile = open('./src/lang/%s.csv' % lang, "wt", encoding='utf8')
|
||||
csvwriter = csv.writer(csvfile)
|
||||
for line in open(fn, encoding='utf8'):
|
||||
line_strip = line.strip()
|
||||
if line_strip.startswith('("'):
|
||||
k, v = line_split(line_strip)
|
||||
csvwriter.writerow([k, v])
|
||||
csvfile.close()
|
||||
|
||||
|
||||
def to_rs(lang):
|
||||
csvfile = open('%s.csv' % lang, "rt", encoding='utf8')
|
||||
fw = open("./src/lang/%s.rs" % lang, "wt", encoding='utf8')
|
||||
fw.write('''lazy_static::lazy_static! {
|
||||
pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
[
|
||||
''')
|
||||
for row in csv.reader(csvfile):
|
||||
fw.write(' ("%s", "%s"),\n' % (row[0].replace('"', '\"'), row[1].replace('"', '\"')))
|
||||
fw.write(''' ].iter().cloned().collect();
|
||||
}
|
||||
''')
|
||||
fw.close()
|
||||
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,36 @@
|
||||
<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<asmv3:application xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
||||
<asmv3:windowsSettings
|
||||
xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
|
||||
</asmv3:windowsSettings>
|
||||
</asmv3:application>
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- Windows 10 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||
<!-- Windows 8.1 -->
|
||||
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
|
||||
<!-- Windows Vista -->
|
||||
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
|
||||
<!-- Windows 7 -->
|
||||
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
|
||||
<!-- Windows 8 -->
|
||||
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
|
||||
</application>
|
||||
</compatibility>
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity
|
||||
type="win32"
|
||||
name="Microsoft.Windows.Common-Controls"
|
||||
version="6.0.0.0"
|
||||
processorArchitecture="*"
|
||||
publicKeyToken="6595b64144ccf1df"
|
||||
language="*"
|
||||
/>
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
</assembly>
|
||||
@@ -0,0 +1,13 @@
|
||||
.vs
|
||||
|
||||
**/bin
|
||||
**/obj
|
||||
|
||||
x64
|
||||
packages
|
||||
|
||||
CustomActions/x64
|
||||
CustomActions/*.user
|
||||
CustomActions/*.filters
|
||||
|
||||
Package/Resources
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <Windows.h>
|
||||
#include <string>
|
||||
|
||||
bool AddFirewallRule(bool add, LPWSTR exeName, LPWSTR exeFile);
|
||||
|
||||
bool QueryServiceStatusExW(LPCWSTR serviceName, SERVICE_STATUS_PROCESS* status);
|
||||
bool IsServiceRunningW(LPCWSTR serviceName);
|
||||
bool MyCreateServiceW(LPCWSTR serviceName, LPCWSTR displayName, LPCWSTR binaryPath);
|
||||
bool MyDeleteServiceW(LPCWSTR serviceName);
|
||||
bool MyStartServiceW(LPCWSTR serviceName);
|
||||
bool MyStopServiceW(LPCWSTR serviceName);
|
||||
|
||||
std::wstring ReadConfig(const std::wstring& filename, const std::wstring& key);
|
||||
|
||||
void UninstallDriver(LPCWSTR hardwareId, BOOL &rebootRequired);
|
||||
|
||||
namespace RemotePrinter
|
||||
{
|
||||
VOID installUpdatePrinter(const std::wstring& installFolder);
|
||||
VOID uninstallPrinter();
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,16 @@
|
||||
LIBRARY "CustomActions"
|
||||
|
||||
EXPORTS
|
||||
CustomActionHello
|
||||
RemoveRuntimeGeneratedFiles
|
||||
TerminateProcesses
|
||||
AddFirewallRules
|
||||
SetPropertyIsServiceRunning
|
||||
TryStopDeleteService
|
||||
CreateStartService
|
||||
TryDeleteStartupShortcut
|
||||
SetPropertyFromConfig
|
||||
AddRegSoftwareSASGeneration
|
||||
RemoveAmyuniIdd
|
||||
InstallPrinter
|
||||
UninstallPrinter
|
||||
@@ -0,0 +1,86 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\packages\WixToolset.WcaUtil.4.0.5\build\WixToolset.WcaUtil.props" Condition="Exists('..\packages\WixToolset.WcaUtil.4.0.5\build\WixToolset.WcaUtil.props')" />
|
||||
<Import Project="..\packages\WixToolset.DUtil.4.0.5\build\WixToolset.DUtil.props" Condition="Exists('..\packages\WixToolset.DUtil.4.0.5\build\WixToolset.DUtil.props')" />
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{6b3647e0-b4a3-46ae-8757-a22ee51c1dac}</ProjectGuid>
|
||||
<RootNamespace>CustomActions</RootNamespace>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;EXAMPLECADLL_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>msi.lib;version.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableUAC>false</EnableUAC>
|
||||
<ModuleDefinitionFile>CustomActions.def</ModuleDefinitionFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="Common.h" />
|
||||
<ClInclude Include="framework.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="CustomActions.cpp" />
|
||||
<ClCompile Include="DeviceUtils.cpp" />
|
||||
<ClCompile Include="dllmain.cpp" />
|
||||
<ClCompile Include="FirewallRules.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ReadConfig.cpp" />
|
||||
<ClCompile Include="RemotePrinter.cpp" />
|
||||
<ClCompile Include="ServiceUtils.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="CustomActions.def" />
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\packages\WixToolset.DUtil.4.0.5\build\WixToolset.DUtil.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\WixToolset.DUtil.4.0.5\build\WixToolset.DUtil.props'))" />
|
||||
<Error Condition="!Exists('..\packages\WixToolset.WcaUtil.4.0.5\build\WixToolset.WcaUtil.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\WixToolset.WcaUtil.4.0.5\build\WixToolset.WcaUtil.props'))" />
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -0,0 +1,84 @@
|
||||
#include "pch.h"
|
||||
|
||||
#include <Windows.h>
|
||||
#include <setupapi.h>
|
||||
#include <devguid.h>
|
||||
#include <cfgmgr32.h>
|
||||
|
||||
#pragma comment(lib, "SetupAPI.lib")
|
||||
|
||||
|
||||
void UninstallDriver(LPCWSTR hardwareId, BOOL &rebootRequired)
|
||||
{
|
||||
HDEVINFO deviceInfoSet = SetupDiGetClassDevsW(&GUID_DEVCLASS_DISPLAY, NULL, NULL, DIGCF_PRESENT);
|
||||
if (deviceInfoSet == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to get device information set, last error: %d", GetLastError());
|
||||
return;
|
||||
}
|
||||
|
||||
SP_DEVINFO_LIST_DETAIL_DATA devInfoListDetail;
|
||||
devInfoListDetail.cbSize = sizeof(SP_DEVINFO_LIST_DETAIL_DATA);
|
||||
if (!SetupDiGetDeviceInfoListDetailW(deviceInfoSet, &devInfoListDetail))
|
||||
{
|
||||
SetupDiDestroyDeviceInfoList(deviceInfoSet);
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to call SetupDiGetDeviceInfoListDetail, last error: %d", GetLastError());
|
||||
return;
|
||||
}
|
||||
|
||||
SP_DEVINFO_DATA deviceInfoData;
|
||||
deviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
|
||||
|
||||
DWORD dataType;
|
||||
WCHAR deviceId[MAX_DEVICE_ID_LEN] = { 0, };
|
||||
|
||||
DWORD deviceIndex = 0;
|
||||
while (SetupDiEnumDeviceInfo(deviceInfoSet, deviceIndex, &deviceInfoData))
|
||||
{
|
||||
if (!SetupDiGetDeviceRegistryPropertyW(deviceInfoSet, &deviceInfoData, SPDRP_HARDWAREID, &dataType, (PBYTE)deviceId, MAX_DEVICE_ID_LEN, NULL))
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to get hardware id, last error: %d", GetLastError());
|
||||
deviceIndex++;
|
||||
continue;
|
||||
}
|
||||
if (wcscmp(deviceId, hardwareId) != 0)
|
||||
{
|
||||
deviceIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
SP_REMOVEDEVICE_PARAMS remove_device_params;
|
||||
remove_device_params.ClassInstallHeader.cbSize = sizeof(SP_CLASSINSTALL_HEADER);
|
||||
remove_device_params.ClassInstallHeader.InstallFunction = DIF_REMOVE;
|
||||
remove_device_params.Scope = DI_REMOVEDEVICE_GLOBAL;
|
||||
remove_device_params.HwProfile = 0;
|
||||
|
||||
if (!SetupDiSetClassInstallParamsW(deviceInfoSet, &deviceInfoData, &remove_device_params.ClassInstallHeader, sizeof(SP_REMOVEDEVICE_PARAMS)))
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to set class install params, last error: %d", GetLastError());
|
||||
deviceIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!SetupDiCallClassInstaller(DIF_REMOVE, deviceInfoSet, &deviceInfoData))
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "ailed to uninstall driver, last error: %d", GetLastError());
|
||||
deviceIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
SP_DEVINSTALL_PARAMS deviceParams;
|
||||
if (SetupDiGetDeviceInstallParamsW(deviceInfoSet, &deviceInfoData, &deviceParams))
|
||||
{
|
||||
if (deviceParams.Flags & (DI_NEEDRESTART | DI_NEEDREBOOT))
|
||||
{
|
||||
rebootRequired = true;
|
||||
}
|
||||
}
|
||||
|
||||
WcaLog(LOGMSG_STANDARD, "Driver uninstalled successfully");
|
||||
deviceIndex++;
|
||||
}
|
||||
|
||||
SetupDiDestroyDeviceInfoList(deviceInfoSet);
|
||||
}
|
||||
@@ -0,0 +1,413 @@
|
||||
// https://learn.microsoft.com/en-us/previous-versions/windows/desktop/ics/c-adding-an-application-rule-edge-traversal
|
||||
|
||||
/********************************************************************
|
||||
Copyright (C) Microsoft. All Rights Reserved.
|
||||
|
||||
Abstract:
|
||||
This C++ file includes sample code that adds a firewall rule with
|
||||
EdgeTraversalOptions (one of the EdgeTraversalOptions values).
|
||||
|
||||
********************************************************************/
|
||||
|
||||
#include "pch.h"
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <netfw.h>
|
||||
#include <strsafe.h>
|
||||
|
||||
#pragma comment(lib, "ole32.lib")
|
||||
#pragma comment(lib, "oleaut32.lib")
|
||||
|
||||
#define STRING_BUFFER_SIZE 500
|
||||
|
||||
|
||||
// Forward declarations
|
||||
HRESULT WFCOMInitialize(INetFwPolicy2** ppNetFwPolicy2);
|
||||
void WFCOMCleanup(INetFwPolicy2* pNetFwPolicy2);
|
||||
HRESULT RemoveFirewallRule(
|
||||
__in INetFwPolicy2* pNetFwPolicy2,
|
||||
__in LPWSTR exeName);
|
||||
HRESULT AddFirewallRuleWithEdgeTraversal(__in INetFwPolicy2* pNetFwPolicy2,
|
||||
__in bool in,
|
||||
__in LPWSTR exeName,
|
||||
__in LPWSTR exeFile);
|
||||
|
||||
|
||||
bool AddFirewallRule(bool add, LPWSTR exeName, LPWSTR exeFile)
|
||||
{
|
||||
bool result = false;
|
||||
HRESULT hrComInit = S_OK;
|
||||
HRESULT hr = S_OK;
|
||||
INetFwPolicy2* pNetFwPolicy2 = NULL;
|
||||
|
||||
// Initialize COM.
|
||||
hrComInit = CoInitializeEx(
|
||||
0,
|
||||
COINIT_APARTMENTTHREADED
|
||||
);
|
||||
|
||||
// Ignore RPC_E_CHANGED_MODE; this just means that COM has already been
|
||||
// initialized with a different mode. Since we don't care what the mode is,
|
||||
// we'll just use the existing mode.
|
||||
if (hrComInit != RPC_E_CHANGED_MODE)
|
||||
{
|
||||
if (FAILED(hrComInit))
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "CoInitializeEx failed: 0x%08lx\n", hrComInit);
|
||||
goto Cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve INetFwPolicy2
|
||||
hr = WFCOMInitialize(&pNetFwPolicy2);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
if (add) {
|
||||
// Add firewall rule with EdgeTraversalOption=DeferApp (Windows7+) if available
|
||||
// else add with Edge=True (Vista and Server 2008).
|
||||
hr = AddFirewallRuleWithEdgeTraversal(pNetFwPolicy2, true, exeName, exeFile);
|
||||
hr = AddFirewallRuleWithEdgeTraversal(pNetFwPolicy2, false, exeName, exeFile);
|
||||
}
|
||||
else {
|
||||
hr = RemoveFirewallRule(pNetFwPolicy2, exeName);
|
||||
}
|
||||
result = SUCCEEDED(hr);
|
||||
|
||||
Cleanup:
|
||||
|
||||
// Release INetFwPolicy2
|
||||
WFCOMCleanup(pNetFwPolicy2);
|
||||
|
||||
// Uninitialize COM.
|
||||
if (SUCCEEDED(hrComInit))
|
||||
{
|
||||
CoUninitialize();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
BSTR MakeRuleName(__in LPWSTR exeName)
|
||||
{
|
||||
WCHAR pwszTemp[STRING_BUFFER_SIZE] = L"";
|
||||
HRESULT hr = StringCchPrintfW(pwszTemp, STRING_BUFFER_SIZE, L"%ls Service", exeName);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to compose a resource identifier string: 0x%08lx\n", hr);
|
||||
return NULL;
|
||||
}
|
||||
return SysAllocString(pwszTemp);
|
||||
}
|
||||
|
||||
HRESULT RemoveFirewallRule(
|
||||
__in INetFwPolicy2* pNetFwPolicy2,
|
||||
__in LPWSTR exeName)
|
||||
{
|
||||
HRESULT hr = S_OK;
|
||||
INetFwRules* pNetFwRules = NULL;
|
||||
|
||||
WCHAR pwszTemp[STRING_BUFFER_SIZE] = L"";
|
||||
|
||||
BSTR RuleName = NULL;
|
||||
|
||||
RuleName = MakeRuleName(exeName);
|
||||
if (NULL == RuleName)
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "\nERROR: Insufficient memory\n");
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
hr = pNetFwPolicy2->get_Rules(&pNetFwRules);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to retrieve firewall rules collection : 0x%08lx\n", hr);
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
// We need to "Remove()" twice, because both "in" and "out" rules are added?
|
||||
// There's no remarks for this case https://learn.microsoft.com/en-us/windows/win32/api/netfw/nf-netfw-inetfwrules-remove
|
||||
hr = pNetFwRules->Remove(RuleName);
|
||||
hr = pNetFwRules->Remove(RuleName);
|
||||
if (FAILED(hr)) {
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to remove firewall rule \"%ls\" : 0x%08lx\n", exeName, hr);
|
||||
}
|
||||
else {
|
||||
WcaLog(LOGMSG_STANDARD, "Firewall rule \"%ls\" is removed\n", exeName);
|
||||
}
|
||||
|
||||
Cleanup:
|
||||
|
||||
SysFreeString(RuleName);
|
||||
|
||||
if (pNetFwRules != NULL)
|
||||
{
|
||||
pNetFwRules->Release();
|
||||
}
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
// Add firewall rule with EdgeTraversalOption=DeferApp (Windows7+) if available
|
||||
// else add with Edge=True (Vista and Server 2008).
|
||||
HRESULT AddFirewallRuleWithEdgeTraversal(
|
||||
__in INetFwPolicy2* pNetFwPolicy2,
|
||||
__in bool in,
|
||||
__in LPWSTR exeName,
|
||||
__in LPWSTR exeFile)
|
||||
{
|
||||
HRESULT hr = S_OK;
|
||||
INetFwRules* pNetFwRules = NULL;
|
||||
|
||||
INetFwRule* pNetFwRule = NULL;
|
||||
INetFwRule2* pNetFwRule2 = NULL;
|
||||
|
||||
WCHAR pwszTemp[STRING_BUFFER_SIZE] = L"";
|
||||
|
||||
BSTR RuleName = NULL;
|
||||
BSTR RuleGroupName = NULL;
|
||||
BSTR RuleDescription = NULL;
|
||||
BSTR RuleAppPath = NULL;
|
||||
|
||||
long CurrentProfilesBitMask = 0;
|
||||
|
||||
|
||||
// For localization purposes, the rule name, description, and group can be
|
||||
// provided as indirect strings. These indirect strings can be defined in an rc file.
|
||||
// Examples of the indirect string definitions in the rc file -
|
||||
// 127 "EdgeTraversalOptions Sample Application"
|
||||
// 128 "Allow inbound TCP traffic to application EdgeTraversalOptions.exe"
|
||||
// 129 "Allow EdgeTraversalOptions.exe to receive inbound traffic for TCP protocol
|
||||
// from remote machines located within your network as well as from
|
||||
// the Internet (i.e from outside of your Edge device like Firewall or NAT"
|
||||
|
||||
|
||||
// Examples of using indirect strings -
|
||||
// hr = StringCchPrintfW(pwszTemp, STRING_BUFFER_SIZE, L"@EdgeTraversalOptions.exe,-128");
|
||||
RuleName = MakeRuleName(exeName);
|
||||
if (NULL == RuleName)
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "\nERROR: Insufficient memory\n");
|
||||
goto Cleanup;
|
||||
}
|
||||
// Examples of using indirect strings -
|
||||
// hr = StringCchPrintfW(pwszTemp, STRING_BUFFER_SIZE, L"@EdgeTraversalOptions.exe,-127");
|
||||
hr = StringCchPrintfW(pwszTemp, STRING_BUFFER_SIZE, exeName);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to compose a resource identifier string: 0x%08lx\n", hr);
|
||||
goto Cleanup;
|
||||
}
|
||||
RuleGroupName = SysAllocString(pwszTemp); // Used for grouping together multiple rules
|
||||
if (NULL == RuleGroupName)
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "\nERROR: Insufficient memory\n");
|
||||
goto Cleanup;
|
||||
}
|
||||
// Examples of using indirect strings -
|
||||
// hr = StringCchPrintfW(pwszTemp, STRING_BUFFER_SIZE, L"@EdgeTraversalOptions.exe,-129");
|
||||
hr = StringCchPrintfW(pwszTemp, STRING_BUFFER_SIZE, L"Allow %ls to receive \
|
||||
inbound traffic from remote machines located within your network as well as \
|
||||
from the Internet", exeName);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to compose a resource identifier string: 0x%08lx\n", hr);
|
||||
goto Cleanup;
|
||||
}
|
||||
RuleDescription = SysAllocString(pwszTemp);
|
||||
if (NULL == RuleDescription)
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "\nERROR: Insufficient memory\n");
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
RuleAppPath = SysAllocString(exeFile);
|
||||
if (NULL == RuleAppPath)
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "\nERROR: Insufficient memory\n");
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
hr = pNetFwPolicy2->get_Rules(&pNetFwRules);
|
||||
|
||||
if (FAILED(hr))
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to retrieve firewall rules collection : 0x%08lx\n", hr);
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
hr = CoCreateInstance(
|
||||
__uuidof(NetFwRule), //CLSID of the class whose object is to be created
|
||||
NULL,
|
||||
CLSCTX_INPROC_SERVER,
|
||||
__uuidof(INetFwRule), // Identifier of the Interface used for communicating with the object
|
||||
(void**)&pNetFwRule);
|
||||
|
||||
if (FAILED(hr))
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "CoCreateInstance for INetFwRule failed: 0x%08lx\n", hr);
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
hr = pNetFwRule->put_Name(RuleName);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Failed INetFwRule::put_Name failed with error: 0x %x.\n", hr);
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
hr = pNetFwRule->put_Grouping(RuleGroupName);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Failed INetFwRule::put_Grouping failed with error: 0x %x.\n", hr);
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
hr = pNetFwRule->put_Description(RuleDescription);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Failed INetFwRule::put_Description failed with error: 0x %x.\n", hr);
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
// If you want the rule to avoid public, you can refer to
|
||||
// https://learn.microsoft.com/en-us/previous-versions/windows/desktop/ics/c-adding-an-outbound-rule
|
||||
CurrentProfilesBitMask = NET_FW_PROFILE2_ALL;
|
||||
|
||||
hr = pNetFwRule->put_Direction(in ? NET_FW_RULE_DIR_IN : NET_FW_RULE_DIR_OUT);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Failed INetFwRule::put_Direction failed with error: 0x %x.\n", hr);
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
|
||||
hr = pNetFwRule->put_Action(NET_FW_ACTION_ALLOW);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Failed INetFwRule::put_Action failed with error: 0x %x.\n", hr);
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
hr = pNetFwRule->put_ApplicationName(RuleAppPath);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Failed INetFwRule::put_ApplicationName failed with error: 0x %x.\n", hr);
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
//hr = pNetFwRule->put_Protocol(6); // TCP
|
||||
//if (FAILED(hr))
|
||||
//{
|
||||
// WcaLog(LOGMSG_STANDARD, "Failed INetFwRule::put_Protocol failed with error: 0x %x.\n", hr);
|
||||
// goto Cleanup;
|
||||
//}
|
||||
|
||||
hr = pNetFwRule->put_Profiles(CurrentProfilesBitMask);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Failed INetFwRule::put_Profiles failed with error: 0x %x.\n", hr);
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
hr = pNetFwRule->put_Enabled(VARIANT_TRUE);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Failed INetFwRule::put_Enabled failed with error: 0x %x.\n", hr);
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
if (in) {
|
||||
// Check if INetFwRule2 interface is available (i.e Windows7+)
|
||||
// If supported, then use EdgeTraversalOptions
|
||||
// Else use the EdgeTraversal boolean flag.
|
||||
|
||||
if (SUCCEEDED(pNetFwRule->QueryInterface(__uuidof(INetFwRule2), (void**)&pNetFwRule2)))
|
||||
{
|
||||
hr = pNetFwRule2->put_EdgeTraversalOptions(NET_FW_EDGE_TRAVERSAL_TYPE_DEFER_TO_APP);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Failed INetFwRule::put_EdgeTraversalOptions failed with error: 0x %x.\n", hr);
|
||||
goto Cleanup;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
hr = pNetFwRule->put_EdgeTraversal(VARIANT_TRUE);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Failed INetFwRule::put_EdgeTraversal failed with error: 0x %x.\n", hr);
|
||||
goto Cleanup;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hr = pNetFwRules->Add(pNetFwRule);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to add firewall rule to the firewall rules collection : 0x%08lx\n", hr);
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
WcaLog(LOGMSG_STANDARD, "Successfully added firewall rule !\n");
|
||||
|
||||
Cleanup:
|
||||
|
||||
SysFreeString(RuleName);
|
||||
SysFreeString(RuleGroupName);
|
||||
SysFreeString(RuleDescription);
|
||||
SysFreeString(RuleAppPath);
|
||||
|
||||
if (pNetFwRule2 != NULL)
|
||||
{
|
||||
pNetFwRule2->Release();
|
||||
}
|
||||
|
||||
if (pNetFwRule != NULL)
|
||||
{
|
||||
pNetFwRule->Release();
|
||||
}
|
||||
|
||||
if (pNetFwRules != NULL)
|
||||
{
|
||||
pNetFwRules->Release();
|
||||
}
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
|
||||
// Instantiate INetFwPolicy2
|
||||
HRESULT WFCOMInitialize(INetFwPolicy2** ppNetFwPolicy2)
|
||||
{
|
||||
HRESULT hr = S_OK;
|
||||
|
||||
hr = CoCreateInstance(
|
||||
__uuidof(NetFwPolicy2),
|
||||
NULL,
|
||||
CLSCTX_INPROC_SERVER,
|
||||
__uuidof(INetFwPolicy2),
|
||||
(void**)ppNetFwPolicy2);
|
||||
|
||||
if (FAILED(hr))
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "CoCreateInstance for INetFwPolicy2 failed: 0x%08lx\n", hr);
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
Cleanup:
|
||||
return hr;
|
||||
}
|
||||
|
||||
|
||||
// Release INetFwPolicy2
|
||||
void WFCOMCleanup(INetFwPolicy2* pNetFwPolicy2)
|
||||
{
|
||||
// Release the INetFwPolicy2 object (Vista+)
|
||||
if (pNetFwPolicy2 != NULL)
|
||||
{
|
||||
pNetFwPolicy2->Release();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#include "pch.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <cwctype>
|
||||
|
||||
void trim(std::wstring& str) {
|
||||
str.erase(str.begin(), std::find_if(str.begin(), str.end(), [](wchar_t ch) {
|
||||
return !std::iswspace(ch);
|
||||
}));
|
||||
str.erase(std::find_if(str.rbegin(), str.rend(), [](wchar_t ch) {
|
||||
return !std::iswspace(ch);
|
||||
}).base(), str.end());
|
||||
}
|
||||
|
||||
std::wstring ReadConfig(const std::wstring& filename, const std::wstring& key)
|
||||
{
|
||||
std::wstring configValue;
|
||||
std::wstring line;
|
||||
std::wifstream file(filename);
|
||||
while (std::getline(file, line)) {
|
||||
trim(line);
|
||||
if (line.find(key) == 0) {
|
||||
std::size_t position = line.find(L"=", key.size());
|
||||
if (position != std::string::npos) {
|
||||
configValue = line.substr(position + 1);
|
||||
trim(configValue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
file.close();
|
||||
return configValue;
|
||||
}
|
||||
@@ -0,0 +1,517 @@
|
||||
#include "pch.h"
|
||||
|
||||
#include <Windows.h>
|
||||
#include <winspool.h>
|
||||
#include <setupapi.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
|
||||
#include "Common.h"
|
||||
|
||||
#pragma comment(lib, "setupapi.lib")
|
||||
#pragma comment(lib, "winspool.lib")
|
||||
|
||||
namespace RemotePrinter
|
||||
{
|
||||
#define HRESULT_ERR_ELEMENT_NOT_FOUND 0x80070490
|
||||
|
||||
LPCWCH RD_DRIVER_INF_PATH = L"drivers\\RustDeskPrinterDriver\\RustDeskPrinterDriver.inf";
|
||||
LPCWCH RD_PRINTER_PORT = L"RustDesk Printer";
|
||||
LPCWCH RD_PRINTER_NAME = L"RustDesk Printer";
|
||||
LPCWCH RD_PRINTER_DRIVER_NAME = L"RustDesk v4 Printer Driver";
|
||||
LPCWCH XCV_MONITOR_LOCAL_PORT = L",XcvMonitor Local Port";
|
||||
|
||||
using FuncEnum = std::function<BOOL(DWORD level, LPBYTE pDriverInfo, DWORD cbBuf, LPDWORD pcbNeeded, LPDWORD pcReturned)>;
|
||||
template <typename T, typename R>
|
||||
using FuncOnData = std::function<std::shared_ptr<R>(const T &)>;
|
||||
template <typename R>
|
||||
using FuncOnNoData = std::function<std::shared_ptr<R>()>;
|
||||
|
||||
template <class T, class R>
|
||||
std::shared_ptr<R> commonEnum(std::wstring funcName, FuncEnum func, DWORD level, FuncOnData<T, R> onData, FuncOnNoData<R> onNoData)
|
||||
{
|
||||
DWORD needed = 0;
|
||||
DWORD returned = 0;
|
||||
func(level, NULL, 0, &needed, &returned);
|
||||
if (needed == 0)
|
||||
{
|
||||
return onNoData();
|
||||
}
|
||||
|
||||
std::vector<BYTE> buffer(needed);
|
||||
if (!func(level, buffer.data(), needed, &needed, &returned))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
T *pPortInfo = reinterpret_cast<T *>(buffer.data());
|
||||
for (DWORD i = 0; i < returned; i++)
|
||||
{
|
||||
auto r = onData(pPortInfo[i]);
|
||||
if (r)
|
||||
{
|
||||
return r;
|
||||
}
|
||||
}
|
||||
return onNoData();
|
||||
}
|
||||
|
||||
BOOL isNameEqual(LPCWSTR lhs, LPCWSTR rhs)
|
||||
{
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-lstrcmpiw
|
||||
// For some locales, the lstrcmpi function may be insufficient.
|
||||
// If this occurs, use `CompareStringEx` to ensure proper comparison.
|
||||
// For example, in Japan call with the NORM_IGNORECASE, NORM_IGNOREKANATYPE, and NORM_IGNOREWIDTH values to achieve the most appropriate non-exact string comparison.
|
||||
// Note that specifying these values slows performance, so use them only when necessary.
|
||||
//
|
||||
// No need to consider `CompareStringEx` for now.
|
||||
return lstrcmpiW(lhs, rhs) == 0 ? TRUE : FALSE;
|
||||
}
|
||||
|
||||
BOOL enumPrinterPort(
|
||||
DWORD level,
|
||||
LPBYTE pPortInfo,
|
||||
DWORD cbBuf,
|
||||
LPDWORD pcbNeeded,
|
||||
LPDWORD pcReturned)
|
||||
{
|
||||
// https://learn.microsoft.com/en-us/windows/win32/printdocs/enumports
|
||||
// This is a blocking or synchronous function and might not return immediately.
|
||||
// How quickly this function returns depends on run-time factors
|
||||
// such as network status, print server configuration, and printer driver implementation factors that are difficult to predict when writing an application.
|
||||
// Calling this function from a thread that manages interaction with the user interface could make the application appear to be unresponsive.
|
||||
return EnumPortsW(NULL, level, pPortInfo, cbBuf, pcbNeeded, pcReturned);
|
||||
}
|
||||
|
||||
BOOL isPortExists(LPCWSTR port)
|
||||
{
|
||||
auto onData = [port](const PORT_INFO_2 &info)
|
||||
{
|
||||
if (isNameEqual(info.pPortName, port) == TRUE) {
|
||||
return std::shared_ptr<BOOL>(new BOOL(TRUE));
|
||||
}
|
||||
else {
|
||||
return std::shared_ptr<BOOL>(nullptr);
|
||||
} };
|
||||
auto onNoData = []()
|
||||
{ return nullptr; };
|
||||
auto res = commonEnum<PORT_INFO_2, BOOL>(L"EnumPortsW", enumPrinterPort, 2, onData, onNoData);
|
||||
if (res == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return *res;
|
||||
}
|
||||
}
|
||||
|
||||
BOOL executeOnLocalPort(LPCWSTR port, LPCWSTR command)
|
||||
{
|
||||
PRINTER_DEFAULTSW dft = {0};
|
||||
dft.DesiredAccess = SERVER_WRITE;
|
||||
HANDLE hMonitor = NULL;
|
||||
if (OpenPrinterW(const_cast<LPWSTR>(XCV_MONITOR_LOCAL_PORT), &hMonitor, &dft) == FALSE)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
DWORD outputNeeded = 0;
|
||||
DWORD status = 0;
|
||||
if (XcvDataW(hMonitor, command, (LPBYTE)port, (lstrlenW(port) + 1) * 2, NULL, 0, &outputNeeded, &status) == FALSE)
|
||||
{
|
||||
ClosePrinter(hMonitor);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
ClosePrinter(hMonitor);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BOOL addLocalPort(LPCWSTR port)
|
||||
{
|
||||
return executeOnLocalPort(port, L"AddPort");
|
||||
}
|
||||
|
||||
BOOL deleteLocalPort(LPCWSTR port)
|
||||
{
|
||||
return executeOnLocalPort(port, L"DeletePort");
|
||||
}
|
||||
|
||||
BOOL checkAddLocalPort(LPCWSTR port)
|
||||
{
|
||||
if (!isPortExists(port))
|
||||
{
|
||||
return addLocalPort(port);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
std::wstring getPrinterInstalledOnPort(LPCWSTR port);
|
||||
|
||||
BOOL checkDeleteLocalPort(LPCWSTR port)
|
||||
{
|
||||
if (isPortExists(port))
|
||||
{
|
||||
if (getPrinterInstalledOnPort(port) != L"")
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "The printer is installed on the port. Please remove the printer first.\n");
|
||||
return FALSE;
|
||||
}
|
||||
return deleteLocalPort(port);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BOOL enumPrinterDriver(
|
||||
DWORD level,
|
||||
LPBYTE pDriverInfo,
|
||||
DWORD cbBuf,
|
||||
LPDWORD pcbNeeded,
|
||||
LPDWORD pcReturned)
|
||||
{
|
||||
// https://learn.microsoft.com/en-us/windows/win32/printdocs/enumprinterdrivers
|
||||
// This is a blocking or synchronous function and might not return immediately.
|
||||
// How quickly this function returns depends on run-time factors
|
||||
// such as network status, print server configuration, and printer driver implementation factors that are difficult to predict when writing an application.
|
||||
// Calling this function from a thread that manages interaction with the user interface could make the application appear to be unresponsive.
|
||||
return EnumPrinterDriversW(
|
||||
NULL,
|
||||
NULL,
|
||||
level,
|
||||
pDriverInfo,
|
||||
cbBuf,
|
||||
pcbNeeded,
|
||||
pcReturned);
|
||||
}
|
||||
|
||||
DWORDLONG getInstalledDriverVersion(LPCWSTR name)
|
||||
{
|
||||
auto onData = [name](const DRIVER_INFO_6W &info)
|
||||
{
|
||||
if (isNameEqual(name, info.pName) == TRUE)
|
||||
{
|
||||
return std::shared_ptr<DWORDLONG>(new DWORDLONG(info.dwlDriverVersion));
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::shared_ptr<DWORDLONG>(nullptr);
|
||||
} };
|
||||
auto onNoData = []()
|
||||
{ return nullptr; };
|
||||
auto res = commonEnum<DRIVER_INFO_6W, DWORDLONG>(L"EnumPrinterDriversW", enumPrinterDriver, 6, onData, onNoData);
|
||||
if (res == nullptr)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return *res;
|
||||
}
|
||||
}
|
||||
|
||||
std::wstring findInf(LPCWSTR name)
|
||||
{
|
||||
auto onData = [name](const DRIVER_INFO_8W &info)
|
||||
{
|
||||
if (isNameEqual(name, info.pName) == TRUE)
|
||||
{
|
||||
return std::shared_ptr<std::wstring>(new std::wstring(info.pszInfPath));
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::shared_ptr<std::wstring>(nullptr);
|
||||
} };
|
||||
auto onNoData = []()
|
||||
{ return nullptr; };
|
||||
auto res = commonEnum<DRIVER_INFO_8W, std::wstring>(L"EnumPrinterDriversW", enumPrinterDriver, 8, onData, onNoData);
|
||||
if (res == nullptr)
|
||||
{
|
||||
return L"";
|
||||
}
|
||||
else
|
||||
{
|
||||
return *res;
|
||||
}
|
||||
}
|
||||
|
||||
BOOL deletePrinterDriver(LPCWSTR name)
|
||||
{
|
||||
// If the printer is used after the spooler service is started. E.g., printing a document through RustDesk Printer.
|
||||
// `DeletePrinterDriverExW()` may fail with `ERROR_PRINTER_DRIVER_IN_USE`(3001, 0xBB9).
|
||||
// We can only ignore this error for now.
|
||||
// Though restarting the spooler service is a solution, it's not a good idea to restart the service.
|
||||
//
|
||||
// Deleting the printer driver after deleting the printer is a common practice.
|
||||
// No idea why `DeletePrinterDriverExW()` fails with `ERROR_UNKNOWN_PRINTER_DRIVER` after using the printer once.
|
||||
// https://github.com/ChromiumWebApps/chromium/blob/c7361d39be8abd1574e6ce8957c8dbddd4c6ccf7/cloud_print/virtual_driver/win/install/setup.cc#L422
|
||||
// AnyDesk printer driver and the simplest printer driver also have the same issue.
|
||||
BOOL res = DeletePrinterDriverExW(NULL, NULL, const_cast<LPWSTR>(name), DPD_DELETE_ALL_FILES, 0);
|
||||
if (res == FALSE)
|
||||
{
|
||||
DWORD error = GetLastError();
|
||||
if (error == ERROR_UNKNOWN_PRINTER_DRIVER)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to delete printer driver. Error (%d)\n", error);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
BOOL deletePrinterDriverPackage(const std::wstring &inf)
|
||||
{
|
||||
// https://learn.microsoft.com/en-us/windows/win32/printdocs/deleteprinterdriverpackage
|
||||
// This function is a blocking or synchronous function and might not return immediately.
|
||||
// How quickly this function returns depends on run-time factors such as network status, print server configuration, and printer driver implementation factors that are difficult to predict when writing an application.
|
||||
// Calling this function from a thread that manages interaction with the user interface could make the application appear to be unresponsive.
|
||||
int tries = 3;
|
||||
HRESULT result = S_FALSE;
|
||||
while ((result = DeletePrinterDriverPackage(NULL, inf.c_str(), NULL)) != S_OK)
|
||||
{
|
||||
if (result == HRESULT_ERR_ELEMENT_NOT_FOUND)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to delete printer driver package. HRESULT (%d)\n", result);
|
||||
tries--;
|
||||
if (tries <= 0)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
Sleep(2000);
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
BOOL uninstallDriver(LPCWSTR name)
|
||||
{
|
||||
auto infFile = findInf(name);
|
||||
if (!deletePrinterDriver(name))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
if (infFile != L"" && !deletePrinterDriverPackage(infFile))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BOOL installDriver(LPCWSTR name, LPCWSTR inf)
|
||||
{
|
||||
DWORD size = MAX_PATH * 10;
|
||||
wchar_t package_path[MAX_PATH * 10] = {0};
|
||||
HRESULT result = UploadPrinterDriverPackage(
|
||||
NULL, inf, NULL,
|
||||
UPDP_SILENT_UPLOAD | UPDP_UPLOAD_ALWAYS, NULL, package_path, &size);
|
||||
if (result != S_OK)
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Uploading the printer driver package to the driver cache silently, failed. Will retry with user UI. HRESULT (%d)\n", result);
|
||||
result = UploadPrinterDriverPackage(
|
||||
NULL, inf, NULL, UPDP_UPLOAD_ALWAYS,
|
||||
GetForegroundWindow(), package_path, &size);
|
||||
if (result != S_OK)
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Uploading the printer driver package to the driver cache failed with user UI. Aborting...\n");
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
result = InstallPrinterDriverFromPackage(
|
||||
NULL, package_path, name, NULL, IPDFP_COPY_ALL_FILES);
|
||||
if (result != S_OK)
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Installing the printer driver failed. HRESULT (%d)\n", result);
|
||||
}
|
||||
return result == S_OK;
|
||||
}
|
||||
|
||||
BOOL enumLocalPrinter(
|
||||
DWORD level,
|
||||
LPBYTE pPrinterInfo,
|
||||
DWORD cbBuf,
|
||||
LPDWORD pcbNeeded,
|
||||
LPDWORD pcReturned)
|
||||
{
|
||||
// https://learn.microsoft.com/en-us/windows/win32/printdocs/enumprinters
|
||||
// This is a blocking or synchronous function and might not return immediately.
|
||||
// How quickly this function returns depends on run-time factors
|
||||
// such as network status, print server configuration, and printer driver implementation factors that are difficult to predict when writing an application.
|
||||
// Calling this function from a thread that manages interaction with the user interface could make the application appear to be unresponsive.
|
||||
return EnumPrintersW(PRINTER_ENUM_LOCAL, NULL, level, pPrinterInfo, cbBuf, pcbNeeded, pcReturned);
|
||||
}
|
||||
|
||||
BOOL isPrinterAdded(LPCWSTR name)
|
||||
{
|
||||
auto onData = [name](const PRINTER_INFO_1W &info)
|
||||
{
|
||||
if (isNameEqual(name, info.pName) == TRUE)
|
||||
{
|
||||
return std::shared_ptr<BOOL>(new BOOL(TRUE));
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::shared_ptr<BOOL>(nullptr);
|
||||
} };
|
||||
auto onNoData = []()
|
||||
{ return nullptr; };
|
||||
auto res = commonEnum<PRINTER_INFO_1W, BOOL>(L"EnumPrintersW", enumLocalPrinter, 1, onData, onNoData);
|
||||
if (res == nullptr)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
return *res;
|
||||
}
|
||||
}
|
||||
|
||||
std::wstring getPrinterInstalledOnPort(LPCWSTR port)
|
||||
{
|
||||
auto onData = [port](const PRINTER_INFO_2W &info)
|
||||
{
|
||||
if (isNameEqual(port, info.pPortName) == TRUE)
|
||||
{
|
||||
return std::shared_ptr<std::wstring>(new std::wstring(info.pPrinterName));
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::shared_ptr<std::wstring>(nullptr);
|
||||
} };
|
||||
auto onNoData = []()
|
||||
{ return nullptr; };
|
||||
auto res = commonEnum<PRINTER_INFO_2W, std::wstring>(L"EnumPrintersW", enumLocalPrinter, 2, onData, onNoData);
|
||||
if (res == nullptr)
|
||||
{
|
||||
return L"";
|
||||
}
|
||||
else
|
||||
{
|
||||
return *res;
|
||||
}
|
||||
}
|
||||
|
||||
BOOL addPrinter(LPCWSTR name, LPCWSTR driver, LPCWSTR port)
|
||||
{
|
||||
PRINTER_INFO_2W printerInfo = {0};
|
||||
printerInfo.pPrinterName = const_cast<LPWSTR>(name);
|
||||
printerInfo.pPortName = const_cast<LPWSTR>(port);
|
||||
printerInfo.pDriverName = const_cast<LPWSTR>(driver);
|
||||
printerInfo.pPrintProcessor = const_cast<LPWSTR>(L"WinPrint");
|
||||
printerInfo.pDatatype = const_cast<LPWSTR>(L"RAW");
|
||||
printerInfo.Attributes = PRINTER_ATTRIBUTE_LOCAL;
|
||||
HANDLE hPrinter = AddPrinterW(NULL, 2, (LPBYTE)&printerInfo);
|
||||
return hPrinter == NULL ? FALSE : TRUE;
|
||||
}
|
||||
|
||||
VOID deletePrinter(LPCWSTR name)
|
||||
{
|
||||
PRINTER_DEFAULTSW dft = {0};
|
||||
dft.DesiredAccess = PRINTER_ALL_ACCESS;
|
||||
HANDLE hPrinter = NULL;
|
||||
if (OpenPrinterW(const_cast<LPWSTR>(name), &hPrinter, &dft) == FALSE)
|
||||
{
|
||||
DWORD error = GetLastError();
|
||||
if (error == ERROR_INVALID_PRINTER_NAME)
|
||||
{
|
||||
return;
|
||||
}
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to open printer. error (%d)\n", error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (SetPrinterW(hPrinter, 0, NULL, PRINTER_CONTROL_PURGE) == FALSE)
|
||||
{
|
||||
ClosePrinter(hPrinter);
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to purge printer queue. error (%d)\n", GetLastError());
|
||||
return;
|
||||
}
|
||||
|
||||
if (DeletePrinter(hPrinter) == FALSE)
|
||||
{
|
||||
ClosePrinter(hPrinter);
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to delete printer. error (%d)\n", GetLastError());
|
||||
return;
|
||||
}
|
||||
|
||||
ClosePrinter(hPrinter);
|
||||
}
|
||||
|
||||
bool FileExists(const std::wstring &filePath)
|
||||
{
|
||||
DWORD fileAttributes = GetFileAttributes(filePath.c_str());
|
||||
return (fileAttributes != INVALID_FILE_ATTRIBUTES && !(fileAttributes & FILE_ATTRIBUTE_DIRECTORY));
|
||||
}
|
||||
|
||||
// Steps:
|
||||
// 1. Add the local port.
|
||||
// 2. Check if the driver is installed.
|
||||
// Uninstall the existing driver if it is installed.
|
||||
// We should not check the driver version because the driver is deployed with the application.
|
||||
// It's better to uninstall the existing driver and install the driver from the application.
|
||||
// 3. Add the printer.
|
||||
VOID installUpdatePrinter(const std::wstring &installFolder)
|
||||
{
|
||||
const std::wstring infFile = installFolder + L"\\" + RemotePrinter::RD_DRIVER_INF_PATH;
|
||||
if (!FileExists(infFile))
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Printer driver INF file not found, aborting...\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!checkAddLocalPort(RD_PRINTER_PORT))
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to check add local port, error (%d)\n", GetLastError());
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Local port added successfully\n");
|
||||
}
|
||||
|
||||
if (getInstalledDriverVersion(RD_PRINTER_DRIVER_NAME) > 0)
|
||||
{
|
||||
deletePrinter(RD_PRINTER_NAME);
|
||||
if (FALSE == uninstallDriver(RD_PRINTER_DRIVER_NAME))
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to uninstall previous printer driver, error (%d)\n", GetLastError());
|
||||
}
|
||||
}
|
||||
|
||||
if (FALSE == installDriver(RD_PRINTER_DRIVER_NAME, infFile.c_str()))
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Driver installation failed, still try to add the printer\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Driver installed successfully\n");
|
||||
}
|
||||
|
||||
if (FALSE == addPrinter(RD_PRINTER_NAME, RD_PRINTER_DRIVER_NAME, RD_PRINTER_PORT))
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to add printer, error (%d)\n", GetLastError());
|
||||
}
|
||||
else
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Printer installed successfully\n");
|
||||
}
|
||||
}
|
||||
|
||||
VOID uninstallPrinter()
|
||||
{
|
||||
deletePrinter(RD_PRINTER_NAME);
|
||||
WcaLog(LOGMSG_STANDARD, "Deleted the printer\n");
|
||||
uninstallDriver(RD_PRINTER_DRIVER_NAME);
|
||||
WcaLog(LOGMSG_STANDARD, "Uninstalled the printer driver\n");
|
||||
checkDeleteLocalPort(RD_PRINTER_PORT);
|
||||
WcaLog(LOGMSG_STANDARD, "Deleted the local port\n");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
// https://learn.microsoft.com/en-us/windows/win32/services/installing-a-service
|
||||
|
||||
#include "pch.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <Windows.h>
|
||||
#include <strsafe.h>
|
||||
|
||||
bool MyCreateServiceW(LPCWSTR serviceName, LPCWSTR displayName, LPCWSTR binaryPath)
|
||||
{
|
||||
SC_HANDLE schSCManager;
|
||||
SC_HANDLE schService;
|
||||
|
||||
// Get a handle to the SCM database.
|
||||
schSCManager = OpenSCManagerW(
|
||||
NULL, // local computer
|
||||
NULL, // ServicesActive database
|
||||
SC_MANAGER_ALL_ACCESS); // full access rights
|
||||
|
||||
if (NULL == schSCManager)
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "OpenSCManager failed (%d)\n", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create the service
|
||||
schService = CreateServiceW(
|
||||
schSCManager, // SCM database
|
||||
serviceName, // name of service
|
||||
displayName, // service name to display
|
||||
SERVICE_ALL_ACCESS, // desired access
|
||||
SERVICE_WIN32_OWN_PROCESS, // service type
|
||||
SERVICE_AUTO_START, // start type
|
||||
SERVICE_ERROR_NORMAL, // error control type
|
||||
binaryPath, // path to service's binary
|
||||
NULL, // no load ordering group
|
||||
NULL, // no tag identifier
|
||||
NULL, // no dependencies
|
||||
NULL, // LocalSystem account
|
||||
NULL); // no password
|
||||
if (schService == NULL)
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "CreateService failed (%d)\n", GetLastError());
|
||||
CloseServiceHandle(schSCManager);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Service installed successfully\n");
|
||||
}
|
||||
|
||||
CloseServiceHandle(schService);
|
||||
CloseServiceHandle(schSCManager);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MyDeleteServiceW(LPCWSTR serviceName)
|
||||
{
|
||||
SC_HANDLE hSCManager = OpenSCManagerW(NULL, NULL, SC_MANAGER_CONNECT);
|
||||
if (hSCManager == NULL) {
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to open Service Control Manager, error: 0x%02X", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
SC_HANDLE hService = OpenServiceW(hSCManager, serviceName, SERVICE_STOP | DELETE);
|
||||
if (hService == NULL) {
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to open service: %ls, error: 0x%02X", serviceName, GetLastError());
|
||||
CloseServiceHandle(hSCManager);
|
||||
return false;
|
||||
}
|
||||
|
||||
SERVICE_STATUS serviceStatus;
|
||||
if (ControlService(hService, SERVICE_CONTROL_STOP, &serviceStatus)) {
|
||||
WcaLog(LOGMSG_STANDARD, "Stopping service: %ls", serviceName);
|
||||
}
|
||||
|
||||
bool success = DeleteService(hService);
|
||||
if (!success) {
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to delete service: %ls, error: 0x%02X", serviceName, GetLastError());
|
||||
}
|
||||
|
||||
CloseServiceHandle(hService);
|
||||
CloseServiceHandle(hSCManager);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool MyStartServiceW(LPCWSTR serviceName)
|
||||
{
|
||||
SC_HANDLE hSCManager = OpenSCManagerW(NULL, NULL, SC_MANAGER_CONNECT);
|
||||
if (hSCManager == NULL) {
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to open Service Control Manager, error: 0x%02X", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
SC_HANDLE hService = OpenServiceW(hSCManager, serviceName, SERVICE_START);
|
||||
if (hService == NULL) {
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to open service: %ls, error: 0x%02X", serviceName, GetLastError());
|
||||
CloseServiceHandle(hSCManager);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool success = StartServiceW(hService, 0, NULL);
|
||||
if (!success) {
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to start service: %ls, error: 0x%02X", serviceName, GetLastError());
|
||||
}
|
||||
|
||||
CloseServiceHandle(hService);
|
||||
CloseServiceHandle(hSCManager);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool MyStopServiceW(LPCWSTR serviceName)
|
||||
{
|
||||
SC_HANDLE hSCManager = OpenSCManagerW(NULL, NULL, SC_MANAGER_CONNECT);
|
||||
if (hSCManager == NULL) {
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to open Service Control Manager");
|
||||
return false;
|
||||
}
|
||||
|
||||
SC_HANDLE hService = OpenServiceW(hSCManager, serviceName, SERVICE_STOP);
|
||||
if (hService == NULL) {
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to open service: %ls", serviceName);
|
||||
CloseServiceHandle(hSCManager);
|
||||
return false;
|
||||
}
|
||||
|
||||
SERVICE_STATUS serviceStatus;
|
||||
if (!ControlService(hService, SERVICE_CONTROL_STOP, &serviceStatus)) {
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to stop service: %ls", serviceName);
|
||||
CloseServiceHandle(hService);
|
||||
CloseServiceHandle(hSCManager);
|
||||
return false;
|
||||
}
|
||||
|
||||
CloseServiceHandle(hService);
|
||||
CloseServiceHandle(hSCManager);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QueryServiceStatusExW(LPCWSTR serviceName, SERVICE_STATUS_PROCESS* status)
|
||||
{
|
||||
SC_HANDLE hSCManager = OpenSCManagerW(NULL, NULL, SC_MANAGER_CONNECT);
|
||||
if (hSCManager == NULL) {
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to open Service Control Manager");
|
||||
return false;
|
||||
}
|
||||
|
||||
SC_HANDLE hService = OpenServiceW(hSCManager, serviceName, SERVICE_QUERY_STATUS);
|
||||
if (hService == NULL) {
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to open service: %ls", serviceName);
|
||||
CloseServiceHandle(hSCManager);
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD bytesNeeded;
|
||||
BOOL success = QueryServiceStatusEx(hService, SC_STATUS_PROCESS_INFO, reinterpret_cast<LPBYTE>(status), sizeof(*status), &bytesNeeded);
|
||||
if (!success) {
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to query service: %ls", serviceName);
|
||||
}
|
||||
|
||||
CloseServiceHandle(hService);
|
||||
CloseServiceHandle(hSCManager);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool IsServiceRunningW(LPCWSTR serviceName)
|
||||
{
|
||||
SERVICE_STATUS_PROCESS serviceStatus;
|
||||
QueryServiceStatusExW(serviceName, &serviceStatus);
|
||||
return (serviceStatus.dwCurrentState == SERVICE_RUNNING);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// dllmain.cpp : Defines the entry point for the DLL application.
|
||||
#include "pch.h"
|
||||
|
||||
BOOL APIENTRY DllMain(
|
||||
__in HMODULE hModule,
|
||||
__in DWORD ulReasonForCall,
|
||||
__in LPVOID
|
||||
)
|
||||
{
|
||||
switch (ulReasonForCall)
|
||||
{
|
||||
case DLL_PROCESS_ATTACH:
|
||||
WcaGlobalInitialize(hModule);
|
||||
break;
|
||||
|
||||
case DLL_PROCESS_DETACH:
|
||||
WcaGlobalFinalize();
|
||||
break;
|
||||
|
||||
case DLL_THREAD_ATTACH:
|
||||
case DLL_THREAD_DETACH:
|
||||
break;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
|
||||
// Windows Header Files
|
||||
#include <windows.h>
|
||||
#include <strsafe.h>
|
||||
#include <msiquery.h>
|
||||
|
||||
// WiX Header Files:
|
||||
#include <wcautil.h>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="WixToolset.DUtil" version="4.0.5" targetFramework="native" />
|
||||
<package id="WixToolset.WcaUtil" version="4.0.5" targetFramework="native" />
|
||||
</packages>
|
||||
@@ -0,0 +1,5 @@
|
||||
// pch.cpp: source file corresponding to the pre-compiled header
|
||||
|
||||
#include "pch.h"
|
||||
|
||||
// When you are using pre-compiled headers, this source file is necessary for compilation to succeed.
|
||||
@@ -0,0 +1,13 @@
|
||||
// pch.h: This is a precompiled header file.
|
||||
// Files listed below are compiled only once, improving build performance for future builds.
|
||||
// This also affects IntelliSense performance, including code completion and many code browsing features.
|
||||
// However, files listed here are ALL re-compiled if any one of them is updated between builds.
|
||||
// Do not add files here that you will be updating frequently as this negates the performance advantage.
|
||||
|
||||
#ifndef PCH_H
|
||||
#define PCH_H
|
||||
|
||||
// add headers that you want to pre-compile here
|
||||
#include "framework.h"
|
||||
|
||||
#endif //PCH_H
|
||||
@@ -0,0 +1,45 @@
|
||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"
|
||||
xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
|
||||
<?include ../Includes.wxi?>
|
||||
|
||||
<Fragment>
|
||||
<!-- For compatibility with command line values from previous versions -->
|
||||
<Property Id="INSTALLFOLDER" Secure="yes">
|
||||
<RegistrySearch Id="InstallFolderSearch" Root="HKCR" Key="$(var.RegKeyRoot)" Name="INSTALLFOLDER" Type="raw" />
|
||||
</Property>
|
||||
|
||||
<!-- If a property value has been passed via the command line (which includes when set from the bundle), the registry search will
|
||||
overwrite the command line value, these actions temporarily store the command line value before the registry search
|
||||
is performed so they can be restored after the registry search is complete -->
|
||||
<SetProperty Id="SavedInstallFolderCmdLineValue" Value="[INSTALLFOLDER]" Before="AppSearch" Sequence="first" Condition="INSTALLFOLDER" />
|
||||
|
||||
<!-- If a command line value was stored, restore it after the registry search has been performed -->
|
||||
<SetProperty Action="RestoreSavedInstallFolderValue" Id="INSTALLFOLDER" Value="[SavedInstallFolderCmdLineValue]" After="AppSearch" Sequence="first" Condition="SavedInstallFolderCmdLineValue" />
|
||||
|
||||
<!-- Normalize INSTALLFOLDER from the command line or registry before assigning INSTALLFOLDER_INNER. -->
|
||||
<!-- Case 1: already ends with \$(var.Product)\, keep it unchanged. -->
|
||||
<SetProperty Action="SetInstallFolderInnerFromProductDir" Id="INSTALLFOLDER_INNER" Value="[INSTALLFOLDER]" After="RestoreSavedInstallFolderValue" Sequence="first" Condition="INSTALLFOLDER AND INSTALLFOLDER ~>> "\$(var.Product)\"" />
|
||||
<!-- Case 2: already ends with \$(var.Product) but has no trailing slash, add the slash. -->
|
||||
<SetProperty Action="SetInstallFolderInnerFromProductDirNoSlash" Id="INSTALLFOLDER_INNER" Value="[INSTALLFOLDER]\" After="RestoreSavedInstallFolderValue" Sequence="first" Condition="INSTALLFOLDER AND INSTALLFOLDER ~>> "\$(var.Product)"" />
|
||||
<!-- Case 3: ends with a slash but not \$(var.Product)\, append $(var.Product)\. -->
|
||||
<SetProperty Action="SetInstallFolderInnerAppendProduct" Id="INSTALLFOLDER_INNER" Value="[INSTALLFOLDER]$(var.Product)\" After="RestoreSavedInstallFolderValue" Sequence="first" Condition="INSTALLFOLDER AND INSTALLFOLDER ~>> "\" AND NOT (INSTALLFOLDER ~>> "\$(var.Product)\" OR INSTALLFOLDER ~>> "\$(var.Product)")" />
|
||||
<!-- Case 4: has no trailing slash and does not end with \$(var.Product), append \$(var.Product)\. -->
|
||||
<SetProperty Action="SetInstallFolderInnerAppendSlashProduct" Id="INSTALLFOLDER_INNER" Value="[INSTALLFOLDER]\$(var.Product)\" After="RestoreSavedInstallFolderValue" Sequence="first" Condition="INSTALLFOLDER AND NOT INSTALLFOLDER ~>> "\" AND NOT (INSTALLFOLDER ~>> "\$(var.Product)\" OR INSTALLFOLDER ~>> "\$(var.Product)")" />
|
||||
|
||||
<!-- INSTALLFOLDER_INNER is defined for compatibility with previous versions of the installer. -->
|
||||
<!-- Because we need to use INSTALLFOLDER as the command line argument. -->
|
||||
<StandardDirectory Id="ProgramFiles6432Folder">
|
||||
<Directory Id="INSTALLFOLDER_INNER" Name="$(var.Product)" />
|
||||
</StandardDirectory>
|
||||
|
||||
<StandardDirectory Id="CommonAppDataFolder">
|
||||
<Directory Id="App.Data.Folder" Name="$(var.Product)" />
|
||||
</StandardDirectory>
|
||||
|
||||
<StandardDirectory Id="ProgramMenuFolder">
|
||||
<Directory Id="App.StartMenu" Name="$(var.Product)" />
|
||||
</StandardDirectory>
|
||||
|
||||
<StandardDirectory Id="DesktopFolder" />
|
||||
</Fragment>
|
||||
</Wix>
|
||||
@@ -0,0 +1,56 @@
|
||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"
|
||||
xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
|
||||
<?include ../Includes.wxi?>
|
||||
|
||||
<Fragment>
|
||||
<!-- Regs for shortcuts are defined in "Fragments/ShortcutProperties.wxs" -->
|
||||
<!-- Component that persists the property values to the registry so they are available during an upgrade/modify -->
|
||||
<DirectoryRef Id="INSTALLFOLDER_INNER">
|
||||
<Component Id="Product.Registry.InstallFolder" Guid="3196EDA7-9AEF-4705-A0C8-E3F3ECCCB153">
|
||||
<RegistryKey Root="HKCR" Key="$(var.RegKeyRoot)">
|
||||
<RegistryValue Type="string" Name="INSTALLFOLDER" Value="[INSTALLFOLDER_INNER]" />
|
||||
</RegistryKey>
|
||||
</Component>
|
||||
|
||||
<Component Id="Product.Registry.DefaultIcon" Guid="6DBF2690-0955-4C6A-940F-634DDA503F49">
|
||||
<RegistryKey Root="HKCR" Key="$(var.RegKeyRoot)\DefaultIcon">
|
||||
<RegistryValue Type="string" Value='"[INSTALLFOLDER_INNER]$(var.Product).exe",0' />
|
||||
</RegistryKey>
|
||||
</Component>
|
||||
|
||||
<Component Id="Product.Registry.CommandPlay" Guid="613C9E4F-2F1F-45A3-96E2-26EBBEBA6B0E">
|
||||
<RegistryKey Root="HKCR" Key="$(var.RegKeyRoot)\shell" />
|
||||
<RegistryKey Root="HKCR" Key="$(var.RegKeyRoot)\shell\open" />
|
||||
<RegistryKey Root="HKCR" Key="$(var.RegKeyRoot)\shell\open\command">
|
||||
<RegistryValue Type="string" Value='"[INSTALLFOLDER_INNER]$(var.Product).exe" --play "%1"' />
|
||||
</RegistryKey>
|
||||
</Component>
|
||||
|
||||
<Component Id="Product.Registry.URLProtocol" Guid="565BE3F8-23A7-4B9D-B0DE-6D51CC86FC0B">
|
||||
<RegistryKey Root="HKCR" Key="$(var.ProductLower)">
|
||||
<RegistryValue Type="string" Name="URL Protocol" Value="" />
|
||||
</RegistryKey>
|
||||
</Component>
|
||||
|
||||
<Component Id="Product.Registry.Command" Guid="BC8D581C-5960-4843-93DC-E347CD43BD49">
|
||||
<RegistryKey Root="HKCR" Key="$(var.ProductLower)\shell" />
|
||||
<RegistryKey Root="HKCR" Key="$(var.ProductLower)\shell\open" />
|
||||
<RegistryKey Root="HKCR" Key="$(var.ProductLower)\shell\open\command">
|
||||
<RegistryValue Type="string" Value='"[INSTALLFOLDER_INNER]$(var.Product).exe" "%1"' />
|
||||
</RegistryKey>
|
||||
</Component>
|
||||
|
||||
<!--For compatibility with registry values from previous versions-->
|
||||
<Component Id="Product.Registry.UninstallApp" Guid="FC1A3D2E-5642-FBD8-CFA6-5ECAC6DE69A8">
|
||||
<RegistryKey Root="HKLM" Key="Software\Microsoft\Windows\CurrentVersion\Uninstall\$(var.Product)" >
|
||||
<RegistryValue Type="string" Name="BuildDate" Value="$(var.BuildDate)" />
|
||||
<RegistryValue Type="string" Name="share_rdp" Value="" />
|
||||
|
||||
<!--$ArpStart$-->
|
||||
<!--$ArpEnd$-->
|
||||
</RegistryKey>
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
|
||||
</Fragment>
|
||||
</Wix>
|
||||
@@ -0,0 +1,154 @@
|
||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"
|
||||
xmlns:fire="http://wixtoolset.org/schemas/v4/wxs/firewall">
|
||||
<Fragment>
|
||||
|
||||
<?include ../Includes.wxi?>
|
||||
|
||||
<DirectoryRef Id="INSTALLFOLDER_INNER" FileSource="$(var.BuildDir)">
|
||||
<Component Id="App.exe" Guid="620F0F69-4C17-4320-A619-495E329712A4">
|
||||
<File Id="App.exe" Name="$(var.Product).exe" KeyPath="yes" Checksum="yes">
|
||||
<!--<fire:FirewallException Id="AppEx" Name="$(var.Product) Service" Scope="any" IgnoreFailure="yes" />-->
|
||||
</File>
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
|
||||
<CustomAction Id="RemoveRuntimeGeneratedFiles.SetParam" Return="check" Property="RemoveRuntimeGeneratedFiles" Value="[INSTALLFOLDER_INNER]" />
|
||||
<CustomAction Id="AddFirewallRules.SetParam" Return="check" Property="AddFirewallRules" Value="1[INSTALLFOLDER_INNER]$(var.Product).exe" />
|
||||
<CustomAction Id="RemoveFirewallRules.SetParam" Return="check" Property="RemoveFirewallRules" Value="0[INSTALLFOLDER_INNER]$(var.Product).exe" />
|
||||
<CustomAction Id="CreateStartService.SetParam" Return="check" Property="CreateStartService" Value="$(var.Product);"[INSTALLFOLDER_INNER]$(var.Product).exe" --service" />
|
||||
<CustomAction Id="TryStopDeleteService.SetParam" Return="check" Property="TryStopDeleteService" Value="$(var.Product)" />
|
||||
|
||||
<CustomAction Id="LaunchApp" ExeCommand="" Return="asyncNoWait" FileRef="App.exe" />
|
||||
<CustomAction Id="LaunchAppTray" ExeCommand=" --tray" Return="asyncNoWait" FileRef="App.exe" />
|
||||
<Property Id="TerminateProcesses" Value="AppTest.exe" />
|
||||
<CustomAction Id="TerminateProcesses.SetParam" Return="check" Property="TerminateProcesses" Value="$(var.Product).exe" />
|
||||
<CustomAction Id="TerminateBrokers.SetParam" Return="check" Property="TerminateProcesses" Value="RuntimeBroker_rustdesk.exe" />
|
||||
<CustomAction Id="SetPropertyIsServiceRunning.SetParam.AppName" Return="check" Property="AppName" Value="$(var.Product)" />
|
||||
<CustomAction Id="SetPropertyIsServiceRunning.SetParam.PropertyName" Return="check" Property="PropertyName" Value="STOP_SERVICE" />
|
||||
<CustomAction Id="SetPropertyServiceStop.SetParam.ConfigFile" Return="check" Property="ConfigFile" Value="[AppDataFolder]$(var.Product)\config\$(var.Product)2.toml" />
|
||||
<CustomAction Id="SetPropertyServiceStop.SetParam.ConfigKey" Return="check" Property="ConfigKey" Value="stop-service" />
|
||||
<CustomAction Id="SetPropertyServiceStop.SetParam.PropertyName" Return="check" Property="PropertyName" Value="STOP_SERVICE" />
|
||||
<CustomAction Id="TryDeleteStartupShortcut.SetParam" Return="check" Property="ShortcutName" Value="$(var.Product) Tray" />
|
||||
<CustomAction Id="RemoveAmyuniIdd.SetParam" Return="check" Property="RemoveAmyuniIdd" Value="[INSTALLFOLDER_INNER]" />
|
||||
<CustomAction Id="InstallPrinter.SetParam" Return="check" Property="InstallPrinter" Value="[INSTALLFOLDER_INNER]" />
|
||||
<InstallExecuteSequence>
|
||||
|
||||
<Custom Action="SetPropertyIsServiceRunning" After="InstallInitialize" Condition="Installed" />
|
||||
<Custom Action="SetPropertyIsServiceRunning.SetParam.AppName" Before="SetPropertyIsServiceRunning" Condition="Installed" />
|
||||
<Custom Action="SetPropertyIsServiceRunning.SetParam.PropertyName" Before="SetPropertyIsServiceRunning" Condition="Installed" />
|
||||
|
||||
<Custom Action="SetPropertyServiceStop" After="InstallInitialize" Condition="NOT Installed" />
|
||||
<Custom Action="SetPropertyServiceStop.SetParam.ConfigFile" Before="SetPropertyServiceStop" Condition="NOT Installed" />
|
||||
<Custom Action="SetPropertyServiceStop.SetParam.ConfigKey" Before="SetPropertyServiceStop" Condition="NOT Installed" />
|
||||
<Custom Action="SetPropertyServiceStop.SetParam.PropertyName" Before="SetPropertyServiceStop" Condition="NOT Installed" />
|
||||
|
||||
<!-- Do not call CreateStartService if is uninstalling. -->
|
||||
<!-- (Installed AND REMOVE AND NOT UPGRADINGPRODUCTCODE) means uninstalling. -->
|
||||
<Custom Action="CreateStartService" Before="InstallFinalize" Condition="(NOT (Installed AND REMOVE AND NOT UPGRADINGPRODUCTCODE)) AND (NOT STOP_SERVICE="'Y'") AND (NOT CC_CONNECTION_TYPE="outgoing")" />
|
||||
<Custom Action="CreateStartService.SetParam" Before="CreateStartService" Condition="(NOT (Installed AND REMOVE AND NOT UPGRADINGPRODUCTCODE)) AND (NOT STOP_SERVICE="'Y'") AND (NOT CC_CONNECTION_TYPE="outgoing")" />
|
||||
|
||||
<Custom Action="CustomActionHello" Before="InstallFinalize" />
|
||||
|
||||
<!--Shortcut is in InstallValidate section. So we just let it be created, then try delete if stopping service.-->
|
||||
<Custom Action="TryDeleteStartupShortcut" After="InstallFinalize" Condition="STOP_SERVICE="'Y'"" />
|
||||
<Custom Action="TryDeleteStartupShortcut.SetParam" Before="SetPropertyIsServiceRunning" Condition="STOP_SERVICE="'Y'"" />
|
||||
|
||||
<!-- Launch ClientLauncher if installing or already installed and not uninstalling -->
|
||||
<!-- https://learn.microsoft.com/en-us/windows/win32/msi/uilevel -->
|
||||
<Custom Action="LaunchApp" After="InstallFinalize" Condition="(NOT UILevel=2) AND (NOT (Installed AND REMOVE AND NOT UPGRADINGPRODUCTCODE)) "/>
|
||||
<Custom Action="LaunchAppTray" After="InstallFinalize" Condition="(LAUNCH_TRAY_APP="Y" OR LAUNCH_TRAY_APP="1") AND (NOT (Installed AND REMOVE AND NOT UPGRADINGPRODUCTCODE)) AND (NOT STOP_SERVICE="'Y'") AND (NOT CC_CONNECTION_TYPE="outgoing")"/>
|
||||
|
||||
<!-- https://learn.microsoft.com/en-us/windows/win32/msi/operating-system-property-values -->
|
||||
<!-- We have to use `VersionNT` to instead of `IsWindows10OrGreater()` in the custom action.
|
||||
Because `IsWindows10OrGreater()` requires the manifest file to be embedded in the executable/dll file.
|
||||
Even I have embedded the manifest file, it still does not work correctly in my case.
|
||||
https://learn.microsoft.com/en-us/windows/win32/sysinfo/version-helper-apis -->
|
||||
<!-- VersionNT >= 603 means can't differentiate between Windows 8.1 and Windows 10.
|
||||
Some msi packages reset the `VersionNT` value to 1000 on Windows 10.
|
||||
https://www.advancedinstaller.com/user-guide/qa-OS-dependent-install.html -->
|
||||
<!-- Remote printer also works on Win8.1 in my test. -->
|
||||
<Custom Action="InstallPrinter" Before="InstallFinalize" Condition="VersionNT >= 603 AND (PRINTER = 1 OR PRINTER = "Y" OR PRINTER = "y")" />
|
||||
<Custom Action="InstallPrinter.SetParam" Before="InstallPrinter" Condition="VersionNT >= 603" />
|
||||
|
||||
<!--Workaround of "fire:FirewallException". If Outbound="Yes" or Outbound="true", the following error occurs.-->
|
||||
<!--ExecFirewallExceptions: Error 0x80070057: failed to add app to the authorized apps list-->
|
||||
<Custom Action="AddFirewallRules" Before="InstallFinalize" Condition="NOT (Installed AND REMOVE AND NOT UPGRADINGPRODUCTCODE)"/>
|
||||
<Custom Action="AddFirewallRules.SetParam" Before="AddFirewallRules" Condition="NOT (Installed AND REMOVE AND NOT UPGRADINGPRODUCTCODE)"/>
|
||||
|
||||
<Custom Action="AddRegSoftwareSASGeneration" Before="InstallFinalize" Condition="NOT (Installed AND REMOVE AND NOT UPGRADINGPRODUCTCODE) AND (NOT CC_CONNECTION_TYPE="outgoing")"/>
|
||||
|
||||
<Custom Action="RemoveRuntimeGeneratedFiles" Before="RemoveFiles" Condition="Installed AND (REMOVE="ALL" OR UPGRADINGPRODUCTCODE)"/>
|
||||
<Custom Action="RemoveRuntimeGeneratedFiles.SetParam" Before="RemoveRuntimeGeneratedFiles" Condition="Installed AND (REMOVE="ALL" OR UPGRADINGPRODUCTCODE)"/>
|
||||
<Custom Action="TryStopDeleteService" Before="RemoveRuntimeGeneratedFiles.SetParam" />
|
||||
<Custom Action="TryStopDeleteService.SetParam" Before="TryStopDeleteService" />
|
||||
|
||||
<Custom Action="RemoveFirewallRules" Before="RemoveFiles"/>
|
||||
<Custom Action="RemoveFirewallRules.SetParam" Before="RemoveFirewallRules"/>
|
||||
|
||||
<Custom Action="UninstallPrinter" Before="RemoveRuntimeGeneratedFiles" Condition="VersionNT >= 603" />
|
||||
|
||||
<Custom Action="TerminateProcesses" Before="RemoveRuntimeGeneratedFiles"/>
|
||||
<Custom Action="TerminateProcesses.SetParam" Before="TerminateProcesses"/>
|
||||
<Custom Action="TerminateBrokers" Before="RemoveRuntimeGeneratedFiles"/>
|
||||
<Custom Action="TerminateBrokers.SetParam" Before="TerminateBrokers"/>
|
||||
<Custom Action="RemoveAmyuniIdd" Before="RemoveRuntimeGeneratedFiles"/>
|
||||
<Custom Action="RemoveAmyuniIdd.SetParam" Before="RemoveAmyuniIdd"/>
|
||||
</InstallExecuteSequence>
|
||||
|
||||
<!-- Shortcuts -->
|
||||
<DirectoryRef Id="App.StartMenu">
|
||||
<Component Id="App.StartMenu" Guid="30F6D57A-B805-4DA4-A071-05A3B22400CA">
|
||||
<RegistryValue Root="HKCU" Key="Software\$(var.Product)" Name="App.StartMenu" Type="string" Value="1" KeyPath="yes" />
|
||||
<RemoveFolder Id="Remove.App.StartMenu" On="uninstall" />
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
|
||||
<DirectoryRef Id="App.StartMenu">
|
||||
<Component Id="App.StartMenu.Shortcut" Guid="43ABCAC7-E47D-42D8-A408-25EC70DBB993" Condition="STARTMENUSHORTCUTS = 1 OR STARTMENUSHORTCUTS = "Y" OR STARTMENUSHORTCUTS = "y"">
|
||||
<Shortcut Id="App.StartMenu.Shortcut" Name="!(loc.SC_Client)" Description="!(loc.SC_Client_Desc)" Target="[!App.exe]" Icon="AppIcon" WorkingDirectory="INSTALLFOLDER_INNER" />
|
||||
<!--
|
||||
Fix ICE 38 by adding a dummy registry key that is the key for this shortcut.
|
||||
https://learn.microsoft.com/en-us/windows/win32/msi/ice38
|
||||
-->
|
||||
<RegistryValue Root="HKCU" Key="Software\$(var.Product)" Name="App.StartMenu.Shortcut" Type="string" Value="1" KeyPath="yes" />
|
||||
</Component>
|
||||
|
||||
<Component Id="App.StartMenu.ShortcutUninstall" Guid="E100D7F8-D607-4513-28DA-2C95E5EA698E" Condition="STARTMENUSHORTCUTS = 1 OR STARTMENUSHORTCUTS = "Y" OR STARTMENUSHORTCUTS = "y"">
|
||||
<Shortcut Id="App.StartMenu.ShortcutUninstall" Name="!(loc.SC_Uninstall)" Description="!(loc.SC_Uninstall_Desc)" Target="[System6432Folder]msiexec.exe" Arguments="/x [ProductCode]" Icon="AppIcon" />
|
||||
<RegistryValue Root="HKCU" Key="Software\$(var.Product)" Name="App.StartMenu.ShortcutUninstall" Type="string" Value="1" KeyPath="yes" />
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
<StandardDirectory Id="DesktopFolder">
|
||||
<Component Id="App.Desktop.Shortcut" Guid="CA8FB7AA-17F7-4E36-A58A-5A016A303709" Condition="DESKTOPSHORTCUTS = 1 OR DESKTOPSHORTCUTS = "Y" OR DESKTOPSHORTCUTS = "y"">
|
||||
<Shortcut Id="App.Desktop.Shortcut" Name="!(loc.SC_Client)" Description="!(loc.SC_Client_Desc)" Target="[!App.exe]" Icon="AppIcon" WorkingDirectory="INSTALLFOLDER_INNER" />
|
||||
<RegistryValue Root="HKCU" Key="Software\$(var.Product)" Name="App.Desktop.Shortcut" Type="string" Value="1" KeyPath="yes" />
|
||||
</Component>
|
||||
</StandardDirectory>
|
||||
<StandardDirectory Id="StartupFolder">
|
||||
<Component Id="App.StartupFolder.ShortcutTray" Guid="B1D1E2BB-E53E-E159-DB7C-744D5C726A8C" Condition="STARTUPSHORTCUTS = 1 AND (NOT CC_CONNECTION_TYPE="outgoing")">
|
||||
<Shortcut Id="App.StartupFolder.ShortcutTray" Name="!(loc.SC_Client_Tray)" Description="!(loc.SC_Client_Tray_Desc)" Target="[!App.exe]" Arguments="--tray" Icon="AppIcon" WorkingDirectory="INSTALLFOLDER_INNER" />
|
||||
<RegistryValue Root="HKCU" Key="Software\$(var.Product)" Name="App.StartupFolder.ShortcutTray" Type="string" Value="1" KeyPath="yes" />
|
||||
</Component>
|
||||
</StandardDirectory>
|
||||
|
||||
<!--<DirectoryRef Id="INSTALLFOLDER_INNER">
|
||||
<Component Id="App.UninstallShortcut" Guid="FB0F2AC7-2AE5-4C54-B860-5E472620B6B1">
|
||||
<Shortcut Id="App.UninstallShortcut" Name="!(loc.SC_Uninstall)" Description="!(loc.SC_Uninstall_Desc)" Target="[System6432Folder]msiexec.exe" Arguments="/x [ProductCode]" Icon="AppIcon" />
|
||||
</Component>
|
||||
</DirectoryRef>-->
|
||||
|
||||
<ComponentGroup Id="Components" Directory="INSTALLFOLDER_INNER">
|
||||
<ComponentRef Id="App.exe" />
|
||||
<ComponentRef Id="App.Desktop.Shortcut" />
|
||||
<!--<ComponentRef Id="App.UninstallShortcut" />-->
|
||||
<ComponentRef Id="App.StartMenu.Shortcut" />
|
||||
<ComponentRef Id="App.StartMenu.ShortcutUninstall" />
|
||||
<ComponentRef Id="App.StartupFolder.ShortcutTray" />
|
||||
|
||||
<!--$AutoComonentStart$-->
|
||||
<!--$AutoComponentEnd$-->
|
||||
|
||||
</ComponentGroup>
|
||||
|
||||
</Fragment>
|
||||
</Wix>
|
||||
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
|
||||
<?include ../Includes.wxi?>
|
||||
|
||||
<Fragment>
|
||||
|
||||
<Property Id="AddRemovePropertiesFile" Value="1" />
|
||||
|
||||
<!--STOP_SERVICE is set to 'Y'. Because the config value may be empty or 'Y'-->
|
||||
<Property Id="STOP_SERVICE" Value="'Y'" />
|
||||
|
||||
<Property Id="LAUNCH_TRAY_APP" Value="Y" />
|
||||
|
||||
<!--
|
||||
Support entries shown when clicking "Click here for support information"
|
||||
in Control Panel's Add/Remove Programs https://learn.microsoft.com/en-us/windows/win32/msi/property-reference
|
||||
-->
|
||||
<!--<Property Id="ARPCOMMENTS" Value="!(loc.AR_Comment)" />
|
||||
<Property Id="ARPCONTACT" Value="https://github.com/rustdesk/rustdesk" />
|
||||
<Property Id="ARPHELPLINK" Value="https://github.com/rustdesk/rustdesk" />
|
||||
<Property Id="ARPREADME" Value="https://github.com/rustdesk/rustdesk" />
|
||||
<Property Id="ARPURLINFOABOUT" Value="https://github.com/rustdesk/rustdesk" />
|
||||
<Property Id="ARPURLUPDATEINFO" Value="https://github.com/rustdesk/rustdesk" />-->
|
||||
|
||||
<Property Id="ARPPRODUCTICON" Value="AppIcon" />
|
||||
|
||||
<!--$ArpStart$-->
|
||||
<!--$ArpEnd$-->
|
||||
|
||||
<!--$CustomClientPropsStart$-->
|
||||
<!--$CustomClientPropsEnd$-->
|
||||
|
||||
<Property Id="APP_WINDOWS_INSTALLER">
|
||||
<RegistrySearch Id="AppWindowsInstallerFolderSearch" Root="HKLM" Key="Software\Microsoft\Windows\CurrentVersion\Uninstall\$(var.Product)" Name="WindowsInstaller" Type="raw" />
|
||||
</Property>
|
||||
</Fragment>
|
||||
</Wix>
|
||||
@@ -0,0 +1,23 @@
|
||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
|
||||
<Fragment>
|
||||
<?include ../Includes.wxi?>
|
||||
|
||||
<Binary Id="Custom_Actions_Dll" SourceFile="$(var.CustomActions.TargetDir)$(var.CustomActions.TargetName).dll" />
|
||||
|
||||
<CustomAction Id="CustomActionHello" DllEntry="CustomActionHello" Impersonate="yes" Execute="immediate" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||
<CustomAction Id="RemoveRuntimeGeneratedFiles" DllEntry="RemoveRuntimeGeneratedFiles" Impersonate="no" Execute="deferred" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||
<CustomAction Id="TerminateProcesses" DllEntry="TerminateProcesses" Impersonate="yes" Execute="immediate" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||
<CustomAction Id="TerminateBrokers" DllEntry="TerminateProcesses" Impersonate="yes" Execute="immediate" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||
<CustomAction Id="AddFirewallRules" DllEntry="AddFirewallRules" Impersonate="no" Execute="deferred" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||
<CustomAction Id="RemoveFirewallRules" DllEntry="AddFirewallRules" Impersonate="no" Execute="deferred" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||
<CustomAction Id="SetPropertyIsServiceRunning" DllEntry="SetPropertyIsServiceRunning" Impersonate="yes" Execute="immediate" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||
<CustomAction Id="CreateStartService" DllEntry="CreateStartService" Impersonate="no" Execute="deferred" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||
<CustomAction Id="TryStopDeleteService" DllEntry="TryStopDeleteService" Impersonate="no" Execute="deferred" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||
<CustomAction Id="TryDeleteStartupShortcut" DllEntry="TryDeleteStartupShortcut" Impersonate="yes" Execute="immediate" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||
<CustomAction Id="SetPropertyServiceStop" DllEntry="SetPropertyFromConfig" Impersonate="yes" Execute="immediate" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||
<CustomAction Id="AddRegSoftwareSASGeneration" DllEntry="AddRegSoftwareSASGeneration" Impersonate="no" Execute="deferred" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||
<CustomAction Id="RemoveAmyuniIdd" DllEntry="RemoveAmyuniIdd" Impersonate="no" Execute="deferred" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||
<CustomAction Id="InstallPrinter" DllEntry="InstallPrinter" Impersonate="no" Execute="deferred" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||
<CustomAction Id="UninstallPrinter" DllEntry="UninstallPrinter" Impersonate="no" Execute="deferred" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||
</Fragment>
|
||||
</Wix>
|
||||
@@ -0,0 +1,87 @@
|
||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
|
||||
<Fragment>
|
||||
|
||||
<?include ..\Includes.wxi?>
|
||||
|
||||
<!--
|
||||
Properties and related actions for specifying whether to install shortcuts and the printer.
|
||||
-->
|
||||
|
||||
<!-- These are the actual properties that get used in conditions to determine whether to
|
||||
install start menu shortcuts or the printer. Shortcut properties default to install;
|
||||
PRINTER defaults to not install. The CREATE* properties below update shortcut
|
||||
properties from command line, bundle, or registry values. -->
|
||||
<Property Id="STARTMENUSHORTCUTS" Value="1" Secure="yes"></Property>
|
||||
<Property Id="DESKTOPSHORTCUTS" Value="1" Secure="yes"></Property>
|
||||
<Property Id="STARTUPSHORTCUTS" Value="1" Secure="yes"></Property>
|
||||
<Property Id="PRINTER" Secure="yes"></Property>
|
||||
|
||||
<!-- These properties get set from either the command line, bundle or registry value,
|
||||
if set they update the properties above with their value. -->
|
||||
<Property Id="CREATESTARTMENUSHORTCUTS" Secure="yes">
|
||||
<RegistrySearch Id="CreateStartMenuShortcutsSearch" Root="HKCR" Key="$(var.RegKeyRoot)" Name="STARTMENUSHORTCUTS" Type="raw" />
|
||||
</Property>
|
||||
<Property Id="CREATEDESKTOPSHORTCUTS" Secure="yes">
|
||||
<RegistrySearch Id="CreateDesktopShortcutsSearch" Root="HKCR" Key="$(var.RegKeyRoot)" Name="DESKTOPSHORTCUTS" Type="raw" />
|
||||
</Property>
|
||||
<Property Id="INSTALLPRINTER" Secure="yes">
|
||||
<RegistrySearch Id="InstallPrinterSearch" Root="HKCR" Key="$(var.RegKeyRoot)" Name="PRINTER" Type="raw" />
|
||||
</Property>
|
||||
|
||||
<!-- Component that persists the property values to the registry so they are available during an upgrade/modify -->
|
||||
<DirectoryRef Id="INSTALLFOLDER_INNER">
|
||||
<Component Id="Product.Registry.PersistedStartMenuShortcutProperties1" Guid="62F79BCF-3367-4ACF-950F-F8BCABACDDC0" Condition="STARTMENUSHORTCUTS = 1 OR STARTMENUSHORTCUTS = "Y" OR STARTMENUSHORTCUTS = "y"">
|
||||
<RegistryKey Root="HKCR" Key="$(var.RegKeyRoot)">
|
||||
<RegistryValue Type="string" Name="STARTMENUSHORTCUTS" Value="1" KeyPath="yes" />
|
||||
</RegistryKey>
|
||||
</Component>
|
||||
<Component Id="Product.Registry.PersistedStartMenuShortcutProperties0" Guid="8EA2D5A8-6E5D-4BDD-9019-2099297FF519" Condition="NOT (STARTMENUSHORTCUTS = 1 OR STARTMENUSHORTCUTS = "Y" OR STARTMENUSHORTCUTS = "y")">
|
||||
<RegistryKey Root="HKCR" Key="$(var.RegKeyRoot)">
|
||||
<RegistryValue Type="string" Name="STARTMENUSHORTCUTS" Value="0" KeyPath="yes" />
|
||||
</RegistryKey>
|
||||
</Component>
|
||||
<Component Id="Product.Registry.PersistedDesktopShortcutProperties1" Guid="1BBAD054-6EC2-4362-BF1B-E8BDE988B597" Condition="DESKTOPSHORTCUTS = 1 OR DESKTOPSHORTCUTS = "Y" OR DESKTOPSHORTCUTS = "y"">
|
||||
<RegistryKey Root="HKCR" Key="$(var.RegKeyRoot)">
|
||||
<RegistryValue Type="string" Name="DESKTOPSHORTCUTS" Value="1" />
|
||||
</RegistryKey>
|
||||
</Component>
|
||||
<Component Id="Product.Registry.PersistedDesktopShortcutProperties0" Guid="FA992614-D2E1-4795-9696-D45A5EF1B9C8" Condition="NOT (DESKTOPSHORTCUTS = 1 OR DESKTOPSHORTCUTS = "Y" OR DESKTOPSHORTCUTS = "y")">
|
||||
<RegistryKey Root="HKCR" Key="$(var.RegKeyRoot)">
|
||||
<RegistryValue Type="string" Name="DESKTOPSHORTCUTS" Value="0" />
|
||||
</RegistryKey>
|
||||
</Component>
|
||||
<Component Id="Product.Registry.PersistedPrinterProperties1" Guid="AF617116-2502-EB3D-5B52-B47AA89EB4B0" Condition="PRINTER = 1 OR PRINTER = "Y" OR PRINTER = "y"">
|
||||
<RegistryKey Root="HKCR" Key="$(var.RegKeyRoot)">
|
||||
<RegistryValue Type="string" Name="PRINTER" Value="1" />
|
||||
</RegistryKey>
|
||||
</Component>
|
||||
<Component Id="Product.Registry.PersistedPrinterProperties0" Guid="51F944D3-AAEB-F167-03A1-081A38E9468A" Condition="NOT (PRINTER = 1 OR PRINTER = "Y" OR PRINTER = "y")">
|
||||
<RegistryKey Root="HKCR" Key="$(var.RegKeyRoot)">
|
||||
<RegistryValue Type="string" Name="PRINTER" Value="0" />
|
||||
</RegistryKey>
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
|
||||
<!-- If a property value has been passed via the command line (which includes when set from the bundle), the registry search will
|
||||
overwrite the command line value, these actions temporarily store the command line value before the registry search
|
||||
is performed so they can be restored after the registry search is complete -->
|
||||
<SetProperty Id="SavedStartMenuShortcutsCmdLineValue" Value="[CREATESTARTMENUSHORTCUTS]" Before="AppSearch" Sequence="first" Condition="CREATESTARTMENUSHORTCUTS" />
|
||||
<SetProperty Id="SavedDesktopShortcutsCmdLineValue" Value="[CREATEDESKTOPSHORTCUTS]" Before="AppSearch" Sequence="first" Condition="CREATEDESKTOPSHORTCUTS" />
|
||||
<SetProperty Id="SavedPrinterCmdLineValue" Value="[INSTALLPRINTER]" Before="AppSearch" Sequence="first" Condition="INSTALLPRINTER" />
|
||||
|
||||
<!-- If a command line value was stored, restore it after the registry search has been performed -->
|
||||
<SetProperty Action="RestoreSavedStartMenuShortcutsValue" Id="CREATESTARTMENUSHORTCUTS" Value="[SavedStartMenuShortcutsCmdLineValue]" After="AppSearch" Sequence="first" Condition="SavedStartMenuShortcutsCmdLineValue" />
|
||||
<SetProperty Action="RestoreSavedDesktopShortcutsValue" Id="CREATEDESKTOPSHORTCUTS" Value="[SavedDesktopShortcutsCmdLineValue]" After="AppSearch" Sequence="first" Condition="SavedDesktopShortcutsCmdLineValue" />
|
||||
<SetProperty Action="RestoreSavedPrinterValue" Id="INSTALLPRINTER" Value="[SavedPrinterCmdLineValue]" After="AppSearch" Sequence="first" Condition="SavedPrinterCmdLineValue" />
|
||||
|
||||
<!-- If a command line value or registry value was set, update the main properties with the value -->
|
||||
<SetProperty Id="STARTMENUSHORTCUTS" Value="" After="RestoreSavedStartMenuShortcutsValue" Sequence="first" Condition="CREATESTARTMENUSHORTCUTS AND NOT (CREATESTARTMENUSHORTCUTS = 1 OR CREATESTARTMENUSHORTCUTS = "Y" OR CREATESTARTMENUSHORTCUTS = "y")" />
|
||||
<SetProperty Id="DESKTOPSHORTCUTS" Value="" After="RestoreSavedDesktopShortcutsValue" Sequence="first" Condition="CREATEDESKTOPSHORTCUTS AND NOT (CREATEDESKTOPSHORTCUTS = 1 OR CREATEDESKTOPSHORTCUTS = "Y" OR CREATEDESKTOPSHORTCUTS = "y")" />
|
||||
<!-- PRINTER defaults to empty now, so a saved or command-line INSTALLPRINTER=1
|
||||
must explicitly enable the main PRINTER property. Non-truthy INSTALLPRINTER
|
||||
values still clear PRINTER so upgrades preserve an explicit disabled choice. -->
|
||||
<SetProperty Action="SetPrinterValueEnabled" Id="PRINTER" Value="1" After="RestoreSavedPrinterValue" Sequence="first" Condition="INSTALLPRINTER = 1 OR INSTALLPRINTER = "Y" OR INSTALLPRINTER = "y"" />
|
||||
<SetProperty Action="SetPrinterValueDisabled" Id="PRINTER" Value="" After="SetPrinterValueEnabled" Sequence="first" Condition="INSTALLPRINTER AND NOT (INSTALLPRINTER = 1 OR INSTALLPRINTER = "Y" OR INSTALLPRINTER = "y")" />
|
||||
|
||||
</Fragment>
|
||||
</Wix>
|
||||
@@ -0,0 +1,10 @@
|
||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
|
||||
<Fragment>
|
||||
|
||||
<Property Id="UpgradesFile" Value="1" />
|
||||
|
||||
<!--$UpgradeStart$-->
|
||||
<!--$UpgradeEnd$-->
|
||||
|
||||
</Fragment>
|
||||
</Wix>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Include xmlns="http://wixtoolset.org/schemas/v4/wxs">
|
||||
|
||||
<!--$PreVarsStart$-->
|
||||
<!--$PreVarsEnd$-->
|
||||
|
||||
</Include>
|
||||
@@ -0,0 +1,56 @@
|
||||
<!--
|
||||
This file contains the declaration of all the localizable strings.
|
||||
-->
|
||||
<WixLocalization Culture="en-us" Codepage="1252"
|
||||
xmlns="http://wixtoolset.org/schemas/v4/wxl">
|
||||
|
||||
<String Id="SummaryCodepage" Value="1252" />
|
||||
<String Id="ProductLanguage" Value="1033" />
|
||||
|
||||
<!-- General strings -->
|
||||
<String Id="DowngradeError" Value="A newer version of [ProductName] is already installed." />
|
||||
|
||||
<String Id="AR_Comment" Value="RustDesk" />
|
||||
|
||||
<String Id="F_App" Value="RustDesk" />
|
||||
<String Id="F_App_Desc" Value="RustDesk - Main programs installations." />
|
||||
|
||||
<String Id="SC_Uninstall" Value="Uninstall RustDesk" />
|
||||
<String Id="SC_Uninstall_Desc" Value="Removes RustDesk or parts of it from the computer" />
|
||||
|
||||
<!-- Client related strings -->
|
||||
<String Id="F_Client" Value="Client" />
|
||||
<String Id="F_Client_Desc" Value="The user interface. Plays media files." />
|
||||
<String Id="F_Client_Plugins" Value="Plugins" />
|
||||
<String Id="F_Client_Plugins_Desc" Value="Plugins for the client." />
|
||||
<String Id="F_LAVFilters" Value="LAV Filters" />
|
||||
<String Id="F_LAVFilters_Desc" Value="Recommended directshow filters for best audio and video playback experience." />
|
||||
|
||||
<String Id="SC_Client" Value="RustDesk" />
|
||||
<String Id="SC_Client_Desc" Value="Start RustDesk." />
|
||||
|
||||
<String Id="SC_Client_Tray" Value="RustDesk Tray" />
|
||||
<String Id="SC_Client_Tray_Desc" Value="Start RustDesk tray." />
|
||||
|
||||
<!-- Server related strings -->
|
||||
<String Id="F_Server" Value="Server" />
|
||||
<String Id="F_Server_Desc" Value="The server part of RustDesk. Provides the MediaLibrary and other services." />
|
||||
<String Id="F_Server_Plugins" Value="Plugins" />
|
||||
<String Id="F_Server_Plugins_Desc" Value="Plugins for the server." />
|
||||
|
||||
<String Id="Service_DisplayName" Value="RustDesk Service" />
|
||||
<String Id="Service_Description" Value="This service runs the RustDesk Server." />
|
||||
|
||||
<!-- Launch Conditions -->
|
||||
<String Id="LC_OS" Value="[ProductName] requires minimum Windows 7 / 2008 R2 as operating system." />
|
||||
<String Id="LC_ADMIN" Value="You need to be an administrator to install [ProductName]." />
|
||||
|
||||
<!-- User Interfaces -->
|
||||
<String Id="AnotherAppDialogTitle" Value="Cancel installation."/>
|
||||
<String Id="AnotherAppDialogDescription" Value="The application is installed by self-installation method, please uninstall it first."/>
|
||||
|
||||
<String Id="MyInstallDirDlgDesktopShortcuts" Value="Create desktop icon" />
|
||||
<String Id="MyInstallDirDlgStartMenuShortcuts" Value="Create start menu shortcuts" />
|
||||
<String Id="MyInstallDirDlgPrinter" Value="Install RustDesk Printer" />
|
||||
|
||||
</WixLocalization>
|
||||
@@ -0,0 +1,32 @@
|
||||
<!--
|
||||
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
|
||||
The use and distribution terms for this software are covered by the
|
||||
Common Public License 1.0 (http://opensource.org/licenses/cpl1.0.php)
|
||||
which can be found in the file CPL.TXT at the root of this distribution.
|
||||
By using this software in any fashion, you are agreeing to be bound by
|
||||
the terms of this license.
|
||||
|
||||
You must not remove this notice, or any other, from this software.
|
||||
-->
|
||||
<!--
|
||||
Adapted by RustDesk for Custom Dialogs insertions
|
||||
-->
|
||||
<WixLocalization Culture="en-us"
|
||||
xmlns="http://wixtoolset.org/schemas/v4/wxl">
|
||||
|
||||
<!-- Firewall Extension needs translation here -->
|
||||
<String Id="msierrFirewallCannotConnect" Overridable="yes" Value="Cannot connect to Windows Firewall. ([2] [3] [4] [5])" />
|
||||
|
||||
<String Id="WixSchedFirewallExceptionsInstall" Overridable="yes" Value="Configuring Windows Firewall" />
|
||||
<String Id="WixSchedFirewallExceptionsUninstall" Overridable="yes" Value="Configuring Windows Firewall" />
|
||||
<String Id="WixRollbackFirewallExceptionsInstall" Overridable="yes" Value="Rolling back Windows Firewall configuration" />
|
||||
<String Id="WixExecFirewallExceptionsInstall" Overridable="yes" Value="Installing Windows Firewall configuration" />
|
||||
<String Id="WixRollbackFirewallExceptionsUninstall" Overridable="yes" Value="Rolling back Windows Firewall configuration" />
|
||||
<String Id="WixExecFirewallExceptionsUninstall" Overridable="yes" Value="Uninstalling Windows Firewall configuration" />
|
||||
|
||||
<!-- Util Extension does not need translation here, because it is already in official WiX Toolkit -->
|
||||
<String Id="msierrSecureObjectsFailedCreateSD" Overridable="yes" Value="Could not create security descriptor [3]\[4], error: [2]" />
|
||||
<String Id="msierrSecureObjectsFailedSet" Overridable="yes" Value="Could not apply object security descriptor [3], error: [2]" />
|
||||
<String Id="msierrSecureObjectsUnknownType" Overridable="yes" Value="Unknown object type [3], error: [2]" />
|
||||
</WixLocalization>
|
||||
@@ -0,0 +1,303 @@
|
||||
{\rtf1\adeflang1025\ansi\ansicpg1252\uc1\adeff1\deff0\stshfdbch31505\stshfloch31506\stshfhich31506\stshfbi0\deflang1033\deflangfe2052\themelang1033\themelangfe0\themelangcs0{\fonttbl{\f0\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f1\fbidi \fswiss\fcharset0\fprq2{\*\panose 020b0604020202020204}Arial;}
|
||||
{\f34\fbidi \froman\fcharset0\fprq2{\*\panose 02040503050406030204}Cambria Math;}{\flomajor\f31500\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}
|
||||
{\fdbmajor\f31501\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fhimajor\f31502\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0302020204030204}Calibri Light;}
|
||||
{\fbimajor\f31503\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\flominor\f31504\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}
|
||||
{\fdbminor\f31505\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fhiminor\f31506\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0502020204030204}Calibri;}
|
||||
{\fbiminor\f31507\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f45\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\f46\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}
|
||||
{\f48\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\f49\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\f50\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\f51\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}
|
||||
{\f52\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\f53\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\f55\fbidi \fswiss\fcharset238\fprq2 Arial CE;}{\f56\fbidi \fswiss\fcharset204\fprq2 Arial Cyr;}
|
||||
{\f58\fbidi \fswiss\fcharset161\fprq2 Arial Greek;}{\f59\fbidi \fswiss\fcharset162\fprq2 Arial Tur;}{\f60\fbidi \fswiss\fcharset177\fprq2 Arial (Hebrew);}{\f61\fbidi \fswiss\fcharset178\fprq2 Arial (Arabic);}
|
||||
{\f62\fbidi \fswiss\fcharset186\fprq2 Arial Baltic;}{\f63\fbidi \fswiss\fcharset163\fprq2 Arial (Vietnamese);}{\f385\fbidi \froman\fcharset238\fprq2 Cambria Math CE;}{\f386\fbidi \froman\fcharset204\fprq2 Cambria Math Cyr;}
|
||||
{\f388\fbidi \froman\fcharset161\fprq2 Cambria Math Greek;}{\f389\fbidi \froman\fcharset162\fprq2 Cambria Math Tur;}{\f392\fbidi \froman\fcharset186\fprq2 Cambria Math Baltic;}{\f393\fbidi \froman\fcharset163\fprq2 Cambria Math (Vietnamese);}
|
||||
{\flomajor\f31508\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\flomajor\f31509\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\flomajor\f31511\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}
|
||||
{\flomajor\f31512\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\flomajor\f31513\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\flomajor\f31514\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}
|
||||
{\flomajor\f31515\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\flomajor\f31516\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fdbmajor\f31518\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}
|
||||
{\fdbmajor\f31519\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fdbmajor\f31521\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fdbmajor\f31522\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}
|
||||
{\fdbmajor\f31523\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fdbmajor\f31524\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fdbmajor\f31525\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}
|
||||
{\fdbmajor\f31526\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fhimajor\f31528\fbidi \fswiss\fcharset238\fprq2 Calibri Light CE;}{\fhimajor\f31529\fbidi \fswiss\fcharset204\fprq2 Calibri Light Cyr;}
|
||||
{\fhimajor\f31531\fbidi \fswiss\fcharset161\fprq2 Calibri Light Greek;}{\fhimajor\f31532\fbidi \fswiss\fcharset162\fprq2 Calibri Light Tur;}{\fhimajor\f31533\fbidi \fswiss\fcharset177\fprq2 Calibri Light (Hebrew);}
|
||||
{\fhimajor\f31534\fbidi \fswiss\fcharset178\fprq2 Calibri Light (Arabic);}{\fhimajor\f31535\fbidi \fswiss\fcharset186\fprq2 Calibri Light Baltic;}{\fhimajor\f31536\fbidi \fswiss\fcharset163\fprq2 Calibri Light (Vietnamese);}
|
||||
{\fbimajor\f31538\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fbimajor\f31539\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fbimajor\f31541\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}
|
||||
{\fbimajor\f31542\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fbimajor\f31543\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fbimajor\f31544\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}
|
||||
{\fbimajor\f31545\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fbimajor\f31546\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\flominor\f31548\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}
|
||||
{\flominor\f31549\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\flominor\f31551\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\flominor\f31552\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}
|
||||
{\flominor\f31553\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\flominor\f31554\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\flominor\f31555\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}
|
||||
{\flominor\f31556\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fdbminor\f31558\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fdbminor\f31559\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}
|
||||
{\fdbminor\f31561\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fdbminor\f31562\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fdbminor\f31563\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}
|
||||
{\fdbminor\f31564\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fdbminor\f31565\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fdbminor\f31566\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}
|
||||
{\fhiminor\f31568\fbidi \fswiss\fcharset238\fprq2 Calibri CE;}{\fhiminor\f31569\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr;}{\fhiminor\f31571\fbidi \fswiss\fcharset161\fprq2 Calibri Greek;}{\fhiminor\f31572\fbidi \fswiss\fcharset162\fprq2 Calibri Tur;}
|
||||
{\fhiminor\f31573\fbidi \fswiss\fcharset177\fprq2 Calibri (Hebrew);}{\fhiminor\f31574\fbidi \fswiss\fcharset178\fprq2 Calibri (Arabic);}{\fhiminor\f31575\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic;}
|
||||
{\fhiminor\f31576\fbidi \fswiss\fcharset163\fprq2 Calibri (Vietnamese);}{\fbiminor\f31578\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fbiminor\f31579\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}
|
||||
{\fbiminor\f31581\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbiminor\f31582\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fbiminor\f31583\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}
|
||||
{\fbiminor\f31584\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbiminor\f31585\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fbiminor\f31586\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}}
|
||||
{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0;
|
||||
\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;\red0\green0\blue0;\red0\green0\blue0;}{\*\defchp \fs22\kerning2\loch\af31506\hich\af31506\dbch\af31505 }{\*\defpap
|
||||
\ql \li0\ri0\sa160\sl259\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 }\noqfpromote {\upr{\stylesheet{\ql \li0\ri0\nowidctlpar\wrapdefault\faauto\rin0\lin0\itap0 \rtlch\fcs1 \af1\afs24\alang1025 \ltrch\fcs0
|
||||
\fs24\lang1031\langfe2052\loch\f1\hich\af1\dbch\af31505\cgrid\langnp1031\langfenp2052 \snext0 \sqformat \spriority0 Normal;}{\s1\ql \li0\ri0\nowidctlpar\wrapdefault\faauto\outlinelevel0\rin0\lin0\itap0 \rtlch\fcs1 \af1\afs24\alang1025 \ltrch\fcs0
|
||||
\fs24\lang1031\langfe2052\loch\f1\hich\af1\dbch\af31505\cgrid\langnp1031\langfenp2052 \sbasedon0 \snext0 \slink15 \sqformat heading 1;}{\s2\ql \li0\ri0\nowidctlpar\wrapdefault\faauto\outlinelevel1\rin0\lin0\itap0 \rtlch\fcs1 \af1\afs24\alang1025
|
||||
\ltrch\fcs0 \fs24\lang1031\langfe2052\loch\f1\hich\af1\dbch\af31505\cgrid\langnp1031\langfenp2052 \sbasedon0 \snext0 \slink16 \sqformat heading 2;}{\s3\ql \li0\ri0\sb240\sa60\keepn\nowidctlpar\wrapdefault\faauto\outlinelevel2\rin0\lin0\itap0 \rtlch\fcs1
|
||||
\ab\af0\afs26\alang1025 \ltrch\fcs0 \b\fs26\lang1031\langfe2052\loch\f31502\hich\af31502\dbch\af31501\cgrid\langnp1031\langfenp2052 \sbasedon0 \snext0 \slink17 \ssemihidden \sunhideused \sqformat \spriority9 \styrsid2828669 heading 3;}{\*\cs10 \additive
|
||||
\ssemihidden \sunhideused \spriority1 Default Paragraph Font;}{\*
|
||||
\ts11\tsrowd\trftsWidthB3\trpaddl108\trpaddr108\trpaddfl3\trpaddft3\trpaddfb3\trpaddfr3\trcbpat1\trcfpat1\tblind0\tblindtype3\tsvertalt\tsbrdrt\tsbrdrl\tsbrdrb\tsbrdrr\tsbrdrdgl\tsbrdrdgr\tsbrdrh\tsbrdrv \ql \li0\ri0\sa160\sl259\slmult1
|
||||
\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs22\alang1025 \ltrch\fcs0 \fs22\lang1033\langfe2052\kerning2\loch\f31506\hich\af31506\dbch\af31505\cgrid\langnp1033\langfenp2052
|
||||
\snext11 \ssemihidden \sunhideused Normal Table;}{\*\cs15 \additive \rtlch\fcs1 \ab\af0\afs32 \ltrch\fcs0 \b\fs32\lang1031\langfe0\kerning32\loch\f31502\hich\af31502\dbch\af31501\langnp1031\langfenp0 \sbasedon10 \slink1 \spriority9 ?? 1 ??;}{\*\cs16
|
||||
\additive \rtlch\fcs1 \ab\ai\af0\afs28 \ltrch\fcs0 \b\i\fs28\lang1031\langfe0\kerning0\loch\f31502\hich\af31502\dbch\af31501\langnp1031\langfenp0 \sbasedon10 \slink2 \ssemihidden \spriority9 ?? 2 ??;}{\*\cs17 \additive \rtlch\fcs1 \ab\af0\afs26
|
||||
\ltrch\fcs0 \b\fs26\lang1031\langfe0\kerning0\loch\f31502\hich\af31502\dbch\af31501\langnp1031\langfenp0 \sbasedon10 \slink3 \ssemihidden \spriority9 \styrsid2828669 ?? 3 ??;}}{\*\ud\uc0{\stylesheet{
|
||||
\ql \li0\ri0\nowidctlpar\wrapdefault\faauto\rin0\lin0\itap0 \rtlch\fcs1 \af1\afs24\alang1025 \ltrch\fcs0 \fs24\lang1031\langfe2052\loch\f1\hich\af1\dbch\af31505\cgrid\langnp1031\langfenp2052 \snext0 \sqformat \spriority0 Normal;}{
|
||||
\s1\ql \li0\ri0\nowidctlpar\wrapdefault\faauto\outlinelevel0\rin0\lin0\itap0 \rtlch\fcs1 \af1\afs24\alang1025 \ltrch\fcs0 \fs24\lang1031\langfe2052\loch\f1\hich\af1\dbch\af31505\cgrid\langnp1031\langfenp2052 \sbasedon0 \snext0 \slink15 \sqformat
|
||||
heading 1;}{\s2\ql \li0\ri0\nowidctlpar\wrapdefault\faauto\outlinelevel1\rin0\lin0\itap0 \rtlch\fcs1 \af1\afs24\alang1025 \ltrch\fcs0 \fs24\lang1031\langfe2052\loch\f1\hich\af1\dbch\af31505\cgrid\langnp1031\langfenp2052
|
||||
\sbasedon0 \snext0 \slink16 \sqformat heading 2;}{\s3\ql \li0\ri0\sb240\sa60\keepn\nowidctlpar\wrapdefault\faauto\outlinelevel2\rin0\lin0\itap0 \rtlch\fcs1 \ab\af0\afs26\alang1025 \ltrch\fcs0
|
||||
\b\fs26\lang1031\langfe2052\loch\f31502\hich\af31502\dbch\af31501\cgrid\langnp1031\langfenp2052 \sbasedon0 \snext0 \slink17 \ssemihidden \sunhideused \sqformat \spriority9 \styrsid2828669 heading 3;}{\*\cs10 \additive
|
||||
\ssemihidden \sunhideused \spriority1 Default Paragraph Font;}{\*
|
||||
\ts11\tsrowd\trftsWidthB3\trpaddl108\trpaddr108\trpaddfl3\trpaddft3\trpaddfb3\trpaddfr3\trcbpat1\trcfpat1\tblind0\tblindtype3\tsvertalt\tsbrdrt\tsbrdrl\tsbrdrb\tsbrdrr\tsbrdrdgl\tsbrdrdgr\tsbrdrh\tsbrdrv \ql \li0\ri0\sa160\sl259\slmult1
|
||||
\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs22\alang1025 \ltrch\fcs0 \fs22\lang1033\langfe2052\kerning2\loch\f31506\hich\af31506\dbch\af31505\cgrid\langnp1033\langfenp2052
|
||||
\snext11 \ssemihidden \sunhideused Normal Table;}{\*\cs15 \additive \rtlch\fcs1 \ab\af0\afs32 \ltrch\fcs0 \b\fs32\lang1031\langfe0\kerning32\loch\f31502\hich\af31502\dbch\af31501\langnp1031\langfenp0 \sbasedon10 \slink1 \spriority9
|
||||
{\uc1\u26631 ?\u-26472 ? 1 \u23383 ?\u31526 ?};}{\*\cs16 \additive \rtlch\fcs1 \ab\ai\af0\afs28 \ltrch\fcs0 \b\i\fs28\lang1031\langfe0\kerning0\loch\f31502\hich\af31502\dbch\af31501\langnp1031\langfenp0 \sbasedon10 \slink2 \ssemihidden \spriority9
|
||||
{\uc1\u26631 ?\u-26472 ? 2 \u23383 ?\u31526 ?};}{\*\cs17 \additive \rtlch\fcs1 \ab\af0\afs26 \ltrch\fcs0 \b\fs26\lang1031\langfe0\kerning0\loch\f31502\hich\af31502\dbch\af31501\langnp1031\langfenp0
|
||||
\sbasedon10 \slink3 \ssemihidden \spriority9 \styrsid2828669 {\uc1\u26631 ?\u-26472 ? 3 \u23383 ?\u31526 ?};}}}}{\*\listtable{\list\listtemplateid-1\listhybrid{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0
|
||||
\levelindent0{\leveltext\leveltemplateid67698713\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0 \fi-360\li720\lin720 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0
|
||||
{\leveltext\leveltemplateid67698713\'02\'01.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fi-360\li1440\lin1440 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext
|
||||
\leveltemplateid67698715\'02\'02.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fi-180\li2160\lin2160 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext
|
||||
\leveltemplateid67698703\'02\'03.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fi-360\li2880\lin2880 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext
|
||||
\leveltemplateid67698713\'02\'04.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fi-360\li3600\lin3600 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext
|
||||
\leveltemplateid67698715\'02\'05.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fi-180\li4320\lin4320 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext
|
||||
\leveltemplateid67698703\'02\'06.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fi-360\li5040\lin5040 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext
|
||||
\leveltemplateid67698713\'02\'07.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fi-360\li5760\lin5760 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext
|
||||
\leveltemplateid67698715\'02\'08.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fi-180\li6480\lin6480 }{\listname ;}\listid825630566}}{\*\listoverridetable{\listoverride\listid825630566\listoverridecount0\ls1}}{\*\pgptbl {\pgp\ipgp3\itap0\li0\ri0\sb0
|
||||
\sa0}{\pgp\ipgp1\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp2\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}}{\*\rsidtbl \rsid83947\rsid598512\rsid1001100\rsid1384617\rsid1523795\rsid1598568\rsid1917520\rsid2380571
|
||||
\rsid2828669\rsid3408922\rsid3425199\rsid3630109\rsid3677587\rsid3958023\rsid4071099\rsid4291156\rsid4471570\rsid5244407\rsid5732487\rsid6045443\rsid6178595\rsid7167559\rsid8404575\rsid8598301\rsid8797129\rsid8979511\rsid9005387\rsid9112532\rsid9119790
|
||||
\rsid9381137\rsid9706100\rsid9788126\rsid9898965\rsid10905159\rsid11670652\rsid11828428\rsid12264694\rsid12287738\rsid12650743\rsid12661227\rsid12936610\rsid13186388\rsid13721198\rsid13789023\rsid14101758\rsid14699391\rsid15087333\rsid15155177
|
||||
\rsid15274734\rsid15295953\rsid15335801\rsid16412676}{\mmathPr\mmathFont34\mbrkBin0\mbrkBinSub0\msmallFrac0\mdispDef1\mlMargin0\mrMargin0\mdefJc1\mwrapIndent1440\mintLim0\mnaryLim1}{\info{\operator hoosm}{\creatim\yr2024\mo3\dy30\hr15\min56}
|
||||
{\revtim\yr2024\mo3\dy30\hr16\min44}{\version52}{\edmins17}{\nofpages2}{\nofwords1021}{\nofchars5820}{\nofcharsws6828}{\vern75}}{\*\xmlnstbl {\xmlns1 http://schemas.microsoft.com/office/word/2003/wordml}}
|
||||
\paperw12240\paperh15840\margl1440\margr1440\margt1440\margb1440\gutter0\ltrsect
|
||||
\widowctrl\ftnbj\aenddoc\trackmoves0\trackformatting1\donotembedsysfont0\relyonvml0\donotembedlingdata1\grfdocevents0\validatexml0\showplaceholdtext0\ignoremixedcontent0\saveinvalidxml0\showxmlerrors0\horzdoc\dghspace120\dgvspace120\dghorigin1701
|
||||
\dgvorigin1984\dghshow0\dgvshow3\jcompress\viewkind1\viewscale150\rsidroot598512 \fet0{\*\wgrffmtfilter 2450}\ilfomacatclnup0\ltrpar \sectd \ltrsect\linex0\sectdefaultcl\sftnbj {\*\pnseclvl1\pnucrm\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl2
|
||||
\pnucltr\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl3\pndec\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang {\pntxta )}}{\*\pnseclvl5\pndec\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl6
|
||||
\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl8\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl9\pnlcrm\pnstart1\pnindent720\pnhang
|
||||
{\pntxtb (}{\pntxta )}}\pard\plain \ltrpar\s2\qc \li0\ri0\sb100\sa100\nowidctlpar\wrapdefault\faauto\outlinelevel1\rin0\lin0\itap0 \rtlch\fcs1 \af1\afs24\alang1025 \ltrch\fcs0
|
||||
\fs24\lang1031\langfe2052\loch\af1\hich\af1\dbch\af31505\cgrid\langnp1031\langfenp2052 {\rtlch\fcs1 \ab\af1 \ltrch\fcs0 \b\ul\cf2\lang1033\langfe2052\langnp1033\insrsid2380571\charrsid2380571 \hich\af1\dbch\af31505\loch\f1 Privacy policy}{\rtlch\fcs1
|
||||
\ab\af1 \ltrch\fcs0 \b\ul\cf2\lang1033\langfe2052\langnp1033\insrsid1917520
|
||||
\par }\pard \ltrpar\s2\qj \li0\ri0\sb100\sa100\nowidctlpar\wrapdefault\faauto\outlinelevel1\rin0\lin0\itap0\pararsid8979511 {\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \fs18\lang1033\langfe2052\langnp1033\insrsid8979511\charrsid8979511 \hich\af1\dbch\af31505\loch\f1
|
||||
\hich\f1 This Privacy Policy (hereinafter the \'93}{\rtlch\fcs1 \ab\af1\afs21 \ltrch\fcs0 \b\fs21\lang1033\langfe2052\langnp1033\insrsid8979511\charrsid1523795 \hich\af1\dbch\af31505\loch\f1 Policy}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0
|
||||
\fs18\lang1033\langfe2052\langnp1033\insrsid8979511\charrsid8979511 \loch\af1\dbch\af31505\hich\f1 \'94\loch\f1 \hich\f1 ) governs the terms and conditions under which Purslane Ltd. (hereinafter \'93}{\rtlch\fcs1 \ab\af1\afs21 \ltrch\fcs0
|
||||
\b\fs21\lang1033\langfe2052\langnp1033\insrsid8979511\charrsid1523795 \hich\af1\dbch\af31505\loch\f1 us}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \fs18\lang1033\langfe2052\langnp1033\insrsid8979511\charrsid8979511 \loch\af1\dbch\af31505\hich\f1 \'94\loch\f1
|
||||
\hich\f1 or \'93}{\rtlch\fcs1 \ab\af1\afs21 \ltrch\fcs0 \b\fs21\lang1033\langfe2052\langnp1033\insrsid8979511\charrsid1523795 \hich\af1\dbch\af31505\loch\f1 we}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0
|
||||
\fs18\lang1033\langfe2052\langnp1033\insrsid8979511\charrsid8979511 \loch\af1\dbch\af31505\hich\f1 \'94\loch\f1 \hich\f1
|
||||
), processes personal data in connection with the activities and services concerning the operation of the website rustdesk.com and other websites or social media profiles run and managed by us (hereinafter the \'93}{\rtlch\fcs1 \ab\af1\afs21 \ltrch\fcs0
|
||||
\b\fs21\lang1033\langfe2052\langnp1033\insrsid8979511\charrsid1523795 \hich\af1\dbch\af31505\loch\f1 Websites}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \fs18\lang1033\langfe2052\langnp1033\insrsid8979511\charrsid8979511 \loch\af1\dbch\af31505\hich\f1 \'94
|
||||
\loch\f1 ).
|
||||
\par \hich\af1\dbch\af31505\loch\f1 We are serious about protecting your personal data and want you to feel safe and comfortable while browsing our Websites. We therefore respect \hich\af1\dbch\af31505\loch\f1
|
||||
the confidentiality of your personal data and always proceed in accordance with the provisions of data protection legislation, in particular, Regulation (EU) 2016/679 of the European Parliament and of the Council (General Data Protection Regulation, herei
|
||||
\hich\af1\dbch\af31505\loch\f1 \hich\f1 nafter the \'93}{\rtlch\fcs1 \ab\af1\afs21 \ltrch\fcs0 \b\fs21\lang1033\langfe2052\langnp1033\insrsid8979511\charrsid1523795 \hich\af1\dbch\af31505\loch\f1 GDPR}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0
|
||||
\fs18\lang1033\langfe2052\langnp1033\insrsid8979511\charrsid8979511 \loch\af1\dbch\af31505\hich\f1 \'94\loch\f1 ), and follow this Policy.
|
||||
\par \hich\af1\dbch\af31505\loch\f1 With respect to the above, we use this Policy to inform you about how, for what purposes and to what extent we use your personal data and what information about you as a user of the Websites we may process.
|
||||
\par }\pard \ltrpar\s2\ql \li0\ri0\sb100\sa100\nowidctlpar\wrapdefault\faauto\outlinelevel1\rin0\lin0\itap0 {\rtlch\fcs1 \ab\af1\afs18 \ltrch\fcs0 \b\fs18\lang1033\langfe2052\langnp1033\insrsid1917520 \hich\af1\dbch\af31505\loch\f1 0. Def
|
||||
\hich\af1\dbch\af31505\loch\f1 initions.
|
||||
\par }\pard \ltrpar\s2\qj \li0\ri0\sb100\sa100\nowidctlpar\wrapdefault\faauto\outlinelevel1\rin0\lin0\itap0\pararsid1523795 {\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \fs18\lang1033\langfe2052\langnp1033\insrsid1523795\charrsid1523795 \loch\af1\dbch\af31505\hich\f1
|
||||
\'93}{\rtlch\fcs1 \ab\af1\afs21 \ltrch\fcs0 \b\fs21\lang1033\langfe2052\langnp1033\insrsid1523795\charrsid13186388 \hich\af1\dbch\af31505\loch\f1 Personal data}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0
|
||||
\fs18\lang1033\langfe2052\langnp1033\insrsid1523795\charrsid1523795 \loch\af1\dbch\af31505\hich\f1 \'94\hich\af1\dbch\af31505\loch\f1 means any information relating to a data subject;
|
||||
\par \loch\af1\dbch\af31505\hich\f1 \'93}{\rtlch\fcs1 \ab\af1\afs21 \ltrch\fcs0 \b\fs21\lang1033\langfe2052\langnp1033\insrsid1523795\charrsid13186388 \hich\af1\dbch\af31505\loch\f1 Controller}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0
|
||||
\fs18\lang1033\langfe2052\langnp1033\insrsid1523795\charrsid1523795 \loch\af1\dbch\af31505\hich\f1 \'94\loch\f1
|
||||
means the natural or legal person, public authority, agency or other body which, alone or jointly with others, determines the purposes and means of the processing of personal data;
|
||||
\par \loch\af1\dbch\af31505\hich\f1 \'93}{\rtlch\fcs1 \ab\af1\afs21 \ltrch\fcs0 \b\fs21\lang1033\langfe2052\langnp1033\insrsid1523795\charrsid13186388 \hich\af1\dbch\af31505\loch\f1 Data subject}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0
|
||||
\fs18\lang1033\langfe2052\langnp1033\insrsid1523795\charrsid1523795 \loch\af1\dbch\af31505\hich\f1 \'94\loch\f1
|
||||
means any identified or identifiable person who can be identified, directly or indirectly, in particular by reference to an identifier such as a name, an identification number, location data, an online identifier or to one or more factors specific to t
|
||||
\hich\af1\dbch\af31505\loch\f1 he physical, physiological, genetic, mental, economic, cultural or social identity of that natural person;
|
||||
\par \loch\af1\dbch\af31505\hich\f1 \'93}{\rtlch\fcs1 \ab\af1\afs21 \ltrch\fcs0 \b\fs21\lang1033\langfe2052\langnp1033\insrsid1523795\charrsid13186388 \hich\af1\dbch\af31505\loch\f1 Data processor}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0
|
||||
\fs18\lang1033\langfe2052\langnp1033\insrsid1523795\charrsid1523795 \loch\af1\dbch\af31505\hich\f1 \'94\loch\f1 means a natural or legal person, public authority, agency or other body which processes personal data on behalf of the controller;
|
||||
\par \loch\af1\dbch\af31505\hich\f1 \'93}{\rtlch\fcs1 \ab\af1\afs21 \ltrch\fcs0 \b\fs21\lang1033\langfe2052\langnp1033\insrsid1523795\charrsid13186388 \hich\af1\dbch\af31505\loch\f1 Processing}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0
|
||||
\fs18\lang1033\langfe2052\langnp1033\insrsid1523795\charrsid1523795 \loch\af1\dbch\af31505\hich\f1 \'94\loch\f1
|
||||
means any operation or set of operations which is performed on personal data or on sets of personal data, whether or not by automated means, such as collection, recording, organi}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0
|
||||
\fs18\lang1033\langfe2052\langnp1033\insrsid14101758 \hich\af1\dbch\af31505\loch\f1 z}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \fs18\lang1033\langfe2052\langnp1033\insrsid1523795\charrsid1523795 \hich\af1\dbch\af31505\loch\f1
|
||||
ation, structuring, storage, adaptation or alteration, retrieval,\hich\af1\dbch\af31505\loch\f1 consultation, use, disclosure by transmission, dissemination or otherwise making available, alignment or combination, restriction, erasure or destruction.
|
||||
|
||||
\par }\pard \ltrpar\s2\ql \li0\ri0\sb100\sa100\nowidctlpar\wrapdefault\faauto\outlinelevel1\rin0\lin0\itap0 {\rtlch\fcs1 \ab\af1\afs18 \ltrch\fcs0 \b\fs18\lang1033\langfe2052\langnp1033\insrsid1917520 \hich\af1\dbch\af31505\loch\f1 1. }{\rtlch\fcs1
|
||||
\ab\af1\afs18 \ltrch\fcs0 \b\fs18\lang1033\langfe2052\langnp1033\insrsid13186388\charrsid13186388 \hich\af1\dbch\af31505\loch\f1 Basic information about personal data processing conducted by us}{\rtlch\fcs1 \ab\af1\afs18 \ltrch\fcs0
|
||||
\b\fs18\lang1033\langfe2052\langnp1033\insrsid6045443 .}{\rtlch\fcs1 \ab\af1\afs18 \ltrch\fcs0 \b\fs18\lang1033\langfe2052\langnp1033\insrsid1917520
|
||||
\par }\pard \ltrpar\s2\qj \li0\ri0\sb100\sa100\nowidctlpar\wrapdefault\faauto\outlinelevel1\rin0\lin0\itap0\pararsid12650743 {\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \fs18\lang1033\langfe2052\langnp1033\insrsid12650743\charrsid12650743
|
||||
\hich\af1\dbch\af31505\loch\f1
|
||||
We always process your personal data lawfully, fairly, in a transparent manner and for specified, explicit and legitimate purposes. We process personal data only to the minimum necessary extent and we keep them in a form which permits your identification
|
||||
\hich\af1\dbch\af31505\loch\f1 for no longer than is necessary \hich\af1\dbch\af31505\loch\f1 \hich\f1 vis-\'e0\loch\f1 -vis the purpose of the processing.
|
||||
\par \hich\af1\dbch\af31505\loch\f1 We process your personal data in a manner that sufficiently ensures their integrity and confidentiality, i.e. by appropriate technical or organi}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0
|
||||
\fs18\lang1033\langfe2052\langnp1033\insrsid12661227 \hich\af1\dbch\af31505\loch\f1 z}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \fs18\lang1033\langfe2052\langnp1033\insrsid12650743\charrsid12650743 \hich\af1\dbch\af31505\loch\f1
|
||||
ational measures and appropriate protection against }{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \fs18\lang1033\langfe2052\langnp1033\insrsid6178595 \hich\af1\dbch\af31505\loch\f1 u}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0
|
||||
\fs18\lang1033\langfe2052\langnp1033\insrsid6178595\charrsid6178595 \hich\af1\dbch\af31505\loch\f1 nauthorized }{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \fs18\lang1033\langfe2052\langnp1033\insrsid12650743\charrsid12650743 \hich\af1\dbch\af31505\loch\f1
|
||||
or unlawful processing and against loss, destruction or damage. We take care to ensure that personal data that are inaccurate, having regard to the purpose for which we process them, are erased or rectified without delay.
|
||||
\par \hich\af1\dbch\af31505\loch\f1 We respect the principle of refraining \hich\af1\dbch\af31505\loch\f1 from personal data processing and the principle of data minimi}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \fs18\lang1033\langfe2052\langnp1033\insrsid12287738
|
||||
\hich\af1\dbch\af31505\loch\f1 z}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \fs18\lang1033\langfe2052\langnp1033\insrsid12650743\charrsid12650743 \hich\af1\dbch\af31505\loch\f1
|
||||
ation. We therefore only retain your personal data if it is necessary in order to achieve the purpose of the processing or for various retention periods specified by law. The relevant data are erased in accordance with the law if the relevant purpose ceas
|
||||
\hich\af1\dbch\af31505\loch\f1 es to exist as a result of the withdrawal of your consent and/or upon the expiration of the lawful retention period.
|
||||
\par \hich\af1\dbch\af31505\loch\f1 For the above reasons, we use computer security such as a firewall and data e\hich\af1\dbch\af31505\loch\f1
|
||||
ncryption to operate our Websites. We have implemented adequate physical, electronic and procedural safeguards and use reliable IT service providers. However, given the nature of the internet, we would like to bring to your attention the fact that certain
|
||||
\hich\af1\dbch\af31505\loch\f1 security gaps may exist in the transmission of personal data via the internet (e.g. in communication via e-mail) and that full protection of personal data preventing third party access is impossible.}{\rtlch\fcs1
|
||||
\af1\afs18 \ltrch\fcs0 \fs18\lang1033\langfe2052\langnp1033\insrsid1917520\charrsid12650743
|
||||
\par }\pard \ltrpar\s2\ql \li0\ri0\sb100\sa100\nowidctlpar\wrapdefault\faauto\outlinelevel1\rin0\lin0\itap0 {\rtlch\fcs1 \ab\af1\afs18 \ltrch\fcs0 \b\fs18\lang1033\langfe2052\langnp1033\insrsid1917520 \hich\af1\dbch\af31505\loch\f1 2. }{\rtlch\fcs1
|
||||
\ab\af1\afs18 \ltrch\fcs0 \b\fs18\lang1033\langfe2052\langnp1033\insrsid11670652\charrsid11670652 \hich\af1\dbch\af31505\loch\f1 Legal ground, purpose and extent of the processing of your personal data}{\rtlch\fcs1 \ab\af1\afs18 \ltrch\fcs0
|
||||
\b\fs18\lang1033\langfe2052\langnp1033\insrsid1917520 .
|
||||
\par }\pard \ltrpar\s2\qj \li0\ri0\sb100\sa100\nowidctlpar\wrapdefault\faauto\outlinelevel1\rin0\lin0\itap0\pararsid15087333 {\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \fs18\lang1033\langfe2052\langnp1033\insrsid4291156\charrsid4291156 \hich\af1\dbch\af31505\loch\f1
|
||||
We may process your personal data for the following legal grounds and for the following purposes:}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \fs18\lang1033\langfe2052\langnp1033\insrsid4291156
|
||||
\par }\pard \ltrpar\s2\qj \li0\ri0\sb120\sa120\nowidctlpar\wrapdefault\faauto\outlinelevel1\rin0\lin0\itap0\pararsid9112532 {\rtlch\fcs1 \ab\af1\afs18 \ltrch\fcs0 \b\fs18\lang1033\langfe2052\langnp1033\insrsid1384617\charrsid15295953
|
||||
\hich\af1\dbch\af31505\loch\f1 a. }{\rtlch\fcs1 \ab\af1\afs18 \ltrch\fcs0 \b\fs18\lang1033\langfe2052\langnp1033\insrsid16412676\charrsid15295953 \hich\af1\dbch\af31505\loch\f1 Provision and improvement of and support for our Websites}{\rtlch\fcs1
|
||||
\ab\af1\afs18 \ltrch\fcs0 \b\fs18\lang1033\langfe2052\langnp1033\insrsid83947\charrsid15295953
|
||||
\par }\pard \ltrpar\s2\qj \li0\ri0\sb100\sa100\nowidctlpar\wrapdefault\faauto\outlinelevel1\rin0\lin0\itap0\pararsid1001100 {\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \fs18\lang1033\langfe2052\langnp1033\insrsid9706100\charrsid1001100 \hich\af1\dbch\af31505\loch\f1
|
||||
We process various information about your online activity, e.g. the time of access to our Websites, the time spent on our websites, conversions (i.e. completed activity on our Websites), etc., for the purposes of technical support and improvement of our W
|
||||
\hich\af1\dbch\af31505\loch\f1 ebsites as well as monitoring of functionalities thereof (for details regarding the extent of the \hich\af1\dbch\af31505\loch\f1 data being processed see Article 4 (c) - d) of this Policy).
|
||||
\par \hich\af1\dbch\af31505\loch\f1 For this purpose of personal data processing, we process your personal data under the lawful ground of legitimate interest (operation of the Websites, statistical purposes and data security).}{\rtlch\fcs1 \af1\afs18
|
||||
\ltrch\fcs0 \fs18\lang1033\langfe2052\langnp1033\insrsid14699391\charrsid1001100
|
||||
\par }\pard \ltrpar\s2\qj \li0\ri0\sb120\sa120\nowidctlpar\wrapdefault\faauto\outlinelevel1\rin0\lin0\itap0\pararsid9112532 {\rtlch\fcs1 \ab\af1\afs18 \ltrch\fcs0 \b\fs18\lang1033\langfe2052\langnp1033\insrsid1001100\charrsid2828669
|
||||
\hich\af1\dbch\af31505\loch\f1 b. Processing of the personal data of the visitors to the Websites
|
||||
\par }\pard \ltrpar\s2\qj \li0\ri0\sb100\sa100\nowidctlpar\wrapdefault\faauto\outlinelevel1\rin0\lin0\itap0\pararsid1001100 {\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \fs18\lang1033\langfe2052\langnp1033\insrsid1001100\charrsid1001100 \hich\af1\dbch\af31505\loch\f1
|
||||
If you publish any personal data on our Websites, we may process such personal data to the extent published for the purpose of responding to your post. Usually, we process following personal d\hich\af1\dbch\af31505\loch\f1
|
||||
ata categories on our Websites: your name, surname and any personal data which you upload on the Websites or which we receive via personal messages.
|
||||
\par \hich\af1\dbch\af31505\loch\f1
|
||||
For these purposes of personal data processing, we process the above mentioned personal data under lawful ground of negotiation and performance of a contract (customer and technical support under the RustDesk software license agreement concluded between u
|
||||
\hich\af1\dbch\af31505\loch\f1 s and yourself) and legitimate interest (general communication between us and yourself).
|
||||
\par }\pard \ltrpar\s2\qj \li0\ri0\sb120\sa120\nowidctlpar\wrapdefault\faauto\outlinelevel1\rin0\lin0\itap0\pararsid9112532 {\rtlch\fcs1 \ab\af1\afs18 \ltrch\fcs0 \b\fs18\lang1033\langfe2052\langnp1033\insrsid1001100\charrsid2828669
|
||||
\hich\af1\dbch\af31505\loch\f1 c. Cookies
|
||||
\par }\pard \ltrpar\s2\qj \li0\ri0\sb100\sa100\nowidctlpar\wrapdefault\faauto\outlinelevel1\rin0\lin0\itap0\pararsid1001100 {\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \fs18\lang1033\langfe2052\langnp1033\insrsid1001100\charrsid1001100 \hich\af1\dbch\af31505\loch\f1
|
||||
We use v\hich\af1\dbch\af31505\loch\f1
|
||||
arious cookie files, which may contain your personal data (e.g. your IP address or the configuration of your browser and computer). We use cookies on the basis of your consent that you express via the cookies settings displayed to you in a banner during y
|
||||
\hich\af1\dbch\af31505\loch\f1 our first visit to our Websites. This consent can be subsequently amended / withdrawn via your web browser settings (to the extended allowed by the respective browser).
|
||||
\par }\pard \ltrpar\s2\qj \li0\ri0\sb120\sa120\nowidctlpar\wrapdefault\faauto\outlinelevel1\rin0\lin0\itap0\pararsid9112532 {\rtlch\fcs1 \ab\af1\afs18 \ltrch\fcs0 \b\fs18\lang1033\langfe2052\langnp1033\insrsid1001100\charrsid2828669
|
||||
\hich\af1\dbch\af31505\loch\f1 d. RustDesk
|
||||
\par }\pard \ltrpar\s2\qj \li0\ri0\sb100\sa100\nowidctlpar\wrapdefault\faauto\outlinelevel1\rin0\lin0\itap0\pararsid1001100 {\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \fs18\lang1033\langfe2052\langnp1033\insrsid1001100\charrsid1001100 \hich\af1\dbch\af31505\loch\f1
|
||||
To provide you with the RustDesk software application and to constantly impr\hich\af1\dbch\af31505\loch\f1
|
||||
ove our services including customer support, we process following personal data about you and your device: start of the RustDesk software application, IP-address of the device, statistical information about your computer (e.g. CPU-type, screen resolution)
|
||||
\hich\af1\dbch\af31505\loch\f1 , time and duration of RustDesk software sessions and RustDesk-IDs of the RustDesk\hich\f1 \rquote \loch\f1 s session participants.
|
||||
\par }\pard \ltrpar\s2\qj \li0\ri0\sb100\sa100\nowidctlpar\wrapdefault\faauto\outlinelevel1\rin0\lin0\itap0\pararsid3630109 {\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \fs18\lang1033\langfe2052\langnp1033\insrsid1001100\charrsid1001100 \hich\af1\dbch\af31505\loch\f1
|
||||
We process the personal data acquired via the RustDesk software under lawful grounds of performance of a contract (performance of RustDesk software license agreement concluded between us and yourself including customer support) and our legitimate interest
|
||||
\hich\af1\dbch\af31505\loch\f1 (RustDesk software development).}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \fs18\lang1033\langfe2052\langnp1033\insrsid1917520\charrsid3630109
|
||||
\par }{\*\themedata 504b030414000600080000002100e9de0fbfff0000001c020000130000005b436f6e74656e745f54797065735d2e786d6cac91cb4ec3301045f748fc83e52d4a
|
||||
9cb2400825e982c78ec7a27cc0c8992416c9d8b2a755fbf74cd25442a820166c2cd933f79e3be372bd1f07b5c3989ca74aaff2422b24eb1b475da5df374fd9ad
|
||||
5689811a183c61a50f98f4babebc2837878049899a52a57be670674cb23d8e90721f90a4d2fa3802cb35762680fd800ecd7551dc18eb899138e3c943d7e503b6
|
||||
b01d583deee5f99824e290b4ba3f364eac4a430883b3c092d4eca8f946c916422ecab927f52ea42b89a1cd59c254f919b0e85e6535d135a8de20f20b8c12c3b0
|
||||
0c895fcf6720192de6bf3b9e89ecdbd6596cbcdd8eb28e7c365ecc4ec1ff1460f53fe813d3cc7f5b7f020000ffff0300504b030414000600080000002100a5d6
|
||||
a7e7c0000000360100000b0000005f72656c732f2e72656c73848fcf6ac3300c87ef85bd83d17d51d2c31825762fa590432fa37d00e1287f68221bdb1bebdb4f
|
||||
c7060abb0884a4eff7a93dfeae8bf9e194e720169aaa06c3e2433fcb68e1763dbf7f82c985a4a725085b787086a37bdbb55fbc50d1a33ccd311ba548b6309512
|
||||
0f88d94fbc52ae4264d1c910d24a45db3462247fa791715fd71f989e19e0364cd3f51652d73760ae8fa8c9ffb3c330cc9e4fc17faf2ce545046e37944c69e462
|
||||
a1a82fe353bd90a865aad41ed0b5b8f9d6fd010000ffff0300504b0304140006000800000021006b799616830000008a0000001c0000007468656d652f746865
|
||||
6d652f7468656d654d616e616765722e786d6c0ccc4d0ac3201040e17da17790d93763bb284562b2cbaebbf600439c1a41c7a0d29fdbd7e5e38337cedf14d59b
|
||||
4b0d592c9c070d8a65cd2e88b7f07c2ca71ba8da481cc52c6ce1c715e6e97818c9b48d13df49c873517d23d59085adb5dd20d6b52bd521ef2cdd5eb9246a3d8b
|
||||
4757e8d3f729e245eb2b260a0238fd010000ffff0300504b030414000600080000002100b9540503a5070000d0200000160000007468656d652f7468656d652f
|
||||
7468656d65312e786d6cec595f8b1b47127f3fc87718e65dd6bf19fd592c0769247963efdac6921df2d82bb566dadb332da65bbb16c1109ca71008049290870b
|
||||
1cf7720fc791c005cee41eeebb9c0f9b5cee435c75cf68a65b6ad9bb8b0fccb1bbcba2e9f955f5afabaaab4add373f7c1a53e70ca79cb0a4e7d66fd45c072733
|
||||
362749d8731f4dc7958eeb70819239a22cc13d778db9fbe1ad0f7e77131d8808c7d801f9841fa09e1b09b13ca856f90c8611bfc1963881770b96c648c0631a56
|
||||
e7293a07bd31ad366ab556354624719d04c5a0f6fe624166d8f9d78bbffffac7effff9d997f0e7dedacc31a2305122b81c98d1742267c086a0c2ce4feb12c1d7
|
||||
3ca0a9738668cf85e9e6ec7c8a9f0ad7a1880b78d1736beac7addeba594507b910157b6435b9b1fac9e57281f96943cd998627c5a49ee77bad7ea15f01a8d8c5
|
||||
8ddaa3d6a855e85300349bc14a332ea6ce7623f072ac06ca3e5a740fdbc366ddc06bfa9b3b9cfbbefc35f00a94e9f776f0e371005634f00a94e1fd1dbc3fe80e
|
||||
86a67e05caf0ad1d7cbbd61f7a6d43bf02459424a73be89adf6a069bd5169005a3875678d7f7c6ed46aebc44413414d125a758b044ec8bb5183d61e918001248
|
||||
91208923d64bbc403308e60051729212e788841104de12258cc370ad511bd79af05ffe7aea93f2283ac0489396bc8009df19927c1c3e4bc952f4dc3ba0d5d520
|
||||
af5ebc78f9fce797cffff6f2f3cf5f3eff319f5ba932e40e5112ea72bffde9ebfffcf099f3efbffee1b76fbecda6dec6731dfffa2f5fbcfee51f6f520f2b2e4d
|
||||
f1eabb9f5efffcd3abefbffaf5cfdf58b4f75374a2c3a724c6dcb987cf9d872c86055af8e393f47212d308115da29f841c2548ce62d13f129181beb746145970
|
||||
036cdaf1710aa9c606bcbd7a62109e44e94a108bc6bb516c008f19a303965aad7057cea59979ba4a42fbe4e94ac73d44e8cc36778012c3cba3d512722cb1a90c
|
||||
226cd07c4051225088132c1cf98e9d626c59dd278418763d26b39471b610ce27c419206235c9949c18d1540a1d9218fcb2b611047f1bb6397eec0c18b5ad7a88
|
||||
cf4c24ec0d442de4a7981a66bc8d5602c53695531453dde0474844369293753ad371232ec0d321a6cc19cd31e73699fb29ac5773fa5d483376b71fd3756c2253
|
||||
414e6d3a8f10633a72c84e8308c54b1b76429248c77ec44f214491f380091bfc98993b443e831f50b2d7dd8f0936dcfdf66cf00832ac4ea90c10f966955a7c79
|
||||
1b33237e276bba40d8966afa696ca4d87e4aacd131588546681f614cd1399a63ec3cfac8c260c09686cd4bd27722c82a87d81658779019abf239c11c3baab9d9
|
||||
cd9347841b213bc121dbc3e778bd9578d6288951ba4ff33df0ba6ef31194bad81600f7e9ec5407de23d00a42bc588d729f830e2db8f76a7d1021a380c9676e8f
|
||||
d7756af8ef227b0cf6e51383c605f625c8e04bcb4062d765de689b29a2c60465c04c117419b6740b2286fb4b11595c95d8ca2ab730376de906e88e8ca62726c9
|
||||
5b3ba0addec7ffdff53ed061bcfafd0f96cdf66efa1dbb6223595db2d3d9974c0eb7fa9b7db8edae2660e99cbcff4dcd10ad920718eac86ec6baee69ae7b1af7
|
||||
ffbea7d9b79faf3b997dfdc67527e3428771ddc9e4872befa693299b17e86be4814776d0a38e7de2bda73e0b42e944ac293ee2eae087c3f799f91806a59c3af8
|
||||
c4c529e032828fb2ccc104062e4c91927152263e26229a446809a74375572a0979ae3ae4ce9271383452c356dd124f57f1319b67879df5ba3cd8cc2a2b47a21c
|
||||
aff9c5381c54890cdd6a970778857ac5365407ad1b0252f63224b4c94c124d0b89f666501a491deb82d12c24d4cade098bae854547aadfb86a8705502bbc025f
|
||||
b81df89ade737d0f444008cee3a0399f4b3f65aede785739f35d7a7a9f318d0880067b1301a5a7bb92ebdee5c9d565a176014f1b24b470334928cba8068f47f0
|
||||
35388f4e397a111a97f575b774a9414f9a42cd07a155d26877dec4e2aabe06b9eddc40133d53d0c439efb9ada60f213343cb9ebb804363f8182f2176b8fcce85
|
||||
680817303391661bfe2a9965997231443cca0cae924e960d622270ea5012f75cb9fcc20d3451394471ab372021bcb7e4ba9056de3772e074d3c978b1c033a1bb
|
||||
5d1b9196ce1e21c367b9c2fa56895f1d2c25d90adc3d89e6e7ce095da50f118498dfae4b03ce0987bb837a66cd3981cbb0229195f1b75598f2b4abdf46a918ca
|
||||
c6115d4628af287a32cfe02a951774d4536103ed295f33185433495e084f42596075a31ad5b4a81a1987bd55f7ed42d2725ad22c6ba6915564d5b4673163864d
|
||||
19d8b2e5d58abcc66a6362c8697a85cf52f776caed6e72dd569f5054093078613f4bd5bd4041d0a8959319d424e3dd342c73763e6ad68ecd02df42ed224542cb
|
||||
faad8dda2dbb1535c23a1d0c5ea9f283dc76d4c2d062d3572a4babcb73fd629b9d3c81e431842e77450557ae846beb14414334513d499636608b3c15f9d6804f
|
||||
ce2a253df7d39adff782861f546a1d7f54f19a5eadd2f1fbcd4adff79bf5915faf0d078d6750584414d7fdece27e0c1718749d5fdfabf19d2bfc7873477363c6
|
||||
e22a5357f455455c5de1d71bb62bfca9bc9c771d0249e7d35663dc6d7607ad4ab7d91f57bce1a053e906ad4165d80adac3f130f03bddf133d7395360afdf0cbc
|
||||
d6a85369d583a0e2b56a927ea75b697b8d46df6bf73b23afff2c6f6360e559fac86d01e655bc6efd170000ffff0300504b0304140006000800000021000dd190
|
||||
9fb60000001b010000270000007468656d652f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73848f4d0ac2301484f7827708
|
||||
6f6fd3ba109126dd88d0add40384e4350d363f2451eced0dae2c082e8761be9969bb979dc9136332de3168aa1a083ae995719ac16db8ec8e4052164e89d93b64
|
||||
b060828e6f37ed1567914b284d262452282e3198720e274a939cd08a54f980ae38a38f56e422a3a641c8bbd048f7757da0f19b017cc524bd62107bd500199650
|
||||
9affb3fd381a89672f1f165dfe514173d9850528a2c6cce0239baa4c04ca5bbabac4df000000ffff0300504b01022d0014000600080000002100e9de0fbfff00
|
||||
00001c0200001300000000000000000000000000000000005b436f6e74656e745f54797065735d2e786d6c504b01022d0014000600080000002100a5d6a7e7c0
|
||||
000000360100000b00000000000000000000000000300100005f72656c732f2e72656c73504b01022d00140006000800000021006b799616830000008a000000
|
||||
1c00000000000000000000000000190200007468656d652f7468656d652f7468656d654d616e616765722e786d6c504b01022d0014000600080000002100b954
|
||||
0503a5070000d02000001600000000000000000000000000d60200007468656d652f7468656d652f7468656d65312e786d6c504b01022d001400060008000000
|
||||
21000dd1909fb60000001b0100002700000000000000000000000000af0a00007468656d652f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73504b050600000000050005005d010000aa0b00000000}
|
||||
{\*\colorschememapping 3c3f786d6c2076657273696f6e3d22312e302220656e636f64696e673d225554462d3822207374616e64616c6f6e653d22796573223f3e0d0a3c613a636c724d
|
||||
617020786d6c6e733a613d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f72672f64726177696e676d6c2f323030362f6d6169
|
||||
6e22206267313d226c743122207478313d22646b3122206267323d226c743222207478323d22646b322220616363656e74313d22616363656e74312220616363
|
||||
656e74323d22616363656e74322220616363656e74333d22616363656e74332220616363656e74343d22616363656e74342220616363656e74353d22616363656e74352220616363656e74363d22616363656e74362220686c696e6b3d22686c696e6b2220666f6c486c696e6b3d22666f6c486c696e6b222f3e}
|
||||
{\*\latentstyles\lsdstimax376\lsdlockeddef0\lsdsemihiddendef0\lsdunhideuseddef0\lsdqformatdef0\lsdprioritydef99{\lsdlockedexcept \lsdqformat1 \lsdpriority0 \lsdlocked0 Normal;\lsdqformat1 \lsdlocked0 heading 1;\lsdqformat1 \lsdlocked0 heading 2;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 3;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 4;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 5;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 6;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 7;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 8;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 9;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 3;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 7;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 8;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 9;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 1;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 2;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 3;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 4;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 5;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 6;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 7;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 8;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 9;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal Indent;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footnote text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation text;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 header;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footer;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index heading;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority35 \lsdlocked0 caption;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 table of figures;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 envelope address;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 envelope return;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footnote reference;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation reference;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 line number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 page number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 endnote reference;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 endnote text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 table of authorities;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 macro;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 toa heading;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 2;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 2;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 2;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 5;\lsdqformat1 \lsdpriority10 \lsdlocked0 Title;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Closing;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Signature;\lsdsemihidden1 \lsdunhideused1 \lsdpriority1 \lsdlocked0 Default Paragraph Font;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 3;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Message Header;\lsdqformat1 \lsdpriority11 \lsdlocked0 Subtitle;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Salutation;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Date;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text First Indent;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text First Indent 2;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Note Heading;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent 2;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Block Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 FollowedHyperlink;
|
||||
\lsdqformat1 \lsdpriority22 \lsdlocked0 Strong;\lsdqformat1 \lsdpriority20 \lsdlocked0 Emphasis;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Document Map;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Plain Text;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 E-mail Signature;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Top of Form;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Bottom of Form;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal (Web);
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Acronym;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Address;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Cite;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Code;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Definition;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Keyboard;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Preformatted;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Sample;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Typewriter;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Variable;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation subject;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 No List;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Balloon Text;
|
||||
\lsdpriority39 \lsdlocked0 Table Grid;\lsdsemihidden1 \lsdlocked0 Placeholder Text;\lsdqformat1 \lsdpriority1 \lsdlocked0 No Spacing;\lsdpriority60 \lsdlocked0 Light Shading;\lsdpriority61 \lsdlocked0 Light List;\lsdpriority62 \lsdlocked0 Light Grid;
|
||||
\lsdpriority63 \lsdlocked0 Medium Shading 1;\lsdpriority64 \lsdlocked0 Medium Shading 2;\lsdpriority65 \lsdlocked0 Medium List 1;\lsdpriority66 \lsdlocked0 Medium List 2;\lsdpriority67 \lsdlocked0 Medium Grid 1;\lsdpriority68 \lsdlocked0 Medium Grid 2;
|
||||
\lsdpriority69 \lsdlocked0 Medium Grid 3;\lsdpriority70 \lsdlocked0 Dark List;\lsdpriority71 \lsdlocked0 Colorful Shading;\lsdpriority72 \lsdlocked0 Colorful List;\lsdpriority73 \lsdlocked0 Colorful Grid;\lsdpriority60 \lsdlocked0 Light Shading Accent 1;
|
||||
\lsdpriority61 \lsdlocked0 Light List Accent 1;\lsdpriority62 \lsdlocked0 Light Grid Accent 1;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 1;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 1;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 1;
|
||||
\lsdsemihidden1 \lsdlocked0 Revision;\lsdqformat1 \lsdpriority34 \lsdlocked0 List Paragraph;\lsdqformat1 \lsdpriority29 \lsdlocked0 Quote;\lsdqformat1 \lsdpriority30 \lsdlocked0 Intense Quote;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 1;
|
||||
\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 1;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 1;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 1;\lsdpriority70 \lsdlocked0 Dark List Accent 1;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 1;
|
||||
\lsdpriority72 \lsdlocked0 Colorful List Accent 1;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 1;\lsdpriority60 \lsdlocked0 Light Shading Accent 2;\lsdpriority61 \lsdlocked0 Light List Accent 2;\lsdpriority62 \lsdlocked0 Light Grid Accent 2;
|
||||
\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 2;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 2;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 2;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 2;
|
||||
\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 2;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 2;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 2;\lsdpriority70 \lsdlocked0 Dark List Accent 2;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 2;
|
||||
\lsdpriority72 \lsdlocked0 Colorful List Accent 2;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 2;\lsdpriority60 \lsdlocked0 Light Shading Accent 3;\lsdpriority61 \lsdlocked0 Light List Accent 3;\lsdpriority62 \lsdlocked0 Light Grid Accent 3;
|
||||
\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 3;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 3;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 3;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 3;
|
||||
\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 3;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 3;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 3;\lsdpriority70 \lsdlocked0 Dark List Accent 3;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 3;
|
||||
\lsdpriority72 \lsdlocked0 Colorful List Accent 3;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 3;\lsdpriority60 \lsdlocked0 Light Shading Accent 4;\lsdpriority61 \lsdlocked0 Light List Accent 4;\lsdpriority62 \lsdlocked0 Light Grid Accent 4;
|
||||
\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 4;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 4;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 4;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 4;
|
||||
\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 4;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 4;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 4;\lsdpriority70 \lsdlocked0 Dark List Accent 4;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 4;
|
||||
\lsdpriority72 \lsdlocked0 Colorful List Accent 4;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 4;\lsdpriority60 \lsdlocked0 Light Shading Accent 5;\lsdpriority61 \lsdlocked0 Light List Accent 5;\lsdpriority62 \lsdlocked0 Light Grid Accent 5;
|
||||
\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 5;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 5;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 5;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 5;
|
||||
\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 5;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 5;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 5;\lsdpriority70 \lsdlocked0 Dark List Accent 5;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 5;
|
||||
\lsdpriority72 \lsdlocked0 Colorful List Accent 5;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 5;\lsdpriority60 \lsdlocked0 Light Shading Accent 6;\lsdpriority61 \lsdlocked0 Light List Accent 6;\lsdpriority62 \lsdlocked0 Light Grid Accent 6;
|
||||
\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 6;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 6;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 6;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 6;
|
||||
\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 6;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 6;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 6;\lsdpriority70 \lsdlocked0 Dark List Accent 6;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 6;
|
||||
\lsdpriority72 \lsdlocked0 Colorful List Accent 6;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 6;\lsdqformat1 \lsdpriority19 \lsdlocked0 Subtle Emphasis;\lsdqformat1 \lsdpriority21 \lsdlocked0 Intense Emphasis;
|
||||
\lsdqformat1 \lsdpriority31 \lsdlocked0 Subtle Reference;\lsdqformat1 \lsdpriority32 \lsdlocked0 Intense Reference;\lsdqformat1 \lsdpriority33 \lsdlocked0 Book Title;\lsdsemihidden1 \lsdunhideused1 \lsdpriority37 \lsdlocked0 Bibliography;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority39 \lsdlocked0 TOC Heading;\lsdpriority41 \lsdlocked0 Plain Table 1;\lsdpriority42 \lsdlocked0 Plain Table 2;\lsdpriority43 \lsdlocked0 Plain Table 3;\lsdpriority44 \lsdlocked0 Plain Table 4;
|
||||
\lsdpriority45 \lsdlocked0 Plain Table 5;\lsdpriority40 \lsdlocked0 Grid Table Light;\lsdpriority46 \lsdlocked0 Grid Table 1 Light;\lsdpriority47 \lsdlocked0 Grid Table 2;\lsdpriority48 \lsdlocked0 Grid Table 3;\lsdpriority49 \lsdlocked0 Grid Table 4;
|
||||
\lsdpriority50 \lsdlocked0 Grid Table 5 Dark;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 1;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 1;
|
||||
\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 1;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 1;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 1;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 1;
|
||||
\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 1;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 2;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 2;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 2;
|
||||
\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 2;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 2;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 2;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 2;
|
||||
\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 3;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 3;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 3;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 3;
|
||||
\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 3;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 3;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 3;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 4;
|
||||
\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 4;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 4;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 4;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 4;
|
||||
\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 4;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 5;
|
||||
\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 5;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 5;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 5;
|
||||
\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 5;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 6;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 6;
|
||||
\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 6;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 6;
|
||||
\lsdpriority46 \lsdlocked0 List Table 1 Light;\lsdpriority47 \lsdlocked0 List Table 2;\lsdpriority48 \lsdlocked0 List Table 3;\lsdpriority49 \lsdlocked0 List Table 4;\lsdpriority50 \lsdlocked0 List Table 5 Dark;
|
||||
\lsdpriority51 \lsdlocked0 List Table 6 Colorful;\lsdpriority52 \lsdlocked0 List Table 7 Colorful;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 1;\lsdpriority47 \lsdlocked0 List Table 2 Accent 1;\lsdpriority48 \lsdlocked0 List Table 3 Accent 1;
|
||||
\lsdpriority49 \lsdlocked0 List Table 4 Accent 1;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 1;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 1;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 1;
|
||||
\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 2;\lsdpriority47 \lsdlocked0 List Table 2 Accent 2;\lsdpriority48 \lsdlocked0 List Table 3 Accent 2;\lsdpriority49 \lsdlocked0 List Table 4 Accent 2;
|
||||
\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 2;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 2;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 2;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 3;
|
||||
\lsdpriority47 \lsdlocked0 List Table 2 Accent 3;\lsdpriority48 \lsdlocked0 List Table 3 Accent 3;\lsdpriority49 \lsdlocked0 List Table 4 Accent 3;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 3;
|
||||
\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 3;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 3;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 4;\lsdpriority47 \lsdlocked0 List Table 2 Accent 4;
|
||||
\lsdpriority48 \lsdlocked0 List Table 3 Accent 4;\lsdpriority49 \lsdlocked0 List Table 4 Accent 4;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 4;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 4;
|
||||
\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 List Table 2 Accent 5;\lsdpriority48 \lsdlocked0 List Table 3 Accent 5;
|
||||
\lsdpriority49 \lsdlocked0 List Table 4 Accent 5;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 5;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 5;
|
||||
\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 List Table 2 Accent 6;\lsdpriority48 \lsdlocked0 List Table 3 Accent 6;\lsdpriority49 \lsdlocked0 List Table 4 Accent 6;
|
||||
\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Mention;
|
||||
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Smart Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hashtag;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Unresolved Mention;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Smart Link;}}}
|
||||
@@ -0,0 +1,22 @@
|
||||
<Project Sdk="WixToolset.Sdk/4.0.5">
|
||||
<PropertyGroup>
|
||||
<IncludeSearchPaths>
|
||||
</IncludeSearchPaths>
|
||||
<Configurations>Release</Configurations>
|
||||
<Platforms>x64</Platforms>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Includes.wxi" />
|
||||
<Content Include="Resources\icon.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="WixToolset.Firewall.wixext" Version="4.0.5" />
|
||||
<PackageReference Include="WixToolset.Heat" Version="4.0.5" />
|
||||
<PackageReference Include="WixToolset.Netfx.wixext" Version="4.0.5" />
|
||||
<PackageReference Include="WixToolset.UI.wixext" Version="4.0.5" />
|
||||
<PackageReference Include="WixToolset.Util.wixext" Version="4.0.5" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\CustomActions\CustomActions.vcxproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,62 @@
|
||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"
|
||||
xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util"
|
||||
xmlns:ui="http://wixtoolset.org/schemas/v4/wxs/ui">
|
||||
|
||||
<?include Includes.wxi?>
|
||||
|
||||
<Package Name="$(var.Product)" Version="$(var.Version)" Manufacturer="$(var.Manufacturer)" Language="!(loc.ProductLanguage)" UpgradeCode="$(var.UpgradeCode)" Scope="perMachine">
|
||||
|
||||
<SummaryInformation Keywords="Installer" Description="$(var.Description)" Codepage="!(loc.SummaryCodepage)" />
|
||||
|
||||
<!--<PropertyRef Id="UpgradesFile" />-->
|
||||
|
||||
<PropertyRef Id="AddRemovePropertiesFile" />
|
||||
|
||||
<Media Id="1" Cabinet="cab1.cab" EmbedCab="yes" CompressionLevel="high" />
|
||||
<Icon Id="AppIcon" SourceFile="Resources\icon.ico" />
|
||||
|
||||
<!-- User Interface -->
|
||||
<WixVariable Id="WixUILicenseRtf" Value="License.rtf" />
|
||||
|
||||
<ui:WixUI Id="UI_MyInstallDialog" InstallDirectory="INSTALLFOLDER_INNER" />
|
||||
<UIRef Id="WixUI_ErrorProgressText" />
|
||||
|
||||
<InstallUISequence>
|
||||
<Show Dialog="UI_AnotherAppDialog" Before="WelcomeDlg" Condition="Not installed AND APP_WINDOWS_INSTALLER="#0""/>
|
||||
</InstallUISequence>
|
||||
|
||||
<InstallExecuteSequence>
|
||||
<InstallExecute After="RemoveExistingProducts" />
|
||||
|
||||
<!--Only do InstallValidate if is not Uninstall-->
|
||||
<!--<InstallValidate Condition="NOT (Installed AND REMOVE AND NOT UPGRADINGPRODUCTCODE )" />-->
|
||||
<!--Only do InstallValidate if is Install-->
|
||||
<InstallValidate Condition="NOT Installed" />
|
||||
|
||||
</InstallExecuteSequence>
|
||||
|
||||
<MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" Schedule="afterInstallInitialize" AllowSameVersionUpgrades="yes" />
|
||||
|
||||
<Feature Id="App" Level="1" AllowAdvertise="no" Display="expand" Title="!(loc.F_App)" Description="!(loc.F_App_Desc)" AllowAbsent="no">
|
||||
<ComponentGroupRef Id="Components" />
|
||||
|
||||
<ComponentRef Id="Product.Registry.InstallFolder" />
|
||||
<ComponentRef Id="Product.Registry.DefaultIcon" />
|
||||
<ComponentRef Id="Product.Registry.CommandPlay" />
|
||||
<ComponentRef Id="Product.Registry.URLProtocol" />
|
||||
<ComponentRef Id="Product.Registry.Command" />
|
||||
<ComponentRef Id="Product.Registry.UninstallApp" />
|
||||
<ComponentRef Id="App.StartMenu" />
|
||||
<ComponentRef Id="Product.Registry.PersistedStartMenuShortcutProperties1" />
|
||||
<ComponentRef Id="Product.Registry.PersistedStartMenuShortcutProperties0" />
|
||||
<ComponentRef Id="Product.Registry.PersistedDesktopShortcutProperties1" />
|
||||
<ComponentRef Id="Product.Registry.PersistedDesktopShortcutProperties0" />
|
||||
<ComponentRef Id="Product.Registry.PersistedPrinterProperties1" />
|
||||
<ComponentRef Id="Product.Registry.PersistedPrinterProperties0" />
|
||||
</Feature>
|
||||
|
||||
<!--https://wixtoolset.org/docs/tools/wixext/wixui/#customizing-a-dialog-set-->
|
||||
<!--$CustomBitmapsStart$-->
|
||||
<!--$CustomBitmapsEnd$-->
|
||||
</Package>
|
||||
</Wix>
|
||||
@@ -0,0 +1,15 @@
|
||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
|
||||
<Fragment>
|
||||
<UI>
|
||||
<Dialog Id="UI_AnotherAppDialog" Width="370" Height="270" Title="!(loc.ExitDialog_Title)">
|
||||
<Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Default="yes" Cancel="yes" Text="!(loc.WixUICancel)">
|
||||
<Publish Event="EndDialog" Value="ErrorAbort" />
|
||||
</Control>
|
||||
<Control Id="Bitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="234" TabSkip="no" Text="!(loc.ExitDialogBitmap)" />
|
||||
<Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
|
||||
<Control Id="Description" Type="Text" X="135" Y="70" Width="220" Height="40" Transparent="yes" NoPrefix="yes" Text="!(loc.AnotherAppDialogDescription)" />
|
||||
<Control Id="Title" Type="Text" X="135" Y="20" Width="220" Height="60" Transparent="yes" NoPrefix="yes" Text="!(loc.AnotherAppDialogTitle)" />
|
||||
</Dialog>
|
||||
</UI>
|
||||
</Fragment>
|
||||
</Wix>
|
||||
@@ -0,0 +1,32 @@
|
||||
<!-- https://github.com/wixtoolset/wix/blob/ce73352b1fa1d4f9cded10a0ee410f2e786bd326/src/ext/UI/wixlib/InstallDirDlg.wxs -->
|
||||
|
||||
<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
|
||||
|
||||
|
||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
|
||||
<Fragment>
|
||||
<UI>
|
||||
<Dialog Id="MyInstallDirDlg" Width="370" Height="270" Title="!(loc.InstallDirDlg_Title)">
|
||||
<Control Id="Next" Type="PushButton" X="236" Y="243" Width="56" Height="17" Default="yes" Text="!(loc.WixUINext)" />
|
||||
<Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Text="!(loc.WixUIBack)" />
|
||||
<Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="!(loc.WixUICancel)">
|
||||
<Publish Event="SpawnDialog" Value="CancelDlg" />
|
||||
</Control>
|
||||
|
||||
<Control Id="Description" Type="Text" X="25" Y="23" Width="280" Height="15" Transparent="yes" NoPrefix="yes" Text="!(loc.InstallDirDlgDescription)" />
|
||||
<Control Id="Title" Type="Text" X="15" Y="6" Width="200" Height="15" Transparent="yes" NoPrefix="yes" Text="!(loc.InstallDirDlgTitle)" />
|
||||
<Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" TabSkip="no" Text="!(loc.InstallDirDlgBannerBitmap)" />
|
||||
<Control Id="BannerLine" Type="Line" X="0" Y="44" Width="370" Height="0" />
|
||||
<Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
|
||||
|
||||
<Control Id="FolderLabel" Type="Text" X="20" Y="60" Width="290" Height="18" NoPrefix="yes" Text="!(loc.InstallDirDlgFolderLabel)" />
|
||||
<Control Id="Folder" Type="PathEdit" X="20" Y="80" Width="320" Height="18" Property="WIXUI_INSTALLDIR" Indirect="yes" />
|
||||
<Control Id="ChangeFolder" Type="PushButton" X="20" Y="100" Width="56" Height="17" Text="!(loc.InstallDirDlgChange)" />
|
||||
|
||||
<Control Id="ChkBoxStartMenuShortcuts" Type="CheckBox" X="20" Y="140" Width="290" Height="17" Property="STARTMENUSHORTCUTS" CheckBoxValue="1" Text="!(loc.MyInstallDirDlgStartMenuShortcuts)" />
|
||||
<Control Id="ChkBoxDesktopShortcuts" Type="CheckBox" X="20" Y="160" Width="290" Height="17" Property="DESKTOPSHORTCUTS" CheckBoxValue="1" Text="!(loc.MyInstallDirDlgDesktopShortcuts)" />
|
||||
<Control Id="ChkBoxInstallPrinter" Type="CheckBox" X="20" Y="180" Width="290" Height="17" Property="PRINTER" CheckBoxValue="1" Text="!(loc.MyInstallDirDlgPrinter)" />
|
||||
</Dialog>
|
||||
</UI>
|
||||
</Fragment>
|
||||
</Wix>
|
||||
@@ -0,0 +1,95 @@
|
||||
<!-- From https://github.com/wixtoolset/wix/blob/ce73352b1fa1d4f9cded10a0ee410f2e786bd326/src/ext/UI/wixlib/WixUI_InstallDir.wxs -->
|
||||
|
||||
<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
|
||||
|
||||
<!--
|
||||
First-time install dialog sequence:
|
||||
- WixUI_WelcomeDlg
|
||||
- WixUI_LicenseAgreementDlg
|
||||
- WixUI_InstallDirDlg
|
||||
- WixUI_VerifyReadyDlg
|
||||
- WixUI_DiskCostDlg
|
||||
|
||||
Maintenance dialog sequence:
|
||||
- WixUI_MaintenanceWelcomeDlg
|
||||
- WixUI_MaintenanceTypeDlg
|
||||
- WixUI_InstallDirDlg
|
||||
- WixUI_VerifyReadyDlg
|
||||
|
||||
Patch dialog sequence:
|
||||
- WixUI_WelcomeDlg
|
||||
- WixUI_VerifyReadyDlg
|
||||
|
||||
-->
|
||||
|
||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:ui="http://wixtoolset.org/schemas/v4/wxs/ui">
|
||||
<?include ../Includes.wxi?>
|
||||
<?foreach WIXUIARCH in X86;X64;A64 ?>
|
||||
<Fragment>
|
||||
<UI Id="UI_MyInstallDialog_$(WIXUIARCH)">
|
||||
<Publish Dialog="LicenseAgreementDlg" Control="Print" Event="DoAction" Value="WixUIPrintEula_$(WIXUIARCH)" />
|
||||
<Publish Dialog="BrowseDlg" Control="OK" Event="DoAction" Value="WixUIValidatePath_$(WIXUIARCH)" Order="3" Condition="NOT WIXUI_DONTVALIDATEPATH" />
|
||||
<Publish Dialog="MyInstallDirDlg" Control="Next" Event="DoAction" Value="WixUIValidatePath_$(WIXUIARCH)" Order="5" Condition="NOT WIXUI_DONTVALIDATEPATH" />
|
||||
</UI>
|
||||
|
||||
<UIRef Id="UI_MyInstallDialog" />
|
||||
</Fragment>
|
||||
<?endforeach?>
|
||||
|
||||
<Fragment>
|
||||
<UI Id="file UI_MyInstallDialog">
|
||||
<TextStyle Id="WixUI_Font_Normal" FaceName="Tahoma" Size="8" />
|
||||
<TextStyle Id="WixUI_Font_Bigger" FaceName="Tahoma" Size="12" />
|
||||
<TextStyle Id="WixUI_Font_Title" FaceName="Tahoma" Size="9" Bold="yes" />
|
||||
|
||||
<Property Id="DefaultUIFont" Value="WixUI_Font_Normal" />
|
||||
|
||||
<DialogRef Id="BrowseDlg" />
|
||||
<DialogRef Id="DiskCostDlg" />
|
||||
<DialogRef Id="ErrorDlg" />
|
||||
<DialogRef Id="FatalError" />
|
||||
<DialogRef Id="FilesInUse" />
|
||||
<DialogRef Id="MsiRMFilesInUse" />
|
||||
<DialogRef Id="PrepareDlg" />
|
||||
<DialogRef Id="ProgressDlg" />
|
||||
<DialogRef Id="ResumeDlg" />
|
||||
<DialogRef Id="UserExit" />
|
||||
<Publish Dialog="BrowseDlg" Control="OK" Event="SpawnDialog" Value="InvalidDirDlg" Order="4" Condition="NOT WIXUI_DONTVALIDATEPATH AND WIXUI_INSTALLDIR_VALID<>"1"" />
|
||||
|
||||
<Publish Dialog="ExitDialog" Control="Finish" Event="EndDialog" Value="Return" Order="999" />
|
||||
|
||||
<Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" Value="LicenseAgreementDlg" Condition="NOT Installed" />
|
||||
<Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" Value="VerifyReadyDlg" Condition="Installed AND PATCH" />
|
||||
|
||||
<Publish Dialog="LicenseAgreementDlg" Control="Back" Event="NewDialog" Value="WelcomeDlg" />
|
||||
<Publish Dialog="LicenseAgreementDlg" Control="Next" Event="NewDialog" Value="MyInstallDirDlg" Condition="LicenseAccepted = "1"" />
|
||||
|
||||
<Publish Dialog="MyInstallDirDlg" Control="Back" Event="NewDialog" Value="LicenseAgreementDlg" />
|
||||
<!-- Normalize INSTALLFOLDER_INNER before SetTargetPath and WixUIValidatePath run. -->
|
||||
<!-- UI case 1: already ends with \$(var.Product) but has no trailing slash, add the slash. -->
|
||||
<Publish Dialog="MyInstallDirDlg" Control="Next" Property="INSTALLFOLDER_INNER" Value="[INSTALLFOLDER_INNER]\" Order="1" Condition="INSTALLFOLDER_INNER AND INSTALLFOLDER_INNER ~>> "\$(var.Product)"" />
|
||||
<!-- UI case 2: ends with a slash but not \$(var.Product)\, append $(var.Product)\. -->
|
||||
<Publish Dialog="MyInstallDirDlg" Control="Next" Property="INSTALLFOLDER_INNER" Value="[INSTALLFOLDER_INNER]$(var.Product)\" Order="2" Condition="INSTALLFOLDER_INNER AND INSTALLFOLDER_INNER ~>> "\" AND NOT (INSTALLFOLDER_INNER ~>> "\$(var.Product)\" OR INSTALLFOLDER_INNER ~>> "\$(var.Product)")" />
|
||||
<!-- UI case 3: has no trailing slash and does not end with \$(var.Product), append \$(var.Product)\. -->
|
||||
<Publish Dialog="MyInstallDirDlg" Control="Next" Property="INSTALLFOLDER_INNER" Value="[INSTALLFOLDER_INNER]\$(var.Product)\" Order="3" Condition="INSTALLFOLDER_INNER AND NOT INSTALLFOLDER_INNER ~>> "\" AND NOT (INSTALLFOLDER_INNER ~>> "\$(var.Product)\" OR INSTALLFOLDER_INNER ~>> "\$(var.Product)")" />
|
||||
<Publish Dialog="MyInstallDirDlg" Control="Next" Event="SetTargetPath" Value="[WIXUI_INSTALLDIR]" Order="4" />
|
||||
<Publish Dialog="MyInstallDirDlg" Control="Next" Event="SpawnDialog" Value="InvalidDirDlg" Order="6" Condition="NOT WIXUI_DONTVALIDATEPATH AND WIXUI_INSTALLDIR_VALID<>"1"" />
|
||||
<Publish Dialog="MyInstallDirDlg" Control="Next" Event="NewDialog" Value="VerifyReadyDlg" Order="7" Condition="WIXUI_DONTVALIDATEPATH OR WIXUI_INSTALLDIR_VALID="1"" />
|
||||
<Publish Dialog="MyInstallDirDlg" Control="ChangeFolder" Property="_BrowseProperty" Value="[WIXUI_INSTALLDIR]" Order="1" />
|
||||
<Publish Dialog="MyInstallDirDlg" Control="ChangeFolder" Event="SpawnDialog" Value="BrowseDlg" Order="2" />
|
||||
<Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="MyInstallDirDlg" Order="1" Condition="NOT Installed" />
|
||||
<Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="MaintenanceTypeDlg" Order="2" Condition="Installed AND NOT PATCH" />
|
||||
<Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="WelcomeDlg" Order="2" Condition="Installed AND PATCH" />
|
||||
|
||||
<Publish Dialog="MaintenanceWelcomeDlg" Control="Next" Event="NewDialog" Value="MaintenanceTypeDlg" />
|
||||
|
||||
<Publish Dialog="MaintenanceTypeDlg" Control="RepairButton" Event="NewDialog" Value="VerifyReadyDlg" />
|
||||
<Publish Dialog="MaintenanceTypeDlg" Control="RemoveButton" Event="NewDialog" Value="VerifyReadyDlg" />
|
||||
<Publish Dialog="MaintenanceTypeDlg" Control="Back" Event="NewDialog" Value="MaintenanceWelcomeDlg" />
|
||||
|
||||
<Property Id="ARPNOMODIFY" Value="1" />
|
||||
</UI>
|
||||
|
||||
<UIRef Id="WixUI_Common" />
|
||||
</Fragment>
|
||||
</Wix>
|
||||
@@ -0,0 +1,44 @@
|
||||
# RustDesk msi project
|
||||
|
||||
Use Visual Studio 2022 to compile this project.
|
||||
|
||||
This project is mainly derived from <https://github.com/MediaPortal/MediaPortal-2.git> .
|
||||
|
||||
## Steps
|
||||
|
||||
1. `python preprocess.py`, see `python preprocess.py -h` for help.
|
||||
2. Build the .sln solution.
|
||||
|
||||
Run `msiexec /i package.msi /l*v install.log` to record the log.
|
||||
|
||||
## Usage
|
||||
|
||||
1. Put the custom dialog bitmaps in "Resources" directory. The supported bitmaps are `['WixUIBannerBmp', 'WixUIDialogBmp', 'WixUIExclamationIco', 'WixUIInfoIco', 'WixUINewIco', 'WixUIUpIco']`.
|
||||
|
||||
## Knowledge
|
||||
|
||||
### properties
|
||||
|
||||
[wix-toolset-set-custom-action-run-only-on-uninstall](https://www.advancedinstaller.com/versus/wix-toolset/wix-toolset-set-custom-action-run-only-on-uninstall.html)
|
||||
|
||||
| Property Name | Install | Uninstall | Change | Repair | Upgrade |
|
||||
| ------ | ------ | ------ | ------ | ------ | ------ |
|
||||
| Installed | False | True | True | True | True |
|
||||
| REINSTALL | False | False | False | True | False |
|
||||
| UPGRADINGPRODUCTCODE | False | False | False | False | True |
|
||||
| REMOVE | False | True | False | False | True |
|
||||
|
||||
## TODOs
|
||||
|
||||
1. Start menu. Uninstall
|
||||
1. custom options
|
||||
1. Custom client.
|
||||
1. firewall and tcp allow. Outgoing
|
||||
1. Show license ?
|
||||
1. Do create service. Outgoing.
|
||||
|
||||
## Refs
|
||||
|
||||
1. [windows-installer-portal](https://learn.microsoft.com/en-us/windows/win32/Msi/windows-installer-portal)
|
||||
1. [wxs](https://wixtoolset.org/docs/schema/wxs/)
|
||||
1. [wxs github](https://github.com/wixtoolset/wix)
|
||||
@@ -0,0 +1,26 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.7.34003.232
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{B7DD6F7E-DEF8-4E67-B5B7-07EF123DB6F0}") = "Package", "Package\Package.wixproj", "{F403A403-CEFF-4399-B51C-CC646C8E98CF}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CustomActions", "CustomActions\CustomActions.vcxproj", "{6B3647E0-B4A3-46AE-8757-A22EE51C1DAC}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Release|x64 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{F403A403-CEFF-4399-B51C-CC646C8E98CF}.Release|x64.ActiveCfg = Release|x64
|
||||
{F403A403-CEFF-4399-B51C-CC646C8E98CF}.Release|x64.Build.0 = Release|x64
|
||||
{6B3647E0-B4A3-46AE-8757-A22EE51C1DAC}.Release|x64.ActiveCfg = Release|x64
|
||||
{6B3647E0-B4A3-46AE-8757-A22EE51C1DAC}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {95277884-55F2-4A1F-BFFB-E82EFE847DC2}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,560 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import json
|
||||
import sys
|
||||
import uuid
|
||||
import argparse
|
||||
import datetime
|
||||
import subprocess
|
||||
import re
|
||||
import platform
|
||||
from pathlib import Path
|
||||
from itertools import chain
|
||||
import shutil
|
||||
|
||||
g_indent_unit = "\t"
|
||||
g_version = ""
|
||||
g_build_date = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
|
||||
|
||||
# Replace the following links with your own in the custom arp properties.
|
||||
# https://learn.microsoft.com/en-us/windows/win32/msi/property-reference
|
||||
g_arpsystemcomponent = {
|
||||
"Comments": {
|
||||
"msi": "ARPCOMMENTS",
|
||||
"t": "string",
|
||||
"v": "!(loc.AR_Comment)",
|
||||
},
|
||||
"Contact": {
|
||||
"msi": "ARPCONTACT",
|
||||
"v": "https://github.com/rustdesk/rustdesk",
|
||||
},
|
||||
"HelpLink": {
|
||||
"msi": "ARPHELPLINK",
|
||||
"v": "https://github.com/rustdesk/rustdesk/issues/",
|
||||
},
|
||||
"ReadMe": {
|
||||
"msi": "ARPREADME",
|
||||
"v": "https://github.com/rustdesk/rustdesk",
|
||||
},
|
||||
}
|
||||
|
||||
def default_revision_version():
|
||||
return int(datetime.datetime.now().timestamp() / 60)
|
||||
|
||||
def make_parser():
|
||||
parser = argparse.ArgumentParser(description="Msi preprocess script.")
|
||||
parser.add_argument(
|
||||
"-d",
|
||||
"--dist-dir",
|
||||
type=str,
|
||||
default="../../rustdesk",
|
||||
help="The dist directory to install.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--arp",
|
||||
action="store_true",
|
||||
help="Is ARPSYSTEMCOMPONENT",
|
||||
default=False,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--custom-arp",
|
||||
type=str,
|
||||
default="{}",
|
||||
help='Custom arp properties, e.g. \'["Comments": {"msi": "ARPCOMMENTS", "v": "Remote control application."}]\'',
|
||||
)
|
||||
parser.add_argument(
|
||||
"-c", "--custom", action="store_true", help="Is custom client", default=False
|
||||
)
|
||||
parser.add_argument(
|
||||
"--conn-type",
|
||||
type=str,
|
||||
default="",
|
||||
help='Connection type, e.g. "incoming", "outgoing". Default is empty, means incoming-outgoing',
|
||||
)
|
||||
parser.add_argument(
|
||||
"--app-name", type=str, default="RustDesk", help="The app name."
|
||||
)
|
||||
parser.add_argument(
|
||||
"-v", "--version", type=str, default="", help="The app version."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--revision-version", type=int, default=default_revision_version(), help="The revision version."
|
||||
)
|
||||
parser.add_argument(
|
||||
"-m",
|
||||
"--manufacturer",
|
||||
type=str,
|
||||
default="PURSLANE",
|
||||
help="The app manufacturer.",
|
||||
)
|
||||
return parser
|
||||
|
||||
|
||||
def read_lines_and_start_index(file_path, tag_start, tag_end):
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
lines = f.readlines()
|
||||
index_start = -1
|
||||
index_end = -1
|
||||
for i, line in enumerate(lines):
|
||||
if tag_start in line:
|
||||
index_start = i
|
||||
if tag_end in line:
|
||||
index_end = i
|
||||
|
||||
if index_start == -1:
|
||||
print(f'Error: start tag "{tag_start}" not found')
|
||||
return None, None
|
||||
if index_end == -1:
|
||||
print(f'Error: end tag "{tag_end}" not found')
|
||||
return None, None
|
||||
return lines, index_start
|
||||
|
||||
|
||||
def insert_components_between_tags(lines, index_start, app_name, dist_dir):
|
||||
indent = g_indent_unit * 3
|
||||
path = Path(dist_dir)
|
||||
idx = 1
|
||||
for file_path in path.glob("**/*"):
|
||||
if file_path.is_file():
|
||||
if file_path.name.lower() == f"{app_name}.exe".lower():
|
||||
continue
|
||||
|
||||
subdir = str(file_path.parent.relative_to(path))
|
||||
dir_attr = ""
|
||||
if subdir != ".":
|
||||
dir_attr = f'Subdirectory="{subdir}"'
|
||||
|
||||
# Don't generate Component Id and File Id like 'Component_{idx}' and 'File_{idx}'
|
||||
# because it will cause error
|
||||
# "Error WIX0130 The primary key 'xxxx' is duplicated in table 'Directory'"
|
||||
to_insert_lines = f"""
|
||||
{indent}<Component Guid="{uuid.uuid4()}" {dir_attr}>
|
||||
{indent}{g_indent_unit}<File Source="{file_path.as_posix()}" KeyPath="yes" Checksum="yes" />
|
||||
{indent}</Component>
|
||||
"""
|
||||
lines.insert(index_start + 1, to_insert_lines[1:])
|
||||
index_start += 1
|
||||
idx += 1
|
||||
return True
|
||||
|
||||
|
||||
def gen_auto_component(app_name, dist_dir):
|
||||
return gen_content_between_tags(
|
||||
"Package/Components/RustDesk.wxs",
|
||||
"<!--$AutoComonentStart$-->",
|
||||
"<!--$AutoComponentEnd$-->",
|
||||
lambda lines, index_start: insert_components_between_tags(
|
||||
lines, index_start, app_name, dist_dir
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def gen_pre_vars(args, dist_dir):
|
||||
def func(lines, index_start):
|
||||
upgrade_code = uuid.uuid5(uuid.NAMESPACE_OID, app_name + ".exe")
|
||||
|
||||
indent = g_indent_unit * 1
|
||||
to_insert_lines = [
|
||||
f'{indent}<?define Version="{g_version}" ?>\n',
|
||||
f'{indent}<?define Manufacturer="{args.manufacturer}" ?>\n',
|
||||
f'{indent}<?define Product="{args.app_name}" ?>\n',
|
||||
f'{indent}<?define Description="{args.app_name} Installer" ?>\n',
|
||||
f'{indent}<?define ProductLower="{args.app_name.lower()}" ?>\n',
|
||||
f'{indent}<?define RegKeyRoot=".$(var.ProductLower)" ?>\n',
|
||||
f'{indent}<?define RegKeyInstall="$(var.RegKeyRoot)\\Install" ?>\n',
|
||||
f'{indent}<?define BuildDir="{dist_dir}" ?>\n',
|
||||
f'{indent}<?define BuildDate="{g_build_date}" ?>\n',
|
||||
"\n",
|
||||
f"{indent}<!-- The UpgradeCode must be consistent for each product. ! -->\n"
|
||||
f'{indent}<?define UpgradeCode = "{upgrade_code}" ?>\n',
|
||||
]
|
||||
|
||||
for i, line in enumerate(to_insert_lines):
|
||||
lines.insert(index_start + i + 1, line)
|
||||
return lines
|
||||
|
||||
return gen_content_between_tags(
|
||||
"Package/Includes.wxi", "<!--$PreVarsStart$-->", "<!--$PreVarsEnd$-->", func
|
||||
)
|
||||
|
||||
|
||||
def replace_app_name_in_langs(app_name):
|
||||
langs_dir = Path(sys.argv[0]).parent.joinpath("Package/Language")
|
||||
for file_path in langs_dir.glob("*.wxl"):
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
lines = f.readlines()
|
||||
for i, line in enumerate(lines):
|
||||
lines[i] = line.replace("RustDesk", app_name)
|
||||
with open(file_path, "w", encoding="utf-8") as f:
|
||||
f.writelines(lines)
|
||||
|
||||
def replace_app_name_in_custom_actions(app_name):
|
||||
custion_actions_dir = Path(sys.argv[0]).parent.joinpath("CustomActions")
|
||||
for file_path in chain(custion_actions_dir.glob("*.cpp"), custion_actions_dir.glob("*.h")):
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
lines = f.readlines()
|
||||
for i, line in enumerate(lines):
|
||||
line = re.sub(r"\bRustDesk\b", app_name, line)
|
||||
line = line.replace(f"{app_name} v4 Printer Driver", "RustDesk v4 Printer Driver")
|
||||
lines[i] = line
|
||||
with open(file_path, "w", encoding="utf-8") as f:
|
||||
f.writelines(lines)
|
||||
|
||||
def gen_upgrade_info():
|
||||
def func(lines, index_start):
|
||||
indent = g_indent_unit * 3
|
||||
|
||||
vs = g_version.split(".")
|
||||
major = vs[0]
|
||||
upgrade_id = uuid.uuid4()
|
||||
to_insert_lines = [
|
||||
f'{indent}<Upgrade Id="{upgrade_id}">\n',
|
||||
f'{indent}{g_indent_unit}<UpgradeVersion Property="OLD_VERSION_FOUND" Minimum="{major}.0.0" Maximum="{major}.99.99" IncludeMinimum="yes" IncludeMaximum="yes" OnlyDetect="no" IgnoreRemoveFailure="yes" MigrateFeatures="yes" />\n',
|
||||
f"{indent}</Upgrade>\n",
|
||||
]
|
||||
|
||||
for i, line in enumerate(to_insert_lines):
|
||||
lines.insert(index_start + i + 1, line)
|
||||
return lines
|
||||
|
||||
return gen_content_between_tags(
|
||||
"Package/Fragments/Upgrades.wxs",
|
||||
"<!--$UpgradeStart$-->",
|
||||
"<!--$UpgradeEnd$-->",
|
||||
func,
|
||||
)
|
||||
|
||||
|
||||
def gen_custom_dialog_bitmaps():
|
||||
def func(lines, index_start):
|
||||
indent = g_indent_unit * 2
|
||||
|
||||
# https://wixtoolset.org/docs/tools/wixext/wixui/#customizing-a-dialog-set
|
||||
vars = [
|
||||
"WixUIBannerBmp",
|
||||
"WixUIDialogBmp",
|
||||
"WixUIExclamationIco",
|
||||
"WixUIInfoIco",
|
||||
"WixUINewIco",
|
||||
"WixUIUpIco",
|
||||
]
|
||||
to_insert_lines = []
|
||||
for var in vars:
|
||||
if Path(f"Package/Resources/{var}.bmp").exists():
|
||||
to_insert_lines.append(
|
||||
f'{indent}<WixVariable Id="{var}" Value="Resources\\{var}.bmp" />\n'
|
||||
)
|
||||
|
||||
for i, line in enumerate(to_insert_lines):
|
||||
lines.insert(index_start + i + 1, line)
|
||||
return lines
|
||||
|
||||
return gen_content_between_tags(
|
||||
"Package/Package.wxs",
|
||||
"<!--$CustomBitmapsStart$-->",
|
||||
"<!--$CustomBitmapsEnd$-->",
|
||||
func,
|
||||
)
|
||||
|
||||
|
||||
def gen_custom_ARPSYSTEMCOMPONENT_False(args):
|
||||
def func(lines, index_start):
|
||||
indent = g_indent_unit * 2
|
||||
|
||||
lines_new = []
|
||||
lines_new.append(
|
||||
f"{indent}<!--https://learn.microsoft.com/en-us/windows/win32/msi/arpsystemcomponent?redirectedfrom=MSDN-->\n"
|
||||
)
|
||||
lines_new.append(
|
||||
f'{indent}<!--<Property Id="ARPSYSTEMCOMPONENT" Value="1" />-->\n\n'
|
||||
)
|
||||
|
||||
lines_new.append(
|
||||
f"{indent}<!--https://learn.microsoft.com/en-us/windows/win32/msi/property-reference-->\n"
|
||||
)
|
||||
for _, v in g_arpsystemcomponent.items():
|
||||
if "msi" in v and "v" in v:
|
||||
lines_new.append(
|
||||
f'{indent}<Property Id="{v["msi"]}" Value="{v["v"]}" />\n'
|
||||
)
|
||||
|
||||
for i, line in enumerate(lines_new):
|
||||
lines.insert(index_start + i + 1, line)
|
||||
return lines
|
||||
|
||||
return gen_content_between_tags(
|
||||
"Package/Fragments/AddRemoveProperties.wxs",
|
||||
"<!--$ArpStart$-->",
|
||||
"<!--$ArpEnd$-->",
|
||||
func,
|
||||
)
|
||||
|
||||
|
||||
def get_folder_size(folder_path):
|
||||
total_size = 0
|
||||
|
||||
folder = Path(folder_path)
|
||||
for file in folder.glob("**/*"):
|
||||
if file.is_file():
|
||||
total_size += file.stat().st_size
|
||||
|
||||
return total_size
|
||||
|
||||
|
||||
def gen_custom_ARPSYSTEMCOMPONENT_True(args, dist_dir):
|
||||
def func(lines, index_start):
|
||||
indent = g_indent_unit * 5
|
||||
|
||||
lines_new = []
|
||||
lines_new.append(
|
||||
f"{indent}<!--https://learn.microsoft.com/en-us/windows/win32/msi/property-reference-->\n"
|
||||
)
|
||||
lines_new.append(
|
||||
f'{indent}<RegistryValue Type="string" Name="DisplayName" Value="{args.app_name}" />\n'
|
||||
)
|
||||
lines_new.append(
|
||||
f'{indent}<RegistryValue Type="string" Name="DisplayIcon" Value="[INSTALLFOLDER_INNER]{args.app_name}.exe" />\n'
|
||||
)
|
||||
lines_new.append(
|
||||
f'{indent}<RegistryValue Type="string" Name="DisplayVersion" Value="{g_version}" />\n'
|
||||
)
|
||||
lines_new.append(
|
||||
f'{indent}<RegistryValue Type="string" Name="Publisher" Value="{args.manufacturer}" />\n'
|
||||
)
|
||||
installDate = datetime.datetime.now().strftime("%Y%m%d")
|
||||
lines_new.append(
|
||||
f'{indent}<RegistryValue Type="string" Name="InstallDate" Value="{installDate}" />\n'
|
||||
)
|
||||
lines_new.append(
|
||||
f'{indent}<RegistryValue Type="string" Name="InstallLocation" Value="[INSTALLFOLDER_INNER]" />\n'
|
||||
)
|
||||
lines_new.append(
|
||||
f'{indent}<RegistryValue Type="string" Name="InstallSource" Value="[InstallSource]" />\n'
|
||||
)
|
||||
lines_new.append(
|
||||
f'{indent}<RegistryValue Type="integer" Name="Language" Value="[ProductLanguage]" />\n'
|
||||
)
|
||||
|
||||
# EstimatedSize in uninstall registry must be in KB.
|
||||
estimated_size_bytes = get_folder_size(dist_dir)
|
||||
estimated_size = max(1, (estimated_size_bytes + 1023) // 1024)
|
||||
lines_new.append(
|
||||
f'{indent}<RegistryValue Type="integer" Name="EstimatedSize" Value="{estimated_size}" />\n'
|
||||
)
|
||||
|
||||
lines_new.append(
|
||||
f'{indent}<RegistryValue Type="expandable" Name="ModifyPath" Value="MsiExec.exe /X [ProductCode]" />\n'
|
||||
)
|
||||
lines_new.append(
|
||||
f'{indent}<RegistryValue Type="integer" Id="NoModify" Value="1" />\n'
|
||||
)
|
||||
lines_new.append(
|
||||
f'{indent}<RegistryValue Type="expandable" Name="UninstallString" Value="MsiExec.exe /X [ProductCode]" />\n'
|
||||
)
|
||||
lines_new.append(
|
||||
f'{indent}<RegistryValue Type="expandable" Name="QuietUninstallString" Value="MsiExec.exe /qn /X [ProductCode]" />\n'
|
||||
)
|
||||
|
||||
vs = g_version.split(".")
|
||||
major, minor, build = vs[0], vs[1], vs[2]
|
||||
lines_new.append(
|
||||
f'{indent}<RegistryValue Type="string" Name="Version" Value="{g_version}" />\n'
|
||||
)
|
||||
lines_new.append(
|
||||
f'{indent}<RegistryValue Type="integer" Name="VersionMajor" Value="{major}" />\n'
|
||||
)
|
||||
lines_new.append(
|
||||
f'{indent}<RegistryValue Type="integer" Name="VersionMinor" Value="{minor}" />\n'
|
||||
)
|
||||
lines_new.append(
|
||||
f'{indent}<RegistryValue Type="integer" Name="VersionBuild" Value="{build}" />\n'
|
||||
)
|
||||
|
||||
lines_new.append(
|
||||
f'{indent}<RegistryValue Type="integer" Name="WindowsInstaller" Value="1" />\n'
|
||||
)
|
||||
for k, v in g_arpsystemcomponent.items():
|
||||
if "v" in v:
|
||||
t = v["t"] if "t" in v is None else "string"
|
||||
lines_new.append(
|
||||
f'{indent}<RegistryValue Type="{t}" Name="{k}" Value="{v["v"]}" />\n'
|
||||
)
|
||||
|
||||
for i, line in enumerate(lines_new):
|
||||
lines.insert(index_start + i + 1, line)
|
||||
return lines
|
||||
|
||||
return gen_content_between_tags(
|
||||
"Package/Components/Regs.wxs",
|
||||
"<!--$ArpStart$-->",
|
||||
"<!--$ArpEnd$-->",
|
||||
func,
|
||||
)
|
||||
|
||||
|
||||
def gen_custom_ARPSYSTEMCOMPONENT(args, dist_dir):
|
||||
try:
|
||||
custom_arp = json.loads(args.custom_arp)
|
||||
g_arpsystemcomponent.update(custom_arp)
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"Failed to decode custom arp: {e}")
|
||||
return False
|
||||
|
||||
if args.arp:
|
||||
return gen_custom_ARPSYSTEMCOMPONENT_True(args, dist_dir)
|
||||
else:
|
||||
return gen_custom_ARPSYSTEMCOMPONENT_False(args)
|
||||
|
||||
def gen_conn_type(args):
|
||||
def func(lines, index_start):
|
||||
indent = g_indent_unit * 3
|
||||
|
||||
lines_new = []
|
||||
if args.conn_type != "":
|
||||
lines_new.append(
|
||||
f"""{indent}<Property Id="CC_CONNECTION_TYPE" Value="{args.conn_type}" />\n"""
|
||||
)
|
||||
|
||||
for i, line in enumerate(lines_new):
|
||||
lines.insert(index_start + i + 1, line)
|
||||
return lines
|
||||
|
||||
return gen_content_between_tags(
|
||||
"Package/Fragments/AddRemoveProperties.wxs",
|
||||
"<!--$CustomClientPropsStart$-->",
|
||||
"<!--$CustomClientPropsEnd$-->",
|
||||
func,
|
||||
)
|
||||
|
||||
def gen_content_between_tags(filename, tag_start, tag_end, func):
|
||||
target_file = Path(sys.argv[0]).parent.joinpath(filename)
|
||||
lines, index_start = read_lines_and_start_index(target_file, tag_start, tag_end)
|
||||
if lines is None:
|
||||
return False
|
||||
|
||||
func(lines, index_start)
|
||||
|
||||
with open(target_file, "w", encoding="utf-8") as f:
|
||||
f.writelines(lines)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def prepare_resources():
|
||||
icon_src = Path(sys.argv[0]).parent.joinpath("../icon.ico")
|
||||
icon_dst = Path(sys.argv[0]).parent.joinpath("Package/Resources/icon.ico")
|
||||
if icon_src.exists():
|
||||
icon_dst.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy(icon_src, icon_dst)
|
||||
return True
|
||||
else:
|
||||
# unreachable
|
||||
print(f"Error: icon.ico not found in {icon_src}")
|
||||
return False
|
||||
|
||||
|
||||
def init_global_vars(dist_dir, app_name, args):
|
||||
dist_app = dist_dir.joinpath(app_name + ".exe")
|
||||
|
||||
def read_process_output(args):
|
||||
process = subprocess.Popen(
|
||||
f"{dist_app} {args}",
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
shell=True,
|
||||
)
|
||||
output, _ = process.communicate()
|
||||
return output.decode("utf-8").strip()
|
||||
|
||||
global g_version
|
||||
global g_build_date
|
||||
g_version = args.version.replace("-", ".")
|
||||
if g_version == "":
|
||||
g_version = read_process_output("--version")
|
||||
version_pattern = re.compile(r"\d+\.\d+\.\d+.*")
|
||||
if not version_pattern.match(g_version):
|
||||
print(f"Error: version {g_version} not found in {dist_app}")
|
||||
return False
|
||||
if g_version.count(".") == 2:
|
||||
# https://github.com/dotnet/runtime/blob/5535e31a712343a63f5d7d796cd874e563e5ac14/src/libraries/System.Private.CoreLib/src/System/Version.cs
|
||||
if args.revision_version < 0 or args.revision_version > 2147483647:
|
||||
raise ValueError(f"Invalid revision version: {args.revision_version}")
|
||||
g_version = f"{g_version}.{args.revision_version}"
|
||||
|
||||
g_build_date = read_process_output("--build-date")
|
||||
build_date_pattern = re.compile(r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}")
|
||||
if not build_date_pattern.match(g_build_date):
|
||||
print(f"Error: build date {g_build_date} not found in {dist_app}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def update_license_file(app_name):
|
||||
if app_name == "RustDesk":
|
||||
return
|
||||
license_file = Path(sys.argv[0]).parent.joinpath("Package/License.rtf")
|
||||
with open(license_file, "r", encoding="utf-8") as f:
|
||||
license_content = f.read()
|
||||
license_content = license_content.replace("website rustdesk.com and other ", "")
|
||||
license_content = license_content.replace("RustDesk", app_name)
|
||||
license_content = re.sub("Purslane Ltd", app_name, license_content, flags=re.IGNORECASE)
|
||||
with open(license_file, "w", encoding="utf-8") as f:
|
||||
f.write(license_content)
|
||||
|
||||
|
||||
def replace_component_guids_in_wxs():
|
||||
langs_dir = Path(sys.argv[0]).parent.joinpath("Package")
|
||||
for file_path in langs_dir.glob("**/*.wxs"):
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
lines = f.readlines()
|
||||
|
||||
# <Component Id="Product.Registry.DefaultIcon" Guid="6DBF2690-0955-4C6A-940F-634DDA503F49">
|
||||
for i, line in enumerate(lines):
|
||||
match = re.search(r'Component.+Guid="([^"]+)"', line)
|
||||
if match:
|
||||
lines[i] = re.sub(r'Guid="[^"]+"', f'Guid="{uuid.uuid4()}"', line)
|
||||
|
||||
with open(file_path, "w", encoding="utf-8") as f:
|
||||
f.writelines(lines)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = make_parser()
|
||||
args = parser.parse_args()
|
||||
|
||||
app_name = args.app_name
|
||||
dist_dir = Path(sys.argv[0]).parent.joinpath(args.dist_dir).resolve()
|
||||
|
||||
if not prepare_resources():
|
||||
sys.exit(-1)
|
||||
|
||||
if not init_global_vars(dist_dir, app_name, args):
|
||||
sys.exit(-1)
|
||||
|
||||
update_license_file(app_name)
|
||||
|
||||
if not gen_pre_vars(args, dist_dir):
|
||||
sys.exit(-1)
|
||||
|
||||
if app_name != "RustDesk":
|
||||
replace_component_guids_in_wxs()
|
||||
|
||||
if not gen_upgrade_info():
|
||||
sys.exit(-1)
|
||||
|
||||
if not gen_custom_ARPSYSTEMCOMPONENT(args, dist_dir):
|
||||
sys.exit(-1)
|
||||
|
||||
if not gen_conn_type(args):
|
||||
sys.exit(-1)
|
||||
|
||||
if not gen_auto_component(app_name, dist_dir):
|
||||
sys.exit(-1)
|
||||
|
||||
if not gen_custom_dialog_bitmaps():
|
||||
sys.exit(-1)
|
||||
|
||||
replace_app_name_in_langs(args.app_name)
|
||||
replace_app_name_in_custom_actions(args.app_name)
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo $MACOS_CODESIGN_IDENTITY
|
||||
cargo install flutter_rust_bridge_codegen --version 1.80.1 --features uuid --locked
|
||||
cd flutter; flutter pub get; cd -
|
||||
~/.cargo/bin/flutter_rust_bridge_codegen --rust-input ./src/flutter_ffi.rs --dart-output ./flutter/lib/generated_bridge.dart --c-output ./flutter/macos/Runner/bridge_generated.h
|
||||
./build.py --flutter
|
||||
rm rustdesk-$VERSION.dmg
|
||||
# security find-identity -v
|
||||
codesign --force --options runtime -s $MACOS_CODESIGN_IDENTITY --deep --strict ./flutter/build/macos/Build/Products/Release/RustDesk.app -vvv
|
||||
create-dmg --icon "RustDesk.app" 200 190 --hide-extension "RustDesk.app" --window-size 800 400 --app-drop-link 600 185 rustdesk-$VERSION.dmg ./flutter/build/macos/Build/Products/Release/RustDesk.app
|
||||
codesign --force --options runtime -s $MACOS_CODESIGN_IDENTITY --deep --strict rustdesk-$VERSION.dmg -vvv
|
||||
# notarize the rustdesk-${{ env.VERSION }}.dmg
|
||||
rcodesign notary-submit --api-key-path ~/.p12/api-key.json --staple rustdesk-$VERSION.dmg
|
||||
@@ -0,0 +1,47 @@
|
||||
# arg 1: the new package version
|
||||
#pre_install() {
|
||||
#}
|
||||
|
||||
# arg 1: the new package version
|
||||
post_install() {
|
||||
# do something here
|
||||
cp /usr/share/rustdesk/files/rustdesk.service /etc/systemd/system/rustdesk.service
|
||||
cp /usr/share/rustdesk/files/rustdesk.desktop /usr/share/applications/
|
||||
cp /usr/share/rustdesk/files/rustdesk-link.desktop /usr/share/applications/
|
||||
systemctl daemon-reload
|
||||
systemctl enable rustdesk
|
||||
systemctl start rustdesk
|
||||
update-desktop-database
|
||||
}
|
||||
|
||||
# arg 1: the new package version
|
||||
# arg 2: the old package version
|
||||
pre_upgrade() {
|
||||
systemctl stop rustdesk || true
|
||||
}
|
||||
|
||||
# arg 1: the new package version
|
||||
# arg 2: the old package version
|
||||
post_upgrade() {
|
||||
cp /usr/share/rustdesk/files/rustdesk.service /etc/systemd/system/rustdesk.service
|
||||
cp /usr/share/rustdesk/files/rustdesk.desktop /usr/share/applications/
|
||||
cp /usr/share/rustdesk/files/rustdesk-link.desktop /usr/share/applications/
|
||||
systemctl daemon-reload
|
||||
systemctl enable rustdesk
|
||||
systemctl start rustdesk
|
||||
update-desktop-database
|
||||
}
|
||||
|
||||
# arg 1: the old package version
|
||||
pre_remove() {
|
||||
systemctl stop rustdesk || true
|
||||
systemctl disable rustdesk || true
|
||||
rm /etc/systemd/system/rustdesk.service || true
|
||||
}
|
||||
|
||||
# arg 1: the old package version
|
||||
post_remove() {
|
||||
rm /usr/share/applications/rustdesk.desktop || true
|
||||
rm /usr/share/applications/rustdesk-link.desktop || true
|
||||
update-desktop-database
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#%PAM-1.0
|
||||
@include common-auth
|
||||
@include common-account
|
||||
@include common-session
|
||||
@include common-password
|
||||
@@ -0,0 +1,5 @@
|
||||
#%PAM-1.0
|
||||
auth include common-auth
|
||||
account include common-account
|
||||
session include common-session
|
||||
password include common-password
|
||||
@@ -0,0 +1,98 @@
|
||||
Name: rustdesk
|
||||
Version: 1.4.7
|
||||
Release: 0
|
||||
Summary: RPM package
|
||||
License: GPL-3.0
|
||||
URL: https://rustdesk.com
|
||||
Vendor: rustdesk <info@rustdesk.com>
|
||||
Requires: gtk3 libxcb1 libXfixes3 alsa-utils libXtst6 libva2 pam gstreamer-plugins-base gstreamer-plugin-pipewire
|
||||
Recommends: libayatana-appindicator3-1 xdotool
|
||||
Provides: libdesktop_drop_plugin.so()(64bit), libdesktop_multi_window_plugin.so()(64bit), libfile_selector_linux_plugin.so()(64bit), libflutter_custom_cursor_plugin.so()(64bit), libflutter_linux_gtk.so()(64bit), libscreen_retriever_plugin.so()(64bit), libtray_manager_plugin.so()(64bit), liburl_launcher_linux_plugin.so()(64bit), libwindow_manager_plugin.so()(64bit), libwindow_size_plugin.so()(64bit), libtexture_rgba_renderer_plugin.so()(64bit)
|
||||
|
||||
# https://docs.fedoraproject.org/en-US/packaging-guidelines/Scriptlets/
|
||||
|
||||
%description
|
||||
The best open-source remote desktop client software, written in Rust.
|
||||
|
||||
%prep
|
||||
# we have no source, so nothing here
|
||||
|
||||
%build
|
||||
# we have no source, so nothing here
|
||||
|
||||
# %global __python %{__python3}
|
||||
|
||||
%install
|
||||
|
||||
mkdir -p "%{buildroot}/usr/share/rustdesk" && cp -r ${HBB}/flutter/build/linux/x64/release/bundle/* -t "%{buildroot}/usr/share/rustdesk"
|
||||
mkdir -p "%{buildroot}/usr/bin"
|
||||
install -Dm 644 $HBB/res/rustdesk.service -t "%{buildroot}/usr/share/rustdesk/files"
|
||||
install -Dm 644 $HBB/res/rustdesk.desktop -t "%{buildroot}/usr/share/rustdesk/files"
|
||||
install -Dm 644 $HBB/res/rustdesk-link.desktop -t "%{buildroot}/usr/share/rustdesk/files"
|
||||
install -Dm 644 $HBB/res/128x128@2x.png "%{buildroot}/usr/share/icons/hicolor/256x256/apps/rustdesk.png"
|
||||
install -Dm 644 $HBB/res/scalable.svg "%{buildroot}/usr/share/icons/hicolor/scalable/apps/rustdesk.svg"
|
||||
|
||||
%files
|
||||
/usr/share/rustdesk/*
|
||||
/usr/share/rustdesk/files/rustdesk.service
|
||||
/usr/share/icons/hicolor/256x256/apps/rustdesk.png
|
||||
/usr/share/icons/hicolor/scalable/apps/rustdesk.svg
|
||||
/usr/share/rustdesk/files/rustdesk.desktop
|
||||
/usr/share/rustdesk/files/rustdesk-link.desktop
|
||||
|
||||
%changelog
|
||||
# let's skip this for now
|
||||
|
||||
%pre
|
||||
# can do something for centos7
|
||||
case "$1" in
|
||||
1)
|
||||
# for install
|
||||
;;
|
||||
2)
|
||||
# for upgrade
|
||||
systemctl stop rustdesk || true
|
||||
;;
|
||||
esac
|
||||
|
||||
%post
|
||||
cp /usr/share/rustdesk/files/rustdesk.service /etc/systemd/system/rustdesk.service
|
||||
cp /usr/share/rustdesk/files/rustdesk.desktop /usr/share/applications/
|
||||
cp /usr/share/rustdesk/files/rustdesk-link.desktop /usr/share/applications/
|
||||
ln -sf /usr/share/rustdesk/rustdesk /usr/bin/rustdesk
|
||||
systemctl daemon-reload
|
||||
systemctl enable rustdesk
|
||||
systemctl start rustdesk
|
||||
update-desktop-database
|
||||
|
||||
%preun
|
||||
case "$1" in
|
||||
0)
|
||||
# for uninstall
|
||||
systemctl stop rustdesk || true
|
||||
systemctl disable rustdesk || true
|
||||
rm /etc/systemd/system/rustdesk.service || true
|
||||
;;
|
||||
1)
|
||||
# for upgrade
|
||||
;;
|
||||
esac
|
||||
|
||||
%postun
|
||||
case "$1" in
|
||||
0)
|
||||
# for uninstall
|
||||
rm /usr/bin/rustdesk || true
|
||||
rmdir /usr/lib/rustdesk || true
|
||||
rmdir /usr/local/rustdesk || true
|
||||
rmdir /usr/share/rustdesk || true
|
||||
rm /usr/share/applications/rustdesk.desktop || true
|
||||
rm /usr/share/applications/rustdesk-link.desktop || true
|
||||
update-desktop-database
|
||||
;;
|
||||
1)
|
||||
# for upgrade
|
||||
rmdir /usr/lib/rustdesk || true
|
||||
rmdir /usr/local/rustdesk || true
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,98 @@
|
||||
Name: rustdesk
|
||||
Version: 1.4.7
|
||||
Release: 0
|
||||
Summary: RPM package
|
||||
License: GPL-3.0
|
||||
URL: https://rustdesk.com
|
||||
Vendor: rustdesk <info@rustdesk.com>
|
||||
Requires: gtk3 libxcb libXfixes alsa-lib libva pam gstreamer1-plugins-base
|
||||
Recommends: libayatana-appindicator-gtk3 libxdo
|
||||
Provides: libdesktop_drop_plugin.so()(64bit), libdesktop_multi_window_plugin.so()(64bit), libfile_selector_linux_plugin.so()(64bit), libflutter_custom_cursor_plugin.so()(64bit), libflutter_linux_gtk.so()(64bit), libscreen_retriever_plugin.so()(64bit), libtray_manager_plugin.so()(64bit), liburl_launcher_linux_plugin.so()(64bit), libwindow_manager_plugin.so()(64bit), libwindow_size_plugin.so()(64bit), libtexture_rgba_renderer_plugin.so()(64bit)
|
||||
|
||||
# https://docs.fedoraproject.org/en-US/packaging-guidelines/Scriptlets/
|
||||
|
||||
%description
|
||||
The best open-source remote desktop client software, written in Rust.
|
||||
|
||||
%prep
|
||||
# we have no source, so nothing here
|
||||
|
||||
%build
|
||||
# we have no source, so nothing here
|
||||
|
||||
# %global __python %{__python3}
|
||||
|
||||
%install
|
||||
|
||||
mkdir -p "%{buildroot}/usr/share/rustdesk" && cp -r ${HBB}/flutter/build/linux/x64/release/bundle/* -t "%{buildroot}/usr/share/rustdesk"
|
||||
mkdir -p "%{buildroot}/usr/bin"
|
||||
install -Dm 644 $HBB/res/rustdesk.service -t "%{buildroot}/usr/share/rustdesk/files"
|
||||
install -Dm 644 $HBB/res/rustdesk.desktop -t "%{buildroot}/usr/share/rustdesk/files"
|
||||
install -Dm 644 $HBB/res/rustdesk-link.desktop -t "%{buildroot}/usr/share/rustdesk/files"
|
||||
install -Dm 644 $HBB/res/128x128@2x.png "%{buildroot}/usr/share/icons/hicolor/256x256/apps/rustdesk.png"
|
||||
install -Dm 644 $HBB/res/scalable.svg "%{buildroot}/usr/share/icons/hicolor/scalable/apps/rustdesk.svg"
|
||||
|
||||
%files
|
||||
/usr/share/rustdesk/*
|
||||
/usr/share/rustdesk/files/rustdesk.service
|
||||
/usr/share/icons/hicolor/256x256/apps/rustdesk.png
|
||||
/usr/share/icons/hicolor/scalable/apps/rustdesk.svg
|
||||
/usr/share/rustdesk/files/rustdesk.desktop
|
||||
/usr/share/rustdesk/files/rustdesk-link.desktop
|
||||
|
||||
%changelog
|
||||
# let's skip this for now
|
||||
|
||||
%pre
|
||||
# can do something for centos7
|
||||
case "$1" in
|
||||
1)
|
||||
# for install
|
||||
;;
|
||||
2)
|
||||
# for upgrade
|
||||
systemctl stop rustdesk || true
|
||||
;;
|
||||
esac
|
||||
|
||||
%post
|
||||
cp /usr/share/rustdesk/files/rustdesk.service /etc/systemd/system/rustdesk.service
|
||||
cp /usr/share/rustdesk/files/rustdesk.desktop /usr/share/applications/
|
||||
cp /usr/share/rustdesk/files/rustdesk-link.desktop /usr/share/applications/
|
||||
ln -sf /usr/share/rustdesk/rustdesk /usr/bin/rustdesk
|
||||
systemctl daemon-reload
|
||||
systemctl enable rustdesk
|
||||
systemctl start rustdesk
|
||||
update-desktop-database
|
||||
|
||||
%preun
|
||||
case "$1" in
|
||||
0)
|
||||
# for uninstall
|
||||
systemctl stop rustdesk || true
|
||||
systemctl disable rustdesk || true
|
||||
rm /etc/systemd/system/rustdesk.service || true
|
||||
;;
|
||||
1)
|
||||
# for upgrade
|
||||
;;
|
||||
esac
|
||||
|
||||
%postun
|
||||
case "$1" in
|
||||
0)
|
||||
# for uninstall
|
||||
rm /usr/bin/rustdesk || true
|
||||
rmdir /usr/lib/rustdesk || true
|
||||
rmdir /usr/local/rustdesk || true
|
||||
rmdir /usr/share/rustdesk || true
|
||||
rm /usr/share/applications/rustdesk.desktop || true
|
||||
rm /usr/share/applications/rustdesk-link.desktop || true
|
||||
update-desktop-database
|
||||
;;
|
||||
1)
|
||||
# for upgrade
|
||||
rmdir /usr/lib/rustdesk || true
|
||||
rmdir /usr/local/rustdesk || true
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,93 @@
|
||||
Name: rustdesk
|
||||
Version: 1.1.9
|
||||
Release: 0
|
||||
Summary: RPM package
|
||||
License: GPL-3.0
|
||||
Requires: gtk3 libxcb1 libXfixes3 alsa-utils libXtst6 libva2 pam gstreamer-plugins-base gstreamer-plugin-pipewire
|
||||
Recommends: libayatana-appindicator3-1 xdotool
|
||||
|
||||
# https://docs.fedoraproject.org/en-US/packaging-guidelines/Scriptlets/
|
||||
|
||||
%description
|
||||
The best open-source remote desktop client software, written in Rust.
|
||||
|
||||
%prep
|
||||
# we have no source, so nothing here
|
||||
|
||||
%build
|
||||
# we have no source, so nothing here
|
||||
|
||||
%global __python %{__python3}
|
||||
|
||||
%install
|
||||
mkdir -p %{buildroot}/usr/bin/
|
||||
mkdir -p %{buildroot}/usr/share/rustdesk/
|
||||
mkdir -p %{buildroot}/usr/share/rustdesk/files/
|
||||
mkdir -p %{buildroot}/usr/share/icons/hicolor/256x256/apps/
|
||||
mkdir -p %{buildroot}/usr/share/icons/hicolor/scalable/apps/
|
||||
install -m 755 $HBB/target/release/rustdesk %{buildroot}/usr/bin/rustdesk
|
||||
install $HBB/libsciter-gtk.so %{buildroot}/usr/share/rustdesk/libsciter-gtk.so
|
||||
install $HBB/res/rustdesk.service %{buildroot}/usr/share/rustdesk/files/
|
||||
install $HBB/res/128x128@2x.png %{buildroot}/usr/share/icons/hicolor/256x256/apps/rustdesk.png
|
||||
install $HBB/res/scalable.svg %{buildroot}/usr/share/icons/hicolor/scalable/apps/rustdesk.svg
|
||||
install $HBB/res/rustdesk.desktop %{buildroot}/usr/share/rustdesk/files/
|
||||
install $HBB/res/rustdesk-link.desktop %{buildroot}/usr/share/rustdesk/files/
|
||||
|
||||
%files
|
||||
/usr/bin/rustdesk
|
||||
/usr/share/rustdesk/libsciter-gtk.so
|
||||
/usr/share/rustdesk/files/rustdesk.service
|
||||
/usr/share/icons/hicolor/256x256/apps/rustdesk.png
|
||||
/usr/share/icons/hicolor/scalable/apps/rustdesk.svg
|
||||
/usr/share/rustdesk/files/rustdesk.desktop
|
||||
/usr/share/rustdesk/files/rustdesk-link.desktop
|
||||
|
||||
%changelog
|
||||
# let's skip this for now
|
||||
|
||||
%pre
|
||||
# can do something for centos7
|
||||
case "$1" in
|
||||
1)
|
||||
# for install
|
||||
;;
|
||||
2)
|
||||
# for upgrade
|
||||
systemctl stop rustdesk || true
|
||||
;;
|
||||
esac
|
||||
|
||||
%post
|
||||
cp /usr/share/rustdesk/files/rustdesk.service /etc/systemd/system/rustdesk.service
|
||||
cp /usr/share/rustdesk/files/rustdesk.desktop /usr/share/applications/
|
||||
cp /usr/share/rustdesk/files/rustdesk-link.desktop /usr/share/applications/
|
||||
systemctl daemon-reload
|
||||
systemctl enable rustdesk
|
||||
systemctl start rustdesk
|
||||
update-desktop-database
|
||||
|
||||
%preun
|
||||
case "$1" in
|
||||
0)
|
||||
# for uninstall
|
||||
systemctl stop rustdesk || true
|
||||
systemctl disable rustdesk || true
|
||||
rm /etc/systemd/system/rustdesk.service || true
|
||||
;;
|
||||
1)
|
||||
# for upgrade
|
||||
;;
|
||||
esac
|
||||
|
||||
%postun
|
||||
case "$1" in
|
||||
0)
|
||||
# for uninstall
|
||||
rm /usr/share/applications/rustdesk.desktop || true
|
||||
rm /usr/share/applications/rustdesk-link.desktop || true
|
||||
update-desktop-database
|
||||
;;
|
||||
1)
|
||||
# for upgrade
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,96 @@
|
||||
Name: rustdesk
|
||||
Version: 1.4.7
|
||||
Release: 0
|
||||
Summary: RPM package
|
||||
License: GPL-3.0
|
||||
URL: https://rustdesk.com
|
||||
Vendor: rustdesk <info@rustdesk.com>
|
||||
Requires: gtk3 libxcb libXfixes alsa-lib libva2 pam gstreamer1-plugins-base
|
||||
Recommends: libayatana-appindicator-gtk3 libxdo
|
||||
|
||||
# https://docs.fedoraproject.org/en-US/packaging-guidelines/Scriptlets/
|
||||
|
||||
%description
|
||||
The best open-source remote desktop client software, written in Rust.
|
||||
|
||||
%prep
|
||||
# we have no source, so nothing here
|
||||
|
||||
%build
|
||||
# we have no source, so nothing here
|
||||
|
||||
%global __python %{__python3}
|
||||
|
||||
%install
|
||||
mkdir -p %{buildroot}/usr/bin/
|
||||
mkdir -p %{buildroot}/usr/share/rustdesk/
|
||||
mkdir -p %{buildroot}/usr/share/rustdesk/files/
|
||||
mkdir -p %{buildroot}/usr/share/icons/hicolor/256x256/apps/
|
||||
mkdir -p %{buildroot}/usr/share/icons/hicolor/scalable/apps/
|
||||
install -m 755 $HBB/target/release/rustdesk %{buildroot}/usr/bin/rustdesk
|
||||
install $HBB/libsciter-gtk.so %{buildroot}/usr/share/rustdesk/libsciter-gtk.so
|
||||
install $HBB/res/rustdesk.service %{buildroot}/usr/share/rustdesk/files/
|
||||
install $HBB/res/128x128@2x.png %{buildroot}/usr/share/icons/hicolor/256x256/apps/rustdesk.png
|
||||
install $HBB/res/scalable.svg %{buildroot}/usr/share/icons/hicolor/scalable/apps/rustdesk.svg
|
||||
install $HBB/res/rustdesk.desktop %{buildroot}/usr/share/rustdesk/files/
|
||||
install $HBB/res/rustdesk-link.desktop %{buildroot}/usr/share/rustdesk/files/
|
||||
|
||||
%files
|
||||
/usr/bin/rustdesk
|
||||
/usr/share/rustdesk/libsciter-gtk.so
|
||||
/usr/share/rustdesk/files/rustdesk.service
|
||||
/usr/share/icons/hicolor/256x256/apps/rustdesk.png
|
||||
/usr/share/icons/hicolor/scalable/apps/rustdesk.svg
|
||||
/usr/share/rustdesk/files/rustdesk.desktop
|
||||
/usr/share/rustdesk/files/rustdesk-link.desktop
|
||||
/usr/share/rustdesk/files/__pycache__/*
|
||||
|
||||
%changelog
|
||||
# let's skip this for now
|
||||
|
||||
%pre
|
||||
# can do something for centos7
|
||||
case "$1" in
|
||||
1)
|
||||
# for install
|
||||
;;
|
||||
2)
|
||||
# for upgrade
|
||||
systemctl stop rustdesk || true
|
||||
;;
|
||||
esac
|
||||
|
||||
%post
|
||||
cp /usr/share/rustdesk/files/rustdesk.service /etc/systemd/system/rustdesk.service
|
||||
cp /usr/share/rustdesk/files/rustdesk.desktop /usr/share/applications/
|
||||
cp /usr/share/rustdesk/files/rustdesk-link.desktop /usr/share/applications/
|
||||
systemctl daemon-reload
|
||||
systemctl enable rustdesk
|
||||
systemctl start rustdesk
|
||||
update-desktop-database
|
||||
|
||||
%preun
|
||||
case "$1" in
|
||||
0)
|
||||
# for uninstall
|
||||
systemctl stop rustdesk || true
|
||||
systemctl disable rustdesk || true
|
||||
rm /etc/systemd/system/rustdesk.service || true
|
||||
;;
|
||||
1)
|
||||
# for upgrade
|
||||
;;
|
||||
esac
|
||||
|
||||
%postun
|
||||
case "$1" in
|
||||
0)
|
||||
# for uninstall
|
||||
rm /usr/share/applications/rustdesk.desktop || true
|
||||
rm /usr/share/applications/rustdesk-link.desktop || true
|
||||
update-desktop-database
|
||||
;;
|
||||
1)
|
||||
# for upgrade
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,11 @@
|
||||
[Desktop Entry]
|
||||
Name=RustDesk
|
||||
NoDisplay=true
|
||||
MimeType=x-scheme-handler/rustdesk;
|
||||
TryExec=rustdesk
|
||||
Exec=rustdesk %u
|
||||
Icon=rustdesk
|
||||
Terminal=false
|
||||
Type=Application
|
||||
StartupNotify=false
|
||||
StartupWMClass=rustdesk
|
||||
@@ -0,0 +1,19 @@
|
||||
[Desktop Entry]
|
||||
Name=RustDesk
|
||||
GenericName=Remote Desktop
|
||||
Comment=Remote Desktop
|
||||
Exec=rustdesk %u
|
||||
Icon=rustdesk
|
||||
Terminal=false
|
||||
Type=Application
|
||||
StartupNotify=true
|
||||
Categories=Network;RemoteAccess;GTK;
|
||||
Keywords=internet;linux;dart;rust;remote-control;p2p;teamviewer;rust-lang;rdp;remote-desktop;vnc;
|
||||
Actions=new-window;
|
||||
StartupWMClass=rustdesk
|
||||
|
||||
X-Desktop-File-Install-Version=0.23
|
||||
|
||||
[Desktop Action new-window]
|
||||
Name=Open a New Window
|
||||
Exec=rustdesk %u
|
||||
@@ -0,0 +1,22 @@
|
||||
[Unit]
|
||||
Description=RustDesk
|
||||
Requires=network.target
|
||||
After=systemd-user-sessions.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/bin/rustdesk --service
|
||||
# kill --tray and --server both
|
||||
ExecStop=pkill -f "rustdesk --"
|
||||
# below two lines do not work, have to use above one line
|
||||
#ExecStop=pkill -f "rustdesk --tray"
|
||||
#ExecStop=pkill -f "rustdesk --server"
|
||||
PIDFile=/run/rustdesk.pid
|
||||
KillMode=mixed
|
||||
TimeoutStopSec=30
|
||||
User=root
|
||||
LimitNOFILE=100000
|
||||
Environment="PULSE_LATENCY_MSEC=60" "PIPEWIRE_LATENCY=1024/48000"
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Executable
+130
@@ -0,0 +1,130 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# This script is derived from https://github.com/neutrinolabs/xrdp/sesman/startwm.sh.
|
||||
|
||||
#
|
||||
# This script is an example. You might need to edit this script
|
||||
# depending on your distro if it doesn't work for you.
|
||||
#
|
||||
# Uncomment the following line for debug:
|
||||
# exec xterm
|
||||
|
||||
|
||||
# Execution sequence for interactive login shell - pseudocode
|
||||
#
|
||||
# IF /etc/profile is readable THEN
|
||||
# execute ~/.bash_profile
|
||||
# END IF
|
||||
# IF ~/.bash_profile is readable THEN
|
||||
# execute ~/.bash_profile
|
||||
# ELSE
|
||||
# IF ~/.bash_login is readable THEN
|
||||
# execute ~/.bash_login
|
||||
# ELSE
|
||||
# IF ~/.profile is readable THEN
|
||||
# execute ~/.profile
|
||||
# END IF
|
||||
# END IF
|
||||
# END IF
|
||||
pre_start()
|
||||
{
|
||||
if [ -r /etc/profile ]; then
|
||||
. /etc/profile
|
||||
fi
|
||||
if [ -r ~/.bash_profile ]; then
|
||||
. ~/.bash_profile
|
||||
else
|
||||
if [ -r ~/.bash_login ]; then
|
||||
. ~/.bash_login
|
||||
else
|
||||
if [ -r ~/.profile ]; then
|
||||
. ~/.profile
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# When loging out from the interactive shell, the execution sequence is:
|
||||
#
|
||||
# IF ~/.bash_logout exists THEN
|
||||
# execute ~/.bash_logout
|
||||
# END IF
|
||||
post_start()
|
||||
{
|
||||
if [ -r ~/.bash_logout ]; then
|
||||
. ~/.bash_logout
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
#start the window manager
|
||||
wm_start()
|
||||
{
|
||||
if [ -r /etc/default/locale ]; then
|
||||
. /etc/default/locale
|
||||
export LANG LANGUAGE
|
||||
fi
|
||||
|
||||
# debian
|
||||
if [ -r /etc/X11/Xsession ]; then
|
||||
pre_start
|
||||
. /etc/X11/Xsession
|
||||
post_start
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# alpine
|
||||
# Don't use /etc/X11/xinit/Xsession - it doesn't work
|
||||
if [ -f /etc/alpine-release ]; then
|
||||
if [ -f /etc/X11/xinit/xinitrc ]; then
|
||||
pre_start
|
||||
/etc/X11/xinit/xinitrc
|
||||
post_start
|
||||
else
|
||||
echo "** xinit package isn't installed" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# el
|
||||
if [ -r /etc/X11/xinit/Xsession ]; then
|
||||
pre_start
|
||||
. /etc/X11/xinit/Xsession
|
||||
post_start
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# suse
|
||||
if [ -r /etc/X11/xdm/Xsession ]; then
|
||||
# since the following script run a user login shell,
|
||||
# do not execute the pseudo login shell scripts
|
||||
. /etc/X11/xdm/Xsession
|
||||
exit 0
|
||||
elif [ -r /usr/etc/X11/xdm/Xsession ]; then
|
||||
. /usr/etc/X11/xdm/Xsession
|
||||
exit 0
|
||||
fi
|
||||
|
||||
pre_start
|
||||
xterm
|
||||
post_start
|
||||
}
|
||||
|
||||
#. /etc/environment
|
||||
#export PATH=$PATH
|
||||
#export LANG=$LANG
|
||||
|
||||
# change PATH to be what your environment needs usually what is in
|
||||
# /etc/environment
|
||||
#PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games"
|
||||
#export PATH=$PATH
|
||||
|
||||
# for PATH and LANG from /etc/environment
|
||||
# pam will auto process the environment file if /etc/pam.d/xrdp-sesman
|
||||
# includes
|
||||
# auth required pam_env.so readenv=1
|
||||
|
||||
wm_start
|
||||
|
||||
exit 1
|
||||
Executable
+301
@@ -0,0 +1,301 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import requests
|
||||
import argparse
|
||||
import json
|
||||
|
||||
|
||||
def check_response(response):
|
||||
"""
|
||||
Check API response and handle errors.
|
||||
|
||||
Two error cases:
|
||||
1. Status code is not 200 -> exit with error
|
||||
2. Response contains {"error": "xxx"} -> exit with error
|
||||
"""
|
||||
if response.status_code != 200:
|
||||
print(f"Error: HTTP {response.status_code}: {response.text}")
|
||||
exit(1)
|
||||
|
||||
# Check for {"error": "xxx"} in response
|
||||
if response.text and response.text.strip():
|
||||
try:
|
||||
json_data = response.json()
|
||||
if isinstance(json_data, dict) and "error" in json_data:
|
||||
print(f"Error: {json_data['error']}")
|
||||
exit(1)
|
||||
return json_data
|
||||
except ValueError:
|
||||
return response.text
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def headers_with(token):
|
||||
return {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
|
||||
|
||||
|
||||
# ---------- Strategies APIs ----------
|
||||
|
||||
def list_strategies(url, token):
|
||||
"""List all strategies"""
|
||||
headers = headers_with(token)
|
||||
r = requests.get(f"{url}/api/strategies", headers=headers)
|
||||
return check_response(r)
|
||||
|
||||
|
||||
def get_strategy_by_guid(url, token, guid):
|
||||
"""Get strategy by GUID"""
|
||||
headers = headers_with(token)
|
||||
r = requests.get(f"{url}/api/strategies/{guid}", headers=headers)
|
||||
return check_response(r)
|
||||
|
||||
|
||||
def get_strategy_by_name(url, token, name):
|
||||
"""Get strategy by name"""
|
||||
strategies = list_strategies(url, token)
|
||||
if not strategies:
|
||||
return None
|
||||
for s in strategies:
|
||||
if str(s.get("name")) == name:
|
||||
return s
|
||||
return None
|
||||
|
||||
|
||||
def enable_strategy(url, token, name):
|
||||
"""Enable a strategy"""
|
||||
headers = headers_with(token)
|
||||
strategy = get_strategy_by_name(url, token, name)
|
||||
if not strategy:
|
||||
print(f"Error: Strategy '{name}' not found")
|
||||
exit(1)
|
||||
guid = strategy.get("guid")
|
||||
r = requests.put(f"{url}/api/strategies/{guid}/status", headers=headers, json=True)
|
||||
check_response(r)
|
||||
return "Success"
|
||||
|
||||
|
||||
def disable_strategy(url, token, name):
|
||||
"""Disable a strategy"""
|
||||
headers = headers_with(token)
|
||||
strategy = get_strategy_by_name(url, token, name)
|
||||
if not strategy:
|
||||
print(f"Error: Strategy '{name}' not found")
|
||||
exit(1)
|
||||
guid = strategy.get("guid")
|
||||
r = requests.put(f"{url}/api/strategies/{guid}/status", headers=headers, json=False)
|
||||
check_response(r)
|
||||
return "Success"
|
||||
|
||||
|
||||
def get_device_guid_by_id(url, token, device_id):
|
||||
"""Get device GUID by device ID (exact match)"""
|
||||
headers = headers_with(token)
|
||||
params = {"id": device_id, "pageSize": 50}
|
||||
r = requests.get(f"{url}/api/devices", headers=headers, params=params)
|
||||
res = check_response(r)
|
||||
if not res:
|
||||
return None
|
||||
|
||||
devices_data = res.get("data", []) if isinstance(res, dict) else res
|
||||
for d in devices_data:
|
||||
if d.get("id") == device_id:
|
||||
return d.get("guid")
|
||||
return None
|
||||
|
||||
|
||||
def get_user_guid_by_name(url, token, name):
|
||||
"""Get user GUID by exact name match"""
|
||||
headers = headers_with(token)
|
||||
params = {"name": name, "pageSize": 50}
|
||||
r = requests.get(f"{url}/api/users", headers=headers, params=params)
|
||||
res = check_response(r)
|
||||
if not res:
|
||||
return None
|
||||
|
||||
users_data = res.get("data", []) if isinstance(res, dict) else res
|
||||
for u in users_data:
|
||||
if u.get("name") == name:
|
||||
return u.get("guid")
|
||||
return None
|
||||
|
||||
|
||||
def get_device_group_guid_by_name(url, token, name):
|
||||
"""Get device group GUID by exact name match"""
|
||||
headers = headers_with(token)
|
||||
params = {"pageSize": 50, "name": name}
|
||||
r = requests.get(f"{url}/api/device-groups", headers=headers, params=params)
|
||||
res = check_response(r)
|
||||
if not res:
|
||||
return None
|
||||
|
||||
groups_data = res.get("data", []) if isinstance(res, dict) else res
|
||||
for g in groups_data:
|
||||
if g.get("name") == name:
|
||||
return g.get("guid")
|
||||
return None
|
||||
|
||||
|
||||
def assign_strategy(url, token, strategy_name, peers=None, users=None, device_groups=None):
|
||||
"""
|
||||
Assign strategy to peers, users, or device groups
|
||||
|
||||
Args:
|
||||
strategy_name: Name of the strategy (or None to unassign)
|
||||
peers: List of device IDs or GUIDs
|
||||
users: List of user names or GUIDs
|
||||
device_groups: List of device group names or GUIDs
|
||||
"""
|
||||
headers = headers_with(token)
|
||||
|
||||
# Get strategy GUID if strategy_name is provided
|
||||
strategy_guid = None
|
||||
if strategy_name:
|
||||
strategy = get_strategy_by_name(url, token, strategy_name)
|
||||
if not strategy:
|
||||
print(f"Error: Strategy '{strategy_name}' not found")
|
||||
exit(1)
|
||||
strategy_guid = strategy.get("guid")
|
||||
|
||||
# Convert device IDs to GUIDs
|
||||
peer_guids = []
|
||||
if peers:
|
||||
for peer in peers:
|
||||
# Check if it's already a GUID format
|
||||
if len(peer) == 36 and peer.count('-') == 4:
|
||||
peer_guids.append(peer)
|
||||
else:
|
||||
# Treat as device ID, look it up
|
||||
guid = get_device_guid_by_id(url, token, peer)
|
||||
if not guid:
|
||||
print(f"Error: Device '{peer}' not found")
|
||||
exit(1)
|
||||
peer_guids.append(guid)
|
||||
|
||||
# Convert user names to GUIDs
|
||||
user_guids = []
|
||||
if users:
|
||||
for user in users:
|
||||
# Check if it's already a GUID format
|
||||
if len(user) == 36 and user.count('-') == 4:
|
||||
user_guids.append(user)
|
||||
else:
|
||||
# Treat as username, look it up
|
||||
guid = get_user_guid_by_name(url, token, user)
|
||||
if not guid:
|
||||
print(f"Error: User '{user}' not found")
|
||||
exit(1)
|
||||
user_guids.append(guid)
|
||||
|
||||
# Convert device group names to GUIDs
|
||||
device_group_guids = []
|
||||
if device_groups:
|
||||
for dg in device_groups:
|
||||
# Check if it's already a GUID format
|
||||
if len(dg) == 36 and dg.count('-') == 4:
|
||||
device_group_guids.append(dg)
|
||||
else:
|
||||
# Treat as device group name, look it up
|
||||
guid = get_device_group_guid_by_name(url, token, dg)
|
||||
if not guid:
|
||||
print(f"Error: Device group '{dg}' not found")
|
||||
exit(1)
|
||||
device_group_guids.append(guid)
|
||||
|
||||
# Build payload
|
||||
payload = {}
|
||||
if strategy_guid:
|
||||
payload["strategy"] = strategy_guid
|
||||
|
||||
payload["peers"] = peer_guids
|
||||
payload["users"] = user_guids
|
||||
payload["groups"] = device_group_guids
|
||||
|
||||
r = requests.post(f"{url}/api/strategies/assign", headers=headers, json=payload)
|
||||
check_response(r)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Strategy manager")
|
||||
parser.add_argument("command", choices=[
|
||||
"list", "view", "enable", "disable", "assign", "unassign"
|
||||
])
|
||||
parser.add_argument("--url", required=True, help="Server URL")
|
||||
parser.add_argument("--token", required=True, help="API token")
|
||||
|
||||
parser.add_argument("--name", help="Strategy name (for view/enable/disable/assign commands)")
|
||||
parser.add_argument("--guid", help="Strategy GUID (for view command, alternative to --name)")
|
||||
|
||||
# For assign/unassign commands
|
||||
parser.add_argument("--peers", help="Comma separated device IDs or GUIDs (requires Device Permission:r)")
|
||||
parser.add_argument("--users", help="Comma separated user names or GUIDs (requires User Permission:r)")
|
||||
parser.add_argument("--device-groups", help="Comma separated device group names or GUIDs (requires Device Group Permission:r)")
|
||||
|
||||
args = parser.parse_args()
|
||||
while args.url.endswith("/"): args.url = args.url[:-1]
|
||||
|
||||
if args.command == "list":
|
||||
res = list_strategies(args.url, args.token)
|
||||
print(json.dumps(res, indent=2))
|
||||
|
||||
elif args.command == "view":
|
||||
if args.guid:
|
||||
res = get_strategy_by_guid(args.url, args.token, args.guid)
|
||||
print(json.dumps(res, indent=2))
|
||||
elif args.name:
|
||||
strategy = get_strategy_by_name(args.url, args.token, args.name)
|
||||
if not strategy:
|
||||
print(f"Error: Strategy '{args.name}' not found")
|
||||
exit(1)
|
||||
# Get full details by GUID
|
||||
guid = strategy.get("guid")
|
||||
res = get_strategy_by_guid(args.url, args.token, guid)
|
||||
print(json.dumps(res, indent=2))
|
||||
else:
|
||||
print("Error: --name or --guid is required for view command")
|
||||
exit(1)
|
||||
|
||||
elif args.command == "enable":
|
||||
if not args.name:
|
||||
print("Error: --name is required")
|
||||
exit(1)
|
||||
print(enable_strategy(args.url, args.token, args.name))
|
||||
|
||||
elif args.command == "disable":
|
||||
if not args.name:
|
||||
print("Error: --name is required")
|
||||
exit(1)
|
||||
print(disable_strategy(args.url, args.token, args.name))
|
||||
|
||||
elif args.command == "assign":
|
||||
if not args.name:
|
||||
print("Error: --name is required")
|
||||
exit(1)
|
||||
if not args.peers and not args.users and not args.device_groups:
|
||||
print("Error: at least one of --peers, --users, or --device-groups is required")
|
||||
exit(1)
|
||||
|
||||
peers = [x.strip() for x in args.peers.split(",") if x.strip()] if args.peers else None
|
||||
users = [x.strip() for x in args.users.split(",") if x.strip()] if args.users else None
|
||||
device_groups = [x.strip() for x in args.device_groups.split(",") if x.strip()] if args.device_groups else None
|
||||
|
||||
assign_strategy(args.url, args.token, args.name, peers=peers, users=users, device_groups=device_groups)
|
||||
count = (len(peers) if peers else 0) + (len(users) if users else 0) + (len(device_groups) if device_groups else 0)
|
||||
print(f"Success: Assigned strategy '{args.name}' to {count} target(s)")
|
||||
|
||||
elif args.command == "unassign":
|
||||
if not args.peers and not args.users and not args.device_groups:
|
||||
print("Error: at least one of --peers, --users, or --device-groups is required")
|
||||
exit(1)
|
||||
|
||||
peers = [x.strip() for x in args.peers.split(",") if x.strip()] if args.peers else None
|
||||
users = [x.strip() for x in args.users.split(",") if x.strip()] if args.users else None
|
||||
device_groups = [x.strip() for x in args.device_groups.split(",") if x.strip()] if args.device_groups else None
|
||||
|
||||
assign_strategy(args.url, args.token, None, peers=peers, users=users, device_groups=device_groups)
|
||||
count = (len(peers) if peers else 0) + (len(users) if users else 0) + (len(device_groups) if device_groups else 0)
|
||||
print(f"Success: Unassigned strategy from {count} target(s)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
Executable
+302
@@ -0,0 +1,302 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import requests
|
||||
import argparse
|
||||
import json
|
||||
|
||||
|
||||
def check_response(response):
|
||||
"""
|
||||
Check API response and handle errors.
|
||||
|
||||
Two error cases:
|
||||
1. Status code is not 200 -> exit with error
|
||||
2. Response contains {"error": "xxx"} -> exit with error
|
||||
"""
|
||||
if response.status_code != 200:
|
||||
print(f"Error: HTTP {response.status_code}: {response.text}")
|
||||
exit(1)
|
||||
|
||||
# Check for {"error": "xxx"} in response
|
||||
if response.text and response.text.strip():
|
||||
try:
|
||||
json_data = response.json()
|
||||
if isinstance(json_data, dict) and "error" in json_data:
|
||||
print(f"Error: {json_data['error']}")
|
||||
exit(1)
|
||||
return json_data
|
||||
except ValueError:
|
||||
return response.text
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def headers_with(token):
|
||||
return {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
|
||||
|
||||
|
||||
# ---------- User Group APIs ----------
|
||||
|
||||
def list_groups(url, token, name=None, page_size=50):
|
||||
headers = headers_with(token)
|
||||
params = {"pageSize": page_size}
|
||||
if name:
|
||||
params["name"] = name
|
||||
data, current = [], 0
|
||||
while True:
|
||||
current += 1
|
||||
params["current"] = current
|
||||
r = requests.get(f"{url}/api/user-groups", headers=headers, params=params)
|
||||
if r.status_code != 200:
|
||||
print(f"Error: HTTP {r.status_code} - {r.text}")
|
||||
exit(1)
|
||||
res = r.json()
|
||||
if "error" in res:
|
||||
print(f"Error: {res['error']}")
|
||||
exit(1)
|
||||
rows = res.get("data", [])
|
||||
data.extend(rows)
|
||||
total = res.get("total", 0)
|
||||
if len(rows) < page_size or current * page_size >= total:
|
||||
break
|
||||
return data
|
||||
|
||||
|
||||
def get_group_by_name(url, token, name):
|
||||
groups = list_groups(url, token, name)
|
||||
for g in groups:
|
||||
if str(g.get("name")) == name:
|
||||
return g
|
||||
return None
|
||||
|
||||
|
||||
def create_group(url, token, name, note=None, accessed_from=None, access_to=None):
|
||||
headers = headers_with(token)
|
||||
payload = {"name": name}
|
||||
if note:
|
||||
payload["note"] = note
|
||||
if accessed_from:
|
||||
payload["allowed_incomings"] = accessed_from
|
||||
if access_to:
|
||||
payload["allowed_outgoings"] = access_to
|
||||
r = requests.post(f"{url}/api/user-groups", headers=headers, json=payload)
|
||||
return check_response(r)
|
||||
|
||||
|
||||
def update_group(url, token, name, new_name=None, note=None, accessed_from=None, access_to=None):
|
||||
headers = headers_with(token)
|
||||
g = get_group_by_name(url, token, name)
|
||||
if not g:
|
||||
print(f"Error: Group '{name}' not found")
|
||||
exit(1)
|
||||
guid = g.get("guid")
|
||||
payload = {}
|
||||
if new_name is not None:
|
||||
payload["name"] = new_name
|
||||
if note is not None:
|
||||
payload["note"] = note
|
||||
if accessed_from is not None:
|
||||
payload["allowed_incomings"] = accessed_from
|
||||
if access_to is not None:
|
||||
payload["allowed_outgoings"] = access_to
|
||||
r = requests.patch(f"{url}/api/user-groups/{guid}", headers=headers, json=payload)
|
||||
check_response(r)
|
||||
return "Success"
|
||||
|
||||
|
||||
def delete_groups(url, token, names):
|
||||
headers = headers_with(token)
|
||||
if isinstance(names, str):
|
||||
names = [names]
|
||||
for n in names:
|
||||
g = get_group_by_name(url, token, n)
|
||||
if not g:
|
||||
print(f"Error: Group '{n}' not found")
|
||||
exit(1)
|
||||
guid = g.get("guid")
|
||||
r = requests.delete(f"{url}/api/user-groups/{guid}", headers=headers)
|
||||
check_response(r)
|
||||
return "Success"
|
||||
|
||||
|
||||
# ---------- User management in group ----------
|
||||
|
||||
def view_users(url, token, group_name=None, name=None, page_size=50):
|
||||
"""View users in a user group with filters"""
|
||||
headers = headers_with(token)
|
||||
|
||||
# Separate exact match and fuzzy match params
|
||||
params = {}
|
||||
fuzzy_params = {
|
||||
"name": name,
|
||||
}
|
||||
|
||||
# Add group_name without wildcard (exact match)
|
||||
if group_name:
|
||||
params["group_name"] = group_name
|
||||
|
||||
# Add wildcard for fuzzy search to other params
|
||||
for k, v in fuzzy_params.items():
|
||||
if v is not None:
|
||||
params[k] = "%" + v + "%" if (v != "-" and "%" not in v) else v
|
||||
|
||||
params["pageSize"] = page_size
|
||||
|
||||
data, current = [], 0
|
||||
while True:
|
||||
current += 1
|
||||
params["current"] = current
|
||||
r = requests.get(f"{url}/api/users", headers=headers, params=params)
|
||||
if r.status_code != 200:
|
||||
return check_response(r)
|
||||
res = r.json()
|
||||
rows = res.get("data", [])
|
||||
data.extend(rows)
|
||||
total = res.get("total", 0)
|
||||
if len(rows) < page_size or current * page_size >= total:
|
||||
break
|
||||
return data
|
||||
|
||||
|
||||
def add_users(url, token, group_name, user_names):
|
||||
"""Add users to a user group"""
|
||||
headers = headers_with(token)
|
||||
if isinstance(user_names, str):
|
||||
user_names = [user_names]
|
||||
|
||||
# Get the user group guid
|
||||
g = get_group_by_name(url, token, group_name)
|
||||
if not g:
|
||||
print(f"Error: Group '{group_name}' not found")
|
||||
exit(1)
|
||||
guid = g.get("guid")
|
||||
|
||||
# Get user GUIDs
|
||||
user_guids = []
|
||||
errors = []
|
||||
|
||||
for user_name in user_names:
|
||||
# Get user by exact name match
|
||||
params = {"name": user_name, "pageSize": 50}
|
||||
r = requests.get(f"{url}/api/users", headers=headers, params=params)
|
||||
if r.status_code != 200:
|
||||
errors.append(f"{user_name}: HTTP {r.status_code}")
|
||||
continue
|
||||
|
||||
users_data = r.json()
|
||||
users_list = users_data.get("data", [])
|
||||
user = None
|
||||
for u in users_list:
|
||||
if u.get("name") == user_name:
|
||||
user = u
|
||||
break
|
||||
|
||||
if not user:
|
||||
errors.append(f"{user_name}: User not found")
|
||||
continue
|
||||
|
||||
user_guids.append(user["guid"])
|
||||
|
||||
if not user_guids:
|
||||
msg = "Error: No valid users found"
|
||||
if errors:
|
||||
msg += ". " + "; ".join(errors)
|
||||
print(msg)
|
||||
exit(1)
|
||||
|
||||
# Add users to group using POST /api/user-groups/:guid
|
||||
r = requests.post(f"{url}/api/user-groups/{guid}", headers=headers, json=user_guids)
|
||||
check_response(r)
|
||||
|
||||
success_msg = f"Success: Added {len(user_guids)} user(s) to group '{group_name}'"
|
||||
if errors:
|
||||
return success_msg + " (with errors: " + "; ".join(errors) + ")"
|
||||
return success_msg
|
||||
|
||||
|
||||
def parse_rules(s):
|
||||
if not s:
|
||||
return None
|
||||
try:
|
||||
v = json.loads(s)
|
||||
if isinstance(v, list):
|
||||
# expect list of {"type": number, "name": string}
|
||||
return v
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="User Group manager")
|
||||
parser.add_argument("command", choices=[
|
||||
"view", "add", "update", "delete",
|
||||
"view-users", "add-users"
|
||||
], help=(
|
||||
"Command to execute. "
|
||||
"[view/add/update/delete/add-users: require User Group Permission] "
|
||||
"[view-users: require User Permission]"
|
||||
))
|
||||
parser.add_argument("--url", required=True)
|
||||
parser.add_argument("--token", required=True)
|
||||
|
||||
parser.add_argument("--name", help="User group name (exact match)")
|
||||
parser.add_argument("--new-name", help="New user group name (for update)")
|
||||
parser.add_argument("--note", help="Note")
|
||||
|
||||
parser.add_argument("--accessed-from", help="JSON array: '[{\"type\":0|2,\"name\":\"...\"}]' (0=User Group, 2=User)")
|
||||
parser.add_argument("--access-to", help="JSON array: '[{\"type\":0|1,\"name\":\"...\"}]' (0=User Group, 1=Device Group)")
|
||||
|
||||
parser.add_argument("--users", help="Comma separated usernames for add-users")
|
||||
|
||||
# Filters for view-users command
|
||||
parser.add_argument("--user-name", help="User name filter (for view-users, supports fuzzy search)")
|
||||
|
||||
args = parser.parse_args()
|
||||
while args.url.endswith("/"): args.url = args.url[:-1]
|
||||
|
||||
if args.command == "view":
|
||||
res = list_groups(args.url, args.token, args.name)
|
||||
print(json.dumps(res, indent=2))
|
||||
elif args.command == "add":
|
||||
if not args.name:
|
||||
print("Error: --name is required")
|
||||
exit(1)
|
||||
print(create_group(
|
||||
args.url, args.token, args.name, args.note,
|
||||
parse_rules(args.accessed_from),
|
||||
parse_rules(args.access_to)
|
||||
))
|
||||
elif args.command == "update":
|
||||
if not args.name:
|
||||
print("Error: --name is required")
|
||||
exit(1)
|
||||
print(update_group(
|
||||
args.url, args.token, args.name, args.new_name, args.note,
|
||||
parse_rules(args.accessed_from),
|
||||
parse_rules(args.access_to)
|
||||
))
|
||||
elif args.command == "delete":
|
||||
if not args.name:
|
||||
print("Error: --name is required (supports comma separated)")
|
||||
exit(1)
|
||||
names = [x.strip() for x in args.name.split(",") if x.strip()]
|
||||
print(delete_groups(args.url, args.token, names))
|
||||
elif args.command == "view-users":
|
||||
res = view_users(
|
||||
args.url,
|
||||
args.token,
|
||||
group_name=args.name,
|
||||
name=args.user_name
|
||||
)
|
||||
print(json.dumps(res, indent=2))
|
||||
elif args.command == "add-users":
|
||||
if not args.name or not args.users:
|
||||
print("Error: --name and --users are required")
|
||||
exit(1)
|
||||
users = [x.strip() for x in args.users.split(",") if x.strip()]
|
||||
print(add_users(args.url, args.token, args.name, users))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Executable
+292
@@ -0,0 +1,292 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import requests
|
||||
import argparse
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
|
||||
def check_response(response):
|
||||
"""
|
||||
Check API response and handle errors properly.
|
||||
Exit with code 1 if there's an error.
|
||||
"""
|
||||
if response.status_code != 200:
|
||||
print(f"Error: HTTP {response.status_code}: {response.text}")
|
||||
exit(1)
|
||||
|
||||
if response.text and response.text.strip():
|
||||
try:
|
||||
json_data = response.json()
|
||||
if isinstance(json_data, dict) and "error" in json_data:
|
||||
print(f"Error: {json_data['error']}")
|
||||
exit(1)
|
||||
return json_data
|
||||
except ValueError:
|
||||
return response.text
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def view(
|
||||
url,
|
||||
token,
|
||||
name=None,
|
||||
group_name=None,
|
||||
):
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
pageSize = 30
|
||||
params = {
|
||||
"name": name,
|
||||
"group_name": group_name,
|
||||
}
|
||||
|
||||
params = {
|
||||
k: "%" + v + "%" if (v != "-" and "%" not in v) else v
|
||||
for k, v in params.items()
|
||||
if v is not None
|
||||
}
|
||||
params["pageSize"] = pageSize
|
||||
|
||||
users = []
|
||||
|
||||
current = 0
|
||||
|
||||
while True:
|
||||
current += 1
|
||||
params["current"] = current
|
||||
response = requests.get(f"{url}/api/users", headers=headers, params=params)
|
||||
if response.status_code != 200:
|
||||
print(f"Error: HTTP {response.status_code} - {response.text}")
|
||||
exit(1)
|
||||
|
||||
response_json = response.json()
|
||||
if "error" in response_json:
|
||||
print(f"Error: {response_json['error']}")
|
||||
exit(1)
|
||||
|
||||
data = response_json.get("data", [])
|
||||
users.extend(data)
|
||||
|
||||
total = response_json.get("total", 0)
|
||||
if len(data) < pageSize or current * pageSize >= total:
|
||||
break
|
||||
|
||||
return users
|
||||
|
||||
|
||||
def disable(url, token, guid, name):
|
||||
print("Disable", name)
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.post(f"{url}/api/users/{guid}/disable", headers=headers)
|
||||
check_response(response)
|
||||
|
||||
|
||||
def enable(url, token, guid, name):
|
||||
print("Enable", name)
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.post(f"{url}/api/users/{guid}/enable", headers=headers)
|
||||
check_response(response)
|
||||
|
||||
|
||||
def delete_user(url, token, guid, name):
|
||||
print("Delete", name)
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.delete(f"{url}/api/users/{guid}", headers=headers)
|
||||
check_response(response)
|
||||
|
||||
|
||||
def new_user(url, token, name, password, group_name=None, email=None, note=None):
|
||||
"""Create a new user"""
|
||||
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
|
||||
payload = {
|
||||
"name": name,
|
||||
"password": password,
|
||||
}
|
||||
if group_name:
|
||||
payload["group_name"] = group_name
|
||||
if email:
|
||||
payload["email"] = email
|
||||
if note:
|
||||
payload["note"] = note
|
||||
response = requests.post(f"{url}/api/users", headers=headers, json=payload)
|
||||
check_response(response)
|
||||
|
||||
|
||||
def invite_user(url, token, email, name, group_name=None, note=None):
|
||||
"""Invite a user by email"""
|
||||
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
|
||||
payload = {
|
||||
"email": email,
|
||||
"name": name,
|
||||
}
|
||||
if group_name:
|
||||
payload["group_name"] = group_name
|
||||
if note:
|
||||
payload["note"] = note
|
||||
response = requests.post(f"{url}/api/users/invite", headers=headers, json=payload)
|
||||
check_response(response)
|
||||
|
||||
|
||||
def enable_2fa_enforce(url, token, user_guids, base_url):
|
||||
"""Enable 2FA enforcement for users"""
|
||||
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
|
||||
payload = {
|
||||
"user_guids": user_guids if isinstance(user_guids, list) else [user_guids],
|
||||
"enforce": True,
|
||||
"url": base_url
|
||||
}
|
||||
response = requests.put(f"{url}/api/users/tfa/totp/enforce", headers=headers, json=payload)
|
||||
check_response(response)
|
||||
|
||||
|
||||
def disable_2fa_enforce(url, token, user_guids, base_url=""):
|
||||
"""Disable 2FA enforcement for users"""
|
||||
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
|
||||
payload = {
|
||||
"user_guids": user_guids if isinstance(user_guids, list) else [user_guids],
|
||||
"enforce": False,
|
||||
"url": base_url
|
||||
}
|
||||
response = requests.put(f"{url}/api/users/tfa/totp/enforce", headers=headers, json=payload)
|
||||
check_response(response)
|
||||
|
||||
|
||||
def disable_email_verification(url, token, user_guids):
|
||||
"""Disable email login verification for users"""
|
||||
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
|
||||
payload = {
|
||||
"user_guids": user_guids if isinstance(user_guids, list) else [user_guids],
|
||||
"type": "email"
|
||||
}
|
||||
response = requests.put(f"{url}/api/users/disable_login_verification", headers=headers, json=payload)
|
||||
check_response(response)
|
||||
|
||||
|
||||
def reset_2fa(url, token, user_guids):
|
||||
"""Reset 2FA for users"""
|
||||
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
|
||||
payload = {
|
||||
"user_guids": user_guids if isinstance(user_guids, list) else [user_guids],
|
||||
"type": "2fa"
|
||||
}
|
||||
response = requests.put(f"{url}/api/users/disable_login_verification", headers=headers, json=payload)
|
||||
check_response(response)
|
||||
|
||||
|
||||
def force_logout(url, token, user_guids):
|
||||
"""Force logout users"""
|
||||
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
|
||||
payload = {
|
||||
"user_guids": user_guids if isinstance(user_guids, list) else [user_guids],
|
||||
}
|
||||
response = requests.post(f"{url}/api/users/force-logout", headers=headers, json=payload)
|
||||
check_response(response)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="User manager")
|
||||
parser.add_argument(
|
||||
"command",
|
||||
choices=["view", "disable", "enable", "delete", "new", "invite",
|
||||
"enable-2fa-enforce", "disable-2fa-enforce",
|
||||
"disable-email-verification", "reset-2fa", "force-logout"],
|
||||
help="Command to execute",
|
||||
)
|
||||
parser.add_argument("--url", required=True, help="URL of the API")
|
||||
parser.add_argument(
|
||||
"--token", required=True, help="Bearer token for authentication"
|
||||
)
|
||||
parser.add_argument("--name", help="User name")
|
||||
parser.add_argument("--group_name", help="Group name (for filtering in view, or for new/invite command)")
|
||||
parser.add_argument("--password", help="User password (for new command)")
|
||||
parser.add_argument("--email", help="User email (for invite command)")
|
||||
parser.add_argument("--note", help="User note (for new/invite command)")
|
||||
parser.add_argument("--web-console-url", help="Web console URL (for 2FA enforce commands)")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
while args.url.endswith("/"): args.url = args.url[:-1]
|
||||
|
||||
if args.command == "new":
|
||||
if not args.name or not args.password or not args.group_name:
|
||||
print("Error: --name and --password and --group_name are required for new command")
|
||||
exit(1)
|
||||
new_user(args.url, args.token, args.name, args.password, args.group_name, args.email, args.note)
|
||||
print("Success: User created")
|
||||
return
|
||||
|
||||
if args.command == "invite":
|
||||
if not args.email or not args.name or not args.group_name:
|
||||
print("Error: --email and --name and --group_name are required for invite command")
|
||||
exit(1)
|
||||
invite_user(args.url, args.token, args.email, args.name, args.group_name, args.note)
|
||||
print("Success: Invitation sent")
|
||||
return
|
||||
|
||||
users = view(
|
||||
args.url,
|
||||
args.token,
|
||||
args.name,
|
||||
args.group_name,
|
||||
)
|
||||
|
||||
if args.command == "view":
|
||||
if len(users) == 0:
|
||||
print("Found 0 users")
|
||||
else:
|
||||
for user in users:
|
||||
print(user)
|
||||
elif args.command in ["disable", "enable", "delete", "enable-2fa-enforce",
|
||||
"disable-2fa-enforce", "disable-email-verification", "reset-2fa", "force-logout"]:
|
||||
if len(users) == 0:
|
||||
print("Found 0 users")
|
||||
return
|
||||
|
||||
# Check if we need user confirmation for multiple users
|
||||
if len(users) > 1:
|
||||
print(f"Found {len(users)} users. Do you want to proceed with {args.command} operation on the users? (Y/N)")
|
||||
confirmation = input("Type 'Y' to confirm: ").strip()
|
||||
if confirmation.upper() != 'Y':
|
||||
print("Operation cancelled.")
|
||||
return
|
||||
|
||||
if args.command == "disable":
|
||||
for user in users:
|
||||
disable(args.url, args.token, user["guid"], user["name"])
|
||||
print("Success")
|
||||
elif args.command == "enable":
|
||||
for user in users:
|
||||
enable(args.url, args.token, user["guid"], user["name"])
|
||||
print("Success")
|
||||
elif args.command == "delete":
|
||||
for user in users:
|
||||
delete_user(args.url, args.token, user["guid"], user["name"])
|
||||
print("Success")
|
||||
elif args.command == "enable-2fa-enforce":
|
||||
if not args.web_console_url:
|
||||
print("Error: --web-console-url is required for enable-2fa-enforce")
|
||||
exit(1)
|
||||
user_guids = [user["guid"] for user in users]
|
||||
enable_2fa_enforce(args.url, args.token, user_guids, args.web_console_url)
|
||||
print(f"Success: Enabled 2FA enforcement for {len(users)} user(s)")
|
||||
elif args.command == "disable-2fa-enforce":
|
||||
user_guids = [user["guid"] for user in users]
|
||||
web_url = args.web_console_url or ""
|
||||
disable_2fa_enforce(args.url, args.token, user_guids, web_url)
|
||||
print(f"Success: Disabled 2FA enforcement for {len(users)} user(s)")
|
||||
elif args.command == "disable-email-verification":
|
||||
user_guids = [user["guid"] for user in users]
|
||||
disable_email_verification(args.url, args.token, user_guids)
|
||||
print(f"Success: Disabled email verification for {len(users)} user(s)")
|
||||
elif args.command == "reset-2fa":
|
||||
user_guids = [user["guid"] for user in users]
|
||||
reset_2fa(args.url, args.token, user_guids)
|
||||
print(f"Success: Reset 2FA for {len(users)} user(s)")
|
||||
elif args.command == "force-logout":
|
||||
user_guids = [user["guid"] for user in users]
|
||||
force_logout(args.url, args.token, user_guids)
|
||||
print(f"Success: Force logout for {len(users)} user(s)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,60 @@
|
||||
diff --git a/build/cmake/cpu.cmake b/build/cmake/cpu.cmake
|
||||
index acebe20..8c67d89 100644
|
||||
--- a/build/cmake/cpu.cmake
|
||||
+++ b/build/cmake/cpu.cmake
|
||||
@@ -120,6 +120,19 @@ elseif("${AOM_TARGET_CPU}" MATCHES "^x86")
|
||||
set(RTCD_ARCH_X86_64 "yes")
|
||||
endif()
|
||||
|
||||
+ # AVX2 requires __m256i definition starting v3.9.0
|
||||
+
|
||||
+ if(ENABLE_AVX2)
|
||||
+ aom_check_source_compiles("x86_64_avx2_m256i_available" "
|
||||
+#include <emmintrin.h>
|
||||
+#ifndef __m256i
|
||||
+#error 1
|
||||
+#endif" HAVE_AVX2_M256I)
|
||||
+ if(HAVE_AVX2_M256I EQUAL 0)
|
||||
+ set(ENABLE_AVX2 0)
|
||||
+ endif()
|
||||
+ endif()
|
||||
+
|
||||
set(X86_FLAVORS "MMX;SSE;SSE2;SSE3;SSSE3;SSE4_1;SSE4_2;AVX;AVX2")
|
||||
foreach(flavor ${X86_FLAVORS})
|
||||
if(ENABLE_${flavor} AND NOT disable_remaining_flavors)
|
||||
diff --git a/aom_dsp/x86/synonyms.h b/aom_dsp/x86/synonyms.h
|
||||
index 0d51cdf..6744ec5 100644
|
||||
--- a/aom_dsp/x86/synonyms.h
|
||||
+++ b/aom_dsp/x86/synonyms.h
|
||||
@@ -46,13 +46,6 @@ static INLINE __m128i xx_loadu_128(const void *a) {
|
||||
return _mm_loadu_si128((const __m128i *)a);
|
||||
}
|
||||
|
||||
-// Load 64 bits from each of hi and low, and pack into an SSE register
|
||||
-// Since directly loading as `int64_t`s and using _mm_set_epi64 may violate
|
||||
-// the strict aliasing rule, this takes a different approach
|
||||
-static INLINE __m128i xx_loadu_2x64(const void *hi, const void *lo) {
|
||||
- return _mm_unpacklo_epi64(_mm_loadu_si64(lo), _mm_loadu_si64(hi));
|
||||
-}
|
||||
-
|
||||
static INLINE void xx_storel_32(void *const a, const __m128i v) {
|
||||
const int val = _mm_cvtsi128_si32(v);
|
||||
memcpy(a, &val, sizeof(val));
|
||||
diff --git a/aom_dsp/x86/synonyms_avx2.h b/aom_dsp/x86/synonyms_avx2.h
|
||||
index d4e8f69..45be17e 100644
|
||||
--- a/aom_dsp/x86/synonyms_avx2.h
|
||||
+++ b/aom_dsp/x86/synonyms_avx2.h
|
||||
@@ -25,6 +25,13 @@
|
||||
* Intrinsics prefixed with yy_ operate on or return 256bit YMM registers.
|
||||
*/
|
||||
|
||||
+// Load 64 bits from each of hi and low, and pack into an SSE register
|
||||
+// Since directly loading as `int64_t`s and using _mm_set_epi64 may violate
|
||||
+// the strict aliasing rule, this takes a different approach
|
||||
+static INLINE __m128i xx_loadu_2x64(const void *hi, const void *lo) {
|
||||
+ return _mm_unpacklo_epi64(_mm_loadu_si64(lo), _mm_loadu_si64(hi));
|
||||
+}
|
||||
+
|
||||
// Loads and stores to do away with the tedium of casting the address
|
||||
// to the right type.
|
||||
static INLINE __m256i yy_load_256(const void *a) {
|
||||
@@ -0,0 +1,75 @@
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 18190f647..f4b1b359d 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -23,6 +23,9 @@ endif()
|
||||
|
||||
project(AOM C CXX)
|
||||
|
||||
+include(GNUInstallDirs)
|
||||
+include(CMakePackageConfigHelpers)
|
||||
+
|
||||
# GENERATED source property global visibility.
|
||||
if(POLICY CMP0118)
|
||||
cmake_policy(SET CMP0118 NEW)
|
||||
@@ -302,6 +305,52 @@ if(BUILD_SHARED_LIBS)
|
||||
set(AOM_LIB_TARGETS ${AOM_LIB_TARGETS} aom_static)
|
||||
endif()
|
||||
|
||||
+set(PUBLIC_HEADERS
|
||||
+ aom/aom.h
|
||||
+ aom/aom_codec.h
|
||||
+ aom/aom_decoder.h
|
||||
+ aom/aom_encoder.h
|
||||
+ aom/aom_frame_buffer.h
|
||||
+ aom/aom_image.h
|
||||
+ aom/aom_integer.h
|
||||
+ aom/aomcx.h
|
||||
+ aom/aomdx.h
|
||||
+)
|
||||
+
|
||||
+set_target_properties(aom PROPERTIES
|
||||
+ PUBLIC_HEADER "${PUBLIC_HEADERS}")
|
||||
+
|
||||
+
|
||||
+target_include_directories(aom
|
||||
+ PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
|
||||
+ $<INSTALL_INTERFACE:include>)
|
||||
+
|
||||
+install(TARGETS aom
|
||||
+ EXPORT unofficial-aom-targets
|
||||
+ ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
|
||||
+ LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
|
||||
+ RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
|
||||
+ PUBLIC_HEADER DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/aom")
|
||||
+
|
||||
+install(EXPORT unofficial-aom-targets
|
||||
+ FILE unofficial-aom-targets.cmake
|
||||
+ NAMESPACE unofficial::
|
||||
+ DESTINATION lib/cmake/aom)
|
||||
+
|
||||
+configure_package_config_file(cmake/aom-config.cmake.in
|
||||
+ ${CMAKE_CURRENT_BINARY_DIR}/aom-config.cmake
|
||||
+ INSTALL_DESTINATION lib/cmake/aom
|
||||
+ NO_SET_AND_CHECK_MACRO
|
||||
+ NO_CHECK_REQUIRED_COMPONENTS_MACRO)
|
||||
+
|
||||
+write_basic_package_version_file(${CMAKE_CURRENT_BINARY_DIR}/aom-config-version.cmake
|
||||
+ VERSION ${SO_FILE_VERSION}
|
||||
+ COMPATIBILITY SameMajorVersion)
|
||||
+
|
||||
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/aom-config.cmake
|
||||
+ ${CMAKE_CURRENT_BINARY_DIR}/aom-config-version.cmake
|
||||
+ DESTINATION lib/cmake/aom)
|
||||
+
|
||||
# Setup dependencies.
|
||||
if(CONFIG_THREE_PASS)
|
||||
setup_ivf_dec_targets()
|
||||
diff --git a/cmake/aom-config.cmake.in b/cmake/aom-config.cmake.in
|
||||
new file mode 100644
|
||||
index 000000000..91cac3b5b
|
||||
--- /dev/null
|
||||
+++ b/cmake/aom-config.cmake.in
|
||||
@@ -0,0 +1,2 @@
|
||||
+@PACKAGE_INIT@
|
||||
+include(${CMAKE_CURRENT_LIST_DIR}/unofficial-aom-targets.cmake)
|
||||
@@ -0,0 +1,13 @@
|
||||
diff --git a/build/cmake/aom_configure.cmake b/build/cmake/aom_configure.cmake
|
||||
index aaef2c310..5500ad4a3 100644
|
||||
--- a/build/cmake/aom_configure.cmake
|
||||
+++ b/build/cmake/aom_configure.cmake
|
||||
@@ -309,6 +309,8 @@ if(MSVC)
|
||||
|
||||
# Disable MSVC warnings that suggest making code non-portable.
|
||||
add_compiler_flag_if_supported("/wd4996")
|
||||
+ # Disable MSVC warnings for potentially uninitialized local pointer variable.
|
||||
+ add_compiler_flag_if_supported("/wd4703")
|
||||
if(ENABLE_WERROR)
|
||||
add_compiler_flag_if_supported("/WX")
|
||||
endif()
|
||||
@@ -0,0 +1,79 @@
|
||||
# NASM is required to build AOM
|
||||
vcpkg_find_acquire_program(NASM)
|
||||
get_filename_component(NASM_EXE_PATH ${NASM} DIRECTORY)
|
||||
vcpkg_add_to_path(${NASM_EXE_PATH})
|
||||
|
||||
# Perl is required to build AOM
|
||||
vcpkg_find_acquire_program(PERL)
|
||||
get_filename_component(PERL_PATH ${PERL} DIRECTORY)
|
||||
vcpkg_add_to_path(${PERL_PATH})
|
||||
|
||||
if(DEFINED ENV{USE_AOM_391})
|
||||
vcpkg_from_git(
|
||||
OUT_SOURCE_PATH SOURCE_PATH
|
||||
URL "https://aomedia.googlesource.com/aom"
|
||||
REF 8ad484f8a18ed1853c094e7d3a4e023b2a92df28 # 3.9.1
|
||||
PATCHES
|
||||
aom-uninitialized-pointer.diff
|
||||
aom-avx2.diff
|
||||
aom-install.diff
|
||||
)
|
||||
else()
|
||||
vcpkg_from_git(
|
||||
OUT_SOURCE_PATH SOURCE_PATH
|
||||
URL "https://aomedia.googlesource.com/aom"
|
||||
REF 10aece4157eb79315da205f39e19bf6ab3ee30d0 # 3.12.1
|
||||
PATCHES
|
||||
aom-uninitialized-pointer.diff
|
||||
# aom-avx2.diff
|
||||
# Can be dropped when https://bugs.chromium.org/p/aomedia/issues/detail?id=3029 is merged into the upstream
|
||||
aom-install.diff
|
||||
)
|
||||
endif()
|
||||
|
||||
set(aom_target_cpu "")
|
||||
if(VCPKG_TARGET_IS_UWP OR (VCPKG_TARGET_IS_WINDOWS AND VCPKG_TARGET_ARCHITECTURE MATCHES "^arm"))
|
||||
# UWP + aom's assembler files result in weirdness and build failures
|
||||
# Also, disable assembly on ARM and ARM64 Windows to fix compilation issues.
|
||||
set(aom_target_cpu "-DAOM_TARGET_CPU=generic")
|
||||
endif()
|
||||
|
||||
if(VCPKG_TARGET_ARCHITECTURE STREQUAL "arm" AND VCPKG_TARGET_IS_LINUX)
|
||||
set(aom_target_cpu "-DENABLE_NEON=OFF")
|
||||
endif()
|
||||
|
||||
vcpkg_cmake_configure(
|
||||
SOURCE_PATH ${SOURCE_PATH}
|
||||
OPTIONS
|
||||
${aom_target_cpu}
|
||||
-DENABLE_DOCS=OFF
|
||||
-DENABLE_EXAMPLES=OFF
|
||||
-DENABLE_TESTDATA=OFF
|
||||
-DENABLE_TESTS=OFF
|
||||
-DENABLE_TOOLS=OFF
|
||||
)
|
||||
|
||||
vcpkg_cmake_install()
|
||||
|
||||
vcpkg_copy_pdbs()
|
||||
|
||||
vcpkg_fixup_pkgconfig()
|
||||
|
||||
if(VCPKG_TARGET_IS_WINDOWS)
|
||||
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/aom.pc" " -lm" "")
|
||||
if(NOT VCPKG_BUILD_TYPE)
|
||||
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/aom.pc" " -lm" "")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Move cmake configs
|
||||
vcpkg_cmake_config_fixup(CONFIG_PATH lib/cmake/${PORT})
|
||||
|
||||
# Remove duplicate files
|
||||
file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/include
|
||||
${CURRENT_PACKAGES_DIR}/debug/share)
|
||||
|
||||
# Handle copyright
|
||||
file(INSTALL ${SOURCE_PATH}/LICENSE DESTINATION ${CURRENT_PACKAGES_DIR}/share/${PORT} RENAME copyright)
|
||||
|
||||
vcpkg_fixup_pkgconfig()
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "aom",
|
||||
"version-semver": "3.12.1",
|
||||
"port-version": 0,
|
||||
"description": "AV1 codec library",
|
||||
"homepage": "https://aomedia.googlesource.com/aom",
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": [
|
||||
{
|
||||
"name": "vcpkg-cmake",
|
||||
"host": true
|
||||
},
|
||||
{
|
||||
"name": "vcpkg-cmake-config",
|
||||
"host": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
diff --git a/configure b/configure
|
||||
index 1f0b9497cb..3243e23021 100644
|
||||
--- a/configure
|
||||
+++ b/configure
|
||||
@@ -5697,17 +5697,19 @@ case $target_os in
|
||||
;;
|
||||
win32|win64)
|
||||
disable symver
|
||||
- if enabled shared; then
|
||||
+# if enabled shared; then
|
||||
# Link to the import library instead of the normal static library
|
||||
# for shared libs.
|
||||
LD_LIB='%.lib'
|
||||
# Cannot build both shared and static libs with MSVC or icl.
|
||||
- disable static
|
||||
- fi
|
||||
+# disable static
|
||||
+# fi
|
||||
! enabled small && test_cmd $windres --version && enable gnu_windres
|
||||
enabled x86_32 && check_ldflags -LARGEADDRESSAWARE
|
||||
add_cppflags -DWIN32_LEAN_AND_MEAN
|
||||
shlibdir_default="$bindir_default"
|
||||
+ LIBPREF=""
|
||||
+ LIBSUF=".lib"
|
||||
SLIBPREF=""
|
||||
SLIBSUF=".dll"
|
||||
SLIBNAME_WITH_VERSION='$(SLIBPREF)$(FULLNAME)-$(LIBVERSION)$(SLIBSUF)'
|
||||
@@ -0,0 +1,11 @@
|
||||
diff --git a/configure b/configure
|
||||
--- a/configure
|
||||
+++ b/configure
|
||||
@@ -6162,6 +6162,7 @@ EOF
|
||||
test -n "$extern_prefix" && append X86ASMFLAGS "-DPREFIX"
|
||||
case "$objformat" in
|
||||
elf*) enabled debug && append X86ASMFLAGS $x86asm_debug ;;
|
||||
+ win*) enabled debug && append X86ASMFLAGS "-g" ;;
|
||||
esac
|
||||
|
||||
enabled avx512 && check_x86asm avx512_external "vmovdqa32 [eax]{k1}{z}, zmm0"
|
||||
@@ -0,0 +1,13 @@
|
||||
diff --git a/fftools/cmdutils.c b/fftools/cmdutils.c
|
||||
--- a/fftools/cmdutils.c
|
||||
+++ b/fftools/cmdutils.c
|
||||
@@ -51,6 +51,8 @@
|
||||
#include "fopen_utf8.h"
|
||||
#include "opt_common.h"
|
||||
#ifdef _WIN32
|
||||
+#define _WIN32_WINNT 0x0502
|
||||
+#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include "compat/w32dlfcn.h"
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
diff --git a/configure b/configure
|
||||
index a8b74e0..c99f41c 100755
|
||||
--- a/configure
|
||||
+++ b/configure
|
||||
@@ -6633,7 +6633,7 @@ fi
|
||||
|
||||
enabled zlib && { check_pkg_config zlib zlib "zlib.h" zlibVersion ||
|
||||
check_lib zlib zlib.h zlibVersion -lz; }
|
||||
-enabled bzlib && check_lib bzlib bzlib.h BZ2_bzlibVersion -lbz2
|
||||
+enabled bzlib && require_pkg_config bzlib bzip2 bzlib.h BZ2_bzlibVersion
|
||||
enabled lzma && check_lib lzma lzma.h lzma_version_number -llzma
|
||||
|
||||
enabled zlib && test_exec $zlib_extralibs <<EOF && enable zlib_gzip
|
||||
@@ -6757,7 +6757,8 @@ if enabled libmfx; then
|
||||
fi
|
||||
|
||||
enabled libmodplug && require_pkg_config libmodplug libmodplug libmodplug/modplug.h ModPlug_Load
|
||||
-enabled libmp3lame && require "libmp3lame >= 3.98.3" lame/lame.h lame_set_VBR_quality -lmp3lame $libm_extralibs
|
||||
+enabled libmp3lame && { check_lib libmp3lame lame/lame.h lame_set_VBR_quality -lmp3lame $libm_extralibs ||
|
||||
+ require libmp3lame lame/lame.h lame_set_VBR_quality -llibmp3lame-static -llibmpghip-static $libm_extralibs; }
|
||||
enabled libmysofa && { check_pkg_config libmysofa libmysofa mysofa.h mysofa_neighborhood_init_withstepdefine ||
|
||||
require libmysofa mysofa.h mysofa_neighborhood_init_withstepdefine -lmysofa $zlib_extralibs; }
|
||||
enabled libnpp && { check_lib libnpp npp.h nppGetLibVersion -lnppig -lnppicc -lnppc -lnppidei -lnppif ||
|
||||
@@ -6772,7 +6773,7 @@ require_pkg_config libopencv opencv opencv/cxcore.h cvCreateImageHeader; }
|
||||
enabled libopenh264 && require_pkg_config libopenh264 "openh264 >= 1.3.0" wels/codec_api.h WelsGetCodecVersion
|
||||
enabled libopenjpeg && { check_pkg_config libopenjpeg "libopenjp2 >= 2.1.0" openjpeg.h opj_version ||
|
||||
{ require_pkg_config libopenjpeg "libopenjp2 >= 2.1.0" openjpeg.h opj_version -DOPJ_STATIC && add_cppflags -DOPJ_STATIC; } }
|
||||
-enabled libopenmpt && require_pkg_config libopenmpt "libopenmpt >= 0.2.6557" libopenmpt/libopenmpt.h openmpt_module_create -lstdc++ && append libopenmpt_extralibs "-lstdc++"
|
||||
+enabled libopenmpt && require_pkg_config libopenmpt "libopenmpt >= 0.2.6557" libopenmpt/libopenmpt.h openmpt_module_create
|
||||
enabled libopenvino && { { check_pkg_config libopenvino openvino openvino/c/openvino.h ov_core_create && enable openvino2; } ||
|
||||
{ check_pkg_config libopenvino openvino c_api/ie_c_api.h ie_c_api_version ||
|
||||
require libopenvino c_api/ie_c_api.h ie_c_api_version -linference_engine_c_api; } }
|
||||
@@ -6796,8 +6797,8 @@ enabled libshaderc && require_pkg_config spirv_compiler "shaderc >= 2019.
|
||||
enabled libshine && require_pkg_config libshine shine shine/layer3.h shine_encode_buffer
|
||||
enabled libsmbclient && { check_pkg_config libsmbclient smbclient libsmbclient.h smbc_init ||
|
||||
require libsmbclient libsmbclient.h smbc_init -lsmbclient; }
|
||||
-enabled libsnappy && require libsnappy snappy-c.h snappy_compress -lsnappy -lstdc++
|
||||
-enabled libsoxr && require libsoxr soxr.h soxr_create -lsoxr
|
||||
+enabled libsnappy && require_pkg_config libsnappy snappy snappy-c.h snappy_compress
|
||||
+enabled libsoxr && require libsoxr soxr.h soxr_create -lsoxr $libm_extralibs
|
||||
enabled libssh && require_pkg_config libssh "libssh >= 0.6.0" libssh/sftp.h sftp_init
|
||||
enabled libspeex && require_pkg_config libspeex speex speex/speex.h speex_decoder_init
|
||||
enabled libsrt && require_pkg_config libsrt "srt >= 1.3.0" srt/srt.h srt_socket
|
||||
@@ -6880,6 +6881,8 @@ enabled openal && { check_pkg_config openal "openal >= 1.1" "AL/al.h"
|
||||
enabled opencl && { check_pkg_config opencl OpenCL CL/cl.h clEnqueueNDRangeKernel ||
|
||||
check_lib opencl OpenCL/cl.h clEnqueueNDRangeKernel "-framework OpenCL" ||
|
||||
check_lib opencl CL/cl.h clEnqueueNDRangeKernel -lOpenCL ||
|
||||
+ check_lib opencl CL/cl.h clEnqueueNDRangeKernel -lOpenCL -lAdvapi32 -lOle32 -lCfgmgr32||
|
||||
+ check_lib opencl CL/cl.h clEnqueueNDRangeKernel -lOpenCL -pthread -ldl ||
|
||||
die "ERROR: opencl not found"; } &&
|
||||
{ test_cpp_condition "OpenCL/cl.h" "defined(CL_VERSION_1_2)" ||
|
||||
test_cpp_condition "CL/cl.h" "defined(CL_VERSION_1_2)" ||
|
||||
@@ -7204,10 +7207,10 @@ enabled amf &&
|
||||
"(AMF_VERSION_MAJOR << 48 | AMF_VERSION_MINOR << 32 | AMF_VERSION_RELEASE << 16 | AMF_VERSION_BUILD_NUM) >= 0x0001000400210000"
|
||||
|
||||
# Funny iconv installations are not unusual, so check it after all flags have been set
|
||||
-if enabled libc_iconv; then
|
||||
+if enabled libc_iconv && disabled iconv; then
|
||||
check_func_headers iconv.h iconv
|
||||
elif enabled iconv; then
|
||||
- check_func_headers iconv.h iconv || check_lib iconv iconv.h iconv -liconv
|
||||
+ check_func_headers iconv.h iconv || check_lib iconv iconv.h iconv -liconv || check_lib iconv iconv.h iconv -liconv -lcharset
|
||||
fi
|
||||
|
||||
enabled debug && add_cflags -g"$debuglevel" && add_asflags -g"$debuglevel"
|
||||
@@ -0,0 +1,78 @@
|
||||
diff --git a/libavcodec/x86/mlpdsp.asm b/libavcodec/x86/mlpdsp.asm
|
||||
index 3dc641e..609b834 100644
|
||||
--- a/libavcodec/x86/mlpdsp.asm
|
||||
+++ b/libavcodec/x86/mlpdsp.asm
|
||||
@@ -23,7 +23,9 @@
|
||||
|
||||
SECTION .text
|
||||
|
||||
-%if ARCH_X86_64
|
||||
+%ifn ARCH_X86_64
|
||||
+mlpdsp_placeholder: times 4 db 0
|
||||
+%else
|
||||
|
||||
%macro SHLX 2
|
||||
%if cpuflag(bmi2)
|
||||
diff --git a/libavcodec/x86/proresdsp.asm b/libavcodec/x86/proresdsp.asm
|
||||
index 65c9fad..5ad73f3 100644
|
||||
--- a/libavcodec/x86/proresdsp.asm
|
||||
+++ b/libavcodec/x86/proresdsp.asm
|
||||
@@ -24,7 +24,10 @@
|
||||
|
||||
%include "libavutil/x86/x86util.asm"
|
||||
|
||||
-%if ARCH_X86_64
|
||||
+%ifn ARCH_X86_64
|
||||
+SECTION .rdata
|
||||
+proresdsp_placeholder: times 4 db 0
|
||||
+%else
|
||||
|
||||
SECTION_RODATA
|
||||
|
||||
diff --git a/libavcodec/x86/vvc/vvc_mc.asm b/libavcodec/x86/vvc/vvc_mc.asm
|
||||
index 30aa97c..3975f98 100644
|
||||
--- a/libavcodec/x86/vvc/vvc_mc.asm
|
||||
+++ b/libavcodec/x86/vvc/vvc_mc.asm
|
||||
@@ -31,7 +31,9 @@
|
||||
|
||||
SECTION_RODATA 32
|
||||
|
||||
-%if ARCH_X86_64
|
||||
+%ifn ARCH_X86_64
|
||||
+vvc_mc_placeholder: times 4 db 0
|
||||
+%else
|
||||
|
||||
%if HAVE_AVX2_EXTERNAL
|
||||
|
||||
diff --git a/libavfilter/x86/vf_atadenoise.asm b/libavfilter/x86/vf_atadenoise.asm
|
||||
index 4945ad3..748b65a 100644
|
||||
--- a/libavfilter/x86/vf_atadenoise.asm
|
||||
+++ b/libavfilter/x86/vf_atadenoise.asm
|
||||
@@ -20,7 +20,10 @@
|
||||
;* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
;******************************************************************************
|
||||
|
||||
-%if ARCH_X86_64
|
||||
+%ifn ARCH_X86_64
|
||||
+SECTION .rdata
|
||||
+vf_atadenoise_placeholder: times 4 db 0
|
||||
+%else
|
||||
|
||||
%include "libavutil/x86/x86util.asm"
|
||||
|
||||
diff --git a/libavfilter/x86/vf_nlmeans.asm b/libavfilter/x86/vf_nlmeans.asm
|
||||
index 8f57801..9aef3a4 100644
|
||||
--- a/libavfilter/x86/vf_nlmeans.asm
|
||||
+++ b/libavfilter/x86/vf_nlmeans.asm
|
||||
@@ -21,7 +21,10 @@
|
||||
|
||||
%include "libavutil/x86/x86util.asm"
|
||||
|
||||
-%if HAVE_AVX2_EXTERNAL && ARCH_X86_64
|
||||
+%ifn HAVE_AVX2_EXTERNAL && ARCH_X86_64
|
||||
+SECTION .rdata
|
||||
+vf_nlmeans_placeholder: times 4 db 0
|
||||
+%else
|
||||
|
||||
SECTION_RODATA 32
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
diff --git a/configure b/configure
|
||||
index d6c4388..75b96c3 100644
|
||||
--- a/configure
|
||||
+++ b/configure
|
||||
@@ -4781,6 +4781,7 @@ msvc_common_flags(){
|
||||
-mfp16-format=*) ;;
|
||||
-lz) echo zlib.lib ;;
|
||||
-lx264) echo libx264.lib ;;
|
||||
+ -lmp3lame) echo libmp3lame.lib ;;
|
||||
-lstdc++) ;;
|
||||
-l*) echo ${flag#-l}.lib ;;
|
||||
-LARGEADDRESSAWARE) echo $flag ;;
|
||||
@@ -0,0 +1,15 @@
|
||||
diff --color -Naur src_old/libavcodec/mf_utils.c src/libavcodec/mf_utils.c
|
||||
--- src_old/libavcodec/mf_utils.c 2020-07-11 05:26:17.000000000 +0700
|
||||
+++ src/libavcodec/mf_utils.c 2020-11-13 12:55:57.226976400 +0700
|
||||
@@ -22,6 +22,11 @@
|
||||
#define _WIN32_WINNT 0x0602
|
||||
#endif
|
||||
|
||||
+#if !defined(WINVER) || WINVER < 0x0602
|
||||
+#undef WINVER
|
||||
+#define WINVER 0x0602
|
||||
+#endif
|
||||
+
|
||||
#include "mf_utils.h"
|
||||
#include "libavutil/pixdesc.h"
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
diff --git a/libswscale/aarch64/yuv2rgb_neon.S b/libswscale/aarch64/yuv2rgb_neon.S
|
||||
index 89d69e7f6c..4bc1607a7a 100644
|
||||
--- a/libswscale/aarch64/yuv2rgb_neon.S
|
||||
+++ b/libswscale/aarch64/yuv2rgb_neon.S
|
||||
@@ -169,19 +169,19 @@ function ff_\ifmt\()_to_\ofmt\()_neon, export=1
|
||||
sqdmulh v26.8h, v26.8h, v0.8h // ((Y1*(1<<3) - y_offset) * y_coeff) >> 15
|
||||
sqdmulh v27.8h, v27.8h, v0.8h // ((Y2*(1<<3) - y_offset) * y_coeff) >> 15
|
||||
|
||||
-.ifc \ofmt,argb // 1 2 3 0
|
||||
+.ifc \ofmt,argb
|
||||
compute_rgba v5.8b,v6.8b,v7.8b,v4.8b, v17.8b,v18.8b,v19.8b,v16.8b
|
||||
.endif
|
||||
|
||||
-.ifc \ofmt,rgba // 0 1 2 3
|
||||
+.ifc \ofmt,rgba
|
||||
compute_rgba v4.8b,v5.8b,v6.8b,v7.8b, v16.8b,v17.8b,v18.8b,v19.8b
|
||||
.endif
|
||||
|
||||
-.ifc \ofmt,abgr // 3 2 1 0
|
||||
+.ifc \ofmt,abgr
|
||||
compute_rgba v7.8b,v6.8b,v5.8b,v4.8b, v19.8b,v18.8b,v17.8b,v16.8b
|
||||
.endif
|
||||
|
||||
-.ifc \ofmt,bgra // 2 1 0 3
|
||||
+.ifc \ofmt,bgra
|
||||
compute_rgba v6.8b,v5.8b,v4.8b,v7.8b, v18.8b,v17.8b,v16.8b,v19.8b
|
||||
.endif
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
diff --git a/configure b/configure
|
||||
index 4f5353f84b..dd9147c677 100755
|
||||
--- a/configure
|
||||
+++ b/configure
|
||||
@@ -5607,8 +5607,8 @@ check_cppflags -D_FILE_OFFSET_BITS=64
|
||||
check_cppflags -D_LARGEFILE_SOURCE
|
||||
|
||||
add_host_cppflags -D_ISOC11_SOURCE
|
||||
check_host_cflags_cc -std=$stdc ctype.h "__STDC_VERSION__ >= 201112L" ||
|
||||
- check_host_cflags_cc -std=c11 ctype.h "__STDC_VERSION__ >= 201112L" || die "Host compiler lacks C11 support"
|
||||
+ check_host_cflags_cc -std=c11 ctype.h "__STDC_VERSION__ >= 201112L"
|
||||
|
||||
check_host_cflags -Wall
|
||||
check_host_cflags $host_cflags_speed
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
diff --git a/libavformat/avformat.h b/libavformat/avformat.h
|
||||
index cd7b0d941c..b4a6dce885 100644
|
||||
--- a/libavformat/avformat.h
|
||||
+++ b/libavformat/avformat.h
|
||||
@@ -1169,7 +1169,11 @@ typedef struct AVStreamGroup {
|
||||
} AVStreamGroup;
|
||||
|
||||
struct AVCodecParserContext *av_stream_get_parser(const AVStream *s);
|
||||
|
||||
+// Chromium: We use the internal field first_dts vvv
|
||||
+int64_t av_stream_get_first_dts(const AVStream *st);
|
||||
+// Chromium: We use the internal field first_dts ^^^
|
||||
+
|
||||
#define AV_PROGRAM_RUNNING 1
|
||||
|
||||
/**
|
||||
diff --git a/libavformat/mux_utils.c b/libavformat/mux_utils.c
|
||||
index de7580c32d..0ef0fe530e 100644
|
||||
--- a/libavformat/mux_utils.c
|
||||
+++ b/libavformat/mux_utils.c
|
||||
@@ -29,7 +29,14 @@ #include "avformat.h"
|
||||
#include "avio.h"
|
||||
#include "internal.h"
|
||||
#include "mux.h"
|
||||
|
||||
+// Chromium: We use the internal field first_dts vvv
|
||||
+int64_t av_stream_get_first_dts(const AVStream *st)
|
||||
+{
|
||||
+ return cffstream(st)->first_dts;
|
||||
+}
|
||||
+// Chromium: We use the internal field first_dts ^^^
|
||||
+
|
||||
int avformat_query_codec(const AVOutputFormat *ofmt, enum AVCodecID codec_id,
|
||||
int std_compliance)
|
||||
{
|
||||
@@ -0,0 +1,13 @@
|
||||
diff --git a/libavdevice/opengl_enc.c b/libavdevice/opengl_enc.c
|
||||
index b2ac6eb..6351614 100644
|
||||
--- a/libavdevice/opengl_enc.c
|
||||
+++ b/libavdevice/opengl_enc.c
|
||||
@@ -116,7 +116,7 @@ typedef void (APIENTRY *FF_PFNGLATTACHSHADERPROC) (GLuint program, GLuint shad
|
||||
typedef GLuint (APIENTRY *FF_PFNGLCREATESHADERPROC) (GLenum type);
|
||||
typedef void (APIENTRY *FF_PFNGLDELETESHADERPROC) (GLuint shader);
|
||||
typedef void (APIENTRY *FF_PFNGLCOMPILESHADERPROC) (GLuint shader);
|
||||
-typedef void (APIENTRY *FF_PFNGLSHADERSOURCEPROC) (GLuint shader, GLsizei count, const char* *string, const GLint *length);
|
||||
+typedef void (APIENTRY *FF_PFNGLSHADERSOURCEPROC) (GLuint shader, GLsizei count, const char* const *string, const GLint *length);
|
||||
typedef void (APIENTRY *FF_PFNGLGETSHADERIVPROC) (GLuint shader, GLenum pname, GLint *params);
|
||||
typedef void (APIENTRY *FF_PFNGLGETSHADERINFOLOGPROC) (GLuint shader, GLsizei bufSize, GLsizei *length, char *infoLog);
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
diff --git a/ffbuild/libversion.sh b/ffbuild/libversion.sh
|
||||
index a94ab58..ecaa90c 100644
|
||||
--- a/ffbuild/libversion.sh
|
||||
+++ b/ffbuild/libversion.sh
|
||||
@@ -1,3 +1,4 @@
|
||||
+#!/bin/sh
|
||||
toupper(){
|
||||
echo "$@" | tr abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
diff --git a/libavfilter/textutils.c b/libavfilter/textutils.c
|
||||
index ef658d0..c61b0ad 100644
|
||||
--- a/libavfilter/textutils.c
|
||||
+++ b/libavfilter/textutils.c
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "libavutil/file.h"
|
||||
#include "libavutil/mem.h"
|
||||
#include "libavutil/time.h"
|
||||
+#include "libavutil/time_internal.h"
|
||||
|
||||
static int ff_expand_text_function_internal(FFExpandTextContext *expand_text, AVBPrint *bp,
|
||||
char *name, unsigned argc, char **argv)
|
||||
@@ -0,0 +1,152 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
export PATH="$PATH:/usr/bin"
|
||||
|
||||
command -v cygpath >/dev/null && have_cygpath=1
|
||||
|
||||
cygpath() {
|
||||
if [ -n "$have_cygpath" ]; then
|
||||
command cygpath "$@"
|
||||
else
|
||||
eval _p='$'$#
|
||||
printf '%s\n' "$_p"
|
||||
fi
|
||||
}
|
||||
|
||||
move_binary() {
|
||||
SOURCE=$1
|
||||
TARGET=$2
|
||||
BINARY=$3
|
||||
|
||||
# run lipo over the command to check whether it really
|
||||
# is a binary that we need to merge architectures
|
||||
lipo $SOURCE/$BINARY -info &> /dev/null || return 0
|
||||
|
||||
# get the directory name the file is in
|
||||
DIRNAME=$(dirname $BINARY)
|
||||
|
||||
# ensure the directory to move the binary to exists
|
||||
mkdir -p $TARGET/$DIRNAME
|
||||
|
||||
# now finally move the binary
|
||||
mv $SOURCE/$BINARY $TARGET/$BINARY
|
||||
}
|
||||
|
||||
move_binaries() {
|
||||
SOURCE=$1
|
||||
TARGET=$2
|
||||
|
||||
[ ! -d $SOURCE ] && return 0
|
||||
pushd $SOURCE
|
||||
|
||||
for BINARY in $(find . -type f); do
|
||||
move_binary $SOURCE $TARGET $BINARY
|
||||
done
|
||||
|
||||
popd
|
||||
}
|
||||
|
||||
merge_binaries() {
|
||||
TARGET=$1
|
||||
SOURCE=$2
|
||||
|
||||
shift
|
||||
shift
|
||||
|
||||
pushd $SOURCE/$1
|
||||
BINARIES=$(find . -type f)
|
||||
popd
|
||||
|
||||
for BINARY in $BINARIES; do
|
||||
COMMAND="lipo -create -output $TARGET/$BINARY"
|
||||
|
||||
for ARCH in $@; do
|
||||
COMMAND="$COMMAND -arch $ARCH $SOURCE/$ARCH/$BINARY"
|
||||
done
|
||||
|
||||
$($COMMAND)
|
||||
done
|
||||
}
|
||||
|
||||
export PKG_CONFIG_PATH="$(cygpath -p "${PKG_CONFIG_PATH}")"
|
||||
|
||||
# Export HTTP(S)_PROXY as http(s)_proxy:
|
||||
[ -n "$HTTP_PROXY" ] && export http_proxy="$HTTP_PROXY"
|
||||
[ -n "$HTTPS_PROXY" ] && export https_proxy="$HTTPS_PROXY"
|
||||
|
||||
PATH_TO_BUILD_DIR=$( cygpath "@BUILD_DIR@")
|
||||
PATH_TO_SRC_DIR=$( cygpath "@SOURCE_PATH@")
|
||||
PATH_TO_PACKAGE_DIR=$(cygpath "@INST_PREFIX@")
|
||||
|
||||
JOBS=@VCPKG_CONCURRENCY@
|
||||
|
||||
OSX_ARCHS="@OSX_ARCHS@"
|
||||
OSX_ARCH_COUNT=0@OSX_ARCH_COUNT@
|
||||
|
||||
# Default to hardware concurrency if unset.
|
||||
: ${JOBS:=$(nproc)}
|
||||
|
||||
# Disable asm and x86asm on all android targets because they trigger build failures:
|
||||
# arm64 Android build fails with 'relocation R_AARCH64_ADR_PREL_PG_HI21 cannot be used against symbol ff_cos_32; recompile with -fPIC'
|
||||
# x86 Android build fails with 'error: inline assembly requires more registers than available'.
|
||||
# x64 Android build fails with 'relocation R_X86_64_PC32 cannot be used against symbol ff_h264_cabac_tables; recompile with -fPIC'
|
||||
if [ "@VCPKG_CMAKE_SYSTEM_NAME@" = "Android" ]; then
|
||||
OPTIONS_arm=" --disable-asm --disable-x86asm"
|
||||
OPTIONS_arm64=" --disable-asm --disable-x86asm"
|
||||
OPTIONS_x86=" --disable-asm --disable-x86asm"
|
||||
OPTIONS_x86_64="${OPTIONS_x86}"
|
||||
else
|
||||
OPTIONS_arm=" --disable-asm --disable-x86asm"
|
||||
OPTIONS_arm64=" --enable-asm --disable-x86asm"
|
||||
OPTIONS_x86=" --enable-asm --enable-x86asm"
|
||||
OPTIONS_x86_64="${OPTIONS_x86}"
|
||||
fi
|
||||
|
||||
build_ffmpeg() {
|
||||
# extract build architecture
|
||||
BUILD_ARCH=$1
|
||||
shift
|
||||
|
||||
echo "BUILD_ARCH=${BUILD_ARCH}"
|
||||
|
||||
# get architecture-specific options
|
||||
OPTION_VARIABLE="OPTIONS_${BUILD_ARCH}"
|
||||
echo "OPTION_VARIABLE=${OPTION_VARIABLE}"
|
||||
|
||||
echo "=== CONFIGURING ==="
|
||||
|
||||
sh "$PATH_TO_SRC_DIR/configure" "--prefix=$PATH_TO_PACKAGE_DIR" @CONFIGURE_OPTIONS@ --arch=${BUILD_ARCH} ${!OPTION_VARIABLE} $@
|
||||
|
||||
echo "=== BUILDING ==="
|
||||
|
||||
make -j${JOBS} V=1
|
||||
|
||||
echo "=== INSTALLING ==="
|
||||
|
||||
make install
|
||||
}
|
||||
|
||||
cd "$PATH_TO_BUILD_DIR"
|
||||
|
||||
if [ $OSX_ARCH_COUNT -gt 0 ]; then
|
||||
for ARCH in $OSX_ARCHS; do
|
||||
echo "=== CLEANING FOR $ARCH ==="
|
||||
|
||||
make clean && make distclean
|
||||
|
||||
build_ffmpeg $ARCH --extra-cflags=-arch --extra-cflags=$ARCH --extra-ldflags=-arch --extra-ldflags=$ARCH
|
||||
|
||||
echo "=== COLLECTING BINARIES FOR $ARCH ==="
|
||||
|
||||
move_binaries $PATH_TO_PACKAGE_DIR/lib $PATH_TO_BUILD_DIR/stage/$ARCH/lib
|
||||
move_binaries $PATH_TO_PACKAGE_DIR/bin $PATH_TO_BUILD_DIR/stage/$ARCH/bin
|
||||
done
|
||||
|
||||
echo "=== MERGING ARCHITECTURES ==="
|
||||
|
||||
merge_binaries $PATH_TO_PACKAGE_DIR $PATH_TO_BUILD_DIR/stage $OSX_ARCHS
|
||||
else
|
||||
build_ffmpeg @BUILD_ARCH@
|
||||
fi
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
From da6921d5bcb50961193526f47aa2dbe71ee5fe81 Mon Sep 17 00:00:00 2001
|
||||
From: 21pages <sunboeasy@gmail.com>
|
||||
Date: Tue, 10 Dec 2024 13:40:46 +0800
|
||||
Subject: [PATCH 1/5] avcodec/amfenc: add query_timeout option for h264/hevc
|
||||
|
||||
Signed-off-by: 21pages <sunboeasy@gmail.com>
|
||||
---
|
||||
libavcodec/amfenc.h | 1 +
|
||||
libavcodec/amfenc_h264.c | 4 ++++
|
||||
libavcodec/amfenc_hevc.c | 4 ++++
|
||||
3 files changed, 9 insertions(+)
|
||||
|
||||
diff --git a/libavcodec/amfenc.h b/libavcodec/amfenc.h
|
||||
index d985d01bb1..320c66919e 100644
|
||||
--- a/libavcodec/amfenc.h
|
||||
+++ b/libavcodec/amfenc.h
|
||||
@@ -91,6 +91,7 @@ typedef struct AmfContext {
|
||||
int quality;
|
||||
int b_frame_delta_qp;
|
||||
int ref_b_frame_delta_qp;
|
||||
+ int64_t query_timeout;
|
||||
|
||||
// Dynamic options, can be set after Init() call
|
||||
|
||||
diff --git a/libavcodec/amfenc_h264.c b/libavcodec/amfenc_h264.c
|
||||
index 8edd39c633..6ad4961b2f 100644
|
||||
--- a/libavcodec/amfenc_h264.c
|
||||
+++ b/libavcodec/amfenc_h264.c
|
||||
@@ -137,6 +137,7 @@ static const AVOption options[] = {
|
||||
|
||||
|
||||
{ "log_to_dbg", "Enable AMF logging to debug output", OFFSET(log_to_dbg) , AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, VE },
|
||||
+ { "query_timeout", "Timeout for QueryOutput call in ms", OFFSET(query_timeout), AV_OPT_TYPE_INT64, { .i64 = -1 }, -1, 1000, VE },
|
||||
|
||||
//Pre Analysis options
|
||||
{ "preanalysis", "Enable preanalysis", OFFSET(preanalysis), AV_OPT_TYPE_BOOL, {.i64 = -1 }, -1, 1, VE },
|
||||
@@ -228,6 +229,9 @@ FF_ENABLE_DEPRECATION_WARNINGS
|
||||
|
||||
AMF_ASSIGN_PROPERTY_RATE(res, ctx->encoder, AMF_VIDEO_ENCODER_FRAMERATE, framerate);
|
||||
|
||||
+ if (ctx->query_timeout >= 0)
|
||||
+ AMF_ASSIGN_PROPERTY_INT64(res, ctx->encoder, AMF_VIDEO_ENCODER_QUERY_TIMEOUT, ctx->query_timeout);
|
||||
+
|
||||
switch (avctx->profile) {
|
||||
case AV_PROFILE_H264_BASELINE:
|
||||
profile = AMF_VIDEO_ENCODER_PROFILE_BASELINE;
|
||||
diff --git a/libavcodec/amfenc_hevc.c b/libavcodec/amfenc_hevc.c
|
||||
index 4898824f3a..22cb95c7ce 100644
|
||||
--- a/libavcodec/amfenc_hevc.c
|
||||
+++ b/libavcodec/amfenc_hevc.c
|
||||
@@ -104,6 +104,7 @@ static const AVOption options[] = {
|
||||
|
||||
|
||||
{ "log_to_dbg", "Enable AMF logging to debug output", OFFSET(log_to_dbg), AV_OPT_TYPE_BOOL,{ .i64 = 0 }, 0, 1, VE },
|
||||
+ { "query_timeout", "Timeout for QueryOutput call in ms", OFFSET(query_timeout), AV_OPT_TYPE_INT64, { .i64 = -1 }, -1, 1000, VE },
|
||||
|
||||
//Pre Analysis options
|
||||
{ "preanalysis", "Enable preanalysis", OFFSET(preanalysis), AV_OPT_TYPE_BOOL, {.i64 = -1 }, -1, 1, VE },
|
||||
@@ -194,6 +195,9 @@ FF_ENABLE_DEPRECATION_WARNINGS
|
||||
|
||||
AMF_ASSIGN_PROPERTY_RATE(res, ctx->encoder, AMF_VIDEO_ENCODER_HEVC_FRAMERATE, framerate);
|
||||
|
||||
+ if (ctx->query_timeout >= 0)
|
||||
+ AMF_ASSIGN_PROPERTY_INT64(res, ctx->encoder, AMF_VIDEO_ENCODER_HEVC_QUERY_TIMEOUT, ctx->query_timeout);
|
||||
+
|
||||
switch (avctx->profile) {
|
||||
case AV_PROFILE_HEVC_MAIN:
|
||||
profile = AMF_VIDEO_ENCODER_HEVC_PROFILE_MAIN;
|
||||
--
|
||||
2.43.0.windows.1
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
From 8d061adb7b00fc765b8001307c025437ef1cad88 Mon Sep 17 00:00:00 2001
|
||||
From: 21pages <sunboeasy@gmail.com>
|
||||
Date: Thu, 5 Sep 2024 16:32:16 +0800
|
||||
Subject: [PATCH 2/5] libavcodec/amfenc: reconfig when bitrate change
|
||||
|
||||
Signed-off-by: 21pages <sunboeasy@gmail.com>
|
||||
---
|
||||
libavcodec/amfenc.c | 20 ++++++++++++++++++++
|
||||
libavcodec/amfenc.h | 1 +
|
||||
2 files changed, 21 insertions(+)
|
||||
|
||||
diff --git a/libavcodec/amfenc.c b/libavcodec/amfenc.c
|
||||
index a47aea6108..f70f0109f6 100644
|
||||
--- a/libavcodec/amfenc.c
|
||||
+++ b/libavcodec/amfenc.c
|
||||
@@ -275,6 +275,7 @@ static int amf_init_context(AVCodecContext *avctx)
|
||||
|
||||
ctx->hwsurfaces_in_queue = 0;
|
||||
ctx->hwsurfaces_in_queue_max = 16;
|
||||
+ ctx->av_bitrate = avctx->bit_rate;
|
||||
|
||||
// configure AMF logger
|
||||
// the return of these functions indicates old state and do not affect behaviour
|
||||
@@ -640,6 +641,23 @@ static void amf_release_buffer_with_frame_ref(AMFBuffer *frame_ref_storage_buffe
|
||||
frame_ref_storage_buffer->pVtbl->Release(frame_ref_storage_buffer);
|
||||
}
|
||||
|
||||
+static int reconfig_encoder(AVCodecContext *avctx)
|
||||
+{
|
||||
+ AmfContext *ctx = avctx->priv_data;
|
||||
+ AMF_RESULT res = AMF_OK;
|
||||
+
|
||||
+ if (ctx->av_bitrate != avctx->bit_rate) {
|
||||
+ av_log(ctx, AV_LOG_INFO, "change bitrate from %d to %d\n", ctx->av_bitrate, avctx->bit_rate);
|
||||
+ ctx->av_bitrate = avctx->bit_rate;
|
||||
+ if (avctx->codec->id == AV_CODEC_ID_H264) {
|
||||
+ AMF_ASSIGN_PROPERTY_INT64(res, ctx->encoder, AMF_VIDEO_ENCODER_TARGET_BITRATE, avctx->bit_rate);
|
||||
+ } else if (avctx->codec->id == AV_CODEC_ID_HEVC) {
|
||||
+ AMF_ASSIGN_PROPERTY_INT64(res, ctx->encoder, AMF_VIDEO_ENCODER_HEVC_TARGET_BITRATE, avctx->bit_rate);
|
||||
+ }
|
||||
+ }
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
int ff_amf_receive_packet(AVCodecContext *avctx, AVPacket *avpkt)
|
||||
{
|
||||
AmfContext *ctx = avctx->priv_data;
|
||||
@@ -653,6 +671,8 @@ int ff_amf_receive_packet(AVCodecContext *avctx, AVPacket *avpkt)
|
||||
int query_output_data_flag = 0;
|
||||
AMF_RESULT res_resubmit;
|
||||
|
||||
+ reconfig_encoder(avctx);
|
||||
+
|
||||
if (!ctx->encoder)
|
||||
return AVERROR(EINVAL);
|
||||
|
||||
diff --git a/libavcodec/amfenc.h b/libavcodec/amfenc.h
|
||||
index 320c66919e..481e0fb75d 100644
|
||||
--- a/libavcodec/amfenc.h
|
||||
+++ b/libavcodec/amfenc.h
|
||||
@@ -115,6 +115,7 @@ typedef struct AmfContext {
|
||||
int max_b_frames;
|
||||
int qvbr_quality_level;
|
||||
int hw_high_motion_quality_boost;
|
||||
+ int64_t av_bitrate;
|
||||
|
||||
// HEVC - specific options
|
||||
|
||||
--
|
||||
2.43.0.windows.1
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
From d74de94b49efcf7a0b25673ace6016938d1b9272 Mon Sep 17 00:00:00 2001
|
||||
From: 21pages <sunboeasy@gmail.com>
|
||||
Date: Tue, 10 Dec 2024 14:12:01 +0800
|
||||
Subject: [PATCH 3/5] videotoolbox changing bitrate
|
||||
|
||||
Signed-off-by: 21pages <sunboeasy@gmail.com>
|
||||
---
|
||||
libavcodec/videotoolboxenc.c | 40 ++++++++++++++++++++++++++++++++++++
|
||||
1 file changed, 40 insertions(+)
|
||||
|
||||
diff --git a/libavcodec/videotoolboxenc.c b/libavcodec/videotoolboxenc.c
|
||||
index da7b291b03..3c866177f5 100644
|
||||
--- a/libavcodec/videotoolboxenc.c
|
||||
+++ b/libavcodec/videotoolboxenc.c
|
||||
@@ -279,6 +279,8 @@ typedef struct VTEncContext {
|
||||
int max_slice_bytes;
|
||||
int power_efficient;
|
||||
int max_ref_frames;
|
||||
+
|
||||
+ int last_bit_rate;
|
||||
} VTEncContext;
|
||||
|
||||
static void vtenc_free_buf_node(BufNode *info)
|
||||
@@ -1180,6 +1182,7 @@ static int vtenc_create_encoder(AVCodecContext *avctx,
|
||||
int64_t one_second_value = 0;
|
||||
void *nums[2];
|
||||
|
||||
+ vtctx->last_bit_rate = bit_rate;
|
||||
int status = VTCompressionSessionCreate(kCFAllocatorDefault,
|
||||
avctx->width,
|
||||
avctx->height,
|
||||
@@ -2638,6 +2641,42 @@ out:
|
||||
return status;
|
||||
}
|
||||
|
||||
+static void update_config(AVCodecContext *avctx)
|
||||
+{
|
||||
+ VTEncContext *vtctx = avctx->priv_data;
|
||||
+
|
||||
+ if (avctx->codec_id != AV_CODEC_ID_PRORES) {
|
||||
+ if (avctx->bit_rate != vtctx->last_bit_rate) {
|
||||
+ av_log(avctx, AV_LOG_INFO, "Setting bit rate to %d\n", avctx->bit_rate);
|
||||
+ vtctx->last_bit_rate = avctx->bit_rate;
|
||||
+ SInt32 bit_rate = avctx->bit_rate;
|
||||
+ CFNumberRef bit_rate_num = CFNumberCreate(kCFAllocatorDefault,
|
||||
+ kCFNumberSInt32Type,
|
||||
+ &bit_rate);
|
||||
+ if (!bit_rate_num) return;
|
||||
+
|
||||
+ if (vtctx->constant_bit_rate) {
|
||||
+ int status = VTSessionSetProperty(vtctx->session,
|
||||
+ compat_keys.kVTCompressionPropertyKey_ConstantBitRate,
|
||||
+ bit_rate_num);
|
||||
+ if (status == kVTPropertyNotSupportedErr) {
|
||||
+ av_log(avctx, AV_LOG_ERROR, "Error: -constant_bit_rate true is not supported by the encoder.\n");
|
||||
+ }
|
||||
+ } else {
|
||||
+ int status = VTSessionSetProperty(vtctx->session,
|
||||
+ kVTCompressionPropertyKey_AverageBitRate,
|
||||
+ bit_rate_num);
|
||||
+ if (status) {
|
||||
+ av_log(avctx, AV_LOG_ERROR, "Error: cannot set average bit rate: %d\n", status);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ CFRelease(bit_rate_num);
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+
|
||||
static av_cold int vtenc_frame(
|
||||
AVCodecContext *avctx,
|
||||
AVPacket *pkt,
|
||||
@@ -2650,6 +2689,7 @@ static av_cold int vtenc_frame(
|
||||
CMSampleBufferRef buf = NULL;
|
||||
ExtraSEI sei = {0};
|
||||
|
||||
+ update_config(avctx);
|
||||
if (frame) {
|
||||
status = vtenc_send_frame(avctx, vtctx, frame);
|
||||
|
||||
--
|
||||
2.43.0.windows.1
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
From 7323bd68c1b34e9298ea557ff7a3e1883b653957 Mon Sep 17 00:00:00 2001
|
||||
From: 21pages <sunboeasy@gmail.com>
|
||||
Date: Tue, 10 Dec 2024 14:28:16 +0800
|
||||
Subject: [PATCH 4/5] mediacodec changing bitrate
|
||||
|
||||
Signed-off-by: 21pages <sunboeasy@gmail.com>
|
||||
---
|
||||
libavcodec/mediacodec_wrapper.c | 98 +++++++++++++++++++++++++++++++++
|
||||
libavcodec/mediacodec_wrapper.h | 7 +++
|
||||
libavcodec/mediacodecenc.c | 18 ++++++
|
||||
3 files changed, 123 insertions(+)
|
||||
|
||||
diff --git a/libavcodec/mediacodec_wrapper.c b/libavcodec/mediacodec_wrapper.c
|
||||
index 96c886666a..06b8504304 100644
|
||||
--- a/libavcodec/mediacodec_wrapper.c
|
||||
+++ b/libavcodec/mediacodec_wrapper.c
|
||||
@@ -35,6 +35,8 @@
|
||||
#include "ffjni.h"
|
||||
#include "mediacodec_wrapper.h"
|
||||
|
||||
+#define PARAMETER_KEY_VIDEO_BITRATE "video-bitrate"
|
||||
+
|
||||
struct JNIAMediaCodecListFields {
|
||||
|
||||
jclass mediacodec_list_class;
|
||||
@@ -195,6 +197,8 @@ struct JNIAMediaCodecFields {
|
||||
jmethodID set_input_surface_id;
|
||||
jmethodID signal_end_of_input_stream_id;
|
||||
|
||||
+ jmethodID set_parameters_id;
|
||||
+
|
||||
jclass mediainfo_class;
|
||||
|
||||
jmethodID init_id;
|
||||
@@ -248,6 +252,8 @@ static const struct FFJniField jni_amediacodec_mapping[] = {
|
||||
{ "android/media/MediaCodec", "setInputSurface", "(Landroid/view/Surface;)V", FF_JNI_METHOD, OFFSET(set_input_surface_id), 0 },
|
||||
{ "android/media/MediaCodec", "signalEndOfInputStream", "()V", FF_JNI_METHOD, OFFSET(signal_end_of_input_stream_id), 0 },
|
||||
|
||||
+ { "android/media/MediaCodec", "setParameters", "(Landroid/os/Bundle;)V", FF_JNI_METHOD, OFFSET(set_parameters_id), 0 },
|
||||
+
|
||||
{ "android/media/MediaCodec$BufferInfo", NULL, NULL, FF_JNI_CLASS, OFFSET(mediainfo_class), 1 },
|
||||
|
||||
{ "android/media/MediaCodec.BufferInfo", "<init>", "()V", FF_JNI_METHOD, OFFSET(init_id), 1 },
|
||||
@@ -292,6 +298,24 @@ typedef struct FFAMediaCodecJni {
|
||||
|
||||
static const FFAMediaCodec media_codec_jni;
|
||||
|
||||
+struct JNIABundleFields
|
||||
+{
|
||||
+ jclass bundle_class;
|
||||
+ jmethodID init_id;
|
||||
+ jmethodID put_int_id;
|
||||
+};
|
||||
+
|
||||
+#define OFFSET(x) offsetof(struct JNIABundleFields, x)
|
||||
+static const struct FFJniField jni_abundle_mapping[] = {
|
||||
+ { "android/os/Bundle", NULL, NULL, FF_JNI_CLASS, OFFSET(bundle_class), 1 },
|
||||
+
|
||||
+ { "android/os/Bundle", "<init>", "()V", FF_JNI_METHOD, OFFSET(init_id), 1 },
|
||||
+ { "android/os/Bundle", "putInt", "(Ljava/lang/String;I)V", FF_JNI_METHOD, OFFSET(put_int_id), 1 },
|
||||
+
|
||||
+ { NULL }
|
||||
+};
|
||||
+#undef OFFSET
|
||||
+
|
||||
#define JNI_GET_ENV_OR_RETURN(env, log_ctx, ret) do { \
|
||||
(env) = ff_jni_get_env(log_ctx); \
|
||||
if (!(env)) { \
|
||||
@@ -1762,6 +1786,70 @@ static int mediacodec_jni_signalEndOfInputStream(FFAMediaCodec *ctx)
|
||||
return 0;
|
||||
}
|
||||
|
||||
+
|
||||
+static int mediacodec_jni_setParameter(FFAMediaCodec *ctx, const char* name, int value)
|
||||
+{
|
||||
+ JNIEnv *env = NULL;
|
||||
+ struct JNIABundleFields jfields = { 0 };
|
||||
+ jobject object = NULL;
|
||||
+ jstring key = NULL;
|
||||
+ FFAMediaCodecJni *codec = (FFAMediaCodecJni *)ctx;
|
||||
+ void *log_ctx = codec;
|
||||
+ int ret = -1;
|
||||
+
|
||||
+ JNI_GET_ENV_OR_RETURN(env, codec, AVERROR_EXTERNAL);
|
||||
+
|
||||
+ if (ff_jni_init_jfields(env, &jfields, jni_abundle_mapping, 0, log_ctx) < 0) {
|
||||
+ av_log(log_ctx, AV_LOG_ERROR, "Failed to init jfields\n");
|
||||
+ goto fail;
|
||||
+ }
|
||||
+
|
||||
+ object = (*env)->NewObject(env, jfields.bundle_class, jfields.init_id);
|
||||
+ if (!object) {
|
||||
+ av_log(log_ctx, AV_LOG_ERROR, "Failed to create bundle object\n");
|
||||
+ goto fail;
|
||||
+ }
|
||||
+
|
||||
+ key = ff_jni_utf_chars_to_jstring(env, name, log_ctx);
|
||||
+ if (!key) {
|
||||
+ av_log(log_ctx, AV_LOG_ERROR, "Failed to convert key to jstring\n");
|
||||
+ goto fail;
|
||||
+ }
|
||||
+
|
||||
+ (*env)->CallVoidMethod(env, object, jfields.put_int_id, key, value);
|
||||
+ if (ff_jni_exception_check(env, 1, log_ctx) < 0) {
|
||||
+ goto fail;
|
||||
+ }
|
||||
+
|
||||
+ if (!codec->jfields.set_parameters_id) {
|
||||
+ av_log(log_ctx, AV_LOG_ERROR, "System doesn't support setParameters\n");
|
||||
+ goto fail;
|
||||
+ }
|
||||
+
|
||||
+ (*env)->CallVoidMethod(env, codec->object, codec->jfields.set_parameters_id, object);
|
||||
+ if (ff_jni_exception_check(env, 1, log_ctx) < 0) {
|
||||
+ goto fail;
|
||||
+ }
|
||||
+
|
||||
+ ret = 0;
|
||||
+
|
||||
+fail:
|
||||
+ if (key) {
|
||||
+ (*env)->DeleteLocalRef(env, key);
|
||||
+ }
|
||||
+ if (object) {
|
||||
+ (*env)->DeleteLocalRef(env, object);
|
||||
+ }
|
||||
+ ff_jni_reset_jfields(env, &jfields, jni_abundle_mapping, 0, log_ctx);
|
||||
+
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+static int mediacodec_jni_setDynamicBitrate(FFAMediaCodec *ctx, int bitrate)
|
||||
+{
|
||||
+ return mediacodec_jni_setParameter(ctx, PARAMETER_KEY_VIDEO_BITRATE, bitrate);
|
||||
+}
|
||||
+
|
||||
static const FFAMediaFormat media_format_jni = {
|
||||
.class = &amediaformat_class,
|
||||
|
||||
@@ -1821,6 +1909,8 @@ static const FFAMediaCodec media_codec_jni = {
|
||||
.getConfigureFlagEncode = mediacodec_jni_getConfigureFlagEncode,
|
||||
.cleanOutputBuffers = mediacodec_jni_cleanOutputBuffers,
|
||||
.signalEndOfInputStream = mediacodec_jni_signalEndOfInputStream,
|
||||
+
|
||||
+ .setDynamicBitrate = mediacodec_jni_setDynamicBitrate,
|
||||
};
|
||||
|
||||
typedef struct FFAMediaFormatNdk {
|
||||
@@ -2335,6 +2425,12 @@ static int mediacodec_ndk_signalEndOfInputStream(FFAMediaCodec *ctx)
|
||||
return 0;
|
||||
}
|
||||
|
||||
+static int mediacodec_ndk_setDynamicBitrate(FFAMediaCodec *ctx, int bitrate)
|
||||
+{
|
||||
+ av_log(ctx, AV_LOG_ERROR, "ndk setDynamicBitrate unavailable\n");
|
||||
+ return -1;
|
||||
+}
|
||||
+
|
||||
static const FFAMediaFormat media_format_ndk = {
|
||||
.class = &amediaformat_ndk_class,
|
||||
|
||||
@@ -2396,6 +2492,8 @@ static const FFAMediaCodec media_codec_ndk = {
|
||||
.getConfigureFlagEncode = mediacodec_ndk_getConfigureFlagEncode,
|
||||
.cleanOutputBuffers = mediacodec_ndk_cleanOutputBuffers,
|
||||
.signalEndOfInputStream = mediacodec_ndk_signalEndOfInputStream,
|
||||
+
|
||||
+ .setDynamicBitrate = mediacodec_ndk_setDynamicBitrate,
|
||||
};
|
||||
|
||||
FFAMediaFormat *ff_AMediaFormat_new(int ndk)
|
||||
diff --git a/libavcodec/mediacodec_wrapper.h b/libavcodec/mediacodec_wrapper.h
|
||||
index 11a4260497..86c64556ad 100644
|
||||
--- a/libavcodec/mediacodec_wrapper.h
|
||||
+++ b/libavcodec/mediacodec_wrapper.h
|
||||
@@ -219,6 +219,8 @@ struct FFAMediaCodec {
|
||||
|
||||
// For encoder with FFANativeWindow as input.
|
||||
int (*signalEndOfInputStream)(FFAMediaCodec *);
|
||||
+
|
||||
+ int (*setDynamicBitrate)(FFAMediaCodec *codec, int bitrate);
|
||||
};
|
||||
|
||||
static inline char *ff_AMediaCodec_getName(FFAMediaCodec *codec)
|
||||
@@ -343,6 +345,11 @@ static inline int ff_AMediaCodec_signalEndOfInputStream(FFAMediaCodec *codec)
|
||||
return codec->signalEndOfInputStream(codec);
|
||||
}
|
||||
|
||||
+static inline int ff_AMediaCodec_setDynamicBitrate(FFAMediaCodec *codec, int bitrate)
|
||||
+{
|
||||
+ return codec->setDynamicBitrate(codec, bitrate);
|
||||
+}
|
||||
+
|
||||
int ff_Build_SDK_INT(AVCodecContext *avctx);
|
||||
|
||||
enum FFAMediaFormatColorRange {
|
||||
diff --git a/libavcodec/mediacodecenc.c b/libavcodec/mediacodecenc.c
|
||||
index 6ca3968a24..221f7360f4 100644
|
||||
--- a/libavcodec/mediacodecenc.c
|
||||
+++ b/libavcodec/mediacodecenc.c
|
||||
@@ -76,6 +76,8 @@ typedef struct MediaCodecEncContext {
|
||||
int level;
|
||||
int pts_as_dts;
|
||||
int extract_extradata;
|
||||
+
|
||||
+ int last_bit_rate;
|
||||
} MediaCodecEncContext;
|
||||
|
||||
enum {
|
||||
@@ -193,6 +195,8 @@ static av_cold int mediacodec_init(AVCodecContext *avctx)
|
||||
int ret;
|
||||
int gop;
|
||||
|
||||
+ s->last_bit_rate = avctx->bit_rate;
|
||||
+
|
||||
if (s->use_ndk_codec < 0)
|
||||
s->use_ndk_codec = !av_jni_get_java_vm(avctx);
|
||||
|
||||
@@ -542,11 +546,25 @@ static int mediacodec_send(AVCodecContext *avctx,
|
||||
return 0;
|
||||
}
|
||||
|
||||
+static void update_config(AVCodecContext *avctx)
|
||||
+{
|
||||
+ MediaCodecEncContext *s = avctx->priv_data;
|
||||
+ if (avctx->bit_rate != s->last_bit_rate) {
|
||||
+ s->last_bit_rate = avctx->bit_rate;
|
||||
+ if (0 != ff_AMediaCodec_setDynamicBitrate(s->codec, avctx->bit_rate)) {
|
||||
+ av_log(avctx, AV_LOG_ERROR, "Failed to set bitrate to %d\n", avctx->bit_rate);
|
||||
+ } else {
|
||||
+ av_log(avctx, AV_LOG_INFO, "Set bitrate to %d\n", avctx->bit_rate);
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
static int mediacodec_encode(AVCodecContext *avctx, AVPacket *pkt)
|
||||
{
|
||||
MediaCodecEncContext *s = avctx->priv_data;
|
||||
int ret;
|
||||
|
||||
+ update_config(avctx);
|
||||
// Return on three case:
|
||||
// 1. Serious error
|
||||
// 2. Got a packet success
|
||||
--
|
||||
2.43.0.windows.1
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,30 @@
|
||||
From 595f0468e127f204741b6c37a479d71daaf571eb Mon Sep 17 00:00:00 2001
|
||||
From: 21pages <sunboeasy@gmail.com>
|
||||
Date: Tue, 10 Dec 2024 21:17:14 +0800
|
||||
Subject: [PATCH] fix linux configure
|
||||
|
||||
Signed-off-by: 21pages <sunboeasy@gmail.com>
|
||||
---
|
||||
configure | 6 ------
|
||||
1 file changed, 6 deletions(-)
|
||||
|
||||
diff --git a/configure b/configure
|
||||
index d77a55b653..48ca90ac5e 100755
|
||||
--- a/configure
|
||||
+++ b/configure
|
||||
@@ -7071,12 +7071,6 @@ enabled mmal && { check_lib mmal interface/mmal/mmal.h mmal_port_co
|
||||
check_lib mmal interface/mmal/mmal.h mmal_port_connect -lmmal_core -lmmal_util -lmmal_vc_client -lbcm_host; } ||
|
||||
die "ERROR: mmal not found" &&
|
||||
check_func_headers interface/mmal/mmal.h "MMAL_PARAMETER_VIDEO_MAX_NUM_CALLBACKS"; }
|
||||
-enabled openal && { check_pkg_config openal "openal >= 1.1" "AL/al.h" alGetError ||
|
||||
- { for al_extralibs in "${OPENAL_LIBS}" "-lopenal" "-lOpenAL32"; do
|
||||
- check_lib openal 'AL/al.h' alGetError "${al_extralibs}" && break; done } ||
|
||||
- die "ERROR: openal not found"; } &&
|
||||
- { test_cpp_condition "AL/al.h" "defined(AL_VERSION_1_1)" ||
|
||||
- die "ERROR: openal must be installed and version must be 1.1 or compatible"; }
|
||||
enabled opencl && { check_pkg_config opencl OpenCL CL/cl.h clEnqueueNDRangeKernel ||
|
||||
check_lib opencl OpenCL/cl.h clEnqueueNDRangeKernel "-framework OpenCL" ||
|
||||
check_lib opencl CL/cl.h clEnqueueNDRangeKernel -lOpenCL ||
|
||||
--
|
||||
2.34.1
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
From 1440f556234d135ce58a2ef38916c6a63b05870e Mon Sep 17 00:00:00 2001
|
||||
From: 21pages <sunboeasy@gmail.com>
|
||||
Date: Sat, 14 Dec 2024 21:39:44 +0800
|
||||
Subject: [PATCH] remove amf loop query
|
||||
|
||||
Signed-off-by: 21pages <sunboeasy@gmail.com>
|
||||
---
|
||||
libavcodec/amfenc.c | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/libavcodec/amfenc.c b/libavcodec/amfenc.c
|
||||
index f70f0109f6..a53a05b16b 100644
|
||||
--- a/libavcodec/amfenc.c
|
||||
+++ b/libavcodec/amfenc.c
|
||||
@@ -886,7 +886,7 @@ int ff_amf_receive_packet(AVCodecContext *avctx, AVPacket *avpkt)
|
||||
av_usleep(1000);
|
||||
}
|
||||
}
|
||||
- } while (block_and_wait);
|
||||
+ } while (false); // already set query timeout
|
||||
|
||||
if (res_query == AMF_EOF) {
|
||||
ret = AVERROR_EOF;
|
||||
--
|
||||
2.43.0.windows.1
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
From bec8d49e75b37806e1cff39c75027860fde0bfa2 Mon Sep 17 00:00:00 2001
|
||||
From: 21pages <sunboeasy@gmail.com>
|
||||
Date: Fri, 27 Dec 2024 08:43:12 +0800
|
||||
Subject: [PATCH] fix nvenc reconfigure blur
|
||||
|
||||
Signed-off-by: 21pages <sunboeasy@gmail.com>
|
||||
---
|
||||
libavcodec/nvenc.c | 4 ++--
|
||||
1 file changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/libavcodec/nvenc.c b/libavcodec/nvenc.c
|
||||
index 2cce478be0..f4c559b7ce 100644
|
||||
--- a/libavcodec/nvenc.c
|
||||
+++ b/libavcodec/nvenc.c
|
||||
@@ -2741,8 +2741,8 @@ static void reconfig_encoder(AVCodecContext *avctx, const AVFrame *frame)
|
||||
}
|
||||
|
||||
if (reconfig_bitrate) {
|
||||
- params.resetEncoder = 1;
|
||||
- params.forceIDR = 1;
|
||||
+ params.resetEncoder = 0;
|
||||
+ params.forceIDR = 0;
|
||||
|
||||
needs_encode_config = 1;
|
||||
needs_reconfig = 1;
|
||||
--
|
||||
2.43.0.windows.1
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
diff --git a/compat/w32dlfcn.h b/compat/w32dlfcn.h
|
||||
index ac20e83..1e83aa6 100644
|
||||
--- a/compat/w32dlfcn.h
|
||||
+++ b/compat/w32dlfcn.h
|
||||
@@ -76,6 +76,7 @@ static inline HMODULE win32_dlopen(const char *name)
|
||||
if (!name_w)
|
||||
goto exit;
|
||||
namelen = wcslen(name_w);
|
||||
+ /*
|
||||
// Try local directory first
|
||||
path = get_module_filename(NULL);
|
||||
if (!path)
|
||||
@@ -91,6 +92,7 @@ static inline HMODULE win32_dlopen(const char *name)
|
||||
path = new_path;
|
||||
wcscpy(path + pathlen + 1, name_w);
|
||||
module = LoadLibraryExW(path, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
|
||||
+ */
|
||||
if (module == NULL) {
|
||||
// Next try System32 directory
|
||||
pathlen = GetSystemDirectoryW(path, pathsize);
|
||||
@@ -131,7 +133,9 @@ exit:
|
||||
return NULL;
|
||||
module = LoadPackagedLibrary(name_w, 0);
|
||||
#else
|
||||
-#define LOAD_FLAGS (LOAD_LIBRARY_SEARCH_APPLICATION_DIR | LOAD_LIBRARY_SEARCH_SYSTEM32)
|
||||
+// #define LOAD_FLAGS (LOAD_LIBRARY_SEARCH_APPLICATION_DIR | LOAD_LIBRARY_SEARCH_SYSTEM32)
|
||||
+// Don't dynamic-link libraries from the application directory.
|
||||
+ #define LOAD_FLAGS LOAD_LIBRARY_SEARCH_SYSTEM32
|
||||
/* filename may be be in CP_ACP */
|
||||
if (!name_w)
|
||||
return LoadLibraryExA(name, NULL, LOAD_FLAGS);
|
||||
@@ -0,0 +1,42 @@
|
||||
From a609e1666c79ccce4faf7aa61d509bf202df9149 Mon Sep 17 00:00:00 2001
|
||||
From: 21pages <sunboeasy@gmail.com>
|
||||
Date: Fri, 5 Sep 2025 21:35:37 +0800
|
||||
Subject: [PATCH] android mediacodec encode align 64
|
||||
|
||||
Signed-off-by: 21pages <sunboeasy@gmail.com>
|
||||
---
|
||||
libavcodec/mediacodecenc.c | 11 ++++++-----
|
||||
1 file changed, 6 insertions(+), 5 deletions(-)
|
||||
|
||||
diff --git a/libavcodec/mediacodecenc.c b/libavcodec/mediacodecenc.c
|
||||
index 221f7360f4..768c8151df 100644
|
||||
--- a/libavcodec/mediacodecenc.c
|
||||
+++ b/libavcodec/mediacodecenc.c
|
||||
@@ -242,18 +242,19 @@ static av_cold int mediacodec_init(AVCodecContext *avctx)
|
||||
ff_AMediaFormat_setString(format, "mime", codec_mime);
|
||||
// Workaround the alignment requirement of mediacodec. We can't do it
|
||||
// silently for AV_PIX_FMT_MEDIACODEC.
|
||||
+ const int align = 64;
|
||||
if (avctx->pix_fmt != AV_PIX_FMT_MEDIACODEC &&
|
||||
(avctx->codec_id == AV_CODEC_ID_H264 ||
|
||||
avctx->codec_id == AV_CODEC_ID_HEVC)) {
|
||||
- s->width = FFALIGN(avctx->width, 16);
|
||||
- s->height = FFALIGN(avctx->height, 16);
|
||||
+ s->width = FFALIGN(avctx->width, align);
|
||||
+ s->height = FFALIGN(avctx->height, align);
|
||||
} else {
|
||||
s->width = avctx->width;
|
||||
s->height = avctx->height;
|
||||
- if (s->width % 16 || s->height % 16)
|
||||
+ if (s->width % align || s->height % align)
|
||||
av_log(avctx, AV_LOG_WARNING,
|
||||
- "Video size %dx%d isn't align to 16, it may have device compatibility issue\n",
|
||||
- s->width, s->height);
|
||||
+ "Video size %dx%d isn't align to %d, it may have device compatibility issue\n",
|
||||
+ s->width, s->height, align);
|
||||
}
|
||||
ff_AMediaFormat_setInt32(format, "width", s->width);
|
||||
ff_AMediaFormat_setInt32(format, "height", s->height);
|
||||
--
|
||||
2.43.0.windows.1
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: RustDesk <support@rustdesk.com>
|
||||
Date: Fri, 1 Nov 2025 08:00:00 +0000
|
||||
Subject: [PATCH] Fix CVBufferCopyAttachments crash on macOS Big Sur
|
||||
|
||||
Use weak linking for CVBufferCopyAttachments to avoid symbol resolution
|
||||
crash on macOS < 12. The function will be NULL on older systems and the
|
||||
code will fall back to the deprecated CVBufferGetAttachments.
|
||||
|
||||
This fixes a crash on macOS Big Sur (11.x) where CVBufferCopyAttachments
|
||||
is not available. The runtime check with __builtin_available is not enough
|
||||
because the symbol is still resolved at load time, causing a dyld error.
|
||||
|
||||
Fixes: https://github.com/rustdesk/rustdesk/issues/13377
|
||||
---
|
||||
libavutil/hwcontext_videotoolbox.c | 21 ++++++++++++++++++++-
|
||||
1 file changed, 20 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/libavutil/hwcontext_videotoolbox.c b/libavutil/hwcontext_videotoolbox.c
|
||||
index 0000000000..1111111111 100644
|
||||
--- a/libavutil/hwcontext_videotoolbox.c
|
||||
+++ b/libavutil/hwcontext_videotoolbox.c
|
||||
@@ -33,6 +33,25 @@
|
||||
#include "pixfmt.h"
|
||||
#include "pixdesc.h"
|
||||
|
||||
+// Weak import CVBufferCopyAttachments to support macOS < 12
|
||||
+// The runtime check with __builtin_available is not enough because
|
||||
+// the symbol is still resolved at load time, causing dyld errors on Big Sur.
|
||||
+// With weak_import, the function pointer will be NULL on older systems.
|
||||
+#if TARGET_OS_OSX && defined(__MAC_12_0) && __MAC_OS_X_VERSION_MAX_ALLOWED >= __MAC_12_0
|
||||
+extern CFDictionaryRef CVBufferCopyAttachments(CVBufferRef buffer, CVAttachmentMode mode)
|
||||
+ __attribute__((weak_import));
|
||||
+#endif
|
||||
+#if TARGET_OS_IOS && defined(__IPHONE_15_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_15_0
|
||||
+extern CFDictionaryRef CVBufferCopyAttachments(CVBufferRef buffer, CVAttachmentMode mode)
|
||||
+ __attribute__((weak_import));
|
||||
+#endif
|
||||
+#if TARGET_OS_TV && defined(__TVOS_15_0) && __TV_OS_VERSION_MAX_ALLOWED >= __TVOS_15_0
|
||||
+extern CFDictionaryRef CVBufferCopyAttachments(CVBufferRef buffer, CVAttachmentMode mode)
|
||||
+ __attribute__((weak_import));
|
||||
+#endif
|
||||
+
|
||||
+// End of weak import section
|
||||
+
|
||||
typedef struct VTFramesContext {
|
||||
/**
|
||||
* The public AVVTFramesContext. See hwcontext_videotoolbox.h for it.
|
||||
@@ -547,7 +566,7 @@ static CFDictionaryRef vt_cv_buffer_copy_attachments(CVBufferRef buffer,
|
||||
(TARGET_OS_TV && defined(__TVOS_15_0) && __TV_OS_VERSION_MAX_ALLOWED >= __TVOS_15_0)
|
||||
// On recent enough versions, just use the respective API
|
||||
if (__builtin_available(macOS 12.0, iOS 15.0, tvOS 15.0, *))
|
||||
- return CVBufferCopyAttachments(buffer, attachment_mode);
|
||||
+ if (CVBufferCopyAttachments != NULL) return CVBufferCopyAttachments(buffer, attachment_mode);
|
||||
#endif
|
||||
|
||||
// Check that the target is lower than macOS 12 / iOS 15 / tvOS 15
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,705 @@
|
||||
vcpkg_from_github(
|
||||
OUT_SOURCE_PATH SOURCE_PATH
|
||||
REPO ffmpeg/ffmpeg
|
||||
REF "n${VERSION}"
|
||||
SHA512 3b273769ef1a1b63aed0691eef317a760f8c83b1d0e1c232b67bbee26db60b4864aafbc88df0e86d6bebf07185bbd057f33e2d5258fde6d97763b9994cd48b6f
|
||||
HEAD_REF master
|
||||
PATCHES
|
||||
0001-create-lib-libraries.patch
|
||||
0002-fix-msvc-link.patch
|
||||
0003-fix-windowsinclude.patch
|
||||
0004-dependencies.patch
|
||||
0005-fix-nasm.patch
|
||||
0007-fix-lib-naming.patch
|
||||
0013-define-WINVER.patch
|
||||
0020-fix-aarch64-libswscale.patch
|
||||
0024-fix-osx-host-c11.patch
|
||||
0040-ffmpeg-add-av_stream_get_first_dts-for-chromium.patch # Do not remove this patch. It is required by chromium
|
||||
0041-add-const-for-opengl-definition.patch
|
||||
0043-fix-miss-head.patch
|
||||
patch/0001-avcodec-amfenc-add-query_timeout-option-for-h264-hev.patch
|
||||
patch/0002-libavcodec-amfenc-reconfig-when-bitrate-change.patch
|
||||
patch/0004-videotoolbox-changing-bitrate.patch
|
||||
patch/0005-mediacodec-changing-bitrate.patch
|
||||
patch/0006-dlopen-libva.patch
|
||||
patch/0007-fix-linux-configure.patch
|
||||
patch/0008-remove-amf-loop-query.patch
|
||||
patch/0009-fix-nvenc-reconfigure-blur.patch
|
||||
patch/0010.disable-loading-DLLs-from-app-dir.patch
|
||||
patch/0011-android-mediacodec-encode-align-64.patch
|
||||
patch/0012-fix-macos-big-sur-CVBufferCopyAttachments.patch
|
||||
)
|
||||
|
||||
if(SOURCE_PATH MATCHES " ")
|
||||
message(FATAL_ERROR "Error: ffmpeg will not build with spaces in the path. Please use a directory with no spaces")
|
||||
endif()
|
||||
|
||||
if(NOT VCPKG_TARGET_ARCHITECTURE STREQUAL "wasm32")
|
||||
vcpkg_find_acquire_program(NASM)
|
||||
get_filename_component(NASM_EXE_PATH "${NASM}" DIRECTORY)
|
||||
vcpkg_add_to_path("${NASM_EXE_PATH}")
|
||||
endif()
|
||||
|
||||
set(OPTIONS "\
|
||||
--disable-shared \
|
||||
--enable-static \
|
||||
--enable-pic \
|
||||
--disable-everything \
|
||||
--disable-programs \
|
||||
--disable-doc \
|
||||
--disable-htmlpages \
|
||||
--disable-manpages \
|
||||
--disable-podpages \
|
||||
--disable-txtpages \
|
||||
--disable-network \
|
||||
--disable-appkit \
|
||||
--disable-coreimage \
|
||||
--disable-metal \
|
||||
--disable-sdl2 \
|
||||
--disable-securetransport \
|
||||
--disable-vulkan \
|
||||
--disable-audiotoolbox \
|
||||
--disable-v4l2-m2m \
|
||||
--disable-debug \
|
||||
--disable-valgrind-backtrace \
|
||||
--disable-large-tests \
|
||||
--disable-bzlib \
|
||||
--disable-avdevice \
|
||||
--enable-avcodec \
|
||||
--enable-avformat \
|
||||
--disable-avfilter \
|
||||
--disable-swresample \
|
||||
--disable-swscale \
|
||||
--disable-postproc \
|
||||
--enable-decoder=h264 \
|
||||
--enable-decoder=hevc \
|
||||
--enable-parser=h264 \
|
||||
--enable-parser=hevc \
|
||||
--enable-bsf=h264_mp4toannexb \
|
||||
--enable-bsf=hevc_mp4toannexb \
|
||||
--enable-bsf=h264_metadata \
|
||||
--enable-bsf=hevc_metadata \
|
||||
--enable-muxer=mp4 \
|
||||
--enable-protocol=file \
|
||||
")
|
||||
|
||||
if(VCPKG_HOST_IS_WINDOWS)
|
||||
vcpkg_acquire_msys(MSYS_ROOT PACKAGES automake1.16)
|
||||
set(SHELL "${MSYS_ROOT}/usr/bin/bash.exe")
|
||||
vcpkg_add_to_path("${MSYS_ROOT}/usr/share/automake-1.16")
|
||||
string(APPEND OPTIONS " --pkg-config=${CURRENT_HOST_INSTALLED_DIR}/tools/pkgconf/pkgconf${VCPKG_HOST_EXECUTABLE_SUFFIX}")
|
||||
else()
|
||||
find_program(SHELL bash)
|
||||
endif()
|
||||
|
||||
if(VCPKG_TARGET_IS_LINUX)
|
||||
string(APPEND OPTIONS "\
|
||||
--target-os=linux \
|
||||
--enable-pthreads \
|
||||
--disable-vdpau \
|
||||
")
|
||||
|
||||
if(VCPKG_TARGET_ARCHITECTURE STREQUAL "arm")
|
||||
else()
|
||||
string(APPEND OPTIONS "\
|
||||
--enable-cuda \
|
||||
--enable-ffnvcodec \
|
||||
--enable-encoder=h264_nvenc \
|
||||
--enable-encoder=hevc_nvenc \
|
||||
--enable-hwaccel=h264_nvdec \
|
||||
--enable-hwaccel=hevc_nvdec \
|
||||
--enable-amf \
|
||||
--enable-encoder=h264_amf \
|
||||
--enable-encoder=hevc_amf \
|
||||
--enable-hwaccel=h264_vaapi \
|
||||
--enable-hwaccel=hevc_vaapi \
|
||||
--enable-encoder=h264_vaapi \
|
||||
--enable-encoder=hevc_vaapi \
|
||||
")
|
||||
|
||||
if(VCPKG_TARGET_ARCHITECTURE STREQUAL "x64")
|
||||
string(APPEND OPTIONS "\
|
||||
--enable-cuda_llvm \
|
||||
")
|
||||
endif()
|
||||
endif()
|
||||
elseif(VCPKG_TARGET_IS_WINDOWS)
|
||||
string(APPEND OPTIONS "\
|
||||
--target-os=win32 \
|
||||
--toolchain=msvc \
|
||||
--cc=cl \
|
||||
--enable-gpl \
|
||||
--enable-d3d11va \
|
||||
--enable-cuda \
|
||||
--enable-ffnvcodec \
|
||||
--enable-hwaccel=h264_nvdec \
|
||||
--enable-hwaccel=hevc_nvdec \
|
||||
--enable-hwaccel=h264_d3d11va \
|
||||
--enable-hwaccel=hevc_d3d11va \
|
||||
--enable-hwaccel=h264_d3d11va2 \
|
||||
--enable-hwaccel=hevc_d3d11va2 \
|
||||
--enable-amf \
|
||||
--enable-encoder=h264_amf \
|
||||
--enable-encoder=hevc_amf \
|
||||
--enable-encoder=h264_nvenc \
|
||||
--enable-encoder=hevc_nvenc \
|
||||
--enable-libmfx \
|
||||
--enable-encoder=h264_qsv \
|
||||
--enable-encoder=hevc_qsv \
|
||||
")
|
||||
|
||||
if(VCPKG_TARGET_ARCHITECTURE STREQUAL "x86")
|
||||
set(LIB_MACHINE_ARG /machine:x86)
|
||||
string(APPEND OPTIONS " --arch=i686 --enable-cross-compile")
|
||||
elseif(VCPKG_TARGET_ARCHITECTURE STREQUAL "x64")
|
||||
set(LIB_MACHINE_ARG /machine:x64)
|
||||
string(APPEND OPTIONS " --arch=x86_64")
|
||||
else()
|
||||
message(FATAL_ERROR "Unsupported target architecture")
|
||||
endif()
|
||||
elseif(VCPKG_TARGET_IS_OSX)
|
||||
string(APPEND OPTIONS "\
|
||||
--disable-autodetect \
|
||||
--enable-videotoolbox \
|
||||
--enable-encoder=h264_videotoolbox,hevc_videotoolbox \
|
||||
--enable-hwaccel=h264_videotoolbox,hevc_videotoolbox \
|
||||
")
|
||||
elseif(VCPKG_TARGET_IS_IOS)
|
||||
string(APPEND OPTIONS "\
|
||||
--arch=arm64 \
|
||||
--disable-autodetect \
|
||||
--disable-hwaccels \
|
||||
--disable-encoders \
|
||||
--disable-videotoolbox \
|
||||
--extra-cflags=\"-arch arm64 -mios-version-min=8.0 -fembed-bitcode\" \
|
||||
--extra-ldflags=\"-arch arm64 -mios-version-min=8.0 -fembed-bitcode\" \
|
||||
")
|
||||
elseif(VCPKG_CMAKE_SYSTEM_NAME STREQUAL "Android")
|
||||
string(APPEND OPTIONS "\
|
||||
--target-os=android \
|
||||
--disable-asm \
|
||||
--disable-iconv \
|
||||
--enable-jni \
|
||||
--enable-mediacodec \
|
||||
--disable-hwaccels \
|
||||
--enable-encoder=h264_mediacodec \
|
||||
--enable-encoder=hevc_mediacodec \
|
||||
--enable-decoder=h264_mediacodec \
|
||||
--enable-decoder=hevc_mediacodec \
|
||||
")
|
||||
endif()
|
||||
|
||||
if(VCPKG_TARGET_IS_OSX)
|
||||
list(JOIN VCPKG_OSX_ARCHITECTURES " " OSX_ARCHS)
|
||||
list(LENGTH VCPKG_OSX_ARCHITECTURES OSX_ARCH_COUNT)
|
||||
endif()
|
||||
|
||||
vcpkg_cmake_get_vars(cmake_vars_file)
|
||||
include("${cmake_vars_file}")
|
||||
|
||||
if(VCPKG_DETECTED_MSVC)
|
||||
string(APPEND OPTIONS " --disable-inline-asm") # clang-cl has inline assembly but this leads to undefined symbols.
|
||||
set(OPTIONS "--toolchain=msvc ${OPTIONS}")
|
||||
|
||||
# This is required because ffmpeg depends upon optimizations to link correctly
|
||||
string(APPEND VCPKG_COMBINED_C_FLAGS_DEBUG " -O2")
|
||||
string(REGEX REPLACE "(^| )-RTC1( |$)" " " VCPKG_COMBINED_C_FLAGS_DEBUG "${VCPKG_COMBINED_C_FLAGS_DEBUG}")
|
||||
string(REGEX REPLACE "(^| )-Od( |$)" " " VCPKG_COMBINED_C_FLAGS_DEBUG "${VCPKG_COMBINED_C_FLAGS_DEBUG}")
|
||||
string(REGEX REPLACE "(^| )-Ob0( |$)" " " VCPKG_COMBINED_C_FLAGS_DEBUG "${VCPKG_COMBINED_C_FLAGS_DEBUG}")
|
||||
endif()
|
||||
|
||||
string(APPEND VCPKG_COMBINED_C_FLAGS_DEBUG " -I \"${CURRENT_INSTALLED_DIR}/include\"")
|
||||
string(APPEND VCPKG_COMBINED_C_FLAGS_RELEASE " -I \"${CURRENT_INSTALLED_DIR}/include\"")
|
||||
|
||||
if(VCPKG_TARGET_IS_WINDOWS)
|
||||
string(APPEND VCPKG_COMBINED_C_FLAGS_DEBUG " -I \"${CURRENT_INSTALLED_DIR}/include/mfx\"")
|
||||
string(APPEND VCPKG_COMBINED_C_FLAGS_RELEASE " -I \"${CURRENT_INSTALLED_DIR}/include/mfx\"")
|
||||
endif()
|
||||
|
||||
# # Setup vcpkg toolchain
|
||||
set(prog_env "")
|
||||
|
||||
if(VCPKG_DETECTED_CMAKE_C_COMPILER)
|
||||
get_filename_component(CC_path "${VCPKG_DETECTED_CMAKE_C_COMPILER}" DIRECTORY)
|
||||
get_filename_component(CC_filename "${VCPKG_DETECTED_CMAKE_C_COMPILER}" NAME)
|
||||
set(ENV{CC} "${CC_filename}")
|
||||
string(APPEND OPTIONS " --cc=${CC_filename}")
|
||||
|
||||
if(VCPKG_HOST_IS_WINDOWS)
|
||||
string(APPEND OPTIONS " --host_cc=${CC_filename}")
|
||||
endif()
|
||||
|
||||
list(APPEND prog_env "${CC_path}")
|
||||
endif()
|
||||
|
||||
if(VCPKG_DETECTED_CMAKE_CXX_COMPILER)
|
||||
get_filename_component(CXX_path "${VCPKG_DETECTED_CMAKE_CXX_COMPILER}" DIRECTORY)
|
||||
get_filename_component(CXX_filename "${VCPKG_DETECTED_CMAKE_CXX_COMPILER}" NAME)
|
||||
set(ENV{CXX} "${CXX_filename}")
|
||||
string(APPEND OPTIONS " --cxx=${CXX_filename}")
|
||||
|
||||
# string(APPEND OPTIONS " --host_cxx=${CC_filename}")
|
||||
list(APPEND prog_env "${CXX_path}")
|
||||
endif()
|
||||
|
||||
if(VCPKG_DETECTED_CMAKE_RC_COMPILER)
|
||||
get_filename_component(RC_path "${VCPKG_DETECTED_CMAKE_RC_COMPILER}" DIRECTORY)
|
||||
get_filename_component(RC_filename "${VCPKG_DETECTED_CMAKE_RC_COMPILER}" NAME)
|
||||
set(ENV{WINDRES} "${RC_filename}")
|
||||
string(APPEND OPTIONS " --windres=${RC_filename}")
|
||||
list(APPEND prog_env "${RC_path}")
|
||||
endif()
|
||||
|
||||
if(VCPKG_DETECTED_CMAKE_LINKER AND VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_MINGW)
|
||||
get_filename_component(LD_path "${VCPKG_DETECTED_CMAKE_LINKER}" DIRECTORY)
|
||||
get_filename_component(LD_filename "${VCPKG_DETECTED_CMAKE_LINKER}" NAME)
|
||||
set(ENV{LD} "${LD_filename}")
|
||||
string(APPEND OPTIONS " --ld=${LD_filename}")
|
||||
|
||||
# string(APPEND OPTIONS " --host_ld=${LD_filename}")
|
||||
list(APPEND prog_env "${LD_path}")
|
||||
endif()
|
||||
|
||||
if(VCPKG_DETECTED_CMAKE_NM)
|
||||
get_filename_component(NM_path "${VCPKG_DETECTED_CMAKE_NM}" DIRECTORY)
|
||||
get_filename_component(NM_filename "${VCPKG_DETECTED_CMAKE_NM}" NAME)
|
||||
set(ENV{NM} "${NM_filename}")
|
||||
string(APPEND OPTIONS " --nm=${NM_filename}")
|
||||
list(APPEND prog_env "${NM_path}")
|
||||
endif()
|
||||
|
||||
if(VCPKG_DETECTED_CMAKE_AR)
|
||||
get_filename_component(AR_path "${VCPKG_DETECTED_CMAKE_AR}" DIRECTORY)
|
||||
get_filename_component(AR_filename "${VCPKG_DETECTED_CMAKE_AR}" NAME)
|
||||
|
||||
if(AR_filename MATCHES [[^(llvm-)?lib\.exe$]])
|
||||
set(ENV{AR} "ar-lib ${AR_filename}")
|
||||
string(APPEND OPTIONS " --ar='ar-lib ${AR_filename}'")
|
||||
else()
|
||||
set(ENV{AR} "${AR_filename}")
|
||||
string(APPEND OPTIONS " --ar='${AR_filename}'")
|
||||
endif()
|
||||
|
||||
list(APPEND prog_env "${AR_path}")
|
||||
endif()
|
||||
|
||||
if(VCPKG_DETECTED_CMAKE_RANLIB)
|
||||
get_filename_component(RANLIB_path "${VCPKG_DETECTED_CMAKE_RANLIB}" DIRECTORY)
|
||||
get_filename_component(RANLIB_filename "${VCPKG_DETECTED_CMAKE_RANLIB}" NAME)
|
||||
set(ENV{RANLIB} "${RANLIB_filename}")
|
||||
string(APPEND OPTIONS " --ranlib=${RANLIB_filename}")
|
||||
list(APPEND prog_env "${RANLIB_path}")
|
||||
endif()
|
||||
|
||||
if(VCPKG_DETECTED_CMAKE_STRIP)
|
||||
get_filename_component(STRIP_path "${VCPKG_DETECTED_CMAKE_STRIP}" DIRECTORY)
|
||||
get_filename_component(STRIP_filename "${VCPKG_DETECTED_CMAKE_STRIP}" NAME)
|
||||
set(ENV{STRIP} "${STRIP_filename}")
|
||||
string(APPEND OPTIONS " --strip=${STRIP_filename}")
|
||||
list(APPEND prog_env "${STRIP_path}")
|
||||
endif()
|
||||
|
||||
if(VCPKG_HOST_IS_WINDOWS)
|
||||
vcpkg_acquire_msys(MSYS_ROOT PACKAGES automake1.16)
|
||||
set(SHELL "${MSYS_ROOT}/usr/bin/bash.exe")
|
||||
list(APPEND prog_env "${MSYS_ROOT}/usr/bin" "${MSYS_ROOT}/usr/share/automake-1.16")
|
||||
else()
|
||||
# find_program(SHELL bash)
|
||||
endif()
|
||||
|
||||
list(REMOVE_DUPLICATES prog_env)
|
||||
vcpkg_add_to_path(PREPEND ${prog_env})
|
||||
|
||||
# More? OBJCC BIN2C
|
||||
file(REMOVE_RECURSE "${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-dbg" "${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-rel")
|
||||
|
||||
set(FFMPEG_PKGCONFIG_MODULES libavutil)
|
||||
|
||||
set(OPTIONS_CROSS "--enable-cross-compile")
|
||||
|
||||
# ffmpeg needs --cross-prefix option to use appropriate tools for cross-compiling.
|
||||
if(VCPKG_DETECTED_CMAKE_C_COMPILER MATCHES "([^\/]*-)gcc$")
|
||||
string(APPEND OPTIONS_CROSS " --cross-prefix=${CMAKE_MATCH_1}")
|
||||
endif()
|
||||
|
||||
if(VCPKG_TARGET_ARCHITECTURE STREQUAL "x64")
|
||||
set(BUILD_ARCH "x86_64")
|
||||
else()
|
||||
set(BUILD_ARCH ${VCPKG_TARGET_ARCHITECTURE})
|
||||
endif()
|
||||
|
||||
if(VCPKG_TARGET_ARCHITECTURE STREQUAL "arm" OR VCPKG_TARGET_ARCHITECTURE STREQUAL "arm64")
|
||||
if(VCPKG_TARGET_IS_WINDOWS)
|
||||
vcpkg_find_acquire_program(GASPREPROCESSOR)
|
||||
|
||||
foreach(GAS_PATH ${GASPREPROCESSOR})
|
||||
get_filename_component(GAS_ITEM_PATH ${GAS_PATH} DIRECTORY)
|
||||
vcpkg_add_to_path("${GAS_ITEM_PATH}")
|
||||
endforeach(GAS_PATH)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(OPTIONS_DEBUG "--disable-optimizations")
|
||||
set(OPTIONS_RELEASE "--enable-optimizations")
|
||||
|
||||
set(OPTIONS "${OPTIONS} ${OPTIONS_CROSS}")
|
||||
|
||||
if(VCPKG_TARGET_IS_MINGW)
|
||||
set(OPTIONS "${OPTIONS} --extra_cflags=-D_WIN32_WINNT=0x0601")
|
||||
elseif(VCPKG_TARGET_IS_WINDOWS)
|
||||
set(OPTIONS "${OPTIONS} --extra-cflags=-DHAVE_UNISTD_H=0")
|
||||
endif()
|
||||
|
||||
vcpkg_find_acquire_program(PKGCONFIG)
|
||||
set(OPTIONS "${OPTIONS} --pkg-config=${PKGCONFIG}")
|
||||
|
||||
if(VCPKG_LIBRARY_LINKAGE STREQUAL "static")
|
||||
set(OPTIONS "${OPTIONS} --pkg-config-flags=--static")
|
||||
endif()
|
||||
|
||||
message(STATUS "Building Options: ${OPTIONS}")
|
||||
|
||||
# Release build
|
||||
if(NOT VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release")
|
||||
if(VCPKG_DETECTED_MSVC)
|
||||
set(OPTIONS_RELEASE "${OPTIONS_RELEASE} --extra-ldflags=-libpath:\"${CURRENT_INSTALLED_DIR}/lib\"")
|
||||
else()
|
||||
set(OPTIONS_RELEASE "${OPTIONS_RELEASE} --extra-ldflags=-L\"${CURRENT_INSTALLED_DIR}/lib\"")
|
||||
endif()
|
||||
|
||||
message(STATUS "Building Release Options: ${OPTIONS_RELEASE}")
|
||||
set(ENV{PKG_CONFIG_PATH} "${CURRENT_INSTALLED_DIR}/lib/pkgconfig")
|
||||
message(STATUS "Building ${PORT} for Release")
|
||||
file(MAKE_DIRECTORY "${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-rel")
|
||||
|
||||
# We use response files here as the only known way to handle spaces in paths
|
||||
set(crsp "${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-rel/cflags.rsp")
|
||||
string(REGEX REPLACE "-arch [A-Za-z0-9_]+" "" VCPKG_COMBINED_C_FLAGS_RELEASE_SANITIZED "${VCPKG_COMBINED_C_FLAGS_RELEASE}")
|
||||
file(WRITE "${crsp}" "${VCPKG_COMBINED_C_FLAGS_RELEASE_SANITIZED}")
|
||||
set(ldrsp "${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-rel/ldflags.rsp")
|
||||
string(REGEX REPLACE "-arch [A-Za-z0-9_]+" "" VCPKG_COMBINED_SHARED_LINKER_FLAGS_RELEASE_SANITIZED "${VCPKG_COMBINED_SHARED_LINKER_FLAGS_RELEASE}")
|
||||
file(WRITE "${ldrsp}" "${VCPKG_COMBINED_SHARED_LINKER_FLAGS_RELEASE_SANITIZED}")
|
||||
set(ENV{CFLAGS} "@${crsp}")
|
||||
|
||||
# All tools except the msvc arm{,64} assembler accept @... as response file syntax.
|
||||
# For that assembler, there is no known way to pass in flags. We must hope that not passing flags will work acceptably.
|
||||
if(NOT VCPKG_DETECTED_MSVC OR NOT VCPKG_TARGET_ARCHITECTURE MATCHES "^arm")
|
||||
set(ENV{ASFLAGS} "@${crsp}")
|
||||
endif()
|
||||
|
||||
set(ENV{LDFLAGS} "@${ldrsp}")
|
||||
set(ENV{ARFLAGS} "${VCPKG_COMBINED_STATIC_LINKER_FLAGS_RELEASE}")
|
||||
|
||||
set(BUILD_DIR "${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-rel")
|
||||
set(CONFIGURE_OPTIONS "${OPTIONS} ${OPTIONS_RELEASE}")
|
||||
set(INST_PREFIX "${CURRENT_PACKAGES_DIR}")
|
||||
|
||||
configure_file("${CMAKE_CURRENT_LIST_DIR}/build.sh.in" "${BUILD_DIR}/build.sh" @ONLY)
|
||||
|
||||
z_vcpkg_setup_pkgconfig_path(CONFIG RELEASE)
|
||||
|
||||
vcpkg_execute_required_process(
|
||||
COMMAND "${SHELL}" ./build.sh
|
||||
WORKING_DIRECTORY "${BUILD_DIR}"
|
||||
LOGNAME "build-${TARGET_TRIPLET}-rel"
|
||||
SAVE_LOG_FILES ffbuild/config.log
|
||||
)
|
||||
|
||||
z_vcpkg_restore_pkgconfig_path()
|
||||
endif()
|
||||
|
||||
# Debug build
|
||||
if(NOT VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug")
|
||||
if(VCPKG_DETECTED_MSVC)
|
||||
set(OPTIONS_DEBUG "${OPTIONS_DEBUG} --extra-ldflags=-libpath:\"${CURRENT_INSTALLED_DIR}/debug/lib\"")
|
||||
else()
|
||||
set(OPTIONS_DEBUG "${OPTIONS_DEBUG} --extra-ldflags=-L\"${CURRENT_INSTALLED_DIR}/debug/lib\"")
|
||||
endif()
|
||||
|
||||
message(STATUS "Building Debug Options: ${OPTIONS_DEBUG}")
|
||||
set(ENV{LDFLAGS} "${VCPKG_COMBINED_SHARED_LINKER_FLAGS_DEBUG}")
|
||||
set(ENV{PKG_CONFIG_PATH} "${CURRENT_INSTALLED_DIR}/debug/lib/pkgconfig")
|
||||
message(STATUS "Building ${PORT} for Debug")
|
||||
file(MAKE_DIRECTORY "${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-dbg")
|
||||
set(crsp "${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-dbg/cflags.rsp")
|
||||
string(REGEX REPLACE "-arch [A-Za-z0-9_]+" "" VCPKG_COMBINED_C_FLAGS_DEBUG_SANITIZED "${VCPKG_COMBINED_C_FLAGS_DEBUG}")
|
||||
file(WRITE "${crsp}" "${VCPKG_COMBINED_C_FLAGS_DEBUG_SANITIZED}")
|
||||
set(ldrsp "${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-dbg/ldflags.rsp")
|
||||
string(REGEX REPLACE "-arch [A-Za-z0-9_]+" "" VCPKG_COMBINED_SHARED_LINKER_FLAGS_DEBUG_SANITIZED "${VCPKG_COMBINED_SHARED_LINKER_FLAGS_DEBUG}")
|
||||
file(WRITE "${ldrsp}" "${VCPKG_COMBINED_SHARED_LINKER_FLAGS_DEBUG_SANITIZED}")
|
||||
set(ENV{CFLAGS} "@${crsp}")
|
||||
|
||||
if(NOT VCPKG_DETECTED_MSVC OR NOT VCPKG_TARGET_ARCHITECTURE MATCHES "^arm")
|
||||
set(ENV{ASFLAGS} "@${crsp}")
|
||||
endif()
|
||||
|
||||
set(ENV{LDFLAGS} "@${ldrsp}")
|
||||
set(ENV{ARFLAGS} "${VCPKG_COMBINED_STATIC_LINKER_FLAGS_DEBUG}")
|
||||
|
||||
set(BUILD_DIR "${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-dbg")
|
||||
set(CONFIGURE_OPTIONS "${OPTIONS} ${OPTIONS_DEBUG}")
|
||||
set(INST_PREFIX "${CURRENT_PACKAGES_DIR}/debug")
|
||||
|
||||
configure_file("${CMAKE_CURRENT_LIST_DIR}/build.sh.in" "${BUILD_DIR}/build.sh" @ONLY)
|
||||
|
||||
z_vcpkg_setup_pkgconfig_path(CONFIG DEBUG)
|
||||
|
||||
vcpkg_execute_required_process(
|
||||
COMMAND "${SHELL}" ./build.sh
|
||||
WORKING_DIRECTORY "${BUILD_DIR}"
|
||||
LOGNAME "build-${TARGET_TRIPLET}-dbg"
|
||||
SAVE_LOG_FILES ffbuild/config.log
|
||||
)
|
||||
|
||||
z_vcpkg_restore_pkgconfig_path()
|
||||
endif()
|
||||
|
||||
if(VCPKG_TARGET_IS_WINDOWS)
|
||||
file(GLOB DEF_FILES "${CURRENT_PACKAGES_DIR}/lib/*.def" "${CURRENT_PACKAGES_DIR}/debug/lib/*.def")
|
||||
|
||||
if(NOT VCPKG_TARGET_IS_MINGW)
|
||||
if(VCPKG_TARGET_ARCHITECTURE STREQUAL "arm")
|
||||
set(LIB_MACHINE_ARG /machine:ARM)
|
||||
elseif(VCPKG_TARGET_ARCHITECTURE STREQUAL "arm64")
|
||||
set(LIB_MACHINE_ARG /machine:ARM64)
|
||||
elseif(VCPKG_TARGET_ARCHITECTURE STREQUAL "x86")
|
||||
set(LIB_MACHINE_ARG /machine:x86)
|
||||
elseif(VCPKG_TARGET_ARCHITECTURE STREQUAL "x64")
|
||||
set(LIB_MACHINE_ARG /machine:x64)
|
||||
else()
|
||||
message(FATAL_ERROR "Unsupported target architecture")
|
||||
endif()
|
||||
|
||||
foreach(DEF_FILE ${DEF_FILES})
|
||||
get_filename_component(DEF_FILE_DIR "${DEF_FILE}" DIRECTORY)
|
||||
get_filename_component(DEF_FILE_NAME "${DEF_FILE}" NAME)
|
||||
string(REGEX REPLACE "-[0-9]*\\.def" "${VCPKG_TARGET_STATIC_LIBRARY_SUFFIX}" OUT_FILE_NAME "${DEF_FILE_NAME}")
|
||||
file(TO_NATIVE_PATH "${DEF_FILE}" DEF_FILE_NATIVE)
|
||||
file(TO_NATIVE_PATH "${DEF_FILE_DIR}/${OUT_FILE_NAME}" OUT_FILE_NATIVE)
|
||||
message(STATUS "Generating ${OUT_FILE_NATIVE}")
|
||||
vcpkg_execute_required_process(
|
||||
COMMAND lib.exe "/def:${DEF_FILE_NATIVE}" "/out:${OUT_FILE_NATIVE}" ${LIB_MACHINE_ARG}
|
||||
WORKING_DIRECTORY "${CURRENT_PACKAGES_DIR}"
|
||||
LOGNAME "libconvert-${TARGET_TRIPLET}"
|
||||
)
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
file(GLOB EXP_FILES "${CURRENT_PACKAGES_DIR}/lib/*.exp" "${CURRENT_PACKAGES_DIR}/debug/lib/*.exp")
|
||||
file(GLOB LIB_FILES "${CURRENT_PACKAGES_DIR}/bin/*${VCPKG_TARGET_STATIC_LIBRARY_SUFFIX}" "${CURRENT_PACKAGES_DIR}/debug/bin/*${VCPKG_TARGET_STATIC_LIBRARY_SUFFIX}")
|
||||
|
||||
if(VCPKG_TARGET_IS_MINGW)
|
||||
file(GLOB LIB_FILES_2 "${CURRENT_PACKAGES_DIR}/bin/*.lib" "${CURRENT_PACKAGES_DIR}/debug/bin/*.lib")
|
||||
endif()
|
||||
|
||||
set(files_to_remove ${EXP_FILES} ${LIB_FILES} ${LIB_FILES_2} ${DEF_FILES})
|
||||
|
||||
if(files_to_remove)
|
||||
file(REMOVE ${files_to_remove})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include" "${CURRENT_PACKAGES_DIR}/debug/share")
|
||||
|
||||
if(VCPKG_LIBRARY_LINKAGE STREQUAL "static")
|
||||
file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/bin" "${CURRENT_PACKAGES_DIR}/debug/bin")
|
||||
endif()
|
||||
|
||||
vcpkg_copy_pdbs()
|
||||
|
||||
if(VCPKG_TARGET_IS_WINDOWS)
|
||||
set(_dirs "/")
|
||||
|
||||
if(NOT VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug")
|
||||
list(APPEND _dirs "/debug/")
|
||||
endif()
|
||||
|
||||
foreach(_debug IN LISTS _dirs)
|
||||
foreach(PKGCONFIG_MODULE IN LISTS FFMPEG_PKGCONFIG_MODULES)
|
||||
set(PKGCONFIG_FILE "${CURRENT_PACKAGES_DIR}${_debug}lib/pkgconfig/${PKGCONFIG_MODULE}.pc")
|
||||
|
||||
# remove redundant cygwin style -libpath entries
|
||||
execute_process(
|
||||
COMMAND "${MSYS_ROOT}/usr/bin/cygpath.exe" -u "${CURRENT_INSTALLED_DIR}"
|
||||
OUTPUT_VARIABLE CYG_INSTALLED_DIR
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
vcpkg_replace_string("${PKGCONFIG_FILE}" "-libpath:${CYG_INSTALLED_DIR}${_debug}lib/pkgconfig/../../lib " "")
|
||||
|
||||
# transform libdir, includedir, and prefix paths from cygwin style to windows style
|
||||
file(READ "${PKGCONFIG_FILE}" PKGCONFIG_CONTENT)
|
||||
|
||||
foreach(PATH_NAME prefix libdir includedir)
|
||||
string(REGEX MATCH "${PATH_NAME}=[^\n]*" PATH_VALUE "${PKGCONFIG_CONTENT}")
|
||||
string(REPLACE "${PATH_NAME}=" "" PATH_VALUE "${PATH_VALUE}")
|
||||
|
||||
if(NOT PATH_VALUE)
|
||||
message(FATAL_ERROR "failed to find pkgconfig variable ${PATH_NAME}")
|
||||
endif()
|
||||
|
||||
execute_process(
|
||||
COMMAND "${MSYS_ROOT}/usr/bin/cygpath.exe" -w "${PATH_VALUE}"
|
||||
OUTPUT_VARIABLE FIXED_PATH
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
file(TO_CMAKE_PATH "${FIXED_PATH}" FIXED_PATH)
|
||||
vcpkg_replace_string("${PKGCONFIG_FILE}" "${PATH_NAME}=${PATH_VALUE}" "${PATH_NAME}=${FIXED_PATH}")
|
||||
endforeach()
|
||||
|
||||
# list libraries with -l flag (so pkgconf knows they are libraries and not just linker flags)
|
||||
foreach(LIBS_ENTRY Libs Libs.private)
|
||||
string(REGEX MATCH "${LIBS_ENTRY}: [^\n]*" LIBS_VALUE "${PKGCONFIG_CONTENT}")
|
||||
|
||||
if(NOT LIBS_VALUE)
|
||||
message(FATAL_ERROR "failed to find pkgconfig entry ${LIBS_ENTRY}")
|
||||
endif()
|
||||
|
||||
string(REPLACE "${LIBS_ENTRY}: " "" LIBS_VALUE "${LIBS_VALUE}")
|
||||
|
||||
if(LIBS_VALUE)
|
||||
set(LIBS_VALUE_OLD "${LIBS_VALUE}")
|
||||
string(REGEX REPLACE "([^ ]+)[.]lib" "-l\\1" LIBS_VALUE "${LIBS_VALUE}")
|
||||
set(LIBS_VALUE_NEW "${LIBS_VALUE}")
|
||||
vcpkg_replace_string("${PKGCONFIG_FILE}" "${LIBS_ENTRY}: ${LIBS_VALUE_OLD}" "${LIBS_ENTRY}: ${LIBS_VALUE_NEW}")
|
||||
endif()
|
||||
endforeach()
|
||||
endforeach()
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
vcpkg_fixup_pkgconfig()
|
||||
|
||||
# Handle dependencies
|
||||
x_vcpkg_pkgconfig_get_modules(PREFIX FFMPEG_PKGCONFIG MODULES ${FFMPEG_PKGCONFIG_MODULES} LIBS)
|
||||
|
||||
function(append_dependencies_from_libs out)
|
||||
cmake_parse_arguments(PARSE_ARGV 1 "arg" "" "LIBS" "")
|
||||
string(REGEX REPLACE "[ ]+" ";" contents "${arg_LIBS}")
|
||||
list(FILTER contents EXCLUDE REGEX "^-F.+")
|
||||
list(FILTER contents EXCLUDE REGEX "^-framework$")
|
||||
list(FILTER contents EXCLUDE REGEX "^-L.+")
|
||||
list(FILTER contents EXCLUDE REGEX "^-libpath:.+")
|
||||
list(TRANSFORM contents REPLACE "^-Wl,-framework," "-l")
|
||||
list(FILTER contents EXCLUDE REGEX "^-Wl,.+")
|
||||
list(TRANSFORM contents REPLACE "^-l" "")
|
||||
list(FILTER contents EXCLUDE REGEX "^avutil$")
|
||||
list(FILTER contents EXCLUDE REGEX "^avcodec$")
|
||||
list(FILTER contents EXCLUDE REGEX "^avdevice$")
|
||||
list(FILTER contents EXCLUDE REGEX "^avfilter$")
|
||||
list(FILTER contents EXCLUDE REGEX "^avformat$")
|
||||
list(FILTER contents EXCLUDE REGEX "^postproc$")
|
||||
list(FILTER contents EXCLUDE REGEX "^swresample$")
|
||||
list(FILTER contents EXCLUDE REGEX "^swscale$")
|
||||
|
||||
if(VCPKG_TARGET_IS_WINDOWS)
|
||||
list(TRANSFORM contents TOLOWER)
|
||||
endif()
|
||||
|
||||
if(contents)
|
||||
list(APPEND "${out}" "${contents}")
|
||||
set("${out}" "${${out}}" PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
append_dependencies_from_libs(FFMPEG_DEPENDENCIES_RELEASE LIBS "${FFMPEG_PKGCONFIG_LIBS_RELEASE}")
|
||||
append_dependencies_from_libs(FFMPEG_DEPENDENCIES_DEBUG LIBS "${FFMPEG_PKGCONFIG_LIBS_DEBUG}")
|
||||
|
||||
# must remove duplicates from the front to respect link order so reverse first
|
||||
list(REVERSE FFMPEG_DEPENDENCIES_RELEASE)
|
||||
list(REVERSE FFMPEG_DEPENDENCIES_DEBUG)
|
||||
list(REMOVE_DUPLICATES FFMPEG_DEPENDENCIES_RELEASE)
|
||||
list(REMOVE_DUPLICATES FFMPEG_DEPENDENCIES_DEBUG)
|
||||
list(REVERSE FFMPEG_DEPENDENCIES_RELEASE)
|
||||
list(REVERSE FFMPEG_DEPENDENCIES_DEBUG)
|
||||
|
||||
message(STATUS "Dependencies (release): ${FFMPEG_DEPENDENCIES_RELEASE}")
|
||||
message(STATUS "Dependencies (debug): ${FFMPEG_DEPENDENCIES_DEBUG}")
|
||||
|
||||
# Handle version strings
|
||||
function(extract_regex_from_file out)
|
||||
cmake_parse_arguments(PARSE_ARGV 1 "arg" "MAJOR" "FILE_WITHOUT_EXTENSION;REGEX" "")
|
||||
file(READ "${arg_FILE_WITHOUT_EXTENSION}.h" contents)
|
||||
|
||||
if(contents MATCHES "${arg_REGEX}")
|
||||
if(NOT CMAKE_MATCH_COUNT EQUAL 1)
|
||||
message(FATAL_ERROR "Could not identify match group in regular expression \"${arg_REGEX}\"")
|
||||
endif()
|
||||
else()
|
||||
if(arg_MAJOR)
|
||||
file(READ "${arg_FILE_WITHOUT_EXTENSION}_major.h" contents)
|
||||
|
||||
if(contents MATCHES "${arg_REGEX}")
|
||||
if(NOT CMAKE_MATCH_COUNT EQUAL 1)
|
||||
message(FATAL_ERROR "Could not identify match group in regular expression \"${arg_REGEX}\"")
|
||||
endif()
|
||||
else()
|
||||
message(WARNING "Could not find line matching \"${arg_REGEX}\" in file \"${arg_FILE_WITHOUT_EXTENSION}_major.h\"")
|
||||
endif()
|
||||
else()
|
||||
message(WARNING "Could not find line matching \"${arg_REGEX}\" in file \"${arg_FILE_WITHOUT_EXTENSION}.h\"")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set("${out}" "${CMAKE_MATCH_1}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(extract_version_from_component out)
|
||||
cmake_parse_arguments(PARSE_ARGV 1 "arg" "" "COMPONENT" "")
|
||||
string(TOLOWER "${arg_COMPONENT}" component_lower)
|
||||
string(TOUPPER "${arg_COMPONENT}" component_upper)
|
||||
extract_regex_from_file(major_version
|
||||
FILE_WITHOUT_EXTENSION "${SOURCE_PATH}/${component_lower}/version"
|
||||
MAJOR
|
||||
REGEX "#define ${component_upper}_VERSION_MAJOR[ ]+([0-9]+)"
|
||||
)
|
||||
extract_regex_from_file(minor_version
|
||||
FILE_WITHOUT_EXTENSION "${SOURCE_PATH}/${component_lower}/version"
|
||||
REGEX "#define ${component_upper}_VERSION_MINOR[ ]+([0-9]+)"
|
||||
)
|
||||
extract_regex_from_file(micro_version
|
||||
FILE_WITHOUT_EXTENSION "${SOURCE_PATH}/${component_lower}/version"
|
||||
REGEX "#define ${component_upper}_VERSION_MICRO[ ]+([0-9]+)"
|
||||
)
|
||||
set("${out}" "${major_version}.${minor_version}.${micro_version}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
extract_regex_from_file(FFMPEG_VERSION
|
||||
FILE_WITHOUT_EXTENSION "${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-rel/libavutil/ffversion"
|
||||
REGEX "#define FFMPEG_VERSION[ ]+\"(.+)\""
|
||||
)
|
||||
|
||||
extract_version_from_component(LIBAVUTIL_VERSION
|
||||
COMPONENT libavutil)
|
||||
extract_version_from_component(LIBAVCODEC_VERSION
|
||||
COMPONENT libavcodec)
|
||||
extract_version_from_component(LIBAVDEVICE_VERSION
|
||||
COMPONENT libavdevice)
|
||||
extract_version_from_component(LIBAVFILTER_VERSION
|
||||
COMPONENT libavfilter)
|
||||
extract_version_from_component(LIBAVFORMAT_VERSION
|
||||
COMPONENT libavformat)
|
||||
extract_version_from_component(LIBSWRESAMPLE_VERSION
|
||||
COMPONENT libswresample)
|
||||
extract_version_from_component(LIBSWSCALE_VERSION
|
||||
COMPONENT libswscale)
|
||||
|
||||
# Handle copyright
|
||||
file(STRINGS "${CURRENT_BUILDTREES_DIR}/build-${TARGET_TRIPLET}-rel-out.log" LICENSE_STRING REGEX "License: .*" LIMIT_COUNT 1)
|
||||
|
||||
if(LICENSE_STRING STREQUAL "License: LGPL version 2.1 or later")
|
||||
set(LICENSE_FILE "COPYING.LGPLv2.1")
|
||||
elseif(LICENSE_STRING STREQUAL "License: LGPL version 3 or later")
|
||||
set(LICENSE_FILE "COPYING.LGPLv3")
|
||||
elseif(LICENSE_STRING STREQUAL "License: GPL version 2 or later")
|
||||
set(LICENSE_FILE "COPYING.GPLv2")
|
||||
elseif(LICENSE_STRING STREQUAL "License: GPL version 3 or later")
|
||||
set(LICENSE_FILE "COPYING.GPLv3")
|
||||
elseif(LICENSE_STRING STREQUAL "License: nonfree and unredistributable")
|
||||
set(LICENSE_FILE "COPYING.NONFREE")
|
||||
file(WRITE "${SOURCE_PATH}/${LICENSE_FILE}" "${LICENSE_STRING}")
|
||||
else()
|
||||
message(FATAL_ERROR "Failed to identify license (${LICENSE_STRING})")
|
||||
endif()
|
||||
|
||||
configure_file("${CMAKE_CURRENT_LIST_DIR}/vcpkg-cmake-wrapper.cmake" "${CURRENT_PACKAGES_DIR}/share/${PORT}/vcpkg-cmake-wrapper.cmake" @ONLY)
|
||||
vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/${LICENSE_FILE}")
|
||||
@@ -0,0 +1,47 @@
|
||||
set(FFMPEG_PREV_MODULE_PATH ${CMAKE_MODULE_PATH})
|
||||
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR})
|
||||
|
||||
include(SelectLibraryConfigurations)
|
||||
|
||||
cmake_policy(SET CMP0012 NEW)
|
||||
|
||||
set(vcpkg_no_avcodec_target ON)
|
||||
set(vcpkg_no_avformat_target ON)
|
||||
set(vcpkg_no_avutil_target ON)
|
||||
if(TARGET FFmpeg::avcodec)
|
||||
set(vcpkg_no_avcodec_target OFF)
|
||||
endif()
|
||||
if(TARGET FFmpeg::avformat)
|
||||
set(vcpkg_no_avformat_target OFF)
|
||||
endif()
|
||||
if(TARGET FFmpeg::avutil)
|
||||
set(vcpkg_no_avutil_target OFF)
|
||||
endif()
|
||||
|
||||
_find_package(${ARGS})
|
||||
|
||||
if(WIN32)
|
||||
set(PKG_CONFIG_EXECUTABLE "${CMAKE_CURRENT_LIST_DIR}/../../../@_HOST_TRIPLET@/tools/pkgconf/pkgconf.exe" CACHE STRING "" FORCE)
|
||||
endif()
|
||||
|
||||
set(PKG_CONFIG_USE_CMAKE_PREFIX_PATH ON) # Required for CMAKE_MINIMUM_REQUIRED_VERSION VERSION_LESS 3.1 which otherwise ignores CMAKE_PREFIX_PATH
|
||||
|
||||
if(@WITH_MFX@)
|
||||
find_package(PkgConfig )
|
||||
pkg_check_modules(libmfx IMPORTED_TARGET libmfx)
|
||||
list(APPEND FFMPEG_LIBRARIES PkgConfig::libmfx)
|
||||
if(vcpkg_no_avcodec_target AND TARGET FFmpeg::avcodec)
|
||||
target_link_libraries(FFmpeg::avcodec INTERFACE PkgConfig::libmfx)
|
||||
endif()
|
||||
if(vcpkg_no_avutil_target AND TARGET FFmpeg::avutil)
|
||||
target_link_libraries(FFmpeg::avutil INTERFACE PkgConfig::libmfx)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(FFMPEG_LIBRARY ${FFMPEG_LIBRARIES})
|
||||
|
||||
set(CMAKE_MODULE_PATH ${FFMPEG_PREV_MODULE_PATH})
|
||||
|
||||
unset(vcpkg_no_avformat_target)
|
||||
unset(vcpkg_no_avcodec_target)
|
||||
unset(vcpkg_no_avutil_target)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user