nwg-shell/nwg_shell/installer.py

318 lines
12 KiB
Python

#!/usr/bin/python3
"""
Installer script to copy all the components' configs and style sheets to their locations, or to restore original files.
Pass the `--all` argument to install/overwrite all, or use w/ no argument to restore selected files interactively.
This script is primarily intended to work (and tested!) on fresh installed Arch Linux.
See: https://github.com/nwg-piotr/nwg-shell/wiki
The package dependencies should pull all the packages needed for the nwg-shell to run.
Project: https://github.com/nwg-piotr/nwg-shell
Author's email: nwg.piotr@gmail.com
Copyright (c) 2022 Piotr Miller
License: MIT
"""
import argparse
import datetime
import os
import subprocess
import sys
import time
from shutil import copy, copy2
from nwg_shell.__about__ import __version__
from nwg_shell.tools import load_json, save_json, is_command
dir_name = os.path.dirname(__file__)
config_home = os.getenv('XDG_CONFIG_HOME') if os.getenv('XDG_CONFIG_HOME') else os.path.join(os.getenv("HOME"),
".config")
data_home = os.getenv('XDG_DATA_HOME') if os.getenv('XDG_DATA_HOME') else os.path.join(os.getenv("HOME"),
".local/share")
shell_data = None
shell_data_file = os.path.join(data_home, "nwg-shell/data")
browsers = {
"brave": "brave --enable-features=UseOzonePlatform --ozone-platform=wayland",
"chromium": "chromium --enable-features=UseOzonePlatform --ozone-platform=wayland",
"google-chrome-stable": "google-chrome-stable --enable-features=UseOzonePlatform --ozone-platform=wayland",
"epiphany": "epiphany",
"falkon": "falkon",
"firefox": "MOZ_ENABLE_WAYLAND=1 firefox",
"konqueror": "konqueror",
"midori": "midori",
"microsoft-edge-stable": "microsoft-edge-stable --enable-features=UseOzonePlatform --ozone-platform=wayland",
"opera": "opera",
"qutebrowser": "qutebrowser",
"seamonkey": "seamonkey",
"surf": "surf",
"vivaldi-stable": "vivaldi-stable --enable-features=UseOzonePlatform --ozone-platform=wayland",
}
def copy_folder(src, dst, hyprland=False):
if not os.path.isdir(dst):
os.mkdir(dst)
for item in os.listdir(src):
if hyprland or "hyprland" not in item:
print(" {}".format(item))
copy2(os.path.join(src, item), os.path.join(dst, item))
def copy_from_skel(name, folder="config", skip_confirmation=False, hyprland=False):
src = os.path.join(dir_name, "skel/{}/".format(folder), name)
if folder == "data":
dst = os.path.join(data_home, name)
else:
dst = os.path.join(config_home, name)
a = input("Install/overwrite files in the '{}' directory? y/N ".format(dst)) if not skip_confirmation else "Y"
if a.strip().upper() != "Y":
print("'{}' directory skipped".format(dst))
return
else:
print("Copying files to '{}'".format(dst))
try:
# copytree(src, dst, dirs_exist_ok=True)
copy_folder(src, dst, hyprland=hyprland)
except Exception as e:
print("Failure: {}".format(e), file=sys.stderr)
def restore(name, folder="config"):
src = os.path.join(dir_name, "skel/{}/".format(folder), name)
if folder == "data":
dst = os.path.join(data_home, name)
if not os.path.isdir(dst):
os.mkdir(dst)
else:
dst = os.path.join(config_home, name)
if not os.path.isdir(dst):
os.mkdir(dst)
response = []
for f in os.listdir(src):
src_path = os.path.join(src, f)
dst_path = os.path.join(dst, f)
try:
if not os.path.isfile(dst_path):
copy(src_path, dst_path, follow_symlinks=False)
response.append("Copied '{}'".format(dst_path))
except Exception as e:
response.append("Couldn't copy '{}' to '{}': {}".format(src_path, dst_path, e))
return "\n".join(response) if len(response) > 0 else None
def main():
parser = argparse.ArgumentParser()
parser.add_argument("-a",
"--all",
action="store_true",
help="install/overwrite All configs and styles w/o confirmation")
parser.add_argument("-hypr",
action="store_true",
help="install Hyprland presets; use together w/ -a or -w")
parser.add_argument("-r",
"--restore",
action="store_true",
help="Restore missing configs, styles & data files")
parser.add_argument("-w",
"--web",
action="store_true",
help="Web installer - skip confirmations")
parser.add_argument("-k",
"--keyboard_layout",
type=str,
default="",
help="Preselect keyboard layout")
parser.add_argument("-s",
"--skip_reboot",
action="store_true",
help="Skip final messages and reboot")
parser.add_argument("-v",
"--version",
action="version",
version="%(prog)s version {}".format(__version__),
help="display Version information")
args = parser.parse_args()
if args.restore:
summary = []
items = ["sway", "nwg-panel", "nwg-drawer", "nwg-dock", "nwg-bar", "nwg-look", "swaync", "swaync", "foot",
"gtklock"]
if args.hypr:
items.append("hypr")
items.append("nwg-dock-hyprland")
for item in items:
r = restore(item, folder="config")
if r:
summary.append(r)
for item in ["nwg-look"]:
r = restore(item, folder="data")
if r:
summary.append(r)
if len(summary) > 0:
print("\n".join(summary))
sys.exit(0)
global shell_data
if not args.web:
print("\n*******************************************************************")
print(" This script installs/overwrites configs and style sheets ")
print(" for sway, Hyprland and nwg-shell components. ")
print(" The only backup that will be made is the main sway config file. ")
print(" This script should be used on a fresh Arch Linux installation. ")
print(" If you're running it on your existing setup, ")
print(" you're doing it at your own risk. ")
print("*******************************************************************")
a = input("\nProceed? y/N ")
else:
a = "Y"
if a.strip().upper() != "Y":
print("Installation cancelled")
sys.exit(0)
else:
if not os.path.isfile(shell_data_file):
# It must be a new installation. We need to init and save the shell data file.
if not os.path.isdir(os.path.join(data_home, "nwg-shell")):
os.makedirs(os.path.join(data_home, "nwg-shell"))
shell_data = {
"installed-version": __version__,
"updates": [__version__],
"interface-locale": ""
}
save_json(shell_data, shell_data_file)
else:
# Installing over existing install
shell_data = load_json(shell_data_file)
# We no longer need the pre-v0.3.0 "last-upgrade" key: delete it if found
if "last-upgrade" in shell_data:
del shell_data["last-upgrade"]
save_json(shell_data, shell_data_file)
# Backup sway config file (we may have none on fresh installation)
now = datetime.datetime.now()
new_name = now.strftime("config-backup-%Y%m%d-%H%M%S")
src = os.path.join(config_home, "sway/config")
dst = os.path.join(config_home, "sway/{}".format(new_name))
proceed = True
try:
if os.path.isfile(src):
copy(src, dst)
print("* Original sway config file copied to '{}'".format(new_name))
except Exception as e:
print("Error: {}".format(e))
a = input("Proceed with installation? y/N ")
proceed = a.strip().upper() == "Y"
if proceed:
skip = args.all or args.web
items = ["sway", "nwg-panel", "nwg-drawer", "nwg-dock", "nwg-bar", "nwg-look", "swaync", "foot", "gtklock",
"gtk-3.0"]
if args.hypr:
items.append("hypr")
items.append("nwg-dock-hyprland")
for item in items:
copy_from_skel(item, folder="config", skip_confirmation=skip, hyprland=args.hypr)
for item in ["nwg-look", "nwg-shell-config"]:
copy_from_skel(item, folder="data", skip_confirmation=skip, hyprland=args.hypr)
# Detect default apps and keyboard layout
shell_config_settings_file = os.path.join(data_home, "nwg-shell-config", "settings")
shell_config_settings = load_json(shell_config_settings_file)
shell_config_settings_hyprland_file = os.path.join(data_home, "nwg-shell-config", "settings-hyprland")
shell_config_settings_hyprland = load_json(shell_config_settings_hyprland_file)
settings = [shell_config_settings]
if args.hypr:
settings.append(shell_config_settings_hyprland)
# Set default apps, if found
for s in settings:
if "terminal" not in s or not s["terminal"]:
s["terminal"] = "foot"
if "file-manager" not in s or not s["file-manager"]:
for cmd in ["thunar", "caja", "dolphin", "nautilus", "nemo", "pcmanfm"]:
if is_command(cmd):
s["file-manager"] = cmd
break
if "editor" not in s or not s["editor"]:
for cmd in ["mousepad", "atom", "emacs", "gedit", "geany", "kate", "vim"]:
if is_command(cmd):
s["editor"] = cmd
break
if "browser" not in s or not s["browser"]:
for cmd in ["brave", "chromium", "google-chrome-stable", "epiphany", "falkon", "firefox", "konqueror",
"midori", "microsoft-edge-stable", "opera", "qutebrowser", "seamonkey", "surf",
"vivaldi-stable"]:
if is_command(cmd):
s["browser"] = browsers[cmd]
break
keymap = args.keyboard_layout
# Detect keyboard layout (requires systemd)
if not keymap and is_command("localectl"):
try:
lines = subprocess.check_output("localectl status", shell=True).decode("utf-8").strip().splitlines()
for line in lines:
if "VC Keymap" in line:
if "unset" not in line:
keymap = line.split()[-1]
break
except subprocess.CalledProcessError:
pass
if keymap:
shell_config_settings["keyboard-xkb-layout"] = keymap
if shell_config_settings_hyprland:
shell_config_settings_hyprland["input-kb_layout"] = keymap
save_json(shell_config_settings, shell_config_settings_file)
if args.hypr:
save_json(shell_config_settings_hyprland, shell_config_settings_hyprland_file)
# Copy default background
bcg = os.path.join(os.getenv("HOME"), "azotebg")
if not os.path.isfile(bcg):
print("Copying default background")
copy(os.path.join(dir_name, "skel", "stuff", "azotebg"), bcg)
os.rename(bcg, os.path.join(os.getenv("HOME"), ".azotebg"))
copy(os.path.join(os.getenv("HOME"), ".azotebg"), os.path.join(os.getenv("HOME"), ".azotebg-hyprland"))
if not args.skip_reboot:
if not args.web:
if is_command("Hyprland"):
print("\nThat's all. You may run sway or Hyprland now.\n")
else:
print("\nThat's all. You may run sway now.\n")
else:
print("Your computer will now restart...")
time.sleep(3)
if is_command("systemctl"):
subprocess.call("sudo systemctl reboot -f", shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
else:
subprocess.call("sudo reboot", shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
if __name__ == '__main__':
main()