From 0a24ffcef1b881dd549554fb0fe7bb26b9182ee6 Mon Sep 17 00:00:00 2001 From: Alex Selimov Date: Thu, 27 Aug 2026 23:56:32 -0400 Subject: [PATCH] Current working changes to configuration --- configuration.nix | 200 +++ hardware-configuration.nix | 45 + home/aselimov/firefox/firefox.nix | 21 + home/aselimov/firefox/user.js | 1319 +++++++++++++++++ home/aselimov/firefox/userChrome.css | 13 + home/aselimov/fuzzel.nix | 43 + home/aselimov/ghostty/config | 35 + home/aselimov/ghostty/themes/zenwritten-dark | 28 + home/aselimov/ghostty/themes/zenwritten-light | 28 + home/aselimov/home.nix | 125 ++ home/aselimov/mako.nix | 52 + home/aselimov/niri/config.kdl | 623 ++++++++ home/aselimov/theme/theme.nix | 91 ++ home/aselimov/theme/wall-dark.png | Bin 0 -> 73981 bytes home/aselimov/theme/wall-light.png | Bin 0 -> 75573 bytes home/aselimov/waybar/config.jsonc | 64 + home/aselimov/waybar/style-dark.css | 67 + home/aselimov/waybar/style-light.css | 67 + home/aselimov/waybar/style.css | 67 + home/aselimov/xremap.yml | 23 + home/neovim/neovim.nix | 22 + home/neovim/neovim/GoogleStyle.xml | 380 +++++ home/neovim/neovim/README.md | 26 + home/neovim/neovim/ftplugin/' | 8 + home/neovim/neovim/ftplugin/cpp.lua | 2 + home/neovim/neovim/ftplugin/cuda.lua | 2 + home/neovim/neovim/ftplugin/gitcommit.vim | 2 + home/neovim/neovim/ftplugin/haskell.lua | 2 + home/neovim/neovim/ftplugin/html.vim | 12 + home/neovim/neovim/ftplugin/java.lua | 4 + home/neovim/neovim/ftplugin/javascript.lua | 2 + home/neovim/neovim/ftplugin/mail.viim | 1 + home/neovim/neovim/ftplugin/markdown.vim | 20 + home/neovim/neovim/ftplugin/python.lua | 4 + home/neovim/neovim/ftplugin/sh.lua | 37 + home/neovim/neovim/ftplugin/tex.vim | 21 + home/neovim/neovim/ftplugin/typescript.lua | 2 + home/neovim/neovim/init.lua | 38 + home/neovim/neovim/lazy-lock.json | 50 + home/neovim/neovim/lua/autopair.lua | 62 + home/neovim/neovim/lua/config/autocmds.lua | 35 + home/neovim/neovim/lua/config/formatting.lua | 32 + home/neovim/neovim/lua/config/keymaps.lua | 20 + home/neovim/neovim/lua/config/lazy.lua | 16 + home/neovim/neovim/lua/config/options.lua | 44 + home/neovim/neovim/lua/iron.lua | 45 + home/neovim/neovim/lua/plugins/autopairs.lua | 59 + home/neovim/neovim/lua/plugins/completion.lua | 137 ++ home/neovim/neovim/lua/plugins/dev-tools.lua | 74 + home/neovim/neovim/lua/plugins/editor.lua | 23 + home/neovim/neovim/lua/plugins/formatting.lua | 40 + home/neovim/neovim/lua/plugins/iron.lua | 51 + home/neovim/neovim/lua/plugins/lsp.lua | 262 ++++ home/neovim/neovim/lua/plugins/telescope.lua | 36 + home/neovim/neovim/lua/plugins/ui.lua | 143 ++ home/neovim/neovim/lua/telescope_settings.lua | 5 + home/neovim/neovim/lua/todo-config.lua | 55 + home/neovim/neovim/lua/utils/statusline.lua | 15 + home/neovim/neovim/lua/workspace.lua | 25 + home/neovim/neovim/spell/en.utf-8.add | 52 + home/neovim/neovim/spell/en.utf-8.add.spl | Bin 0 -> 859 bytes home/root/home.nix | 13 + home/shell/.tmux.conf | 80 + home/shell/.zshrc | 225 +++ home/shell/shell.nix | 51 + home/shell/starship.toml | 166 +++ 66 files changed, 5312 insertions(+) create mode 100644 configuration.nix create mode 100644 hardware-configuration.nix create mode 100644 home/aselimov/firefox/firefox.nix create mode 100644 home/aselimov/firefox/user.js create mode 100644 home/aselimov/firefox/userChrome.css create mode 100644 home/aselimov/fuzzel.nix create mode 100644 home/aselimov/ghostty/config create mode 100644 home/aselimov/ghostty/themes/zenwritten-dark create mode 100644 home/aselimov/ghostty/themes/zenwritten-light create mode 100644 home/aselimov/home.nix create mode 100644 home/aselimov/mako.nix create mode 100644 home/aselimov/niri/config.kdl create mode 100644 home/aselimov/theme/theme.nix create mode 100644 home/aselimov/theme/wall-dark.png create mode 100644 home/aselimov/theme/wall-light.png create mode 100644 home/aselimov/waybar/config.jsonc create mode 100644 home/aselimov/waybar/style-dark.css create mode 100644 home/aselimov/waybar/style-light.css create mode 100644 home/aselimov/waybar/style.css create mode 100644 home/aselimov/xremap.yml create mode 100644 home/neovim/neovim.nix create mode 100644 home/neovim/neovim/GoogleStyle.xml create mode 100644 home/neovim/neovim/README.md create mode 100644 home/neovim/neovim/ftplugin/' create mode 100644 home/neovim/neovim/ftplugin/cpp.lua create mode 100644 home/neovim/neovim/ftplugin/cuda.lua create mode 100644 home/neovim/neovim/ftplugin/gitcommit.vim create mode 100644 home/neovim/neovim/ftplugin/haskell.lua create mode 100644 home/neovim/neovim/ftplugin/html.vim create mode 100644 home/neovim/neovim/ftplugin/java.lua create mode 100644 home/neovim/neovim/ftplugin/javascript.lua create mode 100644 home/neovim/neovim/ftplugin/mail.viim create mode 100644 home/neovim/neovim/ftplugin/markdown.vim create mode 100644 home/neovim/neovim/ftplugin/python.lua create mode 100644 home/neovim/neovim/ftplugin/sh.lua create mode 100644 home/neovim/neovim/ftplugin/tex.vim create mode 100644 home/neovim/neovim/ftplugin/typescript.lua create mode 100644 home/neovim/neovim/init.lua create mode 100644 home/neovim/neovim/lazy-lock.json create mode 100644 home/neovim/neovim/lua/autopair.lua create mode 100644 home/neovim/neovim/lua/config/autocmds.lua create mode 100644 home/neovim/neovim/lua/config/formatting.lua create mode 100644 home/neovim/neovim/lua/config/keymaps.lua create mode 100644 home/neovim/neovim/lua/config/lazy.lua create mode 100644 home/neovim/neovim/lua/config/options.lua create mode 100644 home/neovim/neovim/lua/iron.lua create mode 100644 home/neovim/neovim/lua/plugins/autopairs.lua create mode 100644 home/neovim/neovim/lua/plugins/completion.lua create mode 100644 home/neovim/neovim/lua/plugins/dev-tools.lua create mode 100644 home/neovim/neovim/lua/plugins/editor.lua create mode 100644 home/neovim/neovim/lua/plugins/formatting.lua create mode 100644 home/neovim/neovim/lua/plugins/iron.lua create mode 100644 home/neovim/neovim/lua/plugins/lsp.lua create mode 100644 home/neovim/neovim/lua/plugins/telescope.lua create mode 100644 home/neovim/neovim/lua/plugins/ui.lua create mode 100644 home/neovim/neovim/lua/telescope_settings.lua create mode 100644 home/neovim/neovim/lua/todo-config.lua create mode 100644 home/neovim/neovim/lua/utils/statusline.lua create mode 100644 home/neovim/neovim/lua/workspace.lua create mode 100644 home/neovim/neovim/spell/en.utf-8.add create mode 100644 home/neovim/neovim/spell/en.utf-8.add.spl create mode 100644 home/root/home.nix create mode 100644 home/shell/.tmux.conf create mode 100644 home/shell/.zshrc create mode 100644 home/shell/shell.nix create mode 100644 home/shell/starship.toml diff --git a/configuration.nix b/configuration.nix new file mode 100644 index 0000000..1267bc2 --- /dev/null +++ b/configuration.nix @@ -0,0 +1,200 @@ +# Edit this configuration file to define what should be installed on +# your system. Help is available in the configuration.nix(5) man page +# and in the NixOS manual (accessible by running ‘nixos-help’). + +{ config, pkgs, ... }: +#let +# sops-nix = builtins.fetchTarball { +# # Pin this to a real commit rather than master. +# url = "https://github.com/Mic92/sops-nix/archive/.tar.gz"; +# sha256 = ""; +# }; +#in +{ + imports = + [ # Include the results of the hardware scan. + ./hardware-configuration.nix + + #"${sops-nix}/modules/sops" + ]; + + # Bootloader. + boot.loader.systemd-boot.enable = true; + boot.loader.efi.canTouchEfiVariables = true; + + # Use latest kernel. + boot.kernelPackages = pkgs.linuxPackages_latest; + + networking.hostName = "nixos"; # Define your hostname. + networking.wireless.enable = true; # Enables wireless support via wpa_supplicant. + + # Configure network proxy if necessary + # networking.proxy.default = "http://user:password@proxy:port/"; + # networking.proxy.noProxy = "127.0.0.1,localhost,internal.domain"; + + # Enable networking + networking.networkmanager.enable = true; + + # Set your time zone. + time.timeZone = "America/New_York"; + + # Select internationalisation properties. + i18n.defaultLocale = "en_US.UTF-8"; + + i18n.extraLocaleSettings = { + LC_ADDRESS = "en_US.UTF-8"; + LC_IDENTIFICATION = "en_US.UTF-8"; + LC_MEASUREMENT = "en_US.UTF-8"; + LC_MONETARY = "en_US.UTF-8"; + LC_NAME = "en_US.UTF-8"; + LC_NUMERIC = "en_US.UTF-8"; + LC_PAPER = "en_US.UTF-8"; + LC_TELEPHONE = "en_US.UTF-8"; + LC_TIME = "en_US.UTF-8"; + }; + + # Configure keymap in X11 + services.xserver.xkb = { + layout = "us"; + variant = ""; + }; + + # Define a user account. Don't forget to set a password with ‘passwd’. + users.users."aselimov" = { + isNormalUser = true; + description = "Alex Selimov"; + extraGroups = [ "networkmanager" "wheel" "input" "uinput" ]; + packages = with pkgs; []; + openssh.authorizedKeys.keys = [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKhECKhmRPyFn63AXALvKEaRtvPQn6lWbA8p7jaymwSj alex@alexselimov.xyz" + ]; + }; + + # Allow unfree packages + nixpkgs.config.allowUnfree = true; + + # List packages installed in system profile. To search, run: + # $ nix search wget + environment.systemPackages = with pkgs; [ + htop + killall + pinentry-gnome3 + ]; + + # Some programs need SUID wrappers, can be configured further or are + # started in user sessions. + # programs.mtr.enable = true; + # programs.gnupg.agent = { + # enable = true; + # enableSSHSupport = true; + # }; + programs.niri.enable=true; + + services.displayManager.regreet = { + enable = true; + cageArgs = [ + "-s" + "-d" + "-m" + "last" + ]; + + settings = { + appearance = { + greeting_msg = "Welcome back!"; + }; + + widget.clock = { + format = "%a %H:%M"; + }; + }; + }; + programs.git.enable=true; + + services.keyd = { + enable = true; + keyboards = { + laptop = { + ids = [ "0001:0001"]; + + settings = { + main = { + leftalt = "leftmeta"; + leftmeta = "leftalt"; + }; + }; + }; + }; + }; + + programs.zsh.enable = true; + users.users = { + aselimov.shell = pkgs.zsh; + root.shell = pkgs.zsh; + }; + + # Changes needed to get unfied super+c copy + hardware.uinput.enable = true; + boot.kernelModules = [ + "uinput" + ]; + + services.udev.extraRules = '' + KERNEL=="uinput", GROUP="input", MODE:="0660", TAG+="uaccess", OPTIONS+="static_node=uinput" + ''; + + virtualisation.podman = { + enable = true; + dockerSocket.enable=true; + dockerCompat = true; + defaultNetwork.settings.dns_enabled = true; + }; + + # Set up doas + + security.sudo.enable = false; + security.doas = { + enable = true; + extraRules = [ + { + users = [ "aselimov" ]; + keepEnv = true; + persist = true; + } + ]; + }; + + security.sudo-rs.execWheelOnly = false; + + # List services that you want to enable: + + # Enable the OpenSSH daemon. + services.openssh.enable = true; + + # Open ports in the firewall. + # networking.firewall.allowedTCPPorts = [ ... ]; + # networking.firewall.allowedUDPPorts = [ ... ]; + # Or disable the firewall altogether. + # networking.firewall.enable = false; + + # This value determines the NixOS release from which the default + # settings for stateful data, like file locations and database versions + # on your system were taken. It‘s perfectly fine and recommended to leave + # this value at the release version of the first install of this system. + # Before changing this value read the documentation for this option + # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html). + system.stateVersion = "26.05"; # Did you read the comment? + + home-manager.useGlobalPkgs = true; + home-manager.useUserPackages = true; + + home-manager.users.aselimov = import ./home/aselimov/home.nix; + home-manager.users.root = import ./home/root/home.nix; + + # Getting gsettings working properly for systemd service + environment.sessionVariables.XDG_DATA_DIRS = [ + "${pkgs.gsettings-desktop-schemas}/share/gsettings-schemas/${pkgs.gsettings-desktop-schemas.name}" + "${pkgs.gtk3}/share/gsettings-schemas/${pkgs.gtk3.name}" + "${pkgs.gtk4}/share/gsettings-schemas/${pkgs.gtk4.name}" + ]; +} diff --git a/hardware-configuration.nix b/hardware-configuration.nix new file mode 100644 index 0000000..0f79d3b --- /dev/null +++ b/hardware-configuration.nix @@ -0,0 +1,45 @@ +# Do not modify this file! It was generated by ‘nixos-generate-config’ +# and may be overwritten by future invocations. Please make changes +# to /etc/nixos/configuration.nix instead. +{ config, lib, pkgs, modulesPath, ... }: + +{ + imports = + [ (modulesPath + "/installer/scan/not-detected.nix") + ]; + + boot.initrd.availableKernelModules = [ "xhci_pci" "nvme" "usb_storage" "sd_mod" ]; + boot.initrd.kernelModules = [ ]; + boot.kernelModules = [ "kvm-intel" ]; + boot.extraModulePackages = [ ]; + + fileSystems."/" = + { device = "/dev/mapper/luks-45dfbed5-ac16-4529-b851-2aa9a358e0b8"; + fsType = "btrfs"; + }; + + boot.initrd.luks.devices."luks-45dfbed5-ac16-4529-b851-2aa9a358e0b8".device = "/dev/disk/by-uuid/45dfbed5-ac16-4529-b851-2aa9a358e0b8"; + + fileSystems."/home" = + { device = "/dev/mapper/luks-45dfbed5-ac16-4529-b851-2aa9a358e0b8"; + fsType = "btrfs"; + options = [ "subvol=home" ]; + }; + + fileSystems."/nix" = + { device = "/dev/mapper/luks-45dfbed5-ac16-4529-b851-2aa9a358e0b8"; + fsType = "btrfs"; + options = [ "subvol=nix" ]; + }; + + fileSystems."/boot" = + { device = "/dev/disk/by-uuid/9364-7648"; + fsType = "vfat"; + options = [ "fmask=0077" "dmask=0077" ]; + }; + + swapDevices = [ ]; + + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; + hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; +} diff --git a/home/aselimov/firefox/firefox.nix b/home/aselimov/firefox/firefox.nix new file mode 100644 index 0000000..5096d46 --- /dev/null +++ b/home/aselimov/firefox/firefox.nix @@ -0,0 +1,21 @@ +{config, pkgs, ... }: +{ + programs.firefox = { + enable = true; + + profiles.default = { + id = 0; + isDefault = true; + preConfig = builtins.readFile ./user.js; + + settings = { + "browser.startup.page" = 3; + "toolkit.legacyUserProfileCustomizations.stylesheets" = true; + "sidebar.verticalTabs" = true; + "sidebar.revamp" = true; + }; + + userChrome = builtins.readFile ./userChrome.css; + }; + }; +} diff --git a/home/aselimov/firefox/user.js b/home/aselimov/firefox/user.js new file mode 100644 index 0000000..1e3ffb5 --- /dev/null +++ b/home/aselimov/firefox/user.js @@ -0,0 +1,1319 @@ +/****** +* name: arkenfox user.js +* date: 21 April 2026 +* version: 144 +* urls: https://github.com/arkenfox/user.js [repo] +* : https://arkenfox.github.io/gui/ [interactive] +* license: MIT: https://github.com/arkenfox/user.js/blob/master/LICENSE.txt + +* README: + + 1. Consider using Tor Browser if it meets your needs or fits your threat model + * https://2019.www.torproject.org/about/torusers.html + 2. Read the entire wiki + * https://github.com/arkenfox/user.js/wiki + 3. If you skipped step 2, return to step 2 + 4. Make changes in a user-overrides.js + * There are often trade-offs and conflicts between security vs privacy vs anti-tracking + and these need to be balanced against functionality & convenience & breakage + * Some site breakage and unintended consequences will happen. Everyone's experience will differ + e.g. some user data is erased on exit (section 2800), change this to suit your needs + * While not 100% definitive, search for "[SETUP" tags + 5. Some tag info + [SETUP-SECURITY] it's one item, read it + [SETUP-WEB] can cause some websites to break + [SETUP-CHROME] changes how Firefox itself behaves (i.e. not directly website related) + [SETUP-HARDEN] prefs you may like to add to overrides + 6. Override Recipes: https://github.com/arkenfox/user.js/issues/1080 + +* RELEASES: https://github.com/arkenfox/user.js/releases + + * Use the arkenfox release that matches your Firefox version + - DON'T wait for arkenfox to update Firefox, nothing major changes these days + * Each release + - run prefsCleaner to reset prefs made inactive, including deprecated (9999) + * ESR (Extended Support Release) + - It is recommended to not use the updater, or you will get a later version which may cause issues. + So you should manually append your overrides (and keep a copy), and manually update when you + change ESR releases (arkenfox is already past that release) + - If you decide to keep updating, then the onus is on you - also see section 9999 + +* INDEX: + + 0100: STARTUP + 0200: GEOLOCATION + 0300: QUIETER FOX + 0400: SAFE BROWSING + 0600: BLOCK IMPLICIT OUTBOUND + 0700: DNS / DoH / PROXY / SOCKS + 0800: LOCATION BAR / SEARCH BAR / SUGGESTIONS / HISTORY / FORMS + 0900: PASSWORDS / PASSKEYS + 1000: DISK AVOIDANCE + 1200: HTTPS (SSL/TLS / OCSP / CERTS / HPKP) + 1600: REFERERS + 1700: CONTAINERS + 2000: PLUGINS / MEDIA / WEBRTC + 2400: DOM (DOCUMENT OBJECT MODEL) + 2600: MISCELLANEOUS + 2700: ETP (ENHANCED TRACKING PROTECTION) + 2800: SHUTDOWN & SANITIZING + 4000: FPP (fingerprintingProtection) + 4500: OPTIONAL RFP (resistFingerprinting) + 5000: OPTIONAL OPSEC + 5500: OPTIONAL HARDENING + 6000: DON'T TOUCH + 7000: DON'T BOTHER + 8000: DON'T BOTHER: FINGERPRINTING + 8500: TELEMETRY + 9000: NON-PROJECT RELATED + 9999: DEPRECATED / RENAMED + +******/ + +/* START: internal custom pref to test for syntax errors + * [NOTE] Not all syntax errors cause parsing to abort i.e. reaching the last debug pref + * no longer necessarily means that all prefs have been applied. Check the console right + * after startup for any warnings/error messages related to non-applied prefs + * [1] https://blog.mozilla.org/nnethercote/2018/03/09/a-new-preferences-parser-for-firefox/ ***/ +user_pref( + "_user.js.parrot", + "START: Oh yes, the Norwegian Blue... what's wrong with it?", +); + +/* 0000: disable about:config warning ***/ +user_pref("browser.aboutConfig.showWarning", false); + +/*** [SECTION 0100]: STARTUP ***/ +user_pref("_user.js.parrot", "0100 syntax error: the parrot's dead!"); +/* 0102: set startup page [SETUP-CHROME] + * 0=blank, 1=home, 2=last visited page, 3=resume previous session + * [NOTE] Session Restore is cleared if history is also cleared (2811+), and not used in Private Browsing mode + * [SETTING] General>Startup>Restore previous session ***/ +user_pref("browser.startup.page", 0); +/* 0103: set HOME+NEWWINDOW page + * about:home=Firefox Home (default, see 0105), custom URLs..., Blank Page + * [SETTING] Home>New Windows and Tabs>Homepage and new windows ***/ +user_pref("browser.startup.homepage", "chrome://browser/content/blanktab.html"); +/* 0104: set NEWTAB page + * true=Firefox Home (default, see 0105), false=blank page + * [SETTING] Home>New Windows and Tabs>New tabs ***/ +user_pref("browser.newtabpage.enabled", false); +/* 0105: disable sponsored content on Firefox Home (Activity Stream) + * [SETTING] Home>Firefox Home Content ***/ +user_pref("browser.newtabpage.activity-stream.showSponsored", false); // [FF58+] Sponsored stories +user_pref("browser.newtabpage.activity-stream.showSponsoredTopSites", false); // [FF83+] Sponsored shortcuts +user_pref("browser.newtabpage.activity-stream.showSponsoredCheckboxes", false); // [FF140+] Support Firefox +/* 0106: clear default topsites + * [NOTE] This does not block you from adding your own ***/ +user_pref("browser.newtabpage.activity-stream.default.sites", ""); + +/*** [SECTION 0200]: GEOLOCATION ***/ +user_pref( + "_user.js.parrot", + "0200 syntax error: the parrot's definitely deceased!", +); +/* 0202: disable using the OS's geolocation service ***/ +user_pref("geo.provider.ms-windows-location", false); // [WINDOWS] +user_pref("geo.provider.use_corelocation", false); // [MAC] +user_pref("geo.provider.use_geoclue", false); // [FF102+] [LINUX] + +/*** [SECTION 0300]: QUIETER FOX ***/ +user_pref( + "_user.js.parrot", + "0300 syntax error: the parrot's not pinin' for the fjords!", +); +/** RECOMMENDATIONS ***/ +/* 0320: disable recommendation pane in about:addons (uses Google Analytics) ***/ +user_pref("extensions.getAddons.showPane", false); // [HIDDEN PREF] +/* 0321: disable recommendations in about:addons' Extensions and Themes panes [FF68+] ***/ +user_pref("extensions.htmlaboutaddons.recommendations.enabled", false); +/* 0322: disable personalized Extension Recommendations in about:addons and AMO [FF65+] + * [NOTE] This pref has no effect when Health Reports (8501) are disabled + * [SETTING] Privacy & Security>Firefox Data Collection and Use>Allow personalized extension recommendations + * [1] https://support.mozilla.org/kb/personalized-extension-recommendations ***/ +user_pref("browser.discovery.enabled", false); + +/** ACTIVITY STREAM ***/ +/* 0335: disable Firefox Home (Activity Stream) telemetry ***/ +user_pref("browser.newtabpage.activity-stream.feeds.telemetry", false); +user_pref("browser.newtabpage.activity-stream.telemetry", false); + +/** STUDIES ***/ +/* 0340: disable Studies + * [SETTING] Privacy & Security>Firefox Data Collection and Use>Install and run studies ***/ +user_pref("app.shield.optoutstudies.enabled", false); +/* 0341: disable Normandy/Shield [FF60+] + * Shield is a telemetry system that can push and test "recipes" + * [1] https://mozilla.github.io/normandy/ ***/ +user_pref("app.normandy.enabled", false); +user_pref("app.normandy.api_url", ""); + +/** CRASH REPORTS ***/ +/* 0350: disable Crash Reports ***/ +user_pref("breakpad.reportURL", ""); +user_pref("browser.tabs.crashReporting.sendReport", false); // [FF44+] +// user_pref("browser.crashReports.unsubmittedCheck.enabled", false); // [FF51+] [DEFAULT: false] +/* 0351: enforce no submission of backlogged Crash Reports [FF58+] + * [SETTING] Privacy & Security>Firefox Data Collection and Use>Send backlogged crash reports ***/ +user_pref("browser.crashReports.unsubmittedCheck.autoSubmit2", false); // [DEFAULT: false] + +/** OTHER ***/ +/* 0360: disable Captive Portal detection + * [1] https://www.eff.org/deeplinks/2017/08/how-captive-portals-interfere-wireless-security-and-privacy ***/ +user_pref("captivedetect.canonicalURL", ""); +user_pref("network.captive-portal-service.enabled", false); // [FF52+] +/* 0361: disable Network Connectivity checks [FF65+] + * [1] https://bugzilla.mozilla.org/1460537 ***/ +user_pref("network.connectivity-service.enabled", false); + +/*** [SECTION 0400]: SAFE BROWSING (SB) + SB has taken many steps to preserve privacy. If required, a full url is never sent + to Google, only a part-hash of the prefix, hidden with noise of other real part-hashes. + Firefox takes measures such as stripping out identifying parameters and since SBv4 (FF57+) + doesn't even use cookies. (#Turn on browser.safebrowsing.debug to monitor this activity). + + FF147+ uses SBv5 which incorporates Oblivous HTTP [5] and SBv5's local list mode [6] + + [1] https://feeding.cloud.geek.nz/posts/how-safe-browsing-works-in-firefox/ + [2] https://wiki.mozilla.org/Security/Safe_Browsing + [3] https://support.mozilla.org/kb/how-does-phishing-and-malware-protection-work + [4] https://educatedguesswork.org/posts/safe-browsing-privacy/ + [5] https://developers.google.com/safe-browsing/reference + [6] https://developers.google.com/safe-browsing/reference/Local.List.Mode +***/ +user_pref("_user.js.parrot", "0400 syntax error: the parrot's passed on!"); +/* 0401: disable SB (Safe Browsing) + * [WARNING] Do this at your own risk! These are the master switches + * [SETTING] Privacy & Security>Security>... Block dangerous and deceptive content ***/ +// user_pref("browser.safebrowsing.malware.enabled", false); +// user_pref("browser.safebrowsing.phishing.enabled", false); +/* 0402: disable SB checks for downloads (both local lookups + remote) + * This is the master switch for the safebrowsing.downloads* prefs (0403, 0404) + * [SETTING] Privacy & Security>Security>... "Block dangerous downloads" ***/ +// user_pref("browser.safebrowsing.downloads.enabled", false); +/* 0403: disable SB checks for downloads (remote) + * To verify the safety of certain executable files, Firefox may submit some information about the + * file, including the name, origin, size and a cryptographic hash of the contents, to the Google + * Safe Browsing service which helps Firefox determine whether or not the file should be blocked + * [SETUP-SECURITY] If you do not understand this, or you want this protection, then override this ***/ +user_pref("browser.safebrowsing.downloads.remote.enabled", false); +// user_pref("browser.safebrowsing.downloads.remote.url", ""); // Defense-in-depth +/* 0404: disable SB checks for unwanted software + * [SETTING] Privacy & Security>Security>... "Warn you about unwanted and uncommon software" ***/ +// user_pref("browser.safebrowsing.downloads.remote.block_potentially_unwanted", false); +// user_pref("browser.safebrowsing.downloads.remote.block_uncommon", false); +/* 0405: disable "ignore this warning" on SB warnings [FF45+] + * If clicked, it bypasses the block for that session. This is a means for admins to enforce SB + * [TEST] see https://github.com/arkenfox/user.js/wiki/Appendix-A-Test-Sites#-mozilla + * [1] https://bugzilla.mozilla.org/1226490 ***/ +// user_pref("browser.safebrowsing.allowOverride", false); + +/*** [SECTION 0600]: BLOCK IMPLICIT OUTBOUND [not explicitly asked for - e.g. clicked on] ***/ +user_pref("_user.js.parrot", "0600 syntax error: the parrot's no more!"); +/* 0601: disable link prefetching + * [1] https://developer.mozilla.org/docs/Web/HTTP/Link_prefetching_FAQ ***/ +user_pref("network.prefetch-next", false); +/* 0602: disable DNS prefetching + * [1] https://developer.mozilla.org/docs/Web/HTTP/Headers/X-DNS-Prefetch-Control ***/ +user_pref("network.dns.disablePrefetch", true); +user_pref("network.dns.disablePrefetchFromHTTPS", true); +/* 0604: disable link-mouseover opening connection to linked server + * [1] https://news.slashdot.org/story/15/08/14/2321202/how-to-quash-firefoxs-silent-requests ***/ +user_pref("network.http.speculative-parallel-limit", 0); +/* 0605: disable mousedown speculative connections on bookmarks and history [FF98+] ***/ +user_pref("browser.places.speculativeConnect.enabled", false); +/* 0610: enforce no "Hyperlink Auditing" (click tracking) + * [1] https://www.bleepingcomputer.com/news/software/major-browsers-to-prevent-disabling-of-click-tracking-privacy-risk/ ***/ +// user_pref("browser.send_pings", false); // [DEFAULT: false] + +/*** [SECTION 0700]: DNS / DoH / PROXY / SOCKS ***/ +user_pref( + "_user.js.parrot", + "0700 syntax error: the parrot's given up the ghost!", +); +/* 0702: set the proxy server to do any DNS lookups when using SOCKS + * e.g. in Tor, this stops your local DNS server from knowing your Tor destination + * as a remote Tor node will handle the DNS request + * [1] https://trac.torproject.org/projects/tor/wiki/doc/TorifyHOWTO/WebBrowsers ***/ +user_pref("network.proxy.socks_remote_dns", true); +/* 0703: disable using UNC (Uniform Naming Convention) paths [FF61+] + * [SETUP-CHROME] Can break extensions for profiles on network shares + * [1] https://bugzilla.mozilla.org/1413868 ***/ +user_pref("network.file.disable_unc_paths", true); // [HIDDEN PREF] +/* 0704: disable GIO as a potential proxy bypass vector + * Gvfs/GIO has a set of supported protocols like obex, network, archive, computer, + * dav, cdda, gphoto2, trash, etc. From FF87-117, by default only sftp was accepted + * [1] https://bugzilla.mozilla.org/1433507 + * [2] https://en.wikipedia.org/wiki/GVfs + * [3] https://en.wikipedia.org/wiki/GIO_(software) ***/ +user_pref("network.gio.supported-protocols", ""); // [HIDDEN PREF] [DEFAULT: ""] +/* 0705: disable proxy direct failover for system requests [FF91+] + * [WARNING] Default true is a security feature against malicious extensions [1] + * [SETUP-CHROME] If you use a proxy and you trust your extensions + * [1] https://blog.mozilla.org/security/2021/10/25/securing-the-proxy-api-for-firefox-add-ons/ ***/ +// user_pref("network.proxy.failover_direct", false); +/* 0706: disable proxy bypass for system request failures [FF95+] + * RemoteSettings, UpdateService, Telemetry [1] + * [WARNING] If false, this will break the fallback for some security features + * [SETUP-CHROME] If you use a proxy and you understand the security impact + * [1] https://bugzilla.mozilla.org/buglist.cgi?bug_id=1732792,1733994,1733481 ***/ +// user_pref("network.proxy.allow_bypass", false); +/* 0710: enable DNS-over-HTTPS (DoH) [FF60+] + * 0=default, 2=increased (TRR (Trusted Recursive Resolver) first), 3=max (TRR only), 5=off (no rollout) + * see "doh-rollout.home-region": USA 2019, Canada 2021, Russia/Ukraine 2022 [3] + * [SETTING] Privacy & Security>DNS over HTTPS + * [1] https://hacks.mozilla.org/2018/05/a-cartoon-intro-to-dns-over-https/ + * [2] https://wiki.mozilla.org/Security/DOH-resolver-policy + * [3] https://support.mozilla.org/kb/firefox-dns-over-https + * [4] https://www.eff.org/deeplinks/2020/12/dns-doh-and-odoh-oh-my-year-review-2020 ***/ +// user_pref("network.trr.mode", 3); +/* 0712: set DoH provider + * The custom uri is the value shown when you "Choose provider>Custom>" + * [NOTE] If you USE custom then "network.trr.uri" should be set the same + * [SETTING] Privacy & Security>DNS over HTTPS>Increased/Max>Choose provider ***/ +// user_pref("network.trr.uri", "https://example.dns"); +// user_pref("network.trr.custom_uri", "https://example.dns"); + +/*** [SECTION 0800]: LOCATION BAR / SEARCH BAR / SUGGESTIONS / HISTORY / FORMS ***/ +user_pref("_user.js.parrot", "0800 syntax error: the parrot's ceased to be!"); +/* 0801: disable location bar making speculative connections [FF56+] + * [1] https://bugzilla.mozilla.org/1348275 ***/ +user_pref("browser.urlbar.speculativeConnect.enabled", false); +/* 0802: disable location bar contextual suggestions + * [NOTE] The UI is controlled by the .enabled pref + * [SETTING] Search>Address Bar>Suggestions from... + * [1] https://blog.mozilla.org/data/2021/09/15/data-and-firefox-suggest/ ***/ +user_pref("browser.urlbar.quicksuggest.enabled", false); // [FF92+] +user_pref("browser.urlbar.suggest.quicksuggest.nonsponsored", false); // [FF95+] +user_pref("browser.urlbar.suggest.quicksuggest.sponsored", false); // [FF92+] +/* 0803: disable live search suggestions + * [NOTE] Both must be true for live search to work in the location bar + * [SETUP-CHROME] Override these if you trust and use a privacy respecting search engine + * [SETTING] Search>Show search suggestions | Show search suggestions in address bar results ***/ +user_pref("browser.search.suggest.enabled", false); +user_pref("browser.urlbar.suggest.searches", false); +/* 0805: disable urlbar trending search suggestions [FF118+] + * [SETTING] Search>Search Suggestions>Show trending search suggestions (FF119) ***/ +user_pref("browser.urlbar.trending.featureGate", false); +/* 0806: disable urlbar suggestions ***/ +user_pref("browser.urlbar.addons.featureGate", false); // [FF115+] +user_pref("browser.urlbar.amp.featureGate", false); // [FF141+] adMarketplace +user_pref("browser.urlbar.importantDates.featureGate", false); // [FF143+] +user_pref("browser.urlbar.market.featureGate", false); // [FF143+] stock market +user_pref("browser.urlbar.mdn.featureGate", false); // [FF117+] +user_pref("browser.urlbar.weather.featureGate", false); // [FF108+] +user_pref("browser.urlbar.wikipedia.featureGate", false); // [FF141+] +user_pref("browser.urlbar.yelp.featureGate", false); // [FF124+] +user_pref("browser.urlbar.yelpRealtime.featureGate", false); // [FF144+] +/* 0807: disable urlbar clipboard suggestions [FF118+] ***/ +// user_pref("browser.urlbar.clipboard.featureGate", false); +/* 0808: disable recent searches [FF120+] + * [NOTE] Recent searches are cleared if history is cleared (2811+) + * [1] https://support.mozilla.org/kb/search-suggestions-firefox ***/ +// user_pref("browser.urlbar.recentsearches.featureGate", false); +/* 0810: disable search and form history + * [NOTE] We also clear formdata on exit (2811+) + * [SETUP-WEB] Be aware that autocomplete form data can be read by third parties [1][2] + * [SETTING] Privacy & Security>History>Custom Settings>Remember search and form history + * [1] https://blog.mindedsecurity.com/2011/10/autocompleteagain.html + * [2] https://bugzilla.mozilla.org/381681 ***/ +user_pref("browser.formfill.enable", false); +/* 0815: disable tab-to-search [FF85+] + * Alternatively, you can exclude on a per-engine basis by unchecking them in Options>Search + * [SETTING] Search>Address Bar>When using the address bar, suggest>Search engines ***/ +// user_pref("browser.urlbar.suggest.engines", false); +/* 0820: disable coloring of visited links + * [SETUP-HARDEN] Bulk rapid history sniffing was mitigated in 2010 [1][2]. Slower and more expensive + * redraw timing attacks were largely mitigated in FF77+ [3]. Using RFP (4501) further hampers timing + * attacks. History can also be cleared on exit (2811+). However, social engineering [2#limits][4][5] + * and advanced targeted timing attacks could still produce usable results + * [1] https://developer.mozilla.org/docs/Web/CSS/Privacy_and_the_:visited_selector + * [2] https://dbaron.org/mozilla/visited-privacy + * [3] https://bugzilla.mozilla.org/1632765 + * [4] https://earthlng.github.io/testpages/visited_links.html (see github wiki APPENDIX A on how to use) + * [5] https://lcamtuf.blogspot.com/2016/08/css-mix-blend-mode-is-bad-for-keeping.html ***/ +// user_pref("layout.css.visited_links_enabled", false); +/* 0830: enable separate default search engine in Private Windows and its UI setting + * [SETTING] Search>Default Search Engine>Choose a different default search engine for Private Windows only ***/ +user_pref("browser.search.separatePrivateDefault", true); // [FF70+] +user_pref("browser.search.separatePrivateDefault.ui.enabled", true); // [FF71+] + +/*** [SECTION 0900]: PASSWORDS / PASSKEYS + [1] https://support.mozilla.org/kb/use-primary-password-protect-stored-logins-and-pas +***/ +user_pref("_user.js.parrot", "0900 syntax error: the parrot's expired!"); +/* 0903: disable auto-filling username & password form fields + * can leak in cross-site forms *and* be spoofed + * [NOTE] Username & password is still available when you enter the field + * [SETTING] Privacy & Security>Passwords>Autofill logins and passwords + * [1] https://freedom-to-tinker.com/2017/12/27/no-boundaries-for-user-identities-web-trackers-exploit-browser-login-managers/ + * [2] https://homes.esat.kuleuven.be/~asenol/leaky-forms/ ***/ +user_pref("signon.autofillForms", false); +/* 0904: disable formless login capture for Password Manager [FF51+] ***/ +user_pref("signon.formlessCapture.enabled", false); +/* 0905: limit (or disable) HTTP authentication credentials dialogs triggered by sub-resources [FF41+] + * hardens against potential credentials phishing + * 0 = don't allow sub-resources to open HTTP authentication credentials dialogs + * 1 = don't allow cross-origin sub-resources to open HTTP authentication credentials dialogs + * 2 = allow sub-resources to open HTTP authentication credentials dialogs (default) ***/ +user_pref("network.auth.subresource-http-auth-allow", 1); +/* 0906: enforce no automatic authentication on Microsoft sites [FF91+] [WINDOWS 10+] + * [SETTING] Privacy & Security>Logins and Passwords>Allow Windows single sign-on for... + * [1] https://support.mozilla.org/kb/windows-sso ***/ +// user_pref("network.http.windows-sso.enabled", false); // [DEFAULT: false] +/* 0907: enforce no automatic authentication on Microsoft sites [FF131+] [MAC] + * On macOS, SSO only works on corporate devices ***/ +// user_pref("network.http.microsoft-entra-sso.enabled", false); // [DEFAULT: false] +/* 0910: enforce no direct attestation in passkeys [FF144+] + // [1] https://bugzilla.mozilla.org/show_bug.cgi?id=1981587 ***/ +user_pref("security.webauthn.always_allow_direct_attestation", false); // [DEFAULT: false] + +/*** [SECTION 1000]: DISK AVOIDANCE ***/ +user_pref( + "_user.js.parrot", + "1000 syntax error: the parrot's gone to meet 'is maker!", +); +/* 1001: disable disk cache + * [NOTE] We also clear cache on exit (2811+) + * [SETUP-CHROME] If you think disk cache helps perf, then feel free to override this ***/ +user_pref("browser.cache.disk.enable", false); +/* 1002: set media cache in Private Browsing to in-memory and increase its maximum size + * [NOTE] MSE (Media Source Extensions) are already stored in-memory in PB ***/ +user_pref("browser.privatebrowsing.forceMediaMemoryCache", true); // [FF75+] +user_pref("media.memory_cache_max_size", 65536); +/* 1003: disable storing extra session data [SETUP-CHROME] + * define on which sites to save extra session data such as form content, cookies and POST data + * 0=everywhere, 1=unencrypted sites, 2=nowhere ***/ +user_pref("browser.sessionstore.privacy_level", 2); +/* 1005: disable automatic Firefox start and session restore after reboot [FF62+] [WINDOWS] + * [1] https://bugzilla.mozilla.org/603903 ***/ +user_pref("toolkit.winRegisterApplicationRestart", false); +/* 1006: disable favicons in shortcuts [WINDOWS] + * URL shortcuts use a cached randomly named .ico file which is stored in your + * profile/shortcutCache directory. The .ico remains after the shortcut is deleted + * If set to false then the shortcuts use a generic Firefox icon ***/ +user_pref("browser.shell.shortcutFavicons", false); + +/*** [SECTION 1200]: HTTPS (SSL/TLS / OCSP / CERTS / HPKP) + Your cipher and other settings can be used in server side fingerprinting + [TEST] https://www.ssllabs.com/ssltest/viewMyClient.html + [TEST] https://browserleaks.com/ssl + [TEST] https://ja3er.com/ + [1] https://www.securityartwork.es/2017/02/02/tls-client-fingerprinting-with-bro/ +***/ +user_pref("_user.js.parrot", "1200 syntax error: the parrot's a stiff!"); +/** SSL (Secure Sockets Layer) / TLS (Transport Layer Security) ***/ +/* 1201: require safe negotiation + * Blocks connections to servers that don't support RFC 5746 [2] as they're potentially vulnerable to a + * MiTM attack [3]. A server without RFC 5746 can be safe from the attack if it disables renegotiations + * but the problem is that the browser can't know that. Setting this pref to true is the only way for the + * browser to ensure there will be no unsafe renegotiations on the channel between the browser and the server + * [SETUP-WEB] SSL_ERROR_UNSAFE_NEGOTIATION: is it worth overriding this for that one site? + * [STATS] SSL Labs (Nov 2025) reports almost 99.85% of top sites have secure renegotiation [4] + * [1] https://wiki.mozilla.org/Security:Renegotiation + * [2] https://datatracker.ietf.org/doc/html/rfc5746 + * [3] https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2009-3555 + * [4] https://www.ssllabs.com/ssl-pulse/ ***/ +user_pref("security.ssl.require_safe_negotiation", true); +/* 1206: disable TLS1.3 0-RTT (round-trip time) [FF51+] + * This data is not forward secret, as it is encrypted solely under keys derived using + * the offered PSK. There are no guarantees of non-replay between connections + * [1] https://github.com/tlswg/tls13-spec/issues/1001 + * [2] https://www.rfc-editor.org/rfc/rfc9001.html#name-replay-attacks-with-0-rtt + * [3] https://blog.cloudflare.com/tls-1-3-overview-and-q-and-a/ ***/ +user_pref("security.tls.enable_0rtt_data", false); + +/** CERTS / HPKP (HTTP Public Key Pinning) ***/ +/* 1223: enable strict PKP (Public Key Pinning) + * 0=disabled, 1=allow user MiTM (default; such as your antivirus), 2=strict + * [SETUP-WEB] MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE ***/ +user_pref("security.cert_pinning.enforcement_level", 2); +/* 1224: enable CRLite [FF73+] + * 0 = disabled + * 1 = consult CRLite but only collect telemetry + * 2 = consult CRLite and enforce both "Revoked" and "Not Revoked" results (default) + * 3 = consult CRLite and enforce "Not Revoked" results, but defer to OCSP for "Revoked" (removed FF145) + * [1] https://bugzilla.mozilla.org/buglist.cgi?bug_id=1429800,1670985,1753071 + * [2] https://blog.mozilla.org/security/tag/crlite/ + * [3] https://hacks.mozilla.org/2025/08/crlite-fast-private-and-comprehensive-certificate-revocation-checking-in-firefox/ ***/ +user_pref("security.remote_settings.crlite_filters.enabled", true); // [DEFAULT: true] +user_pref("security.pki.crlite_mode", 2); // [DEFAULT: 2 FF142+] + +/** MIXED CONTENT ***/ +/* 1241: disable insecure passive content (such as images) on https pages ***/ +// user_pref("security.mixed_content.block_display_content", true); // Defense-in-depth (see 1244) +/* 1244: enable HTTPS-Only mode in all windows + * When the top-level is HTTPS, insecure subresources are also upgraded (silent fail) + * [SETTING] to add site exceptions: Padlock>HTTPS-Only mode>On (after "Continue to HTTP Site") + * [SETTING] Privacy & Security>HTTPS-Only Mode (and manage exceptions) + * [TEST] http://example.com [upgrade] + * [TEST] http://httpforever.com/ | http://http.rip [no upgrade] ***/ +user_pref("dom.security.https_only_mode", true); // [FF76+] +// user_pref("dom.security.https_only_mode_pbm", true); // [FF80+] +/* 1245: enable HTTPS-Only mode for local resources [FF77+] ***/ +// user_pref("dom.security.https_only_mode.upgrade_local", true); +/* 1246: disable HTTP background requests [FF82+] + * When attempting to upgrade, if the server doesn't respond within 3 seconds, Firefox sends + * a top-level HTTP request without path in order to check if the server supports HTTPS or not + * This is done to avoid waiting for a timeout which takes 90 seconds + * [1] https://bugzilla.mozilla.org/buglist.cgi?bug_id=1642387,1660945 ***/ +user_pref("dom.security.https_only_mode_send_http_background_request", false); + +/** UI (User Interface) ***/ +/* 1270: display warning on the padlock for "broken security" (if 1201 is false) + * Bug: warning padlock not indicated for subresources on a secure page! [2] + * [1] https://wiki.mozilla.org/Security:Renegotiation + * [2] https://bugzilla.mozilla.org/1353705 ***/ +user_pref("security.ssl.treat_unsafe_negotiation_as_broken", true); +/* 1272: display advanced information on Insecure Connection warning pages + * only works when it's possible to add an exception + * i.e. it doesn't work for HSTS discrepancies (https://subdomain.preloaded-hsts.badssl.com/) + * [TEST] https://expired.badssl.com/ ***/ +user_pref("browser.xul.error_pages.expert_bad_cert", true); + +/*** [SECTION 1600]: REFERERS + full URI: https://example.com:8888/foo/bar.html?id=1234 + scheme+host+port+path: https://example.com:8888/foo/bar.html + scheme+host+port: https://example.com:8888 + [1] https://feeding.cloud.geek.nz/posts/tweaking-referrer-for-privacy-in-firefox/ +***/ +user_pref("_user.js.parrot", "1600 syntax error: the parrot rests in peace!"); +/* 1602: control the amount of cross-origin information to send [FF52+] + * 0=send full URI (default), 1=scheme+host+port+path, 2=scheme+host+port ***/ +user_pref("network.http.referer.XOriginTrimmingPolicy", 2); + +/*** [SECTION 1700]: CONTAINERS ***/ +user_pref("_user.js.parrot", "1700 syntax error: the parrot's bit the dust!"); +/* 1701: enable Container Tabs and its UI setting [FF50+] + * [SETTING] General>Tabs>Enable Container Tabs + * https://wiki.mozilla.org/Security/Contextual_Identity_Project/Containers ***/ +user_pref("privacy.userContext.enabled", true); +user_pref("privacy.userContext.ui.enabled", true); +/* 1702: set behavior on "+ Tab" button to display container menu on left click [FF74+] + * [NOTE] The menu is always shown on long press and right click + * [SETTING] General>Tabs>Enable Container Tabs>Settings>Select a container for each new tab ***/ +// user_pref("privacy.userContext.newTabContainerOnLeftClick.enabled", true); +/* 1703: set external links to open in site-specific containers [FF123+] + * [SETUP-WEB] Depending on your container extension(s) and their settings + * true=Firefox will not choose a container (so your extension can) + * false=Firefox will choose the container/no-container (default) + * [1] https://bugzilla.mozilla.org/1874599 ***/ +// user_pref("browser.link.force_default_user_context_id_for_external_opens", true); + +/*** [SECTION 2000]: PLUGINS / MEDIA / WEBRTC ***/ +user_pref("_user.js.parrot", "2000 syntax error: the parrot's snuffed it!"); +/* 2002: force WebRTC inside the proxy [FF70+] ***/ +user_pref("media.peerconnection.ice.proxy_only_if_behind_proxy", true); +/* 2003: force a single network interface for ICE candidates generation [FF42+] + * When using a system-wide proxy, it uses the proxy interface + * [1] https://developer.mozilla.org/docs/Web/API/RTCIceCandidate + * [2] https://wiki.mozilla.org/Media/WebRTC/Privacy ***/ +user_pref("media.peerconnection.ice.default_address_only", true); +/* 2004: force exclusion of private IPs from ICE candidates [FF51+] + * [SETUP-HARDEN] This will protect your private IP even in TRUSTED scenarios after you + * grant device access, but often results in breakage on video-conferencing platforms ***/ +// user_pref("media.peerconnection.ice.no_host", true); +/* 2020: disable GMP (Gecko Media Plugins) + * [1] https://wiki.mozilla.org/GeckoMediaPlugins ***/ +// user_pref("media.gmp-provider.enabled", false); + +/*** [SECTION 2400]: DOM (DOCUMENT OBJECT MODEL) ***/ +user_pref( + "_user.js.parrot", + "2400 syntax error: the parrot's kicked the bucket!", +); +/* 2402: prevent scripts from moving and resizing open windows ***/ +user_pref("dom.disable_window_move_resize", true); + +/*** [SECTION 2600]: MISCELLANEOUS ***/ +user_pref( + "_user.js.parrot", + "2600 syntax error: the parrot's run down the curtain!", +); +/* 2603: remove temp files opened from non-PB windows with an external application + * [1] https://bugzilla.mozilla.org/buglist.cgi?bug_id=302433,1738574 ***/ +user_pref("browser.download.start_downloads_in_tmp_dir", true); // [FF102+] +user_pref("browser.helperApps.deleteTempFileOnExit", true); +/* 2606: disable UITour backend so there is no chance that a remote page can use it ***/ +user_pref("browser.uitour.enabled", false); +// user_pref("browser.uitour.url", ""); // Defense-in-depth +/* 2608: reset remote debugging to disabled + * [1] https://gitlab.torproject.org/tpo/applications/tor-browser/-/issues/16222 ***/ +user_pref("devtools.debugger.remote-enabled", false); // [DEFAULT: false] +/* 2615: disable websites overriding Firefox's keyboard shortcuts [FF58+] + * 0 (default) or 1=allow, 2=block + * [SETTING] to add site exceptions: Ctrl+I>Permissions>Override Keyboard Shortcuts ***/ +// user_pref("permissions.default.shortcuts", 2); +/* 2616: remove special permissions for certain mozilla domains [FF35+] + * [1] resource://app/defaults/permissions ***/ +user_pref("permissions.manager.defaultsUrl", ""); +/* 2619: use Punycode in Internationalized Domain Names to eliminate possible spoofing + * [SETUP-WEB] Might be undesirable for non-latin alphabet users since legitimate IDN's are also punycoded + * [TEST] https://www.xn--80ak6aa92e.com/ (www.apple.com) + * [1] https://wiki.mozilla.org/IDN_Display_Algorithm + * [2] https://en.wikipedia.org/wiki/IDN_homograph_attack + * [3] https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=punycode+firefox + * [4] https://www.xudongz.com/blog/2017/idn-phishing/ ***/ +user_pref("network.IDN_show_punycode", true); +/* 2620: enforce PDFJS, disable PDFJS scripting + * This setting controls if the option "Display in Firefox" is available in the setting below + * and by effect controls whether PDFs are handled in-browser or externally ("Ask" or "Open With") + * [WHY] pdfjs is lightweight, open source, and secure: In the last 10 years it has only had + * two known exploits, both in 2024: one 'Severe' and one 'Important' [1] + * It doesn't break "state separation" of browser content (by not sharing with OS, independent apps). + * It maintains disk avoidance and application data isolation. It's convenient. You can still save to disk. + * [NOTE] JS can still force a pdf to open in-browser by bundling its own code + * [SETUP-CHROME] You may prefer a different pdf reader for security/workflow reasons + * [SETTING] General>Applications>Portable Document Format (PDF) + * [1] https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=pdf.js+firefox ***/ +user_pref("pdfjs.disabled", false); // [DEFAULT: false] +user_pref("pdfjs.enableScripting", false); // [FF86+] +/* 2624: disable middle click on new tab button opening URLs or searches using clipboard [FF115+] ***/ +user_pref("browser.tabs.searchclipboardfor.middleclick", false); // [DEFAULT: false NON-LINUX] +/* 2630: disable content analysis by DLP (Data Loss Prevention) agents + * DLP agents are background processes on managed computers that allow enterprises to monitor locally running + * applications for data exfiltration events, which they can allow/block based on customer defined DLP policies. + * 0=Block all requests, 1=Warn on all requests (which lets the user decide), 2=Allow all requests + * [1] https://github.com/chromium/content_analysis_sdk ***/ +user_pref("browser.contentanalysis.enabled", false); // [FF121+] [DEFAULT: false] +user_pref("browser.contentanalysis.default_result", 0); // [FF127+] [DEFAULT: 0] +/* 2635: disable referrer and storage access for resources injected by content scripts [FF139+] ***/ +user_pref("privacy.antitracking.isolateContentScriptResources", true); +/* 2640: disable CSP Level 2 Reporting [FF140+] ***/ +user_pref("security.csp.reporting.enabled", false); + +/** DOWNLOADS ***/ +/* 2651: enable user interaction for security by always asking where to download + * [SETUP-CHROME] On Android this blocks longtapping and saving images + * [SETTING] General>Downloads>Always ask you where to save files ***/ +user_pref("browser.download.useDownloadDir", false); +/* 2652: disable downloads panel opening on every download [FF96+] ***/ +user_pref("browser.download.alwaysOpenPanel", false); +/* 2653: disable adding downloads to the system's "recent documents" list ***/ +user_pref("browser.download.manager.addToRecentDocs", false); +/* 2654: enable user interaction for security by always asking how to handle new mimetypes [FF101+] + * [SETTING] General>Files and Applications>What should Firefox do with other files ***/ +user_pref("browser.download.always_ask_before_handling_new_types", true); + +/** EXTENSIONS ***/ +/* 2660: limit allowed extension directories + * 1=profile, 2=user, 4=application, 8=system, 16=temporary, 31=all + * The pref value represents the sum: e.g. 5 would be profile and application directories + * [SETUP-CHROME] Breaks usage of files which are installed outside allowed directories + * [1] https://archive.is/DYjAM ***/ +user_pref("extensions.enabledScopes", 5); // [HIDDEN PREF] +// user_pref("extensions.autoDisableScopes", 15); // [DEFAULT: 15] +/* 2661: disable bypassing 3rd party extension install prompts [FF82+] + * [1] https://bugzilla.mozilla.org/buglist.cgi?bug_id=1659530,1681331 ***/ +user_pref("extensions.postDownloadThirdPartyPrompt", false); +/* 2662: disable webextension restrictions on certain mozilla domains (you also need 4503) [FF60+] + * [1] https://bugzilla.mozilla.org/buglist.cgi?bug_id=1384330,1406795,1415644,1453988 ***/ +// user_pref("extensions.webextensions.restrictedDomains", ""); + +/*** [SECTION 2700]: ETP (ENHANCED TRACKING PROTECTION) ***/ +user_pref( + "_user.js.parrot", + "2700 syntax error: the parrot's joined the bleedin' choir invisible!", +); +/* 2701: enable ETP Strict Mode [FF86+] + * ETP Strict Mode enables Total Cookie Protection (TCP) + * [NOTE] Adding site exceptions disables all ETP protections for that site and increases the risk of + * cross-site state tracking e.g. exceptions for SiteA and SiteB means PartyC on both sites is shared + * [1] https://blog.mozilla.org/security/2021/02/23/total-cookie-protection/ + * [SETTING] to add site exceptions: Urlbar>ETP Shield + * [SETTING] to manage site exceptions: Options>Privacy & Security>Enhanced Tracking Protection>Manage Exceptions ***/ +user_pref("browser.contentblocking.category", "strict"); // [HIDDEN PREF] +/* 2702: disable ETP web compat features (about:compat) [FF93+] + * [SETUP-HARDEN] Includes skip lists, heuristics (SmartBlock) and automatic grants + * Opener and redirect heuristics are granted for 30 days, see [3] + * [1] https://blog.mozilla.org/security/2021/07/13/smartblock-v2/ + * [2] https://hg.mozilla.org/mozilla-central/rev/e5483fd469ab#l4.12 + * [3] https://developer.mozilla.org/docs/Web/Privacy/State_Partitioning#storage_access_heuristics ***/ +// user_pref("privacy.antitracking.enableWebcompat", false); +/* 2705: set ETP Strict/Custom exception lists (FF141+) + [SETTING] Options>Privacy & Security>Enhanced Tracking Protection>Strict/Custom>Fix major [baseline] | minor [convenience] + [1] https://support.mozilla.org/en-US/kb/manage-enhanced-tracking-protection-exceptions + [2] https://etp-exceptions.mozilla.org/ ***/ +user_pref("privacy.trackingprotection.allow_list.baseline.enabled", true); // [DEFAULT: true] +user_pref("privacy.trackingprotection.allow_list.convenience.enabled", true); // [DEFAULT: true] + +/*** [SECTION 2800]: SHUTDOWN & SANITIZING + We enable sanitizeOnShutdown to help prevent 1st party website tracking across sessions. + We consider history/downloads, which are not accessible to websites, as orthogonal and exempt these + + [SETUP-HARDEN] to clear all history/downloads on close, add the appropriate overrides from 2800's +***/ +user_pref( + "_user.js.parrot", + "2800 syntax error: the parrot's bleedin' demised!", +); +/* 2810: enable Firefox to clear items on shutdown + * [NOTE] In FF129+ clearing "siteSettings" on shutdown (2811+), or manually via site data (2820+) and + * via history (2830), will no longer remove sanitize on shutdown "cookie and site data" site exceptions (2815) + * [SETTING] Privacy & Security>History>Custom Settings>Clear history when Firefox closes | Settings ***/ +user_pref("privacy.sanitize.sanitizeOnShutdown", true); + +/** SANITIZE ON SHUTDOWN: IGNORES "ALLOW" SITE EXCEPTIONS ***/ +/* 2811: set/enforce clearOnShutdown items (if 2810 is true) [SETUP-CHROME] [FF128+] ***/ +user_pref("privacy.clearOnShutdown_v2.cache", true); // [DEFAULT: true] +user_pref("privacy.clearOnShutdown_v2.historyFormDataAndDownloads", false); // [DEFAULT: true] +// user_pref("privacy.clearOnShutdown_v2.siteSettings", false); // [DEFAULT: false] +/* 2812: set/enforce clearOnShutdown items [FF136+] ***/ +user_pref("privacy.clearOnShutdown_v2.browsingHistoryAndDownloads", false); // [DEFAULT: true] +user_pref("privacy.clearOnShutdown_v2.downloads", false); // [HIDDEN] +user_pref("privacy.clearOnShutdown_v2.formdata", true); +/* 2813: set Session Restore to clear on shutdown (if 2810 is true) [FF34+] + * [NOTE] Not needed if Session Restore is not used (0102) or it is already cleared with history (2811+) + * [NOTE] If true, this prevents resuming from crashes (also see 5008) ***/ +// user_pref("privacy.clearOnShutdown.openWindows", true); + +/** SANITIZE ON SHUTDOWN: RESPECTS "ALLOW" SITE EXCEPTIONS ***/ +/* 2815: set "Cookies" and "Site Data" to clear on shutdown (if 2810 is true) [SETUP-CHROME] [FF128+] + * [NOTE] Exceptions: For cross-domain logins, add exceptions for both sites + * e.g. https://www.youtube.com (site) + https://accounts.google.com (single sign on) + * [WARNING] Be selective with what sites you "Allow", as they also disable partitioning (1767271) + * [SETTING] to add site exceptions: Ctrl+I>Permissions>Cookies>Allow (when on the website in question) + * [SETTING] to manage site exceptions: Options>Privacy & Security>Permissions>Settings ***/ +user_pref("privacy.clearOnShutdown_v2.cookiesAndStorage", true); + +/** SANITIZE SITE DATA: IGNORES "ALLOW" SITE EXCEPTIONS ***/ +/* 2820: set manual "Clear Data" items [SETUP-CHROME] [FF128+] + * Firefox remembers your last choices. This will reset them when you start Firefox + * [SETTING] Privacy & Security>Browser Privacy>Cookies and Site Data>Clear Data ***/ +user_pref("privacy.clearSiteData.cache", true); // [DEFAULT: true] +user_pref("privacy.clearSiteData.cookiesAndStorage", false); // keep false until it respects "allow" site exceptions +user_pref("privacy.clearSiteData.historyFormDataAndDownloads", false); +// user_pref("privacy.clearSiteData.siteSettings", false); +/* 2821: set manual "Clear Data" items [FF136+] ***/ +user_pref("privacy.clearSiteData.browsingHistoryAndDownloads", false); +user_pref("privacy.clearSiteData.formdata", true); + +/** SANITIZE HISTORY: IGNORES "ALLOW" SITE EXCEPTIONS ***/ +/* 2830: set manual "Clear History" items, also via Ctrl-Shift-Del [SETUP-CHROME] [FF128+] + * Firefox remembers your last choices. This will reset them when you start Firefox + * [SETTING] Privacy & Security>History>Custom Settings>Clear History ***/ +user_pref("privacy.clearHistory.cache", true); // [DEFAULT: true] +user_pref("privacy.clearHistory.cookiesAndStorage", false); +user_pref("privacy.clearHistory.historyFormDataAndDownloads", false); // [DEFAULT: true] +// user_pref("privacy.clearHistory.siteSettings", false); // [DEFAULT: false] +/* 2831: set manual "Clear History" items [FF136+] ***/ +user_pref("privacy.clearHistory.browsingHistoryAndDownloads", false); // [DEFAULT: true] +user_pref("privacy.clearHistory.formdata", true); + +/** SANITIZE MANUAL: TIMERANGE ***/ +/* 2840: set "Time range to clear" for "Clear Data" (2820+) and "Clear History" (2830+) + * Firefox remembers your last choice. This will reset the value when you start Firefox + * 0=everything, 1=last hour, 2=last two hours, 3=last four hours, 4=today + * [NOTE] Values 5 (last 5 minutes) and 6 (last 24 hours) are not listed in the dropdown, + * which will display a blank value, and are not guaranteed to work ***/ +user_pref("privacy.sanitize.timeSpan", 0); + +/*** [SECTION 4000]: FPP (fingerprintingProtection) + RFP (4501) overrides FPP + + In FF118+ FPP is on by default in private windows (4001) and in FF119+ is controlled + by ETP (2701). FPP will also use Remote Services in future to relax FPP protections + on a per site basis for compatibility (4004). + + https://searchfox.org/mozilla-central/source/toolkit/components/resistfingerprinting/RFPTargetsDefault.inc + https://support.mozilla.org/en-US/kb/firefox-protection-against-fingerprinting#w_how-does-each-protection-work + + [NOTE] RFPTargets + granular overrides are somewhat experimental and may produce unexpected results + - e.g. FrameRate can only be controlled per process, not per origin + + 1826408 - restrict to system fonts (kBaseFonts + kLangPackFonts) (Windows, Mac, some Linux) (FF119+) + 1928705: android (FF134+) + https://searchfox.org/mozilla-central/search?path=StandardFonts*.inc + 1858181 - subtly randomize canvas per eTLD+1, per session and per window-mode (FF120+) + 1887682 - use fdlibm's sin, cos and tan in jsmath (FF134+) + 1954194 - available screen resolution: return a fixed offset height from screen per platform when not full screen (FF143+) + 1984333 - hardwareConcurrency: less than 8 return 4 else return 8 (FF143+) + 1977836 - maxTouchPoints: return multi-touch as 5 (FF143+) + 1917607 - subtly randomize WebGL's readPixels (FF145+) +***/ +user_pref("_user.js.parrot", "4000 syntax error: the parrot's bereft of life!"); +/* 4001: enable FPP in PB mode [FF114+] + * [NOTE] In FF119+, FPP for all modes (7016) is enabled with ETP Strict (2701) ***/ +// user_pref("privacy.fingerprintingProtection.pbmode", true); // [DEFAULT: true] +/* 4002: set global FPP overrides [FF114+] + * uses "RFPTargets" [1] which despite the name these are not used by RFP + * e.g. "+AllTargets,-CSSPrefersColorScheme,-JSDateTimeUTC" = all targets but allow prefers-color-scheme and do not change timezone + * e.g. "-AllTargets,+CanvasRandomization,+JSDateTimeUTC" = no targets but do use FPP canvas and change timezone + * [NOTE] Not supported by arkenfox. Either use RFP or FPP at defaults + * [1] https://searchfox.org/mozilla-central/source/toolkit/components/resistfingerprinting/RFPTargets.inc ***/ +// user_pref("privacy.fingerprintingProtection.overrides", ""); +/* 4003: set granular FPP overrides + * JSON format: e.g."[{\"firstPartyDomain\": \"netflix.com\", \"overrides\": \"-CanvasRandomization,-FrameRate,\"}]" + * [NOTE] Not supported by arkenfox. Either use RFP or FPP at defaults ***/ +// user_pref("privacy.fingerprintingProtection.granularOverrides", ""); +/* 4004: disable remote FPP overrides [FF127+] ***/ +// user_pref("privacy.fingerprintingProtection.remoteOverrides.enabled", false); + +/*** [SECTION 4500]: OPTIONAL RFP (resistFingerprinting) + RFP overrides FPP (4000) + + FF128+ Arkenfox by default uses FPP (automatically enabled with ETP Strict). For most people + this is all you need. To use RFP instead, add RFP (4501) to your overrides, and optionally + add letterboxing (4504), spoof_english (4506), and WebGL (4520). + + RFP is an all-or-nothing buy in: you cannot pick and choose what parts you want + + [WARNING] DO NOT USE extensions to alter RFP protected metrics + + 418986 - limit window.screen & CSS media queries (FF41) + FF56 + 1333651 - spoof User Agent & Navigator API + JS: spoofed as Windows 10, OS X 10.15, Android 10, or Linux + HTTP Header: spoofed as Windows 10 or Android 10 until FF136 then matches JS spoof + 1369319 - disable device sensor API + 1369357 - disable site specific zoom + 1337161 - hide gamepads from content + 1372072 - spoof network information API as "unknown" when dom.netinfo.enabled = true + 1333641 - reduce fingerprinting in WebSpeech API + FF57 + 1369309 - spoof media statistics + 1382499 - reduce screen co-ordinate fingerprinting in Touch API + 1217290 & 1409677 - enable some fingerprinting resistance for WebGL + 1354633 - limit MediaError.message to a whitelist + FF58+ + 1372073 - spoof/block fingerprinting in MediaDevices API (FF59) + Spoof: enumerate devices as one "Internal Camera" and one "Internal Microphone" + Block: suppresses the ondevicechange event + 1039069 - warn when language prefs are not set to "en*" (FF59) + 1222285 & 1433592 - spoof keyboard events and suppress keyboard modifier events (FF59) + Spoofing mimics the content language of the document. Currently it only supports en-US. + Modifier events suppressed are SHIFT and both ALT keys. Chrome is not affected. + 1459089 - disable OS locale in HTTP Accept-Language headers (ANDROID) (FF62) + 1479239 - return "no-preference" with prefers-reduced-motion (FF63) + 1492766 - spoof pointerEvent.pointerid (FF65) + 1485266 - disable exposure of system colors to CSS or canvas (FF67) + 1494034 - return "light" with prefers-color-scheme (FF67) + 1564422 - spoof audioContext outputLatency (FF70) + 1595823 - return audioContext sampleRate as 44100 (FF72) + 1607316 - spoof pointer as coarse and hover as none (ANDROID) (FF74) + 1621433 - randomize canvas (previously FF58+ returned an all-white canvas) (FF78) + 1506364 - return "no-preference" with prefers-contrast (FF80) + 1653987 - limit font visibility to bundled and "Base Fonts" (Windows, Mac, some Linux) (FF80) + 1461454 - spoof smooth=true and powerEfficient=false for supported media in MediaCapabilities (FF82) + 531915 - use fdlibm's sin, cos and tan in jsmath (FF93, ESR91.1) + 1692609 - reduce JS timing precision to 16.67ms (previously FF55+ was 100ms) (FF102) + 1422237 - return "srgb" with color-gamut (FF110) + 1794628 - return "none" with inverted-colors (FF114) + 1787790 - normalize system fonts (FF128) + 1835987 - spoof timezone as Atlantic/Reykjavik (previously FF55+ was UTC) (FF128) + 1656377 - spoof pointerEvents azimuthAngle and altitudeAngle (FF131) + 1826051 & 1957658 & 2021715 - spoof/suppress Pointer Events, spoof maxTouchPoints (FF132, FF143/ESR140.2, FF150) + previously FF64+ (1363508) it always returned maxTouchPoints as 0 + FF132: 0 = mac | 10 = windows, linux, mobile + FF143: 0 = mac, linux | 10 = windows | 5 = mobile | no longer spoof touch PointerEvents | backported to ESR140.2 + FF150: 5 = linux + 1834307 - always use smooth scrolling (FF132) + 1918202 - spoof screen orientation based on spoofed screen size and platform (FF132) + previously FF50+ it always returned landscape-primary and an angle of 0 + 1390465 - load all subtitles in WebVTT (Video Text Tracks) (FF133) + 1873382 - make spoofed devicePixelRatio and CSS media queries match (FF133) + previously FF41+ devicePixelRatio was hardcoded as 1 and FF127+ as 2 + previously FF41+ CSS media queries were spoofed as zoom level at a devicePixelRatio of 1 + 1955425 - return 128 for WebGPU subgroupMaxSize (FF138) + 1966860 - spoof WebGL debug renderer info (FF140) + previously FF60+ it was disabled + 1781277 - return 10GiB for storage estimate until persistent-storage granted (FF142, ESR140.1) + 1972600 - spoof network connection for HTMLMediaElement preload (FF142, ESR140.1) + 1975851 - return true for navigator.onLine (FF142, ESR140.1) + 1973265 - disable WebCodecs API (FF142) + 1984333 - spoof navigator.hardwareConcurrency as 4 except mac return 8 (FF143) + previously FF55+ it returned 2 + 1999126 - enforce navigator.pdfViewerEnabled as true and plugins/mimeTypes as hard-coded values (FF147) +***/ +user_pref( + "_user.js.parrot", + "4500 syntax error: the parrot's popped 'is clogs", +); +/* 4501: enable RFP + * [NOTE] pbmode applies if true and the original pref is false + * [SETUP-WEB] RFP can cause some website breakage: mainly canvas, use a canvas site exception via the urlbar. + * RFP also has a few side effects: mainly that timezone is GMT, and websites will prefer light theme ***/ +// user_pref("privacy.resistFingerprinting", true); // [FF41+] +// user_pref("privacy.resistFingerprinting.pbmode", true); // [FF114+] +/* 4502: set RFP new window size max rounded values [FF55+] + * [SETUP-CHROME] sizes round down in hundreds: width to 200s and height to 100s, to fit your screen + * [1] https://bugzilla.mozilla.org/1330882 ***/ +user_pref("privacy.window.maxInnerWidth", 1600); +user_pref("privacy.window.maxInnerHeight", 900); +/* 4503: disable mozAddonManager Web API [FF57+] + * [NOTE] To allow extensions to work on AMO, you also need 2662 + * [1] https://bugzilla.mozilla.org/buglist.cgi?bug_id=1384330,1406795,1415644,1453988 ***/ +user_pref("privacy.resistFingerprinting.block_mozAddonManager", true); +/* 4504: enable letterboxing [FF67+] + * Dynamically resizes the inner window by applying margins in stepped ranges [2] + * If you use the dimension pref, then it will only apply those resolutions. + * The format is "width1xheight1, width2xheight2, ..." (e.g. "800x600, 1000x1000") + * [SETUP-WEB] This is independent of RFP (4501). If you're using RFP, but dislike the + * margins, then don't enable this pref, keeping in mind that it is effectively fingerprintable + * [WARNING] DO NOT USE: the dimension pref is only meant for testing + * [1] https://bugzilla.mozilla.org/1407366 + * [2] https://hg.mozilla.org/mozilla-central/rev/7211cb4f58ff#l5.13 ***/ +// user_pref("privacy.resistFingerprinting.letterboxing", true); // [HIDDEN PREF] +// user_pref("privacy.resistFingerprinting.letterboxing.dimensions", ""); // [HIDDEN PREF] +/* 4505: disable RFP by domain [FF91+] + * [NOTE]: The pref takes comma separated values: e.g. "*domain1.tld, *domain2.tld" + * Working domain examples: "arkenfox.github.io", "*github.io" + * Non-working domain examples: "https://arkenfox.github.io", "github.io", "*arkenfox.github.io" ***/ +// user_pref("privacy.resistFingerprinting.exemptedDomains", "*.example.invalid"); +/* 4506: disable RFP spoof english prompt [FF59+] + * 0=prompt, 1=disabled, 2=enabled + * [NOTE] When changing from value 2, preferred languages ('intl.accept_languages') is not reset. + * [SETUP-WEB] when enabled, sets 'en-US, en' for displaying pages and 'en-US' as locale. + * [SETTING] General>Language>Choose your preferred language for displaying pages>Choose>Request English... ***/ +user_pref("privacy.spoof_english", 1); +/* 4507: skip browser.startup.blankWindow if RFP is used [FF136+] ***/ +// user_pref("privacy.resistFingerprinting.skipEarlyBlankFirstPaint", true); // [DEFAULT: true] +/* 4510: enforce Contrast Control off [FF138+] + * 0=automatic, 1=off, 2=custom + * [SETTING] General>Language and Appearance>Contrast Control ***/ +// user_pref("browser.display.document_color_use", 1); // [DEFAULT: 1 NON-WINDOWS] +/* 4511: disable using system accent colors ***/ +user_pref("widget.non-native-theme.use-theme-accent", false); // [DEFAULT: false WINDOWS] +/* 4512: enforce links targeting new windows to open in a new tab instead + * 1=most recent window or tab, 2=new window, 3=new tab + * Stops malicious window sizes and some screen resolution leaks. + * You can still right-click a link and open in a new window + * [SETTING] General>Tabs>Open links in tabs instead of new windows + * [1] https://gitlab.torproject.org/tpo/applications/tor-browser/-/issues/9881 ***/ +user_pref("browser.link.open_newwindow", 3); // [DEFAULT: 3] +/* 4513: set all open window methods to abide by "browser.link.open_newwindow" (4512) + * [1] https://searchfox.org/mozilla-central/source/dom/tests/browser/browser_test_new_window_from_content.js ***/ +user_pref("browser.link.open_newwindow.restriction", 0); +/* 4520: disable WebGL (Web Graphics Library) ***/ +// user_pref("webgl.disabled", true); + +/*** [SECTION 5000]: OPTIONAL OPSEC + Disk avoidance, application data isolation, eyeballs... +***/ +user_pref( + "_user.js.parrot", + "5000 syntax error: the parrot's taken 'is last bow", +); +/* 5001: start Firefox in PB (Private Browsing) mode + * [NOTE] In this mode all windows are "private windows" and the PB mode icon is not displayed + * [NOTE] The P in PB mode can be misleading: it means no "persistent" disk state such as history, + * caches, searches, cookies, localStorage, IndexedDB etc (which you can achieve in normal mode). + * In fact, PB mode limits or removes the ability to control some of these, and you need to quit + * Firefox to clear them. PB is best used as a one off window (Menu>New Private Window) to provide + * a temporary self-contained new session. Close all private windows to clear the PB session. + * [SETTING] Privacy & Security>History>Custom Settings>Always use private browsing mode + * [1] https://wiki.mozilla.org/Private_Browsing + * [2] https://support.mozilla.org/kb/common-myths-about-private-browsing ***/ +// user_pref("browser.privatebrowsing.autostart", true); +/* 5002: disable memory cache + * capacity: -1=determine dynamically (default), 0=none, n=memory capacity in kibibytes ***/ +// user_pref("browser.cache.memory.enable", false); +// user_pref("browser.cache.memory.capacity", 0); +/* 5003: disable saving passwords + * [NOTE] This does not clear any passwords already saved + * [SETTING] Privacy & Security>Logins and Passwords>Ask to save logins and passwords for websites ***/ +// user_pref("signon.rememberSignons", false); +/* 5004: disable permissions manager from writing to disk [FF41+] [RESTART] + * [NOTE] This means any permission changes are session only + * [1] https://bugzilla.mozilla.org/967812 ***/ +// user_pref("permissions.memory_only", true); // [HIDDEN PREF] +/* 5005: disable intermediate certificate caching [FF41+] [RESTART] + * [NOTE] This affects login/cert/key dbs. The effect is all credentials are session-only. + * Saved logins and passwords are not available. Reset the pref and restart to return them ***/ +// user_pref("security.nocertdb", true); +/* 5006: disable favicons in history and bookmarks + * [NOTE] Stored as data blobs in favicons.sqlite, these don't reveal anything that your + * actual history (and bookmarks) already do. Your history is more detailed, so + * control that instead; e.g. disable history, clear history on exit, use PB mode + * [NOTE] favicons.sqlite is sanitized on Firefox close ***/ +// user_pref("browser.chrome.site_icons", false); +/* 5007: exclude "Undo Closed Tabs" in Session Restore ***/ +// user_pref("browser.sessionstore.max_tabs_undo", 0); +/* 5008: disable resuming session from crash + * [TEST] about:crashparent ***/ +// user_pref("browser.sessionstore.resume_from_crash", false); +/* 5009: disable "open with" in download dialog [FF50+] + * Application data isolation [1] + * [1] https://bugzilla.mozilla.org/1281959 ***/ +// user_pref("browser.download.forbid_open_with", true); +/* 5010: disable location bar suggestion types + * [SETTING] Search>Address Bar>When using the address bar, suggest ***/ +// user_pref("browser.urlbar.suggest.history", false); +// user_pref("browser.urlbar.suggest.bookmark", false); +// user_pref("browser.urlbar.suggest.openpage", false); +// user_pref("browser.urlbar.suggest.topsites", false); // [FF78+] +/* 5011: disable location bar dropdown + * This value controls the total number of entries to appear in the location bar dropdown ***/ +// user_pref("browser.urlbar.maxRichResults", 0); +/* 5012: disable location bar autofill + * [1] https://support.mozilla.org/kb/address-bar-autocomplete-firefox#w_url-autocomplete ***/ +// user_pref("browser.urlbar.autoFill", false); +/* 5013: disable browsing and download history + * [NOTE] We also clear history and downloads on exit (2811+) + * [SETTING] Privacy & Security>History>Custom Settings>Remember browsing and download history ***/ +// user_pref("places.history.enabled", false); +/* 5014: disable Windows jumplist [WINDOWS] ***/ +// user_pref("browser.taskbar.lists.enabled", false); +// user_pref("browser.taskbar.lists.frequent.enabled", false); +// user_pref("browser.taskbar.lists.recent.enabled", false); +// user_pref("browser.taskbar.lists.tasks.enabled", false); +/* 5016: discourage downloading to desktop + * 0=desktop, 1=downloads (default), 2=custom + * [SETTING] To set your custom default "downloads": General>Downloads>Save files to ***/ +// user_pref("browser.download.folderList", 2); +/* 5017: disable Form Autofill + * If .supportedCountries includes your region (browser.search.region) and .supported + * is "detect" (default), then the UI will show. Stored data is not secure, uses JSON + * [SETTING] Privacy & Security>Forms and Autofill>Autofill addresses + * [1] https://wiki.mozilla.org/Firefox/Features/Form_Autofill ***/ +// user_pref("extensions.formautofill.addresses.enabled", false); // [FF55+] +// user_pref("extensions.formautofill.creditCards.enabled", false); // [FF56+] +/* 5018: limit events that can cause a pop-up ***/ +// user_pref("dom.popup_allowed_events", "click dblclick mousedown pointerdown"); +/* 5019: disable page thumbnail collection ***/ +// user_pref("browser.pagethumbnails.capturing_disabled", true); // [HIDDEN PREF] +/* 5020: disable Windows native notifications and use app notications instead [FF111+] [WINDOWS] ***/ +// user_pref("alerts.useSystemBackend.windows.notificationserver.enabled", false); + +/*** [SECTION 5500]: OPTIONAL HARDENING + Not recommended. Overriding these can cause breakage and performance issues, + they are mostly fingerprintable, and the threat model is practically nonexistent +***/ +user_pref("_user.js.parrot", "5500 syntax error: this is an ex-parrot!"); +/* 5501: disable MathML (Mathematical Markup Language) [FF51+] + * [1] https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=mathml ***/ +// user_pref("mathml.disabled", true); // 1173199 +/* 5502: disable in-content SVG (Scalable Vector Graphics) [FF53+] + * [1] https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=firefox+svg ***/ +// user_pref("svg.disabled", true); // 1216893 +/* 5503: disable graphite + * [1] https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=firefox+graphite + * [2] https://en.wikipedia.org/wiki/Graphite_(SIL) ***/ +// user_pref("gfx.font_rendering.graphite.enabled", false); +/* 5504: disable asm.js [FF22+] + * [1] http://asmjs.org/ + * [2] https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=asm.js + * [3] https://rh0dev.github.io/blog/2017/the-return-of-the-jit/ ***/ +// user_pref("javascript.options.asmjs", false); +/* 5505: disable Ion and baseline JIT to harden against JS exploits [RESTART] + * [NOTE] When both Ion and JIT are disabled, and trustedprincipals + * is enabled, then Ion can still be used by extensions (1599226) + * [1] https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=firefox+jit + * [2] https://microsoftedge.github.io/edgevr/posts/Super-Duper-Secure-Mode/ ***/ +// user_pref("javascript.options.ion", false); +// user_pref("javascript.options.baselinejit", false); +// user_pref("javascript.options.jit_trustedprincipals", true); // [FF75+] [HIDDEN PREF] +/* 5506: disable WebAssembly [FF52+] + * Vulnerabilities [1] have increasingly been found, including those known and fixed + * in native programs years ago [2]. WASM has powerful low-level access, making + * certain attacks (brute-force) and vulnerabilities more possible + * [STATS] ~0.2% of websites, about half of which are for cryptomining / malvertising [2][3] + * [1] https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=wasm + * [2] https://spectrum.ieee.org/tech-talk/telecom/security/more-worries-over-the-security-of-web-assembly + * [3] https://www.zdnet.com/article/half-of-the-websites-using-webassembly-use-it-for-malicious-purposes ***/ +// user_pref("javascript.options.wasm", false); +/* 5507: disable rendering of SVG OpenType fonts ***/ +// user_pref("gfx.font_rendering.opentype_svg.enabled", false); +/* 5508: disable all DRM (Digital Rights Management) content (EME: Encryption Media Extension) + * Optionally hide the UI setting which also disables the DRM prompt + * [SETTING] General>DRM Content>Play DRM-controlled content + * [TEST] https://bitmovin.com/demos/drm + * [1] https://www.eff.org/deeplinks/2017/10/drms-dead-canary-how-we-just-lost-web-what-we-learned-it-and-what-we-need-do-next ***/ +// user_pref("media.eme.enabled", false); +// user_pref("browser.eme.ui.enabled", false); +/* 5509: disable IPv6 if using a VPN + * This is an application level fallback. Disabling IPv6 is best done at an OS/network + * level, and/or configured properly in system wide VPN setups. + * [NOTE] PHP defaults to IPv6 with "localhost". Use "php -S 127.0.0.1:PORT" + * [SETUP-WEB] PR_CONNECT_RESET_ERROR + * [TEST] https://ipleak.org/ + * [1] https://www.internetsociety.org/tag/ipv6-security/ (Myths 2,4,5,6) ***/ +// user_pref("network.dns.disableIPv6", true); +/* 5510: control when to send a cross-origin referer + * 0=always (default), 1=only if base domains match, 2=only if hosts match + * [NOTE] Will cause breakage: older modems/routers and some sites e.g banks, vimeo, icloud, instagram ***/ +// user_pref("network.http.referer.XOriginPolicy", 2); +/* 5511: set DoH bootstrap address [FF89+] + * Firefox uses the system DNS to initially resolve the IP address of your DoH server. + * When set to a valid, working value that matches your "network.trr.uri" (0712) Firefox + * won't use the system DNS. If the IP doesn't match then DoH won't work ***/ +// user_pref("network.trr.bootstrapAddr", "10.0.0.1"); // [HIDDEN PREF] + +/*** [SECTION 6000]: DON'T TOUCH ***/ +user_pref("_user.js.parrot", "6000 syntax error: the parrot's 'istory!"); +/* 6001: enforce Firefox blocklist + * [WHY] It includes updates for "revoked certificates" + * [1] https://blog.mozilla.org/security/2015/03/03/revoking-intermediate-certificates-introducing-onecrl/ ***/ +user_pref("extensions.blocklist.enabled", true); // [DEFAULT: true] +/* 6002: enforce no referer spoofing + * [WHY] Spoofing can affect CSRF (Cross-Site Request Forgery) protections ***/ +user_pref("network.http.referer.spoofSource", false); // [DEFAULT: false] +/* 6004: enforce a security delay on some confirmation dialogs such as install, open/save + * [1] https://www.squarefree.com/2004/07/01/race-conditions-in-security-dialogs/ ***/ +user_pref("security.dialog_enable_delay", 1000); // [DEFAULT: 1000] +/* 6008: enforce no First Party Isolation [FF51+] + * [WARNING] Replaced with network partitioning (FF85+) and TCP (2701), and enabling FPI + * disables those. FPI is no longer maintained except at Tor Project for Tor Browser's config ***/ +user_pref("privacy.firstparty.isolate", false); // [DEFAULT: false] +/* 6009: enforce SmartBlock shims (about:compat) [FF81+] + * [1] https://blog.mozilla.org/security/2021/03/23/introducing-smartblock/ ***/ +user_pref("extensions.webcompat.enable_shims", true); // [HIDDEN PREF] [DEFAULT: true] +/* 6010: enforce no TLS 1.0/1.1 downgrades + * [TEST] https://tls-v1-1.badssl.com:1010/ ***/ +user_pref("security.tls.version.enable-deprecated", false); // [DEFAULT: false] +/* 6011: enforce disabling of Web Compatibility Reporter [FF56+] + * Web Compatibility Reporter adds a "Report Site Issue" button to send data to Mozilla + * [WHY] To prevent wasting Mozilla's time with a custom setup ***/ +user_pref("extensions.webcompat-reporter.enabled", false); // [DEFAULT: false] +/* 6012: enforce Quarantined Domains [FF115+] + * [WHY] https://support.mozilla.org/kb/quarantined-domains ***/ +user_pref("extensions.quarantinedDomains.enabled", true); // [DEFAULT: true] +/* 6050: prefsCleaner: reset previously active items removed from arkenfox FF140+ ***/ +// user_pref("browser.display.use_system_colors", ""); +// user_pref("browser.urlbar.fakespot.featureGate", ""); +// user_pref("security.OCSP.enabled", ""); +// user_pref("security.OCSP.require", ""); + +/*** [SECTION 7000]: DON'T BOTHER ***/ +user_pref( + "_user.js.parrot", + "7000 syntax error: the parrot's pushing up daisies!", +); +/* 7001: disable APIs + * Location-Aware Browsing, Full Screen + * [WHY] The API state is easily fingerprintable. + * Geo is behind a prompt (7002). Full screen requires user interaction ***/ +// user_pref("geo.enabled", false); +// user_pref("full-screen-api.enabled", false); +/* 7002: set default permissions + * Location, Camera, Microphone, Notifications [FF58+] Virtual Reality [FF73+] + * 0=always ask (default), 1=allow, 2=block + * [WHY] These are fingerprintable via Permissions API, except VR. Just add site + * exceptions as allow/block for frequently visited/annoying sites: i.e. not global + * [SETTING] to add site exceptions: Ctrl+I>Permissions> + * [SETTING] to manage site exceptions: Options>Privacy & Security>Permissions>Settings ***/ +// user_pref("permissions.default.geo", 0); +// user_pref("permissions.default.camera", 0); +// user_pref("permissions.default.microphone", 0); +// user_pref("permissions.default.desktop-notification", 0); +// user_pref("permissions.default.xr", 0); // Virtual Reality +/* 7003: disable non-modern cipher suites [1] + * [WHY] Passive fingerprinting. Minimal/non-existent threat of downgrade attacks + * [1] https://browserleaks.com/ssl ***/ +// user_pref("security.ssl3.ecdhe_ecdsa_aes_128_sha", false); +// user_pref("security.ssl3.ecdhe_ecdsa_aes_256_sha", false); +// user_pref("security.ssl3.ecdhe_rsa_aes_128_sha", false); +// user_pref("security.ssl3.ecdhe_rsa_aes_256_sha", false); +// user_pref("security.ssl3.rsa_aes_128_gcm_sha256", false); // no PFS +// user_pref("security.ssl3.rsa_aes_256_gcm_sha384", false); // no PFS +// user_pref("security.ssl3.rsa_aes_128_sha", false); // no PFS +// user_pref("security.ssl3.rsa_aes_256_sha", false); // no PFS +/* 7004: control TLS versions + * [WHY] Passive fingerprinting and security ***/ +// user_pref("security.tls.version.min", 3); // [DEFAULT: 3] +// user_pref("security.tls.version.max", 4); +/* 7005: disable SSL session IDs [FF36+] + * [WHY] Passive fingerprinting and perf costs. These are session-only + * and isolated with network partitioning (FF85+) and/or containers ***/ +// user_pref("security.ssl.disable_session_identifiers", true); +/* 7007: referers + * [WHY] Only cross-origin referers (1602, 5510) matter ***/ +// user_pref("network.http.sendRefererHeader", 2); +// user_pref("network.http.referer.trimmingPolicy", 0); +/* 7008: set the default Referrer Policy [FF59+] + * 0=no-referer, 1=same-origin, 2=strict-origin-when-cross-origin, 3=no-referrer-when-downgrade + * [WHY] Defaults are fine. They can be overridden by a site-controlled Referrer Policy ***/ +// user_pref("network.http.referer.defaultPolicy", 2); // [DEFAULT: 2] +// user_pref("network.http.referer.defaultPolicy.pbmode", 2); // [DEFAULT: 2] +/* 7010: disable HTTP Alternative Services [FF37+] + * [WHY] Already isolated with network partitioning (FF85+) ***/ +// user_pref("network.http.altsvc.enabled", false); +/* 7011: disable website control over browser right-click context menu + * [WHY] Just use Shift-Right-Click ***/ +// user_pref("dom.event.contextmenu.enabled", false); +/* 7012: disable icon fonts (glyphs) and local fallback rendering + * [WHY] Breakage, font fallback is equivalency, also RFP + * [1] https://bugzilla.mozilla.org/789788 + * [2] https://gitlab.torproject.org/legacy/trac/-/issues/8455 ***/ +// user_pref("gfx.downloadable_fonts.enabled", false); // [FF41+] +// user_pref("gfx.downloadable_fonts.fallback_delay", -1); +/* 7013: disable Clipboard API + * [WHY] Fingerprintable. Breakage. Cut/copy/paste require user + * interaction, and paste is limited to focused editable fields ***/ +// user_pref("dom.event.clipboardevents.enabled", false); +/* 7014: disable System Add-on updates + * [WHY] It can compromise security. System addons ship with prefs, use those ***/ +// user_pref("extensions.systemAddon.update.enabled", false); // [FF62+] +// user_pref("extensions.systemAddon.update.url", ""); // [FF44+] +/* 7015: enable the DNT (Do Not Track) HTTP header + * [WHY] Fingerprintable. In FF141+ DNT is never enabled. DNT is slated for deprecation [1] + [NOTE] In FF140, DNT is enforced with Tracking Protection which is used in ETP Strict (2701) + [1] https://bugzilla.mozilla.org/1967420 ***/ +// user_pref("privacy.donottrackheader.enabled", true); +/* 7016: customize ETP settings + * [NOTE] FPP (fingerprintingProtection) is ignored when RFP (4501) is enabled + * [WHY] Arkenfox only supports strict (2701) which sets these at runtime ***/ +// user_pref("network.cookie.cookieBehavior", 5); // [DEFAULT: 5] +// user_pref("network.cookie.cookieBehavior.optInPartitioning", true); // [ETP FF132+] +// user_pref("network.http.referer.disallowCrossSiteRelaxingDefault", true); +// user_pref("network.http.referer.disallowCrossSiteRelaxingDefault.top_navigation", true); // [FF100+] +// user_pref("privacy.bounceTrackingProtection.mode", 1); // [FF131+] [ETP FF133+] +// user_pref("privacy.fingerprintingProtection", true); // [FF114+] [ETP FF119+] +// user_pref("privacy.partition.network_state.ocsp_cache", true); // [DEFAULT: true] +// user_pref("privacy.query_stripping.enabled", true); // [FF101+] +// user_pref("privacy.trackingprotection.enabled", true); +// user_pref("privacy.trackingprotection.socialtracking.enabled", true); +// user_pref("privacy.trackingprotection.cryptomining.enabled", true); // [DEFAULT: true] +// user_pref("privacy.trackingprotection.fingerprinting.enabled", true); // [DEFAULT: true] +/* 7017: disable service workers + * [WHY] Already isolated with TCP (2701) behind a pref ***/ +// user_pref("dom.serviceWorkers.enabled", false); +/* 7018: disable Web Notifications [FF22+] + * [WHY] Web Notifications are behind a prompt (7002) + * [1] https://blog.mozilla.org/en/products/firefox/block-notification-requests/ ***/ +// user_pref("dom.webnotifications.enabled", false); +/* 7019: disable Push Notifications [FF44+] + * [WHY] Website "push" requires subscription, and the API is required for CRLite (1224) + * [NOTE] To remove all subscriptions, reset "dom.push.userAgentID" + * [1] https://support.mozilla.org/kb/push-notifications-firefox ***/ +// user_pref("dom.push.enabled", false); +/* 7020: disable WebRTC (Web Real-Time Communication) + * [WHY] Firefox desktop uses mDNS hostname obfuscation and the private IP is never exposed until + * required in TRUSTED scenarios; i.e. after you grant device (microphone or camera) access + * [TEST] https://browserleaks.com/webrtc + * [1] https://groups.google.com/g/discuss-webrtc/c/6stQXi72BEU/m/2FwZd24UAQAJ + * [2] https://datatracker.ietf.org/doc/html/draft-ietf-mmusic-mdns-ice-candidates#section-3.1.1 ***/ +// user_pref("media.peerconnection.enabled", false); +/* 7021: enable GPC (Global Privacy Control) in non-PB windows + * [WHY] Passive and active fingerprinting. Mostly redundant with Tracking Protection + * in ETP Strict (2701) and sanitizing on close (2800s) ***/ +// user_pref("privacy.globalprivacycontrol.enabled", true); +/* 7022: bFPP (baselineFingerprintingProtection) [FF139+] + * [WHY] Arkenfox only supports ETP Strict (2701) which enables FPP browser-wide (normal and private + * browsing window contexts). If FPP is enabled in the same context as bFPP, FPP takes precedence. + // user_pref("privacy.baselineFingerprintingProtection", true); + // user_pref("privacy.baselineFingerprintingProtection.granularOverrides", ""); + // user_pref("privacy.baselineFingerprintingProtection.overrides", ""); + +/*** [SECTION 8000]: DON'T BOTHER: FINGERPRINTING + [WHY] They are insufficient for fingerprinting protection and do more harm than good + [WARNING] DO NOT USE: they can interfere with built-in solutions such as RFP and FPP +***/ +user_pref( + "_user.js.parrot", + "8000 syntax error: the parrot's crossed the Jordan", +); +/* 8001: prefsCleaner: reset items useless for anti-fingerprinting ***/ +// user_pref("browser.display.use_document_fonts", ""); +// user_pref("browser.zoom.siteSpecific", ""); +// user_pref("device.sensors.enabled", ""); +// user_pref("dom.enable_performance", ""); +// user_pref("dom.enable_resource_timing", ""); +// user_pref("dom.gamepad.enabled", ""); +// user_pref("dom.maxHardwareConcurrency", ""); +// user_pref("dom.w3c_touch_events.enabled", ""); +// user_pref("dom.webaudio.enabled", ""); +// user_pref("font.system.whitelist", ""); +// user_pref("general.appname.override", ""); +// user_pref("general.appversion.override", ""); +// user_pref("general.buildID.override", ""); +// user_pref("general.oscpu.override", ""); +// user_pref("general.platform.override", ""); +// user_pref("general.useragent.override", ""); +// user_pref("media.navigator.enabled", ""); +// user_pref("media.video_stats.enabled", ""); +// user_pref("media.webspeech.synth.enabled", ""); +// user_pref("ui.use_standins_for_native_colors", ""); +// user_pref("webgl.enable-debug-renderer-info", ""); + +/*** [SECTION 8500]: TELEMETRY + Arkenfox does not consider Firefox telemetry to be a privacy or security concern - comments below. + But since most arkenfox users prefer it disabled, we'll do that rather than cause overrides. + + Opt-out + - Telemetry is essential: a browser engine is a _very_ large complex beast costing billions to maintain + - Opt-in telemetry _does not_ work and results in data that is unrepresentative and may be misleading + Choice + - Every new profile on first use provides data collection/use policy and the abillty to opt-out + - It can be disabled at any time (Settings>Privacy & Security>Data Collection and Use) + Data + - no PII (Personally Identifiable Information) + - can be viewed in about:telemetry + - uses Prio [1][2][3], Glean [4], Oblivious HTTP [5][6] + + [1] https://crypto.stanford.edu/prio/ + [2] https://hacks.mozilla.org/2018/10/testing-privacy-preserving-telemetry-with-prio/ + [3] https://blog.mozilla.org/security/2019/06/06/next-steps-in-privacy-preserving-telemetry-with-prio/ + [4] https://firefox-source-docs.mozilla.org/toolkit/components/glean/index.html + [5] https://firefox-source-docs.mozilla.org/toolkit/components/glean/user/ohttp.html + [6] https://blog.mozilla.org/en/tag/oblivious-http/ +***/ +user_pref("_user.js.parrot", "8500 syntax error: the parrot's off the twig!"); +/* 8500: disable new data submission [FF41+] + * If disabled, no policy is shown or upload takes place, ever + * [1] https://bugzilla.mozilla.org/1195552 ***/ +user_pref("datareporting.policy.dataSubmissionEnabled", false); +/* 8501: disable Health Reports + * [SETTING] Privacy & Security>Firefox Data Collection and Use>Send technical... data ***/ +user_pref("datareporting.healthreport.uploadEnabled", false); +/* 8502: disable telemetry + * The "unified" pref affects the behavior of the "enabled" pref + * - If "unified" is false then "enabled" controls the telemetry module + * - If "unified" is true then "enabled" only controls whether to record extended data + * [NOTE] "toolkit.telemetry.enabled" is now LOCKED to reflect prerelease (true) or release builds (false) [2] + * [1] https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/telemetry/internals/preferences.html + * [2] https://medium.com/georg-fritzsche/data-preference-changes-in-firefox-58-2d5df9c428b5 ***/ +user_pref("toolkit.telemetry.unified", false); +user_pref("toolkit.telemetry.enabled", false); // see [NOTE] +user_pref("toolkit.telemetry.server", "data:,"); +user_pref("toolkit.telemetry.archive.enabled", false); +user_pref("toolkit.telemetry.newProfilePing.enabled", false); // [FF55+] +user_pref("toolkit.telemetry.shutdownPingSender.enabled", false); // [FF55+] +user_pref("toolkit.telemetry.updatePing.enabled", false); // [FF56+] +user_pref("toolkit.telemetry.bhrPing.enabled", false); // [FF57+] Background Hang Reporter +user_pref("toolkit.telemetry.firstShutdownPing.enabled", false); // [FF57+] +/* 8503: disable Telemetry Coverage + * [1] https://blog.mozilla.org/data/2018/08/20/effectively-measuring-search-in-firefox/ ***/ +user_pref("toolkit.telemetry.coverage.opt-out", true); // [HIDDEN PREF] +user_pref("toolkit.coverage.opt-out", true); // [FF64+] [HIDDEN PREF] +user_pref("toolkit.coverage.endpoint.base", ""); + +/*** [SECTION 9000]: NON-PROJECT RELATED ***/ +user_pref( + "_user.js.parrot", + "9000 syntax error: the parrot's cashed in 'is chips!", +); +/* 9001: disable welcome notices ***/ +user_pref("browser.startup.homepage_override.mstone", "ignore"); // [HIDDEN PREF] +/* 9002: disable General>Browsing>Recommend extensions/features as you browse [FF67+] ***/ +user_pref( + "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.addons", + false, +); +user_pref( + "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features", + false, +); +/* 9004: disable search terms [FF110+] + * [SETTING] Search>Search Bar>Use the address bar for search and navigation>Show search terms instead of URL... ***/ +user_pref("browser.urlbar.showSearchTerms.enabled", false); + +/*** [SECTION 9999]: DEPRECATED / RENAMED ***/ +user_pref( + "_user.js.parrot", + "9999 syntax error: the parrot's shuffled off 'is mortal coil!", +); +/* ESR140.x still uses all the following prefs +// [NOTE] replace the * with a slash in the line above to re-enable active ones +// FF148 +// 0603: disable predictor / prefetching + // [-] https://bugzilla.mozilla.org/2006028 +user_pref("network.predictor.enabled", false); // [DEFAULT: false FF144+] +user_pref("network.predictor.enable-prefetch", false); // [FF48+] [DEFAULT: false] +// ***/ + +/* END: internal custom pref to test for syntax errors ***/ +user_pref( + "_user.js.parrot", + "SUCCESS: No no he's not dead, he's, he's restin'!", +); diff --git a/home/aselimov/firefox/userChrome.css b/home/aselimov/firefox/userChrome.css new file mode 100644 index 0000000..15bfcd2 --- /dev/null +++ b/home/aselimov/firefox/userChrome.css @@ -0,0 +1,13 @@ +/* Niri provides window management */ +.titlebar-buttonbox-container { + display: none !important; +} + +.titlebar-spacer { + display: none !important; +} + +/* Slightly more compact top toolbar */ +#nav-bar { + min-height: 32px !important; +} diff --git a/home/aselimov/fuzzel.nix b/home/aselimov/fuzzel.nix new file mode 100644 index 0000000..5604572 --- /dev/null +++ b/home/aselimov/fuzzel.nix @@ -0,0 +1,43 @@ +{config, pkgs, ... }: +{ + programs.fuzzel.enable = true; + + xdg.configFile."fuzzel/fuzzel.ini".text = '' + include=~/.config/fuzzel/colors.ini + + [main] + font=DepartureMono Nerd Font:size=10 + prompt=❯ + width=45 + lines=5 + horizontal-pad=20 + vertical-pad=12 + inner-pad=8 + + [border] + width=2 + radius=12 + ''; + + xdg.configFile."fuzzel/themes/zenwritten-light.ini".text = '' + [colors] + background=eeeeeeff + text=353535ff + match=286486ff + selection=cfcfcfff + selection-text=353535ff + selection-match=286486ff + border=787878ff + ''; + + xdg.configFile."fuzzel/themes/zenwritten-dark.ini".text = '' + [colors] + background=1c1c1cff + text=b9b9b9ff + match=8ba4b0ff + selection=3a3a3aff + selection-text=eeeeeeff + selection-match=a8c2ceff + border=666666ff + ''; +} diff --git a/home/aselimov/ghostty/config b/home/aselimov/ghostty/config new file mode 100644 index 0000000..7815f18 --- /dev/null +++ b/home/aselimov/ghostty/config @@ -0,0 +1,35 @@ +# --- Basics +auto-update-channel = tip +font-family = "DepartureMono Nerd Font Mono" +font-size = 11 +background-opacity = 1.0 +window-decoration = false +confirm-close-surface = false + +# Auto light/dark theme pair that matches your two palettes below +#theme = dark:my-dark,light:my-light +theme = dark:zenwritten-dark,light:zenwritten-light + +# Font-size keybindings (match Ctrl+Shift+j/k from WezTerm) +keybind = all:ctrl+shift+k=increase_font_size:1 +keybind = all:ctrl+shift+j=decrease_font_size:1 + +# Optional: make the split divider visible (newer Ghostty) +# split-divider-color = #444444 + +keybind = cmd+shift+j=goto_split:next +keybind = cmd+shift+k=goto_split:previous +keybind = cmd+shift+s=new_split:down +keybind = cmd+shift+v=new_split:right +keybind = cmd+shift+e=equalize_splits + +keybind = cmd+shift+u=scroll_page_up +keybind = cmd+shift+d=scroll_page_down +split-inherit-working-directory = true + +keybind = cmd+shift+p=ignore + +confirm-close-surface = true +keybind = cmd+shift+t=close_surface +keybind = ctrl+shift+c=copy_to_clipboard +keybind = ctrl+shift+v=paste_from_clipboard diff --git a/home/aselimov/ghostty/themes/zenwritten-dark b/home/aselimov/ghostty/themes/zenwritten-dark new file mode 100644 index 0000000..e89834e --- /dev/null +++ b/home/aselimov/ghostty/themes/zenwritten-dark @@ -0,0 +1,28 @@ +# Colors = your WezTerm dark palette +foreground = #BBBBBB +background = #191919 +cursor-color = #BBBBBB +cursor-text = #191919 +selection-foreground = #191919 +selection-background = #BBBBBB +bold-color = "#fefefe" + +# ANSI 0..15 +palette = 0=#191919 +palette = 1=#DE6E7C +palette = 2=#819B69 +palette = 3=#B77E64 +palette = 4=#6099C0 +palette = 5=#B279A7 +palette = 6=#66A5AD +palette = 7=#BBBBBB +palette = 8=#3d3839 +palette = 9=#E8838F +palette = 10=#8BAE68 +palette = 11=#D68C67 +palette = 12=#61ABDA +palette = 13=#CF86C1 +palette = 14=#65B8C1 +palette = 15=#8e8e8e + +unfocused-split-fill=#BBBBBB diff --git a/home/aselimov/ghostty/themes/zenwritten-light b/home/aselimov/ghostty/themes/zenwritten-light new file mode 100644 index 0000000..b09907a --- /dev/null +++ b/home/aselimov/ghostty/themes/zenwritten-light @@ -0,0 +1,28 @@ +# Colors = your WezTerm light palette +foreground = #4F5e68 +background = #F0EDEC +cursor-color = #2c363c +cursor-text = #F0EDEC +selection-foreground = #F0EDEC +selection-background = #2c363c +bold-color = "#010101" + +# ANSI 0..15 +palette = 0=#F0EDEC +palette = 1=#A8334C +palette = 2=#4F6C31 +palette = 3=#944927 +palette = 4=#286486 +palette = 5=#88507D +palette = 6=#3B8992 +palette = 7=#2C363C +palette = 8=#CFC1BA +palette = 9=#94253E +palette = 10=#3F5A22 +palette = 11=#803D1C +palette = 12=#1D5573 +palette = 13=#7B3B70 +palette = 14=#2B747C +palette = 15=#4F5E68 + +unfocused-split-fill=#CFC1BA diff --git a/home/aselimov/home.nix b/home/aselimov/home.nix new file mode 100644 index 0000000..3201004 --- /dev/null +++ b/home/aselimov/home.nix @@ -0,0 +1,125 @@ +{config, pkgs, ... }: +let + xremap-niri = pkgs.rustPlatform.buildRustPackage rec { + pname = "xremap"; + version = "0.15.11"; + + src = pkgs.fetchFromGitHub { + owner = "xremap"; + repo = "xremap"; + rev = "v${version}"; + hash = "sha256-N5JItxzg0nU9hsW+fOEJ9FHiq6L0rt8jXvieefuHc5k="; + }; + + cargoHash = "sha256-45YN1ZjM485phmvMNOna/hXE+EorZcz3xLpTVZIWZn8="; + + buildFeatures = [ "niri" ]; + }; +in +{ + imports = [ + ../shell/shell.nix + ../neovim/neovim.nix + ./firefox/firefox.nix + ./theme/theme.nix + ./fuzzel.nix + ./mako.nix + ]; + home.username = "aselimov"; + home.homeDirectory = "/home/aselimov"; + home.stateVersion = "26.05"; + + programs.home-manager.enable = true; + programs.ghostty.enable = true; + programs.mpv = { + enable = true; + + config = { + save-position-on-quit = true; + cache = true; + demuxer-max-bytes = "500MiB"; + demuxer-max-back-bytes = "200MiB"; + demuxer-readahead-secs = 300; + hr-seek = false; + ytdl-format="bestvideo[vcodec^=avc1]+bestaudio/best"; + gpu-context = "wayland"; + }; + }; + home.packages = with pkgs; [ + ripgrep + waybar + mako + wl-clipboard + pavucontrol + xremap-niri + zip + unzip + glib + jujutsu + gnupg + codex + awww + imv + yt-dlp + pass + dino + ]; + home.pointerCursor = { + enable = true; + package = pkgs.phinger-cursors; + name = "phinger-cursors-dark"; + size = 16; + + gtk.enable = true; + }; + gtk = { + enable = true; + + theme = { + name = "Adwaita"; + package = pkgs.gnome-themes-extra; + }; + }; + + dconf.enable = true; + + xdg.configFile."niri/config.kdl".source = ./niri/config.kdl; + xdg.configFile."ghostty" = { + source = ./ghostty; + recursive = true; + }; + xdg.configFile."waybar" = { + source = ./waybar; + recursive = true; + }; + + xdg.configFile."xremap/config.yml".source = ./xremap.yml; + + home.sessionPath = [ + "$HOME/bin" + ]; + + systemd.user.services.xremap = { + Unit = { + Description = "xremap"; + }; + + Service = { + ExecStart = + "${xremap-niri}/bin/xremap --watch --ignore 'AT Translated Set 2 keyboard' %h/.config/xremap/config.yml"; + + Restart = "on-failure"; + RestartSec = 1; + }; +}; + + services.gpg-agent = { + enable = true; + + pinentry.package = pkgs.pinentry-gnome3; + + defaultCacheTtl = 86400; + maxCacheTtl = 86400; + }; + +} diff --git a/home/aselimov/mako.nix b/home/aselimov/mako.nix new file mode 100644 index 0000000..efde831 --- /dev/null +++ b/home/aselimov/mako.nix @@ -0,0 +1,52 @@ +{config, pgs, ... }: +{ + services.mako = { + enable = true; + + settings = { + anchor = "top-right"; + layer = "overlay"; + + width = 360; + height = 110; + margin = 12; + padding = 14; + + font = "JetBrainsMono Nerd Font 11"; + + border-size = 2; + border-radius = 10; + + icons = true; + max-icon-size = 48; + + markup = true; + actions = true; + + default-timeout = 5000; + }; + + extraConfig = '' + include=~/.config/mako/theme.conf + + [urgency=low] + default-timeout=3000 + + [urgency=critical] + default-timeout=0 + ''; + }; + xdg.configFile."mako/themes/zenwritten-light.conf".text = '' + background-color=#eeeeee + text-color=#353535 + border-color=#787878 + progress-color=over #286486 + ''; + + xdg.configFile."mako/themes/zenwritten-dark.conf".text = '' + background-color=#1c1c1c + text-color=#b9b9b9 + border-color=#666666 + progress-color=over #8ba4b0 + ''; +} diff --git a/home/aselimov/niri/config.kdl b/home/aselimov/niri/config.kdl new file mode 100644 index 0000000..0817de8 --- /dev/null +++ b/home/aselimov/niri/config.kdl @@ -0,0 +1,623 @@ +// This config is in the KDL format: https://kdl.dev +// "/-" comments out the following node. +// Check the wiki for a full description of the configuration: +// https://niri-wm.github.io/niri/Configuration:-Introduction + +// Input device configuration. +// Find the full list of options on the wiki: +// https://niri-wm.github.io/niri/Configuration:-Input +input { + keyboard { + xkb { + // You can set rules, model, layout, variant and options. + // For more information, see xkeyboard-config(7). + + // For example: + // layout "us,ru" + // options "grp:win_space_toggle,compose:ralt,ctrl:nocaps" + + // If this section is empty, niri will fetch xkb settings + // from org.freedesktop.locale1. You can control these using + // localectl set-x11-keymap. + options "caps:escape" + } + + // Enable numlock on startup, omitting this setting disables it. + numlock + } + + // Next sections include libinput settings. + // Omitting settings disables them, or leaves them at their default values. + // All commented-out settings here are examples, not defaults. + touchpad { + // off + tap + // dwt + // dwtp + // drag false + // drag-lock + // natural-scroll + // accel-speed 0.2 + // accel-profile "flat" + // scroll-method "two-finger" + // disabled-on-external-mouse + } + + mouse { + // off + // natural-scroll + accel-speed -1.0 + accel-profile "flat" + // scroll-method "no-scroll" + } + + trackpoint { + // off + // natural-scroll + // accel-speed 0.2 + // accel-profile "flat" + // scroll-method "on-button-down" + // scroll-button 273 + // scroll-button-lock + // middle-emulation + } + + // Uncomment this to make the mouse warp to the center of newly focused windows. + warp-mouse-to-focus + + // Focus windows and outputs automatically when moving the mouse into them. + // Setting max-scroll-amount="0%" makes it work only on windows already fully on screen. + // focus-follows-mouse max-scroll-amount="0%" +} + +// You can configure outputs by their name, which you can find +// by running `niri msg outputs` while inside a niri instance. +// The built-in laptop monitor is usually called "eDP-1". +// Find more information on the wiki: +// https://niri-wm.github.io/niri/Configuration:-Outputs +// Remember to uncomment the node by removing "/-"! +output "eDP-1" { + // Uncomment this line to disable this output. + // off + + // Resolution and, optionally, refresh rate of the output. + // The format is "x" or "x@". + // If the refresh rate is omitted, niri will pick the highest refresh rate + // for the resolution. + // If the mode is omitted altogether or is invalid, niri will pick one automatically. + // Run `niri msg outputs` while inside a niri instance to list all outputs and their modes. + mode "1920x1080@120.030" + + // You can use integer or fractional scale, for example use 1.5 for 150% scale. + scale 1 + + // Transform allows to rotate the output counter-clockwise, valid values are: + // normal, 90, 180, 270, flipped, flipped-90, flipped-180 and flipped-270. + transform "normal" + + // Position of the output in the global coordinate space. + // This affects directional monitor actions like "focus-monitor-left", and cursor movement. + // The cursor can only move between directly adjacent outputs. + // Output scale and rotation has to be taken into account for positioning: + // outputs are sized in logical, or scaled, pixels. + // For example, a 3840×2160 output with scale 2.0 will have a logical size of 1920×1080, + // so to put another output directly adjacent to it on the right, set its x to 1920. + // If the position is unset or results in an overlap, the output is instead placed + // automatically. + position x=0 y=0 +} + +cursor { + xcursor-theme "phinger-cursors-dark" + xcursor-size 24 +} + +// Settings that influence how windows are positioned and sized. +// Find more information on the wiki: +// https://niri-wm.github.io/niri/Configuration:-Layout +layout { + // Set gaps around windows in logical pixels. + gaps 16 + + // When to center a column when changing focus, options are: + // - "never", default behavior, focusing an off-screen column will keep at the left + // or right edge of the screen. + // - "always", the focused column will always be centered. + // - "on-overflow", focusing a column will center it if it doesn't fit + // together with the previously focused column. + center-focused-column "never" + + // You can customize the widths that "switch-preset-column-width" (Mod+R) toggles between. + preset-column-widths { + // Proportion sets the width as a fraction of the output width, taking gaps into account. + // For example, you can perfectly fit four windows sized "proportion 0.25" on an output. + // The default preset widths are 1/3, 1/2 and 2/3 of the output. + proportion 0.33333 + proportion 0.5 + proportion 0.66667 + + // Fixed sets the width in logical pixels exactly. + // fixed 1920 + } + + // You can also customize the heights that "switch-preset-window-height" (Mod+Ctrl+Shift+R) toggles between. + // preset-window-heights { } + + // You can change the default width of the new windows. + default-column-width { proportion 0.5; } + // If you leave the brackets empty, the windows themselves will decide their initial width. + // default-column-width {} + + // By default focus ring and border are rendered as a solid background rectangle + // behind windows. That is, they will show up through semitransparent windows. + // This is because windows using client-side decorations can have an arbitrary shape. + // + // If you don't like that, you should uncomment `prefer-no-csd` below. + // Niri will draw focus ring and border *around* windows that agree to omit their + // client-side decorations. + // + // Alternatively, you can override it with a window rule called + // `draw-border-with-background`. + + // You can change how the focus ring looks. + focus-ring { + // Uncomment this line to disable the focus ring. + // off + + // How many logical pixels the ring extends out from the windows. + width 4 + + // Colors can be set in a variety of ways: + // - CSS named colors: "red" + // - RGB hex: "#rgb", "#rgba", "#rrggbb", "#rrggbbaa" + // - CSS-like notation: "rgb(255, 127, 0)", rgba(), hsl() and a few others. + + // Color of the ring on the active monitor. + active-color "#7fc8ff" + + // Color of the ring on inactive monitors. + // + // The focus ring only draws around the active window, so the only place + // where you can see its inactive-color is on other monitors. + inactive-color "#505050" + + // You can also use gradients. They take precedence over solid colors. + // Gradients are rendered the same as CSS linear-gradient(angle, from, to). + // The angle is the same as in linear-gradient, and is optional, + // defaulting to 180 (top-to-bottom gradient). + // You can use any CSS linear-gradient tool on the web to set these up. + // Changing the color space is also supported, check the wiki for more info. + // + // active-gradient from="#80c8ff" to="#c7ff7f" angle=45 + + // You can also color the gradient relative to the entire view + // of the workspace, rather than relative to just the window itself. + // To do that, set relative-to="workspace-view". + // + // inactive-gradient from="#505050" to="#808080" angle=45 relative-to="workspace-view" + } + + // You can also add a border. It's similar to the focus ring, but always visible. + border { + // The settings are the same as for the focus ring. + // If you enable the border, you probably want to disable the focus ring. + off + + width 4 + active-color "#ffc87f" + inactive-color "#505050" + + // Color of the border around windows that request your attention. + urgent-color "#9b0000" + + // Gradients can use a few different interpolation color spaces. + // For example, this is a pastel rainbow gradient via in="oklch longer hue". + // + // active-gradient from="#e5989b" to="#ffb4a2" angle=45 relative-to="workspace-view" in="oklch longer hue" + + // inactive-gradient from="#505050" to="#808080" angle=45 relative-to="workspace-view" + } + + // You can enable drop shadows for windows. + shadow { + // Uncomment the next line to enable shadows. + // on + + // By default, the shadow draws only around its window, and not behind it. + // Uncomment this setting to make the shadow draw behind its window. + // + // Note that niri has no way of knowing about the CSD window corner + // radius. It has to assume that windows have square corners, leading to + // shadow artifacts inside the CSD rounded corners. This setting fixes + // those artifacts. + // + // However, instead you may want to set prefer-no-csd and/or + // geometry-corner-radius. Then, niri will know the corner radius and + // draw the shadow correctly, without having to draw it behind the + // window. These will also remove client-side shadows if the window + // draws any. + // + // draw-behind-window true + + // You can change how shadows look. The values below are in logical + // pixels and match the CSS box-shadow properties. + + // Softness controls the shadow blur radius. + softness 30 + + // Spread expands the shadow. + spread 5 + + // Offset moves the shadow relative to the window. + offset x=0 y=5 + + // You can also change the shadow color and opacity. + color "#0007" + } + + // Struts shrink the area occupied by windows, similarly to layer-shell panels. + // You can think of them as a kind of outer gaps. They are set in logical pixels. + // Left and right struts will cause the next window to the side to always be visible. + // Top and bottom struts will simply add outer gaps in addition to the area occupied by + // layer-shell panels and regular gaps. + struts { + // left 64 + // right 64 + // top 64 + // bottom 64 + } +} + +// Add lines like this to spawn processes at startup. +// Note that running niri as a session supports xdg-desktop-autostart, +// which may be more convenient to use. +// See the binds section below for more spawn examples. + +// This line starts waybar, a commonly used bar for Wayland compositors. +spawn-at-startup "sh" "-c" "systemctl --user import-environment XDG_DATA_DIRS WAYLAND_DISPLAY XDG_RUNTIME_DIR DBUS_SESSION_BUS_ADDRESS NIRI_SOCKET && systemctl --user restart awww.service wallpaper.service" +spawn-at-startup "systemctl" "--user" "restart" "xremap" +spawn-at-startup "waybar" + + +// To run a shell command (with variables, pipes, etc.), use spawn-sh-at-startup: +// spawn-sh-at-startup "qs -c ~/source/qs/MyAwesomeShell" + +hotkey-overlay { + // Uncomment this line to disable the "Important Hotkeys" pop-up at startup. + skip-at-startup +} + +// Uncomment this line to ask the clients to omit their client-side decorations if possible. +// If the client will specifically ask for CSD, the request will be honored. +// Additionally, clients will be informed that they are tiled, removing some client-side rounded corners. +// This option will also fix border/focus ring drawing behind some semitransparent windows. +// After enabling or disabling this, you need to restart the apps for this to take effect. +// prefer-no-csd + +// You can change the path where screenshots are saved. +// A ~ at the front will be expanded to the home directory. +// The path is formatted with strftime(3) to give you the screenshot date and time. +screenshot-path "~/Pictures/Screenshots/Screenshot from %Y-%m-%d %H-%M-%S.png" + +// You can also set this to null to disable saving screenshots to disk. +// screenshot-path null + +// Animation settings. +// The wiki explains how to configure individual animations: +// https://niri-wm.github.io/niri/Configuration:-Animations +animations { + // Uncomment to turn off all animations. + // off + + // Slow down all animations by this factor. Values below 1 speed them up instead. + // slowdown 3.0 +} + +// Window rules let you adjust behavior for individual windows. +// Find more information on the wiki: +// https://niri-wm.github.io/niri/Configuration:-Window-Rules + + +// Open the Firefox picture-in-picture player as floating by default. +window-rule { + // This app-id regular expression will work for both: + // - host Firefox (app-id is "firefox") + // - Flatpak Firefox (app-id is "org.mozilla.firefox") + match app-id=r#"firefox$"# title="^Picture-in-Picture$" + open-floating true +} + +// Example: block out two password managers from screen capture. +// (This example rule is commented out with a "/-" in front.) +/-window-rule { + match app-id=r#"^org\.keepassxc\.KeePassXC$"# + match app-id=r#"^org\.gnome\.World\.Secrets$"# + + block-out-from "screen-capture" + + // Use this instead if you want them visible on third-party screenshot tools. + // block-out-from "screencast" +} + +// Example: enable rounded corners for all windows. +// (This example rule is commented out with a "/-" in front.) +window-rule { + geometry-corner-radius 12 + clip-to-geometry true +} + +window-rule { + match app-id="^mpv$" + open-floating true + default-floating-position x=10 y=10 relative-to="top-right" + default-column-width { + fixed 700 + } + + default-window-height { + fixed 394 + } +} + +binds { + // Keys consist of modifiers separated by + signs, followed by an XKB key name + // in the end. To find an XKB name for a particular key, you may use a program + // like wev. + // + // "Mod" is a special modifier equal to Super when running on a TTY, and to Alt + // when running as a winit window. + // + // Most actions that you can bind here can also be invoked programmatically with + // `niri msg action do-something`. + + // Mod-Shift-/, which is usually the same as Mod-?, + // shows a list of important hotkeys. + Mod+Shift+Slash { show-hotkey-overlay; } + + // Suggested binds for running programs: terminal, app launcher, screen locker. + Mod+Shift+Return hotkey-overlay-title="Open a Terminal: ghostty" { spawn "ghostty"; } + Mod+P hotkey-overlay-title="Run an Application: fuzzel" { spawn "fuzzel"; } + Super+Alt+L hotkey-overlay-title="Lock the Screen: swaylock" { spawn "swaylock"; } + + // Example volume keys mappings for PipeWire & WirePlumber. + // The allow-when-locked=true property makes them work even when the session is locked. + // Using spawn-sh allows to pass multiple arguments together with the command. + // "-l 1.0" limits the volume to 100%. + XF86AudioRaiseVolume allow-when-locked=true { spawn-sh "wpctl set-volume @DEFAULT_AUDIO_SINK@ 0.1+ -l 1.0"; } + XF86AudioLowerVolume allow-when-locked=true { spawn-sh "wpctl set-volume @DEFAULT_AUDIO_SINK@ 0.1-"; } + XF86AudioMute allow-when-locked=true { spawn-sh "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle"; } + XF86AudioMicMute allow-when-locked=true { spawn-sh "wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle"; } + + // Example media keys mapping using playerctl. + // This will work with any MPRIS-enabled media player. + XF86AudioPlay allow-when-locked=true { spawn-sh "playerctl play-pause"; } + XF86AudioPause allow-when-locked=true { spawn-sh "playerctl play-pause"; } + XF86AudioStop allow-when-locked=true { spawn-sh "playerctl stop"; } + XF86AudioPrev allow-when-locked=true { spawn-sh "playerctl previous"; } + XF86AudioNext allow-when-locked=true { spawn-sh "playerctl next"; } + + // Example brightness key mappings for brightnessctl. + // You can use regular spawn with multiple arguments too (to avoid going through "sh"), + // but you need to manually put each argument in separate "" quotes. + XF86MonBrightnessUp allow-when-locked=true { spawn "brightnessctl" "--class=backlight" "set" "+10%"; } + XF86MonBrightnessDown allow-when-locked=true { spawn "brightnessctl" "--class=backlight" "set" "10%-"; } + + // Open/close the Overview: a zoomed-out view of workspaces and windows. + // You can also move the mouse into the top-left hot corner, + // or do a four-finger swipe up on a touchpad. + Mod+O repeat=false { toggle-overview; } + + Mod+Q repeat=false { close-window; } + + Mod+Left { focus-column-left; } + Mod+Down { focus-window-down; } + Mod+Up { focus-window-up; } + Mod+Right { focus-column-right; } + Mod+H { focus-column-left; } + Mod+L { focus-column-right; } + + Mod+Shift+Left { move-column-left; } + Mod+Shift+Down { move-window-down; } + Mod+Shift+Up { move-window-up; } + Mod+Shift+Right { move-column-right; } + Mod+Shift+H { move-column-left; } + Mod+Shift+L { move-column-right; } + + // Alternative commands that move across workspaces when reaching + // the first or last window in a column. + Mod+J { focus-window-or-workspace-down; } + Mod+K { focus-window-or-workspace-up; } + Mod+Shift+J { move-window-down-or-to-workspace-down; } + Mod+Shift+K { move-window-up-or-to-workspace-up; } + + Mod+Home { focus-column-first; } + Mod+End { focus-column-last; } + Mod+Ctrl+Home { move-column-to-first; } + Mod+Ctrl+End { move-column-to-last; } + + //Mod+Shift+Left { focus-monitor-left; } + //Mod+Shift+Down { focus-monitor-down; } + //Mod+Shift+Up { focus-monitor-up; } + //Mod+Shift+Right { focus-monitor-right; } + //Mod+Shift+H { focus-monitor-left; } + //Mod+Shift+J { focus-monitor-down; } + //Mod+Shift+K { focus-monitor-up; } + //Mod+Shift+L { focus-monitor-right; } + + //Mod+Shift+Ctrl+Left { move-column-to-monitor-left; } + //Mod+Shift+Ctrl+Down { move-column-to-monitor-down; } + //Mod+Shift+Ctrl+Up { move-column-to-monitor-up; } + //Mod+Shift+Ctrl+Right { move-column-to-monitor-right; } + //Mod+Shift+Ctrl+H { move-column-to-monitor-left; } + //Mod+Shift+Ctrl+J { move-column-to-monitor-down; } + //Mod+Shift+Ctrl+K { move-column-to-monitor-up; } + //Mod+Shift+Ctrl+L { move-column-to-monitor-right; } + + // Alternatively, there are commands to move just a single window: + // Mod+Shift+Ctrl+Left { move-window-to-monitor-left; } + // ... + + // And you can also move a whole workspace to another monitor: + // Mod+Shift+Ctrl+Left { move-workspace-to-monitor-left; } + // ... + + // Alternatively, there are commands to move just a single window: + // Mod+Ctrl+Page_Down { move-window-to-workspace-down; } + // ... + + // You can bind mouse wheel scroll ticks using the following syntax. + // These binds will change direction based on the natural-scroll setting. + // + // To avoid scrolling through workspaces really fast, you can use + // the cooldown-ms property. The bind will be rate-limited to this value. + // You can set a cooldown on any bind, but it's most useful for the wheel. + Mod+WheelScrollDown cooldown-ms=150 { focus-workspace-down; } + Mod+WheelScrollUp cooldown-ms=150 { focus-workspace-up; } + Mod+Ctrl+WheelScrollDown cooldown-ms=150 { move-column-to-workspace-down; } + Mod+Ctrl+WheelScrollUp cooldown-ms=150 { move-column-to-workspace-up; } + + Mod+WheelScrollRight { focus-column-right; } + Mod+WheelScrollLeft { focus-column-left; } + Mod+Ctrl+WheelScrollRight { move-column-right; } + Mod+Ctrl+WheelScrollLeft { move-column-left; } + + // Usually scrolling up and down with Shift in applications results in + // horizontal scrolling; these binds replicate that. + Mod+Shift+WheelScrollDown { focus-column-right; } + Mod+Shift+WheelScrollUp { focus-column-left; } + Mod+Ctrl+Shift+WheelScrollDown { move-column-right; } + Mod+Ctrl+Shift+WheelScrollUp { move-column-left; } + + // Similarly, you can bind touchpad scroll "ticks". + // Touchpad scrolling is continuous, so for these binds it is split into + // discrete intervals. + // These binds are also affected by touchpad's natural-scroll, so these + // example binds are "inverted", since we have natural-scroll enabled for + // touchpads by default. + // Mod+TouchpadScrollDown { spawn-sh "wpctl set-volume @DEFAULT_AUDIO_SINK@ 0.02+"; } + // Mod+TouchpadScrollUp { spawn-sh "wpctl set-volume @DEFAULT_AUDIO_SINK@ 0.02-"; } + + // You can refer to workspaces by index. However, keep in mind that + // niri is a dynamic workspace system, so these commands are kind of + // "best effort". Trying to refer to a workspace index bigger than + // the current workspace count will instead refer to the bottommost + // (empty) workspace. + // + // For example, with 2 workspaces + 1 empty, indices 3, 4, 5 and so on + // will all refer to the 3rd workspace. + Mod+1 { focus-workspace 1; } + Mod+2 { focus-workspace 2; } + Mod+3 { focus-workspace 3; } + Mod+4 { focus-workspace 4; } + Mod+5 { focus-workspace 5; } + Mod+6 { focus-workspace 6; } + Mod+7 { focus-workspace 7; } + Mod+8 { focus-workspace 8; } + Mod+9 { focus-workspace 9; } + Mod+Ctrl+1 { move-column-to-workspace 1; } + Mod+Ctrl+2 { move-column-to-workspace 2; } + Mod+Ctrl+3 { move-column-to-workspace 3; } + Mod+Ctrl+4 { move-column-to-workspace 4; } + Mod+Ctrl+5 { move-column-to-workspace 5; } + Mod+Ctrl+6 { move-column-to-workspace 6; } + Mod+Ctrl+7 { move-column-to-workspace 7; } + Mod+Ctrl+8 { move-column-to-workspace 8; } + Mod+Ctrl+9 { move-column-to-workspace 9; } + + // Alternatively, there are commands to move just a single window: + // Mod+Ctrl+1 { move-window-to-workspace 1; } + + // Switches focus between the current and the previous workspace. + // Mod+Tab { focus-workspace-previous; } + + // The following binds move the focused window in and out of a column. + // If the window is alone, they will consume it into the nearby column to the side. + // If the window is already in a column, they will expel it out. + Mod+BracketLeft { consume-or-expel-window-left; } + Mod+BracketRight { consume-or-expel-window-right; } + + // Consume one window from the right to the bottom of the focused column. + Mod+Comma { consume-window-into-column; } + // Expel the bottom window from the focused column to the right. + Mod+Period { expel-window-from-column; } + + // Cycle through widths set in preset-column-widths. + Mod+R { switch-preset-column-width; } + // Cycling through the presets in reverse order is also possible. + Mod+Shift+R { switch-preset-column-width-back; } + + Mod+Ctrl+Shift+R { switch-preset-window-height; } + Mod+Ctrl+R { reset-window-height; } + + Mod+F { maximize-column; } + Mod+Shift+F { fullscreen-window; } + + // While maximize-column leaves gaps and borders around the window, + // maximize-window-to-edges doesn't: the window expands to the edges of the screen. + // This bind corresponds to normal window maximizing, + // e.g. by double-clicking on the titlebar. + Mod+M { maximize-window-to-edges; } + + // Expand the focused column to space not taken up by other fully visible columns. + // Makes the column "fill the rest of the space". + Mod+Ctrl+F { expand-column-to-available-width; } + + Mod+C { center-column; } + + // Center all fully visible columns on screen. + Mod+Ctrl+C { center-visible-columns; } + + // Finer width adjustments. + // This command can also: + // * set width in pixels: "1000" + // * adjust width in pixels: "-5" or "+5" + // * set width as a percentage of screen width: "25%" + // * adjust width as a percentage of screen width: "-10%" or "+10%" + // Pixel sizes use logical, or scaled, pixels. I.e. on an output with scale 2.0, + // set-column-width "100" will make the column occupy 200 physical screen pixels. + Mod+Minus { set-column-width "-10%"; } + Mod+Equal { set-column-width "+10%"; } + + // Finer height adjustments when in column with other windows. + Mod+Shift+Minus { set-window-height "-10%"; } + Mod+Shift+Equal { set-window-height "+10%"; } + + // Move the focused window between the floating and the tiling layout. + Mod+U { toggle-window-floating; } + Mod+Shift+U { switch-focus-between-floating-and-tiling; } + + // Toggle tabbed column display mode. + // Windows in this column will appear as vertical tabs, + // rather than stacked on top of each other. + Mod+W { toggle-column-tabbed-display; } + + // Actions to switch layouts. + // Note: if you uncomment these, make sure you do NOT have + // a matching layout switch hotkey configured in xkb options above. + // Having both at once on the same hotkey will break the switching, + // since it will switch twice upon pressing the hotkey (once by xkb, once by niri). + // Mod+Space { switch-layout "next"; } + // Mod+Shift+Space { switch-layout "prev"; } + + Mod+S { screenshot; } + Ctrl+Print { screenshot-screen; } + Alt+Print { screenshot-window; } + + // Applications such as remote-desktop clients and software KVM switches may + // request that niri stops processing the keyboard shortcuts defined here + // so they may, for example, forward the key presses as-is to a remote machine. + // It's a good idea to bind an escape hatch to toggle the inhibitor, + // so a buggy application can't hold your session hostage. + // + // The allow-inhibiting=false property can be applied to other binds as well, + // which ensures niri always processes them, even when an inhibitor is active. + Mod+Escape allow-inhibiting=false { toggle-keyboard-shortcuts-inhibit; } + + // The quit action will show a confirmation dialog to avoid accidental exits. + Mod+Shift+E { quit; } + Ctrl+Alt+Delete { quit; } + + // Powers off the monitors. To turn them back on, do any input like + // moving the mouse or pressing any other key. + //Mod+Shift+P { power-off-monitors; } +} diff --git a/home/aselimov/theme/theme.nix b/home/aselimov/theme/theme.nix new file mode 100644 index 0000000..215bc61 --- /dev/null +++ b/home/aselimov/theme/theme.nix @@ -0,0 +1,91 @@ +{config, pkgs, ... }: +{ + home.file."bin/themer.sh" = { + executable = true; + text = '' + #!/usr/bin/env bash + + set_theme() { + scheme="$(${pkgs.glib}/bin/gsettings get \ + org.gnome.desktop.interface color-scheme)" + + if [[ "$scheme" == *dark* ]]; then + wallpaper="$HOME/media/pics/wall-dark.png" + fuzzel="$HOME/.config/fuzzel/themes/zenwritten-dark.ini" + mako="$HOME/.config/mako/themes/zenwritten-dark.conf" + tmux="$HOME/.config/tmux/themes/zenwritten-dark.conf" + keys=$':silent! let g:light_mode=0 | set background=dark | doautocmd ColorScheme | redraw!' + + else + wallpaper="$HOME/media/pics/wall-light.png" + fuzzel="$HOME/.config/fuzzel/themes/zenwritten-light.ini" + mako="$HOME/.config/mako/themes/zenwritten-light.conf" + tmux="$HOME/.config/tmux/themes/zenwritten-light.conf" + keys=$':silent! let g:light_mode=1 | set background=light | doautocmd ColorScheme | redraw!' + fi + + ${pkgs.awww}/bin/awww img "$wallpaper" \ + --transition-type fade \ + --transition-duration 0.4 + + ln -sf "$fuzzel" "$HOME/.config/fuzzel/colors.ini" + ln -sf "$mako" "$HOME/.config/mako/theme.conf" + ln -sf "$tmux" $HOME/.config/tmux/theme.conf + makoctl reload + tmux source-file ~/.config/tmux/theme.conf + for dir in "$XDG_RUNTIME_DIR-}" "/tmp" "$HOME/.local/state/nvim"; do + [ -d "$dir" ] || continue + find "$dir" -type s -name 'nvim*' 2>/dev/null | while read -r sock; do + timeout 1s nvim --server "$sock" --remote-send "$keys" >/dev/null 2>&1 || true + done + done + + } + + set_theme + + ${pkgs.glib}/bin/gsettings monitor \ + org.gnome.desktop.interface color-scheme | + while read -r _; do + set_theme + done + ''; + }; + home.file."media/pics/wall-light.png".source = ./wall-light.png; + home.file."media/pics/wall-dark.png".source = ./wall-dark.png; + + systemd.user.services.awww = { + Unit = { + Description = "Awww wallpaper daemon"; + After = [ "graphical-session.target" ]; + PartOf = [ "graphical-session.target" ]; + }; + + Service = { + ExecStart = "${pkgs.awww}/bin/awww-daemon"; + Restart = "on-failure"; + }; + + Install = { + WantedBy = [ "graphical-session.target" ]; + }; + }; + + systemd.user.services.themer = { + Unit = { + Description = "gsettings monitor to swap light/dark themes"; + After = [ "graphical-session.target" "awww.service"]; + Requires = [ "awww.service" ]; + PartOf = [ "graphical-session.target" ]; + }; + + Service = { + ExecStart = "/home/aselimov/bin/themer.sh"; + Restart = "always"; + }; + + Install = { + WantedBy = [ "graphical-session.target" ]; + }; + }; +} diff --git a/home/aselimov/theme/wall-dark.png b/home/aselimov/theme/wall-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..68cb7fb9acc25cf3d955684828b1d617273f25b3 GIT binary patch literal 73981 zcmZ5}30TbC+whr&DW!#yq*Owo6o&R1S+cbtJC#3avQgv)p}PC+eT*bjhd1VbSteD>t_cN%6N$S9oyh zy9|2N^Pq-Fui@`a^PKOSmoE7zRaDr={uJo-D3CY9D0rD?6*q0ubb07mD#A{ zA&X1@bng0<$9AmU?0oR3jR=Wk8EpEC8&0#r)D+n$fP)mtXPh{vb4yx=xSdT=JQc;_ zH4hNcivzg)#v1)*y~D;cnTqJbAOYY{{S#z*u6(oOip^tRwg@F9BTf1j6GR2e&cP;U z`hNZVy1Zos{keE?0Fxx~HhBVBW&BHGs@WRk;>{!He=+L2y>!Wn%Yi~NMQ+qfkA z>gJa5RrBHOJ95&qo1U>9Z@R5|nUc)XL_>QpRd0IH=kN;GOiDgva&BGAyn5EJ7iG$* z`F~6)^41PFY9@vW7`ickUyq`Hg0|=Oo$k~fFyZeX;|GD7C|k9kx{2N?N`F)G8q2u? zhQLooO5R_3V?L>9auN4LKk9wO4UOx(Vcp99bv#+1k7!&!`O|XOOuFeHFvGu1>i_Mi z)yIH3uQ27_^kXW%4lGRMsNy};`u7kj;}4YjKd_N(>W?>#{%2D|_q#7`f!8q0|JQ%71A~XmodXOW+b>+m?JUsYfBj-OCh*Rh zO^GNH12p2FO_%q?9_3@spnh5i*8y=<9HnP)mQ}D{k7A4x!EX)wm#y4u>wOGBZ}^AW zKA%o6wakR0b^FiYVJ`2{l}e@V*@`c&VFS8Pz;Bfd{`okBzc~?|1YpD~`hOi9c4^Ut zDN4Wst+`T&kct!F>oBCBW@Qx3tR3OwxPDS{Vx}CO4Dux2(Z3;YJ7Cv3x*w0H3o8Je z;JpBjn_fRSBgPwO1IEH_{XdZsv5sHx9X`C4)-QDeEOA@LNYBvSj}6@bIlQHDKTi`bh5N|Y0Y`<2 z{U0L5a`>R@pAWX;56t>M_~=WC!Y52<=l+ieUkvwk_B*de zF;WD8;qBYE|6a18O-pN*$;)Y{$s? z)t^Hm!on&zh4t9F$~1#P@bHx)a9(_}sV6>!U%6x0W|>wHLEb?rj|8bDpRzyL{VY%Q z7L0gvUqLo+`F((Aj+D2$Z#q58>O`Evs4dsejvPCZJ6s91NU6ZtQyBDSlYr5k2L57j zzp%zxY^ZKGV~YlJQ!;DxIbVS!33H^PoMjnKeVZ`n&7jr55{-RQIp{PTVH24V6}x;* zRwev!kBC(h!{jWHQt#Spw&VWh%2ym_s})#ejeffo;o>LRRPf@G@+#0rj{#B&1PjtA zB$zJ3pP@h?)Eg%~vG3@%_m^3k{8JwWpe7#uF|Yh=JpTdcf^TJCmI>ljN-3sM&UUmJ z`#()?-jP4`0}$FJC6&h9BAc@;&4_?|ZlKgXxxW9{YF_D%Tg^QqHMzWpj#%}O;S8D_ zoVX`@@lh3u+6K+0W&SNgkYIpRrv6Yy`n)sKQRhi`ECcS+z_w6isp^KDBt{bcU8JgUoe&gT> z8|e|lR&=hZ?Fo;FjLfnb=<3@~C8;;$Z8r+M@^$+ikcnSY9U-_tf_~$BeO8%cIj6$B zpGy7T6`jod5^h_`A074e=dT}jM|#1NR7`P@MaXU_Z7_W`O8B=|9P?aTY4W$^>(7f( zv2tBSgLy4$%n{p(W`=NqQCMwL(eZ^$MWIq{3`0ZeZFx5EO+!6J@e%L71Y86$LW88t zulg^*@wJ5zkHzqGHLwk}I%9h%Qj>7Cl z<71vHD{pqHg#l-C+s~*XeN~z*A2(LF&)K=<<||IcSsb?_#2H6R883WR*~p(m@Xncb zN^I_oWSJk&vvEi8UF5}BGyv55yx z{!bF6k~sKO<9@Z}MGzhZl=&P+JTn>>;i63Mmt(f-g({tm8h4P;Z@v_q z@K?I)*rHN61a8M=9fu zzMb3k+!hGHxU&`MN(mrJ^-k%%*k$}`ftEefag&oh;xTBF@|FRMiSX>I1vFDWT`)!6 z(#YyOxPkqb&Bvy&Qe>=~WROr#io`(8T;o|~YMD{vv>V4XiwhV6CJnwo1xS-UW&#Kt z+Y`HR#OX&~;GHu6$&rw~eD63`ims(o)jAyJ{j);&qhG-o%3Q%-afk*&2S(FkbzmK+ z{4txuyI3im7EbGAA*9$TH5Vq=n?n??4;cZj>%TVc2-LfmW=L>KQ;7cisny;B3!KY2 zASBSC0roiTopM?4oqfkbFSbalz)J3haIHLgFWt2cc8!-Lp95!s7tz%=K*D006vE}8 zUmt@KAgpyce(%daezDc;eY^T8uh_Fu8_m9(AfzQtc4n%c-FhZ07WDPS3Y>){C7v9C zLTMa%^$ROLCSUZ)P~BAr3i1#n1;Z;f> zhVY&W`a|a(oWHS~1mDZs0U@ubMO?x7IYonz@FWcyt2~IgwR);6M4;x;K*?({^fz3U_;QJ!J$7Xy$4xc)|)@sa`&HLq^oN z13UkM_o5jq^v!0Ax|&x0VBw&l(;~eL=*-ii8EblK<4A4^KSNoQ8@>dgJ#?LbH)Gms z&g#y=f66)jM#@iwO5m|VX`{jOd%YC^a@At)IUU8S`UZgoSjCspBSp{5M5nwo_OU`2 zQb{7>@oA=c1jLMNYVR>h*qy-@$Zo*IYu|_hq`}mYZGy&-H=IHjwkUrtCY%?Iqqqg~ zxB=jr!wYKzP_Q&2?mo%Vlsg+WD%vgtp;PoZyhv|Bl#3=eP$GKH_3i3NUg}c+g72~S z7KFSZQfDkZWTGNl1?nQL)i`vML1SooAe`uCutoRU9260UAzeS2TK_^lCelfu3^dA< z8mqp!OvPw054k-Io;FQxVGvuyF;on2))HSUw5C`QF8vP55BNK?(nbhzc`xH8AXjM& z5&@wnhM1y>G#B$wm4G|A7)Vm(K)d}dkmtkVC01!pSgAbJq~H$HqGDS$XNHMH}A(mEJ#HNyCW|Xc&=$TX&93;nFxemv;bZ;5J`%k(L z*qiWw1KbG`<=|{?c9i8Yr8;OSfipFuuS5okbZO?f?5AKUg$NTeSlgIHbHtUZ z^Z9wy+2^j|3YPA%x5~&|8hD0l+Cr9V}_rHM% zoskxoZ~+03Jh`-cHIt%>G|&~vOZ3Op&o&)0Qgn|txm3XfgwJIAMu1#~hFk=3Aee_g z^6^_T2F8?4{wIIs9-xrg76(~izrJ(gDH5<xJvmn|v3`wjx7PVRP~mUSOdWZXYyV`gyi8z(xn@%yRnI_Zyp-r~s4svP z5)0NbkCv!0K!ygc;C8Y3RKRp7onBaQsg2sGg1N2^gc?%cfxM~jxZ)KONfQ~_>Et*tD`YC0fixgJC{nuc?1_yK;TGegZz2;rZuQ_rx+FKUOR5z zk5f0R?7`p2VIFP`gQ4gK?PRo?11#U)Z9Lbvd#kz~2ZC^pmSVsF43Y_b zn(Xn+FQaT8Wb6AzH^}ah5H8B=Pq%_E;teOH=B3#>l*vX2v$`3bIy!Al-P6?5l`Qw`8K(};O5L$m;lcm1f@Z}m+1@m>XK4o#(el`uQ&z$+ z$^T#0V`UX^c8|vE=mv&3xY~}5D`?=W4Mg7vErsjTPd84`sSjG&iw0tnpPnWvHAAR| zj$ryuZ|ms&Z3@PJF_;lQaL4B=F0@Xpz3Dqs?ucC=66_?lSKLC8q9!N&wRkQ^9#ghn z!;jy%PqE`VR&l94+MR+4QU)17>UTJCCq*=NYZ8dHODwS)tTv0#(w`6WS(;T20R5Ny zQGvh~5uGG=n0R;e#?W8hjdI%gRqnlEcq!tj6KI+$8a>NydRZ)V{=1fU2-B)fF`(9J zG-eH5*X7s&)$eU>Q-$Sl$7{SAEruJ!agVvya z{*MQ-3F}%Dc1-?Bua9CrRE-~vufu9skk!3U5?vj!TY+@xjN}Yda%@8RjdwcDkHU%6 zo&^^gtjqRlHsMABx}N`m$(cux;MO!Y_27u;oUta?Lk9#Jh*8*vQIX140cbwCn@>WJoW9MeyG zmGFZgD5Cb#z4KX`}eUW_H{!jni(u$ z?z}AFg_`Xoq@3rq;P_KHM$g4Hn#Zsuza+BHo?nZ(rp6mK9+{xHAlJYVFY zPo!q8MVeE)H7+CWE8#zYD4HUk>lT6jw^KV(12D&j5%W0LCndAs`rO4^=isVYR43Ne zo0=f()4hi4oA>ToYR41?5NT-q<;y#W<39KLzSm%fqDe}nm^z}(;? zATaJAFmhe%FB)AaJ=JJJADXXtW6$9sS`jgTwRpU=<tk;9l5On*|m`5Fj!@XcX0^CJj|y)|vA<;Bf1# z_+}G&+D%>Em9);cT2*En)#`d^eF=Q=->omT^~=v*Vj;dKX|vw&TzTwwrkCH_xI@!; z-J`0CMuCW3oie5J2D)MNUZ2x%fC_9v@4w%6)5{yeAsU2>8Z{Zk(Nk=%PFt?kYCy=I zGS#Vc$33_2*bJ-!Em#a;oFDE)Kn!M0$Sttg8JYdlBGe6HRPz(SNHri8qzVB~&nnve z_uG9RZzqvimt9Rg(ACo&h`I8^CHuz@RJ?$xNY_z$hUv*RmHe|hWQoIu!Nca6W+cQ_ zrYNX%6W`2Po!zyXGHSc~k3#FgSkmpLnnOQMgJGCe76Sr>VM%3;Y+^a=>b&DCUOD1# zy&T za#Lcfu(2s*ZDq}eRw?*Q#gID@^~1pClClFY78l5(Blq%iV7KwuxEr&N7(YisE?Z(&Wy4G*ChIC8v-_7&7?dD4V)%w`+1#K=aSwAjWmxgdM2} zY7epHBw}iM_cQcf5IEfo8X$d#*up^*GZKbpO-r6In3x)ls!~=q&@=C9QG_Q}rhi4{ z12Ju)0Xi;zMg*@V&2HS4$EKJJu=#OB=Ft!5g{E?Z0wM!?$!E7>pEVg_|C>Lz$WuX| z-bJtEMmGRUo$yYBne_Pj>ui=LomdO*7fv-=-INPHk0P-qm%IPc&}Smf%sPVht47uF z5bjHaBR5PBq6cHE^s;TC8OsUrLaA&FN9=QFfc;%Gq0p9J8PohZBv5Z=GTEi$bKu?A zqoUNwMj-L%#lCGv-gnqXk`=CjSTcl${T#aJKf-=kCugQ~#dO@P(ty;9I>(~b4bpec zBu#D;DcSL^?lJ8?NWS(f8$8l`Y(~PN%9O8~v^5cA7OuXuV{PJ}T>+q!6ceG0qX8}U zUnIW~j7zsz6i@aD`GKi^dR&xWa3|8NuRATRSb9J)gi_7MqhWdvLSw#t(a-R?>Q_t} zI~+TsiiVYBNs%!d?N~ba;51QEiX^tyK@p!K z49|c_bT}&ZbZ&~Swj%om9rLtSwFFM=*-p@H?Ao-XXkM3VqEPl6gYKnqLPrm7P%D+6 zCZbZB11Gnl(PxU|mkgGg0hgNkr?0Te&FF^Amk_3Y7}as9g2>?oF&8c=i3jG=qzh+@ z=5wna>fcRDi!9zXoz4q!0co*e9Va>5LNMv)RQ)6i8D$GC!3=`V_KI~bkTK28XkE9% zHoYZ^1>SE&SVO{8samP+2-g2}d(e_YcvbHxE+~Zc$AfE|AQMcXV+h0aVF%MBqKHsj z`}bASX_~#pB`~Zo$JKnIb(+&yccrE|J*>QsB@AJGz*Qb|# z`CefVZBx;2^(Yt-x?eei^X_5fK)MMH&Cu+#1~EDIAPvqvHX@iP>6SE2Pa8(w@fKuQ z5{vVR>RgyFuD>kOAApICx(}L}6n{M(0hLBBk*^J&C)AA%PYf$q@3|)hfSwK0nS3f(;@U8rV7l-9%>CtJv`}xZ5L;9gx8QGKYriLjEbE> ztSwS<$&GrWCiDA<7G25}tfL$8i?bKTvyMKOFKf)P0jY=?$u)-hj?Vnr z5t{@VINrqn=9`DFZlR>Yd{#Zi8VAfMx~+K94QN8`RFx$5F1wt08V)e4)p|~Whr2td z_*>DVl}tr#vH)pa-mom#HeDiqD7al|;60GV;w_T>)1qb+8=_jvvdb$BJzkdRbm4H$uQb#v3?ODEZ|R6K|R+ zsYQY+^9hyaQl^)|hkOAO5~-X?3$ud_NF|YTnHm*>jz4t-Z}?H4iR%_-^+uQ~vO7qL zg=6%~cP{*T@ime8pMsg#a zqf74HT{zLknb&j=%K!$*A~Wu_+f7hMbz`VA8ZFv_9UV_lrprqHi;c13K~@% z6Zbjfs!aidEt1v^itggkKHei5%=u6_d-fG}aU{F&K9l{=e0V>g?PB-uhwy8<+IXe? zZS;u4G@rl!y1NyGd@~Vpt>)H*?1o)B=V@`>01)_b1sUVX)=HmqMwlm&UvtjS88saO z4J-0%sL4Hia@!r<^GX^$>3e>`AwY<4!<ShVwl^c49}T<+=us4^T8B%)*5JY((^|8g(DwY}lQJKV{^;5b z_MDy)F>ZSsT|b_XfK&8t=X>~)o>j!9yDF38eh-yjK^ugkAS1C^#tz8KCghWC9io73 zCALt4Gll7u1NS}cyB5XLw5Pr0FGy|iPhFl6!kp9KDJG(jr(XqhWj0>zFaBegeaxff<@Jw1t5^Rl{e zwF`*{DYE*~#j~wck3a-H7aVc$j6h(N?>wjhvnxw%N$VlXEQ+oE5skt{L{#&sK|^Fm z2yTAt^r*J~!rtbwG?hr&D$v^*d4J$AqR8k=?ztLJNxHppt&Z(j6!DOfS|Rc9oE!i` z3m~AYrq|Myopgs22rISw@+Y}G&o;X0URAj zK(W+uU*J8rWY6(E`|h9nNDGzsv)N5BUW_porc;pc3NL?!`7Ap^7}RP)$WED0nQMo) zepNpD_onx@oli-HisNZpx9bfpwl{O9P52UeuZF4kfP@3BsfCdlzCDi9g^7gvr$ROi z=R#i|CWpu#LjUkS#%>}*x&HHKx{aES(--N`XDiHXQI=dN_hyIjntgYFo~2FwCa{Q_ zBym)EShu2HugiYNQ6It1BJab*bQt_&I<%lFi^T*+hd$|)`F1VXO$5aH;W=&7aT07j zHXQIQF1hs+!W23gN}+0oxB-gQ7nwpsVK>3h z=Aly<#IRI-Y;`xxzxwsMyNV&n=R9=4I(>n;vi*b%j^Sq*;l%y~lvOK);LthxDy*QJ zWV|$5L8kZo@rbM7Or?j`hjni)5Wz?dWh7d9_YWJjJ??M}3=2*7Ab z;#c73RJy@s*FJ*PY9sl%z)op3CNAWSbncDB=$~Iz&@)zNhbblU{zpK%zizCt^~Yo5 z#w)VvNV9b}HFkA|;MDQZeK{lIOjIVFjz@@RDu{_W=g4CJPYLi?Z^0MZTbv5NKM+($ z;w?0ySt)%4A;V2jmC;9IiC~9+KDaix*k+*)TSUj3!k`T?!fg_`Ig6cKc@V68p+{zT zh6Ewbyjxe6156`{Qtw-ASO8@k%9W~}XifTktdA@BvYW8TE+un(L&*fSb?krX!65D> zQmyN0wEd7d#DQ<9my5~Yvf2lrrmuj!%5aG;r*&~K9$6yCUR)$Smd99$<#W*&J}V`k z7Je84i(1~l1|e2P)VT5F+{9tO4ec}JPDAd3y=uy*=h67w!>p79`uEG)4lBouP9P2n zluMIK$C9m8Sk<9BM`^IB%tY^cy`XWh+2ME^YY1pEQcW>Uc$ww~3}`0KnhL?a@lzQ0VZe_JUh>k%t3{bRY3M3|M>JKs zJ$iQqr-E)#oHUev$62d1r+70C67ZP9exlLWj@l2|^C6m%ja9vw>KF&Z7}qhPY{9$w zzYaJZ{#JH;?jJEsmF3XXaLAB*OS+5|OcxD048 zU&k}TAuZ+}9Zoi%>Hi{x;$EE?qI))7uaA(Qt$RR~RSe}g7hZ+3V4xw1 zW#5knGR>y0=vc)3z1a8pTTn33k5m0iIGg3WLXu+c$|)_nAH~nS@Nwcx+6qRg?c)$( z^e|gFl4A)oMFn@CpyxAK|U^lVg5hj z{NLp>F>&vvtIS|J(zDhv{U8bhl_v9=*`i0pO}iZEmn-x>XKR|3B6|kmvD(>w@+>u+ z!hqOVl}={8``L%s1v_P+=W9V1o#p#+6_0c>=0Rj?K?Wvxmi^zMCg+~gqad;Lg&EGY zuX&S77B_!V+h+>**0ACV2G1VHQGg|M(bw_zTgME!0T6nAEU{R}dk^yNPK6&DF(aAJ zDmeahN%v^=OZi9R->F$j7Nz4k?*Sg?4n1g&2Tzz!*BEhE4D1V*e-t2S&1%qtu!W8u zMbQO1iR0DQ8R9-*auPXj#xt+Lq+KT&kW97HnaY)GXNT?azYE|{=Gj42XwDWjlbKvR zZ7MxvjOSZRE3TlLOh$25!%x6|U}VLdeG7R{yam-Xk^O7y>W1y(sKD0+Y{wY)MKUtz z^p9`OOJMwvE(gQ|K~vwhENlCeYAOsoSMg>09k>=ZVOs2f0oHKVoE$P~#CrpgyEzu+6{oj=1DpMI_wIh-&ABi*eoK#O9IpbN~`rqqH3&+U40NzxH!{G3ze-zIdXq%*(m7t4niEA{Bwtouu@EjVx7T8Ssk8j_YD5k|?7_nz*5?^iuE)807Nv{YyzhDj->SPq8u|nx|(GrX`K%y64R^AS2 zYSEW;5W~ZxRl%z_(GhJET*}(BBo`aVs*)Lj73{tIiHCT|WP1W<@+7moGf|^xV*vqX zl=)camGHOWjhFCoW%5nf{ zBlx#RZ|+t<>5lIX#aBz)qQMz_%~KDv;EKKkq(GHH%_7`l@-w^4voOYQZmsLD{&41y z>zO6{-yBIiw07Vyq7_X*4bX~MmkRdE^S8?Jb*~oZzfc|%na>`l_F?;aK+}(eflVu) zHZ}h4m1}}ghXZ3nBc62{WC2G@)K0i0Z2byj%Qm#BN~@p<+nU!Fvd!&o83VR6l5A(F z*))XS4mZ=`Xl;;BpzOKg`tu6z&^@1baFA!!tx_Ia^qx3QTuZ?N9;?AF+Rm6ep=o|4 zhk0eu2793HlQU7@Xl^f(a65QzNB-IqCqXAgYH;fMx0Z!4bl+-{?zcmYjGB7Dl~HWK zzCBxhE9BmT2g4&~!)hf7j2k*RU1Zu%xI$tQ^kK>~#_ZCkrGI|C<}i)-ta$q6MpWFE z)v{rNyQGKfVNU5tYz6=sadOq-D{HSoU)Yk`a<`dc0G*H~%%xnO$P7B{`~OaY+{q<* zwCX;K-tGQZ4jy6UO(kN$lK3icDJ!ODb#-;s=;MTI!cfl45knrI+r0mkyj|Is(7iN5 zYb2*DrNV@b!ppX2VNlJEn$XZNHEwWt{W@m%+IkpXK6WN@C6{(;8Io{f3ed(^F=@G3 zB@_V+O*-y9(PO4-N8Fib@nyG-NLvM>KKN&9NqO^`@~0YGj(OgJ&i6TUKCCzTwX;Z% z85HNf?W#%MqBfHmlTzgf0^q@;Qcc;H!rN0S!9=$VDFEwvNS^JbG$J_u&YLxBI?k+M zwiN6oStakb865F+&`hgYWp5ROaYc2?tBo!RNHfCq^V+8|w4+9mODL1&8eo}*)0o*x zlfcTmaIT|YEiGGD9($-9$O`KI^m+fuNwXhJzT>-#h@045LZL&M-V;tFpZy%#10HhL z#9pOON$WPqaH|YE?!h4>)JA_L;inuHa|-wxApR++fbsSyym&pUsG;`c8gn|VXqAM> z^}e50zs_Hy_7*_rYI66C4NR_UezZbkKY5+7woe3 z>+TgXs`Pv@cxfE72g0pSU&CWCv;g-oJJqnoDrbvSU{zT*!*Vz=WW5Kem}i|rdHWLZ zu$vP_dY~~-4jmGZ3Um1e^ty0KFy5Nf)H6QfmV#t6UwoI%!zYx!S=1dxZB`Eknz~A+ z)oYpzH3fjSeubq|@Ef*c+~57rV^ryZSUU+n^x>kDmEFD966DSI!Bh^MtW6-?B5_8s zqeE-Atvm+aJxsn&LeY8^xV!N%<}iKD=+F_>!dM}2edab=r)58VrP02mN_@PI1c^N&AWLkb3k2dDXk z=UMi)NAEe5qh~=|X`AGDdM=lr37)KqnRc)#zKltBOrv%jLoNpJk|`_Kv=7w8xMcg?_nVw&IDRratuWsM66Gpr zep^+YATLID%MWjoekASs)c|L-Ypu-^{ z3Ifkh!*4He_FyZGV4eFAt^Ln#^?0{F*ezp&TWR=Es5+EuO-Wct_;+sWTJsnpjsol? z&+TTd>d1djtOb7GV9)2Ibt`1J!N=}>p=~(_NAFHAp*37peRSmmMUYw!J8m%ZC55jN zv6q6>Gclu+?2oB_4zSeji;oTaUR^T1dT3F$+R!;4uE*sa^9=bL z_iMy`c!U@V=5%Y>WPM!~rqu+U5?BMh`**5DTG>#V zhB~~?|L`TGXlxv`rgS_~){x#Qy#XE#7JLs(-vEDxN~q|9WEdg=GlMs~Xwk#}kg-=XyPeh7Tw?nhBn1~wi_BcR3oZ|c&0aYKsH zp6K%6?)q<`_coKPAZw#o}VCJLbinM&$-iXCLaj&VzZYU{(%tMeKMd`l#z4SVE<1QEwQs}c_yt9uxL z0=&?hX-Qp&63_)MHkhYJU#2MZ5yDdCRgk4MR1FmT9zCYbh4^Prg#+`RQGC;gU)Vo` zq*vqK3JN9@I%p_17;V$xu9t+{a@aA#vSVIyU-APaZm{ap!NkJ2UxyPXW?yZsz z{!{HmrxKeVFoG6nsgWvSx7Z=3&M26^27^Zd%^jzSO0xIxqu`1=R#VWbm-le3mqSO} zW3WuS`r!L;P<*!jUKcIRnf09=;CW!1`W|GkIn8FtcATPDAVl%yLuPC|IPJ8 zk>^5{|B=Oi(RUomAhSp^k;5-PV7cb82)lL7U>mQlLH<}A+pp_0dZ8HpIzZp>wgH3? z$PKg<`lrKjLPkDp=nkBz(Cp|#KPm7FL>K`nGobL~LcanKi@QRS56^fUG=A@ItwaxD zpN5a6`_uQ%nBbVmhe{uRD;QrhyGIWWXu#MU!J$GjZ0<|9m=Oha8FunESqr}`Qau4{k; zrs*iGCp0v*l+y6?%KLk72||Dj?q9ecvfQ~C$vpJHQoWF*a{V2}c`R|xf*2^vH4 zCIka>H0>kIsE0ekDpXbAGyE?SJT_tEEb5sim6E5^>HEK}Fib~F5sbirp))5V2&fy} z520B5!`m#lu&{8W4)H^+FzrD7?55EID7+OTdVmpCfos7~HeCL?Ntqp&Q+({3#j{xY zMk}`Ch)8A?El$>8DhXn7pTZa*IW}}l19u?^cr59SWZt=cQfE;I+@*an>imbO-L%)d z)~`c}g*vvF3$us>2qUB-rr4U)zKO2+;JcDUQ(q{lSIf}jUI^coBpC3xc)}{mfJ93? zCRZYr=Lf?Gq> zu@1}^bKqFI{DI=IcIAWnovYc@*R2|>ex!G}F+Ed&aQKXZVe5k+pR`aYf&ZdE3@^m8 zG^>Gr8DSPClVm$Ck`m7zBLE`K8KW2=3kfCIh%(v_&FTk$>%qqUHEJA#&a z^3>sqZHxRqrl$9*-YZ7F6KPVx4E~)zzuy5<1&&a@hBel`2B8Og4x|shA?z%>M~p4X z`f3>32V!r&AYgE_wmcw0yPsaDklu-hhws#Ipn=vD+WmJcP z{87fK(<0Pq{OeKYAYFuq<`mh!P|4wWGMTjXa#zqBJi_KLwkkKElMfrLJ*YE~GlYnW z*d4Cug=~8UPe1XdgL?c1H=O|5m0{B!ow?vO2SJAFNyX0v!f zy>)ewf7g=hSAqnM_46-us_j&i&OWdo0q1n zt0FHE{!+JZH5DD+Ui$lfB|gR&82xR%4m^%7h0=4gg|4z3po;s9HLRy|)JsWh&3wcbCpxa>N%`&7%e_rgJ zoOxuEb9r29`~B}X&mroKHej3LE6-~5fOJZELM#iS7hAS(EA(S-uhs^$r1uXzk*@_UmSP8-c_}3c!4@L8z*ouTZo0)$OG}zrs%~9}rvav3p zk6!AVtWwY(w8!eGe{I)2Oo^R2${?J-IcBr}4EB%(D-Hh)=XHcXrK|$d*uN8`uS)jU z!0FjB!zqwH&>-b6fMvI~-nuA*auX%XG=q8HtIg@y$s{y*^S!Hk0ck$i$Zw~ku9iA8 z#q_tyk2IFdzj>}{W@$)WR}H)#u~4E(iXCGcq5nr;u@~w@t5}D$=TlcDk+29d|J=i@ zUt{VFI11LuTjmmt>4xh=hqk_bH%|s`Rl^df3l+un44=SP`Tx4}^_*i~->)Qh#&f5%>%AlRctp_GTpZ}vG zhDJ}0FTQ9qTW3}oLOCJ9dj)HhK}Eh@d2t_9C(}HAwGP`r zP!czF`|~z3HZ;Su74)nh+;M$CV7zio!urUnV)d&zK9Jc&glRddl9eEN!b7MffBqE4(;1ZYE3H}@5)H{M4V>pM>1&JL* z__oBiso~6)_#wFyqFurUpV+qWJ9BN)uMtwOP%5t%ob)wBVgym>OCP>m zzDF^urgEG}M#o?rJu4D4t1NlT+l^lEdR*SJE^t|jOGaczu_pa^tZzbS+8Se^v*W#0 zIlk(@dDHvTV4+V%_1!x#XJ9Zrsr#20L|MFu$qjtJ-aTY=E4VtBq@1u(66WzPOuJdJ z`U7paQSHl;Jcp3c3q2E9HN%aNGjW#-9i;Y#cRL_-kRU4dLIPOlzTA>>&fhbis-=&oCR|P~T78?-F&GKalhs{S!F;(RP*16?58#sl zI1l!z#`b=gru2^G0e&yNnKr`A-)QF$i4RZkNGMuuw^3rsg{wE-9RRbJu9jN`Q42DE zjVYwxWVx4eB{;Z(_JGLEtcTj|RMfUj8q!LDC3RygPcIY(644|^1lQPkz%~GCl3!mb zxz`|yA~w9(;`?v6FnH`ZIDP@|d$H=e3HR8dsXk@OS$=O?N$H1kdR%1w&!vRXwGBhX z;PDooO=jg?PW6A!KsCf4arZRT52cvsKd3cDy=|6wvMOA^_H*s6$Z)XLSV*E^mpWVv zYI<^O>o@3Fi|!IgF7XXyoHyNxx^-ZnfDBJ?wD-leV^te0+*tM)CR`^Nn~8*DZHFu( z4@HVCU_n1^0kUmvvyML-qj)2E8gDAGqg{)w*WfpVhae!H}720VH>MV>x>efps?im-6$eqL!$%q6I&_@=MZJ@vr%(l$9OI z>~I+N-qz9>H{zoxkpfajK z9@r3<*~=;a-TO7qw%0%gar_`y*Q(CR>UW=o~3MhB3Y1o=VkH$obo#3%M za^mi$JMcmUKJneT?)yVqr7xYLZhN^Is)76soy+iPn%vyIpZwpE46fDCtegRlgg5mJ z)%l)|4_R_Hs&h26m1wK*yIQ2nX|HumBog3(aMAwM9Os`mvG`QUYSuLFJW)nu`@x4d z@J#6GeUn2c^~^if7>Jj^fQzn|Y7m`HBB7DJ-2aZWAIZY?5u9|ezZG4A#}ROtB({*) zaNZXPxvp3H4`)L-#Se-3aZzTu#pZEcUnk~}g{!eSfoFc=CGtG0%m(iUR)nJv@prM2 zKH4D9?d!~-;+dCg;uGy`OUp1q`@H8v%rsWAya|Mu16oO8DBl{u~1_v z3vPM5Ofr(&nzR$T?Qozj$g$zFA{^29S4>Pu9+LZPN23#}BU8;~wCRTWF@4&l8JS;95Ay5$suG2Qrcbh1l;N@tw>TW6_!@E-dJO;{y z(qYSxyo_xblKRl9c)lG$tk+0FCrfo=0WG{r2HpWZM*LUXjY`I@?Oh@-iJl|gJg&S8 zPr8ha7O(#pGJ4S{P-A3%`fkYxg44Zsy)2}06d*cjvjob}!4I{wM!IPML@(aw-IqeN z;3ET>9;ZZDv3%-&TpjB@{k5rwG|YsN#VdhrONj@YYv&D_6WTUy;WDc;Dh0tb`XHxR zo1Xab-yH6Qyk_6_QYffqPVM{zBP^5@&-oU}L>;p!&xNxFAy)AOD>uXZ^#x07XSz=< z>tDKU8v_MU7q$Xsb}al3Qs?r{x_s-&&%Heg672iNhBMG{2LhF$`3PK$X?mYp-cHxA z5ZXAzUPV6Dpn8J`oHWmkfiVwSQ`$?agt_rqUl?$MeTGisr_@B8Z{cce_`Ef8H=cZb zNng_ct7>l;#;KP>oY|7-;+Jmd##JD;!RgjrN)Z<$NrAjur*1spHz`^?^L>c@!fs5* z7rw^+^fds)JFu5;vdFCfhQ@@-LHU~IpXx7Gmue>JtSgTR?jzma_dF>q3||@ zV^`$$y3Ysl`9o{*1opG-#_cIn(+?7*gSU%-HG5z1GvLDg^X3B;brwp?6@B029t(N# zIx-!z2N@+AYY;A!J;2ozgJ9&{&o?9Beg} z{cvipc6?ZKArZ&pYvnTV!*(;TSGSM-p92?{WHTF9eIB?}-Cv?$yy~7gBawI~9Q$g& z2=1J<${0*pv-HPhKOkJ5NdSpJ550RQgEF(J;}&@?VGCf=Il=L_oa*+52bpP!qzA)0 zG|LzXTS)<7M#o@%u<=6WKW_^EIJqr4nLba#2H`7vIZzwa#F3j}=<9ZJCu+3?OVm+( z^SMHe`}6S61YL6NpQ&Y+>?tfh0Y@d||UJtYdX^$-2+4 zo0pA+KdmCC#~oNYQlN~UVcR_Ws;8T2mFgCPLw@)ICG;HUx*j``3lNs&TePy-HRh5N ze;TZu`d>Whvk4LwJ#b(l7WE=BPJAW-QA|(9P22#7ZtNBkJvE$Z$OJP-fZN!_BV?PM`(rS&e*VFzv_ck@fg+n)U*A5vwu^qT(vY*5iL4>H zNd{ONLB4pq^2`5{24P5GZk8GvXtpC6d`Mgjn!{# z)*C6*fGf?JP+A`%nEJ-o?bE5x+lYwY&b>Jp_0CCHj^I!z9~yOxiRCiIqT|n&Vjuq2=3>`(Z;z zHEZLCoww*$6#fZapp}U%J#RMJMVhBu@ZJ$oElZ9g_ z|0!6YHz16kW^jwy4Oi6?idM2d__^ICFJ~;Ab$b9xH3+@`=sp(#|1w7FwDP_O-)$zh zki@ekq0gtslHz0REF_)=;Dk=@%w~K~{4-QyYx22Jl7tA!v#Pmg=w8Gb0!z*peAxVi zh$OflD6GL-*HCj#vEacafpy4fVpB}c4pTxAb$J6M{_p|*-cOBwKp_C54CRBv;Z_5d z|BQGOTse}IQN+XUTgvhdNW<9?Vt5A)85X~NH-<=TmI{l(`Cu=1@B7luF#yV1_&zwU z>*kA&Vi%XdnKh@(1!gEGb63Z#^-U>zOU?PY98HD6zu)2A(U;`GU_nwKK}}-~ zP6MI1TvX(cQ2x7Z-cSazh0s}~?KAcr?ZBf>G7Y zgCS0YSb6RMWDXsj7T-WHQnH&!aHvnUsFA6H&s~T}u3VH6Lx>a{bu+~GwN9DlccmMo z7>Q86p+p04?N<%Fxj=q7`v!>(e2<;Hk03x8Vt*~ZHpJGBGQqr}U|BX?#7Mpb%m`&y1DNfWIT{0MCwk*I)cQ*@TsnZkad3F)p|GWOjYnNZf=n0;InM1ru_py^VQU3U755H3F zh%KGH6YYFaH_Q{eeJ~qAzwg8I*|~ffL+G=I@6&b?GJX*77p?p3ln3yp6GijOeLRpe zo2un;EE*wogvQp)a?aFS*y*5Pa$i#;u4OtSctye@55Kr%v#?3`HRoR&Na;T3J27|e z{z2+IlNc4FJ9i38AmZ=yM!2|{`f>Si0U5mEW!s z$w_i*TTrSRmG}LR^Y+&3wYF*y;|o*$tC=#K4;Ur~LI5;)QnZIzu9a&d-)>@`^Se@a zMFja@Y}w^KO#1JdiM_r(kQoe>!gPt(dz@6gALdUJp zmWS?$W8&qA0msQr@h1N2HRy0W)z+LWIB=@5;Znwi6=)&;Gg$N^Q-~93FQ+biN7`bO z+Dj0EvhRsh_qjZJ;-|0hk1=oKUjFxr@{MIkxSlQQIMvRlp&Tr&5A_PD!V+5&8YV* z%w9fY3gI5#|0Cpz*i8kv# zN5@l44$)Mr2SIrOfH1z_{VqLl`+s{a(-iHn+U%1<7$xP*B35#xX~Y zEWbS;R6^?|d2c2XZ*D7Idh%wyL;?x@f~dxxT}~83w3mHqZeHDM(-!PvDT(Ic8}TIK z`R}_=(!yA!eQU~+*w`-gZxQ&vgmO_A!jj)=>%``-3O?c(%J3}u{))SEnxeEgQN<;# z;G&O4D#{Gk+CbE~*el|LB!UKpjjL}5B67}W4`V^YGt`Y3_bK+;p_H1Hed^A}k_ZOT zh@&o|*UsL1uE+-~WGO2|OyKr&L`h&`5EW(Cl}Y8&EV7~M+c2=EHNB$J)Kn;9pP9_K zr6tJKNG<)bZB$_EGVCPmEOS?sL46oQqCyfrfg4*^xDM5oc$D~l43qE(aB5pIM?w*s zpRXt986pc};Q~6LBN$o(V)Mwv4CI|X0pumbuRqU~`{omN&ZlN;s~mlYKhn0SP$V); z`9>(c)V5cZ{%0sT7-maPoT9=sl;&~x`B<6}APY~cvzwC*@Qsu>Tt?7_cVY{)WbNM;veGNJXYRTs?ss+p2X{IT|mEZ>e$uD^c(--=lk!zm`mark_lf+Q-%$H#KPTh(nT z)R_ZH--hpo%cNlk;s&ABz4IigI2Z;&h51t-$Pv9=q#8aAj_CBxj5JH|d+_nqk+|i~ zwxXbf=kzU9FwR8adQZ!#Y-8|1#8dyF4Hw|vifECK=F(uSj2 zxx`@6`>3YZ0i{;eqmPz&l$#c%=^#o`p`-R@h$Nc{cOCWAviU?QKI6;V3+%!sDIKX_echuhdzWC!yDacFO+S4 z?zQshy-pb}BoH3aL=0)VLsE&AYqb?k2%uPS6>CdMjJEiSGJJOSYa?pLX5K+h1w)ia zA~=_YdSlJU7kVk=zyH=%BC?ioS8Rr59mOER*My^1+?``kYZ&3W$B3t6JRb3fX?m5l zZ<975ASl5FoSUMolwr>}!ohL1ChBq?z+*K#QX@Q zZx469%D$b~v+a*Yy}<+{Fo?Z)JqrD6(S9OAOP9%7&x6c;hEG~94Ae=)Lva@|JcX!e zjnqG@%e{01|NilPZ9;u4-&Zsv-a-dK#XtwTSu81xCcGo8jg0sKUj0REHyZcnsoTxi z5K!Qfc>T_L=&}r14yvBebcTwtq?w%vvP6%UYZ?9y7f1YdC~t3ck#;pFhhn&Rc2+)S zjOP3dpJR6t{c0e)u@qR4&nMEwdaB0HUa#I{`qHKjBtIA8Pzgt4~5a$RqrnhayQVT61~u>jmpCO%|_cXMrBjv_umX z61PEf)W?YSt%kv`GJNuH9vLZ!{8(iCrL31P9Xbm9If6gONLn#1NMX`1I2^EW)ggYk8-uaOu-+|FxJc% z29KkzQuikGX=75Aii^YrQ!$hasgMDwHR+e?;>k%$`$BFlr59%W(+=RL(AS`xUq0N` zmE6*xg3QGS-X2ORugNI&eQtFR4+2$z+5jq)oQIywoM9vhGWbwRSo!Tv`8(aq^^2td zY-cL?E_$OhAiOyblNLHn4YC~HWWJ^H5snP=rWCkLy>*Sg_vn{V)2VypshrIz0VzN{ z1T_RbwmM=U{=@pD_AYH#KIFS??FfRaYPHC-(E{KU?#0g6UrQ9Ea0wbxS@+~6V6WE{ySzCzz>gGjAj&vLHiAyM1;nUUc7_#(sCvw z$8Msz{?c;rXV`_=L$$?#nQAcM?>|8SwDpW>`mL5ZS02`-h}T6dY_9w6cb6pp_m*rY zWsIprQ9Nx>+*<=QFHl^W1nGj_}{x3b?~0 zsqF2mpK(eEOj7>oh$qVqr<4;_h@it!W(iQ)USdtmjI#Rm!SMDI=0vvEG{%Q5917`9 zFI?fl4wT;Y?sdEiVRx$e;cT^C{8a_znyZwMXpBO~Z_I?Vh`{xHLF4Cv>C-Fi`>u7#Rv582L_2CAgdi?CFz#Tb zYggS~67nR{M|9!ik(sUmlM3%kpMn-As+)N(LLlxBMC$SGD(%xcGp?$6k9=r-y@9tI zqMms1ZnTSY{`F=f(j{r_Ktp`fi+zG1v;*DcPzrfQ3Jr!VU>7!{{ElpX`yzlhoCY-M zNEuE5@mB*9L-qx!GbvheV%vV7C=TQkHnchYHwQ&t47beBltqt8%(=r52sHxYu%}vR zA_AI4*DJVI<#uR5eQw+RF6ZM>R%s%Z`5ovC2g0*RkB5Mj*thAM4fq_dawUuQ z?fKq`E(Vcn`fj#Ol)*Uf<{trk6*Q0B`+|qXZ!RnC3rhs@o;spw%DBNS1;Net4#N~g zLX-Ym%d~lA0Z^(lBJUXHz1B| zU@*tYMSl|!i>B{*X?WWO=sT@y%B>eTtpF^5Z;b^;BnPJzL|^Axwow}j|QSQVb6!+ZPF z#5lAh>T_!Z^xU$3XOv9Lsch47LnF|##GDN%Fu$`I2pIro=mW7MvrU7Wvau4ti;KcG zc(0j90B{zfK9s)Y{^rVX9T+7f9JJ%Z6cSa{VdQf{Vn|i}^^cy%4COf##|AKMh2qqG z!iHuClun%*peFsz*I{uR{*FSANhG+#lUnbX@yv2FL6LW93e5mZ6o6x(``T^gk2*s8c7w8 zQgO%6w#rC&y-l3zEC%r(d?J1?e+wWwJ)t!?ugjy(f5_j9aja9KcO#;5?&&O^kRMF zOUzc^e~Mvk*2bx;nLeDrF>2zb0(Ub@Z4^yR){{fAEmR{Ov@L&YZ3al?GKdNA?~qkZ zi~45qcw+Hy=`-QY!$ODRoc|S#e6Ya)3Xcoy*!G-zHjfMXm-G4}Ek4m?BMWPCh^E)X zBG6lxcNwjbdE%3CGHUBBAO}=?{W%cM+eQv=6tTjfe&I|%)c)?Vn=F-NF~>;_ELLnw zi1SH`lsmFBq`x%}zc6L^KjAID&h&_Np;fz zKLF8tWiUh>O0Mr^T50)+t)232w9T&X4ajiBwzErL(joswweHo2MWrHh87MfxIXMqv zV0%Fgb>hY)L0Q+k9a3*`0G4xBs@h!zDUxu|fvT=HXpV*l{el8o3yRYL7(_(Mkn9tD zLd!tbl@wzABwP*1QY0c6K7Q)la>0p@>#Jw|`45}sgXv`=&fFgVJ3;wnmBe-Wt!hgv zybq+V4g++K^gqOJ8Owan{uOpgyowla9ndnSC6q^9yIiTY^Dd+hJQBkl6fY`(=?`<0? z$<(#D5G*uL(;b0!o2}GoD~1M56aB*))Osi4~ip>NktMNnV{p@9bxO3N4j7)fFrD)zy3&-W1|E8?!#+{VlF zf!?ubtZLY6A{r#qcjs@dnL2BM*s;6v;B0%X_yo_%dGyPPNO*bc4tbR!ZZ|E&{4aO! z*ZI~djYPPb%-y0hgV6BIIOZbw_RAMTiE){}2O#?2x{CYE8_spHmMIUrMD)~$Rj?qd zb(O#Bt%eBAuQYgE0G#_~+%9@b_1-i> z-QVGy6uq0&21AVhKIivkar6)P;ld?8xwFXaXRrZRzr{(@W<$+|t(-zSgryCy{@pEF zK(wLZrOpt?$_GW;QFVO=xMPB|(A^VVm(jflHCEe6PqBdFS((r1k}aVL`l0{e!Y65C zKn8N7fl^j{=`eF)w2HX_36$juzLaWTg#jjz0fmWA+|lqoH!a~jXY#!bOi&rFZ1riW zYOtYy%PU!gkt1?ex9%dux znjB}RkbdJCsp``z{8ef5%FhvIL9%Hl3?WFX0a$I*r#IS&oJRa_bQaZL53|tj-%!2_ zZO3?VO4hiz&WDg0LX|Qu2sQe{aNv2v`F9*cSTGCXjiD7~IP6il+S!dQ#y~6NAc-s7 zJ{$*^L4orDoklJnt=M^R&~1aBOrKcMh zYFpM*x!^Xtk*Muh93ZNE;+W9VA9;pkLM|sEI--&OX)7j1a*z$d_ z68r{P9ZGR|{d0{o!(PaBCf=0!@zihNOF^V;ghaM7d9&ey0m7obWWkFIG>WzF#{Y%@ z2I?rNv@|Z7u_yB}VJ$#TgxjV8S%Ue;(*aOccIbFDaxRbES#~O!*U^`FJGH5t*sgp> zjC7;Qrh6l#cV)7KX8q@8v;4XLzV~6)Dk*GTMm}zbIv4&3YZTHb#FTeoJKD z8Sta*lP8Q9M&EP|D>XO1OEyPU;_xzdvZx`j?J0KuUPVF?)_YVLP79bYR&MR$XqnoV zM@qaKj}8EF5vnr?oluR5_+;9s!Hm?YfcjAuUrMPz{0=YhJFU9i#4yPsPp9dddplcu z>#Bwi*S~)dxQT+6wvQ!?M?!Dk zb>hfA?y=(h&%JGC&Z;g%0HJ^bS}S2w{1S2;&-W}Q&K|-bpO@OA>Bn&_0jAylaSS)! zyFK7o>qEBBhKb~~CgF7rw<_`MN_eeceBF)5g$G0|1|P-KA-~La9XFW9GE~eOocB9z z>$=!}6?}Int)(p%>}kKbYYZK$*jYqJx8Ybj*OP%S^7LlqJzbgn6X;|lTtgJw+XhkR zRP%a#rcH!vxeV9x>1PI8%uIRLHbcNR>*Z;cbZ$qV<{ejd&?efCWex9n5`64-a^cg3 z$rX2oSWIK<;^oUiEWymW&&fOnWSpWxNcgt%ES87S-+Zuu0II0+bc$nXPNDZoqN)*6 z&l)QQ$C*9{-dHEi{TrC{m5~enq0EV8p+~eyXo$6+GVknqvKk+ZE2APJ6OVhN-kpsCa}OA(QFAGIPsU zu#e8i!7ErUZ`}PQR|~4TG%MQES)RGesBnl*bW*hPOwPSysfP?)3jF1ANzFXDq?O)EOT+q=MXXoK_45#4+V6nt6vya>P z)$D?287tYLsBv0tQYNTPe#6Z}Zq^{i4y8O~E8az(t@*HSNB4QdPqN(NlEIcs;5q_u zeY3G)b+k0&0RXVdR$cI1%x+tJhxkzXM4f!#Q2y4-p7==Cji4kRVzHO^Yzio-JTbWN z2moG)cxo$nSCz-!@ z_4X-RM9!MX>}dsOvRJBiJE*9Dqf1OYSaH;CT?PYP{ANKE9Bl66FGSZ}=Fcb=j%1~< z@i!<_#8MbR!!c4ujgGovSpomnaj_Rp?8ldsdbU0vnWWgNRYx6=k^GNDfVss3!lI+}EcWwQ z#zFGsxnBmTo}cHRT*4z(uuT~}bwciOl>h?FM?x1OQh8HWKb!QJ&foR3>^9;ED+vnD zNsB!OcL*eG9a$*%`8U4A8r3krG-d<&0~<*Fv`K1epE%@%)-0DVcULiG+n97*@1CDQ zgc{bGjCF~cPmf4|BQV0Jb9dWJHedfxFXzQ9XNpN{iqe_~f_7qx8@F$>`h19A(B?UjM(#u}yaBw?j&JP$h3yE_0I zVz1wIYr#k>Nfxq!8ZQlys&%t*kID^?SuyzLQc%a#c$> ze+^wgZ?X6Mwkn+VRN$s_vySdl1F^{l$q-ec^23RaJr#OXLD>@H9-DR=8ff;sXq zc4GjL9&3#9mK999Z$bP3L^ZK%pAX0Lp9>HlxPU_g7jX1e_opi?pb9Kd+K8aE?+*7{X>a#Aw9e&aO3FpUg7TIA`PO+kUxa|z;wc~4a82iglyUN$>Lgn zIbd*uK86Knvy!2DdGP3(0hxA{l>+GnjA=n=CWSQxs9e>$Y40)jBAz=TKG#kES2+A& ziGI%i2!~zlu}A(d-Lrz7e-~HOQPA`j8-I8QZosZmtWO>JKkWLQ^_p)Hc11$XVksfp zWQ3=RGoRR6cUMecqFBB-@&t(_$ zTo>K?`CsqiGY30sU;i3R+wkW)f6{z%&3kpQc%CCij(|VMTBhs$ ztvy{QhCPFzf#5I@>e15s91W5RKv@;Hz=wvuTB0jlCmgHgO@vTvGuyFQpLQhiJ5)gK z4#@XYL)_9W3~R?R#j#rg8^6p%10*NPo`1oU)-O3nl)-e=NOBi9F(EwfXDf65!K_2j zbcOb+z|P_bjLByrPlfsA_+57Qw<2~-4$7rtK2$XPVNKRZ+Z5D#hgey}hTEwALo~W{ zd-(VGgxPH$J~Na`OK$UGDc7xp`-P(;1V2J%!+2;Qh}#!YGuug3@69f#)<-6amA(Qo zJKjE0GJsr-=;DMdI$k%x<&oqns5S+rjjKe~*;!o(odRDAIvjAZI79pdCUuDa+us;d zvyhDf0`za+*^R(?^FJCWuJeO#8AB-6%;eU298a+TlK%I)e5!veay1}ZXr|KySIaw> zl;}XS9x#g{H^N)lCS}}d8;>qu2(P5ZIZPcuU~^wv%cRuGzQ%>2kbFpVR21XK6ABRw zuV^2Z;j&Sxz(qEBI;{I$aH`lz!uMR||2FhsY!5f~Gh2K%=d}j#cp{}6o6bwdDR7g> zqebZqkP5nnO@WEz?A<}ZQ-N_REe^nl zQ|N(nxeWH4742=5_3}Q)i-et#EM2Sf9(Q^`!7l>Wd4+E?WU>B8U#GBn{Ecy+nYKph zbXv70<1`aZn5acvBjQImPgf%IZ5f?&7bqJWlN%jP7@QAS1!zasese9HFK+>ClyzN1 z((noH1cJ8h7$W;`Yq2}@95>%XDd%r~ykpgA2#dq!O9|@~_mdRiK!Y+Q#3ci+q#zA> zs9WD%B49CNNazgjWR2O9h5vRhg3>m&FabvjZXh;jkMx*ISSY+*7u97{2I*Z|LPn4q zk~boSO9P!KsMriT?V&=HE>!7A1obDg`O@z0yC)3OpTNlzubP_upT<*GbZ7{h<3L3t zBzBU(fM61o++FbWI#+25MmbJ9>5{u|rh&rEkpL71I|NvjCr9YKSBXJ$C=QSp-z-I1 z46(p-FUXm`uVcDUiQ=#YRGkSNtdV4VoCW8j>QVo;Y&*5#90*JLnRNbBrhKf zXUiJEE0x{S-+x)eyt=JFZBN(NW1kK^^?mb4eyE;J^7Nm7T+R&DTbN*ShL>%j9bc?A zzR&HJ-?{dR1fy_?f_SAE2qAZZ<8$@J9bFEey)hM4YmrUY_O9PKol2B->^ZIP>%k2> zTDr!@gcSFqs>3OEOc$?IOk>+V05Q0d6S)r{y-|QaMhRzTd3l9bO7U2WdU;?-$SsQq zptEpDe56Zvyu(T*sZz>y2l;KvgNemBHdAp>;}f*PTfi}WeiDx36%KBRp-seM5=JTQ zbNs<95MG~f$YuDxw7!_ZthtSoomT*^w08=8YPI8z0`esH*!`rX#rwN1bO(@kRI*+e z#Qhxy;?1qS8WDp?C|7(RTcaxAu(U!DLWTR>{Gl|`G2Hfy!-CvUKkRTvUY)lh{1#CX zkEf#dg!OCy{@PUW+=osG0zMK3$L;xy{782e*O?cZR?IEuuD)~U&Spigy2+Tzb1TKz zg4w-ewtbV*!$-$kflri_kx?fMi*ZY%WgWVOS>Rpa=OX)4aInM_#}#9JLz_Rfb_IRc z#Tk!_7OqDpeTVpb(aS%xl27+dm1PyBs{|~8X;r9?UUeBQ^~F5U_jZ_w6tVK$U~vx) z3ZSP0yd}RT;}#GIPlC~WT+cZ7dv8kjZ#;_Q50E`a!B6aF%$9=_I7@iQA1a4l{t3Nf zAwiuxp!7;x;0E{Sy46_=5jZ%~1oE4H*4ungf4zQHXSt*L(S_@bI&OTP7cv~ z$T&7U;4~BCikDkiSy?@YVM2-%&_4{Z#{N|(g~iVQ1;t5)xD-ZA=aQ0jHOpz0QdBgN zBcGw)-}Sw<%=#Q12P3qwSZh17A%0p+{QtgQstI0Cbg#~l|GxfOz>0s-Ukm4V^kor( zwO{bFU=(8ayIKB${aMiHHIcfdK@C`%~#o4j}L-U_ZbDk<5bL{$-)zEn>4-L6JL z(}lRZGzv)8(r+eH?u5GkxwmyH)56(fJXUzDla}YKr&=UHJiw@mfqXwVz zBfw5Qh|ncM{+8?Gup^qjVDS?{-Y2EU>Y3H`-Q zwgnSiED?7o!=$dx^itBUsPIpMC-4{d^*|c?@BE;Qnb%Mc52e@*u@Jww>Zc|&z(EP& zV@D!Z#;yopWWX~G+`CVf?hOxQ^wDTgAz{HtRt7-#H~v+;zVD)>l_-W(YicZ(dGh+% z^Y=fxs^_4Dg)-+~)54|Inf3ryJ!e1^aRm6+ zS%&*IFgJmFVEr$*31ILV1dT#-WT6S(KiXmfBZeCU*-HC7BcdPG{eUy(t84bus3e<1H@b#o z5wO-s@(R;9cZ|o>2ZdM*2o8OufAPq;F37x7$JajOPz2Fq6d{=R&znQP|2PN9fcA%v zp>EZ8H;Z}~f{{0#0mZ2x5q<7Ea_5DoL3rdK@+UdjJ^*7yH4mfqV7u<=4Ezk5;z(j;3H7U%wD(wpm}h&<&6(hJdO^>syg+bT(T z2AH6XVS-*ID9Mj#!N*qK9wL|Q{uYf4*}0UtWBx^2+h7RDG9)JJC~Zlvuy88#iY>u^uV4XChcmH`+sk=8=i0 zveMI&b=pfmS4JkH7<(Drx+nN~-`h*&VQqZ*<)!`%i5{IpUn=%uBvJn2&}Dy5>39R7 zc_>5RNdje!dd?n6hb?ocp?^XLy@vPM6ztLTi(9X8RljTCE6_?2pL;ka)#`>DT1ao@ z&!3EsLHYh)t}(fzWPL9WGaix>f*4(Mx@Q6f{{tz!s&74~7z*%vD?$0sWJ zeL*=PbY6s#wTsJ$w-4xQ%mo~j^NPNn=;khVQ9EGeU;(+xV^+z6B2t8Ybh>)H`;$ zvTTCSREB+Za;kDY`=F~Gza;h!XZh)8_+?8SR&zwh&WxN-B%@~m5!xE|Pb)E|3i0J@ z51TwbliEZSEMFC>L7Sn%8|h4#r-@H)zfD{ndBr`bc~aC(Wo}+@6~MK+eK6<(_M9WI z2qT#E!x&p9bC|WDxGk|(r%O%^#zY&qVT7cKZbkckh6(uU;K|rK<$B-4m#GoEUU4S0 zK4xXaXx3C3>jgi3_3Ac0wggGfkUn1$i5h=e{_ygLv_wSjTl^Jp;W~nbJLRy=yuM>3 z`(5HylVO}UNdjpuyQ~V$6_IK2(?M{PUQpt-Z=@&bwdMg`XsF{VkG9}-BApo(?UGk= zCo%GYJDHZ46Ul=3LQO~UWV$PR>`MiBlE&%sPKWrsQS7&m1*eUNr#;nl5I2YZWNh`Q#Mq(e(gV>74ADir(Z2c%tviBe7gP~x zp|&sz8iP^Ux(KNr&5;sd?lKwkz8xAbs)gSBN=oO) z;F-$!D`Q;5(^n&W?y_m%29#)Oj|t4>eTG)4w6JMR?>jvT#jNtK8Ba4J@`ujSvyJzB zW>jdvY8l<{9Ic7x5U!fyKmpwiERtV}7R|cxyvC1Nj(PE(YslnEtgYWJTC}fgJkp!C z@u<^}LN<_3>6xbVZGqqKNc$U}R+9Jv4n)J_EyFiF!~EbL8I=6yZ{ZTmR^a6X@<;(u5`auMUAHIG#kgv zf$BabR6V)5+S|F7I3kD}Li=w^@$rqBW_MreT9&N_lxy#s(L@?1XijdbJjEd1 z_$3{CL2%|z3pO|Mi)I@BQ7zUo;G|p`iJGl{n&E~lK#7i*gDfW$HtRkmQq!^Nw*yL8 z0iWkN0qPOy3_3D=dhyLKr;Ohii#GR@hE3}*)KCKPj^CcsLf!}@Q9vEPj#V)Pl|+CU{l$u5q|9f7GC@$f?4{kmhlijkGn5o}*e zAXka}h{yPpy>D)T=Ug6tp-C<{@*JT%z3yyfblL3HYCC+9)G&w%_=zWx;LL4+A6N(% zepW{E6MHmW&EYGqTNABdKf?}(m8R9+Yh*&D)P&%2IkfY>Q~->{LC96ZH=;ahQgz!! z#E=VgIGwWPA#}(@0Wt1aA*<*F=JLm-kP62 zGT>9Z=^2;B|GvqX5w?CyF`s;Q5JOH2bEOh+bw1+BW2T(%^3NWEO>OFKg~gl-R)CmD z5Wf@iljZ%@m>;%vf9w@Ica>PTyvu`%p@UI8xN3^$X`7jA0M}GIW-w6#W~C069NbSw z99b+M_SC_{R_|}Vv+%Yb>Bv4E_n(@Bkb(ZNZ)LPP^NVWjxvB99w>z0VpJDCk7?TNK zn$LTV9fXr<)5FvBlFi^ndVR?@r0>(4ZUZ%R*cIqR)I6kqV&26)aqT4zzO9UOmo;iC~AFU`G;ZGO^p{g%l&|Dm5R18(O zUX5x08`~G))F(N2AcYTOVHhcjNs1j>c73w9%^SJe_iVv--!jaLr|pp7K!Z1m#Y^F* z)7m=3tTce!z=GUG($owGaewGdiTs~gifd5|-q@W4?k=O#GEH7?uXbVy<`z zX(pryUFG8qPgp{oW*IK;9&_bkMB0uL%_(i$eU~Q?2ukh%70|3G#6yRWNs>mYaq^kk zx(AR=fxlz-3uI8qW?F2egW319+(BOqT?q)1UFiNhzIWI+#!eagOaq6~uBQ4W%`yZa zGUBSf2wN0wR~yI%|LvbbFc93?zLJx_Z)2(1)N@UGdhwm+2B9;|&1Mv%F@|42zfH-& z_z##Wd1Jcl2Nh`G4xO^iq|08eYt2wlQoFX|stexuI9d5cUCA+N5{^W&e+xbU_3ZaU z)~Leg!bB{Wxlpt)=;3}gzb#AyMSTj+UNWb6A>1-wGE`aKa)`yahB(eE#^&aG=DwxR zULIOSp_S@b`DHyVU1k6h!HuiONGF7ftOu`$;d$ullJ12I@H>ntG3I7s;*B41T-OE0 zt6_N~ea-4jOmv8~uLO^9Mc+9b-7+`_wQ*pFrPFl(!&(NX;|g>(9?kuGfFw9YVc!D9 zuvpT-;!VFikh+}pc~Q}~NHrNaApECeVfSo3?f~mL^+Hp`#2@F)nB^5UGro}h!MfSL znE$$!&+6yP=+d##{dZFi#tovMAcP)1_-0np7Mne8az*JL?p|?tQs7zDY$l>Z^|vJh zOnlS+^lNxOViS=*KmN?1Gihvpl7^v-eK5GZObg3D-U?j_W53HAPY-rd266yNWrX96 zVn_tpnV)XG1r1oYPvFshU{m* z$T+<*dhaO@a>iNUt(a-ioUQ1ACIbq@W!nK%hN2Ihw zW9asW6=r z+KWuCc|>+1R=kAm=c1?jUqwuNFHy^G!y7ux^w`D-Cw~@6VDpfRI}h{U%ous2Yi01_ z@HUqHg)_%;;1enWzur`gWdSkTcRxAo8Y8A4@v zL3+uyeQ$d%LiHT;{ zwiX)l?!vK@@XJ8Syz#sBmX?LR8Z)sTk#6KB@pM+omz`$6t`dNIk29hB6ug0wJB-Y+x!qATD`L^w1kNVLm!w+E`H0$F z_4wEdAW!@FxUSiM=ZxJjR?40+XQ;ywkX5k<$H0lUeX#Xwy0{~G?$L@h2hdo;42Pc2 z0Ed~(F(!~Oo)aLP5oa) zf62VHHxe#bHtBKi*}kw)1GSYblFa1D@HS;6uzVt|fZaNW14?v`qqRUIr&mTm0w`%p zRxpPOnsuR$8&x;1-^q*h>a0^|GctbjXa|;>*`ah1vr)>pz@<+o58};Ym_KXd|1keP zTWdJhBA+75>5RPeFVB`?`Qb7fG?1@)WP-_{;}W!Laj3O4E^Iz9pPIzbe8N84?WBXj z%;nS1slx5GiL=@vx$(pxumUGjHgNVabFTdIlX+6ySDseQ(O@d+sWzJP72UR{da z#&H@T>PEfTmFPXK;HQ);nN3sJ^koP*0h&}R$PeE37shAfZk8XJ6lgnyE!qd?b2XMN z0T-Ia+}wclh|J$;h)p4JJ#Foudo7Y~8^{l4OY$xv8_X~FXL zy&whD#YAJs=k0T4A#91yK{D4XFji6uk?yj>W!PJVq9nlsUP*oD0VJ=Fz ztR{=^ zFU(LYo2}08^Gl!K zJp5}W`AAJVpmbl`1wQh5c~9zY(g>R1$3nkBRwYbQU1$nKBhWdc*N)}FL;M*VSOBpF zOa_ZzSO#qj{Ye1Q4jcgfJ6Y+=ez>28<&JCF^E>3Zd;SIn1>LZ~!7EgB0meih6Qk4` zvU(rAe0|{Qw$F=FVFp+5omI>YfAxHuoW9(j1c4)q`c#-BHwC3!q088x2h%g*yj za%#hkNSVOL8!0yJg7b~kun48tN|W2){K4qw$6z4%Pliutd8!Ni-%LO}@C(%0 zvfl)VcqP2NKd~kIL-L6Pu8i*9X>K3szF5azTb%p6MGlb!rwy#_==Wmzf4&^5|D2VZ zG#&b4DR7|<211p8CJsV&rl12J}= zeRNvRaXCjAw3}Juau{hQB@$kPx>>ErNsk-W>{dQ_0w!`$Fe*qrD^iR_N0dN`a0k?Z z?sBsGW3^p&V}N>NLA1?qDX^jGc!*IV@?(5T>Ke>5Fp5x0c_EV=rr~^CF8gqP0FcNg zy!Qh`QA)?TYHZvB&Itmh_mmob{aUqH(!BKrfJ`jMKkUeP?`v#q{K^PwP1Zl;pB{pS z-e>0Z!K%FG9dCx_5uc{N(pFB4b^Dm*JjDb&CqHN?B^Gauib&cy6U_dr1})=@W*n^l zoG@E&up3Y!6DmoqwCCt#_WuJ24EQrW-=MfJFE{t)b&5%t$RdxGJH7-xdhhJ)3~PQr z#KJ`wD2>KeF=G?|!i+%fYTbyR!@|=P@|T469LJI2&IExDtU`1cR-rz9plwS(T-wD| zV*^8*%=1M%Y$hom+zU6L1W$~TViDqePnfl^_rS^fR@-AY1k7kmJesZV}e->?tgG zzVgA%K-KeK6(}rO;h2C}<%B!g2CT`F4@yas%`cub!FIR@#21<&_Xxd#&izGH=$|XN zN(3R^W-zGM%KYr@HOSrvy-Ml)GU5&z8=njFy+413E(M}7u;AO4(9()5xEz+YVvw$U z%n+ETHf2g=e4m)lM}8^HW-Qeb`d%`R*MjU6lU3Ee5FUB&*brE+4XoEO(RO$@JW;@V z;=5KB?C?wlB-P2%nk8WGzd3vYzCd!a8pe=l7z2m-LaLWp#n@>RJwbA@iY%aL`|z=I zAP?KHB4EuDy5|jZvZKJ+kB^Vf`|#nz7Dbwh1D8*hfF{`n1V8f`R$VitFwRYSkFs>= zn(kW=Eiy60d(=cGY4P#uWScWO!y}y=nnwree%ur=!z1&scpglR>y0FvxnH?^+|j&EOk!Ro2w3|2_$yH8*~7q>iRnxATjh zce&7JGOAFtqa<+6`tJGlOKu4iG>&4~1br9mLJe;-UZWpqAA@xa6?vU_Ety|@ukz7h zacCqfo_s0DviQYF6!7fMs<&Zer!!aolz!sk;_|>DCN)qZ`;$3Zb;_~#gKsA6YS?hW zXt~p1-G)AaF)t(;1%iH#FC@miAYLq7g4Y}=4ebA)HFwc#)?2n?-LXwIQ){k(0BVv} zmR>qO_}Jc6%zuOo^I+y^F63JZVxD1%`Br;g-P~k(sbBRyt2gUqNA}(>XOn27nLX!OO}-YLSdvj z0kJLw`5*vc(fr$MH}4!}dW(-Ptm;=ZJV1ev|pn zdXGX4Wo>9`3<@n<2-|2{4=tofNMjx}mWEV)L(0Xx3tKeS%INk?GP8(*Zr@%fwc_NG zU13UX$hvAqpX!WOFr8y?X4)@bEM;^1`qL*asei)#u|vWpEb%-ajuFD6O>oa|=Rq8m z4cka1PGZyG^-0Y=qx&sj1Otd*fL4U3=_3|EyeZRX{XOY>nqEU?b-iS?RWM-UBZ`m% z+_JdBsRmf~6rAK3po%X7Ss3ITMCkG%3?@BMe3ffUykx845^9 ztmLb6#BD(o1^38NeZG(tO~ttePH*a{eLrFF$rO-7OE*HzUxZR_U!EKt2jOWdn8qJa zGL=w_nl$yaj4=)-3eF5KXbAXW&4t-nCR$w+;qY!PheqE$uRBSt;#KHY{s~Un^!Kt% zr)l&E08wq4qtHfj0QG0^n%kZed&2%lhEI}Dp6N9S^uB_d9{^^>bTbZOoBaRU%gt{5893HF;?K5&-m&G0Ngrf1VQM7@=ca;%Pep(&X>jXpBAP9IbOEe z*9qgvM(9S+NW3Xa$0O7e+|MJXNNELE(ak}P^Mrs0=Q;1L*pI`?+Xi#@n+ojX-M>P{ zWCHjKU&)I`Dfs7E%ZTlhF2!hRaJJ`cXmv31`+$qonf+^C3U39#pvt-QF@2s9a&$`h)duy0wV?gPm8C{%&E!n`8o3NU>vgY_i%`7c%I&3ZVC~K8DP%t@PG$D ziEdfT_${Wk;_nk#@W`PQt5;|F4ofpkARaSjL)%^uVWrTXhR~#7rh-2-orS&4Eq7n| z0BHb#>h^Y6!xzN?p7-a9I{|CK&?O8|ys#PLUss-?8Tqo`zS~~B`q{8MlR6G66#c6h zkmC5{hO#Pdyp2yFw3v84w%@UIhQ*o1$J6c|7JonfvB~SXuZGZBxM_d_AF+2+fj6Tc znj=GaQyd7|Z$DU+`Y^g#1Sw~+lbR6jXT(JFVX2v_744r0oU?!QC*Vm*J{v^E-WV}=Z!XP4*0FB zkISr6TTSDSK2|rxPFw${Y(aqi@mCek!atne%Gb!Tn7;V_klQ&*a?e&rGx_GK!nyQ{1vufivkdkd}Tb0fX(&Yu6{3h;{<^QER z!zVjjchFrV^cd+?4KsuID&j~e#!%H!*MFn}vGX9;cODqpQ+Q6p2$x!k#iU3jLTdwK zlPdsAB58A}NjBmmtd*3oD~n;jq6Ha-2h-x*7lqn1ErQO1iC=3olHG@LkSIs#>8E}x zMvl1-|1(tpT>Nrto`QXfQ-3(bkxn&be8x8EBn}eJzLKlSM4|ZhGq>>OVWjkBt%T%{ zvIvromr9=%4$s6 z?ZWSj{&2gVtLR@Z_J|%yKCVhSvA8zFQP{4CxVsp_)mYslmECYoiwaq^H~E>w?LoTl z;)XV!4{zM=yWU6?4#h#^$rl~!rPj?CrTWxd448t``@CpyS1i3ldrpr_oNWT*DeL?) zcZAzPJxOVA%lGfMIyEp6P>-!;v^&>a%t^BU+3_;8Uf$UIZnL?3+W0DNoyvvMofG_l zYZ#Gq8nWn56CNv?PP;c^5sZZaO#_#oa4dwk{m0JmXs}B|h!Lv-UEJPXjvtOQ zo)XQ_s@NknJS8UStX!}!%%>zsXOHvYWpkH3A2)XA1dTZ|=lL$6FSeQrPWtWN8K(%t zNl9f3f3B(E+QMcu)L{39SM)bhcP!WmEBg$`>EX1#o)84IN-H+qx$=DKzI3d{6go_5 zpE?R(!%P}JBWg`XD0GacL%z+6a9+bbF$O{gu(HDl zUL4!CYG`oen){3UimqvUtmxi4+Bzii_o=jC5Yo-n57r}MFb~ONFzv`WI z_I6*JvZzVfQ4kosgX5#+!-jEFhCBI#~8 z_BR2Ye{?+7l-V|s@xgDCVO&)eH116L`gXMHxl#BhUyOuzGBY!WKgUpYG;^r6hUHuu zImPdem0o3;DSu~EBp&osa#{3ZK~5mV8r0qVeZ&yYMbei;vXk5;IS7eDz-@HKcJ6|LT_a zl6}8AJ6C^Zye0)%-HwEb&onMwSQ&4A0r0t-6=iO17F~d$995@4NPgOV`_|c#Mk?+E=-fHP}+OmQNQpVGmQ|%q5kW#1x8#oNPPnTQo#h5r6)&Dv`{MHh; zp`+g{!{g{m=8K0cPRkr**wX7V98oVG1j`ZNwWas1aJ35+Fl4D(ytiZy4CjHx_Xd5h zRrkMmCk)J`Zha=|+V>5gYPB0aR0Lr3tM6vg0OVhl^*0;d{PQQ&E{Kb*_+wT}rYLNf zI##QBy<+%H0BCgf+kynrdjDRGjDzo9cRGak#si)7dK{Yj-WN&_ZZ9@b{mreLIZ^xi zkx9J;@PghqzYHlx$WlL4-bn}6OszmUOKO&!Q7Pexmfis5^oFU*t>W)@wb?I`>-U2) z|5B4o#Kd(_?#ay~y+X!~9)y{{it`=j(wBI|hVTxK*ItYV-~a(y|F$aJ6-VB1ZfuqG zx{i{ov7XDfEQzcv?kxXNvN)mi>ThmKN;&<52luwIw9)tNr=-Sqox(J?U?` z6SR%)R^cb_kP}^E14O990CU<`&*?1|UK1WFc0d3D<|!@@xY7e*fI4rdf_zDO6Q9+D zY=L-I(N3A^6I#5*HNAAs87QQmE*$vE02t*!Luf4cFh9rFan*mX&w(o+1|W`^s!S@V zH_eB1utkZk`PS1X6n2A?A+FG{A}hQx=q(fxcNA+ys|N@EBAy3YgR>)ovIBl^bAPz} ztuqsVVbGk7OWryF_g8wZm895_-+^&zeyd+>vWKUoh8x)c0+O2-d?287^d6FUL`Vu^ z`#&qVO`VVf0iGI7Oj|2Ymk<>?CIKkBSBs{%oVcfl4ayasSUhl%)5N<|-(NWRR*-mN*RoVN^Q2g_wSlg z{q_MEbhBc>_XivW)0k+f-qAEQ-LGng_S-+#n=`@#e1{unJrCjwe4*8BPynXgn2IuW z^^j}7zos%qINEZ5-Y69a8~i+TH#{R2&S}MkChzKpl4Q~RLD`c*`*&L{GeSgmm^o5$9@aNEK9Q+ zhVRzRR4{42)hZ4Qj7$78^TPJ;d&)_q7}?t{uv6g;+3X3~$)YQDQq{c@fM`CPa_39O zz=1{MQYoI3Du}H#i2FCZUMBv2a~sGU_NK*ai4;Y`WxG#=r)BH+-U8J@0Z+gN{cGn^!^%@)-`HRn!Ko>_5-e^p2Ns88^OYsLS*eY5j%bfu3!}h;Wv2-s zV4NnY1cWnzk<=ZYjb?e|o(vLt>LswZ1N06;AUZI3PjSAqzobL>G}yYa%C zu?o=euL#3`_zm#7weQ#=mejeuCgPsI{#HY0cjoi0!I`^{o$edx#dC2QKh>6|DZql% zlbqn+wb@o|AB0PLk8*Obuz@Hr(s+fo&5ifG6-oeMC=}P_@o{8ZDRRv{d4OCq-`fwq z%aL}rtWqegUC(!T;i7dF;_>u`cq?)zJu2L1uF6>2NJSL~2a0?jA7pqA&>%S)KSJ~5 zCy0hKW)HIehlUlM-P|zgJlPEwxPo>p!X`f4`TG$QzJ>TLE;SLOr}oV0UhqQy;U?KZ z={%9QVn!<%_d&+}%dO7chjbIS@IR4A#C;Sy?a=OB?g9DbiLsk^c=y+Y*UIMg=|FXu zaP*xQPHs+41rSukS`;C@kK1MBg`s)s6I39!XeKZ@9qXA;^S$%u&yCPI#{?kdW@@k4 zqI_XA&Ah`GREs|qRR{B+bN~dEIEqr;5P8X(;D~sK&pg3DIMuLe<#;# zwQPSk^up$s$tw24!`E70E8jQwWxG+ywh1K)fnwe#b?#IM7}1Xrjbj;X^<%b78^sis+);o)Uogb| zQLt(I`!4smEVgL@chHu3c(Mmga`a)uJrE}_8ooMiRwnPK3XY-vG6oOMXFniZv7*vc3YY8h7fmm$`ccDD9ePn6TOBxSQxIp&Z2erp8wfw zj#UA>S?qQeemzBE0Z4SF$<8khUWC;ez7zkK@k=Y$9{))o(@;Y6Q=$ui;Gi%7_+iY_ z+fAz;GVTif{FyF=Qwj9XO+jxRlN=Y#Znhs=GSEU348?q{Z+owh9Scg>ABZOxPX0=Q|{vI!t1dt2cdu{&m0V4pie_~=jh;L?qf)57D4!Pl!z4-MTSI} zJkLHsLdr;Fg+c68)v>eRvFO29HUlP~>|-FfPlPP(S*2`#;^3?`o#oPnPu1sEt{uMo z)qng6bZcRayibisrdDM9ayPqJ+$W;wq4yEF z?4^$c4RNn9)gi2s$&5jRyLMd1Y^~sGJXI9ZVH?)T- zmE%E4F%H5&u3ww~(2Oz-cWUF%MN2L2ecB0h3K~q$&ZtvE25o=5nzP}OH?=}hov!N2 z32@nlq$6pUym&PsjdpX? z$!=LcxFi7btc);mITdl`g_$cEG$2@GeYp>~r|kP5Oc8HSRW>Ee$<2B9BJPdJ&Pz8Y z7$;# zQ&38&RGZJIH=2WIHz;ziwVxAtT!Aa79O+z*0VH~%TLRU7 zjBr_BJ7o3PPK`<876YO-M|zA{=~ZXE9%8b*B^aHp&0h_3EXfOYq$tLj7;z$n0+Qc5g2*-p^d`9(}70d4Rk16^|zObfLIJS zgL6Pd6%SMpA961%h;xhT6ox-51kxdI&!Uz2&c<($Oc$79%xwT{Zp4~aKQA`JcW18e=hu(kdolj`ZZJb zyY^Q^SOQ=$1CZ$89FO7p{cX4WbjpEb5IvJ8ITPx+U-+R2gf;=73kek>)7<^z%v!ix zM#A;+&~UKM_7YjZRnmE3w*}YQq(D5Mo?;@s#+_<|EI(|LV)Fna6hI*LfC{f*7$jom zCD(sR{?30^XRg8nJc4B4eMmObgDxQl&BPtEpprWsZiPW2kp4W4{#^HM0L&%09D(h; zMA$A#(Q==p3()Bo_YZ3Y|83P5EJ;|nj^K2bRJEDwyy%1LQ3((dDJy*2^F?U&J97#s zF8V>TTo`gTOtoxybuFl6%)N*&95#&5eA;SE^Qs@x{vhKdL7<2KjaHE6p+}_EP$Zc#+1g4w^%=)VZ`22PC;I;OR{C;?LdQxeFB`UiSksV+3q zc`-2x7X(JU|HA|pqzguLab%#&(eP?q1x$cbFLlv-nYd|qwF9K?=#K#ekHNd+)*p#b zIQAbL7m@Z%P@MKxkma9`1(S4wU_!_y&XX5Y!WaFaRVqGET@Z}V^#1cl9aK|o_rf@u zV|e;MzmcMTfeZ+BA#e~S6zZ#Z@bsVXwltobboLL&NHq%j(C*|7*$&Ds+erjZF!VX5 zyu8x?AU2FrgE{L5WOr>|e)f+TYoR*mAuf*xS&VyVBKrLyj2qN=+ls|O^hsRmNI^+w zea&DZRB`KN=3B;tANjcohqYMAKzUBN_`7CSW#I>Cmun?I3!GO?%d#0Fgc1$H9VpZ5 zP2eV@G{bew-wUSCs;XRyBi&Rbi-t0+wSCTL_VV0}fpFV5j4E@O zJ#{4B-t?X=0>itu!mEuv-fV?;f?U=t{{HZLvx4I$U12z+oXA6hoI?pdLhAuzjiIRl zQ~xh0a@Mig@it5js_LYpWuw@(pAJaUW1LIM@cDcrZXNpVjN;%q>H|g@wLI7CfT50^ z*mVF2X8yG?s&z*}i@WSPSRhSpoUn|phT%pqe>I5+hYM#kZF;92DBz&I@K|3WCPpy* zfsEy?lDpArF-wU&7!zrKyvccm&H%Vqsun?mX2j{S7&IY7b}ag)Z*#RPJ>&b}@)=Nv zK%^)#gBNW91B&8P7@Is`T|(nudMbm156GwfO8{MG2JFjm9(5+$KRt^Hb4NICgQ4Re zw!RYqjOB@KA`As-B}roMdUBAR#AaL1nv~vQB3Nq>OIn9{?bb^oK7RhEaxIJgme_Vl z2p9;f=Tvp}gB*$%>9IqC56`C@&15erG9C}6?z|wgu8p$bL79@?VB$He;HH7SSXQ|WYJ$;wus>nx(rru7}>h*5eAVMxp-kZ7yete%zK#aUzz zSjW6E^h6UDSEq-u)LTS^q^MV7am2PYygC8fde}>Xhl|o?RweisMuzZ(T4bw~ApV0z zeV-~l9|He_!JSagS+Kurhz=~VN|hmKlB9SWy0~GZR};EiFo~QPWHa5zZ7EPPln~Hh z5^}xT46}8(qgjHBQMDUrV|X$m1{zvE(AjJ~fAyRhkaW8BGjL zIR@=cx9DX}I@y5)&WId-;LhzrWPeqX}K!oy$VRnqkUL$!cUXr zL;i5^UT(sjt?fanhLk!*V?X~hAG&n0GU7Wa+P1xf&PvBqT{@NXTWOI2y7~{u>!@A( zh;oS;0)7JycPRCWdAQ(?PEg_{1N%d&h+(g@Ea?8a=JFUG0*kkj>-zIQIckwl>GizU zQl+Hiptmq}sY2$#n3tojTZz^`xYbp+b#_YP)6P`q)BEo9YORhP(#sv-tw|b#kZ_*W ze2X#QGpS~BlUA^2r>!t;O~1cK*{R+y(Te#=xInU?hCAzJu`&=Zv|izh&c5eWrIBVe z%t#%=U&lPh?xGrvw<9pkKVnaj2m9PL7#VWg%p2iqfTm9jd;36Wmr4gg2}mSPzV+>s#t$>X^f zAVHkA044k#tM*#i1gJ92GmErp^M+!Q46m*SnKiMRw-Bm9v^P6sC}J?v39G+^4be)E z=IgFC>}KVHu>p;}tm&M|KI)a<8Pk-1e@aiovx54?D}WRWRzY~cI$Cf zLM>PZhu^FBY<#;$ZXrN}OQEqV@ApS$z{VYpT@>JTs6x$@qpuIWJpN>-{OC19`3wGn z_@GtV-^oi1hI(u6{g+@>k1+&|;*y1=rdA9s-j)DD|Ml(L>U;LdUw>DtVi#9_F&`gDQv&f%CKmlMIk zKN_!Q$_-#*Vc;Dx_{mObG-mK~zp0t6!-hKDE$IdmPaar~ucnys{1|72OV6ue`5Tn% z6vy|TwK<%=9s<2f{|dgmF-Wv1&GUWWrA*se0Sl{o}%ZBe3HNx&hahv>(f|7BjT8$elrWcL zqYO+xEiU{oBTRycPidDe;#FHuTq8efOfO{OVCD`(2Ipp4y^6-BAwKh2n}L@$_`uS1DrAN!=l#o7Mbl1|hC% z-*N{t%Ap|O%HA6y@AZrN&BC;DfM{v4sh**0V$fTh7IqHi;q5`d8Lwa|#iVIV&0Lrp zwA{GmOJd)GpfksgxRhR93)SX!iuZbS#gVN!@A`)AXm7Rs@^9N3EM$x_TBhf9Dgq(b zhmb&^(9QBn$)&#LtC_K8(F*2kQx3Dz$!!(XF--IsG|lokD>S@`zUMD$jsJdBcrapA z&Y5xzVm>5$mXs*H@5*XXVabNTZ*344gl1Ar=oaEb#<>Y(?rX(v~7}GUu!hTPXzcd~soK-rt z5+cr83EshpQ4Ey2V+TgUoQ^M1HLEiWS+JUR{HaYrb(j9$l6yXG|Hxw&qZMzIg?qww z`}9xTORdfCx40GVJ!~rZ<(`(Qo-UYHz#Z(ZNoG(X@$mzFci%nedGe^mCdZDEpDvf) zQxx-dIKwm)_ujjKtZ*r6=*h<8zu{lj`7qx;mQ1r*xAHN5QpEWO$JW$1?{&vS<#$)} zDqKjZ$fWDTCV2rUtQHF@U9#@D!SP;?IyP~%T$pinV`(gB`@aJMX%f=6H@;lNQk6B8 zjMt(5*h0o!{a{!D#DS}})V)em$?Mh_®U&?Z+G|mUTdG)qFhWql-8vu2<=Eh=~ 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 Jd!~ED{{ax7y%+!h literal 0 HcmV?d00001 diff --git a/home/aselimov/waybar/config.jsonc b/home/aselimov/waybar/config.jsonc new file mode 100644 index 0000000..ba05fb9 --- /dev/null +++ b/home/aselimov/waybar/config.jsonc @@ -0,0 +1,64 @@ +{ + "reload_style_on_change": true, + "layer": "top", + "position": "top", + "height": 28, + "spacing": 8, + "modules-left": [ + "niri/workspaces" + ], + "modules-center": [ + "clock" + ], + "modules-right": [ + "pulseaudio", + "network", + "battery", + "tray" + ], + "niri/workspaces": { + "format": "{icon}", + "format-icons": { + "active": "●", + "default": "○" + } + }, + "niri/window": { + "format": "{title}", + "max-length": 45 + }, + "clock": { + "format": "{:%a %d %H:%M}", + "tooltip-format": "{:%A, %B %d, %Y}" + }, + "pulseaudio": { + "format": "{volume}% 󰕾", + "format-muted": "mute 󰖁", + "scroll-step": 5, + "on-click": "pavucontrol" + }, + "network": { + "format-wifi": "{signalStrength}% 󰖩", + "format-ethernet": "󰈀", + "format-disconnected": "󰖪", + "tooltip-format-wifi": "{essid}" + }, + "battery": { + "format": "{capacity}% {icon}", + "format-charging": "{capacity}% 󰂄", + "format-icons": [ + "󰁺", + "󰁼", + "󰁾", + "󰂀", + "󰁹" + ], + "states": { + "warning": 25, + "critical": 10 + } + }, + "tray": { + "spacing": 8 + } +} diff --git a/home/aselimov/waybar/style-dark.css b/home/aselimov/waybar/style-dark.css new file mode 100644 index 0000000..15d9f1e --- /dev/null +++ b/home/aselimov/waybar/style-dark.css @@ -0,0 +1,67 @@ +/* Ghostty: zenwritten-dark */ + +* { + font-family: "DepartureMono Nerd Font Mono"; + font-size: 12px; + min-height: 0; +} + +window#waybar { + background: #191919; + color: #bbbbbb; + border-bottom: 1px solid #3d3839; +} + +#workspaces, +#window, +#clock, +#pulseaudio, +#network, +#battery, +#tray { + padding: 0 7px; + margin: 0; + background: transparent; +} + +#workspaces button { + padding: 0 4px; + margin: 0; + color: #8e8e8e; + background: transparent; + border: none; + box-shadow: none; +} + +#workspaces button.active { + color: #bbbbbb; +} + +#workspaces button:hover { + color: #6099c0; + background: transparent; + box-shadow: none; + text-shadow: none; +} + +#window { + color: #8e8e8e; +} + +#clock { + font-weight: bold; +} + +#battery.warning { + color: #b77e64; +} + +#battery.critical { + color: #de6e7c; +} + +tooltip { + background: #191919; + color: #bbbbbb; + border: 1px solid #3d3839; +} diff --git a/home/aselimov/waybar/style-light.css b/home/aselimov/waybar/style-light.css new file mode 100644 index 0000000..2c97f57 --- /dev/null +++ b/home/aselimov/waybar/style-light.css @@ -0,0 +1,67 @@ +/* Ghostty: zenwritten-light */ + +* { + font-family: "DepartureMono Nerd Font Mono"; + font-size: 12px; + min-height: 0; +} + +window#waybar { + background: #f0edec; + color: #4f5e68; + border-bottom: 1px solid #cfc1ba; +} + +#workspaces, +#window, +#clock, +#pulseaudio, +#network, +#battery, +#tray { + padding: 0 7px; + margin: 0; + background: transparent; +} + +#workspaces button { + padding: 0 4px; + margin: 0; + color: #4f5e68; + background: transparent; + border: none; + box-shadow: none; +} + +#workspaces button.active { + color: #2c363c; +} + +#workspaces button:hover { + color: #286486; + background: transparent; + box-shadow: none; + text-shadow: none; +} + +#window { + color: #4f5e68; +} + +#clock { + font-weight: bold; +} + +#battery.warning { + color: #944927; +} + +#battery.critical { + color: #a8334c; +} + +tooltip { + background: #f0edec; + color: #4f5e68; + border: 1px solid #cfc1ba; +} diff --git a/home/aselimov/waybar/style.css b/home/aselimov/waybar/style.css new file mode 100644 index 0000000..5d2e04d --- /dev/null +++ b/home/aselimov/waybar/style.css @@ -0,0 +1,67 @@ +/* ~/.config/waybar/style.css */ + +* { + font-family: "DepartureMono Nerd Font Mono"; + font-size: 12px; + min-height: 0; +} + +window#waybar { + background: #f0edec; + color: #4f5e68; + border-bottom: 1px solid #cfc1ba; +} + +#workspaces, +#window, +#clock, +#pulseaudio, +#network, +#battery, +#tray { + padding: 0 7px; + margin: 0; + background: transparent; +} + +#workspaces button { + padding: 0 4px; + margin: 0; + color: #4f5e68; + background: transparent; + border: none; + box-shadow: none; +} + +#workspaces button.active { + color: #2c363c; +} + +#workspaces button:hover { + color: #286486; + background: transparent; + box-shadow: none; + text-shadow: none; +} + +#window { + color: #4f5e68; +} + +#clock { + font-weight: bold; +} + +#battery.warning { + color: #944927; +} + +#battery.critical { + color: #a8334c; +} + +tooltip { + background: #f0edec; + color: #4f5e68; + border: 1px solid #cfc1ba; +} diff --git a/home/aselimov/xremap.yml b/home/aselimov/xremap.yml new file mode 100644 index 0000000..359a32d --- /dev/null +++ b/home/aselimov/xremap.yml @@ -0,0 +1,23 @@ +keymap: + - name: macOS-style shortcuts + application: + not: + - com.mitchellh.ghostty + + remap: + Super-c: Ctrl-c + Super-v: Ctrl-v + Super-x: Ctrl-x + Super-a: Ctrl-a + Super-z: Ctrl-z + Super-f: Ctrl-f + Super-s: Ctrl-s + + - name: Ghostty shortcuts + application: + only: + - com.mitchellh.ghostty + + remap: + Super-c: Ctrl-Shift-c + Super-v: Ctrl-Shift-v diff --git a/home/neovim/neovim.nix b/home/neovim/neovim.nix new file mode 100644 index 0000000..4203342 --- /dev/null +++ b/home/neovim/neovim.nix @@ -0,0 +1,22 @@ +{config, pkgs, ... }: +{ + programs.neovim = { + enable = true; + defaultEditor = true; + + extraPackages = with pkgs; [ + fd + lua-language-server + stylua + tree-sitter + clang + deno + nodejs + shellcheck + ]; + }; + xdg.configFile."nvim" = { + source = ./neovim; + recursive = true; + }; +} diff --git a/home/neovim/neovim/GoogleStyle.xml b/home/neovim/neovim/GoogleStyle.xml new file mode 100644 index 0000000..1ad1036 --- /dev/null +++ b/home/neovim/neovim/GoogleStyle.xml @@ -0,0 +1,380 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/home/neovim/neovim/README.md b/home/neovim/neovim/README.md new file mode 100644 index 0000000..bd82b25 --- /dev/null +++ b/home/neovim/neovim/README.md @@ -0,0 +1,26 @@ +# Neovim Configuration + +This is a personal Neovim configuration tailored for a variety of programming languages. It uses `lazy.nvim` for plugin management. + +## Features + +* **Plugin Manager**: Plugins are managed using [lazy.nvim](https://github.com/folke/lazy.nvim). +* **LSP**: Language Server Protocol support is provided by [nvim-lspconfig](https://github.com/neovim/nvim-lspconfig), with LSP management through [mason.nvim](https://github.com/williamboman/mason.nvim). +* **Completion**: Autocompletion is handled by [nvim-cmp](https://github.com/hrsh7th/nvim-cmp) with [LuaSnip](https://github.com/L3MON4D3/LuaSnip) for snippets. +* **Fuzzy Finder**: [Telescope.nvim](https://github.com/nvim-telescope/telescope.nvim) is used for fuzzy finding files, buffers, and more. +* **Formatting**: Code formatting is done with [conform.nvim](https://github.com/stevearc/conform.nvim), supporting a range of formatters. +* **Themes**: The colorscheme is [zenbones.nvim](https://github.com/zenbones-theme/zenbones.nvim). + +## Supported Languages + +This configuration has specific support for the following languages: + +* C/C++ +* CUDA +* Java +* Python +* Lua +* LaTeX +* Markdown +* Shell +* Rust diff --git a/home/neovim/neovim/ftplugin/' b/home/neovim/neovim/ftplugin/' new file mode 100644 index 0000000..b38119d --- /dev/null +++ b/home/neovim/neovim/ftplugin/' @@ -0,0 +1,8 @@ +set spell spelllang=en_us +map : w % ! md2pdf % +map : w % ! md2docx % +map : !zathura %:r.pdf & +inoremap ;i ** <++>5hi +inoremap ;b **** <++>6hi +inoremap ;I ![](<++>)6hi +inoremap ;c ``````Oi diff --git a/home/neovim/neovim/ftplugin/cpp.lua b/home/neovim/neovim/ftplugin/cpp.lua new file mode 100644 index 0000000..93eb969 --- /dev/null +++ b/home/neovim/neovim/ftplugin/cpp.lua @@ -0,0 +1,2 @@ +vim.keymap.set({ "v", "n", "i" }, "", ": CMakeBuild") +vim.keymap.set({ "v", "n", "i" }, "", ": CMakeTest") diff --git a/home/neovim/neovim/ftplugin/cuda.lua b/home/neovim/neovim/ftplugin/cuda.lua new file mode 100644 index 0000000..93eb969 --- /dev/null +++ b/home/neovim/neovim/ftplugin/cuda.lua @@ -0,0 +1,2 @@ +vim.keymap.set({ "v", "n", "i" }, "", ": CMakeBuild") +vim.keymap.set({ "v", "n", "i" }, "", ": CMakeTest") diff --git a/home/neovim/neovim/ftplugin/gitcommit.vim b/home/neovim/neovim/ftplugin/gitcommit.vim new file mode 100644 index 0000000..9d9bb91 --- /dev/null +++ b/home/neovim/neovim/ftplugin/gitcommit.vim @@ -0,0 +1,2 @@ +set colorcolumn=72 +set tw=72 diff --git a/home/neovim/neovim/ftplugin/haskell.lua b/home/neovim/neovim/ftplugin/haskell.lua new file mode 100644 index 0000000..de76f40 --- /dev/null +++ b/home/neovim/neovim/ftplugin/haskell.lua @@ -0,0 +1,2 @@ +vim.opt.sw = 2 +vim.opt.ts = 2 diff --git a/home/neovim/neovim/ftplugin/html.vim b/home/neovim/neovim/ftplugin/html.vim new file mode 100644 index 0000000..db5897d --- /dev/null +++ b/home/neovim/neovim/ftplugin/html.vim @@ -0,0 +1,12 @@ +if &ft=="markdown" + finish +endif +map : w % ! firefox % & +inoremap ;d
<++>
kkf"a +inoremap ;a
<++>2F"a +inoremap ;p

