diff --git a/scripts/fancy_copy.py b/scripts/fancy_copy.py
deleted file mode 100644
index 84d01cf71..000000000
--- a/scripts/fancy_copy.py
+++ /dev/null
@@ -1,57 +0,0 @@
-#!/usr/bin/env python3
-
-# Deskflow -- mouse and keyboard sharing utility
-# Copyright (C) 2024 Symless Ltd.
-#
-# This package is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# found in the file LICENSE that should have accompanied this file.
-#
-# This package is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
-import lib.env as env
-
-env.ensure_in_venv(__file__)
-
-import argparse
-import lib.file_utils as file_utils
-import lib.colors as colors
-
-
-def main():
- """
- Cross platform script to copy files and directories.
- This script was mostly created beause the default `copy` command on Windows is too noisy.
- If this becomes complex it must be replaced with a library.
- """
-
- parser = argparse.ArgumentParser()
- parser.add_argument("source", help="Source pattern to copy from")
- parser.add_argument("target", help="Destination pattern to copy to")
- parser.add_argument(
- "--ignore-errors", action="store_true", help="Ignore errors when copying"
- )
- parser.add_argument(
- "--verbose", action="store_true", help="Print more information to the console"
- )
- args = parser.parse_args()
-
- options = file_utils.CopyOptions(args.ignore_errors, args.verbose)
-
- try:
- file_utils.copy(args.source, args.target, options)
- except Exception as e:
- if not args.ignore_errors:
- raise e
- else:
- print(f"{colors.ERROR_TEXT} {e}")
-
-
-if __name__ == "__main__":
- main()
diff --git a/scripts/lib/file_utils.py b/scripts/lib/file_utils.py
deleted file mode 100644
index e6ba02e15..000000000
--- a/scripts/lib/file_utils.py
+++ /dev/null
@@ -1,104 +0,0 @@
-# Deskflow -- mouse and keyboard sharing utility
-# Copyright (C) 2024 Symless Ltd.
-#
-# This package is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# found in the file LICENSE that should have accompanied this file.
-#
-# This package is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
-import glob, os, shutil, sys
-import lib.env as env
-import lib.colors as colors
-
-
-class CopyOptions:
- def __init__(self, ignore_errors, verbose):
- self.ignore_errors = ignore_errors
- self.verbose = verbose
-
-
-class CopyContext:
- def __init__(self):
- self.errors = 0
- self.permission_error = False
-
-
-def copy(source, target, options):
- """Copy files and directories from source to target."""
-
- context = CopyContext()
- if options.verbose:
- print(f"Copying files from {source} to {target}")
-
- try:
- for match in glob.glob(source):
-
- if os.path.isfile(match):
- copy_file(match, target, options, context)
- elif os.path.isdir(match):
- copy_dir(match, target, options, context)
- else:
- raise RuntimeError(f"Path {match} is not a file or directory")
- finally:
- if context.errors and options.ignore_errors:
- print(f"{colors.WARNING_TEXT} Ignored {context.errors} copy error(s)")
-
- if context.permission_error and env.is_windows():
- print(
- f"{colors.HINT_TEXT} A Windows file permission error may mean that the file is in use"
- )
-
-
-def copy_dir(match, target, options, context):
- if options.verbose:
- print(f"Copying directory {match} to {target}")
-
- try:
- shutil.copytree(match, target, dirs_exist_ok=True)
- except Exception as e:
- handle_all_copy_errors(e, options, context)
-
-
-def copy_file(match, target, options, context):
- if options.verbose:
- print(f"Copying file {match} to {target}")
-
- try:
- shutil.copy(match, target)
- except Exception as e:
- handle_all_copy_errors(e, options, context)
-
-
-def handle_all_copy_errors(error, options, context):
-
- if isinstance(error, shutil.Error):
- for _, _, file_error in error.args[0]:
- handle_copy_error(file_error, options, context)
- else:
- handle_copy_error(error, options, context)
-
- if not options.ignore_errors:
- raise error
-
-
-def handle_copy_error(error, options, context):
- if isinstance(error, PermissionError):
- context.permission_error = True
-
- if isinstance(error, str):
- context.permission_error = error.startswith("[Errno 13] Permission denied")
-
- context.errors += 1
-
- if options.ignore_errors:
- print(
- f"{colors.WARNING_TEXT} Copy failed: {error}",
- file=sys.stderr,
- )
diff --git a/scripts/lib/windows.py b/scripts/lib/windows.py
deleted file mode 100644
index b717451f2..000000000
--- a/scripts/lib/windows.py
+++ /dev/null
@@ -1,236 +0,0 @@
-# Deskflow -- mouse and keyboard sharing utility
-# Copyright (C) 2024 Symless Ltd.
-#
-# This package is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# found in the file LICENSE that should have accompanied this file.
-#
-# This package is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
-import ctypes, sys, os, shutil, time, subprocess
-import lib.cmd_utils as cmd_utils
-import lib.env as env
-import psutil # type: ignore
-import lib.colors as colors
-import lib.file_utils as file_utils
-
-LOCK_FILE = "tmp/elevated.lock"
-RUNNER_TEMP_ENV = "RUNNER_TEMP"
-SERVICE_NOT_RUNNING_ERROR = 2
-ERROR_ACCESS_VIOLATION = 0xC0000005
-
-
-def run_elevated(script, args=None, use_sys_argv=True, wait_for_exit=False):
- if not args and use_sys_argv:
- args = " ".join(sys.argv[1:])
-
- if wait_for_exit:
- args += f" --lock-file {LOCK_FILE}"
- env.persist_lock_file(LOCK_FILE)
-
- command = f"{script} {args} --pause-on-exit"
- print(f"Running script with elevated privileges: {command}")
-
- WINDOW_HANDLE = None
- OPERATION = "runas"
- DIRECTORY = None
- SHOW_CMD = 1
- instance = ctypes.windll.shell32.ShellExecuteW(
- WINDOW_HANDLE, OPERATION, sys.executable, command, DIRECTORY, SHOW_CMD
- )
-
- ERROR_ACCESS_DENIED = 5
- if instance == ERROR_ACCESS_DENIED:
- raise RuntimeError(
- f"Failed to run script with elevated privileges, access denied (code {instance})"
- )
-
- ERROR_MAX = 32
- if instance <= ERROR_MAX:
- raise RuntimeError(
- f"Failed to run script with elevated privileges, error code: {instance}"
- )
-
- print("Script is running with elevated privileges")
-
- if wait_for_exit:
- with open(LOCK_FILE, "r") as f:
- pid = f.read()
-
- print(f"Waiting for elevated process to exit: {pid}")
- while os.path.exists(LOCK_FILE):
- # Intentionally wait forever, since this code should not run where a developer
- # has no control, such as in a CI environment.
- pass
-
-
-def is_admin():
- """Returns True if the current process has admin privileges."""
- try:
- return ctypes.windll.shell32.IsUserAnAdmin()
- except ctypes.WinError:
- return False
-
-
-def set_env_var(name, value):
- """
- Sets or updates an environment variable. Appends the value if it doesn't already exist.
-
- Args:
- name (str): The name of the environment variable.
- value (str): The value of the environment variable.
- """
-
- current_value = os.getenv(name, "")
-
- if value not in current_value:
- new_value = f"{current_value}{os.pathsep}{value}" if current_value else value
- os.environ[name] = new_value
- print(f"Setting environment variable: {name}={value}")
- cmd_utils.run(["setx", name, new_value], check=True, shell=True, print_cmd=True)
-
-def assert_vs_cmd(cmd):
- has_cmd = cmd_utils.has_command(cmd)
- if not has_cmd:
- raise RuntimeError(
- f"The '{cmd}' command was not found, "
- "re-run from 'Developer Command Prompt for VS'"
- )
-
-class WindowsService:
- def __init__(self, script, args):
- self.script = script
- self.verbose = args.verbose
- self.bin_name = args.bin_name
- self.source_dir = os.path.abspath(args.source_dir)
- self.target_dir = os.path.abspath(args.target_dir)
- self.service_id = args.service_id
- self.ignore_processes = args.ignore_processes
-
- def print_verbose(self, message):
- if self.verbose:
- print(message)
-
- def ensure_admin(self):
- if not is_admin():
- run_elevated(self.script)
- sys.exit()
-
- def restart(self):
- """Stops the daemon service, copies files, and restarts the daemon service."""
-
- self.ensure_admin()
- self.stop()
- self.copy_files()
- self.start()
-
- def reinstall(self):
- """Stops and uninstalls daemon service, copies files, and reinstalls the daemon service."""
-
- self.ensure_admin()
- self.stop()
-
- source_bin_path = f"{os.path.join(self.source_dir, self.bin_name)}.exe"
-
- self.copy_files()
-
- print("Removing old daemon service")
- try:
- subprocess.run([source_bin_path, "/uninstall"], shell=True, check=True)
- except subprocess.CalledProcessError as e:
- self.check_access_violation(e.returncode, source_bin_path)
- if e.returncode != 0:
- print(
- f"{colors.WARNING_TEXT} Uninstall failed, return code: {e.returncode}",
- file=sys.stderr,
- )
-
- target_bin_path = os.path.join(self.target_dir, self.bin_name + ".exe")
-
- try:
- print("Installing daemon service")
- subprocess.run([target_bin_path, "/install"], shell=True, check=True)
- except subprocess.CalledProcessError as e:
- self.check_access_violation(e.returncode, target_bin_path)
- if e.returncode != 0:
- print(
- f"{colors.WARNING_TEXT} Install failed, return code: {e.returncode}"
- )
-
- def copy_files(self):
- options = file_utils.CopyOptions(ignore_errors=True, verbose=False)
- print(f"Copying files from {self.source_dir} to {self.target_dir}")
- file_utils.copy(f"{self.source_dir}/*", self.target_dir, options)
-
- def stop(self):
- self.ensure_admin()
- print("Stopping daemon service")
- try:
- subprocess.run(["net", "stop", self.service_id], shell=True, check=True)
- except subprocess.CalledProcessError as e:
- if e.returncode == SERVICE_NOT_RUNNING_ERROR:
- self.print_verbose("Daemon service not running")
- else:
- raise e
-
- # Wait for Windows to release the file handles after process termination.
- self.wait_for_stop()
-
- def start(self):
- self.ensure_admin()
- print("Starting daemon service")
- subprocess.run(["net", "start", self.service_id], shell=True, check=True)
-
- def wait_for_stop(self):
- if self.is_any_process_running(self.target_dir):
- print("Waiting for file handles to release...", end="", flush=True)
- while self.is_any_process_running(self.target_dir):
- if not self.verbose:
- print(".", end="", flush=True)
- time.sleep(1)
- if not self.verbose:
- print()
-
- def check_access_violation(self, return_code, bin_path):
- if return_code == ERROR_ACCESS_VIOLATION:
- print(
- f"{colors.WARNING_TEXT} Process crashed with memory access violation: {bin_path}",
- file=sys.stderr,
- )
-
- def is_ignored_process(self, exe):
- for ignore_process in self.ignore_processes:
- if exe.endswith(ignore_process):
- return True
- return False
-
- def is_any_process_running(self, dir):
- """Check if there is any running process that contains the given directory."""
-
- self.print_verbose(f"Checking if any process is running in: {dir}")
-
- for proc in psutil.process_iter(attrs=["name", "exe"]):
- exe = proc.info["exe"]
-
- if not exe:
- self.print_verbose(f"Skipping process with no exe: {proc}")
- continue
-
- if self.is_ignored_process(exe):
- self.print_verbose(f"Ignoring process: {exe}")
- continue
-
- try:
- if dir.lower() in exe.lower():
- self.print_verbose(f"Process found: {exe}")
- return True
- except (psutil.NoSuchProcess, psutil.AccessDenied):
- pass
-
- return False
diff --git a/scripts/pyproject.toml b/scripts/pyproject.toml
index 60b971b10..12fc4fad6 100644
--- a/scripts/pyproject.toml
+++ b/scripts/pyproject.toml
@@ -6,8 +6,5 @@ requires-python = ">=3.9"
dependencies = [
"clang-format",
"python-dotenv",
- "pyyaml",
"colorama",
- "gitpython",
- "psutil",
]