O +inoremap ;b <++>2F>a +inoremap ;li
  • F>a +inoremap ;ll
    O +inoremap ;i <++>5F"i +inoremap ;s
    kO diff --git a/home/neovim/neovim/ftplugin/java.lua b/home/neovim/neovim/ftplugin/java.lua new file mode 100644 index 0000000..7aa6bc5 --- /dev/null +++ b/home/neovim/neovim/ftplugin/java.lua @@ -0,0 +1,4 @@ +vim.opt_local.shiftwidth = 2 +vim.opt_local.tabstop = 2 +vim.opt_local.tw = 100 +vim.opt_local.colorcolumn = "+1" diff --git a/home/neovim/neovim/ftplugin/javascript.lua b/home/neovim/neovim/ftplugin/javascript.lua new file mode 100644 index 0000000..9dfd152 --- /dev/null +++ b/home/neovim/neovim/ftplugin/javascript.lua @@ -0,0 +1,2 @@ +vim.opt_local.shiftwidth = 2 +vim.opt_local.tabstop = 2 diff --git a/home/neovim/neovim/ftplugin/mail.viim b/home/neovim/neovim/ftplugin/mail.viim new file mode 100644 index 0000000..ebc6c9e --- /dev/null +++ b/home/neovim/neovim/ftplugin/mail.viim @@ -0,0 +1 @@ +set textwidth=0 diff --git a/home/neovim/neovim/ftplugin/markdown.vim b/home/neovim/neovim/ftplugin/markdown.vim new file mode 100644 index 0000000..62f3455 --- /dev/null +++ b/home/neovim/neovim/ftplugin/markdown.vim @@ -0,0 +1,20 @@ +set spell spellfile="~/.config/nvim/spell/wordlist" +setlocal conceallevel=0 +setlocal concealcursor= +let g:markdown_fenced_languages = ['html', 'python', 'bash=sh', 'fortran'] +let g:vim_markdown_math = 1 +let g:tex_conceal = "abdmg" +let g:tex_superscripts= "[]" +let g:tex_subscripts= "[]" +let g:tex_conceal_frac=1 +hi clear Conceal +map : w % AsyncRun md2pdf "%" +map : w % AsyncRun md2beamer "%" +map : w % AsyncRun md2docx % +map : !zathura %:r.pdf & +inoremap b ****hi +inoremap i **i +command Stab Tabularize /\s\+\zs\s/l1c0 +nnoremap ;t :Tabularize /\s\+\zs\s/l1c0 +set tw=1000 +set colorcolumn= diff --git a/home/neovim/neovim/ftplugin/python.lua b/home/neovim/neovim/ftplugin/python.lua new file mode 100644 index 0000000..dd78580 --- /dev/null +++ b/home/neovim/neovim/ftplugin/python.lua @@ -0,0 +1,4 @@ +vim.opt_local.colorcolumn = "88" +vim.opt_local.textwidth = 1000 +vim.keymap.set("n", "x", "[hv]h]hk$;sc", { buffer = true }) +vim.keymap.set("n", "r", ":!python3 %", { buffer = true }) diff --git a/home/neovim/neovim/ftplugin/sh.lua b/home/neovim/neovim/ftplugin/sh.lua new file mode 100644 index 0000000..5093592 --- /dev/null +++ b/home/neovim/neovim/ftplugin/sh.lua @@ -0,0 +1,37 @@ +vim.opt_local.colorcolumn = "100" +vim.opt_local.textwidth = 100 +vim.opt_local.expandtab = true + +local function detect_indent(bufnr) + local lines = vim.api.nvim_buf_get_lines(bufnr, 0, 100, false) + local counts = {} + for _, line in ipairs(lines) do + local spaces = line:match("^( +)[^ ]") + if spaces then + local n = #spaces + counts[n] = (counts[n] or 0) + 1 + end + end + -- find the smallest indent size with meaningful usage + for _, size in ipairs({ 2, 4, 8 }) do + if (counts[size] or 0) > 0 then + return size + end + end + return 4 -- default +end + +local bufnr = vim.api.nvim_get_current_buf() +local size = detect_indent(bufnr) + +vim.opt_local.shiftwidth = size +vim.opt_local.tabstop = size +vim.opt_local.softtabstop = size + +-- update beautysh indent size for this buffer +local ok, conform = pcall(require, "conform") +if ok then + conform.formatters.beautysh = { + prepend_args = { "--indent-size", tostring(size) }, + } +end diff --git a/home/neovim/neovim/ftplugin/tex.vim b/home/neovim/neovim/ftplugin/tex.vim new file mode 100644 index 0000000..b9b19a5 --- /dev/null +++ b/home/neovim/neovim/ftplugin/tex.vim @@ -0,0 +1,21 @@ +set spell +set spelllang=en_us +map : w % make! +map : !make read & +inoremap ;f \begin{figure}\centering\includegraphics[]{<++>}\caption{<++>}\end{figure}2kf[a +inoremap ;2f \begin{figure}\centering\begin{subfigure}[t]{0.49\textwidth}\centering\includegraphics[width=\textwidth]{<++>}\caption{<++>}\end{subfigure}\begin{subfigure}[t]{0.49\textwidth}\centering\includegraphics[width=\textwidth]{<++>}\caption{<++>}\end{subfigure}\end{figure}8k/<++>"_c4l +inoremap ;3f \begin{figure}\centering\begin{subfigure}[t]{0.32\textwidth}\centering\includegraphics[width=\textwidth]{<++>}\caption{<++>}\end{subfigure}\begin{subfigure}[t]{0.32\textwidth}\centering\includegraphics[width=\textwidth]{<++>}\caption{<++>}\end{subfigure}\begin{subfigure}[t]{0.32\textwidth}\centering\includegraphics[width=\textwidth]{<++>}\caption{<++>}\end{subfigure}\end{figure}13k/<++>"_c4l +inoremap ;F \begin{frame}{<++>}\end{frame}O<++>-i +inoremap ;b \textbf{} <++>F{a +inoremap ;c \caption{}i +inoremap ;C \begin{columns}\column{0.49\textwidth}\column{0.49\textwidth}<++>\end{columns}2kO +inoremap ;ig \includegraphics[width=\textwidth]{<++>}16hi +inoremap ;li \begin{itemize}\item\end{itemize}kA +inoremap ;le \begin{enumerate}\item\end{enumerate}kA +inoremap ;ll \item +inoremap ;2t \begin{table}[h!]\begin{center}\begin{tabular}{c c} \toprule\textbf{} & \textbf{<++>} \\\midrule<++> & <++> \\\bottomrule\end{tabular}\end{center}\end{table}6kf{a +inoremap ;e \begin{equation}\label{<++>}\end{equation}kO +inoremap ;fw width=\textwidthF\i +inoremap ;fh height=\textheightF\i + +set tw=5000 diff --git a/home/neovim/neovim/ftplugin/typescript.lua b/home/neovim/neovim/ftplugin/typescript.lua new file mode 100644 index 0000000..9dfd152 --- /dev/null +++ b/home/neovim/neovim/ftplugin/typescript.lua @@ -0,0 +1,2 @@ +vim.opt_local.shiftwidth = 2 +vim.opt_local.tabstop = 2 diff --git a/home/neovim/neovim/init.lua b/home/neovim/neovim/init.lua new file mode 100644 index 0000000..7dc21e8 --- /dev/null +++ b/home/neovim/neovim/init.lua @@ -0,0 +1,38 @@ +-- set to true for light mode +local handle = io.popen( + "gsettings get org.gnome.desktop.interface color-scheme" +) + +if handle then + local scheme = handle:read("*a") + handle:close() + + if scheme:match("prefer%-dark") then + vim.g.light_mode = false + else + vim.g.light_mode = true + end +end +vim.g.vim_markdown_conceal = 0 + +-- Neovim configuration +-- Modularized structure for better organization + +-- Load core configuration +require("config.options") +require("config.keymaps") +require("config.autocmds") +require("config.lazy") + +-- Setup lazy.nvim and load plugins +require("lazy").setup({ + -- Import all plugin configurations + { import = "plugins" }, +}) + +-- Load additional configuration +require("colorizer").setup() +require("config.formatting") + +-- Add configuration to listen so we can live reload +pcall(vim.fn.serverstart, vim.fn.stdpath("run") .. "/nvim-" .. vim.fn.getpid()) diff --git a/home/neovim/neovim/lazy-lock.json b/home/neovim/neovim/lazy-lock.json new file mode 100644 index 0000000..dd40571 --- /dev/null +++ b/home/neovim/neovim/lazy-lock.json @@ -0,0 +1,50 @@ +{ + "LuaSnip": { "branch": "master", "commit": "a62e1083a3cfe8b6b206e7d3d33a51091df25357" }, + "asyncrun.vim": { "branch": "master", "commit": "98d3c0fdeb983f0ef62fe3a49da440f6d2c045ce" }, + "claude-code.nvim": { "branch": "main", "commit": "55c0cb59828fbc3bec744288286a46f5d5750b83" }, + "cmp-nvim-lsp": { "branch": "main", "commit": "cbc7b02bb99fae35cb42f514762b89b5126651ef" }, + "cmp-path": { "branch": "main", "commit": "c642487086dbd9a93160e1679a1327be111cbc25" }, + "cmp_luasnip": { "branch": "master", "commit": "98d9cb5c2c38532bd9bdb481067b20fea8f32e90" }, + "conform.nvim": { "branch": "master", "commit": "086a40dc7ed8242c03be9f47fbcee68699cc2395" }, + "fidget.nvim": { "branch": "main", "commit": "889e2e96edef4e144965571d46f7a77bcc4d0ddf" }, + "git-blame.nvim": { "branch": "main", "commit": "5c536e2d4134d064aa3f41575280bc8a2a0e03d7" }, + "iron.nvim": { "branch": "master", "commit": "88cd340407b959f1168af2a6ae5a3d180e6df32c" }, + "lazy.nvim": { "branch": "main", "commit": "306a05526ada86a7b30af95c5cc81ffba93fef97" }, + "lush.nvim": { "branch": "main", "commit": "9c60ec2279d62487d942ce095e49006af28eed6e" }, + "mason-lspconfig.nvim": { "branch": "main", "commit": "0a3b42c3e503df87aef6d6513e13148381495c3a" }, + "mason-tool-installer.nvim": { "branch": "main", "commit": "443f1ef8b5e6bf47045cb2217b6f748a223cf7dc" }, + "mason.nvim": { "branch": "main", "commit": "b03fb0f20bc1d43daf558cda981a2be22e73ac42" }, + "mini.nvim": { "branch": "main", "commit": "29447d7b0e1fc9bc9d0384953e63be3fae816736" }, + "neogen": { "branch": "main", "commit": "b2e78708876f4da507839726816010a68e33fec8" }, + "nvim-autopairs": { "branch": "master", "commit": "59bce2eef357189c3305e25bc6dd2d138c1683f5" }, + "nvim-cmp": { "branch": "main", "commit": "a1d504892f2bc56c2e79b65c6faded2fd21f3eca" }, + "nvim-colorizer.lua": { "branch": "master", "commit": "5cfe7fffbd01e17b3c1e14af85d5febdef88bd8c" }, + "nvim-jdtls": { "branch": "master", "commit": "77ccaeb422f8c81b647605da5ddb4a7f725cda90" }, + "nvim-lspconfig": { "branch": "master", "commit": "4b7fbaa239c5db6b36f424a4521ca9f1a401be33" }, + "nvim-markdown": { "branch": "master", "commit": "37850581fdaec153ce84af677d43bf8fce60813a" }, + "nvim-treesitter": { "branch": "main", "commit": "4916d6592ede8c07973490d9322f187e07dfefac" }, + "nvim-web-devicons": { "branch": "master", "commit": "c72328a5494b4502947a022fe69c0c47e53b6aa6" }, + "plenary.nvim": { "branch": "master", "commit": "74b06c6c75e4eeb3108ec01852001636d85a932b" }, + "rainbow_csv": { "branch": "master", "commit": "3dbbfd7d17536aebfb80f571255548495574c32b" }, + "render-markdown.nvim": { "branch": "main", "commit": "0fd43fb4b1f073931c4b481f5f3b7cea3749e190" }, + "runst.nvim": { "branch": "main", "commit": "9760caf73d8f020b6054d34f5527a85836a11a24" }, + "rustaceanvim": { "branch": "master", "commit": "88575b98bb9937fb9983ddec5e532b67e75ce677" }, + "snacks.nvim": { "branch": "main", "commit": "ad9ede6a9cddf16cedbd31b8932d6dcdee9b716e" }, + "tabular": { "branch": "master", "commit": "12437cd1b53488e24936ec4b091c9324cafee311" }, + "telescope-fzf-native.nvim": { "branch": "main", "commit": "6fea601bd2b694c6f2ae08a6c6fab14930c60e2c" }, + "telescope-live-grep-args.nvim": { "branch": "master", "commit": "53e9df55b3651dd7cf77e172f1e8c9a17407acca" }, + "telescope.nvim": { "branch": "master", "commit": "471eebb1037899fd942cc0f52c012f8773505da1" }, + "tex-conceal.vim": { "branch": "master", "commit": "93ae39d9222b0892684d02324b85ee9d3647bf8e" }, + "todo-comments.nvim": { "branch": "main", "commit": "31e3c38ce9b29781e4422fc0322eb0a21f4e8668" }, + "trouble.nvim": { "branch": "main", "commit": "bd67efe408d4816e25e8491cc5ad4088e708a69a" }, + "vim-abolish": { "branch": "master", "commit": "dcbfe065297d31823561ba787f51056c147aa682" }, + "vim-cmake": { "branch": "master", "commit": "896bc2189c8ade62d00a0ebedc59c65d9d9b0d65" }, + "vim-gitgutter": { "branch": "main", "commit": "21c977e8597c468c7dc76001389b0b430d46a4b0" }, + "vim-sleuth": { "branch": "master", "commit": "be69bff86754b1aa5adcbb527d7fcd1635a84080" }, + "vim-table-mode": { "branch": "master", "commit": "bb025308a45c67c7c8f0763ba37bc2ee3f534df0" }, + "vim-textobj-hydrogen": { "branch": "master", "commit": "e6f9a6b26a3524615bac347503c35327636fab72" }, + "vim-textobj-user": { "branch": "master", "commit": "41a675ddbeefd6a93664a4dc52f302fe3086a933" }, + "vim-tmux-navigator": { "branch": "master", "commit": "e41c431a0c7b7388ae7ba341f01a0d217eb3a432" }, + "windsurf.vim": { "branch": "main", "commit": "a8d47ec54fe82df920b2545559f767003e8a7f8d" }, + "zenbones.nvim": { "branch": "main", "commit": "22b7fb75593412e0dc81b4bdefae718e9e84aa82" } +} diff --git a/home/neovim/neovim/lua/autopair.lua b/home/neovim/neovim/lua/autopair.lua new file mode 100644 index 0000000..acd6975 --- /dev/null +++ b/home/neovim/neovim/lua/autopair.lua @@ -0,0 +1,62 @@ +local remap = vim.api.nvim_set_keymap +local npairs = require('nvim-autopairs') + +npairs.setup({ map_bs = false, map_cr = false }) + +vim.g.coq_settings = { keymap = { recommended = false } } + +-- these mappings are coq recommended mappings unrelated to nvim-autopairs +remap('i', '', [[pumvisible() ? "" : ""]], { expr = true, noremap = true }) +remap('i', '', [[pumvisible() ? "" : ""]], { expr = true, noremap = true }) +remap('i', '', [[pumvisible() ? "" : ""]], { expr = true, noremap = true }) +remap('i', '', [[pumvisible() ? "" : ""]], { expr = true, noremap = true }) + +-- skip it, if you use another global object +_G.MUtils= {} + +MUtils.CR = function() + if vim.fn.pumvisible() ~= 0 then + if vim.fn.complete_info({ 'selected' }).selected ~= -1 then + return npairs.esc('') + else + return npairs.esc('') .. npairs.autopairs_cr() + end + else + return npairs.autopairs_cr() + end +end +remap('i', '', 'v:lua.MUtils.CR()', { expr = true, noremap = true }) + +MUtils.BS = function() + if vim.fn.pumvisible() ~= 0 and vim.fn.complete_info({ 'mode' }).mode == 'eval' then + return npairs.esc('') .. npairs.autopairs_bs() + else + return npairs.autopairs_bs() + end +end +remap('i', '', 'v:lua.MUtils.BS()', { expr = true, noremap = true }) + + +-- put this to setup function and press to use fast_wrap +npairs.setup({ + fast_wrap = {}, +}) + +-- change default fast_wrap +npairs.setup({ + fast_wrap = { + map = '', + chars = { '{', '[', '(', '"', "'" }, + pattern = [=[[%'%"%>%]%)%}%,]]=], + end_key = '$', + before_key = 'h', + after_key = 'l', + cursor_pos_before = true, + keys = 'qwertyuiopzxcvbnmasdfghjkl', + manual_position = true, + highlight = 'Search', + highlight_grey='Comment' + }, +}) + +npairs.remove_rule('`') diff --git a/home/neovim/neovim/lua/config/autocmds.lua b/home/neovim/neovim/lua/config/autocmds.lua new file mode 100644 index 0000000..d1c5449 --- /dev/null +++ b/home/neovim/neovim/lua/config/autocmds.lua @@ -0,0 +1,35 @@ +-- Autocmds configuration + +-- Disable semantic tokens +vim.api.nvim_create_autocmd("LspAttach", { + callback = function(args) + local client = vim.lsp.get_client_by_id(args.data.client_id) + client.server_capabilities.semanticTokensProvider = nil + end, +}) + +-- Go back to last edited line when reopening file +vim.api.nvim_create_autocmd("BufRead", { + callback = function(opts) + vim.api.nvim_create_autocmd("BufWinEnter", { + once = true, + buffer = opts.buf, + callback = function() + local ft = vim.bo[opts.buf].filetype + local last_known_line = vim.api.nvim_buf_get_mark(opts.buf, '"')[1] + if + not (ft:match("gitcommit") and ft:match("gitrebase")) + and last_known_line > 1 + and last_known_line <= vim.api.nvim_buf_line_count(opts.buf) + then + vim.api.nvim_feedkeys([[g`"]], "nx", false) + end + end, + }) + end, +}) + +-- Disable semantic highlights +for _, group in ipairs(vim.fn.getcompletion("@lsp", "highlight")) do + vim.api.nvim_set_hl(0, group, {}) +end \ No newline at end of file diff --git a/home/neovim/neovim/lua/config/formatting.lua b/home/neovim/neovim/lua/config/formatting.lua new file mode 100644 index 0000000..5a4a5fb --- /dev/null +++ b/home/neovim/neovim/lua/config/formatting.lua @@ -0,0 +1,32 @@ +-- Formatting configuration + +-- Commands to disable/enable formatting +require("conform").setup({ + format_on_save = function(bufnr) + -- Disable with a global or buffer-local variable + if vim.g.disable_autoformat or vim.b[bufnr].disable_autoformat then + return + end + return { timeout_ms = 500, lsp_fallback = true } + end, +}) + +vim.api.nvim_create_user_command("FormatDisable", function(args) + if args.bang then + -- FormatDisable! will disable formatting just for this buffer + vim.b.disable_autoformat = true + else + vim.g.disable_autoformat = true + end +end, { + desc = "Disable autoformat-on-save", + bang = true, +}) + +vim.api.nvim_create_user_command("FormatEnable", function() + vim.b.disable_autoformat = false + vim.g.disable_autoformat = false +end, { + desc = "Re-enable autoformat-on-save", +}) + diff --git a/home/neovim/neovim/lua/config/keymaps.lua b/home/neovim/neovim/lua/config/keymaps.lua new file mode 100644 index 0000000..d831f0a --- /dev/null +++ b/home/neovim/neovim/lua/config/keymaps.lua @@ -0,0 +1,20 @@ +-- Keymaps configuration + +-- Clear search highlight +vim.keymap.set("n", "", "nohlsearch") + +-- Diagnostic keymaps +vim.keymap.set("n", "[d", vim.diagnostic.goto_prev, { desc = "Go to previous [D]iagnostic message" }) +vim.keymap.set("n", "]d", vim.diagnostic.goto_next, { desc = "Go to next [D]iagnostic message" }) +vim.keymap.set("n", "e", vim.diagnostic.open_float, { desc = "Show diagnostic [E]rror messages" }) +vim.keymap.set("n", "q", vim.diagnostic.setloclist, { desc = "Open diagnostic [Q]uickfix list" }) + +-- Window navigation +vim.keymap.set("n", "", "", { desc = "Move focus to the left window" }) +vim.keymap.set("n", "", "", { desc = "Move focus to the right window" }) +vim.keymap.set("n", "", "", { desc = "Move focus to the lower window" }) +vim.keymap.set("n", "", "", { desc = "Move focus to the upper window" }) + +-- LSP keymaps +vim.keymap.set("n", "gd", vim.lsp.buf.definition, { desc = "Goto Definition" }) + diff --git a/home/neovim/neovim/lua/config/lazy.lua b/home/neovim/neovim/lua/config/lazy.lua new file mode 100644 index 0000000..0f79ac9 --- /dev/null +++ b/home/neovim/neovim/lua/config/lazy.lua @@ -0,0 +1,16 @@ +-- Lazy.nvim plugin manager setup + +local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim" +if not vim.loop.fs_stat(lazypath) then + local lazyrepo = "https://github.com/folke/lazy.nvim.git" + vim.fn.system({ "git", "clone", "--filter=blob:none", "--branch=stable", lazyrepo, lazypath }) +end +vim.opt.rtp:prepend(lazypath) + +-- User dictionary for spell checking +local path = vim.fn.stdpath("config") .. "/spell/en.utf-8.add" +local words = {} + +for word in io.open(path, "r"):lines() do + table.insert(words, word) +end \ No newline at end of file diff --git a/home/neovim/neovim/lua/config/options.lua b/home/neovim/neovim/lua/config/options.lua new file mode 100644 index 0000000..ad92aef --- /dev/null +++ b/home/neovim/neovim/lua/config/options.lua @@ -0,0 +1,44 @@ +-- Options configuration +vim.g.mapleader = ";" +vim.g.maplocalleader = ";" + +-- Line numbers +vim.opt.number = true +vim.opt.relativenumber = true + +-- Mouse +vim.opt.mouse = "" + +-- Mode display +vim.opt.showmode = false + +-- Search +vim.opt.ignorecase = true +vim.opt.smartcase = true +vim.opt.hlsearch = true + +-- Sign column +vim.opt.signcolumn = "yes" + +-- Splits +vim.opt.splitright = true +vim.opt.splitbelow = true + +-- Whitespace display +vim.opt.list = true +vim.opt.listchars = { tab = "» ", trail = "·", nbsp = "␣" } + +-- Live substitution preview +vim.opt.inccommand = "split" + +-- Cursor line +vim.opt.cursorline = true + +-- General settings +vim.opt.shiftwidth = 4 +vim.opt.tabstop = 4 +vim.opt.expandtab = true +vim.opt.tw = 100 +vim.opt.colorcolumn = "+1" +vim.opt.termguicolors = true +vim.opt.pumheight = 5 diff --git a/home/neovim/neovim/lua/iron.lua b/home/neovim/neovim/lua/iron.lua new file mode 100644 index 0000000..73bad0c --- /dev/null +++ b/home/neovim/neovim/lua/iron.lua @@ -0,0 +1,45 @@ +local iron = require("iron.core") +local view = require("iron.view") + +iron.setup { + config = { + -- Whether a repl should be discarded or not + scratch_repl = true, + -- Your repl definitions come here + repl_definition = { + python = { + -- Can be a table or a function that + -- returns a table (see below) + command = "ipython --no-autoindent" + } + }, + -- How the repl window will be displayed + -- See below for more information + repl_open_cmd = require('iron.view').bottom(20), + }, + -- Iron doesn't set keymaps by default anymore. + -- You can set them here or manually add keymaps to the functions in iron.core + keymaps = { + visual_send = ";sc", + send_file = ";sf", + send_line = ";sl", + cr = ";s", + interrupt = ";s", + exit = ";sq", + clear = ";cl", + }, + -- If the highlight is on, you can change how it looks + -- For the available options, check nvim_set_hl + highlight = { + italic = true + }, + ignore_blank_lines = true, -- ignore blank lines when sending visual select lines +} + +-- iron also has a list of commands, see :h iron-commands for all available commands +vim.keymap.set('n', ';rs', 'IronRepl') +vim.keymap.set('n', ';rr', 'IronRestart') +vim.keymap.set('n', ';rf', 'IronFocus') +vim.keymap.set('n', ';rh', 'IronHide') + +repl_open_cmd = "horizontal bot 20 split" diff --git a/home/neovim/neovim/lua/plugins/autopairs.lua b/home/neovim/neovim/lua/plugins/autopairs.lua new file mode 100644 index 0000000..afc64f8 --- /dev/null +++ b/home/neovim/neovim/lua/plugins/autopairs.lua @@ -0,0 +1,59 @@ +-- Autopairs plugin configuration + +return { + "windwp/nvim-autopairs", + config = function() + local remap = vim.api.nvim_set_keymap + local npairs = require("nvim-autopairs") + + npairs.setup({ map_bs = false, map_cr = false }) + + _G.MUtils = {} + + MUtils.CR = function() + if vim.fn.pumvisible() ~= 0 then + if vim.fn.complete_info({ "selected" }).selected ~= -1 then + return npairs.esc("") + else + return npairs.esc("") .. npairs.autopairs_cr() + end + else + return npairs.autopairs_cr() + end + end + remap("i", "", "v:lua.MUtils.CR()", { expr = true, noremap = true }) + + MUtils.BS = function() + if vim.fn.pumvisible() ~= 0 and vim.fn.complete_info({ "mode" }).mode == "eval" then + return npairs.esc("") .. npairs.autopairs_bs() + else + return npairs.autopairs_bs() + end + end + remap("i", "", "v:lua.MUtils.BS()", { expr = true, noremap = true }) + + -- put this to setup function and press to use fast_wrap + npairs.setup({ + fast_wrap = {}, + }) + + -- change default fast_wrap + npairs.setup({ + fast_wrap = { + map = "", + chars = { "{", "[", "(", '"', "'" }, + pattern = [=[[%'%"%>%]%)%}%,]]=], + end_key = "$", + before_key = "h", + after_key = "l", + cursor_pos_before = true, + keys = "qwertyuiopzxcvbnmasdfghjkl", + manual_position = true, + highlight = "Search", + highlight_grey = "Comment", + }, + }) + + npairs.remove_rule("`") + end, +} \ No newline at end of file diff --git a/home/neovim/neovim/lua/plugins/completion.lua b/home/neovim/neovim/lua/plugins/completion.lua new file mode 100644 index 0000000..660df97 --- /dev/null +++ b/home/neovim/neovim/lua/plugins/completion.lua @@ -0,0 +1,137 @@ +-- Completion and snippets configuration + +return { + { + "hrsh7th/nvim-cmp", + event = "InsertEnter", + dependencies = { + { + "L3MON4D3/LuaSnip", + build = (function() + if vim.fn.has("win32") == 1 or vim.fn.executable("make") == 0 then + return + end + return "make install_jsregexp" + end)(), + config = function() + require("luasnip.loaders.from_snipmate").lazy_load({ paths = "./snippets" }) + local ls = require("luasnip") + + vim.keymap.set({ "i", "s" }, "j", function() + if ls.expand_or_locally_jumpable() then + ls.expand_or_jump() + end + end, { silent = true }) + vim.keymap.set({ "i", "s" }, "k", function() + if ls.locally_jumpable(-1) then + ls.jump(-1) + end + end, { silent = true }) + + local snip = ls.snippet + local node = ls.snippet_node + local text = ls.text_node + local insert = ls.insert_node + local func = ls.function_node + local choice = ls.choice_node + local dynamicn = ls.dynamic_node + + ls.add_snippets(nil, { + python = { + snip({ + trig = "imp", + namr = "Imports", + dscr = "Comments for imports", + }, { + text({ "# Core modules", "" }), + insert(1), + text({ "", "# Non-core modules", "" }), + insert(2), + text({ "", "# SEI modules", "" }), + insert(3), + }), + }, + tex = { + snip({ + trig = "input", + namr = "Input Cell", + dscr = "Cell for SEIInputTable", + }, { + text({ "\\hypertarget{" }), + insert(1), + text({ "}{" }), + insert(2), + text({ "} & \\SEICell{", "\t" }), + insert(3), + text({ "", "}\\\\", "" }), + }), + }, + markdown = { + snip({ + trig = "img", + namr = "image", + dscr = "Markdown img", + }, { + text({ "![" }), + insert(1), + text("]("), + insert(2), + text(")"), + }), + snip({ + trig = "header", + namr = "header", + dscr = "Yaml header for markdown notes", + }, { + text({ "---", "" }), + text("title: "), + insert(1), + text({ "", "author: Alex Selimov", "" }), + text("tags: ["), + insert(2), + text({ "]", "", "" }), + text({ "---", "" }), + }), + }, + }) + end, + }, + "saadparwaiz1/cmp_luasnip", + "hrsh7th/cmp-nvim-lsp", + "hrsh7th/cmp-path", + }, + config = function() + local cmp = require("cmp") + local luasnip = require("luasnip") + luasnip.config.setup({}) + cmp.setup({ + snippet = { + expand = function(args) + luasnip.lsp_expand(args.body) + end, + }, + completion = { completeopt = "menu,menuone,noinsert" }, + + mapping = cmp.mapping.preset.insert({ + [""] = cmp.mapping.select_next_item(), + [""] = cmp.mapping.select_prev_item(), + [""] = cmp.mapping.confirm({ select = true }), + }), + sources = { + { name = "nvim_lsp" }, + { name = "luasnip" }, + { name = "path" }, + }, + }) + end, + snippet = { + expand = function(args) + local luasnip = require("luasnip") + if not luasnip then + return + end + luasnip.lsp_expand(args.body) + end, + }, + }, +} \ No newline at end of file diff --git a/home/neovim/neovim/lua/plugins/dev-tools.lua b/home/neovim/neovim/lua/plugins/dev-tools.lua new file mode 100644 index 0000000..94c606e --- /dev/null +++ b/home/neovim/neovim/lua/plugins/dev-tools.lua @@ -0,0 +1,74 @@ +-- Development tools and utilities + +return { + { + "codersauce/runst.nvim", + lazy = false, + opts = {}, + config = function() + require("runst").setup() + end, + }, + + { + "mrcjkb/rustaceanvim", + version = "^6", -- Recommended + lazy = false, -- This plugin is already lazy + }, + + { + "cdelledonne/vim-cmake", + cond = vim.fn.executable("cmake") == 1, + }, + + { + "f-person/git-blame.nvim", + opts = { + enabled = false, + message_template = "<> • ", + }, + }, + + { + "Exafunction/windsurf.vim", + event = "BufEnter", + cond = os.getenv("NVIM_USE_WINDSURF") == "true", + }, + + { + "danymat/neogen", + setup = { + snippet_engine = "luasnip", + }, + config = function() + require("neogen").setup({}) + local opts = { noremap = true, silent = true } + vim.api.nvim_set_keymap("n", "dg", ":lua require('neogen').generate()", opts) + end, + version = "*", + }, + { + "greggh/claude-code.nvim", + dependencies = { + "nvim-lua/plenary.nvim", -- Required for git operations + }, + config = function() + require("claude-code").setup({ + window = { + split_ratio = 0.3, -- Percentage of screen for the terminal window (height for horizontal, width for vertical splits) + position = "vertical", -- Position of the window: "botright", "topleft", "vertical", "float", etc. + }, + keymaps = { + toggle = { + normal = "cc", -- Normal mode keymap for toggling Claude Code, false to disable + terminal = "cc", -- Terminal mode keymap for toggling Claude Code, false to disable + variants = { + continue = "cC", -- Normal mode keymap for Claude Code with continue flag + verbose = "cV", -- Normal mode keymap for Claude Code with verbose flag + }, + }, + }, + }) + end, + }, +} diff --git a/home/neovim/neovim/lua/plugins/editor.lua b/home/neovim/neovim/lua/plugins/editor.lua new file mode 100644 index 0000000..b385e85 --- /dev/null +++ b/home/neovim/neovim/lua/plugins/editor.lua @@ -0,0 +1,23 @@ +-- Editor enhancement plugins + +return { + "christoomey/vim-tmux-navigator", + "kana/vim-textobj-user", + "mechatroner/rainbow_csv", + { + "GCBallesteros/vim-textobj-hydrogen", + dependencies = { + "kana/vim-textobj-user", + }, + }, + "godlygeek/tabular", + "tpope/vim-sleuth", + "catgoose/nvim-colorizer.lua", + { "ixru/nvim-markdown", ft = "markdown" }, + "KeitaNakamura/tex-conceal.vim", + "skywind3000/asyncrun.vim", + "airblade/vim-gitgutter", + "tpope/vim-abolish", + "dhruvasagar/vim-table-mode", +} + diff --git a/home/neovim/neovim/lua/plugins/formatting.lua b/home/neovim/neovim/lua/plugins/formatting.lua new file mode 100644 index 0000000..1faa2a2 --- /dev/null +++ b/home/neovim/neovim/lua/plugins/formatting.lua @@ -0,0 +1,40 @@ +-- Formatting plugins + +return { + { + "stevearc/conform.nvim", + opts = { + notify_on_error = false, + format_on_save = { + timeout_ms = 2500, + lsp_fallback = true, + }, + formatters = { + beautysh = { + prepend_args = { "--indent-size", "2" }, + }, + golines = { + prepend_args = { + "--max-len=100", + "--base-formatter=gofmt", + }, + }, + }, + formatters_by_ft = { + lua = { "stylua" }, + python = { "ruff_format" }, + cpp = { "clang-format" }, + c = { "clang-format" }, + sh = { "beautysh" }, + tex = { "latexindent" }, + java = { "google-java-format" }, + javascript = { "prettier" }, + typescript = { "deno_fmt" }, + typescriptreact = { "deno_fmt" }, + svelte = { "deno_fmt" }, + json = { "prettier" }, + go = { "golines" }, + }, + }, + }, +} diff --git a/home/neovim/neovim/lua/plugins/iron.lua b/home/neovim/neovim/lua/plugins/iron.lua new file mode 100644 index 0000000..7defb3d --- /dev/null +++ b/home/neovim/neovim/lua/plugins/iron.lua @@ -0,0 +1,51 @@ +-- Iron REPL plugin configuration + +return { + "Vigemus/iron.nvim", + ft = { "python" }, + config = function() + local iron = require("iron.core") + local view = require("iron.view") + + iron.setup({ + config = { + -- Whether a repl should be discarded or not + scratch_repl = true, + -- Your repl definitions come here + repl_definition = { + python = { + -- Can be a table or a function that + -- returns a table (see below) + command = "ipython --no-autoindent", + }, + }, + -- How the repl window will be displayed + -- See below for more information + repl_open_cmd = require("iron.view").bottom(20), + }, + -- Iron doesn't set keymaps by default anymore. + -- You can set them here or manually add keymaps to the functions in iron.core + keymaps = { + visual_send = "sc", + send_file = "sf", + send_line = "sl", + cr = "s", + interrupt = "s", + exit = "sq", + clear = "cl", + }, + -- If the highlight is on, you can change how it looks + -- For the available options, check nvim_set_hl + highlight = { + italic = true, + }, + ignore_blank_lines = false, -- ignore blank lines when sending visual select lines + }) + vim.keymap.set("n", ";rs", "IronRepl") + vim.keymap.set("n", ";rr", "IronRestart") + vim.keymap.set("n", ";rf", "IronFocus") + vim.keymap.set("n", ";rh", "IronHide") + + repl_open_cmd = "horizontal bot 20 split" + end, +} \ No newline at end of file diff --git a/home/neovim/neovim/lua/plugins/lsp.lua b/home/neovim/neovim/lua/plugins/lsp.lua new file mode 100644 index 0000000..6bcd67b --- /dev/null +++ b/home/neovim/neovim/lua/plugins/lsp.lua @@ -0,0 +1,262 @@ +-- LSP configuration + +return { + { + "mfussenegger/nvim-jdtls", + ft = "java", + config = function() + local jdtls = require("jdtls") + + -- Find project root + local root_dir = require("jdtls.setup").find_root({ ".git", "mvnw", "gradlew" }) + + -- Path to your exported Eclipse/IntelliJ style xml + local style_path = vim.fn.expand("~/.config/nvim/formatters/eclipse-java-google-style.xml") + + local java_home = os.getenv("NVIM_JDTLS_JAVA_HOME") + + local config = { + cmd = { + java_home .. "/bin/java", + "-Declipse.application=org.eclipse.jdt.ls.core.id1", + "-Dosgi.bundles.defaultStartLevel=4", + "-Declipse.product=org.eclipse.jdt.ls.core.product", + "-Dlog.protocol=true", + "-Dlog.level=ALL", + "-Xms1g", + "--add-modules=ALL-SYSTEM", + "--add-opens", + "java.base/java.util=ALL-UNNAMED", + "--add-opens", + "java.base/java.lang=ALL-UNNAMED", + "-jar", + vim.fn.glob( + vim.fn.stdpath("data") .. "/mason/packages/jdtls/plugins/org.eclipse.equinox.launcher_*.jar" + ), + "-configuration", + vim.fn.glob(vim.fn.stdpath("data") .. "/mason/packages/jdtls/config_mac"), + "-data", + vim.fn.expand("~/.cache/jdtls-workspace/") .. "/" .. root_dir:gsub(":", "_"), + }, + root_dir = root_dir, + settings = { + java = { + format = { + enabled = false, + }, + }, + }, + } + + jdtls.start_or_attach(config) + end, + }, + + { + "neovim/nvim-lspconfig", + opts = { + autoformat = false, + }, + dependencies = { + "williamboman/mason.nvim", + "williamboman/mason-lspconfig.nvim", + "WhoIsSethDaniel/mason-tool-installer.nvim", + { "j-hui/fidget.nvim", opts = {} }, + }, + config = function() + local util = require("lspconfig.util") + + vim.api.nvim_create_autocmd("LspAttach", { + group = vim.api.nvim_create_augroup("kickstart-lsp-attach", { clear = true }), + callback = function(event) + local map = function(keys, func, desc) + vim.keymap.set("n", keys, func, { buffer = event.buf, desc = "LSP: " .. desc }) + end + + map("gr", require("telescope.builtin").lsp_references, "[G]oto [R]eferences") + map("I", require("telescope.builtin").lsp_implementations, "[G]oto [I]mplementation") + map("D", require("telescope.builtin").lsp_type_definitions, "Type [D]efinition") + map("ds", require("telescope.builtin").lsp_document_symbols, "[D]ocument [S]ymbols") + map( + "ws", + require("telescope.builtin").lsp_dynamic_workspace_symbols, + "[W]orkspace [S]ymbols" + ) + map("rn", vim.lsp.buf.rename, "[R]e[n]ame") + map("ca", vim.lsp.buf.code_action, "[C]ode [A]ction") + map("K", vim.lsp.buf.hover, "Hover Documentation") + map("gD", vim.lsp.buf.declaration, "[G]oto [D]eclaration") + + local client = vim.lsp.get_client_by_id(event.data.client_id) + if client and client.server_capabilities.documentHighlightProvider then + vim.api.nvim_create_autocmd({ "CursorHold", "CursorHoldI" }, { + buffer = event.buf, + callback = vim.lsp.buf.document_highlight, + }) + + vim.api.nvim_create_autocmd({ "CursorMoved", "CursorMovedI" }, { + buffer = event.buf, + callback = vim.lsp.buf.clear_references, + }) + end + end, + }) + + local capabilities = vim.lsp.protocol.make_client_capabilities() + capabilities = vim.tbl_deep_extend("force", capabilities, require("cmp_nvim_lsp").default_capabilities()) + + local function is_executable(path) + return path and vim.fn.executable(path) == 1 + end + + local function get_python_path(root_dir) + local candidates = {} + local active_venv = os.getenv("VIRTUAL_ENV") + + if root_dir then + vim.list_extend(candidates, { + util.path.join(root_dir, ".venv", "bin", "python"), + util.path.join(root_dir, "venv", "bin", "python"), + }) + end + + if active_venv then + table.insert(candidates, util.path.join(active_venv, "bin", "python")) + end + + table.insert(candidates, vim.fn.exepath("python3")) + table.insert(candidates, vim.fn.exepath("python")) + + for _, path in ipairs(candidates) do + if is_executable(path) then + return path + end + end + end + + local servers = { + clangd = { + filetypes = { + "c", + "cpp", + "cuda", + }, + }, + yamlls = { settings = { yaml = { format = { enable = true } } } }, + pyright = { + root_dir = util.root_pattern( + "pyproject.toml", + "uv.lock", + "setup.py", + "setup.cfg", + "requirements.txt", + ".git" + ), + before_init = function(_, config) + local python_path = get_python_path(config.root_dir) + config.settings = vim.tbl_deep_extend("force", config.settings or {}, { + python = { + pythonPath = python_path, + analysis = { + autoSearchPaths = true, + useLibraryCodeForTypes = true, + }, + }, + }) + end, + }, + ruff = { + on_attach = function(client) + client.server_capabilities.documentFormattingProvider = false + end, + }, + jsonls = {}, + denols = {}, + svelte = {}, + arduino_language_server = {}, + zls = {}, + ltex = { + settings = { + ltex = { + enabled = { "latex", "tex", "bib", "markdown" }, + language = "auto", + diagnosticSeverity = "information", + sentenceCacheSize = 2000, + latex = { + commands = { + ["\\hypertarget"] = "dummy", + }, + }, + dictionary = (function() + local files = {} + for _, file in ipairs(vim.api.nvim_get_runtime_file("dict/*", true)) do + local lang = vim.fn.fnamemodify(file, ":t:r") + local fullpath = vim.fs.normalize(file, ":p") + files[lang] = { ":" .. fullpath } + end + + if files.default then + for lang, _ in pairs(files) do + if lang ~= "default" then + vim.list_extend(files[lang], files.default) + end + end + files.default = nil + end + return files + end)(), + }, + }, + }, + lua_ls = { + settings = { + Lua = { + runtime = { version = "LuaJIT" }, + workspace = { + checkThirdParty = false, + library = { + "${3rd}/luv/library", + unpack(vim.api.nvim_get_runtime_file("", true)), + }, + }, + }, + }, + }, + } + + require("mason").setup() + + local ensure_installed = vim.tbl_keys(servers or {}) + vim.list_extend(ensure_installed, { + "jdtls", + "stylua", + "ruff", + "clang-format", + "beautysh", + "latexindent", + "prettier", + }) + require("mason-tool-installer").setup({ ensure_installed = ensure_installed }) + + require("mason-lspconfig").setup({ + handlers = { + function(server_name) + local server = servers[server_name] or {} + require("lspconfig")[server_name].setup({ + cmd = server.cmd, + on_attach = server.on_attach, + before_init = server.before_init, + on_init = function(client) + client.offset_encoding = "utf-8" + end, + root_dir = server.root_dir, + settings = server.settings, + filetypes = server.filetypes, + capabilities = vim.tbl_deep_extend("force", {}, capabilities, server.capabilities or {}), + }) + end, + }, + }) + end, + }, +} diff --git a/home/neovim/neovim/lua/plugins/telescope.lua b/home/neovim/neovim/lua/plugins/telescope.lua new file mode 100644 index 0000000..abe283d --- /dev/null +++ b/home/neovim/neovim/lua/plugins/telescope.lua @@ -0,0 +1,36 @@ +-- Telescope fuzzy finder configuration + +return { + "nvim-telescope/telescope.nvim", + event = "VeryLazy", + branch = "master", + dependencies = { + "nvim-lua/plenary.nvim", + { "nvim-tree/nvim-web-devicons" }, + { "nvim-telescope/telescope-live-grep-args.nvim" }, + { + "nvim-telescope/telescope-fzf-native.nvim", + build = "make", + cond = function() + return vim.fn.executable("make") == 1 + end, + }, + }, + config = function() + require("telescope").setup({ + defaults = vim.tbl_extend("force", require("telescope.themes").get_ivy(), { + path_display = { "smart" }, + }), + }) + + pcall(require("telescope").load_extension("live_grep_args")) + local builtin = require("telescope.builtin") + vim.keymap.set("n", "", require("telescope").extensions.live_grep_args.live_grep_args) + vim.keymap.set( + "x", + "", + "\"zy:lua require('telescope').extensions.live_grep_args.live_grep_args(require('telescope.themes').get_ivy({}))z" + ) + vim.keymap.set("n", "", builtin.find_files) + end, +} \ No newline at end of file diff --git a/home/neovim/neovim/lua/plugins/ui.lua b/home/neovim/neovim/lua/plugins/ui.lua new file mode 100644 index 0000000..416f4f5 --- /dev/null +++ b/home/neovim/neovim/lua/plugins/ui.lua @@ -0,0 +1,143 @@ +-- UI and appearance plugins + +return { + { + "zenbones-theme/zenbones.nvim", + lazy = false, + priority = 1000, + dependencies = "rktjmp/lush.nvim", + config = function() + if vim.g.light_mode then + vim.o.background = "light" + else + vim.o.background = "dark" + end + vim.cmd.colorscheme("zenwritten") + vim.api.nvim_set_hl(0, "Normal", { bg = "none" }) + vim.api.nvim_set_hl(0, "NormalFloat", { bg = "none" }) + end, + }, + + { "folke/todo-comments.nvim", dependencies = { "nvim-lua/plenary.nvim" }, opts = { signs = false } }, + + { + "echasnovski/mini.nvim", + keys = { + { + "m", + function() + require("mini.files").open(vim.api.nvim_buf_get_name(0), true) + end, + desc = "Open mini.files (Directory of Current File)", + }, + }, + config = function() + require("mini.files").setup() + local getWords = require("utils.statusline").getWords + require("mini.statusline").setup({ + content = { + active = function() + local mode, mode_hl = MiniStatusline.section_mode({ trunc_width = 120 }) + local git = MiniStatusline.section_git({ trunc_width = 75 }) + local diagnostics = MiniStatusline.section_diagnostics({ trunc_width = 75 }) + local filename = MiniStatusline.section_filename({ trunc_width = 140 }) + local fileinfo = MiniStatusline.section_fileinfo({ trunc_width = 120 }) + local location = MiniStatusline.section_location({ trunc_width = 75 }) + local search = MiniStatusline.section_searchcount({ trunc_width = 75 }) + local words = getWords() + return MiniStatusline.combine_groups({ + + { hl = mode_hl, strings = { mode } }, + { hl = "MiniStatuslineDevinfo", strings = { git, diagnostics } }, + "%<", + { hl = "MiniStatuslineFilename", strings = { filename } }, + "%=", + { hl = "MiniStatuslineFileinfo", strings = { fileinfo } }, + { hl = "MiniStatuslineFileinfo", strings = { words } }, + { hl = mode_hl, strings = { search, location } }, + }) + end, + }, + }) + end, + }, + + { + "nvim-treesitter/nvim-treesitter", + branch = "main", + main = "nvim-treesitter", + build = ":TSUpdate", + init = function() + -- Install parsers that aren't already present + local ensure_installed = { "bash", "c", "html", "lua", "markdown", "vim", "vimdoc" } + local already_installed = require("nvim-treesitter.config").get_installed() + local to_install = vim.iter(ensure_installed) + :filter(function(parser) + return not vim.tbl_contains(already_installed, parser) + end) + :totable() + if #to_install > 0 then + require("nvim-treesitter").install(to_install) + end + end, + }, + + { + "folke/snacks.nvim", + priority = 1000, + lazy = false, + opts = { + dashboard = { enabled = true }, + input = { enabled = true }, + terminal = { enabled = true }, + notify = { enabled = true }, + }, + keys = { + { + "T", + function() + Snacks.terminal() + end, + desc = "Toggle Terminal", + }, + }, + }, + + { + "folke/trouble.nvim", + opts = {}, + cmd = "Trouble", + keys = { + { + "xx", + "Trouble diagnostics toggle", + desc = "Diagnostics (Trouble)", + }, + { + "xX", + "Trouble diagnostics toggle filter.buf=0", + desc = "Buffer Diagnostics (Trouble)", + }, + { + "cs", + "Trouble symbols toggle focus=false", + desc = "Symbols (Trouble)", + }, + { + "cl", + "Trouble lsp toggle focus=false win.position=right", + desc = "LSP Definitions / references / ... (Trouble)", + }, + { + "xL", + "Trouble loclist toggle", + desc = "Location List (Trouble)", + }, + { + "xQ", + "Trouble qflist toggle", + desc = "Quickfix List (Trouble)", + }, + }, + }, +} diff --git a/home/neovim/neovim/lua/telescope_settings.lua b/home/neovim/neovim/lua/telescope_settings.lua new file mode 100644 index 0000000..84b3a5a --- /dev/null +++ b/home/neovim/neovim/lua/telescope_settings.lua @@ -0,0 +1,5 @@ +require('telescope').setup{ + defaults = { + path_display={"smart"}, + } +} diff --git a/home/neovim/neovim/lua/todo-config.lua b/home/neovim/neovim/lua/todo-config.lua new file mode 100644 index 0000000..c134fce --- /dev/null +++ b/home/neovim/neovim/lua/todo-config.lua @@ -0,0 +1,55 @@ +require("todo-comments").setup({ + signs = true, -- show icons in the signs column + sign_priority = 8, -- sign priority + -- keywords recognized as todo comments + keywords = { + FIX = { + icon = " ", -- icon used for the sign, and in search results + color = "error", -- can be a hex color, or a named color (see below) + alt = { "FIXME", "BUG", "FIXIT", "ISSUE" }, -- a set of other keywords that all map to this FIX keywords + -- signs = false, -- configure signs for some keywords individually + }, + TODO = { icon = " ", color = "info" }, + HACK = { icon = " ", color = "warning" }, + WARN = { icon = " ", color = "warning", alt = { "WARNING", "XXX" } }, + PERF = { icon = "󰾆 ", alt = { "OPTIM", "PERFORMANCE", "OPTIMIZE" } }, + NOTE = { icon = "󰎞 ", color = "hint", alt = { "INFO" } }, + }, + merge_keywords = true, -- when true, custom keywords will be merged with the defaults + -- highlighting of the line containing the todo comment + -- * before: highlights before the keyword (typically comment characters) + -- * keyword: highlights of the keyword + -- * after: highlights after the keyword (todo text) + highlight = { + before = "", -- "fg" or "bg" or empty + keyword = "wide", -- "fg", "bg", "wide" or empty. (wide is the same as bg, but will also highlight surrounding characters) + after = "fg", -- "fg" or "bg" or empty + pattern = [[.*<(KEYWORDS)\s*:]], -- pattern or table of patterns, used for highlightng (vim regex) + comments_only = true, -- uses treesitter to match keywords in comments only + max_line_len = 400, -- ignore lines longer than this + exclude = {}, -- list of file types to exclude highlighting + }, + -- list of named colors where we try to extract the guifg from the + -- list of hilight groups or use the hex color if hl not found as a fallback + colors = { + error = { "DiagnosticError", "ErrorMsg", "#DC2626" }, + warning = { "DiagnosticWarning", "WarningMsg", "#FBBF24" }, + info = { "DiagnosticInfo", "#2563EB" }, + hint = { "DiagnosticHint", "#10B981" }, + default = { "Identifier", "#7C3AED" }, + }, + search = { + command = "rg", + args = { + "--color=never", + "--no-heading", + "--with-filename", + "--line-number", + "--column", + }, + -- regex that will be used to match keywords. + -- don't replace the (KEYWORDS) placeholder + pattern = [[\b(KEYWORDS):]], -- ripgrep regex + -- pattern = [[\b(KEYWORDS)\b]], -- match without the extra colon. You'll likely get false positives + }, +}) diff --git a/home/neovim/neovim/lua/utils/statusline.lua b/home/neovim/neovim/lua/utils/statusline.lua new file mode 100644 index 0000000..1c29c1a --- /dev/null +++ b/home/neovim/neovim/lua/utils/statusline.lua @@ -0,0 +1,15 @@ +-- Statusline utilities + +local M = {} + +-- Function to get word count in status line +function M.getWords() + -- the third string here is the string for visual-block mode (^V) + if vim.fn.mode() == "v" or vim.fn.mode() == "V" or vim.fn.mode() == "" then + return vim.fn.wordcount().visual_words .. "" + else + return vim.fn.wordcount().words .. "" + end +end + +return M \ No newline at end of file diff --git a/home/neovim/neovim/lua/workspace.lua b/home/neovim/neovim/lua/workspace.lua new file mode 100644 index 0000000..0f851e5 --- /dev/null +++ b/home/neovim/neovim/lua/workspace.lua @@ -0,0 +1,25 @@ +local util = require('lspconfig.util') + +local join = util.path.join + +local M = {} + +local env = { + HOME = vim.loop.os_homedir(), + XDG_CACHE_HOME = os.getenv('XDG_CACHE_HOME'), +} + +---Returns the default workspace directory +---@return string +function M.get_default_workspace() + local cache_dir = env.XDG_CACHE_HOME and env.XDG_CACHE_HOME + or join(env.HOME, '.cache') + local project_name = vim.fn.fnamemodify(vim.fn.getcwd(), ':p:h:t') + + local path = + join(cache_dir, 'jdtls', 'workspaces', project_name) + + return path +end + +return M diff --git a/home/neovim/neovim/spell/en.utf-8.add b/home/neovim/neovim/spell/en.utf-8.add new file mode 100644 index 0000000..3667456 --- /dev/null +++ b/home/neovim/neovim/spell/en.utf-8.add @@ -0,0 +1,52 @@ +interphase +nanolaminate +microstructures +multilayers +multiscale +microrotation +microcontinuum +microstructure +heterostructured +proven +diffusionless +martensitic +embrittlement +deliverables +minima +energetics +Predictiveness +atomistic +foundational +diffusivity +equilibrated +compositionally +compositional +nanometers +undeformed +interdiffusion +nanolaminates +interdiffused +monatomic +atomistics +interfacial +interatomic +Fortran +runtimes +spacings +unrelaxed +a +nm +centroids +microrotations +centroid +evolutions +recomendations +rhombohedron +inhibitive +parallelized +assumming +exhibitted +manyfold +reusability +Architected +roadmaps diff --git a/home/neovim/neovim/spell/en.utf-8.add.spl b/home/neovim/neovim/spell/en.utf-8.add.spl new file mode 100644 index 0000000000000000000000000000000000000000..b8bc973a2cf31b3676519281a4f9a632d253d307 GIT binary patch literal 859 zcmX|9L5>qK5OkYxa)b#;IF^w{Q@^M}92*zfP05AiglvCOrtJ5$>tUIt0BNW_WnAl6EgRYp8X ziad#tNzV-dNxU^;%)Z$;0bQg5O@gaKK-7${rU?^HMVw@7GU5Hj#v~co8D%PvBfg?S zO-^`LGVOE*?FMX21@64O9n16i1(d4E7mXM}FPebLE2Ku12*&1-u9qXnX>U{!AS(l7 zD-*d}Q3sB_(yv7(TM3f{_@SUg&;<96n1%v-QTEH|RSjIpxmGE5Ug|%?;!WdO_G$p|C5<*7fWK z7UmJReSsAHNwc8s_hMaDZ?le&`vBJUE7vLDgIm~Fq=3-~J~Z`DPH`6p^zK(Q&}Scr z|8a=pI%%pOSF&}Mu+gI=Bg7{mdc%aeKGgn EKVl86 /dev/null; then + alias k="kubectl" + source <(k completion zsh) +fi +cc() { + CLAUDE_SESSION="${1:-unnamed}" claude "${@:2}" +} + +ssh_no_tmux() { + NO_TMUX=1 nohup ghostty --command="ssh $*" >/dev/null 2>&1 & +} + +alias jjn="jj new -B @ --no-edit" +alias jjs="jj squash --interactive --from @ --into @-" +alias jjp="jj bookmark move master --to @-; jj git push" +alias pkg_search="nix search nixpkgs --extra-experimental-features 'nix-command flakes'" + +sandbox() { + local host_repos="$HOME/repos" + local sandbox_cwd="/workspace" + + if [[ "$PWD" == "$host_repos" ]]; then + sandbox_cwd="/workspace" + elif [[ "$PWD" == "$host_repos"/* ]]; then + sandbox_cwd="/workspace/${PWD#$host_repos/}" + fi + + if ! docker ps -a --format '{{.Names}}' | grep -q '^dev-sandbox$'; then + docker run -dit --name dev-sandbox \ + --env-file "$host_repos/dev_sandbox/.env" \ + -v "$host_repos:/workspace" \ + -v pi-state:/root/.pi \ + -v /var/run/docker.sock:/var/run/docker.sock \ + dev-sandbox:latest + else + docker start dev-sandbox 2>/dev/null + fi + + if (( $# > 0 )); then + docker exec -it -w "$sandbox_cwd" dev-sandbox /bin/zsh -l -c 'exec "$@"' sandbox "$@" + else + docker exec -it -w "$sandbox_cwd" dev-sandbox /bin/zsh -l + fi +} + + +#============================================================================== +# Functions +#============================================================================== + +load_env() { + set -a + . "./$1" + set +a +} + +#============================================================================== +# os functions +#============================================================================== +alias t="nvim ~/.local/share/os/tasks.md" +alias h="habits open" +alias books="nvim ~/.local/share/os/books.md" +alias mindset="nvim ~/.local/share/os/mindset.md" + +#============================================================================== +# Completions +#============================================================================== + +autoload -Uz compinit +compinit +zstyle ':compinstall' filename '/home/aselimov/.zshrc' +zstyle -e ':completion:*:hosts' hosts 'reply=( + ${=${${(f)"$(cat {/etc/ssh_,~/ar.ssh/known_}hosts(|2)(N) 2>/dev/null)"}%%[#| ]*}//,/ } + ${=${${${${(@M)${(f)"$(cat ~/.ssh/config 2>/dev/null)"}:#Host *}#Host }:#*\**}:#*\?*}} +)' + +#============================================================================== +# Plugins & Tools +#============================================================================== + +# nvm +# Lazy-load nvm - only initialize when first used +export NVM_DIR="$HOME/.nvm" + +# Function to load nvm (called only once) +load_nvm() { + [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" + [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" +} + +# Create placeholder functions that load nvm once, then call the real command +nvm() { + unset -f nvm node npm + load_nvm + nvm "$@" +} + +node() { + unset -f nvm node npm + load_nvm + node "$@" +} + +npm() { + unset -f nvm node npm + load_nvm + npm "$@" +} + +# ghcup +[ -f "/home/aselimov/.ghcup/env" ] && . "/home/aselimov/.ghcup/env" # ghcup-env + +# pyenv +if command -v pyenv >/dev/null 2>&1; then + export PYENV_ROOT="$HOME/.pyenv" + [[ -d $PYENV_ROOT/bin ]] && export PATH="$PYENV_ROOT/bin:$PATH" + eval "$(pyenv init - zsh)" +fi + +# jenv +if command -v jenv >/dev/null 2>&1; then + export PATH="$HOME/.jenv/bin:$PATH" + eval "$(jenv init -)" +fi + +# Simple notify function +notify() { + local cmd="$*" + eval "$cmd" + local rc=$? + osascript -e "display notification \"Done (exit=$rc)\" with title \"${cmd//\"/\\\"}\"" + return $rc +} + + +# zsh-autosuggestions +source "$HOME/.config/zsh/zsh-autosuggestions/zsh-autosuggestions.zsh" + +# zsh-syntax-highlighting +source "$HOME/.config/zsh/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh" + +# zsh-history-substring-search +source "$HOME/.config/zsh/zsh-history-substring-search/zsh-history-substring-search.zsh" + +#============================================================================== +# Keybindings +#============================================================================== + +bindkey -v +bindkey '^[[A' history-substring-search-up +bindkey '^[[B' history-substring-search-down + +#============================================================================== +# OS-Specific Configuration +#============================================================================== + +if [ "$(uname)" = "Darwin" ]; then + alias ls="gls --classify --group-directories-first --color" + export NVIM_JDTLS_JAVA_HOME="/Library/Java/JavaVirtualMachines/temurin-21.jdk/Contents/Home/" +else + alias ls="ls --classify --group-directories-first --color" +fi + +# starship +eval "$(starship init zsh)" + +if [[ -z "$TMUX" ]] && [[ -n "$PS1" ]] && [[ -z "$NO_TMUX" ]]; then + tmux attach -t dev || tmux new -s dev +fi + +# Get remaining habits +if [ -n "$HABITS" ]; then + echo "Habits remaining are:" + habits left +fi diff --git a/home/shell/shell.nix b/home/shell/shell.nix new file mode 100644 index 0000000..4d77bd3 --- /dev/null +++ b/home/shell/shell.nix @@ -0,0 +1,51 @@ +{config, pkgs, ...}: +{ + home.packages = with pkgs; [ + starship + ]; + xdg.configFile."starship.toml".source = ./starship.toml; + + programs.zsh = { + enable = true; + initContent='' + source ${./.zshrc} + ''; + shellAliases = { + sudo = "doas"; + }; + }; + programs.direnv = { + enable = true; + nix-direnv.enable = true; + + config = { + global = { + hide_env_diff = true; + }; + }; + }; + + xdg.configFile."zsh/zsh-syntax-highlighting".source = builtins.fetchGit { + url = "https://forge.alexselimov.com/aselimov/zsh-syntax-highlighting.git"; + rev = "2fc57d63067c18b1100ecdbf684fa5baf49459d1"; + }; + + xdg.configFile."zsh/zsh-history-substring-search".source = builtins.fetchGit { + url = "https://forge.alexselimov.com/aselimov/zsh-history-substring-search.git"; + rev = "14c8d2e0ffaee98f2df9850b19944f32546fdea5"; + }; + + xdg.configFile."zsh/zsh-autosuggestions".source = builtins.fetchGit { + url = "https://forge.alexselimov.com/aselimov/zsh-autosuggestions.git"; + rev = "85919cd1ffa7d2d5412f6d3fe437ebdbeeec4fc5"; + }; + + programs.tmux.enable = true; + xdg.configFile."tmux/tmux.conf".source = ./.tmux.conf; + xdg.configFile."tmux/themes/zenwritten-light.conf".text = '' + set -g window-style 'bg=gray' + ''; + xdg.configFile."tmux/themes/zenwritten-dark.conf".text = '' + set -g window-style 'bg=brightblack' + ''; +} diff --git a/home/shell/starship.toml b/home/shell/starship.toml new file mode 100644 index 0000000..9a2259c --- /dev/null +++ b/home/shell/starship.toml @@ -0,0 +1,166 @@ +#8e8e8e normal +#BBBBBB bold +#66a5ad accent + +format = """ +$username\ +$hostname\ +$shlvl\ +$kubernetes\ +$directory\ +$hg_branch\ +$docker_context\ +$package\ +$cmake\ +$dart\ +$dotnet\ +$elixir\ +$elm\ +$erlang\ +$golang\ +$helm\ +$java\ +$julia\ +$kotlin\ +$nim\ +$nodejs\ +$ocaml\ +$perl\ +$php\ +$purescript\ +$python\ +$ruby\ +$rust\ +$swift\ +$terraform\ +$zig\ +$nix_shell\ +$conda\ +$memory_usage\ +$aws\ +$gcloud\ +$openstack\ +$env_var\ +$crystal\ +$custom\ +$cmd_duration\ +$line_break\ +$lua\ +$jobs\ +$battery\ +$time\ +$status\ +$character""" + +[username] +style_user = 'bold #66a5ad' + +[hostname] +style = 'bold #BBBBBB' +ssh_symbol = "" + +[singularity] +style = 'bold #BBBBBB' + +[kubernetes] +style = 'bold #BBBBBB' + +[vcsh] +style = 'bold white' + +[git_branch] +style = 'bold #BBBBBB' + +[git_commit] +style = 'bold #BBBBBB' + +[docker_context] +style = 'bold #BBBBBB' +[package] +style = 'bold #BBBBBB' +symbol = " " +[cmake] +style = 'bold #BBBBBB' +[dart] +style = 'bold #BBBBBB' +[dotnet] +style = 'bold #BBBBBB' +[elixir] +style = 'bold #BBBBBB' +[elm] +style = 'bold #BBBBBB' +[erlang] +style = 'bold #BBBBBB' +[golang] +style = 'bold #BBBBBB' +[helm] +style = 'bold #BBBBBB' +[java] +style = 'bold #BBBBBB' +[julia] +style = 'bold #BBBBBB' +[kotlin] +style = 'bold #BBBBBB' +[nim] +style = 'bold #BBBBBB' +[nodejs] +style = 'bold #BBBBBB' +[ocaml] +style = 'bold #BBBBBB' +[perl] +style = 'bold #BBBBBB' +[php] +style = 'bold #BBBBBB' +[purescript] +style = 'bold #BBBBBB' +[ruby] +style = 'bold #BBBBBB' +[swift] +style = 'bold #BBBBBB' +[terraform] +style = 'bold #BBBBBB' +[zig] +style = 'bold #BBBBBB' +[nix_shell] +style = 'bold #BBBBBB' +[conda] +style = 'bold #BBBBBB' +[memory_usage] +style = 'bold #BBBBBB' +[aws] +style = 'bold #BBBBBB' +[gcloud] +style = 'bold #BBBBBB' +disabled=true +[openstack] +style = 'bold #BBBBBB' +[crystal] +style = 'bold #BBBBBB' +[cmd_duration] +style = 'bold #BBBBBB' +[lua] +style = 'bold #BBBBBB' +symbol = "󰢱 " +[jobs] +style = 'bold #BBBBBB' +[time] +style = 'bold #BBBBBB' + + +[python] +symbol = "󱔎 " +style = 'bold #BBBBBB' + +[rust] +symbol = "󱘗 " +style = 'bold #BBBBBB' + +[directory] +truncate_to_repo = false +style = 'bold #BBBBBB' + +[character] +success_symbol = "[󰜴](bold green)" +error_symbol = "[󰬅](bold red)" +vicmd_symbol = "[N](bold green)" +