aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md1425
-rw-r--r--common/.bash_profile11
-rw-r--r--common/.bashrc371
-rw-r--r--common/.editorconfig80
-rw-r--r--common/.facebin0 -> 11569 bytes
-rw-r--r--common/.gitconfig34
-rw-r--r--common/.gitignore44
-rw-r--r--common/.gitmodules12
-rw-r--r--common/.gitsubtrees7
-rw-r--r--common/.prettierrc.yml5
-rw-r--r--common/.profile99
-rw-r--r--common/.zprofile3
-rw-r--r--common/.zshrc10
-rw-r--r--common/assets/desktop.jpgbin0 -> 523045 bytes
-rw-r--r--common/assets/old_desktop.jpgbin0 -> 108709 bytes
-rw-r--r--common/config/alacritty/alacritty.yml106
-rwxr-xr-xcommon/config/nvim/.gitignore (renamed from .gitignore)0
-rwxr-xr-xcommon/config/nvim/.luacheckrc (renamed from .luacheckrc)0
-rwxr-xr-xcommon/config/nvim/after/ftplugin/c.lua (renamed from after/ftplugin/c.lua)0
-rwxr-xr-xcommon/config/nvim/after/ftplugin/lua.lua.bak (renamed from after/ftplugin/lua.lua.bak)0
-rwxr-xr-xcommon/config/nvim/after/ftplugin/markdown.lua (renamed from after/ftplugin/markdown.lua)0
-rwxr-xr-xcommon/config/nvim/after/ftplugin/vim.lua (renamed from after/ftplugin/vim.lua)0
-rwxr-xr-xcommon/config/nvim/autoload/statusline.vim (renamed from autoload/statusline.vim)0
-rwxr-xr-xcommon/config/nvim/autoload/utils.vim (renamed from autoload/utils.vim)0
-rwxr-xr-xcommon/config/nvim/colors/colorscheme.vim (renamed from colors/colorscheme.vim)0
-rwxr-xr-xcommon/config/nvim/init.lua (renamed from init.lua)0
-rw-r--r--common/config/nvim/lsp/bashls.lua (renamed from lsp/bashls.lua)0
-rw-r--r--common/config/nvim/lsp/clangd.lua (renamed from lsp/clangd.lua)0
-rw-r--r--common/config/nvim/lsp/cssls.lua (renamed from lsp/cssls.lua)0
-rw-r--r--common/config/nvim/lsp/gopls.lua (renamed from lsp/gopls.lua)0
-rw-r--r--common/config/nvim/lsp/html.lua (renamed from lsp/html.lua)0
-rw-r--r--common/config/nvim/lsp/jsonls.lua (renamed from lsp/jsonls.lua)0
-rw-r--r--common/config/nvim/lsp/lua_ls.lua (renamed from lsp/lua_ls.lua)0
-rw-r--r--common/config/nvim/lsp/pyright.lua (renamed from lsp/pyright.lua)0
-rw-r--r--common/config/nvim/lsp/rust_analyzer.lua (renamed from lsp/rust_analyzer.lua)0
-rw-r--r--common/config/nvim/lsp/ts_ls.lua (renamed from lsp/ts_ls.lua)0
-rw-r--r--common/config/nvim/lsp/yamlls.lua (renamed from lsp/yamlls.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/auto-session.lua (renamed from lua/plugins/auto-session.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/autopairs.lua (renamed from lua/plugins/autopairs.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/cmp-gh-source.lua (renamed from lua/plugins/cmp-gh-source.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/cmp.lua (renamed from lua/plugins/cmp.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/colorizer.lua (renamed from lua/plugins/colorizer.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/colorscheme.lua (renamed from lua/plugins/colorscheme.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/comment.lua (renamed from lua/plugins/comment.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/dap.lua (renamed from lua/plugins/dap.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/dashboard.lua (renamed from lua/plugins/dashboard.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/fidget.lua (renamed from lua/plugins/fidget.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/friendly-snippets.lua (renamed from lua/plugins/friendly-snippets.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/fugitive.lua (renamed from lua/plugins/fugitive.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/fzf.lua (renamed from lua/plugins/fzf.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/git.lua (renamed from lua/plugins/git.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/gitsigns.lua (renamed from lua/plugins/gitsigns.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/goto-preview.lua (renamed from lua/plugins/goto-preview.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/hardtime.lua (renamed from lua/plugins/hardtime.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/harpoon.lua (renamed from lua/plugins/harpoon.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/heirline.lua (renamed from lua/plugins/heirline.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/indent-blankline.lua (renamed from lua/plugins/indent-blankline.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/interestingwords.lua (renamed from lua/plugins/interestingwords.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/leetcode.lua (renamed from lua/plugins/leetcode.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/loclist.lua (renamed from lua/plugins/loclist.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/lsp.lua (renamed from lua/plugins/lsp.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/lualine.lua (renamed from lua/plugins/lualine.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/luasnip.lua (renamed from lua/plugins/luasnip.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/messages.lua (renamed from lua/plugins/messages.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/modify-blend.lua (renamed from lua/plugins/modify-blend.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/navic.lua (renamed from lua/plugins/navic.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/neodev.lua (renamed from lua/plugins/neodev.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/neoscroll.lua (renamed from lua/plugins/neoscroll.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/neotest.lua (renamed from lua/plugins/neotest.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/notify.lua (renamed from lua/plugins/notify.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/nvim-tree.lua (renamed from lua/plugins/nvim-tree.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/overseer.lua (renamed from lua/plugins/overseer.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/plenary.lua (renamed from lua/plugins/plenary.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/prettier.lua (renamed from lua/plugins/prettier.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/quickfix.lua (renamed from lua/plugins/quickfix.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/snippets.lua (renamed from lua/plugins/snippets.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/sniprun.lua (renamed from lua/plugins/sniprun.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/statuscol.lua (renamed from lua/plugins/statuscol.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/surround.lua (renamed from lua/plugins/surround.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/telescope.lua (renamed from lua/plugins/telescope.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/toggleterm.lua (renamed from lua/plugins/toggleterm.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/treesitter.lua (renamed from lua/plugins/treesitter.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/trouble.lua (renamed from lua/plugins/trouble.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/vimtex.lua (renamed from lua/plugins/vimtex.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/web-devicons.lua (renamed from lua/plugins/web-devicons.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/which-key.lua (renamed from lua/plugins/which-key.lua)0
-rwxr-xr-xcommon/config/nvim/lua/plugins/zen-mode.lua (renamed from lua/plugins/zen-mode.lua)0
-rwxr-xr-xcommon/config/nvim/lua/setup/compat.lua (renamed from lua/setup/compat.lua)0
-rwxr-xr-xcommon/config/nvim/lua/setup/manager.lua (renamed from lua/setup/manager.lua)0
-rwxr-xr-xcommon/config/nvim/lua/setup/plugins.lua (renamed from lua/setup/plugins.lua)0
-rwxr-xr-xcommon/config/nvim/lua/user/keys.lua (renamed from lua/user/keys.lua)0
-rwxr-xr-xcommon/config/nvim/lua/user/mods.lua (renamed from lua/user/mods.lua)0
-rwxr-xr-xcommon/config/nvim/lua/user/opts.lua (renamed from lua/user/opts.lua)0
-rwxr-xr-xcommon/config/nvim/lua/user/view.lua (renamed from lua/user/view.lua)0
-rwxr-xr-xcommon/config/nvim/neovim.ps1 (renamed from neovim.ps1)0
-rwxr-xr-xcommon/config/nvim/neovim.sh (renamed from neovim.sh)0
-rw-r--r--common/config/nvim/snippets/boilerplate.lua (renamed from snippets/boilerplate.lua)0
-rw-r--r--common/config/nvim/snippets/lua.lua (renamed from snippets/lua.lua)0
-rw-r--r--common/config/nvim/snippets/markdown.lua (renamed from snippets/markdown.lua)0
-rw-r--r--common/config/wezterm/wezterm.lua206
-rw-r--r--common/config/zsh/.zshenv346
-rw-r--r--common/config/zsh/.zshrc73
-rw-r--r--common/config/zsh/user/aliases.zsh208
-rw-r--r--common/config/zsh/user/bindings.zsh175
-rw-r--r--common/config/zsh/user/completion.zsh172
-rw-r--r--common/config/zsh/user/functions.zsh1607
-rw-r--r--common/config/zsh/user/options.zsh66
-rw-r--r--common/config/zsh/user/prompt.zsh679
-rw-r--r--common/config/zsh/user/prompt_minimal.zsh295
-rw-r--r--common/config/zsh/user/prompt_new.zsh863
-rw-r--r--common/config/zsh/user/prompt_simple.zsh227
-rwxr-xr-xcommon/install.sh3715
-rw-r--r--common/packages.yml1066
-rw-r--r--linux/etc/X11/xorg.conf.d/70-synaptics.conf15
-rw-r--r--linux/etc/issue14
-rw-r--r--linux/etc/lightdm/lightdm-gtk-greeter.conf17
-rw-r--r--linux/etc/lightdm/lightdm-webkit2-greeter.conf49
-rw-r--r--linux/etc/lightdm/lightdm.conf161
-rw-r--r--linux/etc/udev/rules.d/99-reload-monitor.rules1
-rw-r--r--linux/home/.config/Code/User/keybindings.json274
-rw-r--r--linux/home/.config/Code/User/settings.json734
-rw-r--r--linux/home/.config/Code/User/snippets/snippet.code-snippets126
-rw-r--r--linux/home/.config/Code/User/spellright.dict14
-rw-r--r--linux/home/.config/Code/User/vsc.css408
-rw-r--r--linux/home/.config/Code/User/vsc.js100
-rw-r--r--linux/home/.config/X11/.Xresources307
-rw-r--r--linux/home/.config/X11/.xbindkeysrc31
-rwxr-xr-xlinux/home/.config/X11/.xinitrc34
-rw-r--r--linux/home/.config/X11/.xprofile24
-rw-r--r--linux/home/.config/ags/.eslintrc.yml130
-rw-r--r--linux/home/.config/ags/.gitignore6
-rw-r--r--linux/home/.config/ags/assets/arrows-down.svg10
-rw-r--r--linux/home/.config/ags/assets/arrows-left.svg10
-rw-r--r--linux/home/.config/ags/assets/arrows-right.svg10
-rw-r--r--linux/home/.config/ags/assets/arrows-up.svg10
-rw-r--r--linux/home/.config/ags/assets/battery-flash-symbolic.svg4
-rw-r--r--linux/home/.config/ags/assets/bomb-kill.svg36
-rw-r--r--linux/home/.config/ags/assets/chat-bubbles-symbolic.svg5
-rw-r--r--linux/home/.config/ags/assets/controller-symbolic.svg4
-rw-r--r--linux/home/.config/ags/assets/controls-symbolic.svg5
-rw-r--r--linux/home/.config/ags/assets/dark-mode-symbolic.svg4
-rw-r--r--linux/home/.config/ags/assets/float.svg44
-rw-r--r--linux/home/.config/ags/assets/fullscreen.svg43
-rw-r--r--linux/home/.config/ags/assets/hourglass-symbolic.svg4
-rw-r--r--linux/home/.config/ags/assets/light-mode-symbolic.svg4
-rw-r--r--linux/home/.config/ags/assets/mixer-symbolic.svg6
-rw-r--r--linux/home/.config/ags/assets/nix-snowflake-symbolic.svg155
-rw-r--r--linux/home/.config/ags/assets/nixos-symbolic.svg155
-rw-r--r--linux/home/.config/ags/assets/nixos.svg277
-rw-r--r--linux/home/.config/ags/assets/osk.svg132
-rw-r--r--linux/home/.config/ags/assets/pinned.svg36
-rw-r--r--linux/home/.config/ags/assets/preferences-desktop-theme-symbolic.svg321
-rw-r--r--linux/home/.config/ags/assets/processor-symbolic.svg17
-rw-r--r--linux/home/.config/ags/assets/rotation.svg8
-rw-r--r--linux/home/.config/ags/assets/swapnext.svg8
-rw-r--r--linux/home/.config/ags/assets/tbox-close.svg49
-rw-r--r--linux/home/.config/ags/assets/terminal-symbolic.svg5
-rw-r--r--linux/home/.config/ags/assets/togglesplit.svg10
-rw-r--r--linux/home/.config/ags/assets/toolbars-symbolic.svg4
-rw-r--r--linux/home/.config/ags/assets/wp-next.svg10
-rw-r--r--linux/home/.config/ags/assets/wp-prev.svg10
-rw-r--r--linux/home/.config/ags/config.js46
-rw-r--r--linux/home/.config/ags/default.nix104
-rw-r--r--linux/home/.config/ags/greeter.js18
-rw-r--r--linux/home/.config/ags/greeter/auth.ts109
-rw-r--r--linux/home/.config/ags/greeter/greeter.scss64
-rw-r--r--linux/home/.config/ags/greeter/greeter.ts37
-rw-r--r--linux/home/.config/ags/greeter/session.ts20
-rw-r--r--linux/home/.config/ags/greeter/statusbar.ts46
-rw-r--r--linux/home/.config/ags/lib/battery.ts16
-rw-r--r--linux/home/.config/ags/lib/client.js134
-rw-r--r--linux/home/.config/ags/lib/cursorhover.js86
-rw-r--r--linux/home/.config/ags/lib/gtk.ts16
-rw-r--r--linux/home/.config/ags/lib/hyprland.ts80
-rw-r--r--linux/home/.config/ags/lib/iconUtils.js46
-rw-r--r--linux/home/.config/ags/lib/icons.ts186
-rw-r--r--linux/home/.config/ags/lib/init.ts19
-rw-r--r--linux/home/.config/ags/lib/matugen.ts113
-rw-r--r--linux/home/.config/ags/lib/notifications.ts16
-rw-r--r--linux/home/.config/ags/lib/option.ts115
-rw-r--r--linux/home/.config/ags/lib/session.ts16
-rw-r--r--linux/home/.config/ags/lib/tmux.ts14
-rw-r--r--linux/home/.config/ags/lib/utils.ts113
-rw-r--r--linux/home/.config/ags/lib/variables.ts47
-rw-r--r--linux/home/.config/ags/main.ts47
-rw-r--r--linux/home/.config/ags/options.ts261
-rw-r--r--linux/home/.config/ags/package.json18
-rw-r--r--linux/home/.config/ags/service/asusctl.ts52
-rw-r--r--linux/home/.config/ags/service/brightness.ts69
-rw-r--r--linux/home/.config/ags/service/colorpicker.ts56
-rw-r--r--linux/home/.config/ags/service/nix.ts109
-rw-r--r--linux/home/.config/ags/service/powermenu.ts43
-rw-r--r--linux/home/.config/ags/service/screenrecord.ts102
-rw-r--r--linux/home/.config/ags/service/wallpaper.ts127
-rw-r--r--linux/home/.config/ags/service/weather.ts59
-rw-r--r--linux/home/.config/ags/style/extra.scss67
-rw-r--r--linux/home/.config/ags/style/mixins/a11y-button.scss48
-rw-r--r--linux/home/.config/ags/style/mixins/button.scss70
-rw-r--r--linux/home/.config/ags/style/mixins/floating-widget.scss12
-rw-r--r--linux/home/.config/ags/style/mixins/hidden.scss15
-rw-r--r--linux/home/.config/ags/style/mixins/media.scss42
-rw-r--r--linux/home/.config/ags/style/mixins/scrollable.scss42
-rw-r--r--linux/home/.config/ags/style/mixins/slider.scss74
-rw-r--r--linux/home/.config/ags/style/mixins/spacing.scss53
-rw-r--r--linux/home/.config/ags/style/mixins/switch.scss16
-rw-r--r--linux/home/.config/ags/style/mixins/unset.scss9
-rw-r--r--linux/home/.config/ags/style/mixins/widget.scss7
-rw-r--r--linux/home/.config/ags/style/style.ts103
-rw-r--r--linux/home/.config/ags/tsconfig.json19
-rw-r--r--linux/home/.config/ags/widget/PopupWindow.ts156
-rw-r--r--linux/home/.config/ags/widget/RegularWindow.ts3
-rw-r--r--linux/home/.config/ags/widget/bar/Bar.ts57
-rw-r--r--linux/home/.config/ags/widget/bar/PanelButton.ts46
-rw-r--r--linux/home/.config/ags/widget/bar/ScreenCorners.ts25
-rw-r--r--linux/home/.config/ags/widget/bar/bar.scss242
-rw-r--r--linux/home/.config/ags/widget/bar/buttons/BatteryBar.ts94
-rw-r--r--linux/home/.config/ags/widget/bar/buttons/ColorPicker.ts37
-rw-r--r--linux/home/.config/ags/widget/bar/buttons/Date.ts15
-rw-r--r--linux/home/.config/ags/widget/bar/buttons/Launcher.ts49
-rw-r--r--linux/home/.config/ags/widget/bar/buttons/Media.ts92
-rw-r--r--linux/home/.config/ags/widget/bar/buttons/Messages.ts16
-rw-r--r--linux/home/.config/ags/widget/bar/buttons/PowerMenu.ts15
-rw-r--r--linux/home/.config/ags/widget/bar/buttons/ScreenRecord.ts21
-rw-r--r--linux/home/.config/ags/widget/bar/buttons/SysTray.ts39
-rw-r--r--linux/home/.config/ags/widget/bar/buttons/SystemIndicators.ts107
-rw-r--r--linux/home/.config/ags/widget/bar/buttons/Taskbar.ts90
-rw-r--r--linux/home/.config/ags/widget/bar/buttons/Workspaces.ts66
-rw-r--r--linux/home/.config/ags/widget/bar/screencorner.scss51
-rw-r--r--linux/home/.config/ags/widget/datemenu/DateColumn.ts58
-rw-r--r--linux/home/.config/ags/widget/datemenu/DateMenu.ts36
-rw-r--r--linux/home/.config/ags/widget/datemenu/NotificationColumn.ts113
-rw-r--r--linux/home/.config/ags/widget/datemenu/datemenu.scss110
-rw-r--r--linux/home/.config/ags/widget/desktop/Desktop.ts40
-rw-r--r--linux/home/.config/ags/widget/dock/Dock.ts150
-rw-r--r--linux/home/.config/ags/widget/dock/FloatingDock.ts70
-rw-r--r--linux/home/.config/ags/widget/dock/ToolBox.ts122
-rw-r--r--linux/home/.config/ags/widget/dock/ToolBoxDock.ts57
-rw-r--r--linux/home/.config/ags/widget/dock/dock.scss73
-rw-r--r--linux/home/.config/ags/widget/launcher/AppLauncher.ts125
-rw-r--r--linux/home/.config/ags/widget/launcher/Launcher.ts134
-rw-r--r--linux/home/.config/ags/widget/launcher/NixRun.ts118
-rw-r--r--linux/home/.config/ags/widget/launcher/ShRun.ts89
-rw-r--r--linux/home/.config/ags/widget/launcher/launcher.scss143
-rw-r--r--linux/home/.config/ags/widget/notifications/Notification.ts138
-rw-r--r--linux/home/.config/ags/widget/notifications/NotificationPopups.ts90
-rw-r--r--linux/home/.config/ags/widget/notifications/notifications.scss79
-rw-r--r--linux/home/.config/ags/widget/osd/OSD.ts111
-rw-r--r--linux/home/.config/ags/widget/osd/Progress.ts74
-rw-r--r--linux/home/.config/ags/widget/osd/osd.scss26
-rw-r--r--linux/home/.config/ags/widget/overview/Overview.ts41
-rw-r--r--linux/home/.config/ags/widget/overview/Window.ts48
-rw-r--r--linux/home/.config/ags/widget/overview/Workspace.ts76
-rw-r--r--linux/home/.config/ags/widget/overview/overview.scss34
-rw-r--r--linux/home/.config/ags/widget/powermenu/PowerMenu.ts56
-rw-r--r--linux/home/.config/ags/widget/powermenu/Verification.ts47
-rw-r--r--linux/home/.config/ags/widget/powermenu/powermenu.scss110
-rw-r--r--linux/home/.config/ags/widget/quicksettings/QuickSettings.ts84
-rw-r--r--linux/home/.config/ags/widget/quicksettings/ToggleButton.ts154
-rw-r--r--linux/home/.config/ags/widget/quicksettings/quicksettings.scss177
-rw-r--r--linux/home/.config/ags/widget/quicksettings/widgets/Bluetooth.ts61
-rw-r--r--linux/home/.config/ags/widget/quicksettings/widgets/Brightness.ts23
-rw-r--r--linux/home/.config/ags/widget/quicksettings/widgets/DND.ts12
-rw-r--r--linux/home/.config/ags/widget/quicksettings/widgets/DarkMode.ts12
-rw-r--r--linux/home/.config/ags/widget/quicksettings/widgets/Header.ts69
-rw-r--r--linux/home/.config/ags/widget/quicksettings/widgets/Media.ts153
-rw-r--r--linux/home/.config/ags/widget/quicksettings/widgets/MicMute.ts18
-rw-r--r--linux/home/.config/ags/widget/quicksettings/widgets/Network.ts61
-rw-r--r--linux/home/.config/ags/widget/quicksettings/widgets/PowerProfile.ts99
-rw-r--r--linux/home/.config/ags/widget/quicksettings/widgets/Volume.ts161
-rw-r--r--linux/home/.config/ags/widget/settings/Group.ts34
-rw-r--r--linux/home/.config/ags/widget/settings/Page.ts19
-rw-r--r--linux/home/.config/ags/widget/settings/Row.ts55
-rw-r--r--linux/home/.config/ags/widget/settings/Setter.ts93
-rw-r--r--linux/home/.config/ags/widget/settings/SettingsDialog.ts63
-rw-r--r--linux/home/.config/ags/widget/settings/Wallpaper.ts31
-rw-r--r--linux/home/.config/ags/widget/settings/layout.ts147
-rw-r--r--linux/home/.config/ags/widget/settings/settingsdialog.scss144
-rw-r--r--linux/home/.config/betterlockscreen/betterlockscreenrc37
-rwxr-xr-xlinux/home/.config/bspwm/bspwmrc275
-rwxr-xr-xlinux/home/.config/bspwm/scripts/bspdragtofloat46
-rwxr-xr-xlinux/home/.config/bspwm/scripts/bspswallow12
-rwxr-xr-xlinux/home/.config/bspwm/scripts/bspwindows14
-rwxr-xr-xlinux/home/.config/bspwm/scripts/bspwm-monitor-setup59
-rwxr-xr-xlinux/home/.config/bspwm/scripts/bspwm-toggle-visibility.sh23
-rwxr-xr-xlinux/home/.config/bspwm/scripts/close-window11
-rwxr-xr-xlinux/home/.config/bspwm/scripts/drag-float42
-rwxr-xr-xlinux/home/.config/bspwm/scripts/external_rules.sh71
-rwxr-xr-xlinux/home/.config/bspwm/scripts/hide-window38
-rw-r--r--linux/home/.config/dunst/assets/notification/fallback.pngbin0 -> 54750 bytes
-rw-r--r--linux/home/.config/dunst/assets/notification/music.pngbin0 -> 814 bytes
-rw-r--r--linux/home/.config/dunst/assets/notification/scrot.pngbin0 -> 35737 bytes
-rw-r--r--linux/home/.config/dunst/assets/ui/volume-high.svg1
-rw-r--r--linux/home/.config/dunst/assets/ui/volume-low.svg1
-rw-r--r--linux/home/.config/dunst/assets/ui/volume-medium.svg1
-rw-r--r--linux/home/.config/dunst/assets/ui/volume-muted.svg1
-rw-r--r--linux/home/.config/dunst/dunstrc456
-rwxr-xr-xlinux/home/.config/dunst/scripts/openEwwPopup.sh36
-rwxr-xr-xlinux/home/.config/dunst/scripts/songArtLogger.sh17
-rw-r--r--linux/home/.config/eww/eww.scss11
-rw-r--r--linux/home/.config/eww/eww.yuck23
-rwxr-xr-xlinux/home/.config/hypr/autostart34
-rw-r--r--linux/home/.config/hypr/hyprland.conf175
-rwxr-xr-xlinux/home/.config/hypr/scripts/move-scratchpad.sh9
-rw-r--r--linux/home/.config/hypr/user/binds.conf167
-rw-r--r--linux/home/.config/hypr/user/env.conf23
-rw-r--r--linux/home/.config/hypr/user/exec.conf4
-rw-r--r--linux/home/.config/hypr/user/monitors.conf4
-rw-r--r--linux/home/.config/hypr/user/window_rules.conf70
-rw-r--r--linux/home/.config/inputrc86
-rw-r--r--linux/home/.config/jgmenu/jgmenurc32
-rw-r--r--linux/home/.config/jgmenu/menu.csv32
-rw-r--r--linux/home/.config/kitty/kitty.conf827
-rw-r--r--linux/home/.config/mimeapps.list14
-rw-r--r--linux/home/.config/picom/picom.conf494
-rw-r--r--linux/home/.config/plank/dock/launchers/Alacritty.dockitem2
-rw-r--r--linux/home/.config/plank/dock/launchers/com.obsproject.Studio.dockitem2
-rw-r--r--linux/home/.config/plank/dock/launchers/discord.dockitem2
-rw-r--r--linux/home/.config/plank/dock/launchers/firefox.dockitem2
-rw-r--r--linux/home/.config/plank/dock/launchers/gimp.dockitem2
-rw-r--r--linux/home/.config/plank/dock/launchers/google-chrome.dockitem2
-rw-r--r--linux/home/.config/plank/dock/launchers/obsidian.dockitem2
-rw-r--r--linux/home/.config/plank/dock/launchers/org.gnome.Nautilus.dockitem2
-rw-r--r--linux/home/.config/plank/dock/launchers/spotify.dockitem2
-rw-r--r--linux/home/.config/plank/dock/launchers/steam.dockitem2
-rw-r--r--linux/home/.config/plank/dock/launchers/trash.dockitem2
-rw-r--r--linux/home/.config/plank/dock/launchers/virtualbox.dockitem2
-rw-r--r--linux/home/.config/plank/dock/launchers/vlc.dockitem2
-rw-r--r--linux/home/.config/plank/dock1/launchers/code-oss.dockitem2
-rw-r--r--linux/home/.config/plank/dock1/launchers/com.obsproject.Studio.dockitem2
-rw-r--r--linux/home/.config/plank/dock1/launchers/discord.dockitem2
-rw-r--r--linux/home/.config/plank/dock1/launchers/firefox.dockitem2
-rw-r--r--linux/home/.config/plank/dock1/launchers/obsidian.dockitem2
-rw-r--r--linux/home/.config/plank/dock1/launchers/org.qbittorrent.qBittorrent.dockitem2
-rw-r--r--linux/home/.config/plank/dock1/launchers/pcmanfm.dockitem2
-rw-r--r--linux/home/.config/plank/dock1/launchers/phototonic.dockitem2
-rw-r--r--linux/home/.config/plank/dock1/launchers/steam.dockitem2
-rw-r--r--linux/home/.config/plank/dock1/launchers/trash.dockitem2
-rw-r--r--linux/home/.config/plank/dock1/launchers/vlc.dockitem2
-rw-r--r--linux/home/.config/polybar/config.ini671
-rwxr-xr-xlinux/home/.config/polybar/launch.sh31
-rwxr-xr-xlinux/home/.config/polybar/scripts/bluetooth.sh12
-rwxr-xr-xlinux/home/.config/polybar/scripts/check-network.sh21
-rwxr-xr-xlinux/home/.config/polybar/scripts/check_updates.sh118
-rwxr-xr-xlinux/home/.config/polybar/scripts/cmus.sh39
-rwxr-xr-xlinux/home/.config/polybar/scripts/get_spotify_status.sh52
-rwxr-xr-xlinux/home/.config/polybar/scripts/menu.sh6
-rwxr-xr-xlinux/home/.config/polybar/scripts/menu.shsave63
-rwxr-xr-xlinux/home/.config/polybar/scripts/menu_full.sh65
-rwxr-xr-xlinux/home/.config/polybar/scripts/now-playing.sh217
-rwxr-xr-xlinux/home/.config/polybar/scripts/polybar_wrapper92
-rwxr-xr-xlinux/home/.config/polybar/scripts/popup-calendar.sh41
-rwxr-xr-xlinux/home/.config/polybar/scripts/rofi-power.sh45
-rwxr-xr-xlinux/home/.config/polybar/scripts/scroll_spotify_status.sh12
-rwxr-xr-xlinux/home/.config/polybar/scripts/sysmenu.sh42
-rwxr-xr-xlinux/home/.config/polybar/scripts/sysmenu.shsave40
-rwxr-xr-xlinux/home/.config/polybar/scripts/system-usb-mount.sh53
-rwxr-xr-xlinux/home/.config/polybar/scripts/temperature.sh18
-rwxr-xr-xlinux/home/.config/polybar/scripts/toggle_bluetooth.sh8
-rwxr-xr-xlinux/home/.config/polybar/scripts/vpn.sh28
-rw-r--r--linux/home/.config/pypoetry/config.toml3
-rw-r--r--linux/home/.config/rofi/Notif.rasi153
-rw-r--r--linux/home/.config/rofi/colors/gruvbox.rasi10
-rw-r--r--linux/home/.config/rofi/colors/nord.rasi10
-rw-r--r--linux/home/.config/rofi/colors/simple.rasi10
-rw-r--r--linux/home/.config/rofi/config.rasi178
-rw-r--r--linux/home/.config/rofi/options_menu.rasi71
-rw-r--r--linux/home/.config/rofi/rofi-network-manager.conf41
-rw-r--r--linux/home/.config/rofi/rofi-network-manager.rasi127
-rw-r--r--linux/home/.config/rofi/styles/appmenu.rasi186
-rw-r--r--linux/home/.config/rofi/styles/powermenu.rasi187
-rw-r--r--linux/home/.config/rofi/themes/colors.rasi18
-rw-r--r--linux/home/.config/rofi/themes/dmenu.rasi38
-rw-r--r--linux/home/.config/rofi/themes/power.rasi34
-rwxr-xr-xlinux/home/.config/sxhkd/show_help.sh3
-rwxr-xr-xlinux/home/.config/sxhkd/sxhkdrc490
-rwxr-xr-xlinux/home/.config/tmux/file_manager.sh108
-rwxr-xr-xlinux/home/.config/tmux/fzf-menu.sh60
-rwxr-xr-xlinux/home/.config/tmux/left-status.sh25
-rwxr-xr-xlinux/home/.config/tmux/notes.sh113
-rwxr-xr-xlinux/home/.config/tmux/right-status.sh27
-rwxr-xr-xlinux/home/.config/tmux/tmux-popup-pane-manager.sh152
-rwxr-xr-xlinux/home/.config/tmux/tmux-toggle-option.sh27
-rw-r--r--linux/home/.config/tmux/tmux.conf835
-rwxr-xr-xlinux/home/.config/tmux/tmux_number.sh11
-rw-r--r--linux/home/.config/tridactyl/tridactylrc170
-rw-r--r--linux/home/.config/user-dirs.dirs15
-rw-r--r--linux/home/.config/user-dirs.locale1
-rw-r--r--linux/home/.config/waybar/config.jsonc292
-rw-r--r--linux/home/.config/waybar/style.css434
-rw-r--r--linux/home/.config/wofi/config17
-rw-r--r--linux/home/.config/wofi/style.css99
-rw-r--r--linux/home/.config/xkb/symbols/custom-us27
-rwxr-xr-xlinux/home/.config/xob/launch.sh10
-rwxr-xr-xlinux/home/.config/xob/manage-brightness16
-rwxr-xr-xlinux/home/.config/xob/manage-microphone56
-rwxr-xr-xlinux/home/.config/xob/manage-volume46
-rw-r--r--linux/home/.config/xob/styles.cfg100
-rw-r--r--linux/home/.config/zathura/zathurarc27
-rwxr-xr-xlinux/home/.local/bin/control-center31
-rwxr-xr-xlinux/home/.local/bin/ewwbin0 -> 22225024 bytes
-rwxr-xr-xlinux/home/.local/bin/ffmpeg24
-rwxr-xr-xlinux/home/.local/bin/nitrogen63
-rwxr-xr-xlinux/home/.local/bin/notification-center26
-rwxr-xr-xlinux/home/.local/bin/xcolor-pick31
-rw-r--r--linux/home/.local/share/applications/com.obsproject.Studio.desktop99
-rw-r--r--linux/home/.local/share/applications/phototonic.desktop17
-rwxr-xr-xlinux/home/.scripts/Heads-Up-Display28
-rw-r--r--linux/home/.scripts/README.md1
-rwxr-xr-xlinux/home/.scripts/bspwm-toggle-visibility.sh23
-rwxr-xr-xlinux/home/.scripts/bspwm_resize.sh67
-rw-r--r--linux/home/.scripts/check-updates.sh0
-rwxr-xr-xlinux/home/.scripts/colors.sh78
-rwxr-xr-xlinux/home/.scripts/cryptocheck31
-rwxr-xr-xlinux/home/.scripts/cryptonotify19
-rwxr-xr-xlinux/home/.scripts/dotfiles.sh37
-rwxr-xr-xlinux/home/.scripts/ffmpeg24
-rwxr-xr-xlinux/home/.scripts/get_zle_keymap_select.sh13
-rwxr-xr-xlinux/home/.scripts/gsettings.sh27
-rwxr-xr-xlinux/home/.scripts/killandnotify4
-rwxr-xr-xlinux/home/.scripts/layer.sh24
-rwxr-xr-xlinux/home/.scripts/neovim.sh421
-rwxr-xr-xlinux/home/.scripts/opacity-change.sh34
-rwxr-xr-xlinux/home/.scripts/powermenu38
-rwxr-xr-xlinux/home/.scripts/qemu-helper.sh172
-rwxr-xr-xlinux/home/.scripts/random_data.py153
-rwxr-xr-xlinux/home/.scripts/scratchpad64
-rwxr-xr-xlinux/home/.scripts/spec73
-rwxr-xr-xlinux/home/.scripts/track-books.sh19
-rw-r--r--linux/home/.scripts/win-nvim.bat37
-rw-r--r--linux/home/.scripts/win-nvim.ps139
-rw-r--r--linux/home/.vim/autoload/autoload/statusline.vim267
-rw-r--r--linux/home/.vim/autoload/statusline.vim267
-rw-r--r--linux/home/.vim/colors/colors/colorscheme.vim247
-rw-r--r--linux/home/.vim/colors/colors/default.vim175
-rw-r--r--linux/home/.vim/colors/colorscheme.vim247
-rw-r--r--linux/home/.vim/colors/default.vim175
-rw-r--r--linux/home/.vim/ftplugin/after/vim.vim12
-rw-r--r--linux/home/.vim/ftplugin/ftplugin/after/vim.vim12
m---------linux/home/.vim/pack/plugins/start/vim-tmux-navigator0
-rw-r--r--linux/home/.vim/vimrc700
-rw-r--r--linux/home/.vnc/config4
-rwxr-xr-xlinux/home/.vnc/xstartup16
-rw-r--r--macos/Library/Preferences/com.example.plist0
-rw-r--r--macos/home/.zshrc0
-rw-r--r--profile/dev/README.md0
-rw-r--r--profile/minimal/README.md0
-rw-r--r--profile/server/README.md0
-rw-r--r--windows/AppData/windows-terminal/settings.json590
-rw-r--r--windows/Documents/PowerShell/Microsoft.PowerShell_profile.ps1284
-rw-r--r--windows/Documents/PowerShell/bloatware.ps1332
-rw-r--r--windows/Documents/PowerShell/bootstrap.ps1669
-rw-r--r--windows/Documents/PowerShell/initialize.ps1227
-rw-r--r--windows/install.bat11
453 files changed, 39033 insertions, 0 deletions
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..a0e61cc
--- /dev/null
+++ b/README.md
@@ -0,0 +1,1425 @@
+<pre>
+<p align="center">
+██████╗ ██████╗ ████████╗███████╗██╗██╗ ███████╗███████╗
+██╔══██╗██╔═══██╗╚══██╔══╝██╔════╝██║██║ ██╔════╝██╔════╝
+██║ ██║██║ ██║ ██║ █████╗ ██║██║ █████╗ ███████╗
+██║ ██║██║ ██║ ██║ ██╔══╝ ██║██║ ██╔══╝ ╚════██║
+██████╔╝╚██████╔╝ ██║ ██║ ██║███████╗███████╗███████║
+╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝
+</p>
+</pre>
+
+<h3 align="center">
+Welcome, and make yourself at <b><i>$HOME</i></b>
+</h3>
+
+![1](common/assets/desktop.jpg)
+
+> Agnostic/cross-platform dotfiles (Linux/MacOS/Windows)
+
+
+- This repository is designed to be a bare Git dotfiles repository located in home directory/work-tree.
+- Easy dotfiles management that respects the file hierarchy/XDG structure of the platform.
+- Custom `config` command that intelligently manages files across different operating systems.
+
+---
+
+## Details
+
+Linux:
+- **OS:** [Gentoo Hardened](https://www.gentoo.org)
+- **WM/Compositor:** [hyprland](https://hyprland.org)
+- **Widgets:** [ags](https://aylur.github.io/ags)
+- **Shell:** [zsh](https://zsh.org)
+- **Terminal:** [wezterm](https://https://wezfurlong.org/wezterm)
+- **Multiplexer:** [tmux](https://github.com/tmux/tmux/wiki)
+- **Editor:** [neovim](https://neovim.io)
+ - **Config:** [nvim](https://github.com/srdusr/nvim)
+- **Fonts:**
+ - **Icons:** Whitesur
+ - **UI:** San Francisco
+ - **Terminal:** JetBrains Mono
+
+---
+
+## Installation Methods
+
+### Method 1: Shell Scripts (Recommended)
+
+**Linux/macOS:**
+```sh
+sh -c "$(curl -fsSL https://raw.githubusercontent.com/srdusr/dotfiles/main/common/install.sh)"
+```
+
+**Windows PowerShell:**
+```powershell
+iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/srdusr/dotfiles/main/windows/Documents/PowerShell/bootstrap.ps1'))
+
+# or
+
+$bat = "$env:TEMP\install.bat"
+Invoke-WebRequest -Uri "https://raw.githubusercontent.com/srdusr/dotfiles/main/windows/install.bat" -OutFile $bat
+cmd /c $bat
+```
+
+### Method 2: Ansible Automation
+
+Alternative to the shell scripts for managing multiple machines:
+
+```bash
+# Clone repository
+git clone https://github.com/srdusr/dotfiles.git
+cd dotfiles/ansible
+
+# Install Ansible
+pip install ansible
+
+# Deploy to localhost (replaces install.sh/bootstrap.ps1)
+ansible-playbook -i inventory.yml playbook.yml -e dotfiles_profile=dev
+
+# Deploy to remote hosts
+ansible-playbook -i inventory.yml playbook.yml --limit linux
+```
+
+### Method 3: Installing onto a new system (Manual)
+
+1. Avoid weird behaviour/recursion issues when `.cfg` tries to track itself
+
+```bash
+echo ".cfg" >> .gitignore
+```
+
+2. Clone the repository
+
+```bash
+# Linux/MacOS/WSL
+git clone --bare https://github.com/srdusr/dotfiles.git $HOME/.cfg
+```
+
+```ps1
+# Windows (PowerShell)
+git clone --bare https://github.com/srdusr/dotfiles.git $env:USERPROFILE/.cfg
+```
+
+<a name="config-example"></a>
+
+3. Setup the `config` command/function
+
+**Linux/macOS:**
+Copy and paste the following snippet to any profile/startup file ie. `~/.bashrc`, `~/.zshrc` etc.
+
+<details>
+ <summary><b>Bash/Zsh:</b> (click here)</summary>
+
+```bash
+# Dotfiles Management System
+if [[ -d "$HOME/.cfg" && -d "$HOME/.cfg/refs" ]]; then
+ # Core git wrapper with repository as work-tree
+ _config() {
+ git --git-dir="$HOME/.cfg" --work-tree="$HOME/.cfg" "$@"
+ }
+
+ # Detect OS
+ case "$(uname -s)" in
+ Linux) CFG_OS="linux" ;;
+ Darwin) CFG_OS="macos" ;;
+ MINGW*|MSYS*|CYGWIN*) CFG_OS="windows" ;;
+ *) CFG_OS="other" ;;
+ esac
+
+ # Map system path to repository path
+ _repo_path() {
+ local f="$1"
+
+ # If it's an absolute path that's not in HOME, handle it specially
+ if [[ "$f" == /* && "$f" != "$HOME/"* ]]; then
+ echo "$CFG_OS/${f#/}"
+ return
+ fi
+
+ # Check for paths that should go to the repository root
+ case "$f" in
+ common/*|linux/*|macos/*|windows/*|profile/*|README.md)
+ echo "$f"
+ return
+ ;;
+ "$HOME/"*)
+ f="${f#$HOME/}"
+ ;;
+ esac
+
+ # Default: put under OS-specific home
+ echo "$CFG_OS/home/$f"
+ }
+
+ _sys_path() {
+ local repo_path="$1"
+ local os_path_pattern="$CFG_OS/"
+
+ # Handle OS-specific files that are not in the home subdirectory
+ if [[ "$repo_path" == "$os_path_pattern"* && "$repo_path" != */home/* ]]; then
+ echo "/${repo_path#$os_path_pattern}"
+ return
+ fi
+
+ case "$repo_path" in
+ # Common configs → OS-specific config dirs
+ common/config/*)
+ case "$CFG_OS" in
+ linux)
+ local base="${XDG_CONFIG_HOME:-$HOME/.config}"
+ echo "$base/${repo_path#common/config/}"
+ ;;
+ macos)
+ echo "$HOME/Library/Application Support/${repo_path#common/config/}"
+ ;;
+ windows)
+ echo "$LOCALAPPDATA\\${repo_path#common/config/}"
+ ;;
+ *)
+ echo "$HOME/.config/${repo_path#common/config/}"
+ ;;
+ esac
+ ;;
+
+ # Common assets → stay in repo
+ common/assets/*)
+ echo "$HOME/.cfg/$repo_path"
+ ;;
+
+ # Other common files (dotfiles like .bashrc, .gitconfig, etc.) → $HOME
+ common/*)
+ echo "$HOME/${repo_path#common/}"
+ ;;
+
+ # OS-specific home
+ */home/*)
+ echo "$HOME/${repo_path#*/home/}"
+ ;;
+
+ # Profile configs and README → stay in repo
+ profile/*|README.md)
+ echo "$HOME/.cfg/$repo_path"
+ ;;
+
+ # Default fallback
+ *)
+ echo "$HOME/.cfg/$repo_path"
+ ;;
+
+ esac
+ }
+
+ # Prompts for sudo if needed and runs the command
+ _sudo_prompt() {
+ if [[ $EUID -eq 0 ]]; then
+ "$@"
+ else
+ if command -v sudo >/dev/null; then
+ sudo "$@"
+ elif command -v doas >/dev/null; then
+ doas "$@"
+ elif command -v pkexec >/dev/null; then
+ pkexec "$@"
+ else
+ echo "Error: No privilege escalation tool found."
+ return 1
+ fi
+ fi
+ }
+
+ # Main config command
+ config() {
+ local cmd="$1"; shift
+ local target_dir=""
+ # Parse optional --target flag for add
+ if [[ "$cmd" == "add" ]]; then
+ while [[ "$1" == --* ]]; do
+ case "$1" in
+ --target|-t)
+ target_dir="$2"
+ shift 2
+ ;;
+ *)
+ echo "Unknown option: $1"
+ return 1
+ ;;
+ esac
+ done
+ fi
+
+ case "$cmd" in
+ add)
+ local file_path
+ for file_path in "$@"; do
+ local repo_path
+ if [[ -n "$target_dir" ]]; then
+ local rel_path
+ if [[ "$file_path" == /* ]]; then
+ rel_path="$(basename "$file_path")"
+ else
+ rel_path="$file_path"
+ fi
+ repo_path="$target_dir/$rel_path"
+ else
+ repo_path="$(_repo_path "$file_path")"
+ fi
+
+ local full_repo_path="$HOME/.cfg/$repo_path"
+ mkdir -p "$(dirname "$full_repo_path")"
+ cp -a "$file_path" "$full_repo_path"
+
+ git --git-dir="$HOME/.cfg" --work-tree="$HOME/.cfg" add "$repo_path"
+
+ echo "Added: $file_path -> $repo_path"
+ done
+ ;;
+ rm)
+ local rm_opts=""
+ local file_path_list=()
+
+ for arg in "$@"; do
+ if [[ "$arg" == "-"* ]]; then
+ rm_opts+=" $arg"
+ else
+ file_path_list+=("$arg")
+ fi
+ done
+
+ for file_path in "${file_path_list[@]}"; do
+ local repo_path="$(_repo_path "$file_path")"
+
+ if [[ "$rm_opts" == *"-r"* ]]; then
+ _config rm --cached -r "$repo_path"
+ else
+ _config rm --cached "$repo_path"
+ fi
+
+ eval "rm $rm_opts \"$file_path\""
+ echo "Removed: $file_path"
+ done
+ ;;
+ sync)
+ local direction="${1:-to-repo}"; shift
+ _config ls-files | while read -r repo_file; do
+ local sys_file="$(_sys_path "$repo_file")"
+ local full_repo_path="$HOME/.cfg/$repo_file"
+ if [[ "$direction" == "to-repo" ]]; then
+ if [[ -e "$sys_file" && -n "$(diff "$full_repo_path" "$sys_file" 2>/dev/null || echo "diff")" ]]; then
+ cp -a "$sys_file" "$full_repo_path"
+ echo "Synced to repo: $sys_file"
+ fi
+ elif [[ "$direction" == "from-repo" ]]; then
+ if [[ -e "$full_repo_path" && -n "$(diff "$full_repo_path" "$sys_file" 2>/dev/null || echo "diff")" ]]; then
+ local dest_dir="$(dirname "$sys_file")"
+ if [[ "$sys_file" == /* && "$sys_file" != "$HOME/"* ]]; then
+ _sudo_prompt mkdir -p "$dest_dir"
+ _sudo_prompt cp -a "$full_repo_path" "$sys_file"
+ else
+ mkdir -p "$dest_dir"
+ cp -a "$full_repo_path" "$sys_file"
+ fi
+ echo "Synced from repo: $sys_file"
+ fi
+ fi
+ done
+ ;;
+ status)
+ local auto_synced=()
+ while read -r repo_file; do
+ local sys_file="$(_sys_path "$repo_file")"
+ local full_repo_path="$HOME/.cfg/$repo_file"
+ if [[ -e "$sys_file" && -e "$full_repo_path" ]]; then
+ if ! diff -q "$full_repo_path" "$sys_file" >/dev/null 2>&1; then
+ cp -fa "$sys_file" "$full_repo_path"
+ auto_synced+=("$repo_file")
+ fi
+ fi
+ done < <(_config ls-files)
+ if [[ ${#auto_synced[@]} -gt 0 ]]; then
+ echo "=== Auto-synced Files ==="
+ for repo_file in "${auto_synced[@]}"; do
+ echo "synced: $(_sys_path "$repo_file") -> $repo_file"
+ done
+ echo
+ fi
+ _config status
+ echo
+ ;;
+ deploy)
+ _config ls-files | while read -r repo_file; do
+ local full_repo_path="$HOME/.cfg/$repo_file"
+ local sys_file="$(_sys_path "$repo_file")" # destination only
+
+ # Only continue if the source exists
+ if [[ -e "$full_repo_path" && -n "$sys_file" ]]; then
+ local dest_dir
+ dest_dir="$(dirname "$sys_file")"
+
+ # Create destination if needed
+ if [[ "$sys_file" == /* && "$sys_file" != "$HOME/"* ]]; then
+ _sudo_prompt mkdir -p "$dest_dir"
+ _sudo_prompt cp -a "$full_repo_path" "$sys_file"
+ else
+ mkdir -p "$dest_dir"
+ cp -a "$full_repo_path" "$sys_file"
+ fi
+
+ echo "Deployed: $repo_file -> $sys_file"
+ fi
+ done
+ ;;
+ checkout)
+ echo "Checking out dotfiles from .cfg..."
+ _config ls-files | while read -r repo_file; do
+ local full_repo_path="$HOME/.cfg/$repo_file"
+ local sys_file="$(_sys_path "$repo_file")"
+
+ if [[ -e "$full_repo_path" && -n "$sys_file" ]]; then
+ local dest_dir
+ dest_dir="$(dirname "$sys_file")"
+
+ # Create destination if it doesn't exist
+ if [[ "$sys_file" == /* && "$sys_file" != "$HOME/"* ]]; then
+ _sudo_prompt mkdir -p "$dest_dir"
+ _sudo_prompt cp -a "$full_repo_path" "$sys_file"
+ else
+ mkdir -p "$dest_dir"
+ cp -a "$full_repo_path" "$sys_file"
+ fi
+
+ echo "Checked out: $repo_file -> $sys_file"
+ fi
+ done
+ ;;
+ backup)
+ local timestamp=$(date +%Y%m%d%H%M%S)
+ local backup_dir="$HOME/.dotfiles_backup/$timestamp"
+ echo "Backing up existing dotfiles to $backup_dir..."
+
+ _config ls-files | while read -r repo_file; do
+ local sys_file="$(_sys_path "$repo_file")"
+ if [[ -e "$sys_file" ]]; then
+ local dest_dir_full="$backup_dir/$(dirname "$repo_file")"
+ mkdir -p "$dest_dir_full"
+ cp -a "$sys_file" "$backup_dir/$repo_file"
+ fi
+ done
+ echo "Backup complete. To restore, copy files from $backup_dir to their original locations."
+ ;;
+ *)
+ _config "$cmd" "$@"
+ ;;
+ esac
+ }
+fi
+```
+
+ </details>
+
+
+**Windows PowerShell:**
+Paste the PowerShell code block directly into your PowerShell profile.
+To find your profile path, simply run `$PROFILE` in your terminal. You can open and edit it with `notepad $PROFILE`.
+If the file doesn't exist, you can create it.
+```ps1
+New-Item -Path $PROFILE -ItemType File -Force
+```
+
+<details>
+ <summary><b>PowerShell:</b> (click here)</summary>
+
+```ps1
+# Dotfiles Management System
+if (Test-Path "$HOME\.cfg" -and Test-Path "$HOME\.cfg\refs") {
+
+ # Core git wrapper with repository as work-tree
+ function _config {
+ param(
+ [Parameter(Mandatory=$true, ValueFromRemainingArguments=$true)]
+ [String[]]$Args
+ )
+ git --git-dir="$HOME\.cfg" --work-tree="$HOME" @Args
+ }
+
+ # Detect OS (cross-platform, PowerShell-native)
+ $osPlatform = [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform
+ if ($osPlatform([System.Runtime.InteropServices.OSPlatform]::Windows)) {
+ $global:CFG_OS = "windows"
+ } elseif ($osPlatform([System.Runtime.InteropServices.OSPlatform]::Linux)) {
+ $global:CFG_OS = "linux"
+ } elseif ($osPlatform([System.Runtime.InteropServices.OSPlatform]::OSX)) {
+ $global:CFG_OS = "macos"
+ } else {
+ $global:CFG_OS = "other"
+ }
+
+ # Map system path to repository path
+ function _repo_path {
+ param([string]$FilePath)
+
+ $repoPath = ""
+ # Handle absolute paths outside the user's home directory
+ if ($FilePath.StartsWith("\") -or $FilePath.Contains(":")) {
+ $repoPath = "$CFG_OS\root\$FilePath"
+ return $repoPath -replace '\\', '/'
+ }
+
+ $homePath = "$HOME"
+ # Check if file is in the home directory
+ if ($FilePath.StartsWith($homePath)) {
+ $relativePath = $FilePath.Substring($homePath.Length + 1)
+ # Check for paths that are explicitly within the repo structure
+ switch -wildcard ($FilePath) {
+ "$HOME\.cfg\*" { $repoPath = "" }
+ "common\*" { $repoPath = $FilePath }
+ "$CFG_OS\*" { $repoPath = $FilePath }
+ default { $repoPath = "$CFG_OS\home\$relativePath" }
+ }
+ } else {
+ # Default for relative paths (assumes they are in the home directory)
+ $repoPath = "$CFG_OS\home\$FilePath"
+ }
+
+ # Clean up path separators
+ return $repoPath -replace '\\', '/'
+ }
+
+ # Map repository path back to system path
+ function _sys_path {
+ param([string]$RepoPath)
+
+ $sysPath = ""
+ switch -wildcard ($RepoPath) {
+ "common/config/*" {
+ $file = $RepoPath.Substring("common/config/".Length)
+ if ($CFG_OS -eq "windows") {
+ $sysPath = Join-Path $HOME "AppData\Local\$file"
+ } else {
+ $sysPath = Join-Path $HOME ".config\$file"
+ }
+ }
+ "common/bin/*" {
+ $file = $RepoPath.Substring("common/bin/".Length)
+ if ($CFG_OS -eq "windows") {
+ $sysPath = Join-Path $HOME "bin\$file"
+ } else {
+ $sysPath = Join-Path $HOME ".local\bin\$file"
+ }
+ }
+ "common/*" {
+ $file = $RepoPath.Substring("common/".Length)
+ $sysPath = Join-Path $HOME $file
+ }
+ "*/home/*" {
+ $file = $RepoPath.Substring($RepoPath.IndexOf("home/") + "home/".Length)
+ $sysPath = Join-Path $HOME $file
+ }
+ "*/root/*" {
+ $file = $RepoPath.Substring($RepoPath.IndexOf("root/") + "root/".Length)
+ $sysPath = $file
+ }
+ default {
+ $sysPath = Join-Path $HOME $RepoPath
+ }
+ }
+ return $sysPath -replace '/', '\'
+ }
+
+ # Prompts for administrator permissions if needed and runs the command
+ function _admin_prompt {
+ param(
+ [Parameter(Mandatory=$true, ValueFromRemainingArguments=$true)]
+ [String[]]$Command
+ )
+ if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
+ Write-Host "Warning: This action requires administrator privileges."
+ Start-Process powershell.exe -ArgumentList "-NoProfile", "-Command", "Set-Location '$PWD'; & $Command" -Verb RunAs
+ } else {
+ & $Command
+ }
+ }
+
+ # NOTE: can change `config` to whatever you feel comfortable ie. dotfiles, dots, cfg etc.
+ function config {
+ param(
+ [string]$Command,
+ [Parameter(ValueFromRemainingArguments=$true)]
+ [string[]]$Args
+ )
+
+ switch ($Command) {
+ "add" {
+ foreach ($file in $Args) {
+ $repoPath = _repo_path $file
+ if ([string]::IsNullOrEmpty($repoPath)) {
+ Write-Host "Warning: Ignoring file within the bare repo: $file"
+ continue
+ }
+ $fullRepoPath = Join-Path "$HOME\.cfg" $repoPath
+ $dir = Split-Path $fullRepoPath
+ if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir | Out-Null }
+ Copy-Item -Path $file -Destination $fullRepoPath -Recurse -Force
+ _config add $repoPath
+ Write-Host "Added: $file -> $repoPath"
+ }
+ }
+
+ "rm" {
+ foreach ($file in $Args) {
+ $repoPath = _repo_path $file
+ _config rm $repoPath
+ Remove-Item -Path (Join-Path "$HOME\.cfg" $repoPath) -Force
+ Write-Host "Removed: $file ($repoPath)"
+ }
+ }
+
+ "sync" {
+ $direction = if ($Args) { $Args[0] } else { "to-repo" }
+ _config ls-files | ForEach-Object {
+ $repoFile = $_
+ $sysFile = _sys_path $repoFile
+ $fullRepoPath = Join-Path "$HOME\.cfg" $repoFile
+ if ($direction -eq "to-repo") {
+ if ((Test-Path $sysFile) -and ((Get-Content $fullRepoPath) -ne (Get-Content $sysFile))) {
+ Copy-Item $sysFile $fullRepoPath -Force
+ Write-Host "Synced to repo: $sysFile"
+ }
+ } elseif ($direction -eq "from-repo") {
+ if ((Test-Path $fullRepoPath) -and ((Get-Content $fullRepoPath) -ne (Get-Content $sysFile))) {
+ $destDir = Split-Path $sysFile
+ if ($sysFile.StartsWith('\') -or $sysFile.Contains(':')) {
+ _admin_prompt Copy-Item $fullRepoPath $sysFile -Recurse -Force
+ } else {
+ if (-not (Test-Path $destDir)) { New-Item -ItemType Directory -Path $destDir | Out-Null }
+ Copy-Item $fullRepoPath $sysFile -Recurse -Force
+ }
+ Write-Host "Synced from repo: $sysFile"
+ }
+ }
+ }
+ }
+
+ "status" {
+ $autoSynced = @()
+ _config ls-files | ForEach-Object {
+ $repoFile = $_
+ $sysFile = _sys_path $repoFile
+ $fullRepoPath = Join-Path "$HOME\.cfg" $repoFile
+ if ((Test-Path $sysFile) -and (Test-Path $fullRepoPath)) {
+ if ((Get-Content $fullRepoPath) -ne (Get-Content $sysFile)) {
+ Copy-Item $sysFile $fullRepoPath -Force
+ $autoSynced += $repoFile
+ }
+ }
+ }
+ if ($autoSynced.Count -gt 0) {
+ Write-Host "=== Auto-synced Files ==="
+ foreach ($repoFile in $autoSynced) {
+ Write-Host "synced: $(_sys_path $repoFile) → $repoFile"
+ }
+ Write-Host
+ }
+ _config status
+ }
+
+ "deploy" {
+ _config ls-files | ForEach-Object {
+ $repoFile = $_
+ $sysFile = _sys_path $repoFile
+ $fullRepoPath = Join-Path "$HOME\.cfg" $repoFile
+ if (Test-Path $fullRepoPath) {
+ if (-not [string]::IsNullOrEmpty($sysFile)) {
+ $destDir = Split-Path $sysFile
+ if ($sysFile.StartsWith('\') -or $sysFile.Contains(':')) {
+ _admin_prompt Copy-Item $fullRepoPath $sysFile -Recurse -Force
+ } else {
+ if (-not (Test-Path $destDir)) { New-Item -ItemType Directory -Path $destDir | Out-Null }
+ Copy-Item $fullRepoPath $sysFile -Recurse -Force
+ }
+ Write-Host "Deployed: $repoFile -> $sysFile"
+ }
+ }
+ }
+ }
+
+ "backup" {
+ $timestamp = Get-Date -Format "yyyyMMddHHmmss"
+ $backupDir = Join-Path $HOME ".dotfiles_backup\$timestamp"
+ Write-Host "Backing up existing dotfiles to $backupDir..."
+
+ _config ls-files | ForEach-Object {
+ $repoFile = $_
+ $sysFile = _sys_path $repoFile
+ if (Test-Path $sysFile) {
+ $destDirFull = Join-Path $backupDir $repoFile
+ if (-not (Test-Path $destDirFull)) { New-Item -ItemType Directory -Path $destDirFull -Force | Out-Null }
+ Copy-Item $sysFile $destDirFull -Recurse -Force
+ }
+ }
+ Write-Host "Backup complete. To restore, copy files from $backupDir to their original locations."
+ }
+
+ default {
+ _config $Command @Args
+ }
+ }
+ }
+}
+```
+
+ </details>
+
+Restart the terminal or source the session profile file used.
+
+
+4. Checkout dotfiles from the repository
+
+**Important:** After cloning the bare repository, you need to checkout the files to restore the directory structure:
+
+```bash
+config checkout
+```
+
+If you get conflicts about existing files, you can force the checkout:
+
+```bash
+config checkout -f
+```
+
+
+5. Configure repository settings
+
+```bash
+config config --local status.showUntrackedFiles no
+```
+
+6. Deploy dotfiles to system locations
+
+```bash
+config deploy
+```
+
+**Note:** All installation methods include:
+- System hardening and security configurations
+- Kernel/OS/distribution update checking
+- Profile-based package installation
+- Development environment setup
+
+---
+
+## Usage Examples
+
+### Adding Files to Your Dotfiles
+
+```bash
+# Add a config file explicitly to the common directory in the repo
+config add --target common .bashrc
+
+# Add with a specific target directory
+config add --target windows ~/Documents/PowerShell/Microsoft.PowerShell_profile.ps1
+
+# Windows:
+config add --target windows "$env:USERPROFILE\Documents\PowerShell\Microsoft.PowerShell_profile.ps1"
+
+# Linux WSL or Git Bash:
+config add --target windows /mnt/c/Users/`username`/Documents/PowerShell/Microsoft.PowerShell_profile.ps1
+
+# Add multiple files at once (each will be mapped appropriately)
+config add ~/.vim .tmux.conf # Will go to OS's home
+
+# Add files outside of home
+config add --target linux/etc /etc/issue
+
+```
+---
+
+<details>
+ <summary><b>Setup/guide</b> (If you have time to read)</summary>
+
+### Dotfiles Setup
+
+
+1. Initialize a Bare Repository
+
+First, set up a bare Git repository in your home directory. A bare repository contains only the internal Git metadata (like commit history and branches) without a checked-out copy of your files. This is perfect for dotfiles because it lets Git manage files directly in your home directory without cluttering it with a visible .git folder.
+
+```bash
+# Bash/Zsh:
+cd ~
+git init --bare $HOME/.cfg
+```
+
+```ps1
+# PowerShell:
+Set-Location $HOME
+git init --bare "$HOME\.cfg"
+```
+
+2. Create the Directory Structure
+
+To keep your dotfiles organized and easily managed across platforms, create subdirectories for common and OS-specific files. This structure allows the `config` command to automatically place files in the correct location.
+
+```bash
+# Bash/Zsh:
+mkdir -p $HOME/.cfg/{common,linux,macos,profile,windows}
+```
+
+```ps1
+# PowerShell:
+New-Item -ItemType Directory -Force -Path "$HOME\.cfg\common","$HOME\.cfg\linux","$HOME\.cfg\macos","$HOME\.cfg\profile","$HOME\.cfg\windows"
+```
+
+3. Create `config` command by pasting this into relevant profile file ie, `.bashrc`, `.zshrc`, `profile.ps1` and restarting the terminal
+[config command:](#config-example)
+
+4. Hide untracked files
+
+```bash
+config config --local status.showUntrackedFiles no
+ ```
+
+5. Add Files to Your Repository
+
+Now you can use the `config` command to add your dotfiles. The `config add` command copies the file from your system into the correct folder within your bare repository and stages it for a Git commit.
+
+To add a file specific to your operating system:
+
+```bash
+# Bash/Zsh:
+config add --target common .bashrc # Added to $HOME/.cfg/common/.bashrc
+```
+
+```bash
+# PowerShell:
+config add $PROFILE
+```
+
+The `config` command intelligently determines the correct subdirectory based on OS and the file's location.
+
+To add a file from the repository's root, like a README.md:
+
+```bash
+# Bash/Zsh:
+config add $HOME/.cfg/README.md # This is added to the root of your repo
+```
+
+```bash
+# PowerShell:
+config add "$HOME\.cfg\README.md"
+```
+
+This works because `config` is configured to recognize and handle files explicitly within the .cfg directory.
+Can also specify/edit/add other other OS/common or outside of home directory by providing a path relative in .cfg/
+
+To add a common file:
+
+```bash
+config add $HOME/.cfg/common/.aliases # Added to $HOME/.cfg/common/.aliases
+```
+
+To add a file from outside the home directory:
+
+```bash
+# Bash/Zsh:
+config add /etc/fstab # Added to $HOME/.cfg/linux/etc/fstab
+```
+
+```bash
+# PowerShell:
+Start-Process powershell -Verb RunAs -ArgumentList "config add C:\Windows\System32\drivers\etc\hosts"
+```
+
+NOTE: The `config` command is also capable of handling system-level configuration files that require administrator privileges. Will ask for your password/need admin privileges
+
+
+6. Commit and Push
+
+Once your files are added, you can commit them and push them to a remote repository (like GitHub or GitLab) for safekeeping and easy synchronization across your machines.
+
+```bash
+# Commit the changes
+config commit -m "Initial commit of dotfiles"
+
+# Add a remote origin (replace with your repository's URL)
+config remote add origin https://github.com/<username>/dotfiles.git
+
+# Push your changes
+config push -u origin main
+```
+
+---
+
+### Fzf
+
+- Install Fzf
+
+```
+$ sudo git clone --depth 1 https://github.com/junegunn/fzf.git /usr/local/bin/fzf
+```
+
+- Put this into `.bashrc`/`.zshrc` or any similar shell configuration file to make it persistent across sessions
+
+```bash
+export PATH="$PATH:/usr/local/bin/fzf/bin"
+export FZF_BASE="/usr/local/bin/fzf"
+```
+
+- Also put this in to load fzf keybindings and completions
+
+```bash
+# bash
+source /usr/local/bin/fzf/shell/key-bindings.bash
+source /usr/local/bin/fzf/shell/completion.bash
+```
+
+```bash
+# zsh
+source /usr/local/bin/fzf/shell/key-bindings.zsh
+source /usr/local/bin/fzf/shell/completion.zsh
+```
+
+---
+
+### Zsh plugins
+
+- Install the plugins
+
+```bash
+# Clone zsh-you-should-use
+$ git clone https://github.com/MichaelAquilina/zsh-you-should-use.git ~/.config/zsh/plugins/zsh-you-should-use
+
+# Clone zsh-syntax-highlighting
+$ git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ~/.config/zsh/plugins/zsh-syntax-highlighting
+
+# Clone zsh-autosuggestions
+$ git clone https://github.com/zsh-users/zsh-autosuggestions.git ~/.config/zsh/plugins/zsh-autosuggestions
+```
+
+- Put this into `.zshrc` (preferably at the very end of the file) to allow it to source the plugins across sessions
+
+```bash
+# Suggest aliases for commands
+source ~/.config/zsh/plugins/zsh-you-should-use/you-should-use.plugin.zsh
+
+# Load zsh-syntax-highlighting
+source ~/.config/zsh/plugins/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh
+
+# Load fish like auto suggestions
+source ~/.config/zsh/plugins/zsh-autosuggestions/zsh-autosuggestions.plugin.zsh
+source ~/.config/zsh/plugins/zsh-autosuggestions/zsh-autosuggestions.zsh
+```
+
+---
+
+### Wezterm
+
+- Make sure Rust is installed first
+
+```bash
+$ curl https://sh.rustup.rs -sSf | sh -s
+```
+
+- Install and build Wezterm
+
+```bash
+$ git clone --depth=1 --branch=main --recursive https://github.com/wez/wezterm.git
+$ cd wezterm
+$ git submodule update --init --recursive
+$ ./get-deps
+$ cargo build --release
+$ cargo run --release --bin wezterm -- start
+$ sudo install wezterm wezterm-gui wezterm-mux-server strip-ansi-escapes /usr/local/bin
+
+```
+
+---
+
+### Neovim
+
+> Dependencies
+
+| Platform | ninja-build | ninja | base-devel | build-base | coreutils | gmake | cmake | make | gcc | g++ | gcc-c++ | unzip | wget | curl | gettext | gettext-tools | gettext-tiny-dev | automake | autoconf | libtool | libtool-bin | pkg-config | pkgconfig | pkgconf | tree-sitter | patch | doxygen | sha | git | Pack Manager |
+| ------------------ | ----------- | ----- | ---------- | ---------- | --------- | ----- | ----- | ---- | --- | --- | ------- | ----- | ---- | ---- | ------- | ------------- | ---------------- | -------- | -------- | ------- | ----------- | ---------- | --------- | ------- | ----------- | ----- | ------- | --- | --- | ------------ |
+| Ubuntu/Debian | ✓ | | | | | | ✓ | | | ✓ | | ✓ | | ✓ | ✓ | | | ✓ | ✓ | ✓ | ✓ | ✓ | | | | | ✓ | | | apt-get |
+| CentOS/RHEL/Fedora | ✓ | | | | | | ✓ | ✓ | ✓ | | ✓ | ✓ | | ✓ | ✓ | | | ✓ | ✓ | ✓ | | | ✓ | | | ✓ | | | | dnf |
+| openSUSE | | ✓ | | | | | ✓ | | | | ✓ | | | ✓ | | ✓ | | ✓ | ✓ | ✓ | | | | | | | | | | zypper |
+| Arch Linux | | ✓ | ✓ | | | | ✓ | | | | | ✓ | | ✓ | | | | | | | | | | | ✓ | | | | | pacman |
+| Alpine Linux | | | | | ✓ | | ✓ | | | | | ✓ | | ✓ | | | ✓ | ✓ | ✓ | ✓ | | | | ✓ | | | | | | apk |
+| Void Linux | | | ✓ | ✓ | | | ✓ | | | | | | | ✓ | | | | | | | | | | | | | | | ✓ | xbps |
+| FreeBSD | | | | | | ✓ | ✓ | | | | | ✓ | ✓ | ✓ | ✓ | | | | | ✓ | | | | ✓ | | | | ✓ | | pkg |
+| OpenBSD | | | | | | ✓ | ✓ | | | | | ✓ | | ✓ | | ✓ | | ✓ | ✓ | ✓ | | | | | | | | | | pkg_add |
+| macOS/Homebrew | | ✓ | | | | | ✓ | | | | | | | ✓ | ✓ | | | ✓ | | ✓ | | ✓ | | | | | | | | brew |
+| macOS/MacPorts | | ✓ | | | | | ✓ | | | | | | | | ✓ | | | | | | | | | | | | | | | port |
+
+- Install (default is nightly)
+ ```bash
+ $ git clone https://github.com/neovim/neovim.git
+ $ cd neovim
+ ```
+ - Optional install stable version
+ ```bash
+ $ git checkout stable
+ ```
+ - or specific version by tag
+ ```bash
+ $ git checkout release-0.7
+ ```
+- Build nvim
+ ```bash
+ $ make CMAKE_BUILD_TYPE=Release
+ $ sudo make install
+ ```
+- Install Packer (package manager)
+ ```bash
+ $ git clone --depth 1 https://github.com/wbthomason/packer.nvim\
+ ~/.local/share/nvim/site/pack/packer/start/packer.nvim
+ ```
+- Post-installation:
+ - Install plugins
+ ```vi
+ :PackerSync
+ ```
+ - or save/write on .config/nvim/lua/user/pack.lua to automatically install plugins
+ ```vi
+ :w
+ ```
+ - Install language servers
+ ```vi
+ :Mason
+ ```
+ - Exit out of Mason with `q`, configured language servers should then install automatically
+ > NOTE: If any errors occur, npm needs to be installed and executable, complete **_Development Environment/Languages/Javascript_** section to install nvm/npm
+ - Reload nvim/config with `<leader><space>` where `<leader>` is `;`
+- Uninstall:
+ ```bash
+ $ sudo rm /usr/local/bin/nvim
+ $ sudo rm -r /usr/local/share/nvim/
+ ```
+
+---
+
+### Gnome Custom Settings
+
+- Run gnome custom settings script, located at `~/.scripts`:
+
+```bash
+$ gsettings.sh
+```
+
+---
+
+## Development Environment
+
+### Languages
+
+#### Python
+
+```bash
+
+```
+
+---
+
+#### Java
+
+Recommended to choose Openjdk 8 or 10 otherwise get an error when using Android tools
+
+```bash
+
+```
+
+---
+
+#### Rust
+
+- Download and run rustup script
+
+```bash
+$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- --no-modify-path --default-toolchain stable -y
+```
+
+---
+
+#### Go
+
+```bash
+
+```
+
+---
+
+#### Lua
+
+- Download LuaRocks
+
+```bash
+$ git clone git://github.com/luarocks/luarocks.git
+```
+
+- Install and specify the installation directory to build and configure LuaRocks
+
+```bash
+$ ./configure --prefix=/usr/local/luarocks
+$ make build
+$ sudo make install
+```
+
+- Add LuaRocks to system's environment variables by running the following command or add it `.bashrc`/`.zshrc` or any similar shell configuration file to make it persistent across sessions
+
+```bash
+export PATH=$PATH:/usr/local/luarocks/bin
+```
+
+- Install Lua
+
+```bash
+$ luarocks install lua
+```
+
+---
+
+#### PHP
+
+- Install PHP
+- Install Web server (Apache or Nginx)
+- Install PHP extensions
+
+```
+php-apache php-cgi php-fpm php-gd php-embed php-intl php-redis php-snmp
+mysql-server php8.1-mysql
+phpmyadmin
+```
+
+- Install composer (Dependency Manager for PHP)
+
+```bash
+$ curl -sS https://getcomposer.org/installer | php
+```
+
+- Install laravel
+
+```bash
+$ composer global require laravel/installer
+```
+
+- Edit PHP config
+
+```bash
+$ sudoedit /etc/php/php.ini
+```
+
+- Enable PHP extensions, make sure these lines are uncommented (remove the `;` from each line)
+
+```
+extention=bcmath
+extention=zip
+extension=pdo_mysql
+extension=mysqli
+extension=iconv
+
+extension=gd
+extension=imagick
+extension=pdo_pgsql
+extension=pgsql
+```
+
+- Recommended to set correct timezone
+
+```
+date.timezone = <Continent/City>
+```
+
+- Display errors to debug PHP code
+
+```
+display_errors = On
+```
+
+- Allow paths to be accessed by PHP
+
+```
+open_basedir = /srv/http/:/var/www/:/home/:/tmp/:/var/tmp/:/var/cache/:/usr/share/pear/:/usr/share/webapps/:/etc/webapps/
+```
+
+---
+
+#### Dart
+
+- Install dart or skip and install flutter (recommended) that includes dart
+
+```bash
+$ curl -O "https://storage.googleapis.com/dart-archive/channels/be/raw/latest/sdk/dartsdk-linux-x64-release.zip"
+$ unzip dartsdk-linux-x64-release.zip
+$ sudo mv dart-sdk /usr/lib/dart
+```
+
+NOTE: If Dart SDK is downloaded separately, make sure that the Flutter version of dart is first in path, as the two versions might not be compatible. Use this command `which flutter dart` to see if flutter and dart originate from the same bin directory and are therefore compatible.
+
+- Install flutter
+
+```bash
+$ git clone https://github.com/flutter/flutter.git -b stable
+```
+
+- Move flutter to the `/opt` directory
+
+```bash
+$ sudo mv flutter /opt/
+```
+
+- Export Flutter over Dart by putting this into `.bashrc`/`.zshrc` or any similar shell configuration file to make it persistent across sessions
+
+```bash
+# Flutter/dart path
+export PATH="/opt/flutter:/usr/lib/dart/bin:$PATH"
+# Flutter Web Support
+export PATH=$PATH:/opt/google/chrome
+```
+
+- Set permissions since only Root has access
+
+```bash
+$ sudo groupadd flutterusers
+$ sudo gpasswd -a $USER flutterusers
+$ sudo chown -R :flutterusers /opt/flutter
+$ sudo chmod -R g+w /opt/flutter/
+```
+
+- If still getting any permission denied errors then do this
+
+```bash
+$ sudo chown -R $USER /opt/flutter
+```
+
+- Continue to step **_Development Tools/Android Studio_** section to complete setup
+
+---
+
+#### Javascript
+
+- nvm install/update script
+
+```bash
+$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash
+```
+
+- Put these lines into `.bashrc`/`.zshrc` or any similar shell configuration file to make it persistent across sessions
+
+```bash
+export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")"
+[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
+```
+
+- Install node
+
+```bash
+$ nvm install node
+```
+
+- Install the latest version in order to make npm executable
+
+```bash
+$ nvm install --lts
+```
+
+---
+
+### Development Tools
+
+#### MySQL
+
+- Install MySQL
+- Ensure the MySQL service starts when reboot or startup machine.
+
+```bash
+$ sudo systemctl start mysqld
+```
+
+- Setup MySQL for use
+
+```bash
+$ sudo mysql_secure_installation
+```
+
+- To check its installed and working just open up mysql command prompt with
+
+```
+$ sudo mysql
+```
+
+---
+
+#### Android Studio/SDK
+
+> NOTE: Android Studio is an Integrated Development Environment (IDE) that provides a comprehensive set of tools for Android app development. It includes the Android SDK (Software Development Kit), which consists of various libraries, tools, and system images necessary for developing Android applications.
+
+> The Android SDK can be installed separately without Android Studio, allowing you to use alternative text editors or IDEs for development. However, Android Studio provides a more streamlined and feature-rich development experience.
+
+> Make sure to properly set the Java environment (either 8 or 10, eg., java-8-openjdk) otherwise android-studio will not start.
+
+> If Android Studio shows up as a blank window try exporting `_JAVA_AWT_WM_NONREPARENTING=1`.
+
+- Install android studio
+ - Directly from the official website
+ ```bash
+ $ curl -L -o android-studio.tar.gz "$(curl -s "https://developer.android.com/studio#downloads" | grep -oP 'https://redirector\.gvt1\.com/[^"]+' | head -n 1)"
+ $ tar -xvzf android-studio.tar.gz
+ $ sudo mv android-studio /opt/
+ $ cd /opt/android-studio/bin script # Configure Android Studio by running this script
+ $ ./studio.sh
+ ```
+ - Or optionally install jetbrains-toolbox that includes android-studio amongst many other applications/tools from jetbrains
+ ```bash
+ $ latest_url=$(curl -sL "https://data.services.jetbrains.com/products/releases?code=TBA" | grep -oP 'https://download.jetbrains.com/toolbox/jetbrains-toolbox-\d+\.\d+\.\d+\.\d+\.tar\.gz' | head -n 1) && curl -L -o jetbrains-toolbox.tar.gz "$latest_url"
+ $ tar -xvzf jetbrains-toolbox.tar.gz
+ $ sudo mv jetbrains-toolbox /opt/jetbrains
+ ```
+- Complete the Android Studio Setup Wizard
+ - Click `Next` on the Welcome Window
+ - Click `Custom` and `Next`
+ - Make sure `/opt/android-sdk` directory exists otherwise create it by typing in the following command in a terminal
+ ```bash
+ $ sudo mkdir /opt/android-sdk
+ ```
+ - Click on the folder icon next to the SDK path field.
+ - In the file picker dialog, navigate to the /opt directory and select the android-sdk directory.
+ - Proceed with the setup wizard, following the remaining instructions to complete the installation.
+- If already installed and prefer not to have a `$HOME/Android` directory but rather use `/opt/android-sdk`
+
+ - Launch Android Studio.
+ - Go to "File" > "Settings" (on Windows/Linux) or "Android Studio" > "Preferences" (on macOS) to open the settings.
+ - In the settings, navigate to "Appearance & Behavior" > "System Settings" > "Android SDK".
+ - In the "Android SDK Location" field, update the path to `/opt/android-sdk`.
+ - Click "Apply" or "OK" to save the settings.
+
+- Put these lines into `.bashrc`/`.zshrc` or any similar shell configuration file to make it persistent across sessions
+
+```
+# Android Home
+export ANDROID_HOME=/opt/android-sdk
+export PATH=$ANDROID_HOME/tools:$PATH
+export PATH=$ANDROID_HOME/tools/bin:$PATH
+export PATH=$ANDROID_HOME/platform-tools:$PATH
+export PATH=$ANDROID_HOME/cmdline-tools/latest/bin:$PATH
+# Android emulator PATH
+export PATH=$ANDROID_HOME/emulator:$PATH
+# Android SDK ROOT PATH
+export ANDROID_SDK_ROOT=/opt/android-sdk
+export PATH=$ANDROID_SDK_ROOT:$PATH
+# Alias for android-studio
+alias android-studio='/opt/android-studio/bin/studio.sh'
+```
+
+- Android SDK and tools installation
+ > NOTE: Can be installed either through Android Studio or separately.
+ - Android Studio Installed: Launch Android Studio and go to the "SDK Manager" (usually found under "Configure" or "Preferences" menu). From the SDK Manager, select the desired SDK components (platforms, build tools, system images, etc.) and click "Apply" to install them.
+ - To install Android SDK separately (without Android Studio):
+ ```bash
+ $ curl -L -o commandlinetools.zip "$(curl -s "https://developer.android.com/studio#downloads" | grep -oP 'https://dl.google.com/android/repository/commandlinetools-linux-\d+_latest\.zip' | head -n 1)"
+ $ unzip commandlinetools.zip -d android-sdk
+ $ mkdir android-sdk/cmdline-tools/latest
+ $ sudo mv android-sdk /opt/
+ or
+ $ sudo mv android-sdk/cmdline-tools /opt/android-sdk/
+ ```
+- If Android SDK was installed separately then configure the user's permissions since android-sdk is installed in /opt/android-sdk directory
+
+```bash
+$ sudo groupadd android-sdk
+$ sudo gpasswd -a $USER android-sdk
+$ sudo setfacl -R -m g:android-sdk:rwx /opt/android-sdk
+$ sudo setfacl -d -m g:android-sdk:rwX /opt/android-sdk
+```
+
+- If Android SDK has been installed separately then install platform-tools and build-tools like this:
+ - First list `sdkmanager`'s available/installed packages
+ ```bash
+ $ sdkmanager --list
+ ```
+ - Install platform-tools and build-tools
+ > NOTE: Replace <version> with the specific version number for platforms and build tools to install (e.g., "platforms;android-`33`" "build-tools;`34.0.0`").
+ ```bash
+ $ sdkmanager "platform-tools" "platforms;android-<version>" "build-tools;<version>"
+ ```
+- Android emulator
+ - List of available android system images.
+ ```bash
+ $ sdkmanager --list
+ ```
+ - Install an android image of your choice. For example.
+ ```bash
+ $ sdkmanager --install "system-images;android-29;default;x86"
+ ```
+ - Then create an android emulator using Android Virtual Devices Manager
+ ```bash
+ $ avdmanager create avd -n <name> -k "system-images;android-29;default;x86"
+ ```
+- Continuing from **_Dart(flutter)_** section
+ - Update Flutter Config SDK PATH for custom SDK PATH
+ ```bash
+ $ flutter config --android-sdk /opt/android-sdk
+ ```
+ - Accept all andfoid licenses with this command
+ ```
+ $ flutter doctor --android-licenses
+ ```
+ - If licenses are still not accepted even after running `flutter doctor --android-licenses` try these commands and then run `flutter doctor --android-licenses again`
+ ```
+ $ sudo chown -R $(whoami) $ANDROID_SDK_ROOT
+ ```
+ - Run this
+ ```
+ $ flutter doctor
+ ```
+- Update emulator binaries
+
+```bash
+$ sdkmanager --sdk_root=${ANDROID_HOME} tools
+```
+
+- Accept emulator licenses
+ > NOTE: Required to accept the necessary license for each package installed.
+
+```bash
+$ sdkmanager --licenses
+```
+
+---
+
+## Commands
+
+---
+
+#### Windows
+
+- Install nvim natively to Windows
+ - First allow script execution, run the following command in PowerShell as an administrator:
+ ```dos
+ Set-ExecutionPolicy RemoteSigned
+ # or
+ Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
+ ```
+ - Then run the script by using this command in the same existing directory:
+ ```dos
+ ./win-nvim.ps1
+ ```
+ ```dos
+ curl -o winget-cli.appxbundle https://aka.ms/winget-cli-appxbundle
+ powershell Add-AppxPackage -Path "winget-cli.appxbundle"
+ Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
+ ```
+ - Use `-y` or consider: choco feature enable -n allowGlobalConfirmation
+ ```dos
+ choco install git
+ ```
+ - Refresh the environment
+ ```dos
+ Import-Module $env:ChocolateyInstall\helpers\chocolateyProfile.psm1
+ refreshenv
+ ```
+ ```dos
+ git config --global user.name "Firstname Lastname"
+ git config --global user.email "your_email@example.com"
+ ```
+ </details>
diff --git a/common/.bash_profile b/common/.bash_profile
new file mode 100644
index 0000000..f6c3ee4
--- /dev/null
+++ b/common/.bash_profile
@@ -0,0 +1,11 @@
+# ~/.bash_profile
+
+# Source ~/.profile if it exists (environment variables)
+if [ -f "$HOME/.profile" ]; then
+ . "$HOME/.profile"
+fi
+
+# Source ~/.bashrc for interactive settings (aliases, prompt, etc.)
+if [ -f "$HOME/.bashrc" ]; then
+ . "$HOME/.bashrc"
+fi
diff --git a/common/.bashrc b/common/.bashrc
new file mode 100644
index 0000000..fbc86fe
--- /dev/null
+++ b/common/.bashrc
@@ -0,0 +1,371 @@
+# shellcheck shell=bash
+#
+#██████╗ █████╗ ███████╗██╗ ██╗██████╗ ██████╗
+#██╔══██╗██╔══██╗██╔════╝██║ ██║██╔══██╗██╔════╝
+#██████╔╝███████║███████╗███████║██████╔╝██║
+#██╔══██╗██╔══██║╚════██║██╔══██║██╔══██╗██║
+#██████╔╝██║ ██║███████║██║ ██║██║ ██║╚██████╗
+#╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝
+#
+# ~/.bashrc
+#
+
+if [[ $- != *i* ]]; then
+ . ~/.profile
+ return
+fi
+
+# Get the current active terminal
+term="$(cat /proc/"$PPID"/comm)"
+
+# Set a default prompt
+p='\[\033[01;37m\]┌─[\[\033[01;32m\]srdusr\[\033[01;37m\]]-[\[\033[01;36m\]archlinux\[\033[01;37m\]]-[\[\033[01;33m\]\W\]\[\033[00;37m\]\[\033
+\[\033[01;37m\]└─[\[\033[05;33m\]$\[\033[00;37m\]\[\033[01;37m\]]\[\033[00;37m\] '
+
+# Set transparency and prompt while using st
+if [[ $term = "st" ]]; then
+ transset-df "0.65" --id "$WINDOWID" >/dev/null
+
+ # [Your_Name]-----| |=======|------[Your_Distro]
+ # [Color]--------| | [Color]------| |
+ # [Style]------------| | | [Style]---------| | |
+ # V V V V V V
+ p='\[\033[01;37m\]┌─[\[\033[01;32m\]srdusr\[\033[01;37m\]]-[\[\033[01;36m\]archlinux\[\033[01;37m\]]-[\[\033[01;33m\]\W\[\033[00;37m\]\[\033[01;37m\]]
+\[\033[01;37m\]└─[\[\033[05;33m\]$\[\033[00;37m\]\[\033[01;37m\]]\[\033[00;37m\] '
+# A A A
+# [Style]----| | |-------- [Your_Choice]
+# [Color]------------|
+
+fi
+
+# If not running interactively, dont do anything
+[[ $- != *i* ]] && return
+
+# My alias commands
+alias ls='ls --color=auto -1'
+alias shred='shred -uzvn3'
+alias wallset='feh --bg-fill'
+
+# Dotfiles Management System
+if [[ -d "$HOME/.cfg" && -d "$HOME/.cfg/refs" ]]; then
+ # Core git wrapper with repository as work-tree
+ _config() {
+ git --git-dir="$HOME/.cfg" --work-tree="$HOME/.cfg" "$@"
+ }
+
+ # Detect OS
+ case "$(uname -s)" in
+ Linux) CFG_OS="linux" ;;
+ Darwin) CFG_OS="macos" ;;
+ MINGW*|MSYS*|CYGWIN*) CFG_OS="windows" ;;
+ *) CFG_OS="other" ;;
+ esac
+
+ # Map system path to repository path
+ _repo_path() {
+ local f="$1"
+
+ # If it's an absolute path that's not in HOME, handle it specially
+ if [[ "$f" == /* && "$f" != "$HOME/"* ]]; then
+ echo "$CFG_OS/${f#/}"
+ return
+ fi
+
+ # Check for paths that should go to the repository root
+ case "$f" in
+ common/*|linux/*|macos/*|windows/*|profile/*|README.md)
+ echo "$f"
+ return
+ ;;
+ "$HOME/"*)
+ f="${f#$HOME/}"
+ ;;
+ esac
+
+ # Default: put under OS-specific home
+ echo "$CFG_OS/home/$f"
+ }
+
+ _sys_path() {
+ local repo_path="$1"
+ local os_path_pattern="$CFG_OS/"
+
+ # Handle OS-specific files that are not in the home subdirectory
+ if [[ "$repo_path" == "$os_path_pattern"* && "$repo_path" != */home/* ]]; then
+ echo "/${repo_path#$os_path_pattern}"
+ return
+ fi
+
+ case "$repo_path" in
+ # Common configs → OS-specific config dirs
+ common/config/*)
+ case "$CFG_OS" in
+ linux)
+ local base="${XDG_CONFIG_HOME:-$HOME/.config}"
+ echo "$base/${repo_path#common/config/}"
+ ;;
+ macos)
+ echo "$HOME/Library/Application Support/${repo_path#common/config/}"
+ ;;
+ windows)
+ echo "$LOCALAPPDATA\\${repo_path#common/config/}"
+ ;;
+ *)
+ echo "$HOME/.config/${repo_path#common/config/}"
+ ;;
+ esac
+ ;;
+
+ # Common assets → stay in repo
+ common/assets/*)
+ echo "$HOME/.cfg/$repo_path"
+ ;;
+
+ # Other common files (dotfiles like .bashrc, .gitconfig, etc.) → $HOME
+ common/*)
+ echo "$HOME/${repo_path#common/}"
+ ;;
+
+ # OS-specific home
+ */home/*)
+ echo "$HOME/${repo_path#*/home/}"
+ ;;
+
+ # Profile configs and README → stay in repo
+ profile/*|README.md)
+ echo "$HOME/.cfg/$repo_path"
+ ;;
+
+ # Default fallback
+ *)
+ echo "$HOME/.cfg/$repo_path"
+ ;;
+
+ esac
+ }
+
+ # Prompts for sudo if needed and runs the command
+ _sudo_prompt() {
+ if [[ $EUID -eq 0 ]]; then
+ "$@"
+ else
+ if command -v sudo >/dev/null; then
+ sudo "$@"
+ elif command -v doas >/dev/null; then
+ doas "$@"
+ elif command -v pkexec >/dev/null; then
+ pkexec "$@"
+ else
+ echo "Error: No privilege escalation tool found."
+ return 1
+ fi
+ fi
+ }
+
+ # Main config command
+ config() {
+ local cmd="$1"; shift
+ local target_dir=""
+ # Parse optional --target flag for add
+ if [[ "$cmd" == "add" ]]; then
+ while [[ "$1" == --* ]]; do
+ case "$1" in
+ --target|-t)
+ target_dir="$2"
+ shift 2
+ ;;
+ *)
+ echo "Unknown option: $1"
+ return 1
+ ;;
+ esac
+ done
+ fi
+
+ case "$cmd" in
+ add)
+ local file_path
+ for file_path in "$@"; do
+ local repo_path
+ if [[ -n "$target_dir" ]]; then
+ local rel_path
+ if [[ "$file_path" == /* ]]; then
+ rel_path="$(basename "$file_path")"
+ else
+ rel_path="$file_path"
+ fi
+ repo_path="$target_dir/$rel_path"
+ else
+ repo_path="$(_repo_path "$file_path")"
+ fi
+
+ local full_repo_path="$HOME/.cfg/$repo_path"
+ mkdir -p "$(dirname "$full_repo_path")"
+ cp -a "$file_path" "$full_repo_path"
+
+ git --git-dir="$HOME/.cfg" --work-tree="$HOME/.cfg" add "$repo_path"
+
+ echo "Added: $file_path -> $repo_path"
+ done
+ ;;
+ rm)
+ local rm_opts=""
+ local file_path_list=()
+
+ for arg in "$@"; do
+ if [[ "$arg" == "-"* ]]; then
+ rm_opts+=" $arg"
+ else
+ file_path_list+=("$arg")
+ fi
+ done
+
+ for file_path in "${file_path_list[@]}"; do
+ local repo_path="$(_repo_path "$file_path")"
+
+ if [[ "$rm_opts" == *"-r"* ]]; then
+ _config rm --cached -r "$repo_path"
+ else
+ _config rm --cached "$repo_path"
+ fi
+
+ eval "rm $rm_opts \"$file_path\""
+ echo "Removed: $file_path"
+ done
+ ;;
+ sync)
+ local direction="${1:-to-repo}"; shift
+ _config ls-files | while read -r repo_file; do
+ local sys_file="$(_sys_path "$repo_file")"
+ local full_repo_path="$HOME/.cfg/$repo_file"
+ if [[ "$direction" == "to-repo" ]]; then
+ if [[ -e "$sys_file" && -n "$(diff "$full_repo_path" "$sys_file" 2>/dev/null || echo "diff")" ]]; then
+ cp -a "$sys_file" "$full_repo_path"
+ echo "Synced to repo: $sys_file"
+ fi
+ elif [[ "$direction" == "from-repo" ]]; then
+ if [[ -e "$full_repo_path" && -n "$(diff "$full_repo_path" "$sys_file" 2>/dev/null || echo "diff")" ]]; then
+ local dest_dir="$(dirname "$sys_file")"
+ if [[ "$sys_file" == /* && "$sys_file" != "$HOME/"* ]]; then
+ _sudo_prompt mkdir -p "$dest_dir"
+ _sudo_prompt cp -a "$full_repo_path" "$sys_file"
+ else
+ mkdir -p "$dest_dir"
+ cp -a "$full_repo_path" "$sys_file"
+ fi
+ echo "Synced from repo: $sys_file"
+ fi
+ fi
+ done
+ ;;
+ status)
+ local auto_synced=()
+ while read -r repo_file; do
+ local sys_file="$(_sys_path "$repo_file")"
+ local full_repo_path="$HOME/.cfg/$repo_file"
+ if [[ -e "$sys_file" && -e "$full_repo_path" ]]; then
+ if ! diff -q "$full_repo_path" "$sys_file" >/dev/null 2>&1; then
+ cp -fa "$sys_file" "$full_repo_path"
+ auto_synced+=("$repo_file")
+ fi
+ fi
+ done < <(_config ls-files)
+ if [[ ${#auto_synced[@]} -gt 0 ]]; then
+ echo "=== Auto-synced Files ==="
+ for repo_file in "${auto_synced[@]}"; do
+ echo "synced: $(_sys_path "$repo_file") -> $repo_file"
+ done
+ echo
+ fi
+ _config status
+ echo
+ ;;
+ deploy)
+ _config ls-files | while read -r repo_file; do
+ local full_repo_path="$HOME/.cfg/$repo_file"
+ local sys_file="$(_sys_path "$repo_file")" # destination only
+
+ # Only continue if the source exists
+ if [[ -e "$full_repo_path" && -n "$sys_file" ]]; then
+ local dest_dir
+ dest_dir="$(dirname "$sys_file")"
+
+ # Create destination if needed
+ if [[ "$sys_file" == /* && "$sys_file" != "$HOME/"* ]]; then
+ _sudo_prompt mkdir -p "$dest_dir"
+ _sudo_prompt cp -a "$full_repo_path" "$sys_file"
+ else
+ mkdir -p "$dest_dir"
+ cp -a "$full_repo_path" "$sys_file"
+ fi
+
+ echo "Deployed: $repo_file -> $sys_file"
+ fi
+ done
+ ;;
+ checkout)
+ echo "Checking out dotfiles from .cfg..."
+ _config ls-files | while read -r repo_file; do
+ local full_repo_path="$HOME/.cfg/$repo_file"
+ local sys_file="$(_sys_path "$repo_file")"
+
+ if [[ -e "$full_repo_path" && -n "$sys_file" ]]; then
+ local dest_dir
+ dest_dir="$(dirname "$sys_file")"
+
+ # Create destination if it doesn't exist
+ if [[ "$sys_file" == /* && "$sys_file" != "$HOME/"* ]]; then
+ _sudo_prompt mkdir -p "$dest_dir"
+ _sudo_prompt cp -a "$full_repo_path" "$sys_file"
+ else
+ mkdir -p "$dest_dir"
+ cp -a "$full_repo_path" "$sys_file"
+ fi
+
+ echo "Checked out: $repo_file -> $sys_file"
+ fi
+ done
+ ;;
+ backup)
+ local timestamp=$(date +%Y%m%d%H%M%S)
+ local backup_dir="$HOME/.dotfiles_backup/$timestamp"
+ echo "Backing up existing dotfiles to $backup_dir..."
+
+ _config ls-files | while read -r repo_file; do
+ local sys_file="$(_sys_path "$repo_file")"
+ if [[ -e "$sys_file" ]]; then
+ local dest_dir_full="$backup_dir/$(dirname "$repo_file")"
+ mkdir -p "$dest_dir_full"
+ cp -a "$sys_file" "$backup_dir/$repo_file"
+ fi
+ done
+ echo "Backup complete. To restore, copy files from $backup_dir to their original locations."
+ ;;
+ *)
+ _config "$cmd" "$@"
+ ;;
+ esac
+ }
+fi
+
+PS1=$p
+
+bind -m vi-command 'Control-l: clear-screen'
+bind -m vi-insert 'Control-l: clear-screen'
+
+export EDITOR="nvim"
+
+#export NVM_DIR="$HOME/.local/share/nvm"
+#[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
+#[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion
+
+export PROMPT_COMMAND="resize &>/dev/null ; $PROMPT_COMMAND"
+
+# Rust environment (silent if not installed)
+export RUSTUP_HOME="${XDG_DATA_HOME:-$HOME/.local/share}/rustup"
+export CARGO_HOME="${XDG_DATA_HOME:-$HOME/.local/share}/cargo"
+export PATH="$CARGO_HOME/bin:$RUSTUP_HOME/bin:$PATH"
+
+if command -v rustc >/dev/null 2>&1; then
+ export RUST_BACKTRACE=1
+fi
diff --git a/common/.editorconfig b/common/.editorconfig
new file mode 100644
index 0000000..22f30d4
--- /dev/null
+++ b/common/.editorconfig
@@ -0,0 +1,80 @@
+
+root = true
+
+
+[*]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+indent_style = space
+indent_size = 2
+max_line_length = 10000
+
+# Makefile-specific settings
+[Makefile]
+indent_style = tab
+indent_size = 4
+
+# C/C++ source files
+[*.c]
+indent_style = tab
+indent_size = 4
+
+# TypeScript/JavaScript config
+[**.{ts,js}]
+indent_size = 2
+
+# Json config
+[**.json]
+indent_size = 2
+
+# Lua config
+[*.lua]
+indent_size = 2
+tab_width = 2
+# [none/single/double]
+quote_style = double
+# [line break]
+break_all_list_when_line_exceed = false
+auto_collapse_lines = false
+break_before_braces = false
+# [preference]
+ignore_space_after_colon = false
+remove_call_expression_list_finish_comma = false
+end_statement_with_semicolon = keep
+
+# Python config
+[*.py]
+indent_size = 4
+
+# Shell config
+[*.sh]
+indent_size = 4
+
+# Bash config
+[*.bash]
+indent_size = 4
+
+# Yaml config
+[*.yml]
+indent_size = 2
+
+# Latex config
+[**.tex]
+trim_trailing_whitespace = false
+
+# Markdown config
+[**.md]
+indent_size = 2
+trim_trailing_whitespace = false
+
+# Textfile config
+[**.txt]
+trim_trailing_whitespace = false
+insert_final_newline = false
+
+# Snippets config
+[**.snippets]
+indent_style = tab
+
diff --git a/common/.face b/common/.face
new file mode 100644
index 0000000..3aafd06
--- /dev/null
+++ b/common/.face
Binary files differ
diff --git a/common/.gitconfig b/common/.gitconfig
new file mode 100644
index 0000000..2c0d5ef
--- /dev/null
+++ b/common/.gitconfig
@@ -0,0 +1,34 @@
+[user]
+ name =
+ email =
+
+[init]
+ defaultBranch = main
+
+[color]
+ ui = true
+
+[alias]
+ graph = log --oneline --graph --decorate
+ ls = log --pretty=format:"%C(yellow)%h%Cred%d\\ %Creset%s%Cblue\\ [%cn]" --decorate
+ ll = log --pretty=format:"%C(yellow)%h%Cred%d\\ %Creset%s%Cblue\\ [%cn]" --decorate --numstat
+ lds = log --pretty=format:"%C(yellow)%h\\ %ad%Cred%d\\ %Creset%s%Cblue\\ [%cn]" --decorate --date=short
+ conflicts = diff --name-only --diff-filter=U
+ local-branches = !git branch -vv | cut -c 3- | awk '$3 !~/\\[/ { print $1 }'
+ recent-branches = !git branch --sort=-committerdate | head
+ authors = !git log --format='%aN <%aE>' | grep -v 'users.noreply.github.com' | sort -u --ignore-case
+ sba ="!f() { git subtree add --prefix $2 $1 main; }; f"
+ sbu ="!f() { git subtree pull --prefix $2 $1 main; }; f"
+ stashrebase = "!f() { if [ \"$(git symbolic-ref --short HEAD)\" = \"main\" ]; then git stash save && git fetch && git rebase origin main && git stash apply; else git stash save && git fetch && git rebase origin master && git stash apply; fi; }; f"
+ dotfiles = "!f() { git --git-dir=$HOME/.cfg --work-tree=$HOME stashrebase; }; f"
+
+
+[credential "https://github.com"]
+ helper =
+ helper = !sh -c 'command -v gh >/dev/null 2>&1 && exec gh auth git-credential || exit 0'
+
+[credential "https://gist.github.com"]
+ helper =
+ helper = !sh -c 'command -v gh >/dev/null 2>&1 && exec gh auth git-credential || exit 0'
+[credential]
+ helper = cache
diff --git a/common/.gitignore b/common/.gitignore
new file mode 100644
index 0000000..9c9bef7
--- /dev/null
+++ b/common/.gitignore
@@ -0,0 +1,44 @@
+# Ignore .git directory
+.git/
+
+# Ignore git config
+.gitconfig
+
+# Ignore Packer's compiled files
+packer_compiled.lua
+
+# Ignore Zsh plugins directory
+~/.config/zsh/plugins
+
+# Ignore zcompdump files
+zcompdump
+
+# Ignore .DS_Store files (macOS)
+*.DS_Store
+
+# Ignore .spl files
+*.spl
+
+# Ignore node_modules directory
+node_modules/
+
+# Ignore .zip files
+*.zip
+
+# Ignore .pxd files
+*.pxd
+
+# Ignore .cache directory
+^.cache/
+
+# Ignore normal directories
+^downloads/
+^music/
+^images/
+^pictures/
+^videos/
+^virt/
+
+# Ignore dotfiles dir
+~/.cfg
+.cfg
diff --git a/common/.gitmodules b/common/.gitmodules
new file mode 100644
index 0000000..37da2b9
--- /dev/null
+++ b/common/.gitmodules
@@ -0,0 +1,12 @@
+[submodule ".vim/pack/plugins/start/vim-tmux-navigator"]
+ path = .vim/pack/plugins/start/vim-tmux-navigator
+ url = https://github.com/christoomey/vim-tmux-navigator.git
+[submodule "zsh/plugins/zsh-you-should-use"]
+ path = .config/zsh/plugins/zsh-you-should-use
+ url = git@github.com:MichaelAquilina/zsh-you-should-use.git
+[submodule "zsh/plugins/zsh-syntax-highlighting"]
+ path = .config/zsh/plugins/zsh-syntax-highlighting
+ url = git@github.com:zsh-users/zsh-syntax-highlighting.git
+[submodule "zsh/plugins/zsh-syntax-highlighting"]
+ path = .config/zsh/plugins/zsh-autosuggestions
+ url = git@github.com:zsh-users/zsh-autosuggestions.git
diff --git a/common/.gitsubtrees b/common/.gitsubtrees
new file mode 100644
index 0000000..d8a7f29
--- /dev/null
+++ b/common/.gitsubtrees
@@ -0,0 +1,7 @@
+[subtree "common/config/nvim"]
+ path = common/config/nvim
+ url = git@github.com:srdusr/nvim.git
+
+[subtree "common/scripts"]
+ path = common/scripts
+ url = git@github.com:srdusr/scripts.git
diff --git a/common/.prettierrc.yml b/common/.prettierrc.yml
new file mode 100644
index 0000000..c5d13e2
--- /dev/null
+++ b/common/.prettierrc.yml
@@ -0,0 +1,5 @@
+semi: true
+singleQuote: true
+jsxSingleQuote: true
+trailingComma: all
+arrowParens: avoid
diff --git a/common/.profile b/common/.profile
new file mode 100644
index 0000000..c63f761
--- /dev/null
+++ b/common/.profile
@@ -0,0 +1,99 @@
+#!/bin/bash
+
+# ======================================
+# Basic environment setup
+# ======================================
+
+export EDITOR="$(command -v nvim || command -v vim || echo nano)"
+
+# Load zsh env if running zsh
+if [ -n "$ZSH_VERSION" ] && [ -f "$HOME/.config/zsh/.zshenv" ]; then
+ . "$HOME/.config/zsh/.zshenv"
+fi
+
+cd "$HOME" || exit 1
+
+# ======================================
+# Session launcher
+# ======================================
+
+# Detect graphical DE session
+if [ -n "$DISPLAY" ]; then
+ #echo "Graphical session detected ($XDG_SESSION_DESKTOP). Skipping auto TTY session launch."
+ return
+fi
+
+# Only run on first virtual terminal
+if [ -z "$XDG_VTNR" ] || [ "$XDG_VTNR" -ne 1 ]; then
+ return
+fi
+
+# Clean environment
+unset DISPLAY XAUTHORITY DBUS_SESSION_BUS_ADDRESS
+
+# Priority-ordered list of sessions (WM/DE)
+sessions=(
+ "Hyprland"
+ "bspwm"
+ "sway"
+ "gnome-session"
+ "startplasma-x11"
+ "startxfce4"
+ "openbox"
+ "i3"
+)
+
+# Handle saved session
+if [ -f "$HOME/.session" ]; then
+ chosen_session=$(<"$HOME/.session")
+ rm -f "$HOME/.session"
+fi
+
+# Start a session
+start_session() {
+ local s="$1"
+ case "$s" in
+ bspwm)
+ export XDG_SESSION_TYPE="x11"
+ exec startx /usr/bin/bspwm
+ ;;
+ Hyprland|sway)
+ exec dbus-launch --sh-syntax --exit-with-session "$s"
+ ;;
+ gnome-session|startplasma-x11|startxfce4|openbox|i3)
+ exec "$s"
+ ;;
+ *)
+ return 1
+ ;;
+ esac
+}
+
+# Try saved session first
+if [ -n "$chosen_session" ]; then
+ if start_session "$chosen_session"; then
+ exit
+ else
+ echo "Saved session '$chosen_session' not found. Falling back..."
+ fi
+fi
+
+# Try default sessions in priority
+for wm in "${sessions[@]}"; do
+ if command -v "$wm" >/dev/null 2>&1; then
+ echo "Starting session: $wm"
+ start_session "$wm"
+ exit
+ fi
+done
+
+# Fallback: Check for common display managers (GDM/LightDM/SDDM)
+for dm in gdm lightdm sddm; do
+ if command -v "$dm" >/dev/null 2>&1; then
+ echo "Launching display manager: $dm"
+ exec "$dm"
+ fi
+done
+
+echo "No suitable window manager or display manager found."
+exit 1
diff --git a/common/.zprofile b/common/.zprofile
new file mode 100644
index 0000000..dafce71
--- /dev/null
+++ b/common/.zprofile
@@ -0,0 +1,3 @@
+emulate sh -c '. ~/.profile'
+#[[ -f ~/.config/zsh/.zshenv ]] && . ~/.config/zsh/.zshenv
+#[[ -f ~/.profile ]] && . ~/.profile
diff --git a/common/.zshrc b/common/.zshrc
new file mode 100644
index 0000000..bd22e32
--- /dev/null
+++ b/common/.zshrc
@@ -0,0 +1,10 @@
+# ~/.zshrc
+[[ -f ~/.config/zsh/.zshrc ]] && source ~/.config/zsh/.zshrc
+
+# Point all zsh startup files to ~/.config/zsh
+export ZDOTDIR="$HOME/.config/zsh"
+
+# If you want, you can still source your real zshenv from there:
+if [[ -f "$ZDOTDIR/.zshenv" ]]; then
+ source "$ZDOTDIR/.zshenv"
+fi
diff --git a/common/assets/desktop.jpg b/common/assets/desktop.jpg
new file mode 100644
index 0000000..e85b2ff
--- /dev/null
+++ b/common/assets/desktop.jpg
Binary files differ
diff --git a/common/assets/old_desktop.jpg b/common/assets/old_desktop.jpg
new file mode 100644
index 0000000..3ecb22e
--- /dev/null
+++ b/common/assets/old_desktop.jpg
Binary files differ
diff --git a/common/config/alacritty/alacritty.yml b/common/config/alacritty/alacritty.yml
new file mode 100644
index 0000000..21e70c7
--- /dev/null
+++ b/common/config/alacritty/alacritty.yml
@@ -0,0 +1,106 @@
+# ~/.config/alacritty/alacritty.yml
+
+
+live_config_reload: true
+
+window:
+ opacity: 0.6
+ dynamic_title: true
+ dimensions:
+ columns: 2
+ lines: 2
+
+
+ # startup_mode: Maximized
+ # position:
+ # x: 0
+ # y: 0
+
+ padding:
+ x: 9
+ y: 9
+
+ dynamic_padding: false
+ decorations: none
+
+scrolling:
+ history: 50000
+ multiplier: 3
+
+
+
+font:
+ normal:
+ family: JetBrains Mono Medium
+ #family: Fira Mono Regular
+ #family: UbuntuMono Nerd Font Regular
+ #family: monospace
+ size: 8.5
+ offset:
+ x: 0
+ y: 0
+ glyph_offset:
+ x: 0
+ y: 0
+ builtin_box_drawing: true
+ #size: 8.5
+
+ # Glyph offset determines the locations of the glyphs within their cells with
+ # the default being at the bottom. Increase the x offset to move the glyph to
+ # the right, increase the y offset to move the glyph upward.
+
+key_bindings:
+- { key: V, mods: Control, action: Paste }
+- { key: C, mods: Control, action: Copy }
+- { key: C, mods: Control|Shift, chars: "\x03" }
+- { key: N, mods: Control|Shift, action: SpawnNewInstance }
+- { key: O, mods: Control|Shift, command: { program: "opacity-change.sh", args: ["-"] } }
+- { key: P, mods: Control|Shift, command: { program: "opacity-change.sh", args: ["+"] } }
+save_to_clipboard: true
+
+colors:
+ primary:
+ background: '#000000'
+ foreground: '#FFFACD'
+ normal:
+ black: '#313539'
+ red: '#b02626'
+ green: '#40a62f'
+ yellow: '#f2e635'
+ blue: '#314ad0'
+ magenta: '#b30ad0'
+ cyan: '#32d0fc'
+ white: '#acadb1'
+ bright:
+ black: '#676f78'
+ red: '#b55454'
+ green: '#78a670'
+ yellow: '#faf380'
+ blue: '#707fd0'
+ magenta: '#c583d0'
+ cyan: '#8adaf1'
+ white: '#e0e3e7'
+
+
+#colors:
+# primary:
+# background: '#0F111A'
+# foreground: '#8F93A2'
+# normal:
+# black: '#0F111A'
+# red: '#FF5370'
+# green: '#99C794'
+# yellow: '#C4E88D'
+# blue: '#82AAFF'
+# magenta: '#C792EA'
+# cyan: '#89DDFF'
+# white: '#464B5D'
+# bright:
+# black: '#0F111A'
+# red: '#FF5370'
+# green: '#99C794'
+# yellow: '#C4E88D'
+# blue: '#82AAFF'
+# magenta: '#C792EA'
+ # cyan: '#89DDFF'
+ # white: '#8F93A2'
diff --git a/.gitignore b/common/config/nvim/.gitignore
index f6f2fa2..f6f2fa2 100755
--- a/.gitignore
+++ b/common/config/nvim/.gitignore
diff --git a/.luacheckrc b/common/config/nvim/.luacheckrc
index 26f9f67..26f9f67 100755
--- a/.luacheckrc
+++ b/common/config/nvim/.luacheckrc
diff --git a/after/ftplugin/c.lua b/common/config/nvim/after/ftplugin/c.lua
index 6af8a5c..6af8a5c 100755
--- a/after/ftplugin/c.lua
+++ b/common/config/nvim/after/ftplugin/c.lua
diff --git a/after/ftplugin/lua.lua.bak b/common/config/nvim/after/ftplugin/lua.lua.bak
index fe9587b..fe9587b 100755
--- a/after/ftplugin/lua.lua.bak
+++ b/common/config/nvim/after/ftplugin/lua.lua.bak
diff --git a/after/ftplugin/markdown.lua b/common/config/nvim/after/ftplugin/markdown.lua
index 5941402..5941402 100755
--- a/after/ftplugin/markdown.lua
+++ b/common/config/nvim/after/ftplugin/markdown.lua
diff --git a/after/ftplugin/vim.lua b/common/config/nvim/after/ftplugin/vim.lua
index 7823f73..7823f73 100755
--- a/after/ftplugin/vim.lua
+++ b/common/config/nvim/after/ftplugin/vim.lua
diff --git a/autoload/statusline.vim b/common/config/nvim/autoload/statusline.vim
index bf5f972..bf5f972 100755
--- a/autoload/statusline.vim
+++ b/common/config/nvim/autoload/statusline.vim
diff --git a/autoload/utils.vim b/common/config/nvim/autoload/utils.vim
index d92b771..d92b771 100755
--- a/autoload/utils.vim
+++ b/common/config/nvim/autoload/utils.vim
diff --git a/colors/colorscheme.vim b/common/config/nvim/colors/colorscheme.vim
index ce0526e..ce0526e 100755
--- a/colors/colorscheme.vim
+++ b/common/config/nvim/colors/colorscheme.vim
diff --git a/init.lua b/common/config/nvim/init.lua
index 75ca825..75ca825 100755
--- a/init.lua
+++ b/common/config/nvim/init.lua
diff --git a/lsp/bashls.lua b/common/config/nvim/lsp/bashls.lua
index fc7d709..fc7d709 100644
--- a/lsp/bashls.lua
+++ b/common/config/nvim/lsp/bashls.lua
diff --git a/lsp/clangd.lua b/common/config/nvim/lsp/clangd.lua
index 4a19600..4a19600 100644
--- a/lsp/clangd.lua
+++ b/common/config/nvim/lsp/clangd.lua
diff --git a/lsp/cssls.lua b/common/config/nvim/lsp/cssls.lua
index e734c19..e734c19 100644
--- a/lsp/cssls.lua
+++ b/common/config/nvim/lsp/cssls.lua
diff --git a/lsp/gopls.lua b/common/config/nvim/lsp/gopls.lua
index cf959c4..cf959c4 100644
--- a/lsp/gopls.lua
+++ b/common/config/nvim/lsp/gopls.lua
diff --git a/lsp/html.lua b/common/config/nvim/lsp/html.lua
index 5b322b1..5b322b1 100644
--- a/lsp/html.lua
+++ b/common/config/nvim/lsp/html.lua
diff --git a/lsp/jsonls.lua b/common/config/nvim/lsp/jsonls.lua
index 6474e0a..6474e0a 100644
--- a/lsp/jsonls.lua
+++ b/common/config/nvim/lsp/jsonls.lua
diff --git a/lsp/lua_ls.lua b/common/config/nvim/lsp/lua_ls.lua
index d248e2e..d248e2e 100644
--- a/lsp/lua_ls.lua
+++ b/common/config/nvim/lsp/lua_ls.lua
diff --git a/lsp/pyright.lua b/common/config/nvim/lsp/pyright.lua
index f89d41f..f89d41f 100644
--- a/lsp/pyright.lua
+++ b/common/config/nvim/lsp/pyright.lua
diff --git a/lsp/rust_analyzer.lua b/common/config/nvim/lsp/rust_analyzer.lua
index b4522f5..b4522f5 100644
--- a/lsp/rust_analyzer.lua
+++ b/common/config/nvim/lsp/rust_analyzer.lua
diff --git a/lsp/ts_ls.lua b/common/config/nvim/lsp/ts_ls.lua
index 940fd3d..940fd3d 100644
--- a/lsp/ts_ls.lua
+++ b/common/config/nvim/lsp/ts_ls.lua
diff --git a/lsp/yamlls.lua b/common/config/nvim/lsp/yamlls.lua
index e52d322..e52d322 100644
--- a/lsp/yamlls.lua
+++ b/common/config/nvim/lsp/yamlls.lua
diff --git a/lua/plugins/auto-session.lua b/common/config/nvim/lua/plugins/auto-session.lua
index d982e08..d982e08 100755
--- a/lua/plugins/auto-session.lua
+++ b/common/config/nvim/lua/plugins/auto-session.lua
diff --git a/lua/plugins/autopairs.lua b/common/config/nvim/lua/plugins/autopairs.lua
index 22dcf27..22dcf27 100755
--- a/lua/plugins/autopairs.lua
+++ b/common/config/nvim/lua/plugins/autopairs.lua
diff --git a/lua/plugins/cmp-gh-source.lua b/common/config/nvim/lua/plugins/cmp-gh-source.lua
index 4990c35..4990c35 100755
--- a/lua/plugins/cmp-gh-source.lua
+++ b/common/config/nvim/lua/plugins/cmp-gh-source.lua
diff --git a/lua/plugins/cmp.lua b/common/config/nvim/lua/plugins/cmp.lua
index 7de04ad..7de04ad 100755
--- a/lua/plugins/cmp.lua
+++ b/common/config/nvim/lua/plugins/cmp.lua
diff --git a/lua/plugins/colorizer.lua b/common/config/nvim/lua/plugins/colorizer.lua
index 6019bc5..6019bc5 100755
--- a/lua/plugins/colorizer.lua
+++ b/common/config/nvim/lua/plugins/colorizer.lua
diff --git a/lua/plugins/colorscheme.lua b/common/config/nvim/lua/plugins/colorscheme.lua
index 7fbabc1..7fbabc1 100755
--- a/lua/plugins/colorscheme.lua
+++ b/common/config/nvim/lua/plugins/colorscheme.lua
diff --git a/lua/plugins/comment.lua b/common/config/nvim/lua/plugins/comment.lua
index 392b279..392b279 100755
--- a/lua/plugins/comment.lua
+++ b/common/config/nvim/lua/plugins/comment.lua
diff --git a/lua/plugins/dap.lua b/common/config/nvim/lua/plugins/dap.lua
index 7de032c..7de032c 100755
--- a/lua/plugins/dap.lua
+++ b/common/config/nvim/lua/plugins/dap.lua
diff --git a/lua/plugins/dashboard.lua b/common/config/nvim/lua/plugins/dashboard.lua
index 43a3461..43a3461 100755
--- a/lua/plugins/dashboard.lua
+++ b/common/config/nvim/lua/plugins/dashboard.lua
diff --git a/lua/plugins/fidget.lua b/common/config/nvim/lua/plugins/fidget.lua
index d401c5f..d401c5f 100755
--- a/lua/plugins/fidget.lua
+++ b/common/config/nvim/lua/plugins/fidget.lua
diff --git a/lua/plugins/friendly-snippets.lua b/common/config/nvim/lua/plugins/friendly-snippets.lua
index 2a7695e..2a7695e 100755
--- a/lua/plugins/friendly-snippets.lua
+++ b/common/config/nvim/lua/plugins/friendly-snippets.lua
diff --git a/lua/plugins/fugitive.lua b/common/config/nvim/lua/plugins/fugitive.lua
index 22620e3..22620e3 100755
--- a/lua/plugins/fugitive.lua
+++ b/common/config/nvim/lua/plugins/fugitive.lua
diff --git a/lua/plugins/fzf.lua b/common/config/nvim/lua/plugins/fzf.lua
index 9e62c48..9e62c48 100755
--- a/lua/plugins/fzf.lua
+++ b/common/config/nvim/lua/plugins/fzf.lua
diff --git a/lua/plugins/git.lua b/common/config/nvim/lua/plugins/git.lua
index 24a0871..24a0871 100755
--- a/lua/plugins/git.lua
+++ b/common/config/nvim/lua/plugins/git.lua
diff --git a/lua/plugins/gitsigns.lua b/common/config/nvim/lua/plugins/gitsigns.lua
index 7bbe637..7bbe637 100755
--- a/lua/plugins/gitsigns.lua
+++ b/common/config/nvim/lua/plugins/gitsigns.lua
diff --git a/lua/plugins/goto-preview.lua b/common/config/nvim/lua/plugins/goto-preview.lua
index eb54a8c..eb54a8c 100755
--- a/lua/plugins/goto-preview.lua
+++ b/common/config/nvim/lua/plugins/goto-preview.lua
diff --git a/lua/plugins/hardtime.lua b/common/config/nvim/lua/plugins/hardtime.lua
index b440334..b440334 100755
--- a/lua/plugins/hardtime.lua
+++ b/common/config/nvim/lua/plugins/hardtime.lua
diff --git a/lua/plugins/harpoon.lua b/common/config/nvim/lua/plugins/harpoon.lua
index 8e842b3..8e842b3 100755
--- a/lua/plugins/harpoon.lua
+++ b/common/config/nvim/lua/plugins/harpoon.lua
diff --git a/lua/plugins/heirline.lua b/common/config/nvim/lua/plugins/heirline.lua
index a4c2fc3..a4c2fc3 100755
--- a/lua/plugins/heirline.lua
+++ b/common/config/nvim/lua/plugins/heirline.lua
diff --git a/lua/plugins/indent-blankline.lua b/common/config/nvim/lua/plugins/indent-blankline.lua
index cbbcf27..cbbcf27 100755
--- a/lua/plugins/indent-blankline.lua
+++ b/common/config/nvim/lua/plugins/indent-blankline.lua
diff --git a/lua/plugins/interestingwords.lua b/common/config/nvim/lua/plugins/interestingwords.lua
index 655ed42..655ed42 100755
--- a/lua/plugins/interestingwords.lua
+++ b/common/config/nvim/lua/plugins/interestingwords.lua
diff --git a/lua/plugins/leetcode.lua b/common/config/nvim/lua/plugins/leetcode.lua
index 50369e1..50369e1 100755
--- a/lua/plugins/leetcode.lua
+++ b/common/config/nvim/lua/plugins/leetcode.lua
diff --git a/lua/plugins/loclist.lua b/common/config/nvim/lua/plugins/loclist.lua
index 9b72a94..9b72a94 100755
--- a/lua/plugins/loclist.lua
+++ b/common/config/nvim/lua/plugins/loclist.lua
diff --git a/lua/plugins/lsp.lua b/common/config/nvim/lua/plugins/lsp.lua
index 5ed1152..5ed1152 100755
--- a/lua/plugins/lsp.lua
+++ b/common/config/nvim/lua/plugins/lsp.lua
diff --git a/lua/plugins/lualine.lua b/common/config/nvim/lua/plugins/lualine.lua
index 9c1cc43..9c1cc43 100755
--- a/lua/plugins/lualine.lua
+++ b/common/config/nvim/lua/plugins/lualine.lua
diff --git a/lua/plugins/luasnip.lua b/common/config/nvim/lua/plugins/luasnip.lua
index 75f4c28..75f4c28 100755
--- a/lua/plugins/luasnip.lua
+++ b/common/config/nvim/lua/plugins/luasnip.lua
diff --git a/lua/plugins/messages.lua b/common/config/nvim/lua/plugins/messages.lua
index 8e46c09..8e46c09 100755
--- a/lua/plugins/messages.lua
+++ b/common/config/nvim/lua/plugins/messages.lua
diff --git a/lua/plugins/modify-blend.lua b/common/config/nvim/lua/plugins/modify-blend.lua
index 1b2c6d5..1b2c6d5 100755
--- a/lua/plugins/modify-blend.lua
+++ b/common/config/nvim/lua/plugins/modify-blend.lua
diff --git a/lua/plugins/navic.lua b/common/config/nvim/lua/plugins/navic.lua
index a574d5c..a574d5c 100755
--- a/lua/plugins/navic.lua
+++ b/common/config/nvim/lua/plugins/navic.lua
diff --git a/lua/plugins/neodev.lua b/common/config/nvim/lua/plugins/neodev.lua
index 07843e1..07843e1 100755
--- a/lua/plugins/neodev.lua
+++ b/common/config/nvim/lua/plugins/neodev.lua
diff --git a/lua/plugins/neoscroll.lua b/common/config/nvim/lua/plugins/neoscroll.lua
index f2ecb04..f2ecb04 100755
--- a/lua/plugins/neoscroll.lua
+++ b/common/config/nvim/lua/plugins/neoscroll.lua
diff --git a/lua/plugins/neotest.lua b/common/config/nvim/lua/plugins/neotest.lua
index 1034d33..1034d33 100755
--- a/lua/plugins/neotest.lua
+++ b/common/config/nvim/lua/plugins/neotest.lua
diff --git a/lua/plugins/notify.lua b/common/config/nvim/lua/plugins/notify.lua
index 62a8f47..62a8f47 100755
--- a/lua/plugins/notify.lua
+++ b/common/config/nvim/lua/plugins/notify.lua
diff --git a/lua/plugins/nvim-tree.lua b/common/config/nvim/lua/plugins/nvim-tree.lua
index a212eab..a212eab 100755
--- a/lua/plugins/nvim-tree.lua
+++ b/common/config/nvim/lua/plugins/nvim-tree.lua
diff --git a/lua/plugins/overseer.lua b/common/config/nvim/lua/plugins/overseer.lua
index 593d094..593d094 100755
--- a/lua/plugins/overseer.lua
+++ b/common/config/nvim/lua/plugins/overseer.lua
diff --git a/lua/plugins/plenary.lua b/common/config/nvim/lua/plugins/plenary.lua
index f572244..f572244 100755
--- a/lua/plugins/plenary.lua
+++ b/common/config/nvim/lua/plugins/plenary.lua
diff --git a/lua/plugins/prettier.lua b/common/config/nvim/lua/plugins/prettier.lua
index ca57ea9..ca57ea9 100755
--- a/lua/plugins/prettier.lua
+++ b/common/config/nvim/lua/plugins/prettier.lua
diff --git a/lua/plugins/quickfix.lua b/common/config/nvim/lua/plugins/quickfix.lua
index 4a76da0..4a76da0 100755
--- a/lua/plugins/quickfix.lua
+++ b/common/config/nvim/lua/plugins/quickfix.lua
diff --git a/lua/plugins/snippets.lua b/common/config/nvim/lua/plugins/snippets.lua
index 989ad8a..989ad8a 100755
--- a/lua/plugins/snippets.lua
+++ b/common/config/nvim/lua/plugins/snippets.lua
diff --git a/lua/plugins/sniprun.lua b/common/config/nvim/lua/plugins/sniprun.lua
index 418e8cc..418e8cc 100755
--- a/lua/plugins/sniprun.lua
+++ b/common/config/nvim/lua/plugins/sniprun.lua
diff --git a/lua/plugins/statuscol.lua b/common/config/nvim/lua/plugins/statuscol.lua
index c538790..c538790 100755
--- a/lua/plugins/statuscol.lua
+++ b/common/config/nvim/lua/plugins/statuscol.lua
diff --git a/lua/plugins/surround.lua b/common/config/nvim/lua/plugins/surround.lua
index 71023c7..71023c7 100755
--- a/lua/plugins/surround.lua
+++ b/common/config/nvim/lua/plugins/surround.lua
diff --git a/lua/plugins/telescope.lua b/common/config/nvim/lua/plugins/telescope.lua
index 5aca8ac..5aca8ac 100755
--- a/lua/plugins/telescope.lua
+++ b/common/config/nvim/lua/plugins/telescope.lua
diff --git a/lua/plugins/toggleterm.lua b/common/config/nvim/lua/plugins/toggleterm.lua
index 6b7aad5..6b7aad5 100755
--- a/lua/plugins/toggleterm.lua
+++ b/common/config/nvim/lua/plugins/toggleterm.lua
diff --git a/lua/plugins/treesitter.lua b/common/config/nvim/lua/plugins/treesitter.lua
index 9df99b8..9df99b8 100755
--- a/lua/plugins/treesitter.lua
+++ b/common/config/nvim/lua/plugins/treesitter.lua
diff --git a/lua/plugins/trouble.lua b/common/config/nvim/lua/plugins/trouble.lua
index 4a07e3b..4a07e3b 100755
--- a/lua/plugins/trouble.lua
+++ b/common/config/nvim/lua/plugins/trouble.lua
diff --git a/lua/plugins/vimtex.lua b/common/config/nvim/lua/plugins/vimtex.lua
index 732e6ed..732e6ed 100755
--- a/lua/plugins/vimtex.lua
+++ b/common/config/nvim/lua/plugins/vimtex.lua
diff --git a/lua/plugins/web-devicons.lua b/common/config/nvim/lua/plugins/web-devicons.lua
index a565a31..a565a31 100755
--- a/lua/plugins/web-devicons.lua
+++ b/common/config/nvim/lua/plugins/web-devicons.lua
diff --git a/lua/plugins/which-key.lua b/common/config/nvim/lua/plugins/which-key.lua
index 10015aa..10015aa 100755
--- a/lua/plugins/which-key.lua
+++ b/common/config/nvim/lua/plugins/which-key.lua
diff --git a/lua/plugins/zen-mode.lua b/common/config/nvim/lua/plugins/zen-mode.lua
index 7e52854..7e52854 100755
--- a/lua/plugins/zen-mode.lua
+++ b/common/config/nvim/lua/plugins/zen-mode.lua
diff --git a/lua/setup/compat.lua b/common/config/nvim/lua/setup/compat.lua
index ef90444..ef90444 100755
--- a/lua/setup/compat.lua
+++ b/common/config/nvim/lua/setup/compat.lua
diff --git a/lua/setup/manager.lua b/common/config/nvim/lua/setup/manager.lua
index 9cf1d14..9cf1d14 100755
--- a/lua/setup/manager.lua
+++ b/common/config/nvim/lua/setup/manager.lua
diff --git a/lua/setup/plugins.lua b/common/config/nvim/lua/setup/plugins.lua
index 0fb0886..0fb0886 100755
--- a/lua/setup/plugins.lua
+++ b/common/config/nvim/lua/setup/plugins.lua
diff --git a/lua/user/keys.lua b/common/config/nvim/lua/user/keys.lua
index 63b64fa..63b64fa 100755
--- a/lua/user/keys.lua
+++ b/common/config/nvim/lua/user/keys.lua
diff --git a/lua/user/mods.lua b/common/config/nvim/lua/user/mods.lua
index b4e1579..b4e1579 100755
--- a/lua/user/mods.lua
+++ b/common/config/nvim/lua/user/mods.lua
diff --git a/lua/user/opts.lua b/common/config/nvim/lua/user/opts.lua
index bac80c3..bac80c3 100755
--- a/lua/user/opts.lua
+++ b/common/config/nvim/lua/user/opts.lua
diff --git a/lua/user/view.lua b/common/config/nvim/lua/user/view.lua
index f243194..f243194 100755
--- a/lua/user/view.lua
+++ b/common/config/nvim/lua/user/view.lua
diff --git a/neovim.ps1 b/common/config/nvim/neovim.ps1
index a63965e..a63965e 100755
--- a/neovim.ps1
+++ b/common/config/nvim/neovim.ps1
diff --git a/neovim.sh b/common/config/nvim/neovim.sh
index 842abed..842abed 100755
--- a/neovim.sh
+++ b/common/config/nvim/neovim.sh
diff --git a/snippets/boilerplate.lua b/common/config/nvim/snippets/boilerplate.lua
index 04e973a..04e973a 100644
--- a/snippets/boilerplate.lua
+++ b/common/config/nvim/snippets/boilerplate.lua
diff --git a/snippets/lua.lua b/common/config/nvim/snippets/lua.lua
index eb46b67..eb46b67 100644
--- a/snippets/lua.lua
+++ b/common/config/nvim/snippets/lua.lua
diff --git a/snippets/markdown.lua b/common/config/nvim/snippets/markdown.lua
index d0d1487..d0d1487 100644
--- a/snippets/markdown.lua
+++ b/common/config/nvim/snippets/markdown.lua
diff --git a/common/config/wezterm/wezterm.lua b/common/config/wezterm/wezterm.lua
new file mode 100644
index 0000000..4f4bd07
--- /dev/null
+++ b/common/config/wezterm/wezterm.lua
@@ -0,0 +1,206 @@
+local wezterm = require("wezterm")
+
+---- Function to unset Ctrl+C keybinding
+--local function unsetCtrlCKeybinding(window)
+-- local keys = window:get_config().keys
+-- for i, key in ipairs(keys) do
+-- if key.key == 'c' and key.mods == 'CTRL' then
+-- table.remove(keys, i)
+-- break
+-- end
+-- end
+-- window:set_config({ keys = keys })
+--end
+--
+---- Event handler to unset Ctrl+C keybinding when using nvim
+--wezterm.on("spawn_command", function(window, pane)
+-- local cmd = pane:get_command()
+-- if cmd and cmd[1] == "nvim" then
+-- unsetCtrlCKeybinding(window)
+-- end
+--end)
+
+--local function isNvimRunning(window)
+-- local pane = window.active_pane
+-- local cmd = pane:get_command()
+-- return cmd and cmd[1] == "nvim"
+--end
+--
+---- Function to modify keybindings based on the current program
+--local function updateKeybindings(window)
+-- local isNvim = isNvimRunning(window)
+--
+-- local keys = {}
+-- if not isNvim then
+-- -- Add the default Ctrl+C keybinding
+-- keys = {
+-- {
+-- key = "c",
+-- mods = "CTRL",
+-- action = wezterm.action{CopyTo = "ClipboardAndPrimarySelection"}
+-- }
+-- }
+-- end
+--
+-- window:set_config({
+-- keys = keys
+-- })
+--end
+--
+---- Event handler to update keybindings when the active program changes
+--wezterm.on("update-right-status", function(window)
+-- updateKeybindings(window)
+--end)
+
+wezterm.on("toggle-opacity", function(window)
+ local overrides = window:get_config_overrides() or {}
+ if not overrides.window_background_opacity then
+ overrides.window_background_opacity = 1.0
+ elseif overrides.window_background_opacity == 1.0 then
+ overrides.window_background_opacity = 0.6
+ else
+ overrides.window_background_opacity = nil
+ end
+ window:set_config_overrides(overrides)
+end)
+
+return {
+ front_end = "OpenGL",
+ --font = wezterm.font 'JetBrains Mono',
+ font = wezterm.font_with_fallback({
+ {
+ family = "JetBrains Mono",
+ --intensity = 'Normal',
+ weight = "Medium",
+ italic = false,
+ harfbuzz_features = { "calt=0", "clig=0", "liga=0" },
+ },
+ { family = "Hack Nerd Font", weight = "Medium" },
+ {
+ family = "Fira Code",
+ harfbuzz_features = { "zero" },
+ },
+ { family = "Terminus", weight = "Bold" },
+ "Noto Color Emoji",
+ }),
+ font_size = 9,
+ warn_about_missing_glyphs = false,
+ adjust_window_size_when_changing_font_size = false,
+ line_height = 1.0,
+ --dpi = 96.0,
+ -- Keybinds
+ disable_default_key_bindings = true,
+ use_dead_keys = false,
+ mouse_bindings = {
+ -- Ctrl-click will open the link under the mouse cursor
+ {
+ event = { Up = { streak = 1, button = "Left" } },
+ mods = "CTRL",
+ action = wezterm.action.OpenLinkAtMouseCursor,
+ },
+ },
+ keys = {
+ --leader = { key = 'a', mods = 'CTRL', timeout_milliseconds = 1000 },
+ {
+ key = "O",
+ mods = "CTRL|SHIFT",
+ action = wezterm.action({ EmitEvent = "toggle-opacity" }),
+ },
+ { key = "R", mods = "CTRL", action = "ReloadConfiguration" },
+ { key = "Y", mods = "CTRL", action = "ShowDebugOverlay" },
+ {
+ key = "-",
+ mods = "CTRL",
+ action = wezterm.action.DecreaseFontSize,
+ },
+ {
+ key = "=",
+ mods = "CTRL",
+ action = wezterm.action.IncreaseFontSize,
+ },
+ {
+ key = "0",
+ mods = "CTRL",
+ action = wezterm.action.ResetFontSize,
+ },
+ {
+ key = "v",
+ mods = "CTRL",
+ action = wezterm.action({ PasteFrom = "Clipboard" }),
+ },
+ --{
+ -- key = "c",
+ -- mods = "CTRL",
+ -- action = wezterm.action({ CopyTo = "ClipboardAndPrimarySelection" }),
+ --},
+ {
+ key = "c",
+ mods = "CTRL",
+ action = wezterm.action_callback(function(window, pane)
+ local has_selection = window:get_selection_text_for_pane(pane) ~= ""
+ if has_selection then
+ window:perform_action(wezterm.action({ CopyTo = "ClipboardAndPrimarySelection" }), pane)
+ window:perform_action("ClearSelection", pane)
+ else
+ window:perform_action(wezterm.action({ SendKey = { key = "c", mods = "CTRL" } }), pane)
+ end
+ end),
+ },
+ },
+ -- Aesthetic Night Colorscheme
+ bold_brightens_ansi_colors = true,
+ -- Padding
+ window_padding = {
+ left = 5,
+ right = 5,
+ top = 6,
+ bottom = 4,
+ },
+ -- Cursor style
+ --default_cursor_style = "BlinkingUnderline",
+ default_cursor_style = "BlinkingBar",
+ cursor_blink_rate = 700,
+ -- needed to prevent 'easing' from using 40%+ cpu util ...
+ --animation_fps = 1,
+ force_reverse_video_cursor = true,
+ colors = {
+ cursor_bg = "white",
+ compose_cursor = "orange",
+ --cursor_border = 'white',
+ },
+
+ -- Tab Bar
+ enable_tab_bar = false,
+ --hide_tab_bar_if_only_one_tab = true,
+ --show_tab_index_in_tab_bar = false,
+ tab_bar_at_bottom = false,
+
+ -- General
+ -- X11
+ enable_wayland = false,
+ audible_bell = "Disabled",
+
+ visual_bell = {
+ fade_in_duration_ms = 5,
+ fade_out_duration_ms = 5,
+ target = "CursorColor",
+ },
+ automatically_reload_config = true,
+ scrollback_lines = 3500,
+ --inactive_pane_hsb = { saturation = 1.0, brightness = 1.0 },
+ --text_background_opacity = 0.3,
+ window_background_opacity = 0.8,
+ --window_background_image = '/path/to/wallpaper.jpg',
+ --window_background_image_hsb = {
+ -- -- Darken the background image by reducing it to 1/3rd
+ -- brightness = 0.3,
+ -- -- You can adjust the hue by scaling its value.
+ -- -- a multiplier of 1.0 leaves the value unchanged.
+ -- hue = 1.0,
+ -- -- You can adjust the saturation also.
+ -- saturation = 1.0,
+ --},
+ window_close_confirmation = "NeverPrompt",
+ --color_scheme = 'transparent',
+ use_resize_increments = true,
+}
diff --git a/common/config/zsh/.zshenv b/common/config/zsh/.zshenv
new file mode 100644
index 0000000..cdab5b7
--- /dev/null
+++ b/common/config/zsh/.zshenv
@@ -0,0 +1,346 @@
+# Load local/system wide binaries and scripts
+export PATH=$HOME/.bin:$HOME/.local/bin:$HOME/.scripts:/usr/local/bin:/sbin:/usr/sbin:$PATH
+export PATH="/data/data/com.termux/files/usr/local/bin:$PATH"
+
+# List of directories to ignore (relative to ~/.scripts)
+EXCLUDE_DIRS=("assets" "test")
+
+# Add .scripts to path
+if [ -d "$HOME/.scripts" ]; then
+ while IFS= read -r -d '' dir; do
+ # Extract relative path
+ rel_path="${dir#$HOME/.scripts/}"
+
+ # Check if the directory is in the exclude list
+ skip=false
+ for exclude in "${EXCLUDE_DIRS[@]}"; do
+ if [[ "$rel_path" == "$exclude"* ]]; then
+ skip=true
+ break
+ fi
+ done
+
+ # Add to PATH if not excluded
+ if [ "$skip" = false ]; then
+ PATH="$dir:$PATH"
+ fi
+ done < <(find "$HOME/.scripts" -type d -print0)
+fi
+
+# Global TERM color
+export TERM=xterm-256color
+
+
+# Conditionally set default term
+available_terms=("wezterm" "kitty" "alacritty" "xterm")
+for term in "${available_terms[@]}"; do
+ if command -v "$term" &> /dev/null; then
+ export TERMINAL="$term"
+ break
+ fi
+done
+
+# Default Programs:
+export EDITOR=$(command -v nvim || echo "vim")
+export TEXEDIT="$EDITOR"
+export FCEDIT="$EDITOR"
+export VISUAL="$EDITOR"
+export GIT_EDITOR="$EDITOR"
+export COLORTERM="truecolor"
+export TERM="xterm-256color"
+export READER="zathura"
+export BROWSER="firefox"
+export OPENER="xdg-open"
+if command -v nvim &> /dev/null; then
+ export MANPAGER="sh -c 'col -b | nvim -c \"set ft=man ts=8 nomod nolist nonu noma\" -c \"autocmd VimEnter * call feedkeys(\\\"\\<CR>q\\\")\" -'"
+else
+ export MANPAGER="bat"
+fi
+export MANROFFOPT="-c"
+export PAGER="less"
+export FAQ_STYLE='github'
+export VIDEO="mpv"
+export IMAGE="phototonic"
+
+# XDG Paths:
+export XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-$HOME/.config}
+export XDG_DATA_HOME=${XDG_DATA_HOME:-$HOME/.local/share}
+export XDG_CACHE_HOME=${XDG_CACHE_HOME:-$HOME/.cache}
+export INPUTRC="${XDG_CONFIG_HOME:-$HOME/.config}/inputrc"
+
+export ZDOTDIR="$XDG_CONFIG_HOME/zsh"
+export HISTFILE="$ZDOTDIR/.zhistory" # History filepath
+export HISTSIZE=1000000 # Maximum events for internal history
+export SAVEHIST=1000000 # Maximum events in history file
+export BANG_HIST # Treat the '!' character specially during expansion.
+export EXTENDED_HISTORY # Write the history file in the ":start:elapsed;command" format.
+export INC_APPEND_HISTORY # Write to the history file immediately, not when the shell exits.
+export SHARE_HISTORY # Share history between all sessions.
+export HIST_EXPIRE_DUPS_FIRST # Expire duplicate entries first when trimming history.
+export HIST_IGNORE_DUPS # Don't record an entry that was just recorded again.
+export HIST_IGNORE_ALL_DUPS # Delete old recorded entry if new entry is a duplicate.
+export HIST_FIND_NO_DUPS # Do not display a line previously found.
+export HIST_IGNORE_SPACE # Don't record an entry starting with a space.
+export HIST_SAVE_NO_DUPS # Don't write duplicate entries in the history file.
+export HIST_REDUCE_BLANKS # Remove superfluous blanks before recording entry.
+export HIST_VERIFY # Don't execute immediately upon history expansion.
+export HIST_BEEP # Beep when accessing nonexistent history.
+export INC_APPEND_HISTORY
+
+# Customize `ls` colours
+export LSCOLORS=ExGxBxDxCxEgEdxbxgxcxd
+
+# Other XDG paths:
+export RIPGREP_CONFIG_PATH="$XDG_CONFIG_HOME/ripgrep/ripgreprc"
+export DOCKER_CONFIG="$XDG_CONFIG_HOME/docker"
+export VSCODE_PORTABLE="$XDG_DATA_HOME/vscode"
+export GTK2_RC_FILES="$XDG_CONFIG_HOME/gtk-2.0/gtkrc"
+export PATH="/usr/bin/cmake:$PATH"
+export PATH=$PATH:/opt/google/chrome
+export DISCORD_USER_DATA_DIR="$XDG_DATA_HOME"
+export LYNX_CFG="$XDG_CONFIG_HOME/.lynxrc"
+
+# Manage Arch linux build sources
+export ASPROOT="${XDG_CACHE_HOME:-$HOME/.cache}/asp"
+
+# Homebrew
+#export PATH=/opt/homebrew/bin:$PATH
+export PATH="/opt/homebrew/sbin:$PATH"
+
+# Nix-profile
+export PATH=$HOME/.nix-profile/bin:$PATH
+
+# GnuPG
+export GPG_TTY=$(tty)
+#export GNUPGHOME="$XDG_CONFIG_HOME/gnupg"
+
+# Nvim
+export NVIM_TUI_ENABLE_TRUE_COLOR=1
+
+# Let FZF use ripgrep by default
+if type rg &> /dev/null; then
+ export FZF_DEFAULT_COMMAND="rg --files --hidden --glob '!{node_modules/*,.git/*}'"
+ export FZF_DEFAULT_OPTS='-m --height 50% --border'
+ export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"
+fi
+
+# Zoxide (cd alternative)
+if command -v zoxide >/dev/null 2>&1; then
+ eval "$(zoxide init zsh)"
+fi
+
+export XDG_MENU_PREFIX=gnome-
+
+# enable git scripts
+export DEVELOPMENT_DIRECTORY="$HOME/code"
+
+# Android Home
+export ANDROID_HOME=/opt/android-sdk
+export PATH=$ANDROID_HOME/cmdline-tools/latest/bin:$PATH
+#export PATH=$ANDROID_HOME/cmdline-tools/bin:$PATH
+export PATH=$ANDROID_HOME/tools:$PATH
+export PATH=$ANDROID_HOME/tools/bin:$PATH
+export PATH=$ANDROID_HOME/platform-tools:$PATH
+# Android emulator PATH
+export PATH=$ANDROID_HOME/emulator:$PATH
+# Android SDK ROOT PATH
+export ANDROID_SDK_ROOT=/opt/android-sdk
+export PATH=$ANDROID_SDK_ROOT:$PATH
+#export ANDROID_SDK_HOME="${XDG_CONFIG_HOME:-$HOME/.config}/android"
+
+# Programming Environment Variables:
+
+# Rust
+export RUSTUP_HOME=${XDG_DATA_HOME:-$HOME/.local/share}/rustup
+export CARGO_HOME=${XDG_DATA_HOME:-$HOME/.local/share}/cargo
+export PATH="${CARGO_HOME}/bin:${RUSTUP_HOME}/bin:$PATH"
+#export PATH="$PATH:$CARGO_HOME/bin"
+#[[ -d $CARGO_HOME/bin ]] && path=($CARGO_HOME/bin $path)
+if which rustc > /dev/null; then export RUST_BACKTRACE=1; fi
+#export PATH="$HOME/.cargo/bin:$PATH"
+#export CARGO_HOME=${XDG_DATA_HOME}/cargo
+#export RUSTUP_HOME=${XDG_DATA_HOME}/rustup
+
+
+# Dotnet
+# # Currently dotnet does not support XDG ( https://github.com/dotnet/sdk/issues/10390 )
+#export DOTNET_TOOLS_DIR="$HOME/.dotnet/tools"
+export DOTNET_HOME=${XDG_DATA_HOME:-$HOME/.local/share}/dotnet
+export DOTNET_CLI_HOME="$XDG_CONFIG_HOME/dotnet"
+#mkdir -p "$DOTNET_CLI_HOME";
+export PATH="$PATH":"$DOTNET_HOME"/tools
+export DOTNET_ROOT=/opt/dotnet
+# Disable telemetry for dotnet apps
+export DOTNET_CLI_TELEMETRY_OPTOUT=1
+
+
+# Java
+#export JAVA_HOME=/usr/lib/jvm/default-java
+#export JAVA_HOME='/usr/lib/jvm/java-8-openjdk'
+#export JAVA_HOME='/usr/lib/jvm/java-10-openjdk'
+#export JAVA_HOME='/usr/lib/jvm/java-11-openjdk'
+#export JAVA_HOME='/usr/lib/jvm/java-17-openjdk'
+export JAVA_HOME='/usr/lib/jvm/java-20-openjdk'
+#export PATH=$JAVA_HOME/bin:$PATH
+export _JAVA_OPTIONS=-Djava.util.prefs.userRoot="$XDG_CONFIG_HOME"/java
+#export DEFAULT_JVM_OPTS='"-Dcom.android.sdklib.toolsdir=$APP_HOME" -XX:+IgnoreUnrecognizedVMOptions'
+#export _JAVA_AWT_WM_NONREPARENTING=1
+#export JAVA_OPTS='-XX:+IgnoreUnrecognizedVMOptions --add-modules java.se.ee'
+#export JAVA_OPTS='-XX:+IgnoreUnrecognizedVMOptions --add-modules java.xml.bind'
+#Windows:
+#set JAVA_OPTS=-XX:+IgnoreUnrecognizedVMOptions --add-modules java.se.ee
+
+
+# Dart/Flutter
+export PATH="/opt/flutter/bin:/usr/lib/dart/bin:$PATH"
+
+
+# Go
+export GO_PATH=${XDG_DATA_HOME}/go
+export GOPATH="${XDG_DATA_HOME:-$HOME/.local/share}/go"
+
+
+# Javascript
+# NVM
+export NVM_DIR="$HOME/.config/nvm"
+[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
+#[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
+
+# global node installs (gross)
+[[ -d "$XDG_DATA_HOME/node/bin" ]] && path=($XDG_DATA_HOME/node/bin $path)
+export NODE_REPL_HISTORY="$XDG_DATA_HOME"/node_repl_history
+export NPM_CONFIG_USERCONFIG=$XDG_CONFIG_HOME/npm/npmrc
+#export NPM_CONFIG_INIT_AUTHOR_NAME='srdusr'
+#export NPM_CONFIG_INIT_AUTHOR_EMAIL='trevorgray@srdusr.com'
+#export NPM_CONFIG_INIT_AUTHOR_URL='https://srdusr.com'
+#export NPM_CONFIG_INIT_LICENSE='GPL-3.0'
+#export NPM_CONFIG_INIT_VERSION='0.0.0'
+#export NPM_CONFIG_SIGN_GIT_TAG='true'
+
+export BUN_INSTALL="$HOME/.bun"
+export PATH="$BUN_INSTALL/bin:$PATH"
+
+# Register Bun completion
+#fpath=("$HOME/.bun" $fpath)
+
+# Yarn
+#if command -v yarn >/dev/null 2>&1; then
+# export PATH="$PATH:`yarn global bin`"
+#fi
+#export PATH="$(yarn global bin):$PATH"
+#YARN_PATH="$HOME/.yarn/bin"
+#YARN_BIN_EXPORT="$HOME/.config/yarn/global/node_modules/.bin"
+
+# Ruby
+export GEM_PATH="$XDG_DATA_HOME/ruby/gems"
+export GEM_SPEC_CACHE="$XDG_DATA_HOME/ruby/specs"
+export GEM_HOME="$XDG_DATA_HOME/ruby/gems"
+#if [[ -d ~/.gem/ruby ]]; then
+# ver=$(find ~/.gem/ruby/* -maxdepth 0 | sort -rV | head -n 1)
+# export PATH="$PATH:${ver}/bin"
+#fi
+
+
+# Python
+# lazy load pyenv
+#export PYENV_ROOT=${PYENV_ROOT:-$HOME/.pyenv}
+#[[ -a $PYENV_ROOT/bin/pyenv ]] && path=($PYENV_ROOT/bin $path)
+#if type pyenv &> /dev/null || [[ -a $PYENV_ROOT/bin/pyenv ]]; then
+# function pyenv() {
+# unset pyenv
+# path=($PYENV_ROOT/shims $path)
+# eval "$(command pyenv init -)"
+# if which pyenv-virtualenv-init > /dev/null; then
+# eval "$(pyenv virtualenv-init -)"
+# export PYENV_VIRTUALENV_DISABLE_PROMPT=1
+# fi
+# pyenv $@
+# }
+#fi
+#export WORKON_HOME="$XDG_DATA_HOME/virtualenvs"
+#export WORKON_HOME=$HOME/.virtualenvs
+#export VIRTUALENVWRAPPER_PYTHON=`which python3`
+#export VIRTUALENVWRAPPER_PYTHON=$(which python3)
+#export VIRTUALENVWRAPPER_VIRTUALENV=`which virtualenv`
+#source /usr/local/bin/virtualenvwrapper.sh
+
+# Check if virtualenvwrapper.sh exists before sourcing
+if command -v virtualenvwrapper.sh >/dev/null 2>&1; then
+ export WORKON_HOME="$HOME/.virtualenvs"
+ export VIRTUALENVWRAPPER_PYTHON="$(command -v python3)"
+ export VIRTUALENVWRAPPER_VIRTUALENV="$(command -v virtualenv)"
+ source "$(command -v virtualenvwrapper.sh)"
+fi
+
+export VIRTUAL_ENV_DISABLE_PROMPT=false
+export JUPYTER_CONFIG_DIR="$XDG_CONFIG_HOME/jupyter"
+export IPYTHONDIR="$XDG_CONFIG_HOME/jupyter"
+
+# Python
+[[ "$(uname)" == "Darwin" ]] && export PYTHON_CONFIGURE_OPTS="--enable-framework"
+[[ "$(uname)" == "Linux" ]] && export PYTHON_CONFIGURE_OPTS="--enable-shared"
+
+export PYENV_ROOT="$HOME/.pyenv"
+export PATH="$PYENV_ROOT/bin:$PATH"
+
+# PHP
+PATH="$HOME/.config/composer/vendor/bin:$PATH"
+
+
+# Lua
+export PATH=$PATH:/usr/local/luarocks/bin
+#export PATH="$XDG_DATA_HOME/luarocks/bin:$PATH"
+
+#ver=$(find lua* -maxdepth 0 | sort -rV | head -n 1)
+#export LUA_PATH="$LUA_PATH:${ver}/share/lua/5.1/?.lua;${ver}/share/lua/5.1/?/init.lua;;"
+#export LUA_CPATH="$LUA_CPATH:${ver}/lib/lua/5.1/?.so;;"
+
+#LUAROCKS_PREFIX=/usr/local
+#export LUA_PATH="$LUAROCKS_PREFIX/share/lua/5.1/?.lua;$LUAROCKS_PREFIX/share/lua/5.1/?/init.lua;;"
+#export LUA_CPATH="$LUAROCKS_PREFIX/lib/lua/5.1/?.so;;"
+
+#export LUA_PATH="<path-to-add>;;"
+#export LUA_CPATH="./?.so;/usr/local/lib/lua/5.3/?.so;
+# /usr/local/share/lua/5.3/?.so;<path-to-add>"
+
+
+# Program settings
+#export MOZ_USE_XINPUT2="1" # Mozilla smooth scrolling/touchpads.
+# Pixel-perfect Firefox touchpad scrolling
+export MOZ_USE_XINPUT2=1
+
+# Cmake
+export PKG_CONFIG_PATH="/usr/local/lib64/pkgconfig:$PKG_CONFIG_PATH"
+
+
+# Scaling
+#export QT_AUTO_SCREEN_SCALE_FACTOR=0
+#export QT_SCALE_FACTOR=1
+#export QT_SCREEN_SCALE_FACTORS="1;1;1"
+#export GDK_SCALE=1
+#export GDK_DPI_SCALE=1
+
+## Prevent duplicate paths
+#typeset -U PATH path
+#
+## Default most programs to use fcitx global keyboard configurations
+#export GTK_IM_MODULE='fcitx'
+#export QT_IM_MODULE='fcitx'
+#export SDL_IM_MODULE='fcitx'
+#export XMODIFIERS='@im=fcitx'
+
+
+# Start blinking
+export LESS_TERMCAP_mb=$(tput bold; tput setaf 2) # green
+# Start bold
+export LESS_TERMCAP_md=$(tput bold; tput setaf 2) # green
+# Start stand out
+export LESS_TERMCAP_so=$(tput bold; tput setaf 3) # yellow
+# End standout
+export LESS_TERMCAP_se=$(tput rmso; tput sgr0)
+# Start underline
+export LESS_TERMCAP_us=$(tput smul; tput bold; tput setaf 1) # red
+# End Underline
+export LESS_TERMCAP_ue=$(tput sgr0)
+# End bold, blinking, standout, underline
+export LESS_TERMCAP_me=$(tput sgr0).
diff --git a/common/config/zsh/.zshrc b/common/config/zsh/.zshrc
new file mode 100644
index 0000000..0ada4f5
--- /dev/null
+++ b/common/config/zsh/.zshrc
@@ -0,0 +1,73 @@
+# ███████╗███████╗██╗ ██╗██████╗ ██████╗
+# ╚══███╔╝██╔════╝██║ ██║██╔══██╗██╔════╝
+# ███╔╝ ███████╗███████║██████╔╝██║
+# ███╔╝ ╚════██║██╔══██║██╔══██╗██║
+# ███████╗███████║██║ ██║██║ ██║╚██████╗
+# ╚══════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝
+
+# Profile zsh time
+#zmodload zsh/zprof
+
+# If not running interactively, and not being sourced, don’t do anything
+[[ $- != *i* ]] && [[ "${BASH_SOURCE[0]:-${(%):-%N}}" == "$0" ]] && return
+
+# Terminal key bindings
+#stty intr '^q' # Free Ctrl+C for copy use Ctrl+Q instead for Interrupt
+stty lnext '^-' # Free Ctrl+V for paste use Ctrl+- instead for Literal next
+stty stop undef # Disable Ctrl+S to freeze terminal
+stty start undef # Disable Ctrl+Q nfreeze terminal
+
+# Set the current prompt file (e.g., prompt, or prompt_minimal)
+ZSH_PROMPT="${ZSH_PROMPT:-prompt}"
+#ZSH_PROMPT="${ZSH_PROMPT:-prompt_minimal}"
+#ZSH_PROMPT="${ZSH_PROMPT:-prompt_new}"
+#ZSH_PROMPT="${ZSH_PROMPT:-prompt_simple}"
+
+# Source common Zsh files (excluding any that start with 'prompt')
+ZSH_SOURCES=()
+
+for zsh_source in "$HOME"/.config/zsh/user/*.zsh; do
+ if [[ $(basename "$zsh_source") == prompt* && $(basename "$zsh_source" .zsh) != "$ZSH_PROMPT" ]]; then
+ continue
+ fi
+ ZSH_SOURCES+=("$zsh_source")
+done
+
+# Source ZSH files
+for zsh_source in "${ZSH_SOURCES[@]}"; do
+ source "$zsh_source"
+done
+
+# Faster SSH
+if [[ -n "$SSH_CLIENT" ]]; then
+ export KEYTIMEOUT=10
+else
+ export KEYTIMEOUT=15
+fi
+
+# Prevent non-login shell anomalies or toolchain misidentification in VS Code
+if [[ "${TERM_PROGRAM:-}" == "vscode" ]]; then
+ unset ARGV0
+fi
+
+########## Source Plugins, should be last ##########
+
+# Load fzf keybindings and completion if fzf is installed
+if command -v fzf >/dev/null 2>&1; then
+ FZF_BASE="/usr/local/bin/fzf/shell"
+ [[ -f "${FZF_BASE}/key-bindings.zsh" ]] && source "${FZF_BASE}/key-bindings.zsh"
+ [[ -f "${FZF_BASE}/completion.zsh" ]] && source "${FZF_BASE}/completion.zsh"
+fi
+
+# Source plugins
+for plugin in \
+ "$HOME/.config/zsh/plugins/zsh-you-should-use/you-should-use.plugin.zsh" \
+ "$HOME/.config/zsh/plugins/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh" \
+ "$HOME/.config/zsh/plugins/zsh-autosuggestions/zsh-autosuggestions.plugin.zsh" \
+ "$HOME/.config/zsh/plugins/zsh-autosuggestions/zsh-autosuggestions.zsh"
+do
+ [ -f "$plugin" ] && source "$plugin"
+done
+
+# Profile zsh time
+#zprof # At the end of .zshrc
diff --git a/common/config/zsh/user/aliases.zsh b/common/config/zsh/user/aliases.zsh
new file mode 100644
index 0000000..c205a9b
--- /dev/null
+++ b/common/config/zsh/user/aliases.zsh
@@ -0,0 +1,208 @@
+########## Aliases ##########
+
+# Define alias for nvim/vim (fallback to vim)
+if command -v nvim > /dev/null; then
+ alias vi='nvim'
+else
+ alias vi='vim'
+fi
+
+#alias vv='$(history -p !vim)'
+alias vv="vim -c 'norm! ^O'"
+
+# Confirmation #
+alias mv='mv -i'
+alias cp='cp -i'
+alias ln='ln -i'
+
+# Disable 'rm'
+#alias rm='function _rm() { echo -e "\033[0;31mrm\033[0m is disabled, use \033[0;32mtrash\033[0m or \033[0;32mdel \033[0m\033[0;33m$1\033[0m"; }; _rm'
+#alias del='/bin/rm'
+
+# Use lsd for ls if available
+if command -v lsd >/dev/null 2>&1; then
+ alias ls='lsd --color=auto --group-directories-first'
+fi
+
+# ls variants
+alias l='ls -FAh --group-directories-first'
+alias la='ls -lAFh --group-directories-first'
+alias lt='ls -lFAht --group-directories-first'
+alias lr='ls -RFAh --group-directories-first'
+
+# more ls variants
+alias ldot='ls -ld .* --group-directories-first'
+alias lS='ls -1FASsh --group-directories-first'
+alias lart='ls -1Fcart --group-directories-first'
+alias lrt='ls -1Fcrt --group-directories-first'
+
+# ls with different alphabethical sorting
+#unalias ll
+#ll() { LC_COLLATE=C ls "$@" }
+
+# suffix aliases
+alias -g CP='| xclip -selection clipboard -rmlastnl'
+alias -g LL="| less exit 2>1 /dev/null"
+alias -g CA="| cat -A"
+alias -g KE="2>&1"
+alias -g NE="2>/dev/null"
+alias -g NUL=">/dev/null 2>&1"
+
+alias grep='grep --color=auto --exclude-dir={.git,.svn,.hg}'
+alias egrep='egrep --color=auto --exclude-dir={.git,.svn,.hg}'
+alias egrep='fgrep --color=auto --exclude-dir={.git,.svn,.hg}'
+
+#alias hist="grep '$1' $HISTFILE"
+alias hist="history | grep $1"
+
+
+alias gdb='gdb -q'
+alias rust-gdb='rust-gdb -q'
+
+alias cd="cd-clear-ls"
+alias clear='newline_clear'
+
+# List upto last 10 visited directories using "d" and quickly cd into any specific one
+alias d="dirs -v | head -10"
+
+# Using just a number from "0" to "9"
+alias 0="cd +0"
+alias 1="cd +1"
+alias 2="cd +2"
+alias 3="cd +3"
+alias 4="cd +4"
+alias 5="cd +5"
+alias 6="cd +6"
+alias 7="cd +7"
+alias 8="cd +8"
+alias 9="cd +9"
+
+alias sudo='sudo ' # zsh: elligible for alias expansion/fix syntax highlight
+alias sedit='sudoedit'
+#alias se='sudoedit'
+alias se='sudo -e'
+alias :q='exit 2>1 /dev/null'
+alias disk-destroyer='$(command -v dd)'
+alias dd='echo "Warning use command: disk-destroyer"'
+alias sc="systemctl"
+alias jc="journalctl"
+alias jck="journalctl -k" # Kernel
+alias jce='sudo journalctl -b --priority 0..3' # error
+alias journalctl-error='sudo journalctl -b --priority 0..3'
+alias jcssh="sudo journalctl -u sshd"
+alias tunnel='ssh -fNTL'
+# tty aliases
+#if [[ "$TERM" == 'linux' ]]; then
+# alias tmux='/usr/bin/tmux -L linux'
+#fi
+#alias logout="loginctl kill-user $(whoami)"
+
+logout() {
+ local wm
+ wm="$(windowManagerName)"
+ if [[ -n "$wm" ]]; then
+ echo "Logging out by killing window manager: $wm"
+ pkill "$wm"
+ else
+ echo "No window manager detected!" >&2
+ fi
+}
+alias lg="logout"
+
+#alias suspend='systemctl suspend && betterlockscreen -l' # Suspend(sleep) and lock screen if using systemctl
+#alias suspend='systemctl suspend' # Suspend(sleep) and lock screen if using systemctl
+alias suspend='loginctl suspend' # Suspend(sleep) and lock screen if using systemctl
+#alias shutdown='loginctl poweroff' # Suspend(sleep) and lock screen if using systemctl
+#alias shutdown='sudo /sbin/shutdown -h'
+#alias poweroff='loginctl poweroff'
+#alias reboot='loginctl reboot'
+alias reboot='sudo reboot'
+#alias hibernate='systemctl hibernate' # Hibernate
+alias lock='DISPLAY=:0 xautolock -locknow' # Lock my workstation screen from my phone
+alias oports="sudo lsof -i -P -n | grep -i 'listen'" # List open ports
+alias keyname="xev | sed -n 's/[ ]*state.* \([^ ]*\)).*/\1/p'"
+alias wget=wget --hsts-file="$XDG_CACHE_HOME/wget-hsts" # wget does not support environment variables
+alias open="xdg-open"
+alias pp='getlast 2>&1 |&tee -a output.txt'
+#alias lg='la | grep'
+alias pg='ps aux | grep'
+alias py='python'
+alias py3='python3'
+alias activate='source ~/.local/share/venv/bin/activate'
+alias sha256='shasum -a 256'
+alias rgf='rg -F'
+alias weather='curl wttr.in/durban'
+alias diary='nvim "$HOME/documents/main/inbox/diary/$(date +'%Y-%m-%d').md"'
+alias wifi='nmcli dev wifi show-password'
+alias ddg='w3m lite.duckduckgo.com'
+alias rss='newsboat'
+alias vpn='protonvpn'
+alias yt-dl="yt-dlp -f 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/mp4' --restrict-filename"
+#alias com.obsproject.Studio="obs"
+#alias obs="com.obsproject.Studio"
+#alias obs-stuido="obs"
+
+# Time aliases
+alias utc='TZ=Africa/Johannesburg date'
+alias ber='TZ=Europe/Berlin date'
+alias nyc='TZ=America/New_York date'
+alias sfo='TZ=America/Los_Angeles date'
+alias utc='TZ=Etc/UTC date'
+
+alias src='source $ZDOTDIR/.zshrc'
+alias p=proxy
+
+alias cheat='~/.scripts/cheat.sh ~/documents/notes/cheatsheets'
+alias crypto='curl -s rate.sx | head -n -2 | tail -n +10'
+#alias todo='glow "$HOME"/documents/main/notes/TODO.md'
+
+alias todo='$EDITOR "$(find "$HOME"/documents/main -type f -iname "todo.md" | head -n 1)"'
+alias android-studio='/opt/android-studio/bin/studio.sh' # android-studio
+alias nomachine='/usr/NX/bin/nxplayer' # nomachine
+alias firefox="firefox-bin"
+alias discord="vesktop-bin"
+alias fetch="fastfetch"
+alias batt='upower -i /org/freedesktop/UPower/devices/battery_BAT0 | grep -E "state|to full|percentage"'
+alias emerge-fetch='sudo tail -f /var/log/emerge-fetch.log'
+alias spotify="env LD_PRELOAD=/usr/local/lib/spotify-adblock.so spotify %U"
+
+alias proofread='firejail --private --private-tmp --net=none --seccomp --caps.drop=all zathura'
+
+# NVM
+if [ -s "$NVM_DIR/nvm.sh" ]; then
+ nvm_cmds=(nvm node npm yarn)
+ for cmd in "${nvm_cmds[@]}"; do
+ alias "$cmd"="unalias ${nvm_cmds[*]} && unset nvm_cmds && . $NVM_DIR/nvm.sh && $cmd"
+ done
+fi
+
+# Kubernetes
+if command -v kubectl > /dev/null; then
+ replaceNS() { kubectl config view --minify --flatten --context=$(kubectl config current-context) | yq ".contexts[0].context.namespace=\"$1\"" ; }
+ alias kks='KUBECONFIG=<(replaceNS "kube-system") kubectl'
+ alias kam='KUBECONFIG=<(replaceNS "authzed-monitoring") kubectl'
+ alias kas='KUBECONFIG=<(replaceNS "authzed-system") kubectl'
+ alias kar='KUBECONFIG=<(replaceNS "authzed-region") kubectl'
+ alias kt='KUBECONFIG=<(replaceNS "tenant") kubectl'
+
+ if command -v kubectl-krew > /dev/null; then
+ path=($XDG_CONFIG_HOME/krew/bin $path)
+ fi
+
+ rmfinalizers() {
+ kubectl get deployment "$1" -o json | jq '.metadata.finalizers = null' | kubectl apply -f -
+ }
+fi
+
+# Castero
+castero() {
+ if [[ -f ~/.local/share/venv/bin/activate ]]; then
+ . ~/.local/share/venv/bin/activate
+ fi
+ command castero "$@"
+}
+
+# Zoxide (cd alternative)
+if command -v zoxide >/dev/null 2>&1; then
+ eval "$(zoxide init zsh)"
+fi
diff --git a/common/config/zsh/user/bindings.zsh b/common/config/zsh/user/bindings.zsh
new file mode 100644
index 0000000..52cab06
--- /dev/null
+++ b/common/config/zsh/user/bindings.zsh
@@ -0,0 +1,175 @@
+########## Vi mode ##########
+bindkey -v
+
+local WORDCHARS='*?_-.[]~=&;!#$%^(){}<>'
+backward-kill-dir () {
+ local WORDCHARS=${WORDCHARS/\/}
+ zle backward-kill-word
+ zle -f kill
+}
+
+zle -N backward-kill-dir
+bindkey '^[^?' backward-kill-dir
+bindkey "^W" backward-kill-dir
+
+bindkey -M viins '^[[3~' delete-char
+bindkey -M vicmd '^[[3~' delete-char
+bindkey -v '^?' backward-delete-char
+bindkey -r '\e/'
+bindkey -s jk '\e'
+#bindkey "^W" backward-kill-word
+bindkey "^H" backward-delete-char # Control-h also deletes the previous char
+bindkey "^U" backward-kill-line
+bindkey "^[j" history-search-forward # or you can bind it to the down key "^[[B"
+bindkey "^[k" history-search-backward # or you can bind it to Up key "^[[A"
+
+bindkey '^[[D' backward-char # Left arrow
+bindkey '^[[C' forward-char # Right arrow
+bindkey '^[D' backward-char # Left arrow
+bindkey '^[C' forward-char # Right arrow
+bindkey '[C' forward-word
+bindkey '[D' backward-word
+bindkey -M viins '^[[D' backward-char # Left arrow
+bindkey -M viins '^[[C' forward-char # Right arrow
+
+bindkey -M vicmd '^[[D' backward-char # Left arrow
+bindkey -M vicmd '^[[C' forward-char # Right arrow
+
+# Define the 'autosuggest-execute' and 'autosuggest-accept' ZLE widgets
+autoload -Uz autosuggest-execute autosuggest-accept
+zle -N autosuggest-execute
+zle -N autosuggest-accept
+bindkey '^X' autosuggest-execute
+bindkey '^Y' autosuggest-accept
+bindkey '\M-l' accept-and-complete-next-history
+
+# Accept completion with <tab> or Ctrl+i and go to next/previous suggestions with Vi like keys: Ctrl+n/p
+zmodload -i zsh/complist
+accept-and-complete-next-history() {
+ zle expand-or-complete-prefix
+}
+zle -N accept-and-complete-next-history
+#bindkey -M menuselect '^i' accept-and-complete-next-history
+bindkey '^n' expand-or-complete
+bindkey '^p' reverse-menu-complete
+#bindkey '^I' expand-or-complete
+#bindkey '^[[Z]]' reverse-menu-complete
+bindkey -M menuselect '^[' undo
+
+# Edit line in vim with alt-e
+autoload edit-command-line; zle -N edit-command-line
+bindkey '^e' edit-command-line
+bindkey '^[e' edit-command-line # alt + e
+
+# Allow CTRL+D to exit zsh with partial command line (non empty line)
+exit_zsh() { exit }
+zle -N exit_zsh
+bindkey '^D' exit_zsh
+
+# Copy/Paste
+# Safe clipboard copy
+smart_copy() {
+ local text="${LBUFFER}${RBUFFER}"
+
+ # Prefer Wayland, fallback to X11, then others
+ if command -v wl-copy >/dev/null 2>&1 && [[ "$WAYLAND_DISPLAY" || "$XDG_SESSION_TYPE" == "wayland" ]]; then
+ echo -n "$text" | wl-copy --foreground --type text/plain 2>/dev/null || true
+ elif command -v xclip >/dev/null 2>&1 && [[ "$DISPLAY" || "$XDG_SESSION_TYPE" == "x11" || "$XDG_SESSION_TYPE" == "x11-xwayland" ]]; then
+ echo -n "$text" | xclip -selection clipboard 2>/dev/null || true
+ elif [[ "$(uname -s)" == "Darwin" ]] && command -v pbcopy >/dev/null 2>&1; then
+ echo -n "$text" | pbcopy 2>/dev/null || true
+ elif [[ "$OSTYPE" == "cygwin" || "$OSTYPE" == "msys" || "$OSTYPE" == "win32" ]]; then
+ echo -n "$text" | clip.exe 2>/dev/null || true
+ else
+ echo "smart_copy: No supported clipboard utility found." >&2
+ fi
+}
+
+# Safe clipboard paste
+smart_paste() {
+ local clip=""
+ if command -v wl-paste >/dev/null 2>&1 && [[ "$WAYLAND_DISPLAY" || "$XDG_SESSION_TYPE" == "wayland" ]]; then
+ clip=$(wl-paste --no-newline 2>/dev/null)
+ elif command -v xclip >/dev/null 2>&1 && [[ "$DISPLAY" || "$XDG_SESSION_TYPE" == "x11" || "$XDG_SESSION_TYPE" == "x11-xwayland" ]]; then
+ clip=$(xclip -selection clipboard -o 2>/dev/null)
+ elif [[ "$(uname -s)" == "Darwin" ]] && command -v pbpaste >/dev/null 2>&1; then
+ clip=$(pbpaste 2>/dev/null)
+ elif [[ "$OSTYPE" == "cygwin" || "$OSTYPE" == "msys" || "$OSTYPE" == "win32" ]]; then
+ clip=$(powershell.exe -Command 'Get-Clipboard -Raw' 2>/dev/null | tr -d '\r')
+ else
+ echo "smart_paste: No supported clipboard utility found." >&2
+ fi
+
+ LBUFFER+="$clip"
+ zle reset-prompt
+}
+
+# Register widgets
+zle -N smart_copy
+zle -N smart_paste
+
+# Bind keys (optional: choose your preferred)
+bindkey '^V' smart_paste
+bindkey -M viins '^V' smart_paste
+bindkey -M vicmd '^V' smart_paste
+bindkey -M vicmd 'p' smart_paste
+
+bindkey '^Y' smart_copy
+bindkey -M viins '^Y' smart_copy
+bindkey -M vicmd '^Y' smart_copy
+bindkey -M vicmd 'y' smart_copy
+
+# In vi mode, map Alt-H and Alt-L
+#bindkey -M viins "^[u" go_up # Alt-H to go up
+#bindkey -M viins "^[o" go_into # Alt-L to go into a directory
+
+
+# Newline and clear
+function newline_clear() {
+ printf "\n"
+ command clear
+}
+
+zle -N newline_clear
+
+no_tmux_clear() {
+ zle clear-screen
+}
+zle -N no_tmux_clear
+
+# Newline before clear
+if [[ -n "$TMUX" ]]; then
+ # Bind Ctrl-L to send newline and clear screen
+ bindkey '^L' newline_clear
+else
+ bindkey '^L' no_tmux_clear
+fi
+
+# use ctrl-z to toggle in and out of bg
+function toggle_fg_bg() {
+ if [[ $#BUFFER -eq 0 ]]; then
+ BUFFER="fg"
+ zle accept-line
+ else
+ BUFFER=""
+ zle clear-screen
+ fi
+}
+zle -N toggle_fg_bg
+bindkey '^Z' toggle_fg_bg
+
+
+
+
+## Custom key bindings to control history behavior
+#bindkey -M vicmd '^[[C' vi-forward-char # Right arrow in normal mode - just move cursor
+#bindkey -M vicmd '^[[D' vi-backward-char # Left arrow in normal mode - just move cursor
+#bindkey -M vicmd '^A' beginning-of-line # Ctrl-A - go to beginning of line
+#bindkey -M vicmd '^E' end-of-line # Ctrl-E - go to end of line
+
+# Disable automatic suggestion accept on right arrow in normal mode
+
+## Additional vi-mode key bindings to prevent unwanted history completion
+## Disable automatic history completion in normal mode
+#bindkey -M vicmd '^[[C' vi-forward-char # Right arrow - just move right, don't complete
+#bindkey -M vicmd '^[[D' vi-backward-char # Left arrow - just move left
diff --git a/common/config/zsh/user/completion.zsh b/common/config/zsh/user/completion.zsh
new file mode 100644
index 0000000..2445548
--- /dev/null
+++ b/common/config/zsh/user/completion.zsh
@@ -0,0 +1,172 @@
+#!/bin/zsh
+
+########## Completion(s) ##########
+
+autoload -Uz compinit
+_comp_path="${XDG_CACHE_HOME:-$HOME/.cache}/zcompdump"
+
+# Expands globs in conditional expressions
+if [[ $_comp_path(#qNmh-20) ]]; then
+ # -C (skip function check) implies -i (skip security check).
+ compinit -C -d "$_comp_path"
+else
+ mkdir -p "$_comp_path:h"
+ compinit -i -d "$_comp_path"
+ # Keep $_comp_path younger than cache time even if it isn't regenerated.
+ touch "$_comp_path"
+fi
+unset _comp_path
+
+# Skip the not really helpful global compinit
+skip_global_compinit=0
+
+DISABLE_MAGIC_FUNCTIONS=true
+
+
+#zstyle ':completion:*' menu select=1
+#zstyle ':completion:*:directory-stack' list-colors '=(#b) #([0-9]#)*( *)==95=38;5;12'
+
+# Options
+#setopt COMPLETE_IN_WORD # Complete from both ends of a word.
+##setopt ALWAYS_TO_END # Move cursor to the end of a completed word.
+##setopt PATH_DIRS # Perform path search even on command names with slashes.
+#setopt AUTO_MENU # Show completion menu on a successive tab press.
+#setopt AUTO_LIST # Automatically list choices on ambiguous completion.
+#setopt AUTO_PARAM_SLASH # If completed parameter is a directory, add a trailing slash.
+#setopt EXTENDED_GLOB # Needed for file modification glob modifiers with compinit.
+#unsetopt MENU_COMPLETE # Do not autoselect the first completion entry.
+
+## Disable all custom completions
+unsetopt COMPLETE_IN_WORD
+#unsetopt AUTO_MENU
+#unsetopt AUTO_LIST
+#unsetopt AUTO_PARAM_SLASH
+#unsetopt EXTENDED_GLOB
+#unsetopt MENU_COMPLETE # Do not autoselect the first completion entry.
+
+# Optional: Uncomment to disable waiting dots on completion
+# COMPLETION_WAITING_DOTS="false"
+
+setopt ALWAYS_TO_END # Move cursor to the end of a completed word.
+setopt PATH_DIRS # Perform path search even on command names with slashes.
+setopt AUTO_MENU # Show completion menu on a successive tab press.
+setopt AUTO_LIST # Automatically list choices on ambiguous completion.
+setopt AUTO_PARAM_SLASH # If completed parameter is a directory, add a trailing slash.
+setopt EXTENDED_GLOB # Needed for file modification glob modifiers with compinit.
+unsetopt MENU_COMPLETE # Do not autoselect the first completion entry.
+
+# Test the behavior with just the basics
+compinit
+# Variables
+LS_COLORS=${LS_COLORS:-'di=34:ln=35:so=32:pi=33:ex=31:bd=36;01:cd=33;01:su=31;40;07:sg=36;40;07:tw=32;40;07:ow=33;40;07:'}
+
+# Styles
+# Defaults.
+zstyle ':completion:*:default' list-colors ${(s.:.)LS_COLORS}
+zstyle ':completion:*:default' list-prompt '%S%M matches%s'
+
+# Use caching to make completion for commands such as dpkg and apt usable.
+zstyle ':completion::complete:*' use-cache on
+zstyle ':completion::complete:*' cache-path "${XDG_CACHE_HOME:-$HOME/.cache}/zcompcache"
+
+
+# Group matches and describe.
+zstyle ':completion:*:*:*:*:*' menu select
+zstyle ':completion:*:matches' group 'yes'
+zstyle ':completion:*:options' description 'yes'
+zstyle ':completion:*:options' auto-description '%d'
+zstyle ':completion:*:corrections' format ' %F{green}-- %d (errors: %e) --%f'
+zstyle ':completion:*:descriptions' format ' %F{yellow}-- %d --%f'
+zstyle ':completion:*:messages' format ' %F{purple} -- %d --%f'
+zstyle ':completion:*:warnings' format ' %F{red}-- no matches found --%f'
+zstyle ':completion:*' format ' %F{yellow}-- %d --%f'
+zstyle ':completion:*' group-name ''
+zstyle ':completion:*' verbose yes
+
+# Fuzzy match mistyped completions.
+zstyle ':completion:*' completer _complete _match _approximate
+zstyle ':completion:*:match:*' original only
+zstyle ':completion:*:approximate:*' max-errors 1 numeric
+
+# Increase the number of errors based on the length of the typed word. But make
+# sure to cap (at 7) the max-errors to avoid hanging.
+zstyle -e ':completion:*:approximate:*' max-errors 'reply=($((($#PREFIX+$#SUFFIX)/3>7?7:($#PREFIX+$#SUFFIX)/3))numeric)'
+
+# Don't complete unavailable commands.
+zstyle ':completion:*:functions' ignored-patterns '(_*|pre(cmd|exec))'
+
+# Array completion element sorting.
+zstyle ':completion:*:*:-subscript-:*' tag-order indexes parameters
+
+# Directories
+zstyle ':completion:*:*:cd:*' tag-order local-directories directory-stack path-directories
+zstyle ':completion:*:*:cd:*:directory-stack' menu yes select
+zstyle ':completion:*:-tilde-:*' group-order 'named-directories' 'path-directories' 'users' 'expand'
+zstyle ':completion:*' squeeze-slashes true
+
+# History
+zstyle ':completion:*:history-words' stop yes
+zstyle ':completion:*:history-words' remove-all-dups yes
+zstyle ':completion:*:history-words' list false
+zstyle ':completion:*:history-words' menu yes
+
+# Environment Variables
+zstyle ':completion::*:(-command-|export):*' fake-parameters ${${${_comps[(I)-value-*]#*,}%%,*}:#-*-}
+
+# Populate hostname completion. But allow ignoring custom entries from static
+# */etc/hosts* which might be uninteresting.
+zstyle -a ':completion:*:hosts' etc-host-ignores '_etc_host_ignores'
+
+zstyle -e ':completion:*:hosts' hosts 'reply=(
+ ${=${=${=${${(f)"$(cat {/etc/ssh/ssh_,~/.ssh/}known_hosts(|2)(N) 2> /dev/null)"}%%[#| ]*}//\]:[0-9]*/ }//,/ }//\[/ }
+ ${=${(f)"$(cat /etc/hosts(|)(N) <<(ypcat hosts 2> /dev/null))"}%%(\#${_etc_host_ignores:+|${(j:|:)~_etc_host_ignores}})*}
+ ${=${${${${(@M)${(f)"$(cat ~/.ssh/config 2> /dev/null)"}:#Host *}#Host }:#*\**}:#*\?*}}
+)'
+
+# Don't complete uninteresting users...
+zstyle ':completion:*:*:*:users' ignored-patterns \
+ adm amanda apache avahi beaglidx bin cacti canna clamav daemon \
+ dbus distcache dovecot fax ftp games gdm gkrellmd gopher \
+ hacluster haldaemon halt hsqldb ident junkbust ldap lp mail \
+ mailman mailnull mldonkey mysql nagios \
+ named netdump news nfsnobody nobody nscd ntp nut nx openvpn \
+ operator pcap postfix postgres privoxy pulse pvm quagga radvd \
+ rpc rpcuser rpm shutdown squid sshd sync uucp vcsa xfs '_*'
+
+# ... unless we really want to.
+zstyle '*' single-ignored show
+
+# Ignore multiple entries.
+zstyle ':completion:*:(rm|kill|diff):*' ignore-line other
+zstyle ':completion:*:rm:*' file-patterns '*:all-files'
+
+# Kill
+zstyle ':completion:*:*:*:*:processes' command 'ps -u $LOGNAME -o pid,user,command -w'
+zstyle ':completion:*:*:kill:*:processes' list-colors '=(#b) #([0-9]#) ([0-9a-z-]#)*=01;36=0=01'
+zstyle ':completion:*:*:kill:*' menu yes select
+zstyle ':completion:*:*:kill:*' force-list always
+zstyle ':completion:*:*:kill:*' insert-ids single
+
+# Man
+zstyle ':completion:*:manuals' separate-sections true
+zstyle ':completion:*:manuals.(^1*)' insert-sections true
+
+# Media Players
+zstyle ':completion:*:*:mpg123:*' file-patterns '*.(mp3|MP3):mp3\ files *(-/):directories'
+zstyle ':completion:*:*:mpg321:*' file-patterns '*.(mp3|MP3):mp3\ files *(-/):directories'
+zstyle ':completion:*:*:ogg123:*' file-patterns '*.(ogg|OGG|flac):ogg\ files *(-/):directories'
+zstyle ':completion:*:*:mocp:*' file-patterns '*.(wav|WAV|mp3|MP3|ogg|OGG|flac):ogg\ files *(-/):directories'
+
+# Mutt
+if [[ -s "$HOME/.mutt/aliases" ]]; then
+ zstyle ':completion:*:*:mutt:*' menu yes select
+ zstyle ':completion:*:mutt:*' users ${${${(f)"$(<"$HOME/.mutt/aliases")"}#alias[[:space:]]}%%[[:space:]]*}
+fi
+
+# SSH/SCP/RSYNC
+zstyle ':completion:*:(ssh|scp|rsync):*' tag-order 'hosts:-host:host hosts:-domain:domain hosts:-ipaddr:ip\ address *'
+zstyle ':completion:*:(scp|rsync):*' group-order users files all-files hosts-domain hosts-host hosts-ipaddr
+zstyle ':completion:*:ssh:*' group-order users hosts-domain hosts-host users hosts-ipaddr
+zstyle ':completion:*:(ssh|scp|rsync):*:hosts-host' ignored-patterns '*(.|:)*' loopback ip6-loopback localhost ip6-localhost broadcasthost
+zstyle ':completion:*:(ssh|scp|rsync):*:hosts-domain' ignored-patterns '<->.<->.<->.<->' '^[-[:alnum:]]##(.[-[:alnum:]]##)##' '*@*'
+zstyle ':completion:*:(ssh|scp|rsync):*:hosts-ipaddr' ignored-patterns '^(<->.<->.<->.<->|(|::)([[:xdigit:].]##:(#c,2))##(|%*))' '127.0.0.<->' '255.255.255.255' '::1' 'fe80::*'
diff --git a/common/config/zsh/user/functions.zsh b/common/config/zsh/user/functions.zsh
new file mode 100644
index 0000000..56f0ca1
--- /dev/null
+++ b/common/config/zsh/user/functions.zsh
@@ -0,0 +1,1607 @@
+# Dotfiles Management System
+if [[ -d "$HOME/.cfg" && -d "$HOME/.cfg/refs" ]]; then
+ # Core git wrapper with repository as work-tree
+ _config() {
+ git --git-dir="$HOME/.cfg" --work-tree="$HOME/.cfg" "$@"
+ }
+
+ # Detect OS
+ case "$(uname -s)" in
+ Linux) CFG_OS="linux" ;;
+ Darwin) CFG_OS="macos" ;;
+ MINGW*|MSYS*|CYGWIN*) CFG_OS="windows" ;;
+ *) CFG_OS="other" ;;
+ esac
+
+ # Map system path to repository path
+ _repo_path() {
+ local f="$1"
+
+ # If it's an absolute path that's not in HOME, handle it specially
+ if [[ "$f" == /* && "$f" != "$HOME/"* ]]; then
+ echo "$CFG_OS/${f#/}"
+ return
+ fi
+
+ # Check for paths that should go to the repository root
+ case "$f" in
+ common/*|linux/*|macos/*|windows/*|profile/*|README.md)
+ echo "$f"
+ return
+ ;;
+ "$HOME/"*)
+ f="${f#$HOME/}"
+ ;;
+ esac
+
+ # Default: put under OS-specific home
+ echo "$CFG_OS/home/$f"
+ }
+
+ _sys_path() {
+ local repo_path="$1"
+ local os_path_pattern="$CFG_OS/"
+
+ # Handle OS-specific files that are not in the home subdirectory
+ if [[ "$repo_path" == "$os_path_pattern"* && "$repo_path" != */home/* ]]; then
+ echo "/${repo_path#$os_path_pattern}"
+ return
+ fi
+
+ case "$repo_path" in
+ # Common configs → OS-specific config dirs
+ common/config/*)
+ case "$CFG_OS" in
+ linux)
+ local base="${XDG_CONFIG_HOME:-$HOME/.config}"
+ echo "$base/${repo_path#common/config/}"
+ ;;
+ macos)
+ echo "$HOME/Library/Application Support/${repo_path#common/config/}"
+ ;;
+ windows)
+ echo "$LOCALAPPDATA\\${repo_path#common/config/}"
+ ;;
+ *)
+ echo "$HOME/.config/${repo_path#common/config/}"
+ ;;
+ esac
+ ;;
+
+ # Common assets → stay in repo
+ common/assets/*)
+ echo "$HOME/.cfg/$repo_path"
+ ;;
+
+ # Other common files (dotfiles like .bashrc, .gitconfig, etc.) → $HOME
+ common/*)
+ echo "$HOME/${repo_path#common/}"
+ ;;
+
+ # OS-specific home
+ */home/*)
+ echo "$HOME/${repo_path#*/home/}"
+ ;;
+
+ # Profile configs and README → stay in repo
+ profile/*|README.md)
+ echo "$HOME/.cfg/$repo_path"
+ ;;
+
+ # Default fallback
+ *)
+ echo "$HOME/.cfg/$repo_path"
+ ;;
+
+ esac
+ }
+
+ # Prompts for sudo if needed and runs the command
+ _sudo_prompt() {
+ if [[ $EUID -eq 0 ]]; then
+ "$@"
+ else
+ if command -v sudo >/dev/null; then
+ sudo "$@"
+ elif command -v doas >/dev/null; then
+ doas "$@"
+ elif command -v pkexec >/dev/null; then
+ pkexec "$@"
+ else
+ echo "Error: No privilege escalation tool found."
+ return 1
+ fi
+ fi
+ }
+
+ # Main config command
+ config() {
+ local cmd="$1"; shift
+ local target_dir=""
+ # Parse optional --target flag for add
+ if [[ "$cmd" == "add" ]]; then
+ while [[ "$1" == --* ]]; do
+ case "$1" in
+ --target|-t)
+ target_dir="$2"
+ shift 2
+ ;;
+ *)
+ echo "Unknown option: $1"
+ return 1
+ ;;
+ esac
+ done
+ fi
+
+ case "$cmd" in
+ add)
+ local file_path
+ for file_path in "$@"; do
+ local repo_path
+ if [[ -n "$target_dir" ]]; then
+ local rel_path
+ if [[ "$file_path" == /* ]]; then
+ rel_path="$(basename "$file_path")"
+ else
+ rel_path="$file_path"
+ fi
+ repo_path="$target_dir/$rel_path"
+ else
+ repo_path="$(_repo_path "$file_path")"
+ fi
+
+ local full_repo_path="$HOME/.cfg/$repo_path"
+ mkdir -p "$(dirname "$full_repo_path")"
+ cp -a "$file_path" "$full_repo_path"
+
+ git --git-dir="$HOME/.cfg" --work-tree="$HOME/.cfg" add "$repo_path"
+
+ echo "Added: $file_path -> $repo_path"
+ done
+ ;;
+ rm)
+ local rm_opts=""
+ local file_path_list=()
+
+ for arg in "$@"; do
+ if [[ "$arg" == "-"* ]]; then
+ rm_opts+=" $arg"
+ else
+ file_path_list+=("$arg")
+ fi
+ done
+
+ for file_path in "${file_path_list[@]}"; do
+ local repo_path="$(_repo_path "$file_path")"
+
+ if [[ "$rm_opts" == *"-r"* ]]; then
+ _config rm --cached -r "$repo_path"
+ else
+ _config rm --cached "$repo_path"
+ fi
+
+ eval "rm $rm_opts \"$file_path\""
+ echo "Removed: $file_path"
+ done
+ ;;
+ sync)
+ local direction="${1:-to-repo}"; shift
+ _config ls-files | while read -r repo_file; do
+ local sys_file="$(_sys_path "$repo_file")"
+ local full_repo_path="$HOME/.cfg/$repo_file"
+ if [[ "$direction" == "to-repo" ]]; then
+ if [[ -e "$sys_file" && -n "$(diff "$full_repo_path" "$sys_file" 2>/dev/null || echo "diff")" ]]; then
+ cp -a "$sys_file" "$full_repo_path"
+ echo "Synced to repo: $sys_file"
+ fi
+ elif [[ "$direction" == "from-repo" ]]; then
+ if [[ -e "$full_repo_path" && -n "$(diff "$full_repo_path" "$sys_file" 2>/dev/null || echo "diff")" ]]; then
+ local dest_dir="$(dirname "$sys_file")"
+ if [[ "$sys_file" == /* && "$sys_file" != "$HOME/"* ]]; then
+ _sudo_prompt mkdir -p "$dest_dir"
+ _sudo_prompt cp -a "$full_repo_path" "$sys_file"
+ else
+ mkdir -p "$dest_dir"
+ cp -a "$full_repo_path" "$sys_file"
+ fi
+ echo "Synced from repo: $sys_file"
+ fi
+ fi
+ done
+ ;;
+ status)
+ local auto_synced=()
+ while read -r repo_file; do
+ local sys_file="$(_sys_path "$repo_file")"
+ local full_repo_path="$HOME/.cfg/$repo_file"
+ if [[ -e "$sys_file" && -e "$full_repo_path" ]]; then
+ if ! diff -q "$full_repo_path" "$sys_file" >/dev/null 2>&1; then
+ cp -fa "$sys_file" "$full_repo_path"
+ auto_synced+=("$repo_file")
+ fi
+ fi
+ done < <(_config ls-files)
+ if [[ ${#auto_synced[@]} -gt 0 ]]; then
+ echo "=== Auto-synced Files ==="
+ for repo_file in "${auto_synced[@]}"; do
+ echo "synced: $(_sys_path "$repo_file") -> $repo_file"
+ done
+ echo
+ fi
+ _config status
+ echo
+ ;;
+ deploy)
+ _config ls-files | while read -r repo_file; do
+ local full_repo_path="$HOME/.cfg/$repo_file"
+ local sys_file="$(_sys_path "$repo_file")" # destination only
+
+ # Only continue if the source exists
+ if [[ -e "$full_repo_path" && -n "$sys_file" ]]; then
+ local dest_dir
+ dest_dir="$(dirname "$sys_file")"
+
+ # Create destination if needed
+ if [[ "$sys_file" == /* && "$sys_file" != "$HOME/"* ]]; then
+ _sudo_prompt mkdir -p "$dest_dir"
+ _sudo_prompt cp -a "$full_repo_path" "$sys_file"
+ else
+ mkdir -p "$dest_dir"
+ cp -a "$full_repo_path" "$sys_file"
+ fi
+
+ echo "Deployed: $repo_file -> $sys_file"
+ fi
+ done
+ ;;
+ checkout)
+ echo "Checking out dotfiles from .cfg..."
+ _config ls-files | while read -r repo_file; do
+ local full_repo_path="$HOME/.cfg/$repo_file"
+ local sys_file="$(_sys_path "$repo_file")"
+
+ if [[ -e "$full_repo_path" && -n "$sys_file" ]]; then
+ local dest_dir
+ dest_dir="$(dirname "$sys_file")"
+
+ # Create destination if it doesn't exist
+ if [[ "$sys_file" == /* && "$sys_file" != "$HOME/"* ]]; then
+ _sudo_prompt mkdir -p "$dest_dir"
+ _sudo_prompt cp -a "$full_repo_path" "$sys_file"
+ else
+ mkdir -p "$dest_dir"
+ cp -a "$full_repo_path" "$sys_file"
+ fi
+
+ echo "Checked out: $repo_file -> $sys_file"
+ fi
+ done
+ ;;
+ backup)
+ local timestamp=$(date +%Y%m%d%H%M%S)
+ local backup_dir="$HOME/.dotfiles_backup/$timestamp"
+ echo "Backing up existing dotfiles to $backup_dir..."
+
+ _config ls-files | while read -r repo_file; do
+ local sys_file="$(_sys_path "$repo_file")"
+ if [[ -e "$sys_file" ]]; then
+ local dest_dir_full="$backup_dir/$(dirname "$repo_file")"
+ mkdir -p "$dest_dir_full"
+ cp -a "$sys_file" "$backup_dir/$repo_file"
+ fi
+ done
+ echo "Backup complete. To restore, copy files from $backup_dir to their original locations."
+ ;;
+ *)
+ _config "$cmd" "$@"
+ ;;
+ esac
+ }
+fi
+
+# Make SUDO_ASKPASS agnostic: pick the first available askpass binary.
+# You can predefine SUDO_ASKPASS env var to force a particular path.
+: "${SUDO_ASKPASS:=""}"
+
+# list of common askpass binaries (order: preferred -> fallback)
+_askpass_candidates=(
+ "$SUDO_ASKPASS" # user-specified (if absolute path)
+ "/usr/lib/ssh/x11-ssh-askpass"
+ "/usr/libexec/openssh/ssh-askpass"
+ "/usr/lib/ssh/ssh-askpass"
+ "/usr/bin/ssh-askpass"
+ "/usr/bin/ssh-askpass-gtk"
+ "/usr/bin/ssh-askpass-gnome"
+ "/usr/bin/ssh-askpass-qt"
+ "/usr/bin/ksshaskpass"
+ "/usr/bin/zenity" # use zenity --entry as wrapper (see below)
+ "/usr/bin/mate-ssh-askpass"
+ "/usr/bin/xdg-open" # last-resort GUI helper (not ideal)
+)
+
+find_askpass() {
+ for p in "${_askpass_candidates[@]}"; do
+ [ -z "$p" ] && continue
+ # if user gave a path in SUDO_ASKPASS we accept it only if it's executable
+ if [ -n "$SUDO_ASKPASS" ] && [ "$p" = "$SUDO_ASKPASS" ]; then
+ [ -x "$p" ] && { printf '%s\n' "$p"; return 0; }
+ continue
+ fi
+
+ # if candidate is an absolute path, test directly
+ if [ "${p#/}" != "$p" ]; then
+ [ -x "$p" ] && { printf '%s\n' "$p"; return 0; }
+ continue
+ fi
+
+ # otherwise try to resolve via PATH
+ if command -v "$p" >/dev/null 2>&1; then
+ # For zenity, we will use a small wrapper (see below)
+ printf '%s\n' "$(command -v "$p")"
+ return 0
+ fi
+ done
+
+ return 1
+}
+
+# If zenity is chosen, use a thin wrapper script so sudo -A can call it like an askpass binary.
+# This wrapper will be created in $XDG_RUNTIME_DIR or /tmp (non-persistent).
+create_zenity_wrapper() {
+ local wrapper
+ wrapper="${XDG_RUNTIME_DIR:-/tmp}/.sudo_askpass_zenity.sh"
+ cat >"$wrapper" <<'EOF'
+#!/bin/sh
+# simple zenity askpass wrapper for sudo
+# prints password to stdout so sudo -A works
+zenity --entry --title="Authentication" --text="Elevated privileges are required" --hide-text 2>/dev/null || exit 1
+EOF
+ chmod 700 "$wrapper"
+ printf '%s\n' "$wrapper"
+}
+
+# Set askpass
+if [ -z "$SUDO_ASKPASS" ]; then
+ candidate="$(find_askpass || true)"
+ if [ -n "$candidate" ]; then
+ if command -v zenity >/dev/null 2>&1 && [ "$(command -v zenity)" = "$candidate" ]; then
+ # create the wrapper and export it
+ wrapper="$(create_zenity_wrapper)"
+ export SUDO_ASKPASS="$wrapper"
+ else
+ export SUDO_ASKPASS="$candidate"
+ fi
+ else
+ # optional: leave unset or set to empty to avoid mistakes
+ unset SUDO_ASKPASS
+ fi
+fi
+# debug: (uncomment to print what was chosen)
+# printf 'SUDO_ASKPASS -> %s\n' "${SUDO_ASKPASS:-<none>}"
+
+
+# Git
+# No arguments: `git status`
+# With arguments: acts like `git`
+g() {
+ if [ $# -gt 0 ]; then
+ git "$@" # If arguments are provided, pass them to git
+ else
+ git status # Otherwise, show git status
+ fi
+}
+
+# Complete g like git
+compdef g=git
+
+# Git alias commands
+ga() { g add "$@"; } # ga: Add files to the staging area
+gaw() { g add -A && g diff --cached -w | g apply --cached -R; } # gaw: Add all changes to the staging area and unstage whitespace changes
+grm() { g rm "$@"; }
+gb() { g branch "$@"; } # gb: List branches
+gbl() { g branch -l "$@"; } # gbl: List local branches
+gbD() { g branch -D "$@"; } # gbD: Delete a branch
+gbu() { g branch -u "$@"; } # gbu: Set upstream branch
+ge() { g clone "$@"; }
+gc() { g commit "$@"; } # gc: Commit changes
+gcm() { g commit -m "$@"; } # gcm: Commit with a message
+gca() { g commit -a "$@"; } # gca: Commit all changes
+gcaa() { g commit -a --amend "$@"; } # gcaa: Amend the last commit
+gcam() { g commit -a -m "$@"; } # gcam: Commit all changes with a message
+gce() { g commit -e "$@"; } # gce: Commit with message and allow editing
+gcfu() { g commit --fixup "$@"; } # gcfu: Commit fixes in the context of the previous commit
+gco() { g checkout "$@"; } # gco: Checkout a branch or file
+gcob() { g checkout -b "$@"; } # gcob: Checkout a new branch
+gcoB() { g checkout -B "$@"; } # gcoB: Checkout a new branch, even if it exists
+gcp() { g cherry-pick "$@"; } # gcp: Cherry-pick a commit
+gcpc() { g cherry-pick --continue "$@"; } # gcpc: Continue cherry-picking after resolving conflicts
+gd() { g diff "$@"; } # gd: Show changes
+#gd^() { g diff HEAD^ HEAD "$@"; } # gd^: Show changes between HEAD^ and HEAD
+gds() { g diff --staged "$@"; } # gds: Show staged changes
+gl() { g lg "$@"; } # gl: Show a customized log
+glg() { g log --graph --decorate --all "$@"; } # glg: Show a customized log with graph
+gls() { # Query `glog` with regex query.
+ query="$1"
+ shift
+ glog --pickaxe-regex "-S$query" "$@"
+}
+gdc() { g diff --cached "$@"; } # gdc: Show changes between the working directory and the index
+gu() { g pull "$@"} # gu: Pull
+gp() { g push "$@"} # gp: Push
+gpom() { g push origin main "$@"; } # gpom: Push changes to origin main
+gr() { g remote "$@"; } # gr: Show remote
+gra() { g rebase --abort "$@"; } # gra: Abort a rebase
+grb() { g rebase --committer-date-is-author-date "$@"; } # grb: Rebase with the author date preserved
+grbom() { grb --onto master "$@"; } # grbom: Rebase onto master
+grbasi() { g rebase --autosquash --interactive "$@"; } # grbasi: Interactive rebase with autosquash
+grc() { g rebase --continue "$@"; } # grc: Continue a rebase
+grs() { g restore --staged "$@"; } # grs: Restore changes staged for the next commit
+grv() { g remote -v "$@"; } # grv: Show remote URLs after each name
+grh() { g reset --hard "$@"; } # grh: Reset the repository and the working directory
+grH() { g reset HEAD "$@"; } # grH: Reset the index but not the working directory
+#grH^() { g reset HEAD^ "$@"; } # grH^: Reset the index and working directory to the state of the HEAD's first parent
+gs() { g status -sb "$@"; } # gs: Show the status of the working directory and the index
+gsd() { g stash drop "$@"; } # gsd: Drop a stash
+gsl() { g stash list --date=relative "$@"; } # gsl: List all stashes
+gsp() { g stash pop "$@"; } # gsp: Apply and remove a single stash
+gss() { g stash show "$@"; } # gss: Show changes recorded in the stash as a diff
+gst() { g status "$@"; } # gst: Show the status of the working directory and the index
+gsu() { g standup "$@"; } # gsu: Customized standup command
+gforgotrecursive() { g submodule update --init --recursive --remote "$@"; } # gforgotrecursive: Update submodules recursively
+gfp() { g commit --amend --no-edit && g push --force-with-lease "$@"; } # gfp: Amending the last commit and force-pushing
+
+# Temporarily unset GIT_WORK_TREE
+function git_without_work_tree() {
+ # Only proceed if a git command is being run
+ if [ "$1" = "git" ]; then
+ shift
+ # Check if the current directory is inside a Git work tree
+ if git rev-parse --is-inside-work-tree &>/dev/null; then
+ # If inside a work tree, temporarily unset GIT_WORK_TREE
+ GIT_WORK_TREE_OLD="$GIT_WORK_TREE"
+ unset GIT_WORK_TREE
+ git "$@"
+ export GIT_WORK_TREE="$GIT_WORK_TREE_OLD"
+ else
+ # If not inside a work tree, call git command directly
+ git "$@"
+ fi
+ else
+ # If it's not a git command, just execute it normally
+ command "$@"
+ fi
+}
+
+# Set alias conditionally
+#alias git='git_without_work_tree git'
+
+# Set bare dotfiles repository git environment variables dynamically
+function set_git_env_vars() {
+ # Do nothing unless ~/.cfg exists and is a bare git repo
+ [[ -d "$HOME/.cfg" ]] || return
+ git --git-dir="$HOME/.cfg" rev-parse --is-bare-repository &>/dev/null || return
+
+ # Skip if last command was a package manager
+ if [[ "${(%)${(z)history[1]}}" =~ ^(pacman|yay|apt|dnf|brew|npm|pip|gem|go|cargo) ]]; then
+ return
+ fi
+
+ # Only set env vars if not already inside another Git repo
+ if ! git rev-parse --is-inside-work-tree &>/dev/null; then
+ export GIT_DIR="$HOME/.cfg"
+ export GIT_WORK_TREE="$(realpath ~)"
+ else
+ unset GIT_DIR
+ unset GIT_WORK_TREE
+ fi
+}
+
+# Hook and initial call
+function chpwd() { set_git_env_vars }
+set_git_env_vars
+
+# Git Subtrees
+function gsp() {
+ # Config file for subtrees
+ #
+ # Format:
+ # <prefix>;<remote address>;<remote branch>
+ # # Lines starting with '#' will be ignored
+ GIT_SUBTREE_FILE="$PWD/.gitsubtrees"
+
+ if [ ! -f "$GIT_SUBTREE_FILE" ]; then
+ echo "Nothing to do - file <$GIT_SUBTREE_FILE> does not exist."
+ return
+ fi
+
+ if ! command -v config &> /dev/null; then
+ echo "Error: 'config' command not found. Make sure it's available in your PATH."
+ return
+ fi
+
+ OLD_IFS=$IFS
+ IFS=$'\n'
+ for LINE in $(cat "$GIT_SUBTREE_FILE"); do
+
+ # Skip lines starting with '#'.
+ if [[ $LINE = \#* ]]; then
+ continue
+ fi
+
+ # Parse the current line.
+ PREFIX=$(echo "$LINE" | cut -d';' -f 1)
+ REMOTE=$(echo "$LINE" | cut -d';' -f 2)
+ BRANCH=$(echo "$LINE" | cut -d';' -f 3)
+
+ # Pull from the remote.
+ echo "Executing: git subtree pull --prefix=$PREFIX $REMOTE $BRANCH"
+ if git subtree pull --prefix="$PREFIX" "$REMOTE" "$BRANCH"; then
+ echo "Subtree pull successful for $PREFIX."
+ else
+ echo "Error: Subtree pull failed for $PREFIX."
+ fi
+ done
+
+ IFS=$OLD_IFS
+}
+
+# Print previous command into a file
+getlast () {
+ fc -nl $((HISTCMD - 1))
+}
+
+# Copy the current command to a file
+copy_command_to_file() {
+ # Only write the last command if BUFFER is not empty
+ if [[ -n "$BUFFER" ]]; then
+ echo "$BUFFER" > ~/command_log.txt # Overwrite with the latest command
+ else
+ # If the buffer is empty, remove the previous log file
+ command rm -f ~/command_log.txt # Optionally remove the log if no command is present
+ fi
+}
+
+# Display the latest command from the log in the user input
+display_latest_command() {
+ if [[ -f ~/command_log.txt ]]; then
+ # Read the last command from the log
+ local last_command
+ last_command=$(< ~/command_log.txt)
+
+ # Only display if the last command is not empty
+ if [[ -n "$last_command" ]]; then
+ BUFFER="$last_command" # Set the BUFFER to the last command
+ CURSOR=${#BUFFER} # Set the cursor to the end of the command
+ fi
+ fi
+ zle reset-prompt # Refresh the prompt
+}
+
+# Go up a directory
+go_up() {
+ copy_command_to_file # Copy the current command to a file
+ BUFFER="" # Clear the current command line
+ cd .. || return # Change directory and return if it fails
+ display_latest_command # Display the latest command in the user input
+}
+
+# Initialize a variable to store the previous directory
+previous_dir=""
+
+# Function to change directories
+go_into() {
+ copy_command_to_file # Copy the current command to a file
+
+ # Use fzf or another tool to choose the directory
+ local dir
+ dir=$( (ls -d */; echo "Go Last directory:") | fzf --height 40% --reverse --tac) # Include previous directory as an option
+
+ if [[ -n "$dir" ]]; then
+ # Check if the user selected the previous directory
+ if [[ "$dir" == Previous:* ]]; then
+ cd - || return # Change to the previous directory
+ else
+ cd "${dir%/}" || return # Change directory if a selection is made (remove trailing slash)
+ fi
+
+ # Save the current directory to previous_dir
+ previous_dir=$(pwd) # Update previous_dir to current directory after changing
+ BUFFER="" # Clear the current command line
+ display_latest_command # Display the last command if available
+ fi
+}
+
+# Register functions as ZLE widgets
+zle -N go_up
+zle -N go_into
+
+
+# XDG_GAMES_DIR:
+# Path to user-dirs config
+USER_DIRS_FILE="$HOME/.config/user-dirs.dirs"
+
+if [ -f "$USER_DIRS_FILE" ]; then
+ # Extract directory names from user-dirs config
+ _dirs=(
+ ${(f)"$(grep '^XDG_.*_DIR=' "$USER_DIRS_FILE" \
+ | cut -d= -f2 \
+ | tr -d '"' \
+ | sed "s|^\$HOME/||")"}
+ )
+
+ _lowercase_count=0
+ _total_count=0
+ for d in "${_dirs[@]}"; do
+ [ -n "$d" ] || continue
+ _total_count=$(( _total_count + 1 ))
+ case "$d" in
+ [a-z0-9]*)
+ _lowercase_count=$(( _lowercase_count + 1 ))
+ ;;
+ esac
+ done
+
+ # Require majority lowercase (≥70%)
+ if [ "$_total_count" -gt 0 ]; then
+ _percent=$(( 100 * _lowercase_count / _total_count ))
+ if [ "$_percent" -ge 70 ]; then
+ # Ensure the lowercase games directory exists
+ if [ ! -d "$HOME/games" ]; then
+ mkdir -p "$HOME/games"
+ fi
+
+ # Create symbolic link if it doesn't already exist
+ if [ ! -L "$HOME/Games" ] && [ ! -d "$HOME/Games" ]; then
+ ln -s "$HOME/games" "$HOME/Games"
+ fi
+
+ export XDG_GAMES_DIR="$HOME/games"
+ fi
+ fi
+
+ unset _dirs _lowercase_count _total_count _percent
+fi
+
+
+# Enter directory and list contents
+function cd-clear-ls() {
+ if [ -n "$1" ]; then
+ builtin cd "$@" 2>/dev/null || { echo "cd: no such file or directory: $1"; return 1; }
+ else
+ builtin cd ~ || return 1
+ fi
+
+ echo -e "\033[H\033[J" # Clear screen but keep scroll buffer
+
+ if [ "$PWD" != "$HOME" ] && git rev-parse --is-inside-work-tree &>/dev/null; then
+ ls -a
+ else
+ ls
+ fi
+}
+
+# cd using "up n" as a command up as many directories, example "up 3"
+up() {
+ # default parameter to 1 if non provided
+ declare -i d=${@:-1}
+ # ensure given parameter is non-negative. Print error and return if it is
+ (( $d < 0 )) && (>&2 echo "up: Error: negative value provided") && return 1;
+ # remove last d directories from pwd, append "/" in case result is empty
+ cd "$(pwd | sed -E 's;(/[^/]*){0,'$d'}$;;')/";
+}
+
+# cd into $XDG_CONFIG_HOME/$1 directory
+c() {
+ local root=${XDG_CONFIG_HOME:-~/.config}
+ local dname="$root/$1"
+ if [[ ! -d "$dname" ]]; then
+ return
+ fi
+ cd "$dname"
+}
+
+# Make and cd into directory and any parent directories
+mkcd () {
+ if [[ -z "$1" ]]; then
+ echo "Usage: mkcd <dir>" 1>&2
+ return 1
+ fi
+ mkdir -p "$1"
+ cd "$1"
+}
+
+bak() {
+ if [[ -e "$1" ]]; then
+ echo "Found: $1"
+ mv "${1%.*}"{,.bak}
+ elif [[ -e "$1.bak" ]]; then
+ echo "Found: $1.bak"
+ mv "$1"{.bak,}
+ fi
+}
+
+back() {
+ for file in "$@"; do
+ cp -r "$file" "$file".bak
+ done
+}
+
+# tre is a shorthand for tree
+tre() {
+ tree -aC -I \
+ '.git|.hg|.svn|.tmux|.backup|.vim-backup|.swap|.vim-swap|.undo|.vim-undo|*.bak|tags' \
+ --dirsfirst "$@" \
+ | less
+}
+
+# switch from/to project/package dir
+pkg() {
+ if [ "$#" -eq 2 ]; then
+ ln -s "$(readlink -f $1)" "$(readlink -f $2)"/._pkg
+ ln -s "$(readlink -f $2)" "$(readlink -f $1)"/._pkg
+ else
+ cd "$(readlink -f ./._pkg)"
+ fi
+}
+
+# Prepare C/C++ project for Language Server Protoco
+lsp-prep() {
+ (cd build && cmake .. -DCMAKE_EXPORT_COMPILE_COMMANDS=ON) \
+ && ln -sf build/compile_commands.json
+}
+
+reposize() {
+ url=`echo $1 \
+ | perl -pe 's#(?:https?://github.com/)([\w\d.-]+\/[\w\d.-]+).*#\1#g' \
+ | perl -pe 's#git\@github.com:([\w\d.-]+\/[\w\d.-]+)\.git#\1#g'
+ `
+ printf "https://github.com/$url => "
+ curl -s https://api.github.com/repos/$url \
+ | jq '.size' \
+ | numfmt --to=iec --from-unit=1024
+}
+
+# Launch a program in a terminal without getting any output,
+# and detach the process from terminal
+# (can then close the terminal without terminating process)
+-echo() {
+ "$@" &> /dev/null & disown
+}
+
+# Reload shell
+function reload() {
+ local compdump_files="$ZDOTDIR/.zcompdump*"
+
+ if ls $compdump_files &> /dev/null; then
+ rm -f $compdump_files
+ fi
+
+ exec $SHELL -l
+}
+
+#pom() {
+# local -r HOURS=${1:?}
+# local -r MINUTES=${2:-0}
+# local -r POMODORO_DURATION=${3:-25}
+#
+# bc <<< "(($HOURS * 60) + $MINUTES) / $POMODORO_DURATION"
+#}
+
+#mnt() {
+# local FILE="/mnt/external"
+# if [ ! -z $2 ]; then
+# FILE=$2
+# fi
+#
+# if [ ! -z $1 ]; then
+# sudo mount "$1" "$FILE" -o rw
+# echo "Device in read/write mounted in $FILE"
+# fi
+#
+# if [ $# = 0 ]; then
+# echo "You need to provide the device (/dev/sd*) - use lsblk"
+# fi
+#}
+#
+#umnt() {
+# local DIRECTORY="/mnt"
+# if [ ! -z $1 ]; then
+# DIRECTORY=$1
+# fi
+# MOUNTED=$(grep $DIRECTORY /proc/mounts | cut -f2 -d" " | sort -r)
+# cd "/mnt"
+# sudo umount $MOUNTED
+# echo "$MOUNTED unmounted"
+#}
+
+mntmtp() {
+ local DIRECTORY="$HOME/mnt"
+ if [ ! -z $2 ]; then
+ local DIRECTORY=$2
+ fi
+ if [ ! -d $DIRECTORY ]; then
+ mkdir $DIRECTORY
+ fi
+
+ if [ ! -z $1 ]; then
+ simple-mtpfs --device "$1" "$DIRECTORY"
+ echo "MTPFS device in read/write mounted in $DIRECTORY"
+ fi
+
+ if [ $# = 0 ]; then
+ echo "You need to provide the device number - use simple-mtpfs -l"
+ fi
+}
+
+umntmtp() {
+ local DIRECTORY="$HOME/mnt"
+ if ; then
+ DIRECTORY=$1
+ fi
+ cd $HOME
+ umount $DIRECTORY
+ echo "$DIRECTORY with mtp filesystem unmounted"
+}
+duckduckgo() {
+ lynx -vikeys -accept_all_cookies "https://lite.duckduckgo.com/lite/?q=$@"
+}
+
+wikipedia() {
+ lynx -vikeys -accept_all_cookies "https://en.wikipedia.org/wiki?search=$@"
+}
+
+#function filesize() {
+# # Check if 'du' supports the -b option, which provides sizes in bytes.
+# if du -b /dev/null > /dev/null 2>&1; then
+# local arg=-sbh; # If supported, use -sbh options for 'du'.
+# else
+# local arg=-sh; # If not supported, use -sh options for 'du'.
+# fi
+#
+# # Check if no arguments are provided.
+# if [ "$#" -eq 0 ]; then
+# # Calculate and display sizes for all files and directories in cwd.
+# du $arg ./*
+# else
+# # Calculate and display sizes for the specified files and directories.
+# du $arg -- "$@"
+# fi
+#}
+#
+
+fgl() {
+ git log --graph --color=always \
+ --format="%C(auto)%h%d %s %C(black)%C(bold)%cr" "$@" |
+ fzf --ansi --no-sort --reverse --tiebreak=index --bind=ctrl-s:toggle-sort \
+ --bind "ctrl-m:execute:
+ (grep -o '[a-f0-9]\{7\}' | head -1 |
+ xargs -I % sh -c 'git show --color=always % | less -R') << 'FZF-EOF'
+ {}
+FZF-EOF"
+}
+
+fgb() {
+ local branches branch
+ branches=$(git --no-pager branch -vv) &&
+ branch=$(echo "$branches" | fzf +m) &&
+ git checkout $(echo "$branch" | awk '{print $1}' | sed "s/.* //")
+}
+
+# +--------+
+# | Pacman |
+# +--------+
+
+# TODO can improve that with a bind to switch to what was installed
+fpac() {
+ pacman -Slq | fzf --multi --reverse --preview 'pacman -Si {1}' | xargs -ro sudo pacman -S
+}
+
+fyay() {
+ yay -Slq | fzf --multi --reverse --preview 'yay -Si {1}' | xargs -ro yay -S
+}
+
+# +------+
+# | tmux |
+# +------+
+
+fmux() {
+ prj=$(find $XDG_CONFIG_HOME/tmuxp/ -execdir bash -c 'basename "${0%.*}"' {} ';' | sort | uniq | nl | fzf | cut -f 2)
+ echo $prj
+ [ -n "$prj" ] && tmuxp load $prj
+}
+
+# ftmuxp - propose every possible tmuxp session
+ftmuxp() {
+ if [[ -n $TMUX ]]; then
+ return
+ fi
+
+ # get the IDs
+ ID="$(ls $XDG_CONFIG_HOME/tmuxp | sed -e 's/\.yml$//')"
+ if [[ -z "$ID" ]]; then
+ tmux new-session
+ fi
+
+ create_new_session="Create New Session"
+
+ ID="${create_new_session}\n$ID"
+ ID="$(echo $ID | fzf | cut -d: -f1)"
+
+ if [[ "$ID" = "${create_new_session}" ]]; then
+ tmux new-session
+ elif [[ -n "$ID" ]]; then
+ # Change name of urxvt tab to session name
+ printf '\033]777;tabbedx;set_tab_name;%s\007' "$ID"
+ tmuxp load "$ID"
+ fi
+}
+
+# ftmux - help you choose tmux sessions
+ftmux() {
+ if [[ ! -n $TMUX ]]; then
+ # get the IDs
+ ID="`tmux list-sessions`"
+ if [[ -z "$ID" ]]; then
+ tmux new-session
+ fi
+ create_new_session="Create New Session"
+ ID="$ID\n${create_new_session}:"
+ ID="`echo $ID | fzf | cut -d: -f1`"
+ if [[ "$ID" = "${create_new_session}" ]]; then
+ tmux new-session
+ elif [[ -n "$ID" ]]; then
+ printf '\033]777;tabbedx;set_tab_name;%s\007' "$ID"
+ tmux attach-session -t "$ID"
+ else
+ : # Start terminal normally
+ fi
+ fi
+}
+
+# +-------+
+# | Other |
+# +-------+
+
+# List install files for dotfiles
+fdot() {
+ file=$(find "$DOTFILES/install" -exec basename {} ';' | sort | uniq | nl | fzf | cut -f 2)
+ [ -n "$file" ] && "$EDITOR" "$DOTFILES/install/$file"
+}
+
+# List projects
+fwork() {
+ result=$(find ~/workspace/* -type d -prune -exec basename {} ';' | sort | uniq | nl | fzf | cut -f 2)
+ [ -n "$result" ] && cd ~/workspace/$result
+}
+
+# Open pdf with Zathura
+fpdf() {
+ result=$(find -type f -name '*.pdf' | fzf --bind "ctrl-r:reload(find -type f -name '*.pdf')" --preview "pdftotext {} - | less")
+ [ -n "$result" ] && nohup zathura "$result" &> /dev/null & disown
+}
+
+# Open epubs with Zathura
+fepub() {
+ result=$(find -type f -name '*.epub' | fzf --bind "ctrl-r:reload(find -type f -name '*.epub')")
+ [ -n "$result" ] && nohup zathura "$result" &> /dev/null & disown
+}
+
+# Search and find directories in the dir stack
+fpop() {
+ # Only work with alias d defined as:
+
+ # alias d='dirs -v'
+ # for index ({1..9}) alias "$index"="cd +${index}"; unset index
+
+ d | fzf --height="20%" | cut -f 1 | source /dev/stdin
+}
+
+#ip() {
+# emulate -LR zsh
+#
+# if [[ $1 == 'get' ]]; then
+# res=$(curl -s ipinfo.io/ip)
+# echo -n $res | xsel --clipboard
+# echo "copied $res to clipboard"
+# # only run ip if it exists
+# elif (( $+commands[ip] )); then
+# command ip $*
+# fi
+#}
+
+ssh-create() {
+ if [ ! -z "$1" ]; then
+ ssh-keygen -f $HOME/.ssh/$1 -t rsa -N '' -C "$1"
+ chmod 700 $HOME/.ssh/$1*
+ fi
+}
+
+guest() {
+ local guest="$1"
+ shift
+
+ local port
+ if [[ "$#" -ge 2 && "${@: -1}" =~ ^[0-9]+$ ]]; then
+ port="${@: -1}"
+ set -- "${@:1:$(($#-1))}"
+ fi
+
+ if [[ -z "$guest" || "$#" -lt 1 ]]; then
+ echo "Send file(s) or directories to remote machine"
+ echo "Usage: guest <guest-alias> <file-or-directory>... [port]"
+ return 1
+ fi
+
+ # Auto-detect port
+ if [[ -z "$port" ]]; then
+ if nc -z localhost 22220 2>/dev/null; then
+ port=22220
+ elif nc -z localhost 22 2>/dev/null; then
+ port=22
+ else
+ echo "No known SSH port (22220 or 22) is open. Specify a port manually."
+ return 1
+ fi
+ fi
+
+ for src in "$@"; do
+ src="${src/#\~/$HOME}"
+ if [[ ! -e "$src" ]]; then
+ echo "Error: '$src' does not exist."
+ continue
+ fi
+
+ local abs_path dest_dir rel_dir rsync_src rsync_dest
+
+ abs_path=$(realpath "$src")
+ rel_dir="${abs_path#$HOME/}"
+ dest_dir=$(dirname "$rel_dir")
+
+ # Ensure target dir exists remotely
+ ssh -p "$port" "$guest" "mkdir -p ~/$dest_dir"
+
+ if [[ -d "$src" ]]; then
+ # Add trailing slash to copy contents instead of nesting the dir
+ rsync_src="${src%/}/"
+ rsync_dest="~/$rel_dir/"
+ else
+ rsync_src="$src"
+ rsync_dest="~/$dest_dir/"
+ fi
+
+ echo "Sending '$src' to '$guest:$rsync_dest'..."
+ rsync -avz -e "ssh -p $port" "$rsync_src" "$guest:$rsync_dest"
+ done
+}
+historystat() {
+ history 0 | awk '{print $2}' | sort | uniq -c | sort -n -r | head
+}
+
+promptspeed() {
+ for i in $(seq 1 10); do /usr/bin/time zsh -i -c exit; done
+}
+
+
+matrix() {
+ local lines=$(tput lines)
+ cols=$(tput cols)
+
+ # Check if tmux is available
+ if command -v tmux > /dev/null; then
+ # Save the current status setting
+ local status_setting=$(tmux show -g -w -v status)
+
+ # Turn off tmux status
+ tmux set -g status off
+ else
+ echo "tmux is not available. Exiting."
+ return 1
+ fi
+
+ # Function to restore terminal state
+ restore_terminal() {
+ # Clear the screen
+ clear
+
+ # Bring back tmux status to its original setting
+ if command -v tmux > /dev/null; then
+ tmux set -g status "$status_setting"
+ fi
+ }
+
+ trap 'restore_terminal' INT
+
+ awkscript='
+ {
+ letters="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@#$%^&*()"
+ lines=$1
+ random_col=$3
+ c=$4
+ letter=substr(letters,c,1)
+ cols[random_col]=0;
+ for (col in cols) {
+ line=cols[col];
+ cols[col]=cols[col]+1;
+ printf "\033[%s;%sH\033[2;32m%s", line, col, letter;
+ printf "\033[%s;%sH\033[1;37m%s\033[0;0H", cols[col], col, letter;
+ if (cols[col] >= lines) {
+ cols[col]=0;
+ }
+ }
+ }
+ '
+
+ echo -e "\e[1;40m"
+ clear
+
+ while :; do
+ echo $lines $cols $(( $RANDOM % $cols)) $(( $RANDOM % 72 ))
+ sleep 0.05
+ done | awk "$awkscript"
+
+ # Restore terminal state
+ restore_terminal
+}
+
+## Reload shell
+function reload() {
+ local compdump_files="$ZDOTDIR/.zcompdump*"
+
+ if ls $compdump_files &> /dev/null; then
+ rm -f $compdump_files
+ fi
+
+ exec $SHELL -l
+}
+## Generate a secure password
+function passgen() {
+ LC_ALL=C tr -dc ${1:-"[:graph:]"} < /dev/urandom | head -c ${2:-20}
+}
+## Encode/Decode string using base64
+function b64e() {
+ echo "$@" | base64
+}
+
+function b64d() {
+ echo "$@" | base64 -D
+}
+# Search through all man pages
+function fman() {
+ man -k . | fzf -q "$1" --prompt='man> ' --preview $'echo {} | tr -d \'()\' | awk \'{printf "%s ", $2} {print $1}\' | xargs -r man' | tr -d '()' | awk '{printf "%s ", $2} {print $1}' | xargs -r man
+}
+# Back up a file. Usage "backupthis <filename>"
+backupthis() {
+ cp -riv $1 ${1}-$(date +%Y%m%d%H%M).backup;
+}
+
+# Spawn a clone of current terminal
+putstate () {
+ declare +x >~/environment.tmp
+ declare -x >>~/environment.tmp
+ echo cd "$PWD" >>~/environment.tmp
+}
+
+getstate () {
+ . ~/environment.tmp
+}
+
+
+# Tmux layout
+openSession () {
+ tmux split-window -h -t
+ tmux split-window -v -t
+ tmux resize-pane -U 5
+}
+
+# archive compress
+compress() {
+ if [[ -n "$1" ]]; then
+ local file=$1
+ shift
+ case "$file" in
+ *.tar ) tar cf "$file" "$*" ;;
+ *.tar.bz2 ) tar cjf "$file" "$*" ;;
+ *.tar.gz ) tar czf "$file" "$*" ;;
+ *.tgz ) tar czf "$file" "$*" ;;
+ *.zip ) zip "$file" "$*" ;;
+ *.rar ) rar "$file" "$*" ;;
+ * ) tar zcvf "$file.tar.gz" "$*" ;;
+ esac
+ else
+ echo 'usage: compress <foo.tar.gz> ./foo ./bar'
+ fi
+}
+
+extract() {
+ if [[ -f "$1" ]] ; then
+ local filename
+ filename=$(basename "$1")
+ local foldername="${filename%%.*}"
+ local fullpath
+ fullpath=$(perl -e 'use Cwd "abs_path";print abs_path(shift)' "$1")
+ local didfolderexist=false
+
+ if [[ -d "$foldername" ]]; then
+ didfolderexist=true
+ read -p "$foldername already exists, do you want to overwrite it? (y/n) " -n 1
+ echo
+ if [[ "$REPLY" =~ ^[Nn]$ ]]; then
+ return
+ fi
+ fi
+
+ mkdir -p "$foldername" && cd "$foldername" || return
+
+ case "$1" in
+ *.tar.bz2) tar xjf "$fullpath" ;;
+ *.tar.gz) tar xzf "$fullpath" ;;
+ *.tar.xz) tar Jxf "$fullpath" ;;
+ *.tar.Z) tar xzf "$fullpath" ;;
+ *.tar) tar xf "$fullpath" ;;
+ *.taz) tar xzf "$fullpath" ;;
+ *.tb2) tar xjf "$fullpath" ;;
+ *.tbz) tar xjf "$fullpath" ;;
+ *.tbz2) tar xjf "$fullpath" ;;
+ *.tgz) tar xzf "$fullpath" ;;
+ *.txz) tar Jxf "$fullpath" ;;
+ *.rar) unrar x -o+ "$fullpath" >/dev/null ;;
+ *.zip) unzip -o "$fullpath" ;;
+ *)
+ echo "'$1' cannot be extracted via extract()" \
+ && cd .. \
+ && ! "$didfolderexist" \
+ && rm -r "$foldername"
+ ;;
+ esac
+ else
+ echo "'$1' is not a valid file"
+ fi
+}
+
+ports() {
+ local result
+ result=$(sudo netstat -tulpn | grep LISTEN)
+ echo "$result" | fzf
+}
+
+trash() {
+ case "$1" in
+ --list)
+ ls -A1 ~/.local/share/Trash/files/
+ ;;
+ --empty)
+ ls -A1 ~/.local/share/Trash/files/ && \rm -rfv ~/.local/share/Trash/files/*
+ ;;
+ --restore)
+ gio trash --restore "$(gio trash --list | fzf | cut -f 1)"
+ ;;
+ --delete)
+ trash_files=$(ls -A ~/.local/share/Trash/files/ | fzf --multi); echo $trash_files | xargs -I {} rm -rf ~/.local/share/Trash/files/{}
+ ;;
+ *)
+ gio trash "$@"
+ ;;
+ esac
+}
+
+what() {
+ type "$1"
+ echo "$PATH"
+}
+
+shutdown() {
+ if [ "$#" -eq 0 ]; then
+ sudo /sbin/shutdown -h now
+ else
+ sudo /sbin/shutdown -h "$@"
+ fi
+}
+
+windowManagerName () {
+ local window=$(
+ xprop -root -notype
+ )
+
+ local identifier=$(
+ echo "${window}" |
+ awk '$1=="_NET_SUPPORTING_WM_CHECK:"{print $5}'
+ )
+
+ local attributes=$(
+ xprop -id "${identifier}" -notype -f _NET_WM_NAME 8t
+ )
+
+ local name=$(
+ echo "${attributes}" |
+ grep "_NET_WM_NAME = " |
+ cut --delimiter=' ' --fields=3 |
+ cut --delimiter='"' --fields=2
+ )
+
+ echo "${name}"
+}
+
+logout() {
+ local wm
+ wm="$(windowManagerName)"
+ if [[ -n "$wm" ]]; then
+ echo "Logging out by killing window manager: $wm"
+ pkill "$wm"
+ else
+ echo "No window manager detected!" >&2
+ fi
+}
+
+# Gentoo
+emg() {
+ if [[ -z "$1" ]]; then
+ echo "Usage: emg [USE_FLAGS] package [package...]"
+ return 1
+ fi
+
+ if [[ "$1" =~ ^[^-].* ]]; then
+ local use_flags="$1"
+ shift
+ sudo USE="$use_flags" emerge -av "$@"
+ else
+ sudo emerge -av "$@"
+ fi
+}
+
+# Remove command from history
+forget () { # Accepts one history line number as argument or search term
+ if [[ -z "$1" ]]; then
+ echo "Usage: hist <history_number> | hist -s <search_term>"
+ return 1
+ fi
+
+ if [[ "$1" == "-s" ]]; then
+ if [[ -z "$2" ]]; then
+ echo "Usage: hist -s <search_term>"
+ return 1
+ fi
+
+ local search_term="$2"
+
+ if [[ "$OSTYPE" == "linux-gnu"* ]]; then
+ LC_ALL=C sed -i "/${search_term}/d" "$HISTFILE" # GNU sed
+ else
+ LC_ALL=C sed -i '' "/${search_term}/d" "$HISTFILE" # BSD/macOS sed
+ fi
+
+ fc -R "$HISTFILE"
+ echo "Deleted all history entries matching '$search_term'."
+ else
+ local num=$1
+ local cmd=$(fc -ln $num $num 2>/dev/null)
+
+ if [[ -z "$cmd" ]]; then
+ echo "No history entry found for index $num"
+ return 1
+ fi
+
+ history -d $num
+
+ local escaped_cmd=$(echo "$cmd" | sed 's/[\/&]/\\&/g')
+
+ if [[ "$OSTYPE" == "linux-gnu"* ]]; then
+ LC_ALL=C sed -i "/${escaped_cmd}/d" "$HISTFILE"
+ else
+ LC_ALL=C sed -i '' "/${escaped_cmd}/d" "$HISTFILE"
+ fi
+
+ fc -R "$HISTFILE"
+ echo "Deleted '$cmd' from history."
+ fi
+}
+
+# Remove hist command itself
+remove_hist_command() {
+ [[ $1 != 'hist '* ]]
+}
+
+remove_hist_command
+
+
+search() {
+ # Search for a pattern in the specified directory (non-recursive).
+ dir="${1:-.}"
+ ls -1 "$dir" | grep -i "$2"
+}
+
+deepsearch() {
+ # Perform a recursive search for a pattern in the specified directory.
+ dir="${1:-.}"
+ find "$dir" -iname "$2"
+}
+
+notes() {
+ local base_dir="$HOME/documents/main/"
+
+ if [[ -z "$1" ]]; then
+ # No argument → cd to notes directory
+ cd "$base_dir" || return
+ return
+ fi
+
+ local target="$1" # The argument itself
+
+ # Use find to check if the file exists anywhere in the base directory
+ local found_files=($(find "$base_dir" -type f -name "$target"))
+
+ if [[ ${#found_files[@]} -eq 1 ]]; then
+ # Only one match found, open it directly
+ $EDITOR "${found_files[0]}"
+ elif [[ ${#found_files[@]} -gt 1 ]]; then
+ # Multiple files found, prompt the user to select one
+ echo "Multiple files found for '$target'. Please choose one:"
+ PS3="Please enter a number to select a file (1-${#found_files[@]}): "
+ select selected_file in "${found_files[@]}"; do
+ if [[ -n "$selected_file" ]]; then
+ $EDITOR "$selected_file"
+ break
+ else
+ echo "Invalid selection, try again."
+ fi
+ done
+ else
+ # If no match found, search for a directory
+ local found_dir=$(find "$base_dir" -type d -name "$target" -print -quit)
+
+ if [[ -n "$found_dir" ]]; then
+ # Directory found, cd into it
+ cd "$found_dir" || return
+ else
+ # If no match found, create the file and open it
+ local full_target="$base_dir/$target"
+ mkdir -p "$(dirname "$full_target")"
+ $EDITOR "$full_target"
+ fi
+ fi
+}
+
+# Enable tab completion for files and directories
+_notes_complete() {
+ local base_dir="$HOME/documents/main"
+ compadd -o nospace -- $(find "$base_dir" -type f -o -type d -printf '%P\n')
+}
+
+compdef _notes_complete notes
+
+
+ship() {
+ local binary_dir="$HOME/.local/share"
+ local bin_symlink_dir="$HOME/.local/bin"
+ local project_dirs=(
+ "$HOME/projects/"
+ "$HOME/src/"
+ "$HOME/src/site/"
+ )
+
+ mkdir -p "$binary_dir" "$bin_symlink_dir"
+
+ local project_dir=""
+
+ if [[ -n "$1" ]]; then
+ # Project name specified
+ for dir in "${project_dirs[@]}"; do
+ if [[ -d "$dir/$1" ]]; then
+ project_dir="$dir/$1"
+ break
+ fi
+ done
+
+ if [[ -z "$project_dir" ]]; then
+ echo "Project '$1' not found."
+ return 1
+ fi
+ else
+ # No argument: pick latest edited
+ local bin_file
+ bin_file=$(find "${project_dirs[@]}" -type f -name "Cargo.toml" -exec stat --format="%Y %n" {} \; 2>/dev/null | sort -nr | head -n1 | cut -d' ' -f2-)
+
+ if [[ -z "$bin_file" ]]; then
+ echo "No Cargo.toml found."
+ return 1
+ fi
+
+ project_dir=$(dirname "$bin_file")
+ fi
+
+ cd "$project_dir" || return
+ echo "Building project in $project_dir..."
+
+ # Build it
+ cargo build --release || { echo "Build failed"; return 1; }
+
+ # Assume binary has same name as project dir
+ local binary_name
+ binary_name=$(basename "$project_dir")
+ local built_binary="target/release/$binary_name"
+
+ if [[ -x "$built_binary" ]]; then
+ echo "Copying $built_binary to $binary_dir/$binary_name"
+ cp "$built_binary" "$binary_dir/$binary_name"
+
+ # Create/Update symlink
+ local symlink_path="$bin_symlink_dir/$binary_name"
+ ln -sf "$binary_dir/$binary_name" "$symlink_path"
+
+ echo "Binary is now at: $binary_dir/$binary_name"
+ echo "Symlink created at: $symlink_path"
+ else
+ echo "Built binary not found: $built_binary"
+ echo "You may need to manually specify the output binary."
+ fi
+}
+
+
+forge() {
+
+ local install=no
+ local usage="Usage: forge [--install]"
+
+ # Handle --install flag
+ if [[ "$1" == "--install" ]]; then
+ install=yes
+ shift
+ elif [[ "$1" == "-h" || "$1" == "--help" ]]; then
+ echo "$usage"
+ return 0
+ fi
+
+ if [[ -f "CMakeLists.txt" ]]; then
+ echo "📦 CMake project detected"
+ [[ ! -d build ]] && mkdir build
+ cmake -B build -DCMAKE_BUILD_TYPE=Release || return 1
+ cmake --build build || return 1
+ [[ "$install" == "yes" ]] && sudo cmake --install build
+
+ elif [[ -f "meson.build" ]]; then
+ echo "📦 Meson project detected"
+ if [[ ! -d build ]]; then
+ meson setup build || return 1
+ fi
+ ninja -C build || return 1
+ [[ "$install" == "yes" ]] && sudo ninja -C build install
+
+ elif [[ -f "Makefile" ]]; then
+ echo "📦 Makefile project detected"
+ # Try `make all`, fallback to `make` if `all` fails
+ if make -q all 2>/dev/null; then
+ make all || return 1
+ else
+ make || return 1
+ fi
+ [[ "$install" == "yes" ]] && sudo make install
+
+ else
+ echo "❌ No supported build system found."
+ return 1
+ fi
+}
+
+# Windows Path:
+windows_home() {
+ for dir in /mnt/windows/Users/*(N); do
+ base=${dir:t} # `:t` is zsh's "tail" = basename
+ if [[ -d $dir && ! $base =~ ^(All Users|Default|Default User|Public|nx|desktop.ini)$ ]]; then
+ echo "$dir"
+ return 0
+ fi
+ done
+ return 1
+}
+
+if winhome_path=$(windows_home); then
+ hash -d winhome="$winhome_path"
+fi
+
+# Allow nnn filemanager to cd on quit
+nnn() {
+ declare -x +g NNN_TMPFILE=$(mktemp --tmpdir $0.XXXX)
+ trap "command rm -f $NNN_TMPFILE" EXIT
+ =nnn $@
+ [ -s $NNN_TMPFILE ] && source $NNN_TMPFILE
+}
diff --git a/common/config/zsh/user/options.zsh b/common/config/zsh/user/options.zsh
new file mode 100644
index 0000000..99840d7
--- /dev/null
+++ b/common/config/zsh/user/options.zsh
@@ -0,0 +1,66 @@
+# Recursion limits
+FUNCNEST=999
+
+DISABLE_MAGIC_FUNCTIONS=true
+
+# Enable various options for Zsh behavior
+setopt interactive_comments # Allow comments to appear in interactive mode
+unsetopt BEEP # Disable the system beep (to prevent annoying beeps)
+setopt extendedglob # Enable extended globbing for complex pattern matching
+setopt nomatch # Prevent errors when a glob pattern doesn't match any files
+setopt notify # Notify when background jobs complete
+setopt completeinword # Allow tab completion within words
+setopt prompt_subst # Allow prompt variables to be substituted
+
+# Enable automatic directory navigation
+setopt autocd # Automatically change to a directory if the directory name is typed alone
+setopt AUTO_PUSHD # Save more directory history, and use "cd -" with tab completion
+
+# Hide history of commands starting with a space
+setopt histignorespace # Do not save commands that start with a space in the history
+
+
+# --- Detect terminal control characters and behavior ---
+
+# Set these to true/false to run on every new tmux/terms
+: ${CHECK_ON_TMUX_CHANGES:=false}
+: ${CHECK_ON_NEW_INSTANCES:=false}
+
+# Fast terminal fingerprinting for optimizing prompt rendering
+function initialize_terminal_fingerprint() {
+ # --- Fast terminal fingerprint ---
+ TERM_BASIC="$TERM-$COLORTERM"
+ TERM_TMUX=""
+ [[ "$CHECK_ON_TMUX_CHANGES" == "true" && -n "$TMUX" ]] && TERM_TMUX="-tmux$TMUX_PANE"
+ TERM_INSTANCE=""
+ [[ "$CHECK_ON_NEW_INSTANCES" == "true" ]] && TERM_INSTANCE="-$$"
+ # Combine fingerprint parts only if they're non-empty (faster than function call)
+ CURRENT_TERM_FINGERPRINT="${TERM_BASIC}${TERM_TMUX}${TERM_INSTANCE}"
+ # Only run detection if terminal has changed (single comparison)
+ if [[ "$CURRENT_TERM_FINGERPRINT" != "$LAST_TERM_FINGERPRINT" ]]; then
+ export LAST_TERM_FINGERPRINT="$CURRENT_TERM_FINGERPRINT"
+ # Fast reset
+ export CTRL_C_SIGINT=false
+ export CTRL_V_PASTE=false
+ # Fast SIGINT check (single command, no pipes)
+ INTR_CHAR=$(stty -a 2>/dev/null | sed -n 's/.*intr = \([^;]*\);.*/\1/p' | tr -d ' ')
+ [[ "$INTR_CHAR" == "^C" ]] && export CTRL_C_SIGINT=true
+ # Check if Ctrl+V is bound to lnext terminal function
+ LNEXT_CHAR=$(stty -a 2>/dev/null | sed -n 's/.*lnext = \([^;]*\);.*/\1/p' | tr -d ' ')
+ # If lnext is NOT ^V, then Ctrl+V might work as paste
+ if [[ "$LNEXT_CHAR" != "^V" ]]; then
+ # Check if clipboard tools exist
+ if [[ -n "$WAYLAND_DISPLAY" && -x "$(command -v wl-paste)" ]]; then
+ export CTRL_V_PASTE=true
+ elif [[ -n "$DISPLAY" && -x "$(command -v xclip)" ]]; then
+ export CTRL_V_PASTE=true
+ fi
+ fi
+ # Print status only if debug is enabled
+ [[ -n "$DEBUG_TERM_DETECT" ]] && echo "Terminal: CTRL_C_SIGINT=$CTRL_C_SIGINT CTRL_V_PASTE=$CTRL_V_PASTE"
+ fi
+}
+
+# Initialize terminal fingerprint on startup
+initialize_terminal_fingerprint
+
diff --git a/common/config/zsh/user/prompt.zsh b/common/config/zsh/user/prompt.zsh
new file mode 100644
index 0000000..c55a835
--- /dev/null
+++ b/common/config/zsh/user/prompt.zsh
@@ -0,0 +1,679 @@
+#!/bin/zsh
+
+########## Prompt(s) ##########
+
+# Autoload necessary functions for vcs_info and coloring
+autoload -Uz vcs_info
+autoload -Uz add-zsh-hook
+autoload -U colors && colors
+
+# Enable prompt substitution
+setopt prompt_subst
+
+# Display git branch status and color
+precmd_vcs_info() { vcs_info }
+
+# Add vcs_info to precmd functions
+precmd_functions+=( precmd_vcs_info )
+
+# Manipulates cursor position: moves down by 2 lines, saves position, and restores cursor after an operation.
+terminfo_down_sc=$terminfo[cud1]$terminfo[cuu1]$terminfo[sc]$terminfo[cud1]
+
+# Track last executed command for exit code display
+typeset -g _last_executed_command=""
+typeset -g _cmd_start_time=0
+typeset -g _cmd_end_time=0
+typeset -g _cmd_duration=0
+typeset -g _spinner_idx=0
+typeset -ga _spinner_frames=('⣾' '⣽' '⣻' '⢿' '⡿' '⣟' '⣯' '⣷')
+typeset -g _cmd_is_running=0
+typeset -g _show_spinner=0
+typeset -g _SPINNER_DELAY=5 # Only show spinner after 5 seconds
+typeset -g _FINISHED_DELAY=10 # Only show finished message after 10 seconds
+
+# Register the ZLE widget for spinner updates - do this early
+zle -N update_spinner
+
+# Cache git information to avoid repeated expensive operations
+typeset -g _git_cached_info=""
+typeset -g _git_cache_timestamp=0
+typeset -g _git_cache_lifetime=2 # seconds before cache expires
+
+# Calculate how much space is available for the prompt components
+function available_space() {
+ local width=${COLUMNS:-80}
+ echo $width
+}
+
+# Check if we need to abbreviate git info
+function need_to_abbreviate_git() {
+ local available=$(available_space)
+ local vi_mode_len=13 # Length of "-- INSERT --"
+ local prompt_base_len=20 # Base prompt elements length
+ local path_len=${#${PWD/#$HOME/\~}}
+ local git_full_len=0
+
+ # Try to estimate git info length if available
+ if git rev-parse --is-inside-work-tree &>/dev/null; then
+ local branch=$(git symbolic-ref --short HEAD 2>/dev/null)
+ git_full_len=${#branch}
+
+ # Add length for status indicators
+ if [[ -n "$(git status --porcelain)" ]]; then
+ # Rough estimate for status text
+ git_full_len=$((git_full_len + 20))
+ fi
+ fi
+
+ # Calculate total space needed
+ local total_needed=$((vi_mode_len + prompt_base_len + path_len + git_full_len))
+
+ # Determine if we need to abbreviate
+ if [[ $total_needed -gt $available ]]; then
+ return 0 # Need to abbreviate
+ else
+ return 1 # Don't need to abbreviate
+ fi
+}
+
+# Custom git branch coloring based on status
+git_branch_test_color() {
+ local now=$(date +%s)
+ local cache_age=$((now - _git_cache_timestamp))
+
+ # Use cached value if available and not expired
+ if [[ -n "$_git_cached_info" && $cache_age -lt $_git_cache_lifetime ]]; then
+ echo "$_git_cached_info"
+ return
+ fi
+
+ local ref=$(git symbolic-ref --short HEAD 2> /dev/null)
+ if [ -n "${ref}" ]; then
+ if [ -n "$(git status --porcelain)" ]; then
+ local gitstatuscolor='%F{green}'
+ else
+ local gitstatuscolor='%F{82}'
+ fi
+ _git_cached_info="${gitstatuscolor}${ref}"
+ _git_cache_timestamp=$now
+ echo "$_git_cached_info"
+ else
+ _git_cached_info=""
+ _git_cache_timestamp=$now
+ echo ""
+ fi
+}
+
+# Git branch with dynamic abbreviation
+git_branch_dynamic() {
+ local now=$(date +%s)
+ local cache_age=$((now - _git_cache_timestamp))
+
+ # Only query git if cache is expired
+ if [[ $cache_age -ge $_git_cache_lifetime ]]; then
+ local ref=$(git symbolic-ref --short HEAD 2> /dev/null)
+ if [ -n "${ref}" ]; then
+ if need_to_abbreviate_git; then
+ # Abbreviated version for small terminals
+ case "${ref}" in
+ "main") _git_cached_info="m" ;;
+ "master") _git_cached_info="m" ;;
+ "development") _git_cached_info="d" ;;
+ "develop") _git_cached_info="d" ;;
+ "feature/"*) _git_cached_info="f/${ref#feature/}" | cut -c 1-4 ;;
+ "release/"*) _git_cached_info="r/${ref#release/}" | cut -c 1-4 ;;
+ "hotfix/"*) _git_cached_info="h/${ref#hotfix/}" | cut -c 1-4 ;;
+ *) _git_cached_info="${ref}" | cut -c 1-5 ;; # Truncate to first 5 chars for other branches
+ esac
+ else
+ # Full branch name when there's room
+ _git_cached_info="${ref}"
+ fi
+ _git_cache_timestamp=$now
+ echo "$_git_cached_info"
+ else
+ _git_cached_info=""
+ _git_cache_timestamp=$now
+ echo ""
+ fi
+ else
+ echo "$_git_cached_info"
+ fi
+}
+
+# VCS info styles (e.g., git)
+zstyle ':vcs_info:*' check-for-changes true
+zstyle ':vcs_info:*' enable git
+
+# Dynamically configure vcs_info formats based on available space
+function configure_vcs_styles() {
+ if need_to_abbreviate_git; then
+ # Abbreviated versions
+ zstyle ':vcs_info:*' stagedstr ' +%F{15}s%f'
+ zstyle ':vcs_info:*' unstagedstr ' -%F{15}u%f'
+ else
+ # Full versions
+ zstyle ':vcs_info:*' stagedstr ' +%F{15}staged%f'
+ zstyle ':vcs_info:*' unstagedstr ' -%F{15}unstaged%f'
+ fi
+
+ zstyle ':vcs_info:*' actionformats '%F{5}%F{2}%b%F{3}|%F{1}%a%F{5}%f '
+ zstyle ':vcs_info:*' formats '%F{208} '$'\uE0A0'' %f$(git_branch_test_color)%f%F{76}%c%F{3}%u%f '
+ zstyle ':vcs_info:git*+set-message:*' hooks git-untracked git-dynamic
+}
+
+# Show "untracked" status in git - with conditional abbreviation
++vi-git-untracked() {
+ if [[ $(git rev-parse --is-inside-work-tree 2> /dev/null) == 'true' ]] && \
+ git status --porcelain | grep '??' &> /dev/null ; then
+
+ if need_to_abbreviate_git; then
+ hook_com[unstaged]+='%F{196} !%f%F{15}u%f'
+ else
+ hook_com[unstaged]+='%F{196} !%f%F{15}untracked%f'
+ fi
+ fi
+}
+
+# Dynamic git branch hook
++vi-git-dynamic() {
+ hook_com[branch]=$(git_branch_dynamic)
+}
+
+# SSH info with conditional abbreviation
+ssh_name() {
+ if [[ -n $SSH_CONNECTION ]]; then
+ local ssh_info
+
+ if need_to_abbreviate_git; then
+ # Abbreviated SSH info
+ ssh_info="ssh:%F{green}%n$nc%f"
+ else
+ ssh_info="ssh:%F{green}%n$nc%f"
+ if [[ -n $SSH_CONNECTION ]]; then
+ local ip_address
+ ip_address=$(echo $SSH_CONNECTION | awk '{print $3}')
+ ssh_info="$ssh_info@%F{green}$ip_address%f"
+ fi
+ fi
+ echo " ${ssh_info}"
+ fi
+}
+
+# Job names (for job control) with conditional abbreviation
+function job_name() {
+ job_name=""
+ job_length=0
+ local available=$(available_space)
+
+ # Only show jobs if we have reasonable space
+ if [ "${available}" -gt 60 ]; then
+ local job_count=$(jobs | wc -l)
+ if [ "${job_count}" -gt 0 ]; then
+ if need_to_abbreviate_git; then
+ job_name+="%F{green}j:${job_count}%f"
+ else
+ local title_jobs="jobs:"
+ job_name="${title_jobs}"
+ job_length=$((${available}-70))
+ [ "${job_length}" -lt "0" ] && job_length=0
+
+ if [ "${job_length}" -gt 0 ]; then
+ job_name+="%F{green}$(jobs | grep + | tr -s " " | cut -d " " -f 4- | cut -b 1-${job_length} | sed "s/\(.*\)/\1/")%f"
+ else
+ job_name+="%F{green}${job_count}%f"
+ fi
+ fi
+ fi
+ fi
+
+ echo "${job_name}"
+}
+
+# Check if we should show the spinner based on elapsed time
+function should_show_spinner() {
+ if [[ $_cmd_is_running -eq 1 ]]; then
+ local current_time=$(date +%s)
+ local elapsed=$((current_time - _cmd_start_time))
+
+ # Show spinner only after delay threshold
+ if [[ $elapsed -ge $_SPINNER_DELAY ]]; then
+ _show_spinner=1
+ return 0 # Yes, show spinner
+ fi
+ fi
+
+ _show_spinner=0
+ return 1 # No, don't show spinner
+}
+
+# Update spinner animation - simplified version
+function update_spinner() {
+ # This function is now just a ZLE widget placeholder
+ # The actual spinner updates happen in the TRAPALRM handler
+ :
+}
+
+# Start spinner timer when command runs longer than threshold
+function start_spinner_timer() {
+ _spinner_idx=0
+ _cmd_is_running=1
+ _show_spinner=0 # Start with spinner hidden until delay passes
+
+ # Set up the TRAPALRM for periodic updates - CRITICAL FIX
+ TMOUT=0.5 # Update spinner every 0.5 seconds
+
+ # Define TRAPALRM function - this is key to the spinner working
+ TRAPALRM() {
+ if [[ $_cmd_is_running -eq 1 ]]; then
+ local current_time=$(date +%s)
+ local elapsed=$((current_time - _cmd_start_time))
+
+ # Show spinner only after delay threshold
+ if [[ $elapsed -ge $_SPINNER_DELAY ]]; then
+ _show_spinner=1
+ _spinner_idx=$(( (_spinner_idx + 1) % ${#_spinner_frames[@]} ))
+
+ # Force prompt refresh - critical for updating the spinner
+ if [[ -o zle ]]; then
+ zle reset-prompt 2>/dev/null || true
+ zle -R
+ fi
+ fi
+ fi
+ }
+}
+
+# Stop spinner when command finishes
+function stop_spinner_timer() {
+ _cmd_is_running=0
+ _show_spinner=0
+
+ # Disable the alarm trap and timer
+ TRAPALRM() { : }
+ TMOUT=0
+
+ # Force prompt refresh to clear spinner
+ if [[ -o zle ]]; then
+ zle reset-prompt 2>/dev/null || true
+ zle -R
+ fi
+}
+
+# Format time in a human-readable way
+function format_time() {
+ local seconds=$1
+ local result=""
+
+ # Format time as hours:minutes:seconds for long durations
+ if [[ $seconds -ge 3600 ]]; then
+ local hours=$((seconds / 3600))
+ local minutes=$(( (seconds % 3600) / 60 ))
+ local secs=$((seconds % 60))
+ result="${hours}h${minutes}m${secs}s"
+ elif [[ $seconds -ge 60 ]]; then
+ local minutes=$((seconds / 60))
+ local secs=$((seconds % 60))
+ result="${minutes}m${secs}s"
+ else
+ result="${seconds}s"
+ fi
+
+ echo "$result"
+}
+
+# Error code display for RPROMPT with spinner - fixed version
+function exit_code_info() {
+ local exit_code=$?
+
+ # If a command is running and we should show spinner
+ if [[ $_cmd_is_running -eq 1 && $_show_spinner -eq 1 ]]; then
+ local spinner=${_spinner_frames[$_spinner_idx]}
+ local current_time=$(date +%s)
+ local elapsed=$((current_time - _cmd_start_time))
+ echo "%F{yellow}${spinner} ${elapsed}s%f"
+ return
+ fi
+
+ # Don't show error code when line editor is active (user is typing)
+ if [[ -o zle ]]; then
+ echo ""
+ return
+ fi
+
+ # Show command finished message for completed commands that took longer than threshold
+ if [[ -n "$_last_executed_command" && $_cmd_duration -ge $_FINISHED_DELAY ]]; then
+ local duration_formatted=$(format_time $_cmd_duration)
+
+ # Show error code along with finished message if there was an error
+ if [[ $exit_code -ne 0 ]]; then
+ # Show TSTP (148) as a suspension indicator instead of error
+ if [[ $exit_code -eq 148 ]]; then
+ echo "%F{cyan}finished ${duration_formatted}%f %F{yellow}⏸ TSTP%f"
+ return
+ fi
+
+ local signal_name=""
+ # Check if it's a signal
+ if [[ $exit_code -gt 128 && $exit_code -le 165 ]]; then
+ local signal_num=$((exit_code - 128))
+ signal_name=$(kill -l $signal_num 2>/dev/null)
+ if [[ -n "$signal_name" ]]; then
+ signal_name=" ($signal_name)"
+ fi
+ fi
+
+ # Return formatted error code with finished message
+ echo "%F{cyan}finished ${duration_formatted}%f %F{red}✘ $exit_code$signal_name%f"
+ else
+ echo "%F{cyan}finished ${duration_formatted}%f %F{green}✓%f"
+ fi
+ return
+ fi
+
+ # Don't show anything for exit code 0 (success) if this is first command
+ if [[ -z "$_last_executed_command" && $exit_code -eq 0 ]]; then
+ echo ""
+ return
+ fi
+
+ # Show TSTP (148) as a suspension indicator instead of error
+ if [[ $exit_code -eq 148 ]]; then
+ echo "%F{yellow}⏸ TSTP%f"
+ return
+ fi
+
+ if [[ $exit_code -ne 0 ]]; then
+ local signal_name=""
+
+ # Check if it's a signal
+ if [[ $exit_code -gt 128 && $exit_code -le 165 ]]; then
+ local signal_num=$((exit_code - 128))
+ signal_name=$(kill -l $signal_num 2>/dev/null)
+ if [[ -n "$signal_name" ]]; then
+ signal_name=" ($signal_name)"
+ fi
+ fi
+
+ # Return formatted error code
+ echo "%F{red}✘ $exit_code$signal_name%f"
+ else
+ echo "%F{green}✓%f" # Success indicator
+ fi
+}
+
+abbreviated_path() {
+ local full_path="${PWD/#$HOME/~}" # Replace $HOME with ~
+ local available=$(available_space)
+
+ # If path is root
+ if [[ "$full_path" == "/" ]]; then
+ echo "%F{4}/%f"
+ return
+ fi
+
+ # If path is just ~
+ if [[ "$full_path" == "~" ]]; then
+ echo "%F{4}~%f"
+ return
+ fi
+
+ # If extremely small terminal, show nothing to avoid breaking prompt
+ if (( available < 20 )); then
+ echo ""
+ return
+ fi
+
+ # For very narrow terminals, just show the current dir
+ if (( available < 30 )); then
+ echo "%F{4}%1~%f"
+ return
+ fi
+
+ # For moderately narrow terminals, show last two components
+ if (( available < 40 )); then
+ echo "%F{4}%2~%f"
+ return
+ fi
+
+ # For wide terminals, show full path
+ if (( available > 70 )); then
+ echo "%F{4}${full_path}%f"
+ return
+ fi
+
+ # Otherwise, show abbreviated path (e.g. ~/d/p/n)
+ local parts=("${(s:/:)full_path}")
+ local result=""
+ local last_index=${#parts[@]}
+
+ for i in {1..$((last_index - 1))}; do
+ [[ -n ${parts[i]} ]] && result+="/${parts[i]:0:1}"
+ done
+
+ result+="/${parts[last_index]}"
+ echo "%F{4}${result}%f"
+}
+
+
+# Prompt variables
+user="%n"
+at="%F{15}at%{$reset_color%}"
+machine="%F{4}%m%{$reset_color%}"
+relative_home="%F{4}%~%{$reset_color%}"
+carriage_return=""$'\n'""
+empty_line_bottom=""
+chevron_right=""
+color_reset="%{$(tput sgr0)%}"
+color_yellow="%{$(tput setaf 226)%}"
+color_blink="%{$(tput blink)%}"
+prompt_symbol="$"
+dollar_sign="${color_yellow}${color_blink}${prompt_symbol}${color_reset}"
+dollar="%(?:%F{2}${dollar_sign}:%F{1}${dollar_sign})"
+space=" "
+#thin_space=$'\u2009'
+thin_space=$'\u202F'
+cmd_prompt="%(?:%F{2}${chevron_right} :%F{1}${chevron_right} )"
+git_info="\$vcs_info_msg_0_"
+v1="%{┌─[%}"
+v2="%{]%}"
+v3="└──["
+v4="]"
+newline=$'\n'
+
+# Indicate INSERT mode for vi - NEVER truncate this
+function insert-mode () {
+ echo "-- INSERT --"
+}
+
+# Indicate NORMAL mode for vi - NEVER truncate this
+function normal-mode () {
+ echo "-- NORMAL --"
+}
+
+# Vi mode indicator
+vi-mode-indicator () {
+ if [[ ${KEYMAP} == vicmd || ${KEYMAP} == vi-cmd-mode ]]; then
+ echo -ne '\e[1 q'
+ vi_mode=$(normal-mode)
+ elif [[ ${KEYMAP} == main || ${KEYMAP} == viins || ${KEYMAP} == '' ]]; then
+ echo -ne '\e[5 q'
+ vi_mode=$(insert-mode)
+ fi
+}
+
+# Prompt function to ensure the prompt stays on one line, even in narrow terminals
+function set-prompt() {
+ vi-mode-indicator
+ configure_vcs_styles # Dynamically set vcs styles based on available space
+ vcs_info # Refresh vcs info with new styles
+
+ local available=$(available_space)
+ if (( available < 14 )); then
+ # Extremely narrow terminal — use minimal prompt
+ PS1="${carriage_return}${dollar}${space}${empty_line_bottom}"
+ RPROMPT='$(exit_code_info)'
+
+ else
+ # Path display - always show something for path, but adapt based on space
+ local path_display="$(abbreviated_path)"
+
+ # Git info - omit entirely if not enough space
+ local gitinfo=""
+ if [[ $available -gt 40 ]]; then
+ gitinfo="${vcs_info_msg_0_}"
+ fi
+
+ # Jobs info
+ local jobs=" $(job_name)"
+
+ # SSH info
+ local sshinfo="$(ssh_name)"
+
+ # Vi mode is priority 1 - ALWAYS show it
+ mode="%F{145}%{$terminfo_down_sc$vi_mode$terminfo[rc]%f%}"
+
+ # Right prompt for error codes or spinner
+ RPROMPT='$(exit_code_info)'
+
+ PS1="${newline}${v1}${user}${v2} ${path_display}${gitinfo}${jobs}${sshinfo}${carriage_return}${mode}${v3}${dollar}${v4}${empty_line_bottom}"
+ fi
+}
+
+# Pre-command hook to set prompt
+my_precmd() {
+ # Calculate command duration if a command was run
+ if [[ -n "$_last_executed_command" && $_cmd_start_time -gt 0 ]]; then
+ _cmd_end_time=$(date +%s)
+ _cmd_duration=$((_cmd_end_time - _cmd_start_time))
+ else
+ _cmd_duration=0
+ fi
+
+ stop_spinner_timer # Make sure spinner is stopped
+ vcs_info
+ set-prompt
+ vi-mode-indicator
+}
+
+add-zsh-hook precmd my_precmd
+
+# Update mode file based on current mode
+update-mode-file() {
+ set-prompt
+ local current_mode=$(cat ~/.vi-mode 2>/dev/null || echo "")
+ local new_mode="$vi_mode"
+
+ if [[ "$new_mode" != "$current_mode" ]]; then
+ echo "$new_mode" >| ~/.vi-mode
+ fi
+
+ # Ensure we're in an interactive shell and ZLE is active
+ if [[ -o zle ]] && zle -l &>/dev/null; then
+ zle reset-prompt 2>/dev/null || true
+ else
+ # If ZLE is not active, fallback and print the prompt manually
+ set-prompt
+ print -Pn "$PS1"
+ fi
+
+ # Refresh tmux client if tmux is running
+ if command -v tmux &>/dev/null && [[ -n "$TMUX" ]]; then
+ tmux refresh-client -S
+ fi
+}
+
+# Check if nvim is running and update mode
+function check-nvim-running() {
+ if pgrep -x "nvim" > /dev/null; then
+ vi_mode=""
+ update-mode-file
+ if command -v tmux &>/dev/null && [[ -n "$TMUX" ]]; then
+ tmux refresh-client -S
+ fi
+ else
+ if [[ ${KEYMAP} == vicmd || ${KEYMAP} == vi-cmd-mode ]]; then
+ vi_mode=$(normal-mode)
+ elif [[ ${KEYMAP} == main || ${KEYMAP} == viins || ${KEYMAP} == '' ]]; then
+ vi_mode=$(insert-mode)
+ fi
+ update-mode-file
+ if command -v tmux &>/dev/null && [[ -n "$TMUX" ]]; then
+ tmux refresh-client -S
+ fi
+ fi
+}
+
+# ZLE line initialization hook
+function zle-line-init() {
+ zle reset-prompt
+ vi-mode-indicator
+ case "${KEYMAP}" in
+ vicmd)
+ echo -ne '\e[1 q'
+ ;;
+ main|viins|*)
+ echo -ne '\e[5 q'
+ ;;
+ esac
+}
+
+# ZLE keymap select hook
+function zle-keymap-select() {
+ update-mode-file
+ zle reset-prompt
+ zle -R
+ vi-mode-indicator
+ case "${KEYMAP}" in
+ vicmd)
+ echo -ne '\e[1 q'
+ ;;
+ main|viins|*)
+ echo -ne '\e[5 q'
+ ;;
+ esac
+}
+
+# Safer version of zle reset-prompt
+function safe_reset_prompt() {
+ # Only reset if ZLE is active
+ if [[ -o zle ]] && zle -l &>/dev/null; then
+ zle reset-prompt 2>/dev/null || true
+ fi
+}
+
+# Preexec hook for command execution - NO BACKGROUND JOBS VERSION
+function preexec() {
+ # Store the command being executed
+ _last_executed_command=$1
+ _cmd_start_time=$(date +%s)
+ _cmd_is_running=1
+ _show_spinner=0 # Reset spinner flag
+
+ # Start the spinner timer immediately
+ start_spinner_timer
+
+ print -rn -- $terminfo[el]
+ echo -ne '\e[5 q'
+ vi-mode-indicator
+}
+
+# Terminal resizing: resets the prompt if ZLE is active, updates the mode file.
+TRAPWINCH() {
+ if [[ -o zle ]] && zle -l &>/dev/null; then
+ zle -R
+ zle reset-prompt 2>/dev/null || true
+ fi
+ update-mode-file 2>/dev/null
+}
+
+# Register ZLE hooks
+zle -N zle-line-init
+zle -N zle-keymap-select
+zle -N update_spinner
+
+# Register hooks
+add-zsh-hook preexec preexec
+add-zsh-hook precmd my_precmd
+
+set-prompt
diff --git a/common/config/zsh/user/prompt_minimal.zsh b/common/config/zsh/user/prompt_minimal.zsh
new file mode 100644
index 0000000..0389e7d
--- /dev/null
+++ b/common/config/zsh/user/prompt_minimal.zsh
@@ -0,0 +1,295 @@
+# vim:ft=zsh ts=2 sw=2 sts=2
+#=#=#=
+# simle_is_power theme
+# folked from agnoster's Theme - https://gist.github.com/3712874
+#
+# In order for this theme to render correctly, you will need a
+# [Powerline-patched font](https://github.com/Lokaltog/powerline-fonts).
+#=#=
+#==============================================================================
+# Color setting {{{
+#==============================================================================
+
+setopt prompt_subst
+
+bg_dir=240
+bg_dark=237
+fg_red=210
+
+#===========================================================================}}}
+# Segment drawing {{{
+#==============================================================================
+# A few utility functions to make it easy and re-usable to draw segmented prompts
+
+CURRENT_BG='NONE'
+# SEGMENT_SEPARATOR=''
+SEGMENT_SEPARATOR=''
+# SEGMENT_SEPARATOR=''
+# SEGMENT_SEPARATOR='▒'
+# SEGMENT_SEPARATOR='▓▒░'
+
+# Begin a segment
+# Takes two arguments, background and foreground. Both can be omitted,
+# rendering default background/foreground.
+prompt_segment() {
+ local bg fg
+ [[ -n $1 ]] && bg="%K{$1}" || bg="%k"
+ [[ -n $2 ]] && fg="%F{$2}" || fg="%f"
+ if [[ $CURRENT_BG != 'NONE' && $1 != $CURRENT_BG ]]; then
+ echo -n " %{$bg%F{$CURRENT_BG}%}$SEGMENT_SEPARATOR%{$fg%} "
+ else
+ echo -n "%{$bg%}%{$fg%} "
+ fi
+ CURRENT_BG=$1
+ [[ -n $3 ]] && echo -n $3
+}
+
+# End the prompt, closing any open segments
+prompt_end() {
+ if [[ -n $CURRENT_BG ]]; then
+ echo -n " %{%k%F{$CURRENT_BG}%}$SEGMENT_SEPARATOR"
+ else
+ echo -n "%{%k%}"
+ fi
+ echo -n "%{%f%}"
+ CURRENT_BG=''
+}
+
+#===========================================================================}}}
+# Prompt components {{{
+#==============================================================================
+# Each component will draw itself, and hide itself if no information needs to be shown
+#------------------------------------------------------------------------------
+# Init: {{{
+#------------------------------------------------------------------------------
+
+prompt_init() {
+ echo -n "%{%F{240}%K{240}%}"
+}
+
+#---------------------------------------------------------------------------}}}
+# Status: {{{
+#------------------------------------------------------------------------------
+# - was there an error
+# - am I root
+# - are there background jobs?
+# - am I in ranger subshell?
+
+prompt_status() {
+ local symbols
+ symbols=()
+ [[ $RETVAL -ne 0 ]] && symbols+="%{%F{${fg_red}}%}✞"
+ [[ $UID -eq 0 ]] && symbols+="%{%F{223}%}⚡"
+ [[ $(jobs -l | wc -l) -gt 0 ]] && symbols+="%{%F{cyan}%}⚙"
+ [[ -n ${RANGER_LEVEL} ]] && symbols+="%{%F{153}%}®"
+
+ [[ -n "$symbols" ]] && prompt_segment ${bg_dark} NONE "$symbols"
+}
+
+#---------------------------------------------------------------------------}}}
+# Virtualenv: current working virtualenv {{{
+#------------------------------------------------------------------------------
+
+prompt_virtualenv() {
+ local virtualenv_path="$VIRTUAL_ENV"
+ if [[ -n $virtualenv_path && -n $VIRTUAL_ENV_DISABLE_PROMPT ]]; then
+ prompt_segment green black "(`basename $virtualenv_path`)"
+ fi
+}
+
+#---------------------------------------------------------------------------}}}
+# Dir: current working directory {{{
+#------------------------------------------------------------------------------
+
+prompt_dir() {
+ prompt_segment ${bg_dir} 231 '%~'
+}
+
+#---------------------------------------------------------------------------}}}
+# Git: branch/detached head, dirty status {{{
+#------------------------------------------------------------------------------
+
+prompt_git() {
+ local ref dirty mode repo_path
+ repo_path=$(git rev-parse --git-dir 2>/dev/null)
+
+ if $(git rev-parse --is-inside-work-tree >/dev/null 2>&1); then
+ # dirty=$(parse_git_dirty)
+ ref=$(git symbolic-ref HEAD 2> /dev/null) || ref="➔ $(git show-ref --head -s --abbrev |head -n1 2> /dev/null)"
+ # if [[ -n $dirty ]]; then
+ # prompt_segment ${bg_dark} 223
+ # else
+ prompt_segment ${bg_dark} 153
+ # fi
+
+ if [[ -e "${repo_path}/BISECT_LOG" ]]; then
+ mode=" <B>"
+ elif [[ -e "${repo_path}/MERGE_HEAD" ]]; then
+ mode=" >M<"
+ elif [[ -e "${repo_path}/rebase" || -e "${repo_path}/rebase-apply" || -e "${repo_path}/rebase-merge" || -e "${repo_path}/../.dotest" ]]; then
+ mode=" >R>"
+ fi
+
+ autoload -Uz vcs_info
+
+ zstyle ':vcs_info:*' enable git
+ zstyle ':vcs_info:*' get-revision true
+ zstyle ':vcs_info:*' check-for-changes true
+ zstyle ':vcs_info:*' stagedstr '+'
+ zstyle ':vcs_info:git:*' unstagedstr '*'
+ zstyle ':vcs_info:*' formats ' %u%c'
+ zstyle ':vcs_info:*' actionformats ' %u%c'
+ vcs_info
+ echo -n "${ref/refs\/heads\// }${vcs_info_msg_0_%% }${mode}"
+ fi
+}
+
+#---------------------------------------------------------------------------}}}
+# Hg: prompt {{{
+#------------------------------------------------------------------------------
+
+prompt_hg() {
+ local rev status
+ if $(hg id >/dev/null 2>&1); then
+ if $(hg prompt >/dev/null 2>&1); then
+ if [[ $(hg prompt "{status|unknown}") = "?" ]]; then
+ # if files are not added
+ prompt_segment ${fg_red} ${bg_dark}
+ st='±'
+ elif [[ -n $(hg prompt "{status|modified}") ]]; then
+ # if any modification
+ prompt_segment 223 ${bg_dark}
+ st='±'
+ else
+ # if working copy is clean
+ prompt_segment 153 ${bg_dark}
+ fi
+ echo -n $(hg prompt "☿ {rev}@{branch}") $st
+ else
+ st=""
+ rev=$(hg id -n 2>/dev/null | sed 's/[^-0-9]//g')
+ branch=$(hg id -b 2>/dev/null)
+ if `hg st | grep -q "^\?"`; then
+ prompt_segment ${fg_red} ${bg_dark}
+ st='±'
+ elif `hg st | grep -q "^(M|A)"`; then
+ prompt_segment 223 ${bg_dark}
+ st='±'
+ else
+ prompt_segment 153 ${bg_dark}
+ fi
+ echo -n "☿ $rev@$branch" $st
+ fi
+ fi
+}
+
+#}}}========================================================================}}}
+# Build main prompt {{{
+#==============================================================================
+
+
+function vi-mode-indicator() {
+ local current_mode
+ current_mode=$(cat ~/.vi-mode 2>/dev/null || echo "")
+
+ if [[ ${KEYMAP} == vicmd || ${KEYMAP} == vi-cmd-mode ]]; then
+ [[ "$current_mode" != "-- NORMAL --" ]] && echo "-- NORMAL --" >| ~/.vi-mode
+ elif [[ ${KEYMAP} == main || ${KEYMAP} == viins || ${KEYMAP} == '' ]]; then
+ [[ "$current_mode" != "-- INSERT --" ]] && echo "-- INSERT --" >| ~/.vi-mode
+ fi
+}
+
+build_prompt() {
+ RETVAL=$?
+ vi-mode-indicator
+ prompt_init
+ prompt_virtualenv
+ prompt_dir
+ prompt_git
+ prompt_hg
+ prompt_status
+ prompt_end
+}
+
+color_reset="%{$(tput sgr0)%}"
+color_yellow="%{$(tput setaf 226)%}"
+color_blink="%{$(tput blink)%}"
+prompt_symbol="$"
+dollar_sign="${color_yellow}${color_blink}${prompt_symbol}${color_reset}"
+dollar="%(?:%F{2}${dollar_sign}:%F{1}${dollar_sign})"
+
+v1="%{┌─[%}"
+v2="%{]%}"
+v3="└─["
+v4="]"
+user="%n"
+
+PROMPT="${v1}${user}%f%b%k${v2}$(build_prompt)$reset_color
+${v3}${dollar}${v4}${empty_line_bottom}$reset_color"
+#PROMPT='%n@%m:%~%# '
+#%{%F{240}%}\$ %{$reset_color%}'
+#%{${dollar}%} %{$reset_color%}'
+RPROMPT=''
+
+PROMPT2='%{%F{30}%}↪%{$reset_color%} '
+RPROMPT2='%{$fg_bold[green]%}%_%{$reset_color%}'
+
+function update-mode-file() {
+ local current_mode=$(cat ~/.vi-mode 2>/dev/null || echo "")
+ local new_mode="$vi_mode"
+
+ # Check if the mode is different before updating
+ if [[ "$new_mode" != "$current_mode" ]]; then
+ echo "$new_mode" >| ~/.vi-mode
+ fi
+
+ # Only call zle if ZLE is active
+ if [[ -o zle ]]; then
+ zle reset-prompt # Force refresh
+ fi
+
+ # Ensure tmux client refresh only happens if tmux is running
+ if command -v tmux &>/dev/null && [[ -n "$TMUX" ]]; then
+ tmux refresh-client -S
+ fi
+}
+function zle-line-init() {
+ zle reset-prompt
+ case "${KEYMAP}" in
+ vicmd)
+ echo -ne '\e[1 q'
+ ;;
+ main|viins|*)
+ echo -ne '\e[5 q'
+ ;;
+ esac
+}
+function zle-keymap-select() {
+ local current_keymap
+ current_keymap="${KEYMAP}"
+
+ update-mode-file
+ zle reset-prompt
+
+ case "$current_keymap" in
+ vicmd)
+ echo -ne '\e[1 q'
+ ;;
+ main|viins|*)
+ echo -ne '\e[5 q'
+ ;;
+ esac
+}
+
+precmd () {
+ print -rP
+}
+
+preexec () {
+ print -rn -- $terminfo[el]
+ echo -ne '\e[5 q' # Reset cursor shape
+}
+zle -N zle-line-init
+zle -N zle-keymap-select
+
+#===========================================================================}}}
diff --git a/common/config/zsh/user/prompt_new.zsh b/common/config/zsh/user/prompt_new.zsh
new file mode 100644
index 0000000..78791ef
--- /dev/null
+++ b/common/config/zsh/user/prompt_new.zsh
@@ -0,0 +1,863 @@
+# __ _ __ _| | _____ ______ _| | __
+# / _` |/ _` | |/ / _ \_ / _` | |/ /
+# | (_| | (_| | < (_) / / (_| | <
+# \__,_|\__, |_|\_\___/___\__,_|_|\_\
+# |___/
+#
+# An asynchronous, dynamic color prompt for ZSH with Git, vi mode, and exit
+# status indicators
+#
+#
+# MIT License
+#
+# Copyright (c) 2017-2019 Alexandros Kozak
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+#
+#
+# https://github.com/agkozak/agkozak-zsh-prompt
+#
+
+# shellcheck disable=SC1090,SC2016,SC2034,SC2088,SC2148,SC2154,SC2190
+
+# psvar[] Usage
+#
+# psvar Index Prompt String Equivalent Usage
+#
+# psvar[1] %1v Hostname/abbreviated hostname (only
+# displayed for SSH connections)
+# psvar[2] %2v Working directory or abbreviation
+# thereof
+# psvar[3] %3v Current working Git branch, along
+# with indicator of changes made
+# psvar[4] %4v Equals 'vicmd' when vi command mode
+# is enabled; otherwise empty
+
+# Set AGKOZAK_PROMPT_DEBUG=1 to see debugging information
+AGKOZAK_PROMPT_DEBUG=${AGKOZAK_PROMPT_DEBUG:-0}
+
+############################################################
+# Display a message on STDERR if debug mode is enabled
+#
+# Globals:
+# AGKOZAK_PROMPT_DEBUG
+# Arguments:
+# $1 Message to send to STDERR
+############################################################
+_agkozak_debug_print() {
+ (( AGKOZAK_PROMPT_DEBUG )) && print -- "agkozak-zsh-prompt: $1" >&2
+}
+
+if (( AGKOZAK_PROMPT_DEBUG )); then
+ autoload -Uz is-at-least
+
+ setopt WARN_CREATE_GLOBAL
+
+ if is-at-least 5.4.0; then
+ setopt WARN_NESTED_VAR
+ fi
+fi
+
+# Set AGKOZAK_PROMPT_DIRTRIM to the desired number of directory elements to
+# display, or set it to 0 for no directory trimming
+typeset -g AGKOZAK_PROMPT_DIRTRIM=${AGKOZAK_PROMPT_DIRTRIM:-2}
+
+# Set AGKOZAK_MULTILINE to 0 to enable the legacy, single-line prompt
+typeset -g AGKOZAK_MULTILINE=${AGKOZAK_MULTILINE:-1}
+
+# Set AGKOZAK_LEFT_PROMPT_ONLY to have the Git status appear in the left prompt
+typeset -g AGKOZAK_LEFT_PROMPT_ONLY=${AGKOZAK_LEFT_PROMPT_ONLY:-0}
+
+# Set AGKOZAK_COLORS_* variables to any valid color
+# AGKOZAK_COLORS_EXIT_STATUS changes the exit status color (default: red)
+# AGKOZAK_COLORS_USER_HOST changes the username/hostname color (default: green)
+# AGKOZAK_COLORS_PATH changes the path color (default: blue)
+# AGKOZAK_COLORS_BRANCH_STATUS changes the branch status color (default: yellow)
+# AGKOZAK_COLORS_PROMPT_CHAR changes the prompt character color (default: white)
+typeset -g AGKOZAK_COLORS_EXIT_STATUS=${AGKOZAK_COLORS_EXIT_STATUS:-red}
+typeset -g AGKOZAK_COLORS_USER_HOST=${AGKOZAK_COLORS_USER_HOST:-green}
+typeset -g AGKOZAK_COLORS_PATH=${AGKOZAK_COLORS_PATH:-blue}
+typeset -g AGKOZAK_COLORS_BRANCH_STATUS=${AGKOZAK_COLORS_BRANCH_STATUS:-yellow}
+
+setopt PROMPT_SUBST NO_PROMPT_BANG
+
+######################################################################
+# GENERAL FUNCTIONS
+######################################################################
+
+############################################################
+# Are colors available?
+#
+# Globals:
+# AGKOZAK_HAS_COLORS
+############################################################
+_agkozak_has_colors() {
+ if (( $+AGKOZAK_HAS_COLORS )); then
+ :
+ else
+ case $TERM in
+ *-256color) typeset -g AGKOZAK_HAS_COLORS=1 ;;
+ vt100|dumb) typeset -g AGKOZAK_HAS_COLORS=0 ;;
+ *)
+ local colors
+ case $OSTYPE in
+ freebsd*|dragonfly*) colors=$(tput Co) ;;
+ *) colors=$(tput colors) ;;
+ esac
+ typeset -g AGKOZAK_HAS_COLORS=$(( colors >= 8 ))
+ ;;
+ esac
+ fi
+ (( AGKOZAK_HAS_COLORS ))
+}
+
+############################################################
+# Is the user connected via SSH?
+#
+# This function works perfectly for regular users. It is
+# nearly impossible to detect with accuracy how a superuser
+# is connected, so this prompt opts simply to display his or
+# her username and hostname in inverse video.
+############################################################
+_agkozak_is_ssh() {
+ [[ -n "${SSH_CONNECTION-}${SSH_CLIENT-}${SSH_TTY-}" ]]
+}
+
+############################################################
+# Emulation of bash's PROMPT_DIRTRIM for ZSH
+#
+# Take PWD and substitute HOME with `~'. If the rest of PWD
+# has more than a certain number of elements in its
+# directory tree, keep the number specified by
+# AGKOZAK_PROMPT_DIRTRIM (default: 2) and abbreviate the
+# rest with `...'. (Set AGKOZAK_PROMPT_DIRTRIM=0 to disable
+# directory trimming). For example,
+#
+# $HOME/dotfiles/polyglot/img
+#
+# will be displayed as
+#
+# ~/.../polyglot/img
+#
+# Named directories will by default be displayed using their
+# aliases in the prompt (e.g. `~project'). Set
+# AGKOZAK_NAMED_DIRS=0 to have them displayed just like any
+# other directory.
+#
+# Globals:
+# AGKOZAK_NAMED_DIRS
+# Arguments:
+# $@ [Optional] If `-v', store the function's output in
+# psvar[2] instead of printing it to STDOUT
+# $@ Number of directory elements to display (default: 2)
+############################################################
+_agkozak_prompt_dirtrim() {
+ # Process arguments
+ local argument
+ for argument in $@; do
+ [[ $argument == '-v' ]] && local var=1
+ done
+ until [[ $1 != '-v' ]]; do
+ shift
+ done
+ [[ $1 -ge 0 ]] || set 2
+
+ # Default behavior (when AGKOZAK_NAMED_DIRS is 1)
+ typeset -g AGKOZAK_NAMED_DIRS=${AGKOZAK_NAMED_DIRS:-1}
+ if (( AGKOZAK_NAMED_DIRS )); then
+ local zsh_pwd
+ print -Pnz '%~'
+
+ # IF AGKOZAK_PROMPT_DIRTRIM is not 0, trim directory
+ if (( $1 )); then
+ read -rz zsh_pwd
+ case $zsh_pwd in
+ \~) print -Pnz $zsh_pwd ;;
+ \~/*) print -Pnz "%($(( $1 + 2 ))~|~/.../%${1}~|%~)" ;;
+ \~*) print -Pnz "%($(( $1 + 2 ))~|${zsh_pwd%%${zsh_pwd#\~*\/}}.../%${1}~|%~)" ;;
+ *) print -Pnz "%($(( $1 + 1 ))/|.../%${1}d|%d)" ;;
+ esac
+ fi
+
+ # If AGKOZAK_NAMED_DIRS is 0
+ else
+ local dir dir_count
+ case $HOME in
+ /) dir=${PWD} ;;
+ *) dir=${PWD#$HOME} ;;
+ esac
+
+ # If AGKOZAK_PROMPT_DIRTRIM is not 0, trim the directory
+ if (( $1 > 0 )); then
+
+ # The number of directory elements is the number of slashes in ${PWD#$HOME}
+ dir_count=$(( ${#dir} - ${#${dir//\//}} ))
+ if (( dir_count <= $1 )); then
+ case $PWD in
+ ${HOME}) print -nz '~' ;;
+ ${HOME}*) print -nz "~${dir}" ;;
+ *) print -nz "$PWD" ;;
+ esac
+ else
+ local lopped_path i
+ lopped_path=${dir}
+ i=0
+ while (( i != $1 )); do
+ lopped_path=${lopped_path%\/*}
+ (( i++ ))
+ done
+ case $PWD in
+ ${HOME}*) print -nz "~/...${dir#${lopped_path}}" ;;
+ *) print -nz -f '...%s' "${PWD#${lopped_path}}" ;;
+ esac
+ fi
+
+ # If AGKOZAK_PROMPT_DIRTRIM is 0
+ else
+ case $PWD in
+ ${HOME}) print -nz '~' ;;
+ ${HOME}*) print -nz "~${dir}" ;;
+ *) print -nz "$PWD" ;;
+ esac
+ fi
+ fi
+
+ local output
+ read -rz output
+
+ # Argument -v stores the output to psvar[2]; otherwise send to STDOUT
+ if (( var )); then
+ psvar[2]=$output
+ else
+ print $output
+ fi
+}
+
+############################################################
+# Display current branch name, followed by symbols
+# representing changes to the working copy
+############################################################
+_agkozak_branch_status() {
+ local ref branch
+ ref=$(command git symbolic-ref --quiet HEAD 2> /dev/null)
+ case $? in # See what the exit code is.
+ 0) ;; # $ref contains the name of a checked-out branch.
+ 128) return ;; # No Git repository here.
+ # Otherwise, see if HEAD is in detached state.
+ *) ref=$(command git rev-parse --short HEAD 2> /dev/null) || return ;;
+ esac
+ branch=${ref#refs/heads/}
+
+ if [[ -n $branch ]]; then
+ local git_status symbols i=1 k
+ git_status="$(LC_ALL=C command git status 2>&1)"
+
+ typeset -A messages
+ messages=(
+ '&*' ' have diverged,'
+ '&' 'Your branch is behind '
+ '*' 'Your branch is ahead of '
+ '+' 'new file: '
+ 'x' 'deleted: '
+ '!' 'modified: '
+ '>' 'renamed: '
+ '?' 'Untracked files:'
+ )
+
+ for k in '&*' '&' '*' '+' 'x' '!' '>' '?'; do
+ case $git_status in
+ *${messages[$k]}*) symbols+="${AGKOZAK_CUSTOM_SYMBOLS[$i]:-$k}" ;;
+ esac
+ (( i++ ))
+ done
+
+ [[ -n $symbols ]] && symbols=" ${symbols}"
+
+ printf '%s(%s%s)' "${AGKOZAK_BRANCH_STATUS_SEPARATOR- }" "$branch" "$symbols"
+ fi
+}
+
+############################################################
+# Redraw the prompt when the vi mode changes. When the user
+# enters vi command mode, the % or # in the prompt changes
+# to a colon
+############################################################
+zle-keymap-select() {
+ [[ $KEYMAP == 'vicmd' ]] && psvar[4]='vicmd' || psvar[4]=''
+ zle reset-prompt
+ zle -R
+}
+
+############################################################
+# Redraw prompt when terminal size changes
+############################################################
+TRAPWINCH() {
+ zle && zle -R
+}
+
+############################################################
+# For legacy custom prompts: print a vi mode indicator
+############################################################
+_agkozak_vi_mode_indicator() {
+ case $KEYMAP in
+ vicmd) print -n ':' ;;
+ *) print -n '%#' ;;
+ esac
+}
+
+######################################################################
+# ASYNCHRONOUS FUNCTIONS
+######################################################################
+
+# Standarized $0 handling
+# (See https://github.com/zdharma/Zsh-100-Commits-Club/blob/master/Zsh-Plugin-Standard.adoc)
+0="${${ZERO:-${0:#$ZSH_ARGZERO}}:-${(%):-%N}}"
+typeset -g AGKOZAK_PROMPT_DIR="${0:A:h}"
+
+############################################################
+# If zsh-async has not already been loaded, try to load it
+#
+# Globals:
+# AGKOZAK_PROMPT_DEBUG
+# AGKOZAK_PROMPT_DIR
+############################################################
+_agkozak_load_async_lib() {
+ if ! whence -w async_init &> /dev/null; then # Don't load zsh-async twice
+ if (( AGKOZAK_PROMPT_DEBUG )); then
+ source "${AGKOZAK_PROMPT_DIR}/lib/async.zsh"
+ else
+ source "${AGKOZAK_PROMPT_DIR}/lib/async.zsh" &> /dev/null
+ fi
+ local success=$?
+ return $success
+ fi
+}
+
+############################################################
+# If SIGUSR1 is available and not already in use by ZSH, use
+# it; otherwise disable asynchronous mode
+############################################################
+_agkozak_has_usr1() {
+ if whence -w TRAPUSR1 &> /dev/null; then
+ _agkozak_debug_print 'TRAPUSR1 already defined.'
+ return 1
+ else
+ case $signals in # Array containing names of available signals
+ *USR1*) return 0 ;;
+ *)
+ _agkozak_debug_print 'SIGUSR1 not available.'
+ return 1
+ ;;
+ esac
+ fi
+}
+
+############################################################
+# If AGKOZAK_FORCE_ASYNC_METHOD is set to a valid value,
+# set AGKOZAK_ASYNC_METHOD to that; otherwise, determine
+# the optimal asynchronous method from the environment (usr1
+# for MSYS2/Cygwin, zsh-async for WSL, subst-async for
+# everything else), with fallbacks being available. Define
+# the necessary asynchronous functions (loading async.zsh
+# when necessary).
+#
+# Globals:
+# AGKOZAK_IS_WSL
+# AGKOZAK_ASYNC_METHOD
+# AGKOZAK_FORCE_ASYNC_METHOD
+# AGKOZAK_TRAPUSR1_FUNCTION
+############################################################
+_agkozak_async_init() {
+
+ # WSL should have BG_NICE disabled, since it does not have a Linux kernel
+ setopt LOCAL_OPTIONS EXTENDED_GLOB
+ if [[ -e /proc/version ]]; then
+ if [[ -n ${(M)${(f)"$(</proc/version)"}:#*Microsoft*} ]]; then
+ unsetopt BG_NICE
+ typeset -g AGKOZAK_IS_WSL=1 # For later reference
+ fi
+ fi
+
+ # If AGKOZAK_FORCE_ASYNC_METHOD is set, force the asynchronous method
+ [[ $AGKOZAK_FORCE_ASYNC_METHOD == 'zsh-async' ]] && _agkozak_load_async_lib
+ if [[ $AGKOZAK_FORCE_ASYNC_METHOD == (subst-async|zsh-async|usr1|none) ]]; then
+ typeset -g AGKOZAK_ASYNC_METHOD=$AGKOZAK_FORCE_ASYNC_METHOD
+
+ # Otherwise, first provide for certain quirky systems
+ else
+
+ if (( AGKOZAK_IS_WSL )) || [[ $OSTYPE == solaris* ]]; then
+ if [[ $ZSH_VERSION != '5.0.2' ]] &&_agkozak_load_async_lib; then
+ typeset -g AGKOZAK_ASYNC_METHOD='zsh-async'
+ elif _agkozak_has_usr1; then
+ typeset -g AGKOZAK_ASYNC_METHOD='usr1'
+ else
+ typeset -g AGKOZAK_ASYNC_METHOD='subst-async'
+ fi
+
+ # SIGUSR1 method is still much faster on MSYS2, Cygwin, and ZSH v5.0.2
+ elif [[ $ZSH_VERSION == '5.0.2' ]] || [[ $OSTYPE == (msys|cygwin) ]]; then
+ if _agkozak_has_usr1; then
+ typeset -g AGKOZAK_ASYNC_METHOD='usr1'
+ else
+ typeset -g AGKOZAK_ASYNC_METHOD='subst-async'
+ fi
+
+ # Asynchronous methods don't work in Emacs shell mode (but they do in term
+ # and ansi-term)
+ elif [[ $TERM == 'dumb' ]]; then
+ typeset -g AGKOZAK_ASYNC_METHOD='none'
+
+ # Otherwise use subst-async
+ else
+ typeset -g AGKOZAK_ASYNC_METHOD='subst-async'
+ fi
+ fi
+
+ ############################################################
+ # Process substitution async method
+ #
+ # Fork a background process to fetch the Git status and feed
+ # it asynchronously to a file descriptor. Install a callback
+ # handler to process input from the file descriptor.
+ #
+ # Globals:
+ # AGKOZAK_ASYNC_FD
+ # AGKOZAK_IS_WSL
+ ############################################################
+ _agkozak_subst_async() {
+ setopt LOCAL_OPTIONS NO_IGNORE_BRACES
+ typeset -g AGKOZAK_ASYNC_FD=13371
+
+ # Workaround for buggy behavior in MSYS2, Cygwin, and Solaris
+ if [[ $OSTYPE == (msys|cygwin|solaris*) ]]; then
+ exec {AGKOZAK_ASYNC_FD}< <(_agkozak_branch_status; command true)
+ # Prevent WSL from locking up when using X; also workaround for ZSH v5.0.2
+ elif (( AGKOZAK_IS_WSL )) && (( $+DISPLAY )) \
+ || [[ $ZSH_VERSION == '5.0.2' ]]; then
+ exec {AGKOZAK_ASYNC_FD}< <(_agkozak_branch_status)
+ command sleep 0.01
+ else
+ exec {AGKOZAK_ASYNC_FD}< <(_agkozak_branch_status)
+ fi
+
+ # Bug workaround; see http://www.zsh.org/mla/workers/2018/msg00966.html
+ command true
+
+ zle -F "$AGKOZAK_ASYNC_FD" _agkozak_zsh_subst_async_callback
+ }
+
+ ############################################################
+ # ZLE callback handler
+ #
+ # Read Git status from file descriptor and set psvar[3]
+ #
+ # Arguments:
+ # $1 File descriptor
+ ############################################################
+ _agkozak_zsh_subst_async_callback() {
+ setopt LOCAL_OPTIONS NO_IGNORE_BRACES
+
+ local FD="$1" response
+
+ # Read data from $FD descriptor
+ IFS='' builtin read -rs -d $'\0' -u "$FD" response
+
+ # Withdraw callback and close the file descriptor
+ zle -F ${FD}; exec {FD}<&-
+
+ # Make the changes visible
+ psvar[3]="$response"
+ zle && zle reset-prompt
+ }
+
+ case $AGKOZAK_ASYNC_METHOD in
+
+ zsh-async)
+
+ ############################################################
+ # Create zsh-async worker
+ ############################################################
+ _agkozak_zsh_async() {
+ async_start_worker agkozak_git_status_worker -n
+ async_register_callback agkozak_git_status_worker _agkozak_zsh_async_callback
+ async_job agkozak_git_status_worker _agkozak_branch_status
+ }
+
+ ############################################################
+ # Set RPROMPT and stop worker
+ ############################################################
+ _agkozak_zsh_async_callback() {
+ psvar[3]=$3
+ zle && zle reset-prompt
+ async_stop_worker agkozak_git_status_worker -n
+ }
+ ;;
+
+ usr1)
+
+ ############################################################
+ # Launch async workers to calculate Git status. TRAPUSR1
+ # actually displays the status; if some other script
+ # redefines TRAPUSR1, drop the prompt into synchronous mode.
+ #
+ # Globals:
+ # AGKOZAK_TRAPUSR1_FUNCTION
+ # AGKOZAK_USR1_ASYNC_WORKER
+ # AGKOZAK_ASYNC_METHOD
+ ############################################################
+ _agkozak_usr1_async() {
+ if [[ "$(builtin which TRAPUSR1)" = "$AGKOZAK_TRAPUSR1_FUNCTION" ]]; then
+ # Kill running child process if necessary
+ if (( AGKOZAK_USR1_ASYNC_WORKER )); then
+ kill -s HUP "$AGKOZAK_USR1_ASYNC_WORKER" &> /dev/null || :
+ fi
+
+ # Start background computation of Git status
+ _agkozak_usr1_async_worker &!
+ typeset -g AGKOZAK_USR1_ASYNC_WORKER=$!
+ else
+ _agkozak_debug_print 'TRAPUSR1 has been redefined. Switching to subst-async mode.'
+ typeset -g AGKOZAK_ASYNC_METHOD='subst-async'
+ psvar[3]="$(_agkozak_branch_status)"
+ fi
+ }
+
+ ############################################################
+ # Calculate Git status and store it in a temporary file;
+ # then kill own process, sending SIGUSR1
+ #
+ # Globals:
+ # AGKOZAK_PROMPT_DEBUG
+ ############################################################
+ _agkozak_usr1_async_worker() {
+ # Save Git branch status to temporary file
+ _agkozak_branch_status >| /tmp/agkozak_zsh_prompt_$$
+
+ # Signal parent process
+ if (( AGKOZAK_PROMPT_DEBUG )); then
+ kill -s USR1 $$
+ else
+ kill -s USR1 $$ &> /dev/null
+ fi
+ }
+
+ ############################################################
+ # On SIGUSR1, fetch Git status from temprary file and store
+ # it in psvar[3]. This function caches its own code in
+ # AGKOZAK_TRAPUSR1_FUNCTION so that it can tell if it has
+ # been redefined by another script.
+ #
+ # Globals:
+ # AGKOZAK_USR1_ASYNC_WORKER
+ # AGKOZAK_TRAPUSR1_FUNCTION
+ ############################################################
+ TRAPUSR1() {
+ # Set prompt from contents of temporary file
+ psvar[3]=$(print -n -- "$(< /tmp/agkozak_zsh_prompt_$$)")
+
+ # Reset asynchronous process number
+ typeset -g AGKOZAK_USR1_ASYNC_WORKER=0
+
+ # Redraw the prompt
+ zle && zle reset-prompt
+ }
+
+ typeset -g AGKOZAK_TRAPUSR1_FUNCTION="$(builtin which TRAPUSR1)"
+ ;;
+ esac
+}
+
+######################################################################
+# THE PROMPT
+######################################################################
+
+############################################################
+# Strip color codes from a prompt string
+#
+# Arguments:
+# $1 The prompt string
+############################################################
+_agkozak_strip_colors() {
+
+ local prompt=$1
+ local open_braces
+
+ while [[ -n $prompt ]]; do
+ case $prompt in
+ %F\{*|%K\{*)
+ (( open_braces++ ))
+ prompt=${prompt#%[FK]\{}
+ while (( open_braces )); do
+ case ${prompt:0:1} in
+ \{) (( open_braces++ )) ;;
+ \}) (( open_braces-- )) ;;
+ esac
+ prompt=${prompt#?}
+ done
+ ;;
+ %f*|%k*) prompt=${prompt#%[fk]} ;;
+ *)
+ print -n -- "${prompt:0:1}"
+ prompt=${prompt#?}
+ ;;
+ esac
+ done
+}
+
+############################################################
+# Runs right before each prompt is displayed; hooks into
+# precmd
+#
+# 1) Redisplays path ($psvar[2]) whenever the value of
+# AGKOZAK_PROMPT_DIRTRIM or AGKOZAK_NAMED_DIRS changes
+# 2) If AGKOZAK_MULTILINE is changed to 0, set
+# AGKOZAK_LEFT_PROMPT_ONLY=0
+# 3) If AGKOZAK_LEFT_PROMPT_ONLY is changed, updated both
+# prompt strings
+# 4) Resets Git status and vi mode display
+# 5) Begins to calculate Git status
+# 6) Sets AGKOZAK_PROMPT_WHITESPACE based on value of
+# AGKOZAK_MULTILINE
+# 7) Optionally display a blank line (AGKOZAK_BLANK_LINES),
+# while avoiding a blank line when the shell is first
+# loaded
+# 8) If custom prompts are defined, update the prompt
+# strings
+#
+# TODO: Consider making AGKOZAK_PROMPT_WHITESPACE a psvar
+#
+# Globals:
+# AGKOZAK_PROMPT_DIRTRIM
+# AGKOZAK_OLD_PROMPT_DIRTRIM
+# AGKOZAK_NAMED_DIRS
+# AGKOZAK_OLD_NAMED_DIRS
+# AGKOZAK_MULTILINE
+# AGKOZAK_OLD_MULTILINE
+# AGKOZAK_LEFT_PROMPT_ONLY
+# AGKOZAK_OLD_LEFT_PROMPT_ONLY
+# AGKOZAK_ASYNC_METHOD
+# AGKOZAK_PROMPT_WHITESPACE
+# AGKOZAK_BLANK_LINES
+# AGKOZAK_FIRST_PROMPT_PRINTED
+# AGKOZAK_CUSTOM_PROMPT
+# AGKOZAK_CURRENT_CUSTOM_PROMPT
+# AGKOZAK_CUSTOM_RPROMPT
+# AGKOZAK_CURRENT_CUSTOM_RPROMPT
+############################################################
+_agkozak_precmd() {
+ # Update displayed directory when AGKOZAK_PROMPT_DIRTRIM or AGKOZAK_NAMED_DIRS
+ # changes or when first sourcing this script
+ if (( AGKOZAK_PROMPT_DIRTRIM != AGKOZAK_OLD_PROMPT_DIRTRIM )) \
+ || (( AGKOZAK_NAMED_DIRS != AGKOZAK_OLD_NAMED_DIRS )) \
+ || (( ! $+psvar[2] )); then
+ _agkozak_prompt_dirtrim -v $AGKOZAK_PROMPT_DIRTRIM
+ typeset -g AGKOZAK_OLD_PROMPT_DIRTRIM=$AGKOZAK_PROMPT_DIRTRIM
+ typeset -g AGKOZAK_OLD_NAMED_DIRS=$AGKOZAK_NAMED_DIRS
+ fi
+
+ if (( AGKOZAK_MULTILINE != AGKOZAK_OLD_MULTILINE )); then
+ (( AGKOZAK_MULTILINE == 0 )) && AGKOZAK_LEFT_PROMPT_ONLY=0
+ typeset -g AGKOZAK_OLD_MULTILINE=$AGKOZAK_MULTILINE
+ fi
+
+ if (( AGKOZAK_LEFT_PROMPT_ONLY != AGKOZAK_OLD_LEFT_PROMPT_ONLY )); then
+ unset AGKOZAK_CUSTOM_PROMPT AGKOZAK_CUSTOM_RPROMPT
+ typeset -g AGKOZAK_OLD_LEFT_PROMPT_ONLY=$AGKOZAK_LEFT_PROMPT_ONLY
+ _agkozak_prompt_string
+ fi
+
+ psvar[3]=''
+ psvar[4]=''
+
+ case $AGKOZAK_ASYNC_METHOD in
+ 'subst-async') _agkozak_subst_async ;;
+ 'zsh-async') _agkozak_zsh_async ;;
+ 'usr1') _agkozak_usr1_async ;;
+ *) psvar[3]="$(_agkozak_branch_status)" ;;
+ esac
+
+ if (( AGKOZAK_MULTILINE == 0 )) && (( ! AGKOZAK_LEFT_PROMPT_ONLY )) \
+ && [[ -z $INSIDE_EMACS ]]; then
+ typeset -g AGKOZAK_PROMPT_WHITESPACE=' '
+ else
+ typeset -g AGKOZAK_PROMPT_WHITESPACE=$'\n'
+ fi
+
+ if (( AGKOZAK_BLANK_LINES )); then
+ if (( AGKOZAK_FIRST_PROMPT_PRINTED )); then
+ print
+ fi
+ typeset -g AGKOZAK_FIRST_PROMPT_PRINTED=1
+ fi
+
+ # If AGKOZAK_CUSTOM_PROMPT or AGKOZAK_CUSTOM_RPROMPT changes, the
+ # corresponding prompt is updated
+
+ if [[ ${AGKOZAK_CUSTOM_PROMPT} != "${AGKOZAK_CURRENT_CUSTOM_PROMPT}" ]]; then
+ typeset -g AGKOZAK_CURRENT_CUSTOM_PROMPT=${AGKOZAK_CUSTOM_PROMPT}
+ PROMPT=${AGKOZAK_CUSTOM_PROMPT}
+ if ! _agkozak_has_colors; then
+ PROMPT=$(_agkozak_strip_colors "${PROMPT}")
+ fi
+ fi
+
+ if [[ ${AGKOZAK_CUSTOM_RPROMPT} != "${AGKOZAK_CURRENT_CUSTOM_RPROMPT}" ]]; then
+ typeset -g AGKOZAK_CURRENT_CUSTOM_RPROMPT=${AGKOZAK_CUSTOM_RPROMPT}
+ RPROMPT=${AGKOZAK_CUSTOM_RPROMPT}
+ if ! _agkozak_has_colors; then
+ RPROMPT=$(_agkozak_strip_colors "${RPROMPT}")
+ fi
+ fi
+}
+
+############################################################
+# Set the prompt strings
+#
+# Globals:
+# AGKOZAK_CUSTOM_PROMPT
+# AGKOZAK_COLORS_EXIT_STATUS
+# AGKOZAK_COLORS_USER_HOST
+# AGKOZAK_COLORS_PATH
+# AGKOZAK_PROMPT_WHITESPACE
+# AGKOZAK_COLORS_PROMPT_CHAR
+# AGKOZAK_PROMPT_CHAR
+# AGKOZAK_CURRENT_CUSTOM_PROMPT
+# AGKOZAK_CUSTOM_RPROMPT
+# AGKOZAK_COLORS_BRANCH_STATUS
+# AGKOZAK_CURRENT_CUSTOM_RPROMPT
+############################################################
+_agkozak_prompt_string () {
+ if (( $+AGKOZAK_CUSTOM_PROMPT )); then
+ PROMPT=${AGKOZAK_CUSTOM_PROMPT}
+ else
+ # The color left prompt
+ PROMPT='%(?..%B%F{${AGKOZAK_COLORS_EXIT_STATUS}}(%?%)%f%b )'
+ PROMPT+='%(!.%S%B.%B%F{${AGKOZAK_COLORS_USER_HOST}})%n%1v%(!.%b%s.%f%b) '
+ PROMPT+='%B%F{${AGKOZAK_COLORS_PATH}}%2v%f%b'
+ if (( AGKOZAK_LEFT_PROMPT_ONLY )); then
+ PROMPT+='%(3V.%F{${AGKOZAK_COLORS_BRANCH_STATUS}}%3v%f.)'
+ fi
+ PROMPT+='${AGKOZAK_PROMPT_WHITESPACE}'
+ PROMPT+='${AGKOZAK_COLORS_PROMPT_CHAR:+%F{${AGKOZAK_COLORS_PROMPT_CHAR}\}}'
+ PROMPT+='%(4V.${AGKOZAK_PROMPT_CHAR[3]:-:}.%(!.${AGKOZAK_PROMPT_CHAR[2]:-%#}.${AGKOZAK_PROMPT_CHAR[1]:-%#}))'
+ PROMPT+='${AGKOZAK_COLORS_PROMPT_CHAR:+%f} '
+
+ typeset -g AGKOZAK_CUSTOM_PROMPT=${PROMPT}
+ typeset -g AGKOZAK_CURRENT_CUSTOM_PROMPT=${AGKOZAK_CUSTOM_PROMPT}
+ fi
+
+ if (( $+AGKOZAK_CUSTOM_RPROMPT )); then
+ RPROMPT=${AGKOZAK_CUSTOM_RPROMPT}
+ else
+ # The color right prompt
+ if (( ! AGKOZAK_LEFT_PROMPT_ONLY )); then
+ typeset -g RPROMPT='%(3V.%F{${AGKOZAK_COLORS_BRANCH_STATUS}}%3v%f.)'
+ else
+ typeset -g RPROMPT=''
+ fi
+
+ typeset -g AGKOZAK_CUSTOM_RPROMPT=${RPROMPT}
+ typeset -g AGKOZAK_CURRENT_CUSTOM_RPROMPT=${RPROMPT}
+ fi
+
+ if ! _agkozak_has_colors; then
+ PROMPT=$(_agkozak_strip_colors "$PROMPT")
+ RPROMPT=$(_agkozak_strip_colors "$RPROMPT")
+ fi
+}
+
+############################################################
+# Prompt setup
+#
+# Globals:
+# AGKOZAK_ASYNC_METHOD
+# AGKOZAK_USR1_ASYNC_WORKER
+# AGKOZAK_PROMPT_DIRTRIM
+############################################################
+() {
+
+ _agkozak_async_init
+
+ case $AGKOZAK_ASYNC_METHOD in
+ 'subst-async') ;;
+ 'zsh-async') async_init ;;
+ 'usr1') typeset -g AGKOZAK_USR1_ASYNC_WORKER=0 ;;
+ esac
+
+ zle -N zle-keymap-select
+
+ # Don't use ZSH hooks in Emacs classic shell
+ if (( $+INSIDE_EMACS )) && [[ $TERM == 'dumb' ]]; then
+ :
+ else
+ autoload -Uz add-zsh-hook
+ add-zsh-hook precmd _agkozak_precmd
+
+ ############################################################
+ # Update the displayed directory when the PWD changes
+ ############################################################
+ _agkozak_chpwd() {
+ _agkozak_prompt_dirtrim -v $AGKOZAK_PROMPT_DIRTRIM
+ }
+
+ add-zsh-hook chpwd _agkozak_chpwd
+ fi
+
+ # Only display the HOSTNAME for an SSH connection or for a superuser
+ if _agkozak_is_ssh || (( EUID == 0 )); then
+ psvar[1]="@${HOST%%.*}"
+ else
+ psvar[1]=''
+ fi
+
+ # The DragonFly BSD console and Emacs shell can't handle bracketed paste.
+ # Avoid the ugly ^[[?2004 control sequence.
+ if [[ $TERM == 'cons25' ]] || [[ $TERM == 'dumb' ]]; then
+ unset zle_bracketed_paste
+ fi
+
+ # The Emacs shell has only limited support for some ZSH features, so use a
+ # more limited prompt.
+ if [[ $TERM == 'dumb' ]]; then
+ PROMPT='%(?..(%?%) )'
+ PROMPT+='%n%1v '
+ PROMPT+='$(_agkozak_prompt_dirtrim "$AGKOZAK_PROMPT_DIRTRIM")'
+ PROMPT+='$(_agkozak_branch_status) '
+ PROMPT+='%# '
+ else
+ # Avoid continuation lines in Emacs term and ansi-term
+ (( $+INSIDE_EMACS )) && ZLE_RPROMPT_INDENT=3
+
+ # When VSCode is using the DOM renderer, the right prompt overflows off the
+ # side of the screen
+ (( $+VSCODE_PID )) && ZLE_RPROMPT_INDENT=6
+
+ _agkozak_prompt_string
+
+ fi
+
+ _agkozak_debug_print "Using async method: $AGKOZAK_ASYNC_METHOD"
+}
+
+# Clean up environment
+unfunction _agkozak_load_async_lib _agkozak_has_usr1 _agkozak_is_ssh \
+ _agkozak_async_init
+
+# vim: ts=2:et:sts=2:sw=2:ROMPT='%~%<< $(git_prompt_info)${PR_BOLD_WHITE}>%{${reset_color}%} '
diff --git a/common/config/zsh/user/prompt_simple.zsh b/common/config/zsh/user/prompt_simple.zsh
new file mode 100644
index 0000000..0bbad44
--- /dev/null
+++ b/common/config/zsh/user/prompt_simple.zsh
@@ -0,0 +1,227 @@
+# vim:ft=zsh ts=2 sw=2 sts=2
+#
+### Segment drawing
+# A few utility functions to make it easy and re-usable to draw segmented prompts
+CURRENT_BG='NONE'
+
+case ${SOLARIZED_THEME:-dark} in
+ light) CURRENT_FG='white';;
+ *) CURRENT_FG='black';;
+esac
+
+# Segments
+() {
+ local LC_ALL="" LC_CTYPE="en_US.UTF-8"
+ SEGMENT_SEPARATOR=
+}
+
+# Begin a segment
+# Takes two arguments, background and foreground. Both can be omitted,
+# rendering default background/foreground.
+prompt_segment() {
+ local bg fg
+ [[ -n $2 ]] && fg="$FG[254]" || fg="%f"
+ if [[ $CURRENT_BG != 'NONE' && $1 != $CURRENT_BG ]]; then
+ echo -n " %{$bg%F{$CURRENT_BG}%}$SEGMENT_SEPARATOR%{$fg%} "
+ else
+ echo -n "%{$bg%}%{$fg%}"
+ fi
+ CURRENT_BG=$1
+ [[ -n $3 ]] && echo -n $3
+}
+
+# End the prompt, closing any open segments
+prompt_end() {
+ if [[ -n $CURRENT_BG ]]; then
+ echo -n " %{%k%F{$CURRENT_BG}%}$SEGMENT_SEPARATOR"
+ else
+ echo -n "%{%k%}"
+ fi
+ echo -n "%{%f%}"
+ CURRENT_BG=''
+}
+
+### Prompt components
+# Each component will draw itself, and hide itself if no information needs to be shown
+
+# Context: user@hostname (who am I and where am I)
+prompt_context() { }
+
+parse_git_dirty() {
+ local -a git_status
+ git_status=($(git status --porcelain 2>/dev/null))
+ if [[ ${#git_status[@]} -gt 0 ]]; then
+ echo "±"
+ fi
+}
+
+# Git: branch/detached head, dirty status
+prompt_git() {
+ (( $+commands[git] )) || return
+ if [[ "$(git config --get oh-my-zsh.hide-status 2>/dev/null)" = 1 ]]; then
+ return
+ fi
+ local PL_BRANCH_CHAR
+ () {
+ local LC_ALL="" LC_CTYPE="en_US.UTF-8"
+ PL_BRANCH_CHAR=$'' # 
+ }
+ local ref dirty mode repo_path
+
+ if [[ "$(git rev-parse --is-inside-work-tree 2>/dev/null)" = "true" ]]; then
+ repo_path=$(git rev-parse --git-dir 2>/dev/null)
+ dirty=$(parse_git_dirty)
+ ref=$(git symbolic-ref HEAD 2> /dev/null) || ref="➦ $(git rev-parse --short HEAD 2> /dev/null)"
+ if [[ -n $dirty ]]; then
+ prompt_segment yellow black
+ PL_BRANCH_CHAR=%{%F{yellow}%}''
+ else
+ prompt_segment green $CURRENT_FG
+ fi
+
+ if [[ -e "${repo_path}/BISECT_LOG" ]]; then
+ mode=" <B>"
+ elif [[ -e "${repo_path}/MERGE_HEAD" ]]; then
+ mode=" >M<"
+ elif [[ -e "${repo_path}/rebase" || -e "${repo_path}/rebase-apply" || -e "${repo_path}/rebase-merge" || -e "${repo_path}/../.dotest" ]]; then
+ mode=" >R>"
+ fi
+
+ setopt promptsubst
+ autoload -Uz vcs_info
+
+ zstyle ':vcs_info:*' enable git
+ zstyle ':vcs_info:*' get-revision true
+ zstyle ':vcs_info:*' check-for-changes true
+ zstyle ':vcs_info:*' stagedstr '✚'
+ zstyle ':vcs_info:*' unstagedstr '± '
+ zstyle ':vcs_info:*' formats ' %u%c'
+ zstyle ':vcs_info:*' actionformats ' %u%c'
+ vcs_info
+ echo -n "${ref/refs\/heads\//$PL_BRANCH_CHAR }${vcs_info_msg_0_%% }${mode}"
+ fi
+}
+
+prompt_bzr() {
+ (( $+commands[bzr] )) || return
+
+ # Test if bzr repository in directory hierarchy
+ local dir="$PWD"
+ while [[ ! -d "$dir/.bzr" ]]; do
+ [[ "$dir" = "/" ]] && return
+ dir="${dir:h}"
+ done
+
+ local bzr_status status_mod status_all revision
+ if bzr_status=$(bzr status 2>&1); then
+ status_mod=$(echo -n "$bzr_status" | head -n1 | grep "modified" | wc -m)
+ status_all=$(echo -n "$bzr_status" | head -n1 | wc -m)
+ revision=$(bzr log -r-1 --log-format line | cut -d: -f1)
+ if [[ $status_mod -gt 0 ]] ; then
+ prompt_segment yellow black "bzr@$revision ✚"
+ else
+ if [[ $status_all -gt 0 ]] ; then
+ prompt_segment yellow black "bzr@$revision"
+ else
+ prompt_segment green black "bzr@$revision"
+ fi
+ fi
+ fi
+}
+
+prompt_hg() {
+ (( $+commands[hg] )) || return
+ local rev st branch
+ if $(hg id >/dev/null 2>&1); then
+ if $(hg prompt >/dev/null 2>&1); then
+ if [[ $(hg prompt "{status|unknown}") = "?" ]]; then
+ # if files are not added
+ prompt_segment red white
+ st='±'
+ elif [[ -n $(hg prompt "{status|modified}") ]]; then
+ # if any modification
+ prompt_segment yellow black
+ st='±'
+ else
+ # if working copy is clean
+ prompt_segment green $CURRENT_FG
+ fi
+ echo -n $(hg prompt "☿ {rev}@{branch}") $st
+ else
+ st=""
+ rev=$(hg id -n 2>/dev/null | sed 's/[^-0-9]//g')
+ branch=$(hg id -b 2>/dev/null)
+ if `hg st | grep -q "^\?"`; then
+ prompt_segment red black
+ st='±'
+ elif `hg st | grep -q "^[MA]"`; then
+ prompt_segment yellow black
+ st='±'
+ else
+ prompt_segment green $CURRENT_FG
+ fi
+ echo -n "☿ $rev@$branch" $st
+ fi
+ fi
+}
+
+# Change prompt for HOME dir
+prompt_dir () {
+ if [[ "$PWD" == "$HOME" ]]; then
+ prompt_segment blue $CURRENT_FG ''
+ else
+ prompt_segment blue CURRENT_FG '%2~'
+ fi
+}
+
+# Virtualenv: current working virtualenv
+prompt_virtualenv() {
+ local virtualenv_path="$VIRTUAL_ENV"
+ if [[ -n $virtualenv_path && -n $VIRTUAL_ENV_DISABLE_PROMPT ]]; then
+ prompt_segment blue black "(`basename $virtualenv_path`)"
+ fi
+}
+
+# Status:
+# - was there an error
+# - am I root
+# - are there background jobs?
+prompt_status() {
+ local -a symbols
+
+ [[ $RETVAL -ne 0 ]] && symbols+=" %{%F{red}%}"
+ [[ $UID -eq 0 ]] && symbols+="%{%F{yellow}%}⚡"
+ [[ $(jobs -l | wc -l) -gt 0 ]] && symbols+="%{%F{cyan}%}⚙"
+
+ [[ -n "$symbols" ]] && prompt_segment black default "$symbols"
+}
+
+#AWS Profile:
+# - display current AWS_PROFILE name
+# - displays yellow on red if profile name contains 'production' or
+# ends in '-prod'
+# - displays black on green otherwise
+prompt_aws() {
+ [[ -z "$AWS_PROFILE" || "$SHOW_AWS_PROMPT" = false ]] && return
+ case "$AWS_PROFILE" in
+ *-prod|*production*) prompt_segment red yellow "AWS: $AWS_PROFILE" ;;
+ *) prompt_segment green black "AWS: $AWS_PROFILE" ;;
+ esac
+}
+
+## Main prompt
+build_prompt() {
+ RETVAL=$?
+ prompt_status
+ prompt_virtualenv
+ prompt_aws
+ prompt_context
+ prompt_dir
+ prompt_git
+ prompt_bzr
+ prompt_hg
+ prompt_end
+}
+
+PROMPT='%{%F{blue}%}  %{%f%b%k%}$(build_prompt) '
+bindkey -M vicmd '\e[C' vi-forward-char # ESC + right arrow
diff --git a/common/install.sh b/common/install.sh
new file mode 100755
index 0000000..4a2b209
--- /dev/null
+++ b/common/install.sh
@@ -0,0 +1,3715 @@
+#!/usr/bin/env bash
+
+# Created By: srdusr
+# Created On: Tue 06 Sep 2025 16:20:52 PM CAT
+# Project: Dotfiles installation script
+
+# TODO: allow optional change user/password, also optional change root password, first check if they are the same (auto)
+
+# Dependencies: git, curl
+
+set -euo pipefail # Exit on error, undefined vars, pipe failures
+
+#======================================
+# Variables & Configuration
+#======================================
+
+# Color definitions
+NOCOLOR='\033[0m'
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[0;33m'
+BLUE='\033[0;34m'
+CYAN='\033[0;36m'
+WHITE='\033[0;37m'
+BOLD='\033[1m'
+
+# Dotfiles configuration
+DOTFILES_URL='https://github.com/srdusr/dotfiles.git'
+DOTFILES_DIR="$HOME/.cfg"
+LOG_FILE="$HOME/.local/share/dotfiles_install.log"
+STATE_FILE="$HOME/.local/share/dotfiles_install_state"
+BACKUP_DIR="$HOME/.dotfiles-backup-$(date +%Y%m%d-%H%M%S)"
+PACKAGES_FILE="packages.yml"
+
+# Network connectivity check
+CONNECTIVITY_CHECKED=false
+INTERNET_AVAILABLE=false
+
+# Installation tracking
+INSTALL_SUMMARY=()
+
+FAILED_ITEMS=()
+SKIPPED_ITEMS=()
+COMPLETED_STEPS=()
+
+# Script options
+RESUME_MODE=false
+UPDATE_MODE=false
+VERBOSE_MODE=false
+DRY_RUN=false
+FORCE_MODE=false
+ASK_MODE=false # New: ask for each step
+INSTALL_MODE="ask" # ask, essentials, full, profile
+
+# Global variables for system detection
+CFG_OS=""
+DISTRO=""
+PACKAGE_MANAGER=""
+PACKAGE_UPDATE_CMD=""
+PACKAGE_INSTALL_CMD=""
+PRIVILEGE_TOOL=""
+PRIVILEGE_CACHED=false
+
+# Essential tools needed by this script
+ESSENTIAL_TOOLS=("git" "curl" "wget")
+PACKAGE_TOOLS=("yq" "jq")
+
+# Config command tracking
+CONFIG_COMMAND_AVAILABLE=false
+CONFIG_COMMAND_FILE=""
+
+# Steps can be skipped by providing a comma-separated list in SKIP_STEPS
+SKIP_STEPS="${SKIP_STEPS:-}"
+
+# Run control: run only a specific step, or start from a specific step
+RUN_ONLY_STEP="${RUN_ONLY_STEP:-}"
+RUN_FROM_STEP="${RUN_FROM_STEP:-}"
+__RUN_FROM_STARTED=false
+
+# Interactive per-step prompt even without --ask (opt-in)
+# Set INTERACTIVE_SKIP=true to be prompted for non-essential steps.
+INTERACTIVE_SKIP="${INTERACTIVE_SKIP:-false}"
+
+# Steps considered essential (should rarely be skipped)
+ESSENTIAL_STEPS=(
+ setup_environment
+ check_connectivity
+ detect_package_manager
+ install_dependencies
+)
+
+is_step_skipped() {
+ local step="$1"
+ [[ ",${SKIP_STEPS}," == *",${step},"* ]]
+}
+
+skip_step_if_requested() {
+ local step="$1"
+ if is_step_skipped "$step"; then
+ print_skip "Skipping step by request: $step"
+ mark_step_completed "$step"
+ return 1
+ fi
+ return 0
+}
+
+should_run_step() {
+ local step="$1"
+ # If RUN_ONLY_STEP is set, only run that exact step
+ if [[ -n "$RUN_ONLY_STEP" && "$step" != "$RUN_ONLY_STEP" ]]; then
+ print_skip "Skipping step (RUN_ONLY_STEP=$RUN_ONLY_STEP): $step"
+ return 1
+ fi
+ # If RUN_FROM_STEP is set, skip until we reach it, then run subsequent steps
+ if [[ -n "$RUN_FROM_STEP" && "$__RUN_FROM_STARTED" != true ]]; then
+ if [[ "$step" == "$RUN_FROM_STEP" ]]; then
+ __RUN_FROM_STARTED=true
+ else
+ print_skip "Skipping step until RUN_FROM_STEP=$RUN_FROM_STEP: $step"
+ return 1
+ fi
+ fi
+ return 0
+}
+
+# Installation profiles
+declare -A INSTALLATION_PROFILES=(
+ ["essentials"]="Essential packages only (git, curl, wget, vim, zsh)"
+ ["minimal"]="Minimal setup for basic development"
+ ["dev"]="Full development environment"
+ ["server"]="Server configuration"
+ ["full"]="Complete installation with all packages"
+)
+
+# Installation steps configuration
+declare -A INSTALLATION_STEPS=(
+ ["setup_environment"]="Setup installation environment"
+ ["check_connectivity"]="Check internet connectivity"
+ ["detect_package_manager"]="Detect or configure package manager"
+ ["install_dependencies"]="Install dependencies"
+ ["install_dotfiles"]="Install dotfiles repository"
+ ["setup_user_dirs"]="Setup user directories"
+ ["install_essentials"]="Install essential tools"
+ ["install_packages"]="Install system packages"
+ ["setup_shell"]="Setup shell environment"
+ ["setup_ssh"]="Setup SSH configuration"
+ ["configure_services"]="Configure system services"
+ ["setup_development_environment"]="Setup development environment"
+ ["apply_tweaks"]="Apply system tweaks"
+ ["deploy_config"]="Deploy config command and dotfiles"
+)
+
+# Step order
+STEP_ORDER=(
+ "setup_environment"
+ "check_connectivity"
+ "detect_package_manager"
+ "install_dependencies"
+ "install_dotfiles"
+ "deploy_config"
+ "setup_user_dirs"
+ "install_essentials"
+ "install_packages"
+ "setup_shell"
+ "setup_ssh"
+ "configure_services"
+ "setup_development_environment"
+ "apply_tweaks"
+)
+
+#======================================
+# State Management Functions
+#======================================
+
+save_state() {
+ local current_step="$1"
+ local status="$2"
+
+ mkdir -p "$(dirname "$STATE_FILE")"
+
+ {
+ echo "LAST_STEP=$current_step"
+ echo "STEP_STATUS=$status"
+ echo "TIMESTAMP=$(date +%s)"
+ echo "RESUME_AVAILABLE=true"
+ echo "PRIVILEGE_CACHED=$PRIVILEGE_CACHED"
+ echo "INSTALL_MODE=$INSTALL_MODE"
+ echo "COMPLETED_STEPS=(${COMPLETED_STEPS[*]})"
+ echo "CFG_OS=$CFG_OS"
+ echo "DISTRO=${DISTRO:-}"
+ echo "PACKAGE_MANAGER=${PACKAGE_MANAGER:-}"
+ echo "PRIVILEGE_TOOL=${PRIVILEGE_TOOL:-}"
+ echo "CONNECTIVITY_CHECKED=$CONNECTIVITY_CHECKED"
+ echo "INTERNET_AVAILABLE=$INTERNET_AVAILABLE"
+ } > "$STATE_FILE"
+}
+
+load_state() {
+ if [[ -f "$STATE_FILE" ]]; then
+ source "$STATE_FILE"
+ return 0
+ else
+ return 1
+ fi
+}
+
+clear_state() {
+ [[ -f "$STATE_FILE" ]] && rm -f "$STATE_FILE"
+}
+
+is_step_completed() {
+ local step="$1"
+ [[ " ${COMPLETED_STEPS[*]} " =~ " ${step} " ]]
+}
+
+mark_step_completed() {
+ local step="$1"
+ if ! is_step_completed "$step"; then
+ COMPLETED_STEPS+=("$step")
+ fi
+ save_state "$step" "completed"
+}
+
+mark_step_failed() {
+ local step="$1"
+ save_state "$step" "failed"
+}
+
+#======================================
+# UI Functions
+#======================================
+
+print_color() {
+ local color="$1"
+ local message="$2"
+ echo -e "${color}${message}${NOCOLOR}"
+
+ if [[ -n "${LOG_FILE:-}" && -f "$LOG_FILE" ]]; then
+ echo "$(date +'%Y-%m-%d %H:%M:%S') - $message" >> "$LOG_FILE"
+ fi
+}
+
+print_header() {
+ local title="$1"
+ local border_char="="
+ local border_length=60
+
+ echo
+ print_color "$CYAN" "$(printf '%*s' $border_length '' | tr ' ' "$border_char")"
+ print_color "$CYAN$BOLD" "$(printf '%*s' $(((border_length + ${#title}) / 2)) "$title")"
+ print_color "$CYAN" "$(printf '%*s' $border_length '' | tr ' ' "$border_char")"
+ echo
+}
+
+print_section() {
+ local title="$1"
+ echo
+ print_color "$BLUE$BOLD" "▶ $title"
+ print_color "$BLUE" "$(printf '%*s' $((${#title} + 2)) '' | tr ' ' '-')"
+}
+
+print_success() {
+ local message="$1"
+ print_color "$GREEN" "✓ $message"
+ INSTALL_SUMMARY+=("✓ $message")
+}
+
+print_error() {
+ local message="$1"
+ print_color "$RED" "✗ $message" >&2
+ FAILED_ITEMS+=("✗ $message")
+}
+
+print_warning() {
+ local message="$1"
+ print_color "$YELLOW" "⚠ $message"
+}
+
+print_info() {
+ local message="$1"
+ if [[ "$VERBOSE_MODE" == true ]] || [[ "${2:-}" == "always" ]]; then
+ print_color "$CYAN" "ℹ $message"
+ fi
+}
+
+print_skip() {
+ local message="$1"
+ print_color "$YELLOW" "⏭ $message"
+ SKIPPED_ITEMS+=("⏭ $message")
+}
+
+print_dry_run() {
+ local message="$1"
+ print_color "$CYAN" "[DRY RUN] $message"
+}
+
+#======================================
+# Network Connectivity Functions
+#======================================
+
+check_internet_connectivity() {
+ if [[ "$CONNECTIVITY_CHECKED" == true ]]; then
+ return $([[ "$INTERNET_AVAILABLE" == true ]] && echo 0 || echo 1)
+ fi
+
+ print_section "Checking Internet Connectivity"
+
+ local test_urls=("8.8.8.8" "1.1.1.1" "google.com" "github.com")
+
+ for url in "${test_urls[@]}"; do
+ if ping -c 1 -W 2 "$url" &>/dev/null || curl -s --connect-timeout 5 "https://$url" &>/dev/null; then
+ INTERNET_AVAILABLE=true
+ CONNECTIVITY_CHECKED=true
+ print_success "Internet connectivity confirmed"
+ return 0
+ fi
+ done
+
+ INTERNET_AVAILABLE=false
+ CONNECTIVITY_CHECKED=true
+ print_error "No internet connectivity detected"
+
+ # Try to connect to WiFi or prompt user
+ attempt_network_connection
+
+ return 1
+}
+
+attempt_network_connection() {
+ print_warning "Attempting to establish network connection..."
+
+ # Try NetworkManager
+ if command_exists nmcli; then
+ print_info "Available WiFi networks:"
+ nmcli device wifi list 2>/dev/null || print_warning "Could not list WiFi networks"
+
+ if prompt_user "Would you like to connect to a WiFi network?"; then
+ print_color "$YELLOW" "Enter WiFi network name (SSID): "
+ read -r wifi_ssid
+ if [[ -n "$wifi_ssid" ]]; then
+ print_color "$YELLOW" "Enter WiFi password: "
+ read -rs wifi_password
+ echo
+
+ if execute_with_privilege "nmcli device wifi connect '$wifi_ssid' password '$wifi_password'"; then
+ print_success "Connected to WiFi network: $wifi_ssid"
+ # Re-check connectivity
+ CONNECTIVITY_CHECKED=false
+ check_internet_connectivity
+ return $?
+ else
+ print_error "Failed to connect to WiFi network"
+ fi
+ fi
+ fi
+ fi
+
+ # Try other connection methods
+ if command_exists iwctl; then
+ print_info "You can also connect manually using iwctl"
+ fi
+
+ return 1
+}
+
+#======================================
+# System Detection Functions
+#======================================
+
+detect_os() {
+ case "$(uname -s)" in
+ Linux) CFG_OS="linux" ;;
+ Darwin) CFG_OS="macos" ;;
+ MINGW*|MSYS*|CYGWIN*) CFG_OS="windows" ;;
+ *) CFG_OS="unknown" ;;
+ esac
+
+ print_info "Detected OS: $CFG_OS" "always"
+}
+
+detect_privilege_tools() {
+ if [[ "$(id -u)" -eq 0 ]]; then
+ PRIVILEGE_TOOL=""
+ print_info "Running as root, no privilege escalation needed"
+ return 0
+ fi
+
+ for tool in sudo doas pkexec; do
+ if command -v "$tool" &>/dev/null; then
+ PRIVILEGE_TOOL="$tool"
+ print_success "Using privilege escalation tool: $PRIVILEGE_TOOL"
+ return 0
+ fi
+ done
+
+ print_warning "No privilege escalation tool found (sudo, doas, pkexec)"
+ PRIVILEGE_TOOL=""
+ return 1
+}
+
+test_privilege_access() {
+ if [[ "$PRIVILEGE_CACHED" == true ]]; then
+ return 0
+ fi
+
+ if [[ -z "$PRIVILEGE_TOOL" ]]; then
+ return 0 # Running as root or no privilege needed
+ fi
+
+ print_info "Testing privilege access..."
+ if "$PRIVILEGE_TOOL" -v &>/dev/null || echo "test" | "$PRIVILEGE_TOOL" -S true &>/dev/null; then
+ PRIVILEGE_CACHED=true
+ print_success "Privilege access confirmed"
+ return 0
+ else
+ print_error "Failed to obtain privilege access"
+ return 1
+ fi
+}
+
+detect_package_manager() {
+ print_section "Detecting Package Manager"
+ save_state "detect_package_manager" "started"
+
+ # First try to detect from OS release files
+ if [[ "$CFG_OS" == "linux" && -f /etc/os-release ]]; then
+ source /etc/os-release
+ case "$ID" in
+ arch|manjaro|endeavouros|artix)
+ DISTRO="$ID"
+ PACKAGE_MANAGER="pacman"
+ PACKAGE_UPDATE_CMD="pacman -Sy"
+ PACKAGE_INSTALL_CMD="pacman -S --noconfirm"
+ ;;
+ debian|ubuntu|mint|pop|elementary|zorin)
+ DISTRO="$ID"
+ PACKAGE_MANAGER="apt"
+ PACKAGE_UPDATE_CMD="apt-get update"
+ PACKAGE_INSTALL_CMD="apt-get install -y"
+ ;;
+ fedora|rhel|centos|rocky|almalinux)
+ DISTRO="$ID"
+ PACKAGE_MANAGER="dnf"
+ PACKAGE_UPDATE_CMD="dnf check-update"
+ PACKAGE_INSTALL_CMD="dnf install -y"
+ ;;
+ opensuse*|sles)
+ DISTRO="$ID"
+ PACKAGE_MANAGER="zypper"
+ PACKAGE_UPDATE_CMD="zypper refresh"
+ PACKAGE_INSTALL_CMD="zypper install -y"
+ ;;
+ gentoo)
+ DISTRO="$ID"
+ PACKAGE_MANAGER="portage"
+ PACKAGE_UPDATE_CMD="emerge --sync"
+ PACKAGE_INSTALL_CMD="emerge"
+ ;;
+ alpine)
+ DISTRO="$ID"
+ PACKAGE_MANAGER="apk"
+ PACKAGE_UPDATE_CMD="apk update"
+ PACKAGE_INSTALL_CMD="apk add"
+ ;;
+ void)
+ DISTRO="$ID"
+ PACKAGE_MANAGER="xbps"
+ PACKAGE_UPDATE_CMD="xbps-install -S"
+ PACKAGE_INSTALL_CMD="xbps-install -y"
+ ;;
+ nixos)
+ DISTRO="$ID"
+ PACKAGE_MANAGER="nix"
+ PACKAGE_UPDATE_CMD="nix-channel --update"
+ PACKAGE_INSTALL_CMD="nix-env -iA nixpkgs."
+ ;;
+ esac
+ elif [[ "$CFG_OS" == "macos" ]]; then
+ DISTRO="macos"
+ if command -v brew &>/dev/null; then
+ PACKAGE_MANAGER="brew"
+ PACKAGE_UPDATE_CMD="brew update"
+ PACKAGE_INSTALL_CMD="brew install"
+ else
+ PACKAGE_MANAGER="brew-install"
+ fi
+ fi
+
+ # Fallback: detect by available commands
+ if [[ -z "$PACKAGE_MANAGER" ]]; then
+ local managers=(
+ "pacman:pacman:pacman -Sy:pacman -S --noconfirm"
+ "apt:apt:apt-get update:apt-get install -y"
+ "dnf:dnf:dnf check-update:dnf install -y"
+ "yum:yum:yum check-update:yum install -y"
+ "zypper:zypper:zypper refresh:zypper install -y"
+ "emerge:portage:emerge --sync:emerge"
+ "apk:apk:apk update:apk add"
+ "xbps-install:xbps:xbps-install -S:xbps-install -y"
+ "nix-env:nix:nix-channel --update:nix-env -iA nixpkgs."
+ "pkg:pkg:pkg update:pkg install -y"
+ "brew:brew:brew update:brew install"
+ )
+
+ for manager in "${managers[@]}"; do
+ local cmd="${manager%%:*}"
+ local name="${manager#*:}"; name="${name%%:*}"
+ local update_cmd="${manager#*:*:}"; update_cmd="${update_cmd%%:*}"
+ local install_cmd="${manager##*:}"
+
+ if command -v "$cmd" &>/dev/null; then
+ PACKAGE_MANAGER="$name"
+ PACKAGE_UPDATE_CMD="$update_cmd"
+ PACKAGE_INSTALL_CMD="$install_cmd"
+ break
+ fi
+ done
+ fi
+
+ if [[ -n "$PACKAGE_MANAGER" ]]; then
+ print_success "Detected package manager: $PACKAGE_MANAGER"
+ [[ -n "$DISTRO" ]] && print_info "Distribution: $DISTRO"
+
+ # Try to override commands from packages.yml -> package_managers
+ # Find packages.yml in standard locations
+ local original_dir="$PWD"
+ cd "$HOME" 2>/dev/null || true
+ local packages_files=("$PACKAGES_FILE" "common/$PACKAGES_FILE" ".cfg/common/$PACKAGES_FILE")
+ local found_packages_file=""
+ for pf in "${packages_files[@]}"; do
+ if [[ -f "$pf" ]]; then
+ found_packages_file="$pf"
+ break
+ fi
+ done
+ cd "$original_dir" 2>/dev/null || true
+
+ if command_exists yq && [[ -n "$found_packages_file" ]]; then
+ # Prefer distro block, fallback to manager block
+ # Initialize to avoid set -u (nounset) issues before assignment
+ local pm_update="" pm_install=""
+ if [[ -n "$DISTRO" ]]; then
+ pm_update=$(yq eval ".package_managers.${DISTRO}.update" "$found_packages_file" 2>/dev/null | grep -v "^null$" || echo "")
+ pm_install=$(yq eval ".package_managers.${DISTRO}.install" "$found_packages_file" 2>/dev/null | grep -v "^null$" || echo "")
+ fi
+ if [[ -z "$pm_update" || -z "$pm_install" ]]; then
+ pm_update=$(yq eval ".package_managers.${PACKAGE_MANAGER}.update" "$found_packages_file" 2>/dev/null | grep -v "^null$" || echo "")
+ pm_install=$(yq eval ".package_managers.${PACKAGE_MANAGER}.install" "$found_packages_file" 2>/dev/null | grep -v "^null$" || echo "")
+ fi
+ if [[ -n "$pm_update" && -n "$pm_install" ]]; then
+ PACKAGE_UPDATE_CMD="$pm_update"
+ PACKAGE_INSTALL_CMD="$pm_install"
+ print_info "Using package manager commands from packages.yml"
+ fi
+ fi
+
+ # Export for compatibility with packages.yml custom commands that reference CFG_DISTRO
+ export CFG_DISTRO="$DISTRO"
+
+ mark_step_completed "detect_package_manager"
+ return 0
+ else
+ print_error "Could not detect package manager"
+ manual_package_manager_setup
+ return $?
+ fi
+}
+
+manual_package_manager_setup() {
+ print_warning "No supported package manager detected automatically"
+ print_info "Please provide package manager commands manually:"
+
+ while true; do
+ print_color "$YELLOW" "Enter package update command (e.g., 'apt-get update'): "
+ read -r PACKAGE_UPDATE_CMD
+ [[ -n "$PACKAGE_UPDATE_CMD" ]] && break
+ print_warning "Update command cannot be empty"
+ done
+
+ while true; do
+ print_color "$YELLOW" "Enter package install command (e.g., 'apt-get install -y'): "
+ read -r PACKAGE_INSTALL_CMD
+ [[ -n "$PACKAGE_INSTALL_CMD" ]] && break
+ print_warning "Install command cannot be empty"
+ done
+
+ PACKAGE_MANAGER="manual"
+ print_success "Manual package manager configuration set"
+ print_info "Update command: $PACKAGE_UPDATE_CMD"
+ print_info "Install command: $PACKAGE_INSTALL_CMD"
+
+ mark_step_completed "detect_package_manager"
+ return 0
+}
+
+#======================================
+# Utility Functions
+#======================================
+
+
+command_exists() {
+ command -v "$1" &>/dev/null
+}
+
+execute_command() {
+ local cmd="$*"
+
+ if [[ "$DRY_RUN" == true ]]; then
+ print_dry_run "$cmd"
+ return 0
+ fi
+
+ if [[ "$VERBOSE_MODE" == true ]]; then
+ print_info "Running: $cmd"
+ fi
+
+ eval "$cmd"
+}
+
+execute_with_privilege() {
+ local cmd="$*"
+
+ if [[ "$DRY_RUN" == true ]]; then
+ if [[ -n "$PRIVILEGE_TOOL" ]]; then
+ print_dry_run "$PRIVILEGE_TOOL $cmd"
+ else
+ print_dry_run "$cmd"
+ fi
+ return 0
+ fi
+
+ if [[ -n "$PRIVILEGE_TOOL" ]]; then
+ if [[ "$PRIVILEGE_CACHED" != true ]]; then
+ test_privilege_access || return 1
+ fi
+ eval "$PRIVILEGE_TOOL $cmd"
+ else
+ eval "$cmd"
+ fi
+}
+
+prompt_user() {
+ local question="$1"
+ local default="${2:-Y}"
+ local response
+
+ if [[ "$FORCE_MODE" == true ]]; then
+ print_info "Auto-answering '$question' with: $default"
+ [[ "$default" =~ ^[Yy] ]] && return 0 || return 1
+ fi
+
+ while true; do
+ if [[ "$default" == "Y" ]]; then
+ printf "%b%s%b" "$YELLOW" "$question [Y/n]: " "$NOCOLOR"
+ else
+ printf "%b%s%b" "$YELLOW" "$question [y/N]: " "$NOCOLOR"
+ fi
+
+ read -r response
+
+ if [[ -z "$response" ]]; then
+ response="$default"
+ fi
+
+ case "${response^^}" in
+ Y|YES) echo; return 0 ;;
+ N|NO) echo; return 1 ;;
+ *) echo; print_warning "Please answer Y/yes or N/no" ;;
+ esac
+ done
+}
+
+create_dir() {
+ local dir="$1"
+ local permissions="${2:-755}"
+
+ if [[ "$DRY_RUN" == true ]]; then
+ print_dry_run "Create directory: $dir (mode: $permissions)"
+ return 0
+ fi
+
+ if [[ ! -d "$dir" ]]; then
+ mkdir -p "$dir" || {
+ print_error "Failed to create directory: $dir"
+ return 1
+ }
+ chmod "$permissions" "$dir"
+ print_success "Created directory: $dir"
+ else
+ print_info "Directory already exists: $dir"
+ fi
+}
+
+setup_logging() {
+ local log_dir
+ log_dir="$(dirname "$LOG_FILE")"
+
+ if [[ ! -d "$log_dir" ]]; then
+ mkdir -p "$log_dir" || {
+ print_error "Failed to create log directory: $log_dir"
+ exit 1
+ }
+ fi
+
+ {
+ echo "======================================="
+ echo "Dotfiles Installation Log"
+ echo "Date: $(date)"
+ echo "User: $USER"
+ echo "Host: ${HOSTNAME:-$(hostname)}"
+ echo "OS: $(uname -s)"
+ echo "Install Mode: $INSTALL_MODE"
+ echo "======================================="
+ echo
+ } > "$LOG_FILE"
+
+ print_info "Log file initialized: $LOG_FILE" "always"
+}
+
+get_package_names() {
+ local package="$1"
+ local packages_file="${2:-}"
+
+ # If packages.yml is available, check for distribution-specific mappings
+ if [[ -n "$packages_file" ]] && [[ -f "$packages_file" ]] && command_exists yq; then
+ local distro_packages=""
+
+ # Try to get package name(s) for current distribution
+ case "$DISTRO" in
+ arch|manjaro|endeavouros|artix)
+ distro_packages=$(yq eval ".arch.$package" "$packages_file" 2>/dev/null | grep -v "^null$" || echo "")
+ ;;
+ debian|ubuntu|mint|pop|elementary|zorin)
+ distro_packages=$(yq eval ".debian.$package" "$packages_file" 2>/dev/null | grep -v "^null$" || echo "")
+ ;;
+ fedora|rhel|centos|rocky|almalinux)
+ distro_packages=$(yq eval ".rhel.$package" "$packages_file" 2>/dev/null | grep -v "^null$" || echo "")
+ ;;
+ opensuse*|sles)
+ distro_packages=$(yq eval ".opensuse.$package" "$packages_file" 2>/dev/null | grep -v "^null$" || echo "")
+ ;;
+ gentoo)
+ distro_packages=$(yq eval ".gentoo.$package" "$packages_file" 2>/dev/null | grep -v "^null$" || echo "")
+ ;;
+ alpine)
+ distro_packages=$(yq eval ".alpine.$package" "$packages_file" 2>/dev/null | grep -v "^null$" || echo "")
+ ;;
+ void)
+ distro_packages=$(yq eval ".void.$package" "$packages_file" 2>/dev/null | grep -v "^null$" || echo "")
+ ;;
+ macos)
+ # macOS uses array format, check if package exists in the list
+ if yq eval ".macos[]" "$packages_file" 2>/dev/null | grep -q "^$package$"; then
+ distro_packages="$package"
+ fi
+ ;;
+ esac
+
+ # Return the distribution-specific package name(s) if found
+ if [[ -n "$distro_packages" ]]; then
+ echo "$distro_packages"
+ return 0
+ fi
+ fi
+
+ # Fallback to original package name
+ echo "$package"
+}
+
+get_package_use_flags() {
+ local package="$1"
+ local packages_file="${2:-}"
+
+ # Only relevant for Gentoo/Portage
+ if [[ "$PACKAGE_MANAGER" != "portage" ]]; then
+ echo ""
+ return 0
+ fi
+
+ if [[ -n "$packages_file" ]] && [[ -f "$packages_file" ]] && command_exists yq; then
+ local use_flags
+ use_flags=$(yq eval ".gentoo_use_flags.$package" "$packages_file" 2>/dev/null | grep -v "^null$" || echo "")
+ echo "$use_flags"
+ else
+ echo ""
+ fi
+}
+
+#======================================
+# Dependency Installation Functions
+#======================================
+
+install_dependencies_if_missing() {
+ print_section "Checking for dependencies git, wget/curl"
+ save_state "install_dependencies" "started"
+
+ local missing_deps=()
+ local failed_deps=()
+
+ # Check for missing essential tools
+ for tool in "${ESSENTIAL_TOOLS[@]}"; do
+ if ! command_exists "$tool"; then
+ missing_deps+=("$tool")
+ fi
+ done
+
+ # If everything is already present, skip with a clear message
+ if [[ ${#missing_deps[@]} -eq 0 ]]; then
+ print_skip "All required dependencies are already installed"
+ mark_step_completed "install_dependencies"
+ return 0
+ fi
+
+ # If no internet and dependencies are missing, try offline packages
+ if [[ "$INTERNET_AVAILABLE" != true ]] && [[ ${#missing_deps[@]} -gt 0 ]]; then
+ print_warning "No internet connection available"
+ print_info "Attempting to install dependencies from local packages..."
+
+ # Try to install from local package cache
+ for tool in "${missing_deps[@]}"; do
+ if install_package_offline "$tool"; then
+ print_success "Installed $tool from local cache"
+ else
+ failed_deps+=("$tool")
+ fi
+ done
+ elif [[ ${#missing_deps[@]} -gt 0 ]]; then
+ # Online installation
+ print_info "Installing missing dependencies: ${missing_deps[*]}"
+ update_package_database
+
+ for tool in "${missing_deps[@]}"; do
+ if install_single_package "$tool" "dependency"; then
+ print_success "Installed dependency: $tool"
+ else
+ failed_deps+=("$tool")
+ fi
+ done
+ fi
+
+ if [[ ${#failed_deps[@]} -gt 0 ]]; then
+ print_error "Failed to install dependencies: ${failed_deps[*]}"
+ mark_step_failed "install_dependencies"
+ return 1
+ else
+ print_success "Dependencies satisfied: ${missing_deps[*]}"
+ mark_step_completed "install_dependencies"
+ return 0
+ fi
+}
+
+install_package_offline() {
+ local package="$1"
+
+ case "$PACKAGE_MANAGER" in
+ pacman)
+ # Check if package is in cache
+ if execute_with_privilege "pacman -U /var/cache/pacman/pkg/${package}-*.pkg.tar.*" 2>/dev/null; then
+ return 0
+ fi
+ ;;
+ apt)
+ # Try from local cache
+ if execute_with_privilege "apt-get install --no-download '$package'" 2>/dev/null; then
+ return 0
+ fi
+ ;;
+ esac
+
+ return 1
+}
+
+#======================================
+# Package Management Functions
+#======================================
+
+
+install_single_package() {
+ local package="$1"
+ local package_type="${2:-system}"
+ local packages_file="${3:-}"
+
+ # Get the correct package name(s) for this distro - can be multiple packages
+ local pkg_names
+ pkg_names=$(get_package_names "$package" "$packages_file")
+
+ # Get USE flags for Gentoo
+ local use_flags
+ use_flags=$(get_package_use_flags "$package" "$packages_file")
+
+ print_info "Installing $package_type package: $package -> $pkg_names"
+
+ # Handle multiple packages
+ local install_success=true
+ for pkg_name in $pkg_names; do
+ print_info "Installing: $pkg_name"
+
+ case "$PACKAGE_MANAGER" in
+ pacman)
+ execute_with_privilege "$PACKAGE_INSTALL_CMD '$pkg_name'" || install_success=false
+ ;;
+ apt)
+ execute_with_privilege "$PACKAGE_INSTALL_CMD '$pkg_name'" || install_success=false
+ ;;
+ dnf|yum)
+ execute_with_privilege "$PACKAGE_INSTALL_CMD '$pkg_name'" || install_success=false
+ ;;
+ zypper)
+ execute_with_privilege "$PACKAGE_INSTALL_CMD '$pkg_name'" || install_success=false
+ ;;
+ portage)
+ local emerge_cmd="$PACKAGE_INSTALL_CMD"
+ if [[ -n "$use_flags" ]]; then
+ emerge_cmd="USE='$use_flags' $PACKAGE_INSTALL_CMD"
+ print_info "Using USE flags for $pkg_name: $use_flags"
+ fi
+ execute_with_privilege "$emerge_cmd '$pkg_name'" || install_success=false
+ ;;
+ apk)
+ execute_with_privilege "$PACKAGE_INSTALL_CMD '$pkg_name'" || install_success=false
+ ;;
+ xbps)
+ execute_with_privilege "$PACKAGE_INSTALL_CMD '$pkg_name'" || install_success=false
+ ;;
+ nix)
+ execute_command "$PACKAGE_INSTALL_CMD$pkg_name" || install_success=false
+ ;;
+ brew)
+ execute_command "$PACKAGE_INSTALL_CMD '$pkg_name'" || install_success=false
+ ;;
+ brew-install)
+ print_error "Homebrew not installed. Please install it first."
+ return 1
+ ;;
+ manual)
+ execute_with_privilege "$PACKAGE_INSTALL_CMD '$pkg_name'" || install_success=false
+ ;;
+ *)
+ print_error "Package manager '$PACKAGE_MANAGER' not supported"
+ return 1
+ ;;
+ esac
+ done
+
+ return $([[ "$install_success" == true ]] && echo 0 || echo 1)
+}
+
+update_package_database() {
+ print_info "Updating package database..."
+
+ case "$PACKAGE_MANAGER" in
+ pacman)
+ execute_with_privilege "$PACKAGE_UPDATE_CMD" ;;
+ apt)
+ execute_with_privilege "$PACKAGE_UPDATE_CMD" ;;
+ dnf|yum)
+ execute_with_privilege "$PACKAGE_UPDATE_CMD" || true ;;
+ zypper)
+ execute_with_privilege "$PACKAGE_UPDATE_CMD" ;;
+ portage)
+ execute_with_privilege "$PACKAGE_UPDATE_CMD" ;;
+ apk)
+ execute_with_privilege "$PACKAGE_UPDATE_CMD" ;;
+ xbps)
+ execute_with_privilege "$PACKAGE_UPDATE_CMD" ;;
+ brew)
+ execute_command "$PACKAGE_UPDATE_CMD" ;;
+ manual)
+ execute_with_privilege "$PACKAGE_UPDATE_CMD" ;;
+ *)
+ print_info "Package database update not needed for $PACKAGE_MANAGER" ;;
+ esac
+}
+
+install_homebrew() {
+ if command_exists brew; then
+ print_info "Homebrew already installed"
+ return 0
+ fi
+
+ print_info "Installing Homebrew..."
+ if execute_command '/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'; then
+ print_success "Homebrew installed"
+ PACKAGE_MANAGER="brew"
+
+ # Add to PATH for current session
+ if [[ -f "/opt/homebrew/bin/brew" ]]; then
+ eval "$(/opt/homebrew/bin/brew shellenv)"
+ elif [[ -f "/usr/local/bin/brew" ]]; then
+ eval "$(/usr/local/bin/brew shellenv)"
+ fi
+ return 0
+ else
+ print_error "Failed to install Homebrew"
+ return 1
+ fi
+}
+
+install_yq() {
+ if command_exists yq; then
+ print_info "yq already installed"
+ return 0
+ fi
+
+ print_info "Installing yq..."
+
+ local bin_dir="$HOME/.local/bin"
+ create_dir "$bin_dir"
+
+ local yq_path="$bin_dir/yq"
+ local yq_url=""
+
+ case "$(uname -m)" in
+ x86_64|amd64)
+ yq_url="https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64" ;;
+ aarch64|arm64)
+ yq_url="https://github.com/mikefarah/yq/releases/latest/download/yq_linux_arm64" ;;
+ armv7l)
+ yq_url="https://github.com/mikefarah/yq/releases/latest/download/yq_linux_arm" ;;
+ *)
+ print_error "Unsupported architecture: $(uname -m)"
+ return 1 ;;
+ esac
+
+ if execute_command "curl -L '$yq_url' -o '$yq_path'"; then
+ execute_command "chmod +x '$yq_path'"
+
+ # Add to PATH if not already there
+ if [[ ":$PATH:" != *":$bin_dir:"* ]]; then
+ export PATH="$bin_dir:$PATH"
+ fi
+
+ print_success "yq installed successfully"
+ return 0
+ else
+ print_error "Failed to install yq"
+ return 1
+ fi
+}
+
+parse_packages_from_yaml() {
+ local packages_file="$1"
+ local section="$2"
+ local packages=()
+
+ if [[ ! -f "$packages_file" ]]; then
+ print_warning "Package file not found: $packages_file"
+ return 1
+ fi
+
+ if ! command_exists yq; then
+ print_error "yq not available for parsing packages.yml"
+ return 1
+ fi
+
+ # Try to parse packages from the specified section
+ if yq eval ".$section" "$packages_file" &>/dev/null; then
+ mapfile -t packages < <(yq eval ".$section[]" "$packages_file" 2>/dev/null | grep -v "^null$" || true)
+ fi
+
+ # Output packages (one per line)
+ printf '%s\n' "${packages[@]}"
+}
+
+get_profile_package_groups() {
+ local packages_file="$1"
+ local profile="$2"
+ local groups=()
+
+ if [[ ! -f "$packages_file" ]]; then
+ print_warning "Package file not found: $packages_file"
+ return 1
+ fi
+
+ # Get package groups for the profile from the profiles section
+ if yq eval ".profiles.$profile.packages" "$packages_file" &>/dev/null; then
+ mapfile -t groups < <(yq eval ".profiles.$profile.packages[]" "$packages_file" 2>/dev/null | grep -v "^null$" || true)
+ fi
+
+ # Fallback to old method if profiles section doesn't exist
+ if [[ ${#groups[@]} -eq 0 ]]; then
+ case "$profile" in
+ essentials)
+ groups=("common" "essentials") ;;
+ minimal)
+ groups=("common" "essentials" "minimal") ;;
+ dev)
+ groups=("common" "essentials" "minimal" "dev") ;;
+ server)
+ groups=("common" "essentials" "minimal" "server") ;;
+ full)
+ groups=("common" "essentials" "minimal" "dev" "server" "desktop" "wm" "media" "fonts") ;;
+ *)
+ print_error "Unknown profile: $profile"
+ return 1
+ ;;
+ esac
+ fi
+
+ printf '%s\n' "${groups[@]}"
+}
+
+install_packages_from_yaml() {
+ local packages_file="$1"
+ local profile="${2:-essentials}"
+ local failed_packages=()
+ local installed_count=0
+
+ print_section "Installing Packages (Profile: $profile)"
+
+ if [[ ! -f "$packages_file" ]]; then
+ print_warning "Package file not found: $packages_file, skipping package installation"
+ return 0
+ fi
+
+ # Get package groups to install based on profile
+ local groups
+ mapfile -t groups < <(get_profile_package_groups "$packages_file" "$profile")
+
+ if [[ ${#groups[@]} -eq 0 ]]; then
+ print_error "No package groups found for profile: $profile"
+ return 1
+ fi
+
+ print_info "Installing package groups for $profile: ${groups[*]}"
+
+ # Install packages from each group
+ for group in "${groups[@]}"; do
+ print_info "Installing packages from group: $group"
+
+ local packages
+ mapfile -t packages < <(parse_packages_from_yaml "$packages_file" "$group")
+
+ if [[ ${#packages[@]} -eq 0 ]]; then
+ print_info "No packages found in group: $group"
+ continue
+ fi
+
+ print_info "Found ${#packages[@]} packages in group $group: ${packages[*]}"
+
+ for package in "${packages[@]}"; do
+ [[ -z "$package" ]] && continue
+
+ if install_single_package "$package" "$group" "$packages_file"; then
+ print_success "Installed: $package"
+ ((installed_count++))
+ else
+ print_error "Failed to install: $package"
+ failed_packages+=("$package")
+ fi
+ done
+ done
+
+ # Handle development environment setup
+ if yq eval ".profiles.$profile.enable_development" "$packages_file" 2>/dev/null | grep -q "true"; then
+ setup_development_environment "$packages_file"
+ fi
+
+ print_info "Package installation summary:"
+ print_color "$GREEN" " Installed: $installed_count"
+ print_color "$RED" " Failed: ${#failed_packages[@]}"
+
+ if [[ ${#failed_packages[@]} -gt 0 ]]; then
+ print_warning "Failed packages: ${failed_packages[*]}"
+ print_info "Failed packages will be listed in the final summary"
+ return 0
+ else
+ print_success "All packages installed successfully"
+ return 0
+ fi
+}
+
+#======================================
+# Dotfiles Management System (Config Command)
+#======================================
+
+check_existing_config_command() {
+ print_info "Checking for existing config command..."
+
+ # Known function files where config might already be defined
+ local function_files=(
+ "$HOME/.config/zsh/user/functions.zsh"
+ "$HOME/.config/zsh/.zshrc"
+ "$HOME/.zshrc"
+ "$HOME/.bashrc"
+ "$HOME/.profile"
+ )
+
+ # Check if config command is already available in current shell
+ if type config >/dev/null 2>&1; then
+ CONFIG_COMMAND_AVAILABLE=true
+ print_success "Config command already available in current shell"
+ return 0
+ fi
+
+ # Check files for existing config function definition
+ for f in "${function_files[@]}"; do
+ if [[ -f "$f" ]]; then
+ if grep -q '^\s*config\s*()' "$f" || grep -q '# Dotfiles Management System' "$f"; then
+ CONFIG_COMMAND_AVAILABLE=true
+ CONFIG_COMMAND_FILE="$f"
+ print_success "Config command found in: $f"
+ return 0
+ fi
+ fi
+ done
+
+ CONFIG_COMMAND_AVAILABLE=false
+ print_info "No existing config command found"
+ return 1
+}
+
+install_config_command() {
+ print_section "Installing Config Command"
+
+ if check_existing_config_command; then
+ if [[ "$FORCE_MODE" == true ]]; then
+ print_info "Force mode: reinstalling config command"
+ else
+ return 0
+ fi
+ fi
+
+ # Determine current shell and profile file
+ local current_shell
+ current_shell=$(basename "$SHELL")
+
+ local profile_file=""
+ case "$current_shell" in
+ bash)
+ if [[ -f "$HOME/.bashrc" ]]; then
+ profile_file="$HOME/.bashrc"
+ else
+ profile_file="$HOME/.bashrc"
+ touch "$profile_file"
+ fi
+ ;;
+ zsh)
+ if [[ -f "$HOME/.config/zsh/user/functions.zsh" ]]; then
+ profile_file="$HOME/.config/zsh/user/functions.zsh"
+ elif [[ -f "$HOME/.config/zsh/.zshrc" ]]; then
+ profile_file="$HOME/.config/zsh/.zshrc"
+ elif [[ -f "$HOME/.zshrc" ]]; then
+ profile_file="$HOME/.zshrc"
+ else
+ profile_file="$HOME/.zshrc"
+ touch "$profile_file"
+ fi
+ ;;
+ *)
+ if [[ -f "$HOME/.profile" ]]; then
+ profile_file="$HOME/.profile"
+ else
+ profile_file="$HOME/.profile"
+ touch "$profile_file"
+ fi
+ ;;
+ esac
+
+ if [[ ! -w "$profile_file" ]]; then
+ print_error "Cannot write to profile file: $profile_file"
+ return 1
+ fi
+
+ # Check if config function already exists in the target file
+ if grep -q "# Dotfiles Management System" "$profile_file" 2>/dev/null; then
+ print_info "Config function already exists in $profile_file"
+ CONFIG_COMMAND_AVAILABLE=true
+ CONFIG_COMMAND_FILE="$profile_file"
+ return 0
+ fi
+
+ print_info "Adding config function to: $profile_file"
+
+ # Add the config function
+ cat >> "$profile_file" << 'EOF'
+
+# Dotfiles Management System
+if [[ -d "$HOME/.cfg" && -d "$HOME/.cfg/refs" ]]; then
+ # Core git wrapper with repository as work-tree
+ _config() {
+ git --git-dir="$HOME/.cfg" --work-tree="$HOME/.cfg" "$@"
+ }
+
+ # Detect OS
+ case "$(uname -s)" in
+ Linux) CFG_OS="linux" ;;
+ Darwin) CFG_OS="macos" ;;
+ MINGW*|MSYS*|CYGWIN*) CFG_OS="windows" ;;
+ *) CFG_OS="other" ;;
+ esac
+
+ # Map system path to repository path
+ _repo_path() {
+ local f="$1"
+
+ # If it's an absolute path that's not in HOME, handle it specially
+ if [[ "$f" == /* && "$f" != "$HOME/"* ]]; then
+ echo "$CFG_OS/${f#/}"
+ return
+ fi
+
+ # Check for paths that should go to the repository root
+ case "$f" in
+ common/*|linux/*|macos/*|windows/*|profile/*|README.md)
+ echo "$f"
+ return
+ ;;
+ "$HOME/"*)
+ f="${f#$HOME/}"
+ ;;
+ esac
+
+ # Default: put under OS-specific home
+ echo "$CFG_OS/home/$f"
+ }
+
+ _sys_path() {
+ local repo_path="$1"
+ local os_path_pattern="$CFG_OS/"
+
+ # Handle OS-specific files that are not in the home subdirectory
+ if [[ "$repo_path" == "$os_path_pattern"* && "$repo_path" != */home/* ]]; then
+ echo "/${repo_path#$os_path_pattern}"
+ return
+ fi
+
+ case "$repo_path" in
+ # Common configs → OS-specific config dirs
+ common/config/*)
+ case "$CFG_OS" in
+ linux)
+ local base="${XDG_CONFIG_HOME:-$HOME/.config}"
+ echo "$base/${repo_path#common/config/}"
+ ;;
+ macos)
+ echo "$HOME/Library/Application Support/${repo_path#common/config/}"
+ ;;
+ windows)
+ echo "$LOCALAPPDATA\\${repo_path#common/config/}"
+ ;;
+ *)
+ echo "$HOME/.config/${repo_path#common/config/}"
+ ;;
+ esac
+ ;;
+
+ # Common assets → stay in repo
+ common/assets/*)
+ echo "$HOME/.cfg/$repo_path"
+ ;;
+
+ # Other common files (dotfiles like .bashrc, .gitconfig, etc.) → $HOME
+ common/*)
+ echo "$HOME/${repo_path#common/}"
+ ;;
+
+ # OS-specific home
+ */home/*)
+ echo "$HOME/${repo_path#*/home/}"
+ ;;
+
+ # Profile configs and README → stay in repo
+ profile/*|README.md)
+ echo "$HOME/.cfg/$repo_path"
+ ;;
+
+ # Default fallback
+ *)
+ echo "$HOME/.cfg/$repo_path"
+ ;;
+
+ esac
+ }
+
+ # Prompts for sudo if needed and runs the command
+ _sudo_prompt() {
+ if [[ $EUID -eq 0 ]]; then
+ "$@"
+ else
+ if command -v sudo >/dev/null; then
+ sudo "$@"
+ elif command -v doas >/dev/null; then
+ doas "$@"
+ elif command -v pkexec >/dev/null; then
+ pkexec "$@"
+ else
+ echo "Error: No privilege escalation tool found."
+ return 1
+ fi
+ fi
+ }
+
+ # Main config command
+ config() {
+ local cmd="$1"; shift
+ local target_dir=""
+
+ # Parse optional --target flag for add
+ if [[ "$cmd" == "add" ]]; then
+ while [[ "$1" == --* ]]; do
+ case "$1" in
+ --target|-t)
+ target_dir="$2"
+ shift 2
+ ;;
+ *)
+ echo "Unknown option: $1"
+ return 1
+ ;;
+ esac
+ done
+ fi
+
+ case "$cmd" in
+ add)
+ local file_path
+ for file_path in "$@"; do
+ local repo_path
+ if [[ -n "$target_dir" ]]; then
+ local rel_path
+ if [[ "$file_path" == /* ]]; then
+ rel_path="$(basename "$file_path")"
+ else
+ rel_path="$file_path"
+ fi
+ repo_path="$target_dir/$rel_path"
+ else
+ repo_path="$(_repo_path "$file_path")"
+ fi
+
+ local full_repo_path="$HOME/.cfg/$repo_path"
+ mkdir -p "$(dirname "$full_repo_path")"
+ cp -a "$file_path" "$full_repo_path"
+
+ git --git-dir="$HOME/.cfg" --work-tree="$HOME/.cfg" add "$repo_path"
+
+ echo "Added: $file_path -> $repo_path"
+ done
+ ;;
+ rm)
+ local rm_opts=""
+ local file_path_list=()
+
+ for arg in "$@"; do
+ if [[ "$arg" == "-"* ]]; then
+ rm_opts+=" $arg"
+ else
+ file_path_list+=("$arg")
+ fi
+ done
+
+ for file_path in "${file_path_list[@]}"; do
+ local repo_path="$(_repo_path "$file_path")"
+
+ if [[ "$rm_opts" == *"-r"* ]]; then
+ _config rm --cached -r "$repo_path"
+ else
+ _config rm --cached "$repo_path"
+ fi
+
+ eval "rm $rm_opts \"$file_path\""
+ echo "Removed: $file_path"
+ done
+ ;;
+ sync)
+ local direction="${1:-to-repo}"; shift
+ _config ls-files | while read -r repo_file; do
+ local sys_file="$(_sys_path "$repo_file")"
+ local full_repo_path="$HOME/.cfg/$repo_file"
+ if [[ "$direction" == "to-repo" ]]; then
+ if [[ -e "$sys_file" && -n "$(diff "$full_repo_path" "$sys_file" 2>/dev/null || echo "diff")" ]]; then
+ cp -a "$sys_file" "$full_repo_path"
+ echo "Synced to repo: $sys_file"
+ fi
+ elif [[ "$direction" == "from-repo" ]]; then
+ if [[ -e "$full_repo_path" && -n "$(diff "$full_repo_path" "$sys_file" 2>/dev/null || echo "diff")" ]]; then
+ local dest_dir="$(dirname "$sys_file")"
+ if [[ "$sys_file" == /* && "$sys_file" != "$HOME/"* ]]; then
+ _sudo_prompt mkdir -p "$dest_dir"
+ _sudo_prompt cp -a "$full_repo_path" "$sys_file"
+ else
+ mkdir -p "$dest_dir"
+ cp -a "$full_repo_path" "$sys_file"
+ fi
+ echo "Synced from repo: $sys_file"
+ fi
+ fi
+ done
+ ;;
+ status)
+ local auto_synced=()
+ while read -r repo_file; do
+ local sys_file="$(_sys_path "$repo_file")"
+ local full_repo_path="$HOME/.cfg/$repo_file"
+ if [[ -e "$sys_file" && -e "$full_repo_path" ]]; then
+ if ! diff -q "$full_repo_path" "$sys_file" >/dev/null 2>&1; then
+ cp -fa "$sys_file" "$full_repo_path"
+ auto_synced+=("$repo_file")
+ fi
+ fi
+ done < <(_config ls-files)
+ if [[ ${#auto_synced[@]} -gt 0 ]]; then
+ echo "=== Auto-synced Files ==="
+ for repo_file in "${auto_synced[@]}"; do
+ echo "synced: $(_sys_path "$repo_file") -> $repo_file"
+ done
+ echo
+ fi
+ _config status
+ echo
+ ;;
+ deploy)
+ _config ls-files | while read -r repo_file; do
+ local full_repo_path="$HOME/.cfg/$repo_file"
+ local sys_file="$(_sys_path "$repo_file")"
+
+ if [[ -e "$full_repo_path" && -n "$sys_file" ]]; then
+ local dest_dir
+ dest_dir="$(dirname "$sys_file")"
+
+ # Create destination if needed
+ if [[ "$sys_file" == /* && "$sys_file" != "$HOME/"* ]]; then
+ _sudo_prompt mkdir -p "$dest_dir"
+ _sudo_prompt cp -a "$full_repo_path" "$sys_file"
+ else
+ mkdir -p "$dest_dir"
+ cp -a "$full_repo_path" "$sys_file"
+ fi
+
+ echo "Deployed: $repo_file -> $sys_file"
+ fi
+ done
+ ;;
+ checkout)
+ echo "Checking out dotfiles from .cfg..."
+ _config ls-files | while read -r repo_file; do
+ local full_repo_path="$HOME/.cfg/$repo_file"
+ local sys_file="$(_sys_path "$repo_file")"
+
+ if [[ -e "$full_repo_path" && -n "$sys_file" ]]; then
+ local dest_dir
+ dest_dir="$(dirname "$sys_file")"
+
+ # Create destination if it doesn't exist
+ if [[ "$sys_file" == /* && "$sys_file" != "$HOME/"* ]]; then
+ _sudo_prompt mkdir -p "$dest_dir"
+ _sudo_prompt cp -a "$full_repo_path" "$sys_file"
+ else
+ mkdir -p "$dest_dir"
+ cp -a "$full_repo_path" "$sys_file"
+ fi
+
+ echo "Checked out: $repo_file -> $sys_file"
+ fi
+ done
+ ;;
+ backup)
+ local timestamp=$(date +%Y%m%d%H%M%S)
+ local backup_dir="$HOME/.dotfiles_backup/$timestamp"
+ echo "Backing up existing dotfiles to $backup_dir..."
+
+ _config ls-files | while read -r repo_file; do
+ local sys_file="$(_sys_path "$repo_file")"
+ if [[ -e "$sys_file" ]]; then
+ local dest_dir_full="$backup_dir/$(dirname "$repo_file")"
+ mkdir -p "$dest_dir_full"
+ cp -a "$sys_file" "$backup_dir/$repo_file"
+ fi
+ done
+ echo "Backup complete. To restore, copy files from $backup_dir to their original locations."
+ ;;
+ *)
+ _config "$cmd" "$@"
+ ;;
+ esac
+ }
+fi
+EOF
+
+ if [[ $? -eq 0 ]]; then
+ print_success "Config command added to: $profile_file"
+ CONFIG_COMMAND_AVAILABLE=true
+ CONFIG_COMMAND_FILE="$profile_file"
+
+ # Source the file to make config command available immediately
+ # shellcheck disable=SC1090
+ source "$profile_file" 2>/dev/null || print_warning "Failed to source $profile_file"
+
+ return 0
+ else
+ print_error "Failed to add config command to $profile_file"
+ return 1
+ fi
+}
+
+deploy_config() {
+ print_section "Deploying Configuration"
+ save_state "deploy_config" "started"
+
+ # Ensure config command is available
+ if [[ "$CONFIG_COMMAND_AVAILABLE" != true ]]; then
+ install_config_command || {
+ print_error "Failed to install config command"
+ mark_step_failed "deploy_config"
+ return 1
+ }
+ fi
+
+ # Deploy dotfiles from repository to system
+ if [[ -d "$DOTFILES_DIR" ]]; then
+ print_info "Checking out dotfiles from repository..."
+
+ # First, checkout files from the bare repository to restore directory structure
+ if [[ "$DRY_RUN" == true ]]; then
+ print_dry_run "config checkout"
+ else
+ # Source the config function if available
+ if type config >/dev/null 2>&1; then
+ print_info "Using config command to checkout files..."
+ if config checkout; then
+ print_success "Files checked out from repository"
+ else
+ print_warning "Some files may have failed to checkout, trying force checkout..."
+ config checkout -f || print_warning "Force checkout also had issues"
+ fi
+ else
+ # Fallback: use git directly
+ print_info "Using git directly to checkout files..."
+ # IMPORTANT: use $HOME/.cfg as work-tree, never the bare repo path
+ if git --git-dir="$DOTFILES_DIR" --work-tree="$HOME/.cfg" checkout HEAD -- . 2>/dev/null; then
+ print_success "Files checked out using git directly"
+ else
+ print_warning "Git checkout had issues, continuing anyway..."
+ fi
+ fi
+ fi
+
+ # Backup existing files prior to deployment (prompt, allow skip)
+ if [[ "$DRY_RUN" == true ]]; then
+ print_dry_run "Backup existing dotfiles prior to deployment"
+ else
+ if [[ "$FORCE_MODE" == true ]]; then
+ # In force mode, perform backup without prompting
+ backup_existing_dotfiles || print_warning "Backup encountered issues (continuing)"
+ else
+ if prompt_user "Backup existing dotfiles before deployment?"; then
+ backup_existing_dotfiles || print_warning "Backup encountered issues (continuing)"
+ else
+ print_skip "User chose to skip backup before deployment"
+ fi
+ fi
+ fi
+
+ print_info "Deploying dotfiles from repository to system locations..."
+
+ # Verify config command is working
+ if ! verify_config_command; then
+ print_warning "Config command not working properly, using manual deployment"
+ manual_deploy_dotfiles
+ else
+ print_info "Config command available, deploying files..."
+
+ if [[ "$DRY_RUN" == true ]]; then
+ print_dry_run "config deploy"
+ else
+ # Use the config function to deploy files
+ if config deploy; then
+ print_success "Dotfiles deployed successfully"
+ else
+ print_warning "Some files may have failed to deploy"
+ fi
+ fi
+ fi
+
+ # Set appropriate permissions
+ set_dotfile_permissions
+
+ else
+ print_warning "Dotfiles directory not found, skipping deployment"
+ fi
+
+ mark_step_completed "deploy_config"
+}
+
+verify_config_command() {
+ # Always verify the function is actually available in this shell
+ if type config >/dev/null 2>&1; then
+ CONFIG_COMMAND_AVAILABLE=true
+ print_success "Config command is available and working"
+ return 0
+ fi
+ # Try sourcing the detected profile file if known
+ if [[ -n "$CONFIG_COMMAND_FILE" && -f "$CONFIG_COMMAND_FILE" ]]; then
+ # shellcheck disable=SC1090
+ source "$CONFIG_COMMAND_FILE" 2>/dev/null || true
+ if type config >/dev/null 2>&1; then
+ CONFIG_COMMAND_AVAILABLE=true
+ print_success "Config command is available and working"
+ return 0
+ fi
+ fi
+ print_warning "Config command not available"
+ return 1
+}
+
+# Manual deployment function (fallback when config command not available)
+manual_deploy_dotfiles() {
+ print_info "Using manual deployment method..."
+
+ if [[ ! -d "$DOTFILES_DIR" ]]; then
+ print_error "Dotfiles directory not found: $DOTFILES_DIR"
+ return 1
+ fi
+
+ # Source locations are always within the checked-out work-tree ($HOME/.cfg)
+ local os_dir="$HOME/.cfg/$CFG_OS"
+ local common_dir="$HOME/.cfg/common"
+
+ deploy_file() {
+ local repo_file="$1"
+ local rel_path sys_file sys_dir base
+
+ # Determine destination based on repo path
+ rel_path="${repo_file#$DOTFILES_DIR/}"
+
+ # OS-specific files outside home
+ if [[ "$rel_path" == "$CFG_OS/"* && "$rel_path" != */home/* ]]; then
+ sys_file="/${rel_path#$CFG_OS/}"
+ else
+ case "$rel_path" in
+ common/config/*)
+ case "$CFG_OS" in
+ linux)
+ base="${XDG_CONFIG_HOME:-$HOME/.config}"
+ sys_file="$base/${rel_path#common/config/}"
+ ;;
+ macos)
+ sys_file="$HOME/Library/Application Support/${rel_path#common/config/}"
+ ;;
+ windows)
+ sys_file="$LOCALAPPDATA\\${rel_path#common/config/}"
+ ;;
+ *)
+ sys_file="$HOME/.config/${rel_path#common/config/}"
+ ;;
+ esac
+ ;;
+ common/assets/*)
+ # Assets are repo-internal; do not deploy to filesystem
+ return 0
+ ;;
+ common/*)
+ sys_file="$HOME/${rel_path#common/}"
+ ;;
+ */home/*)
+ sys_file="$HOME/${rel_path#*/home/}"
+ ;;
+ profile/*|README.md)
+ sys_file="$HOME/.cfg/$rel_path"
+ ;;
+ *)
+ sys_file="$HOME/.cfg/$rel_path"
+ ;;
+ esac
+ fi
+
+ sys_dir="$(dirname "$sys_file")"
+ mkdir -p "$sys_dir"
+
+ # Avoid copying if source and destination resolve to the same file
+ local src_real dst_real
+ src_real=$(readlink -f -- "$repo_file" 2>/dev/null || echo "$repo_file")
+ dst_real=$(readlink -f -- "$sys_file" 2>/dev/null || echo "$sys_file")
+ if [[ -n "$dst_real" && "$src_real" == "$dst_real" ]]; then
+ print_skip "Skipping self-copy: $rel_path"
+ return 0
+ fi
+
+ # Copy with privilege if path is system (/etc, /usr, etc.)
+ if [[ "$sys_file" == /* ]]; then
+ # If we lack a privilege tool and are not root, skip with clear message
+ if [[ -z "$PRIVILEGE_TOOL" && "$EUID" -ne 0 ]]; then
+ print_skip "Skipping privileged deploy (no sudo/doas): $rel_path -> $sys_file"
+ else
+ execute_with_privilege "cp -a '$repo_file' '$sys_file'" \
+ && print_info "Deployed (privileged): $rel_path" \
+ || print_error "Failed to deploy (privileged): $rel_path"
+ fi
+ else
+ cp -a "$repo_file" "$sys_file" \
+ && print_info "Deployed: $rel_path" \
+ || print_error "Failed to deploy: $rel_path"
+ fi
+ }
+
+ # Deploy all files in OS dir
+ if [[ -d "$os_dir" ]]; then
+ find "$os_dir" -type f | while read -r f; do
+ deploy_file "$f"
+ done
+ fi
+
+ # Deploy all files in common dir
+ if [[ -d "$common_dir" ]]; then
+ find "$common_dir" -type f | while read -r f; do
+ deploy_file "$f"
+ done
+ fi
+}
+
+# Set appropriate file permissions
+set_dotfile_permissions() {
+ print_info "Setting appropriate file permissions..."
+
+ # SSH directory permissions
+ if [[ -d "$HOME/.ssh" ]]; then
+ chmod 700 "$HOME/.ssh"
+ find "$HOME/.ssh" -name "id_*" -not -name "*.pub" -exec chmod 600 {} \; 2>/dev/null || true
+ find "$HOME/.ssh" -name "*.pub" -exec chmod 644 {} \; 2>/dev/null || true
+ find "$HOME/.ssh" -name "config" -exec chmod 600 {} \; 2>/dev/null || true
+ print_info "SSH permissions set"
+ fi
+
+ # GPG directory permissions
+ if [[ -d "$HOME/.gnupg" ]]; then
+ chmod 700 "$HOME/.gnupg"
+ find "$HOME/.gnupg" -type f -exec chmod 600 {} \; 2>/dev/null || true
+ print_info "GPG permissions set"
+ fi
+
+ # Make scripts executable
+ if [[ -d "$HOME/.local/bin" ]]; then
+ find "$HOME/.local/bin" -type f -exec chmod +x {} \; 2>/dev/null || true
+ print_info "Script permissions set"
+ fi
+
+ if [[ -d "$HOME/.scripts" ]]; then
+ find "$HOME/.scripts" -type f -name "*.sh" -exec chmod +x {} \; 2>/dev/null || true
+ print_info "Shell script permissions set"
+ fi
+}
+
+#======================================
+# Installation Step Functions
+#======================================
+
+setup_environment() {
+ print_section "Setting Up Environment"
+ save_state "setup_environment" "started"
+
+ detect_os
+ detect_privilege_tools
+ detect_package_manager || {
+ print_error "Cannot proceed without a supported package manager"
+ mark_step_failed "setup_environment"
+ return 1
+ }
+
+ if [[ -n "$PRIVILEGE_TOOL" ]]; then
+ test_privilege_access || {
+ print_error "Cannot obtain necessary privileges"
+ mark_step_failed "setup_environment"
+ return 1
+ }
+ fi
+
+ mark_step_completed "setup_environment"
+}
+
+check_connectivity() {
+ print_section "Checking Connectivity"
+ save_state "check_connectivity" "started"
+
+ if check_internet_connectivity; then
+ mark_step_completed "check_connectivity"
+ return 0
+ else
+ print_warning "Limited internet connectivity - some features may be unavailable"
+ mark_step_completed "check_connectivity"
+ return 0 # Don't fail completely
+ fi
+}
+
+install_dependencies() {
+ print_section "Installing Dependencies"
+ save_state "install_dependencies" "started"
+
+ if install_dependencies_if_missing; then
+ mark_step_completed "install_dependencies"
+ return 0
+ else
+ mark_step_failed "install_dependencies"
+ return 1
+ fi
+}
+
+install_dotfiles() {
+ print_section "Installing Dotfiles"
+ save_state "install_dotfiles" "started"
+
+ local update=false
+
+ # Check internet connectivity for git operations
+ if [[ "$INTERNET_AVAILABLE" != true ]]; then
+ print_warning "No internet connectivity - skipping dotfiles installation"
+ mark_step_completed "install_dotfiles"
+ return 0
+ fi
+
+ if [[ -d "$DOTFILES_DIR" ]]; then
+ if [[ "$UPDATE_MODE" == true ]] || prompt_user "Dotfiles repository already exists. Update it?"; then
+ print_info "Updating existing dotfiles..."
+ # Detect ahead/behind before pulling to avoid unexpected fast-forwards
+ execute_command "git --git-dir='$DOTFILES_DIR' fetch origin main" || true
+ local ahead behind ab_line
+ ahead=0; behind=0
+ ab_line=$(git --git-dir="$DOTFILES_DIR" rev-list --left-right --count HEAD...origin/main 2>/dev/null || true)
+ # Expected format: "<ahead>\t<behind>"; parse safely
+ if [[ "$ab_line" =~ ^([0-9]+)[[:space:]]+([0-9]+)$ ]]; then
+ ahead="${BASH_REMATCH[1]}"
+ behind="${BASH_REMATCH[2]}"
+ fi
+ if [[ ${ahead:-0} -gt 0 && ${behind:-0} -eq 0 ]]; then
+ print_warning "Your local dotfiles are ahead of origin/main by $ahead commit(s)."
+ while true; do
+ echo
+ print_color "$YELLOW" "Choose an action for local-ahead state:"
+ echo " [k] Keep local (skip pull)"
+ echo " [p] Push local commits"
+ echo " [c] Commit new changes and push"
+ echo " [s] Stash uncommitted changes (if any) and pull"
+ echo " [a] Abort"
+ printf "%b%s%b" "$YELLOW" "Enter choice [k/p/c/s/a]: " "$NOCOLOR"
+ read -r choice
+ case "${choice,,}" in
+ k)
+ print_warning "Keeping local commits; skipping pull"
+ break
+ ;;
+ p)
+ if execute_command "git --git-dir='$DOTFILES_DIR' --work-tree='$HOME/.cfg' push origin HEAD:main"; then
+ print_success "Pushed local commits"
+ else
+ print_error "Push failed"
+ fi
+ break
+ ;;
+ c)
+ print_info "Committing changes before push..."
+ printf "%b%s%b" "$YELLOW" "Commit message (default: 'WIP local changes via installer'): " "$NOCOLOR"
+ read -r commit_msg
+ [[ -z "$commit_msg" ]] && commit_msg="WIP local changes via installer"
+ if execute_command "git --git-dir='$DOTFILES_DIR' --work-tree='$HOME/.cfg' add -A" \
+ && execute_command "git --git-dir='$DOTFILES_DIR' --work-tree='$HOME/.cfg' commit -m \"$commit_msg\"" \
+ && execute_command "git --git-dir='$DOTFILES_DIR' --work-tree='$HOME/.cfg' push origin HEAD:main"; then
+ print_success "Committed and pushed"
+ else
+ print_error "Commit/push failed"
+ fi
+ break
+ ;;
+ s)
+ print_info "Stashing local (including untracked) before pull..."
+ if execute_command "git --git-dir='$DOTFILES_DIR' --work-tree='$HOME/.cfg' stash push -u -m 'installer-stash'"; then
+ print_success "Stashed local changes"
+ else
+ print_error "Stash failed"
+ fi
+ break
+ ;;
+ a)
+ print_error "Aborted by user"
+ mark_step_failed "install_dotfiles"
+ return 1
+ ;;
+ *)
+ print_warning "Invalid choice. Please enter k/p/c/s/a."
+ ;;
+ esac
+ done
+ fi
+ # If remote is ahead (fast-forward), ask the user before pulling
+ if [[ ${behind:-0} -gt 0 && ${ahead:-0} -eq 0 ]]; then
+ print_warning "Origin/main is ahead by $behind commit(s)."
+ if ! prompt_user "Fast-forward to origin/main now?"; then
+ print_skip "User chose not to fast-forward; skipping pull"
+ # Skip pull entirely
+ goto_after_pull=true
+ fi
+ fi
+ if [[ "${goto_after_pull:-false}" == true ]] || execute_command "git --git-dir='$DOTFILES_DIR' --work-tree='$HOME/.cfg' pull origin main"; then
+ update=true
+ print_success "Dotfiles updated successfully"
+ else
+ print_error "Failed to pull updates"
+ # Interactive resolution for local changes
+ while true; do
+ echo
+ print_color "$YELLOW" "Local changes detected. Choose an action:"
+ echo " [c] Commit local changes"
+ echo " [s] Stash local changes"
+ echo " [k] Keep local changes (skip pulling)"
+ echo " [a] Abort"
+ printf "%b%s%b" "$YELLOW" "Enter choice [c/s/k/a]: " "$NOCOLOR"
+ read -r choice
+ case "${choice,,}" in
+ c)
+ print_info "Committing local changes..."
+ printf "%b%s%b" "$YELLOW" "Commit message (default: 'WIP local changes via installer'): " "$NOCOLOR"
+ read -r commit_msg
+ [[ -z "$commit_msg" ]] && commit_msg="WIP local changes via installer"
+ if execute_command "git --git-dir='$DOTFILES_DIR' --work-tree='$HOME/.cfg' add -A" \
+ && execute_command "git --git-dir='$DOTFILES_DIR' --work-tree='$HOME/.cfg' commit -m \"$commit_msg\""; then
+ print_success "Committed local changes"
+ print_info "Retrying pull..."
+ if execute_command "git --git-dir='$DOTFILES_DIR' --work-tree='$HOME/.cfg' pull origin main"; then
+ update=true; print_success "Dotfiles updated successfully"; break
+ else
+ print_error "Pull failed again after commit. You may resolve manually or choose another option."
+ fi
+ else
+ print_error "Commit failed. Try another option."
+ fi
+ ;;
+ s)
+ print_info "Stashing local changes..."
+ if execute_command "git --git-dir='$DOTFILES_DIR' --work-tree='$HOME/.cfg' stash push -u -m 'installer-stash'"; then
+ print_success "Stashed local changes"
+ print_info "Retrying pull..."
+ if execute_command "git --git-dir='$DOTFILES_DIR' --work-tree='$HOME/.cfg' pull origin main"; then
+ update=true; print_success "Dotfiles updated successfully"; break
+ else
+ print_error "Pull failed again after stash. You may resolve manually or choose another option."
+ fi
+ else
+ print_error "Stash failed. Try another option."
+ fi
+ ;;
+ k)
+ print_warning "Keeping local changes and skipping pull"
+ break
+ ;;
+ a)
+ print_error "Aborted by user"
+ mark_step_failed "install_dotfiles"
+ return 1
+ ;;
+ *)
+ print_warning "Invalid choice. Please enter c/s/k/a."
+ ;;
+ esac
+ done
+ fi
+ else
+ print_skip "Skipping dotfiles update"
+ mark_step_completed "install_dotfiles"
+ return 0
+ fi
+ else
+ print_info "Cloning dotfiles repository..."
+ if execute_command "git clone --bare '$DOTFILES_URL' '$DOTFILES_DIR'"; then
+ print_success "Dotfiles repository cloned"
+ else
+ print_error "Failed to clone dotfiles repository"
+ mark_step_failed "install_dotfiles"
+ return 1
+ fi
+ fi
+
+ # Configure the repository
+ execute_command "git --git-dir='$DOTFILES_DIR' --work-tree='$HOME/.cfg' config status.showUntrackedFiles no"
+
+ mark_step_completed "install_dotfiles"
+ print_success "Dotfiles installed successfully"
+}
+
+setup_user_dirs() {
+ print_section "Setting Up User Directories"
+ save_state "setup_user_dirs" "started"
+
+ local directories=('.cache' '.config' '.local/bin' '.local/share' '.scripts')
+
+ for dir in "${directories[@]}"; do
+ create_dir "$HOME/$dir"
+ done
+
+ # Set up XDG directories (ensure existence; no deletions)
+ if command_exists xdg-user-dirs-update; then
+ # Suppress tool output to avoid misleading terms like "removed"; we only ensure presence.
+ execute_command "xdg-user-dirs-update >/dev/null 2>&1 || true"
+ print_success "Ensured XDG user directories exist"
+ fi
+
+ mark_step_completed "setup_user_dirs"
+}
+
+install_essentials() {
+ print_section "Installing Essential Tools"
+ save_state "install_essentials" "started"
+
+ # Fast-path: determine if any package tools are actually missing
+ local missing_tools=()
+ for tool in "${PACKAGE_TOOLS[@]}"; do
+ if [[ "$tool" == "yq" ]]; then
+ if command_exists yq || [[ -x "$HOME/.local/bin/yq" ]]; then
+ continue
+ fi
+ elif [[ "$tool" == "jq" ]]; then
+ if command_exists jq || is_package_installed jq; then
+ continue
+ fi
+ fi
+ if ! command_exists "$tool"; then
+ missing_tools+=("$tool")
+ fi
+ done
+
+ if [[ ${#missing_tools[@]} -eq 0 ]]; then
+ print_skip "All essential tools are already installed"
+ mark_step_completed "install_essentials"
+ return 0
+ fi
+
+ # Install package processing tools first
+ for tool in "${PACKAGE_TOOLS[@]}"; do
+ if [[ "$tool" == "yq" ]]; then
+ if command_exists yq || [[ -x "$HOME/.local/bin/yq" ]]; then
+ print_info "Package tool already available: yq"
+ continue
+ fi
+ elif [[ "$tool" == "jq" ]]; then
+ if command_exists jq || is_package_installed jq; then
+ print_info "Package tool already available: jq"
+ continue
+ fi
+ fi
+
+ if ! command_exists "$tool"; then
+ case "$tool" in
+ yq)
+ if install_yq; then
+ print_success "Installed package tool: $tool"
+ else
+ print_error "Failed to install package tool: $tool"
+ mark_step_failed "install_essentials"
+ return 1
+ fi
+ ;;
+ jq)
+ if command_exists jq || is_package_installed jq; then
+ print_info "Package tool already available: jq"
+ elif install_single_package "jq" "essential"; then
+ print_success "Installed package tool: $tool"
+ else
+ print_error "Failed to install package tool: $tool"
+ mark_step_failed "install_essentials"
+ return 1
+ fi
+ ;;
+ esac
+ else
+ print_info "Package tool already available: $tool"
+ fi
+ done
+
+ mark_step_completed "install_essentials"
+}
+
+install_packages() {
+ print_section "Installing Packages"
+ save_state "install_packages" "started"
+
+ # Skip if essentials-only mode
+ if [[ "$INSTALL_MODE" == "essentials" ]]; then
+ print_skip "Package installation (essentials-only mode)"
+ mark_step_completed "install_packages"
+ return 0
+ fi
+
+ # Skip if no internet and packages require download
+ if [[ "$INTERNET_AVAILABLE" != true ]]; then
+ print_warning "No internet connectivity - skipping package installation"
+ mark_step_completed "install_packages"
+ return 0
+ fi
+
+ # Change to home directory to find packages.yml
+ local original_dir="$PWD"
+ cd "$HOME" 2>/dev/null || true
+
+ # Look for packages.yml in common locations
+ local packages_files=("$PACKAGES_FILE" "common/$PACKAGES_FILE" ".cfg/common/$PACKAGES_FILE")
+ local found_packages_file=""
+
+ for pf in "${packages_files[@]}"; do
+ if [[ -f "$pf" ]]; then
+ found_packages_file="$pf"
+ break
+ fi
+ done
+
+ if [[ -n "$found_packages_file" ]]; then
+ # Handle custom installs first
+ handle_custom_installs "$found_packages_file"
+
+ # Install packages
+ if install_packages_from_yaml "$found_packages_file" "$INSTALL_MODE"; then
+ mark_step_completed "install_packages"
+ else
+ print_warning "Some packages failed to install, but continuing..."
+ mark_step_completed "install_packages"
+ fi
+ else
+ print_warning "packages.yml not found, attempting to download from GitHub..."
+
+ # Derive raw URL from DOTFILES_URL
+ # Supports formats like:
+ # https://github.com/<owner>/<repo>.git
+ # git@github.com:<owner>/<repo>.git
+ # https://github.com/<owner>/<repo>
+ local owner repo branch
+ branch="main"
+ case "$DOTFILES_URL" in
+ git@github.com:*)
+ owner="${DOTFILES_URL#git@github.com:}"
+ owner="${owner%.git}"
+ repo="${owner#*/}"
+ owner="${owner%%/*}"
+ ;;
+ https://github.com/*)
+ owner="${DOTFILES_URL#https://github.com/}"
+ owner="${owner%.git}"
+ repo="${owner#*/}"
+ owner="${owner%%/*}"
+ ;;
+ *)
+ owner=""
+ repo=""
+ ;;
+ esac
+
+ local packages_url=""
+ if [[ -n "$owner" && -n "$repo" ]]; then
+ packages_url="https://raw.githubusercontent.com/$owner/$repo/$branch/common/packages.yml"
+ fi
+ local temp_packages="/tmp/packages.yml"
+
+ if command_exists curl && [[ -n "$packages_url" ]]; then
+ if curl -fsSL "$packages_url" -o "$temp_packages" 2>/dev/null; then
+ # Create common directory if it doesn't exist
+ mkdir -p "$HOME/.cfg/common" 2>/dev/null || mkdir -p "$HOME/common" 2>/dev/null
+
+ # Move to appropriate location
+ if [[ -d "$HOME/.cfg/common" ]]; then
+ mv "$temp_packages" "$HOME/.cfg/common/packages.yml"
+ found_packages_file="$HOME/.cfg/common/packages.yml"
+ elif [[ -d "$HOME/common" ]]; then
+ mv "$temp_packages" "$HOME/common/packages.yml"
+ found_packages_file="$HOME/common/packages.yml"
+ else
+ mv "$temp_packages" "$HOME/packages.yml"
+ found_packages_file="$HOME/packages.yml"
+ fi
+
+ print_success "Downloaded packages.yml from GitHub"
+
+ # Now install packages with the downloaded file
+ handle_custom_installs "$found_packages_file"
+ if install_packages_from_yaml "$found_packages_file" "$INSTALL_MODE"; then
+ mark_step_completed "install_packages"
+ else
+ print_warning "Some packages failed to install, but continuing..."
+ mark_step_completed "install_packages"
+ fi
+ else
+ print_warning "Failed to download packages.yml, skipping package installation"
+ mark_step_completed "install_packages"
+ fi
+ else
+ print_warning "curl not available and packages.yml not found, skipping package installation"
+ mark_step_completed "install_packages"
+ fi
+ fi
+
+ cd "$original_dir" 2>/dev/null || true
+}
+
+setup_shell() {
+ print_section "Setting Up Shell Environment"
+ save_state "setup_shell" "started"
+
+ # Ensure config command is available before changing shells
+ if [[ "$CONFIG_COMMAND_AVAILABLE" != true ]]; then
+ print_warning "Config command not available, installing it first..."
+ install_config_command || {
+ print_error "Failed to install config command before shell setup"
+ mark_step_failed "setup_shell"
+ return 1
+ }
+ fi
+
+ if command_exists zsh; then
+ local zsh_path
+ zsh_path="$(command -v zsh)"
+
+ if [[ "$FORCE_MODE" == true ]]; then
+ print_info "FORCE mode: changing default shell to Zsh without prompting"
+ if execute_with_privilege "chsh -s '$zsh_path' '$USER'"; then
+ print_success "Default shell changed to Zsh"
+ print_warning "Please log out and log back in to apply changes"
+ else
+ print_error "Failed to change default shell"
+ fi
+ elif [[ "$ASK_MODE" == true ]]; then
+ if prompt_user "Change default shell to Zsh?" "N"; then
+ if execute_with_privilege "chsh -s '$zsh_path' '$USER'"; then
+ print_success "Default shell changed to Zsh"
+ print_warning "Please log out and log back in to apply changes"
+ else
+ print_error "Failed to change default shell"
+ fi
+ else
+ print_skip "Default shell change (user chose No)"
+ fi
+ else
+ print_info "Skipping shell change (non-interactive mode). Use --ask to be prompted or --force to auto-change."
+ fi
+ else
+ print_warning "Zsh not installed, skipping shell setup"
+ fi
+
+ # Zsh plugins are managed via packages.yml custom_installs (zsh_plugins)
+ # No direct plugin installation here to avoid duplication.
+
+ mark_step_completed "setup_shell"
+}
+
+## install_zsh_plugins deprecated; handled via packages.yml
+
+setup_ssh() {
+ print_section "Setting Up SSH"
+ save_state "setup_ssh" "started"
+
+ local ssh_dir="$HOME/.ssh"
+
+ if [[ ! -f "$ssh_dir/id_rsa" && ! -f "$ssh_dir/id_ed25519" ]]; then
+ if [[ "$FORCE_MODE" == true ]] || prompt_user "Generate SSH key pair?"; then
+ create_dir "$ssh_dir" 700
+
+ local email="${USER}@${HOSTNAME:-$(hostname)}"
+ local key_file="$ssh_dir/id_ed25519"
+
+ if execute_command "ssh-keygen -t ed25519 -f '$key_file' -N '' -C '$email'"; then
+ print_success "SSH key pair generated (Ed25519)"
+ execute_command "chmod 600 '$key_file'"
+ execute_command "chmod 644 '$key_file.pub'"
+
+ if [[ "$DRY_RUN" != true ]] && [[ -f "$key_file.pub" ]]; then
+ print_info "Your public key:"
+ print_color "$GREEN" "$(cat "$key_file.pub")"
+ print_info "Copy this key to your Git hosting service"
+ fi
+ else
+ print_error "Failed to generate SSH key"
+ mark_step_failed "setup_ssh"
+ return 1
+ fi
+ fi
+ else
+ print_info "SSH key already exists"
+ fi
+
+ mark_step_completed "setup_ssh"
+}
+
+# Helper function to detect the init system
+detect_init_system() {
+ if [ -d /run/systemd/system ]; then
+ echo "systemd"
+ elif command -v rc-service &>/dev/null; then
+ echo "openrc"
+ elif [ -d /etc/sv ]; then
+ echo "runit"
+ elif command -v service &>/dev/null; then
+ echo "sysvinit"
+ else
+ echo "unknown"
+ fi
+}
+
+# Helper function to manage a service (enable/start)
+manage_service() {
+ local action="$1"
+ local service="$2"
+ local init_system="$3"
+ # use numeric success code: 0=success, 1=failure
+ local success=1
+
+ case "$init_system" in
+ systemd)
+ # Resolve common generic service names to distro-specific systemd unit names
+ local svc_candidates=()
+ local lower_service
+ lower_service="${service,,}"
+ case "$lower_service" in
+ networkmanager)
+ svc_candidates+=("NetworkManager" "NetworkManager.service" "network-manager")
+ ;;
+ sshd)
+ # Debian uses 'ssh' service, others commonly use 'sshd'
+ svc_candidates+=("sshd" "ssh" "sshd.service" "ssh.service")
+ ;;
+ *)
+ svc_candidates+=("$service")
+ ;;
+ esac
+
+ local tried=false
+ local rc=1
+ for svc in "${svc_candidates[@]}"; do
+ tried=true
+ if [ "$action" == "enable" ]; then
+ # Prefer enabling and starting in one go when possible
+ if ! execute_command "$PRIVILEGE_TOOL systemctl enable --now '$svc'"; then
+ execute_command "$PRIVILEGE_TOOL systemctl enable '$svc'"
+ fi
+ rc=$?
+ elif [ "$action" == "start" ]; then
+ execute_command "$PRIVILEGE_TOOL systemctl start '$svc'"
+ rc=$?
+ else
+ rc=1
+ fi
+ if [[ $rc -eq 0 ]]; then
+ success=0
+ break
+ fi
+ print_warning "Failed to $action service candidate: $svc"
+ done
+ # If we didn't have a special mapping, fall back to original name once
+ if [[ "$tried" == false ]]; then
+ if [ "$action" == "enable" ]; then
+ execute_command "$PRIVILEGE_TOOL systemctl enable '$service'"
+ rc=$?
+ elif [ "$action" == "start" ]; then
+ execute_command "$PRIVILEGE_TOOL systemctl start '$service'"
+ rc=$?
+ fi
+ [[ $rc -eq 0 ]] && success=0
+ fi
+ ;;
+ openrc)
+ if [ "$action" == "enable" ]; then
+ execute_command "$PRIVILEGE_TOOL rc-update add '$service' default"
+ success=$?
+ elif [ "$action" == "start" ]; then
+ execute_command "$PRIVILEGE_TOOL rc-service '$service' start"
+ success=$?
+ fi
+ ;;
+ runit)
+ if [ "$action" == "enable" ]; then
+ # Runit services are enabled by creating a symlink in the run level directory
+ execute_command "$PRIVILEGE_TOOL ln -sf /etc/sv/'$service' /var/service/"
+ success=$?
+ elif [ "$action" == "start" ]; then
+ # The 'start' action is usually implied by the symlink, but you can
+ # manually start it if needed
+ execute_command "$PRIVILEGE_TOOL sv start '$service'"
+ success=$?
+ fi
+ ;;
+ sysvinit|unknown)
+ # Use the generic 'service' command
+ if [ "$action" == "start" ]; then
+ execute_command "$PRIVILEGE_TOOL service '$service' start"
+ success=$?
+ fi
+ # Enabling is system-dependent for sysvinit/unknown; we'll check for chkconfig
+ if [ "$action" == "enable" ]; then
+ if command -v chkconfig &>/dev/null; then
+ execute_command "$PRIVILEGE_TOOL chkconfig '$service' on"
+ success=$?
+ else
+ success=0
+ fi
+ fi
+ ;;
+ *)
+ print_error "Unknown init system: $init_system. Cannot $action service '$service'."
+ return 1
+ ;;
+ esac
+
+ return $success
+}
+
+#======================================
+# Service Management Functions
+#======================================
+
+configure_services_from_yaml() {
+ local packages_file="$1"
+ local profile="$2"
+
+ print_section "Configuring System Services"
+ save_state "configure_services" "started"
+
+ if [[ "$CFG_OS" != "linux" ]]; then
+ print_skip "Service configuration (not supported on $CFG_OS)"
+ mark_step_completed "configure_services"
+ return 0
+ fi
+
+ if [[ ! -f "$packages_file" ]]; then
+ print_warning "Package file not found, skipping service configuration"
+ mark_step_completed "configure_services"
+ return 0
+ fi
+
+ # Detect the init system
+ local INIT_SYSTEM=$(detect_init_system)
+ print_info "Detected Init System: $INIT_SYSTEM"
+
+ # Get services to enable for all profiles
+ local services_all
+ mapfile -t services_all < <(yq eval ".services.enable.all[]" "$packages_file" 2>/dev/null | grep -v "^null$" || true)
+
+ # Get services to enable for specific profile
+ local services_profile
+ mapfile -t services_profile < <(yq eval ".services.enable.$profile[]" "$packages_file" 2>/dev/null | grep -v "^null$" || true)
+
+ # Get services to disable for specific profile
+ local services_disable
+ mapfile -t services_disable < <(yq eval ".services.disable.$profile[]" "$packages_file" 2>/dev/null | grep -v "^null$" || true)
+
+ # Enable services
+ for service in "${services_all[@]}" "${services_profile[@]}"; do
+ [[ -z "$service" ]] && continue
+ if [[ "$FORCE_MODE" == true ]] || prompt_user "Enable $service service?"; then
+ if manage_service "enable" "$service" "$INIT_SYSTEM"; then
+ manage_service "start" "$service" "$INIT_SYSTEM"
+ print_success "Enabled and started $service"
+ else
+ print_error "Failed to enable $service"
+ fi
+ fi
+ done
+
+ # Disable services
+ for service in "${services_disable[@]}"; do
+ [[ -z "$service" ]] && continue
+ if [[ "$FORCE_MODE" == true ]] || prompt_user "Disable $service service?"; then
+ if manage_service "stop" "$service" "$INIT_SYSTEM"; then
+ manage_service "disable" "$service" "$INIT_SYSTEM"
+ print_success "Stopped and disabled $service"
+ else
+ print_error "Failed to disable $service"
+ fi
+ fi
+ done
+
+ mark_step_completed "configure_services"
+}
+
+configure_services() {
+ # Change to home directory to find packages.yml
+ local original_dir="$PWD"
+ cd "$HOME" 2>/dev/null || true
+
+ local packages_files=("$PACKAGES_FILE" "common/$PACKAGES_FILE" ".cfg/common/$PACKAGES_FILE")
+ local found_packages_file=""
+
+ for pf in "${packages_files[@]}"; do
+ if [[ -f "$pf" ]]; then
+ found_packages_file="$pf"
+ break
+ fi
+ done
+
+ if [[ -n "$found_packages_file" ]]; then
+ configure_services_from_yaml "$found_packages_file" "$INSTALL_MODE"
+ else
+ # Fallback to original configure_services logic
+ print_section "Configuring System Services"
+ save_state "configure_services" "started"
+
+ if [[ "$CFG_OS" != "linux" ]]; then
+ print_skip "Service configuration (not supported on $CFG_OS)"
+ mark_step_completed "configure_services"
+ return 0
+ fi
+
+ # Original service configuration logic here...
+ mark_step_completed "configure_services"
+ fi
+
+ cd "$original_dir" 2>/dev/null || true
+}
+
+setup_tmux_plugins() {
+ if [[ "$INTERNET_AVAILABLE" != true ]]; then
+ print_warning "No internet connectivity - skipping Tmux plugins installation"
+ return 0
+ fi
+
+ local tpm_dir="$HOME/.config/tmux/plugins/tpm"
+ local plugins_dir="$HOME/.config/tmux/plugins"
+
+ if [[ ! -f "$HOME/.tmux.conf" && ! -f "$HOME/.config/tmux/tmux.conf" ]]; then
+ print_info "Tmux config not found, skipping plugin setup"
+ return 0
+ fi
+
+ print_info "Setting up Tmux plugins..."
+ create_dir "$plugins_dir"
+
+ if [[ ! -d "$tpm_dir" || ! "$(ls -A "$tpm_dir" 2>/dev/null)" ]]; then
+ print_info "Installing Tmux Plugin Manager (TPM)..."
+ if execute_command "git clone https://github.com/tmux-plugins/tpm '$tpm_dir'"; then
+ print_success "TPM installed successfully"
+ print_info "Run 'tmux' and press 'prefix + I' to install plugins"
+ else
+ print_error "Failed to install TPM"
+ fi
+ else
+ print_info "TPM already installed"
+ fi
+}
+
+#======================================
+# Development Environment Setup
+#======================================
+
+
+setup_development_environment() {
+ # Accept optional packages_file argument. If missing, try to locate a default.
+ local packages_file="${1:-}"
+ if [[ -z "$packages_file" ]]; then
+ local candidates=("$HOME/$PACKAGES_FILE" "$HOME/common/$PACKAGES_FILE" "$HOME/.cfg/common/$PACKAGES_FILE")
+ for pf in "${candidates[@]}"; do
+ if [[ -f "$pf" ]]; then
+ packages_file="$pf"
+ break
+ fi
+ done
+ fi
+
+ print_info "Setting up development environment"
+
+ if [[ -z "$packages_file" || ! -f "$packages_file" ]]; then
+ print_warning "Package file not found, skipping development setup"
+ return 0
+ fi
+
+ # Apply git configuration
+ local git_configs
+ if command_exists yq; then
+ mapfile -t git_configs < <(yq eval ".development.git_config[]" "$packages_file" 2>/dev/null | grep -v "^null$" || true)
+ else
+ git_configs=()
+ fi
+
+ if [[ ${#git_configs[@]} -gt 0 ]] && command_exists git; then
+ print_info "Applying git configuration"
+ for config in "${git_configs[@]}"; do
+ [[ -z "$config" ]] && continue
+ print_info "Running: $config"
+ execute_command "$config"
+ done
+ fi
+}
+
+# Backup existing files that will be affected by deployment
+backup_existing_dotfiles() {
+ local backup_root="$BACKUP_DIR/pre-deploy"
+ local os_dir="$DOTFILES_DIR/$CFG_OS"
+ local common_dir="$DOTFILES_DIR/common"
+
+ print_info "Creating backup at: $backup_root"
+ mkdir -p "$backup_root" 2>/dev/null || true
+
+ # Helper to compute destination path similar to manual_deploy_dotfiles
+ _compute_dest_path() {
+ local repo_file="$1"
+ local rel_path sys_file base
+ rel_path="${repo_file#$DOTFILES_DIR/}"
+
+ if [[ "$rel_path" == "$CFG_OS/"* && "$rel_path" != */home/* ]]; then
+ sys_file="/${rel_path#$CFG_OS/}"
+ else
+ case "$rel_path" in
+ common/config/*)
+ case "$CFG_OS" in
+ linux)
+ base="${XDG_CONFIG_HOME:-$HOME/.config}"
+ sys_file="$base/${rel_path#common/config/}"
+ ;;
+ macos)
+ sys_file="$HOME/Library/Application Support/${rel_path#common/config/}"
+ ;;
+ windows)
+ sys_file="$LOCALAPPDATA\\${rel_path#common/config/}"
+ ;;
+ *)
+ sys_file="$HOME/.config/${rel_path#common/config/}"
+ ;;
+ esac
+ ;;
+ common/assets/*)
+ sys_file="$HOME/.cfg/$rel_path"
+ ;;
+ common/*)
+ sys_file="$HOME/${rel_path#common/}"
+ ;;
+ */home/*)
+ sys_file="$HOME/${rel_path#*/home/}"
+ ;;
+ profile/*|README.md)
+ sys_file="$HOME/.cfg/$rel_path"
+ ;;
+ *)
+ sys_file="$HOME/.cfg/$rel_path"
+ ;;
+ esac
+ fi
+
+ echo "$sys_file"
+ }
+
+ _backup_one() {
+ local repo_file="$1"
+ local dest
+ dest=$(_compute_dest_path "$repo_file")
+ [[ -z "$dest" ]] && return 0
+
+ if [[ -e "$dest" ]]; then
+ local rel_path="${repo_file#$DOTFILES_DIR/}"
+ local backup_path="$backup_root/$rel_path"
+ local backup_dir
+ backup_dir="$(dirname "$backup_path")"
+ mkdir -p "$backup_dir" 2>/dev/null || true
+
+ if [[ "$dest" == /* ]]; then
+ execute_with_privilege "cp -a '$dest' '$backup_path'" \
+ && print_info "Backed up (privileged): $rel_path" \
+ || print_warning "Failed to backup (privileged): $rel_path"
+ else
+ cp -a "$dest" "$backup_path" \
+ && print_info "Backed up: $rel_path" \
+ || print_warning "Failed to backup: $rel_path"
+ fi
+ fi
+ }
+
+ # Backup files from OS dir
+ if [[ -d "$os_dir" ]]; then
+ find "$os_dir" -type f | while read -r f; do
+ _backup_one "$f"
+ done
+ fi
+
+ # Backup files from common dir
+ if [[ -d "$common_dir" ]]; then
+ find "$common_dir" -type f | while read -r f; do
+ _backup_one "$f"
+ done
+ fi
+
+ print_success "Backup completed at: $backup_root"
+}
+
+install_rust_development() {
+ local packages_file="$1"
+
+ if ! command_exists rustc; then
+ install_rust
+ fi
+
+ if command_exists cargo; then
+ print_info "Installing Rust components"
+ local components
+ mapfile -t components < <(yq eval ".development.rust.components[]" "$packages_file" 2>/dev/null | grep -v "^null$" || true)
+
+ for component in "${components[@]}"; do
+ [[ -z "$component" ]] && continue
+ execute_command "rustup component add $component"
+ done
+ fi
+}
+
+install_nodejs_development() {
+ local packages_file="$1"
+
+ if ! command_exists node; then
+ install_nvm
+ install_node
+ fi
+
+ if command_exists npm; then
+ print_info "Installing global Node.js packages"
+ local packages
+ mapfile -t packages < <(yq eval ".development.nodejs.global_packages[]" "$packages_file" 2>/dev/null | grep -v "^null$" || true)
+
+ for package in "${packages[@]}"; do
+ [[ -z "$package" ]] && continue
+ execute_command "npm install -g $package"
+ done
+ fi
+}
+
+install_python_development() {
+ local packages_file="$1"
+
+ if command_exists pip || command_exists pip3; then
+ print_info "Installing global Python packages"
+ local packages
+ mapfile -t packages < <(yq eval ".development.python.global_packages[]" "$packages_file" 2>/dev/null | grep -v "^null$" || true)
+
+ local pip_cmd="pip3"
+ command_exists pip3 || pip_cmd="pip"
+
+ for package in "${packages[@]}"; do
+ [[ -z "$package" ]] && continue
+ execute_command "$pip_cmd install --user $package"
+ done
+ fi
+}
+
+get_git_email_guess() {
+ local email_guess=""
+
+ # Try to get email from existing git config
+ if command_exists git; then
+ email_guess=$(git config --global user.email 2>/dev/null || echo "")
+ if [[ -n "$email_guess" ]]; then
+ echo "$email_guess"
+ return 0
+ fi
+ fi
+
+ # Try to extract from common email-related environment variables
+ for var in EMAIL MAIL USER_EMAIL GIT_AUTHOR_EMAIL GIT_COMMITTER_EMAIL; do
+ if [[ -n "${!var:-}" ]]; then
+ echo "${!var}"
+ return 0
+ fi
+ done
+
+ # Check for email in /etc/passwd gecos field
+ if [[ -f /etc/passwd ]]; then
+ local gecos
+ gecos=$(getent passwd "$USER" 2>/dev/null | cut -d: -f5 | cut -d, -f1)
+ if [[ "$gecos" == *@* ]]; then
+ echo "$gecos"
+ return 0
+ fi
+ fi
+
+ # Try to guess based on common patterns
+ local domain=""
+
+ # Check if we can determine domain from hostname
+ if command_exists hostname; then
+ local fqdn
+ fqdn=$(hostname -f 2>/dev/null || hostname 2>/dev/null || echo "")
+ if [[ "$fqdn" == *.* ]]; then
+ domain="${fqdn#*.}"
+ fi
+ fi
+
+ # Fallback domain guessing
+ if [[ -z "$domain" ]]; then
+ if [[ -f /etc/mailname ]]; then
+ domain=$(cat /etc/mailname 2>/dev/null || echo "")
+ elif [[ -f /etc/hostname ]]; then
+ local hostname_file
+ hostname_file=$(cat /etc/hostname 2>/dev/null || echo "")
+ if [[ "$hostname_file" == *.* ]]; then
+ domain="${hostname_file#*.}"
+ fi
+ fi
+ fi
+
+ # Final fallback
+ if [[ -z "$domain" ]]; then
+ domain="localhost"
+ fi
+
+ echo "${USER}@${domain}"
+}
+
+configure_git() {
+ local git_name="${USER}"
+ local git_email
+ git_email=$(get_git_email_guess)
+
+ if [[ "$FORCE_MODE" != true ]]; then
+ print_color "$YELLOW" "Enter your Git username [$git_name]: "
+ read -r input_name
+ [[ -n "$input_name" ]] && git_name="$input_name"
+
+ print_color "$YELLOW" "Enter your Git email [$git_email]: "
+ read -r input_email
+ [[ -n "$input_email" ]] && git_email="$input_email"
+ fi
+
+ execute_command "git config --global user.name '$git_name'"
+ execute_command "git config --global user.email '$git_email'"
+ execute_command "git config --global init.defaultBranch main"
+ execute_command "git config --global pull.rebase false"
+ print_success "Git configured with name: $git_name, email: $git_email"
+}
+
+install_development_tools() {
+ if [[ "$INTERNET_AVAILABLE" != true ]]; then
+ print_warning "No internet connectivity - skipping development tools installation"
+ return 0
+ fi
+
+ print_info "Installing development tools..."
+
+ # Install Rust if not present
+ if ! command_exists rustc; then
+ install_rust
+ fi
+
+ # Install Node.js via NVM if not present
+ if ! command_exists node; then
+ install_nvm
+ install_node
+ fi
+
+ # Install Yarn if Node.js is available
+ if command_exists npm && ! command_exists yarn; then
+ install_yarn
+ fi
+}
+
+install_rust() {
+ print_info "Installing Rust via rustup..."
+
+ if command_exists rustup; then
+ print_info "Rust already installed"
+ return 0
+ fi
+
+ local cargo_home="${XDG_DATA_HOME:-$HOME/.local/share}/cargo"
+ local rustup_home="${XDG_DATA_HOME:-$HOME/.local/share}/rustup"
+
+ create_dir "$(dirname "$cargo_home")"
+
+ if execute_command "CARGO_HOME='$cargo_home' RUSTUP_HOME='$rustup_home' curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y"; then
+ print_success "Rust installed successfully"
+
+ # Add to PATH for current session
+ if [[ -f "$cargo_home/env" ]]; then
+ source "$cargo_home/env"
+ fi
+
+ return 0
+ else
+ print_error "Failed to install Rust"
+ return 1
+ fi
+}
+
+install_nvm() {
+ local nvm_dir="$HOME/.config/nvm"
+
+ if [[ -d "$nvm_dir" && -f "$nvm_dir/nvm.sh" ]]; then
+ print_info "NVM already installed"
+ return 0
+ fi
+
+ print_info "Installing Node Version Manager (NVM)..."
+ create_dir "$nvm_dir"
+
+ if execute_command "curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | NVM_DIR='$nvm_dir' bash"; then
+ export NVM_DIR="$nvm_dir"
+ if [[ -s "$NVM_DIR/nvm.sh" ]]; then
+ source "$NVM_DIR/nvm.sh"
+ print_success "NVM installed successfully"
+ return 0
+ else
+ print_error "NVM installation failed - script not found"
+ return 1
+ fi
+ else
+ print_error "Failed to install NVM"
+ return 1
+ fi
+}
+
+install_node() {
+ if command_exists node; then
+ print_info "Node.js already installed"
+ return 0
+ fi
+
+ print_info "Installing Node.js..."
+
+ # Source NVM if available
+ local nvm_dir="$HOME/.config/nvm"
+ if [[ -s "$nvm_dir/nvm.sh" ]]; then
+ export NVM_DIR="$nvm_dir"
+ source "$NVM_DIR/nvm.sh"
+ fi
+
+ if command_exists nvm; then
+ if execute_command "nvm install --lts" && execute_command "nvm use --lts" && execute_command "nvm alias default lts/*"; then
+ print_success "Node.js installed successfully"
+ return 0
+ else
+ print_error "Failed to install Node.js via NVM"
+ return 1
+ fi
+ else
+ print_error "NVM not available for Node.js installation"
+ return 1
+ fi
+}
+
+install_yarn() {
+ print_info "Installing Yarn..."
+
+ if execute_command "curl -o- -L https://yarnpkg.com/install.sh | bash"; then
+ print_success "Yarn installed successfully"
+
+ # Add to PATH for current session
+ local yarn_bin="$HOME/.yarn/bin"
+ if [[ -d "$yarn_bin" && ":$PATH:" != *":$yarn_bin:"* ]]; then
+ export PATH="$yarn_bin:$PATH"
+ fi
+
+ return 0
+ else
+ print_error "Failed to install Yarn"
+ return 1
+ fi
+}
+
+#======================================
+# System Tweaks Functions
+#======================================
+
+apply_system_tweaks() {
+ local packages_file="$1"
+
+ print_section "Applying System Tweaks"
+
+ if [[ ! -f "$packages_file" ]]; then
+ print_warning "Package file not found, skipping system tweaks"
+ return 0
+ fi
+
+ # Detect desktop environment and apply appropriate tweaks
+ local desktop_env=""
+ if [[ "$XDG_CURRENT_DESKTOP" == *"GNOME"* ]] || command_exists gnome-shell; then
+ desktop_env="gnome"
+ elif [[ "$XDG_CURRENT_DESKTOP" == *"KDE"* ]] || command_exists plasmashell; then
+ desktop_env="kde"
+ fi
+
+ if [[ -n "$desktop_env" ]]; then
+ print_info "Applying $desktop_env tweaks"
+
+ # Get tweak commands for the desktop environment
+ local tweaks
+ mapfile -t tweaks < <(yq eval ".system_tweaks.$desktop_env[]" "$packages_file" 2>/dev/null | grep -v "^null$" || true)
+
+ for tweak in "${tweaks[@]}"; do
+ [[ -z "$tweak" ]] && continue
+ print_info "Applying tweak: $tweak"
+ if execute_command "$tweak"; then
+ print_success "Applied: $tweak"
+ else
+ print_warning "Failed to apply: $tweak"
+ fi
+ done
+ else
+ print_info "No supported desktop environment detected for tweaks"
+ fi
+
+}
+
+apply_tweaks() {
+ print_section "Applying System Tweaks"
+ save_state "apply_tweaks" "started"
+
+ # Change to home directory to find packages.yml
+ local original_dir="$PWD"
+ cd "$HOME" 2>/dev/null || true
+
+ local packages_files=("$PACKAGES_FILE" "common/$PACKAGES_FILE" ".cfg/common/$PACKAGES_FILE")
+ local found_packages_file=""
+
+ for pf in "${packages_files[@]}"; do
+ if [[ -f "$pf" ]]; then
+ found_packages_file="$pf"
+ break
+ fi
+ done
+
+ if [[ -n "$found_packages_file" ]]; then
+ apply_system_tweaks "$found_packages_file"
+ else
+ case "$CFG_OS" in
+ linux)
+ apply_linux_tweaks
+ ;;
+ macos)
+ apply_macos_tweaks
+ ;;
+ *)
+ print_info "No system tweaks defined for $CFG_OS"
+ ;;
+ esac
+ fi
+
+ cd "$original_dir" 2>/dev/null || true
+ mark_step_completed "apply_tweaks"
+}
+
+apply_linux_tweaks() {
+ # --- Locale tweak ---
+ if command -v localectl >/dev/null 2>&1; then
+ local current_locale
+ current_locale=$(localectl status | grep "System Locale" | cut -d= -f2 | cut -d, -f1)
+ if [[ "$current_locale" != "en_US.UTF-8" ]]; then
+ if prompt_user "Set system locale to en_US.UTF-8?"; then
+ if execute_with_privilege "localectl set-locale LANG=en_US.UTF-8"; then
+ print_success "Locale set to en_US.UTF-8"
+ else
+ print_error "Failed to set locale"
+ fi
+ fi
+ fi
+ fi
+
+ # Desktop environment tweaks should be declared in packages.yml under system_tweaks.
+ print_info "Linux system tweaks applied (core). Desktop tweaks come from packages.yml."
+}
+
+apply_macos_tweaks() {
+ print_info "macOS system tweaks applied (placeholder)"
+}
+
+#======================================
+# Custom Installation Functions
+#======================================
+
+handle_custom_installs() {
+ local packages_file="$1"
+
+ if [[ ! -f "$packages_file" ]] || ! command_exists yq; then
+ return 0
+ fi
+
+ print_info "Processing custom installations..."
+
+ # Get custom install commands
+ local custom_installs
+ mapfile -t custom_installs < <(yq eval ".custom_installs | keys | .[]" "$packages_file" 2>/dev/null | grep -v "^null$" || true)
+
+ for install_name in "${custom_installs[@]}"; do
+ [[ -z "$install_name" ]] && continue
+
+ # Check condition
+ local condition
+ condition=$(yq eval ".custom_installs.$install_name.condition" "$packages_file" 2>/dev/null | grep -v "^null$" || echo "")
+
+ if [[ -n "$condition" ]]; then
+ # Evaluate condition safely even under set -u (nounset)
+ local -i _had_nounset=0
+ if set -o | grep -q "nounset\s*on"; then
+ _had_nounset=1
+ set +u
+ fi
+ if ! eval "$condition" 2>/dev/null; then
+ if [[ $_had_nounset -eq 1 ]]; then set -u; fi
+ print_info "Skipping $install_name (condition not met)"
+ continue
+ fi
+ if [[ $_had_nounset -eq 1 ]]; then set -u; fi
+ fi
+
+ # Get OS-specific command
+ local install_cmd=""
+ case "$CFG_OS" in
+ linux)
+ install_cmd=$(yq eval ".custom_installs.$install_name.linux" "$packages_file" 2>/dev/null | grep -v "^null$" || echo "")
+ ;;
+ macos)
+ install_cmd=$(yq eval ".custom_installs.$install_name.macos" "$packages_file" 2>/dev/null | grep -v "^null$" || echo "")
+ ;;
+ windows)
+ install_cmd=$(yq eval ".custom_installs.$install_name.windows" "$packages_file" 2>/dev/null | grep -v "^null$" || echo "")
+ ;;
+ esac
+
+ # Fallback to generic command
+ if [[ -z "$install_cmd" ]]; then
+ install_cmd=$(yq eval ".custom_installs.$install_name.command" "$packages_file" 2>/dev/null | grep -v "^null$" || echo "")
+ fi
+
+ if [[ -n "$install_cmd" ]]; then
+ print_info "Running custom install: $install_name"
+ if execute_command "$install_cmd"; then
+ print_success "Custom install completed: $install_name"
+ # If yq was installed into ~/.local/bin via custom install, ensure PATH includes it for current session
+ if [[ "$install_name" == "yq" && -x "$HOME/.local/bin/yq" ]]; then
+ if [[ ":$PATH:" != *":$HOME/.local/bin:"* ]]; then
+ export PATH="$HOME/.local/bin:$PATH"
+ print_info "Added $HOME/.local/bin to PATH for current session"
+ fi
+ fi
+ else
+ print_error "Custom install failed: $install_name"
+ fi
+ else
+ print_warning "No install command found for $install_name on $CFG_OS"
+ fi
+ done
+}
+
+#======================================
+# Installation Mode Selection
+#======================================
+
+
+detect_installation_mode() {
+ if [[ "$INSTALL_MODE" != "ask" ]]; then
+ return 0 # Mode already set via command line
+ fi
+
+ # Check if this is a re-run
+ if [[ -d "$DOTFILES_DIR" && ! "$UPDATE_MODE" == true ]]; then
+ print_section "Existing Installation Detected"
+ print_info "Dotfiles repository already exists at: $DOTFILES_DIR"
+
+ if [[ "$FORCE_MODE" == true ]]; then
+ print_info "Force mode: proceeding with update"
+ UPDATE_MODE=true
+ INSTALL_MODE="essentials" # Default to essentials for updates
+ else
+ while true; do
+ print_color "$YELLOW" "What would you like to do?"
+ print_color "$CYAN" "1. Update existing dotfiles and system"
+ print_color "$CYAN" "2. Full reinstallation"
+ print_color "$CYAN" "3. Exit"
+ print_color "$YELLOW" "Select option [1-3]: "
+ read -r response
+
+ case "$response" in
+ 1)
+ UPDATE_MODE=true
+ INSTALL_MODE="essentials"
+ print_success "Update mode selected"
+ break
+ ;;
+ 2)
+ print_warning "This will backup and reinstall everything"
+ if prompt_user "Continue with full reinstallation?"; then
+ # Backup existing installation
+ local backup_timestamp=$(date +%Y%m%d-%H%M%S)
+ local backup_location="$HOME/.dotfiles-backup-$backup_timestamp"
+ print_info "Backing up existing installation to: $backup_location"
+ cp -r "$DOTFILES_DIR" "$backup_location" 2>/dev/null || true
+ break
+ else
+ continue
+ fi
+ ;;
+ 3)
+ print_info "Installation cancelled by user"
+ exit 0
+ ;;
+ *)
+ print_warning "Invalid selection. Please enter 1-3"
+ ;;
+ esac
+ done
+ fi
+ fi
+
+ # If still asking, show installation mode selection
+ if [[ "$INSTALL_MODE" == "ask" ]]; then
+ select_installation_mode
+ fi
+}
+
+select_installation_mode() {
+ print_header "Installation Mode Selection"
+
+ print_color "$CYAN" "Available installation modes:"
+ echo
+
+ local mode_number=1
+ local mode_options=()
+
+ for mode in essentials minimal dev server full; do
+ local description="${INSTALLATION_PROFILES[$mode]}"
+ print_color "$YELLOW" "$mode_number. $mode - $description"
+ mode_options+=("$mode")
+ ((mode_number++))
+ done
+
+ echo
+ print_color "$CYAN" "You can also specify a custom profile from the profiles/ directory"
+ echo
+
+ while true; do
+ print_color "$YELLOW" "Select installation mode [1-5] or enter profile name [dev]: "
+ read -r response
+
+ if [[ -z "$response" ]]; then
+ INSTALL_MODE="dev"
+ break
+ elif [[ "$response" =~ ^[1-5]$ ]]; then
+ INSTALL_MODE="${mode_options[$((response-1))]}"
+ break
+ elif [[ "$response" =~ ^[a-zA-Z][a-zA-Z0-9_-]*$ ]]; then
+ # Check if it's a valid profile
+ if [[ -f "profiles/$response.yml" ]] || [[ "${INSTALLATION_PROFILES[$response]:-}" ]]; then
+ INSTALL_MODE="$response"
+ break
+ else
+ print_warning "Profile '$response' not found"
+ fi
+ else
+ print_warning "Invalid selection. Please enter 1-5 or a profile name"
+ fi
+ done
+
+ print_success "Selected installation mode: $INSTALL_MODE"
+ print_info "Description: ${INSTALLATION_PROFILES[$INSTALL_MODE]:-Custom profile}"
+}
+
+#======================================
+# Ask Mode Implementation
+#======================================
+
+should_run_step() {
+ local step="$1"
+ local description="${INSTALLATION_STEPS[$step]}"
+
+ # Respect explicit skip list
+ if is_step_skipped "$step"; then
+ return 1
+ fi
+
+ # Run-only and run-from controls
+ if [[ -n "$RUN_ONLY_STEP" && "$step" != "$RUN_ONLY_STEP" ]]; then
+ return 1
+ fi
+ if [[ -n "$RUN_FROM_STEP" && "$__RUN_FROM_STARTED" != true ]]; then
+ if [[ "$step" == "$RUN_FROM_STEP" ]]; then
+ __RUN_FROM_STARTED=true
+ else
+ return 1
+ fi
+ fi
+
+ # Skip already completed steps unless forced
+ if is_step_completed "$step" && [[ "$FORCE_MODE" != true ]]; then
+ return 1
+ fi
+
+ # Ask mode prompt
+ if [[ "$ASK_MODE" == true ]]; then
+ prompt_user "Run step: $description?" && return 0 || return 1
+ fi
+
+ # Interactive skip even when not in ask mode (non-essential steps)
+ if [[ "$INTERACTIVE_SKIP" == true ]]; then
+ local is_essential=false
+ for es in "${ESSENTIAL_STEPS[@]}"; do [[ "$es" == "$step" ]] && is_essential=true && break; done
+ if [[ "$is_essential" != true ]]; then
+ if ! prompt_user "Proceed with: $description? (Choose No to skip)"; then
+ return 1
+ fi
+ fi
+ fi
+
+ return 0
+}
+
+#======================================
+# Command Line Argument Parsing
+#======================================
+
+show_help() {
+ cat << EOF
+Dotfiles Installation Script
+
+USAGE:
+ $0 [OPTIONS]
+
+OPTIONS:
+ -h, --help Show this help message
+ -r, --resume Resume from last failed step
+ -u, --update Update existing dotfiles and packages
+ -v, --verbose Enable verbose output
+ -n, --dry-run Show what would be done without executing
+ -f, --force Force reinstallation and skip prompts
+ -a, --ask Ask before running each step
+ -m, --mode MODE Installation mode (essentials|minimal|dev|server|full|PROFILE)
+
+INSTALLATION MODES:
+ essentials Install only essential packages (git, curl, etc.)
+ minimal Minimal setup for basic development
+ dev Full development environment (default)
+ server Server configuration
+ full Complete installation with all packages
+ PROFILE Custom profile from profiles/ directory
+
+EXAMPLES:
+ $0 # Interactive installation (asks for mode)
+ $0 --mode essentials # Install essentials only
+ $0 --mode dev # Development environment
+ $0 --resume # Resume from last failed step
+ $0 --update --mode full # Update and install all packages
+ $0 --dry-run --mode dev # Preview development installation
+ $0 --ask --mode minimal # Ask before each step in minimal mode
+
+NOTES:
+ • Running without arguments on an existing installation will default to update mode
+ • Use --force to override existing installations
+ • Use --ask to have control over each installation step
+ • Configuration files are backed up before modification
+
+EOF
+}
+
+parse_arguments() {
+ while [[ $# -gt 0 ]]; do
+ case $1 in
+ -h|--help)
+ show_help
+ exit 0
+ ;;
+ -r|--resume)
+ RESUME_MODE=true
+ shift
+ ;;
+ -u|--update)
+ UPDATE_MODE=true
+ shift
+ ;;
+ -v|--verbose)
+ VERBOSE_MODE=true
+ shift
+ ;;
+ -n|--dry-run)
+ DRY_RUN=true
+ shift
+ ;;
+ -f|--force)
+ FORCE_MODE=true
+ shift
+ ;;
+ -a|--ask)
+ ASK_MODE=true
+ shift
+ ;;
+ -m|--mode)
+ INSTALL_MODE="$2"
+ shift 2
+ ;;
+ *)
+ print_error "Unknown option: $1"
+ show_help
+ exit 1
+ ;;
+ esac
+ done
+
+ # Validate install mode
+ if [[ "$INSTALL_MODE" != "ask" ]]; then
+ if [[ ! "${INSTALLATION_PROFILES[$INSTALL_MODE]:-}" ]] && [[ ! -f "profiles/$INSTALL_MODE.yml" ]]; then
+ print_error "Invalid installation mode: $INSTALL_MODE"
+ print_info "Available modes: ${!INSTALLATION_PROFILES[*]}"
+ exit 1
+ fi
+ fi
+}
+
+#======================================
+# Summary Functions
+#======================================
+
+print_installation_summary() {
+ print_header "Installation Summary"
+
+ local total_steps=${#STEP_ORDER[@]}
+ local completed_count=${#COMPLETED_STEPS[@]}
+ local failed_count=${#FAILED_ITEMS[@]}
+
+ print_section "Progress Overview"
+ print_color "$CYAN" "Installation Mode: $INSTALL_MODE"
+ print_color "$CYAN" "Total Steps: $total_steps"
+ print_color "$GREEN" "Completed: $completed_count"
+ print_color "$RED" "Failed: $failed_count"
+
+ if [[ ${#INSTALL_SUMMARY[@]} -gt 0 ]]; then
+ print_section "Successful Operations"
+ printf '%s\n' "${INSTALL_SUMMARY[@]}"
+ fi
+
+ if [[ ${#FAILED_ITEMS[@]} -gt 0 ]]; then
+ print_section "Failed Operations"
+ printf '%s\n' "${FAILED_ITEMS[@]}"
+ print_warning "Check the log file: $LOG_FILE"
+ fi
+
+ if [[ ${#SKIPPED_ITEMS[@]} -gt 0 ]]; then
+ print_section "Skipped Operations"
+ printf '%s\n' "${SKIPPED_ITEMS[@]}"
+ fi
+
+ echo
+ print_color "$GREEN$BOLD" "Installation completed!"
+ print_info "Log file: $LOG_FILE" "always"
+}
+
+#======================================
+# Main Installation Flow
+#======================================
+
+execute_step() {
+ local step_name="$1"
+ local step_desc="${INSTALLATION_STEPS[$step_name]}"
+
+ if is_step_completed "$step_name" && [[ "$FORCE_MODE" != true ]]; then
+ print_success "$step_desc (already completed)"
+ return 0
+ fi
+
+ if "$step_name"; then
+ print_success "$step_desc completed"
+ mark_step_completed "$step_name"
+ return 0
+ else
+ print_error "$step_desc failed"
+ mark_step_failed "$step_name"
+ return 1
+ fi
+}
+
+main() {
+ parse_arguments "$@"
+ setup_logging
+
+ print_header "Dotfiles Installation"
+
+ if [[ "$DRY_RUN" == true ]]; then
+ print_warning "DRY RUN MODE - No changes will be made"
+ echo
+ fi
+
+ if [[ "$ASK_MODE" == true ]]; then
+ print_warning "ASK MODE - You will be prompted for each step"
+ echo
+ fi
+
+ print_info "Starting installation for user: $USER" "always"
+ print_info "Log file: $LOG_FILE" "always"
+
+ # Handle resume mode
+ if [[ "$RESUME_MODE" == true ]]; then
+ if load_state; then
+ print_info "Resuming from previous installation..." "always"
+ print_info "Last step: ${LAST_STEP:-unknown}" "always"
+ if [[ -n "${COMPLETED_STEPS:-}" ]]; then
+ eval "COMPLETED_STEPS=(${COMPLETED_STEPS:-})"
+ fi
+ else
+ print_warning "No previous installation state found"
+ print_info "Starting fresh installation..."
+ RESUME_MODE=false
+ fi
+ fi
+
+ # Detect installation mode (handles re-runs and updates)
+ detect_installation_mode
+
+ # Show installation plan
+ echo
+ print_color "$YELLOW$BOLD" "Installation Plan (Mode: $INSTALL_MODE):"
+ local step_number=1
+ for step in "${STEP_ORDER[@]}"; do
+ local step_desc="${INSTALLATION_STEPS[$step]}"
+ if is_step_completed "$step" && [[ "$FORCE_MODE" != true ]]; then
+ print_color "$GREEN" "$step_number. $step_desc (✓ completed)"
+ else
+ print_color "$CYAN" "$step_number. $step_desc"
+ fi
+ step_number=$((step_number + 1))
+ done
+
+ echo
+ if [[ "$FORCE_MODE" != true ]] && [[ "$DRY_RUN" != true ]] && [[ "$ASK_MODE" != true ]]; then
+ if ! prompt_user "Continue with installation?"; then
+ print_info "Installation cancelled by user"
+ exit 0
+ fi
+ fi
+
+ # Execute installation steps
+ local failed_steps=()
+ local step_number=1
+ local total_steps=${#STEP_ORDER[@]}
+
+ for step in "${STEP_ORDER[@]}"; do
+ echo
+ print_color "$CYAN$BOLD" "[$step_number/$total_steps] ${INSTALLATION_STEPS[$step]}"
+
+ # Check if we should run this step (ask mode)
+ if ! should_run_step "$step"; then
+ print_skip "${INSTALLATION_STEPS[$step]} (user choice)"
+ step_number=$((step_number + 1))
+ continue
+ fi
+
+ if execute_step "$step"; then
+ print_info "Step completed successfully: $step"
+ else
+ failed_steps+=("$step")
+ print_error "Step failed: $step"
+
+ if [[ "$FORCE_MODE" != true ]] && [[ "$DRY_RUN" != true ]]; then
+ echo
+ if ! prompt_user "Step '$step' failed. Continue with remaining steps?" "Y"; then
+ print_info "Installation stopped by user"
+ break
+ fi
+ fi
+ fi
+
+ step_number=$((step_number + 1))
+ done
+
+ # Post-installation
+ if [[ ${#failed_steps[@]} -eq 0 ]]; then
+ print_success "All installation steps completed successfully!"
+ clear_state
+ else
+ print_warning "${#failed_steps[@]} steps failed: ${failed_steps[*]}"
+ if [[ "${failed_steps[-1]:-}" != "" ]]; then
+ save_state "${failed_steps[-1]}" "failed"
+ fi
+ fi
+
+ print_installation_summary
+
+ # Final recommendations
+ if [[ "$DRY_RUN" != true ]]; then
+ echo
+ print_section "Post-Installation Recommendations"
+ print_color "$CYAN" "• Restart your terminal or run: source ~/.bashrc (or ~/.zshrc)"
+ print_color "$CYAN" "• Review your dotfiles configuration in: $DOTFILES_DIR"
+ print_color "$CYAN" "• Use the 'config' command to manage your dotfiles"
+ print_color "$CYAN" "• Test the config command: config status"
+
+ if [[ ${#failed_steps[@]} -gt 0 ]]; then
+ print_color "$YELLOW" "• Run '$0 --resume' to retry failed steps"
+ print_color "$YELLOW" "• Check the log file for detailed error information: $LOG_FILE"
+ fi
+
+ echo
+ print_color "$GREEN$BOLD" "Thank you for using the Dotfiles Installation Script!"
+ fi
+
+ [[ ${#failed_steps[@]} -eq 0 ]] && exit 0 || exit 1
+}
+
+#======================================
+# Script Entry Point
+#======================================
+
+cleanup_on_exit() {
+ local exit_code=$?
+
+ if [[ $exit_code -ne 0 ]] && [[ "$DRY_RUN" != true ]]; then
+ print_error "Installation interrupted (exit code: $exit_code)"
+
+ if [[ -n "${current_step:-}" ]]; then
+ save_state "$current_step" "interrupted"
+ print_info "State saved. Run with --resume to continue"
+ fi
+ fi
+}
+
+handle_interrupt() {
+ print_warning "Installation interrupted by user"
+ exit 130
+}
+
+trap cleanup_on_exit EXIT
+trap handle_interrupt INT
+
+# Execute main if script is run directly
+if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
+ # Check basic requirements
+ for req in git curl; do
+ if ! command_exists "$req"; then
+ print_error "$req is required but not installed"
+ exit 1
+ fi
+ done
+
+ main "$@"
+fi
diff --git a/common/packages.yml b/common/packages.yml
new file mode 100644
index 0000000..0fce986
--- /dev/null
+++ b/common/packages.yml
@@ -0,0 +1,1066 @@
+# Dotfiles Installation Packages Configuration
+# This file defines packages to install based on installation profiles and distribution-specific mappings
+
+#======================================
+# Installation Profiles
+#======================================
+
+# Core packages needed by all installations
+common:
+ - git
+ - curl
+ - wget
+
+# Essential packages for basic functionality
+essentials:
+ - zsh
+ - bash
+ - vim
+ - openssh
+ - sudo
+ - man
+ - bc
+ - time
+ - rsync
+ - tree
+
+# Minimal development environment
+minimal:
+ - gcc
+ - make
+ - python
+ - jq
+ - fzf
+ - neovim
+ - tmux
+
+# Full development environment
+dev:
+ - clang
+ - meson
+ - gdb
+ - cmake
+ - go
+ - ninja
+ - ripgrep
+ - fd
+ - nodejs
+ - emacs
+ - vscode
+ - ansible
+
+# Server-focused packages
+server:
+ - ufw
+ - net-tools
+ - htop
+ - btop
+ - powertop
+ - clamav
+ - ntp
+ - networkmanager
+ - smartmontools
+ - hdparm
+ - acpi
+ - parted
+ - sysstat
+ - hwinfo
+ - ansible
+
+# Desktop environment packages
+desktop:
+ - xorg
+ - wayland
+ - xclip
+ - xterm
+ - gtk
+ - firefox
+ - mpv
+ - discord
+ - libinput
+ - nnn
+ - ranger
+ - obs-studio
+ - unrar
+ - unzip
+ - p7zip
+ - imagemagick
+ - ffmpeg
+ - wezterm
+ - ncdu
+ - picom
+ - rofi
+ - udiskie
+ - brightnessctl
+ - wl-clipboard
+ - nemo
+ - blueman
+ - bluez
+
+# Window managers
+wm:
+ - hyprland
+ - bspwm
+ - sxhkd
+ - polybar
+
+# Audio/Media packages
+media:
+ - mpd
+ - pipewire
+ - ncmpcpp
+
+# Gaming
+gaming:
+ - wine
+ - steam
+
+# Virtualization
+virtualization:
+ - libvirt
+ - qemu
+
+# Fonts
+fonts:
+ - hack-font
+ - nerd-fonts
+ - font-awesome
+ - dejavu-fonts
+
+#======================================
+# Distribution-specific package mappings
+# Format: generic_name -> distro_specific_name
+#======================================
+
+arch:
+ # Core tools
+ python: python
+ nodejs: nodejs
+ man: man-pages man-db
+ tree: tree
+ ntp: ntpsec
+ hack-font: ttf-hack
+ nerd-fonts: ttf-nerd-fonts-symbols-mono
+ font-awesome: ttf-font-awesome
+ dejavu-fonts: ttf-dejavu
+ networkmanager: networkmanager
+ qemu: qemu-full
+ vscode: code
+
+ # Desktop specific
+ xorg: xorg xorg-server
+ wayland: wayland xorg-xwayland
+ gtk: gtk3 gtk4
+ libinput: libinput xf86-input-libinput
+ bluez: bluez bluez-utils bluez-tools
+
+ # Media
+ pipewire: pipewire wireplumber
+
+debian:
+ # Core tools
+ python: python3 python3-pip
+ nodejs: nodejs npm
+ man: man-pages-dev man-db
+ tree: tree
+ ntp: ntp
+ hack-font: fonts-hack
+ nerd-fonts: fonts-nerd-font-symbols
+ font-awesome: fonts-font-awesome
+ dejavu-fonts: fonts-dejavu
+ networkmanager: network-manager
+ qemu: qemu-system
+ vscode: code
+ fd: fd-find
+ openssh: openssh-server
+ ansible: ansible
+
+fedora:
+ # Core tools
+ python: python3 python3-pip
+ nodejs: nodejs npm
+ man: man-pages man-db
+ tree: tree
+ ntp: chrony
+ hack-font: adobe-source-code-pro-fonts
+ nerd-fonts: powerline-fonts
+ font-awesome: fontawesome-fonts
+ dejavu-fonts: dejavu-fonts-common
+ networkmanager: NetworkManager
+ qemu: qemu-kvm
+ vscode: code-oss
+ fd: fd-find
+ openssh: openssh-server
+ ansible: ansible
+ ninja: ninja-build
+
+ # Desktop specific
+ xorg: xorg xserver-xorg
+ wayland: libwayland-dev xwayland
+ gtk: libgtk-3-dev libgtk-4-dev
+ libinput: libinput10 xserver-xorg-input-libinput
+ bluez: bluez bluez-tools
+
+ # Media
+ pipewire: pipewire wireplumber
+
+ # System tools
+ ufw: ufw
+ net-tools: net-tools
+ btop: btop
+ powertop: powertop
+ clamav: clamav
+ smartmontools: smartmontools
+ hdparm: hdparm
+ acpi: acpi
+ parted: parted
+ cups: cups
+ sysstat: sysstat
+ hwinfo: hwinfo
+
+rhel:
+ # Core tools
+ python: python3 python3-pip
+ nodejs: nodejs npm
+ man: man-pages man-db
+ tree: tree
+ ntp: chrony
+ hack-font: adobe-source-code-pro-fonts
+ nerd-fonts: powerline-fonts
+ font-awesome: fontawesome-fonts
+ dejavu-fonts: dejavu-fonts-common
+ networkmanager: NetworkManager
+ qemu: qemu-kvm
+ vscode: code
+ fd: fd-find
+ openssh: openssh-server
+ ansible: ansible
+ ninja: ninja-build
+
+ # Desktop specific
+ xorg: xorg-x11-server-Xorg
+ wayland: wayland-devel xorg-x11-server-Xwayland
+ gtk: gtk3-devel gtk4-devel
+ libinput: libinput
+ bluez: bluez bluez-tools
+
+ # System tools
+ ufw: firewalld
+ net-tools: net-tools
+ btop: htop
+ powertop: powertop
+ clamav: clamav
+ smartmontools: smartmontools
+ hdparm: hdparm
+ acpi: acpi
+ parted: parted
+ cups: cups
+ sysstat: sysstat
+
+opensuse:
+ # Core tools
+ python: python3 python3-pip
+ nodejs: nodejs16 npm16
+ man: man-pages man
+ tree: tree
+ ntp: chrony
+ hack-font: adobe-sourcecodepro-fonts
+ nerd-fonts: powerline-fonts
+ font-awesome: fontawesome-fonts
+ dejavu-fonts: dejavu-fonts
+ networkmanager: NetworkManager
+ qemu: qemu
+ vscode: code
+ openssh: openssh
+ ansible: ansible
+
+gentoo:
+ # Core tools with full package paths
+ git: dev-vcs/git
+ curl: net-misc/curl
+ wget: net-misc/wget
+ zsh: app-shells/zsh
+ bash: app-shells/bash
+ vim: app-editors/vim
+ neovim: app-editors/neovim
+ tmux: app-misc/tmux
+ openssh: net-misc/openssh
+ sudo: app-admin/sudo
+ man: sys-apps/man-pages sys-apps/man-db
+ bc: sys-devel/bc
+ time: sys-process/time
+ rsync: net-misc/rsync
+ tree: app-text/tree
+ gcc: sys-devel/gcc
+ clang: sys-devel/clang
+ make: sys-devel/make
+ cmake: dev-util/cmake
+ meson: dev-util/meson
+ gdb: sys-devel/gdb
+ ninja: dev-util/ninja
+ ripgrep: sys-apps/ripgrep
+ fd: sys-apps/fd
+ python: dev-lang/python
+ nodejs: net-libs/nodejs
+ jq: app-misc/jq
+ fzf: app-shells/fzf
+ emacs: app-editors/emacs
+ vscode: app-editors/vscode
+ go: dev-lang/go
+ htop: sys-process/htop
+ ufw: net-firewall/ufw
+ net-tools: sys-apps/net-tools
+ btop: sys-process/btop
+ powertop: sys-power/powertop
+ clamav: app-antivirus/clamav
+ ntp: net-misc/chrony
+ networkmanager: net-misc/networkmanager
+ smartmontools: sys-apps/smartmontools
+ hdparm: sys-apps/hdparm
+ acpi: sys-power/acpi
+ parted: sys-block/parted
+ cups: net-print/cups
+ sysstat: app-admin/sysstat
+ hwinfo: sys-apps/hwinfo
+ hack-font: media-fonts/hack
+ nerd-fonts: media-fonts/nerd-fonts
+ font-awesome: media-fonts/fontawesome
+ dejavu-fonts: media-fonts/dejavu
+
+ # Desktop
+ xorg: x11-base/xorg-server
+ wayland: dev-libs/wayland x11-base/xwayland
+ xclip: x11-misc/xclip
+ xterm: x11-terms/xterm
+ gtk: x11-libs/gtk+
+ firefox: www-client/firefox
+ mpv: media-video/mpv
+ discord: net-im/discord-bin
+ libinput: dev-libs/libinput x11-drivers/xf86-input-libinput
+ nnn: app-misc/nnn
+ ranger: app-misc/ranger
+ obs-studio: media-video/obs-studio
+ unrar: app-arch/unrar
+ unzip: app-arch/unzip
+ p7zip: app-arch/p7zip
+ imagemagick: media-gfx/imagemagick
+ ffmpeg: media-video/ffmpeg
+ wezterm: x11-terms/wezterm
+ ncdu: sys-fs/ncdu
+ picom: x11-misc/picom
+ rofi: x11-misc/rofi
+ udiskie: sys-fs/udiskie
+ brightnessctl: app-misc/brightnessctl
+ wl-clipboard: gui-apps/wl-clipboard
+ nemo: gnome-extra/nemo
+ blueman: net-wireless/blueman
+ bluez: net-wireless/bluez
+
+ # Window managers
+ hyprland: gui-wm/hyprland
+ bspwm: x11-wm/bspwm
+ sxhkd: x11-misc/sxhkd
+ polybar: x11-misc/polybar
+
+ # Media
+ mpd: media-sound/mpd
+ pipewire: media-video/pipewire media-video/wireplumber
+ ncmpcpp: media-sound/ncmpcpp
+
+ # Gaming
+ wine: app-emulation/wine-vanilla
+ steam: games-util/steam-launcher
+
+ # Virtualization
+ libvirt: app-emulation/libvirt
+ qemu: app-emulation/qemu
+
+alpine:
+ python: python3 py3-pip
+ nodejs: nodejs npm
+ man: man-pages man-db
+ ntp: chrony
+ htop: htop
+ openssh: openssh
+ ansible: ansible
+
+void:
+ python: python3 python3-pip
+ nodejs: nodejs
+ man: man-pages
+ ntp: chrony
+ openssh: openssh
+ ripgrep: ripgrep
+ fd: fd
+ btop: btop
+ networkmanager: NetworkManager
+ ansible: ansible
+
+macos:
+ # Homebrew packages
+ - git
+ - curl
+ - wget
+ - zsh
+ - bash
+ - vim
+ - neovim
+ - tmux
+ - openssh
+ - python3
+ - node
+ - jq
+ - fzf
+ - ripgrep
+ - fd
+ - bat
+ - htop
+ - rsync
+ - cmake
+ - ninja
+ - go
+ - emacs
+ - visual-studio-code
+ - ansible
+
+windows:
+ - git
+ - ripgrep
+ - fd
+ - sudo
+ - win32yank
+ - microsoft-windows-terminal
+ - wsl
+ - firefox
+ - setdefaultbrowser
+ - nodejs
+ - bat
+ - 7zip
+ - python
+ - javaruntime
+ - autohotkey
+ - bitwarden
+ - notepadplusplus
+ - neovim
+
+bloatware:
+ # - Anytime
+ - BioEnrollment
+ # - Browser
+ - ContactSupport
+ - Cortana
+ # - Defender
+ - Feedback
+ - Flash
+ # - Gaming # Breaks Xbox Live Account Login
+ # - Holo
+ # - InternetExplorer
+ - Maps
+ # - MiracastView
+ - OneDrive
+ # - SecHealthUI
+ - Wallet
+ # - Xbox # Causes a bootloop since upgrade 1511?
+
+default:
+ # default Windows 10 apps
+ # - Microsoft.3DBuilder
+ - Microsoft.Appconnector
+ - Microsoft.BingFinance
+ - Microsoft.BingNews
+ - Microsoft.BingSports
+ - Microsoft.BingTranslator
+ - Microsoft.BingWeather
+ # - Microsoft.FreshPaint
+ # - Microsoft.Microsoft3DViewer
+ - Microsoft.MicrosoftOfficeHub
+ - Microsoft.MicrosoftSolitaireCollection
+ - Microsoft.MicrosoftPowerBIForWindows
+ - Microsoft.MinecraftUWP
+ # - Microsoft.MicrosoftStickyNotes
+ # - Microsoft.NetworkSpeedTest
+ - Microsoft.Office.OneNote
+ # - Microsoft.OneConnect
+ - Microsoft.People
+ # - Microsoft.Print3D
+ - Microsoft.SkypeApp
+ - Microsoft.Wallet
+ # - Microsoft.Windows.Photos
+ # - Microsoft.WindowsAlarms
+ # - Microsoft.WindowsCalculator
+ - Microsoft.WindowsCamera
+ - microsoft.windowscommunicationsapps
+ - Microsoft.WindowsMaps
+ - Microsoft.WindowsPhone
+ - Microsoft.WindowsSoundRecorder
+ - Microsoft.WindowsStore
+ # - Microsoft.XboxApp
+ # - Microsoft.XboxGameOverlay
+ # - Microsoft.XboxIdentityProvider
+ # - Microsoft.XboxSpeechToTextOverlay
+ - Microsoft.ZuneMusic
+ - Microsoft.ZuneVideo
+
+ # Threshold 2 apps
+ - Microsoft.CommsPhone
+ - Microsoft.ConnectivityStore
+ - Microsoft.GetHelp
+ - Microsoft.Getstarted
+ - Microsoft.Messaging
+ - Microsoft.Office.Sway
+ - Microsoft.OneConnect
+ - Microsoft.WindowsFeedbackHub
+
+ # Redstone apps
+ - Microsoft.BingFoodAndDrink
+ - Microsoft.BingTravel
+ - Microsoft.BingHealthAndFitness
+ - Microsoft.WindowsReadingList
+
+ # non-Microsoft
+ - king.com.CandyCrushSaga
+ - king.com.CandyCrushSodaSaga
+ - king.com.*
+ - Facebook.Facebook
+
+ # apps which cannot be removed using Remove-AppxPackage
+ # - Microsoft.BioEnrollment
+ # - Microsoft.MicrosoftEdge
+ # - Microsoft.Windows.Cortana
+ # - Microsoft.WindowsFeedback
+ # - Microsoft.XboxGameCallableUI
+ # - Microsoft.XboxIdentityProvider
+ # - Windows.ContactSupport
+
+#======================================
+# Gentoo USE flags configuration
+#======================================
+gentoo_use_flags:
+ git: "curl gpg perl python"
+ curl: "ssl http2 ipv6"
+ wget: "ssl ipv6 nls"
+ zsh: "unicode pcre gdbm"
+ bash: "net nls readline"
+ tmux: "vim-syntax"
+ vim: "python lua ruby perl cscope"
+ neovim: "lua python ruby"
+ emacs: "gtk jpeg png svg tiff xpm cairo dbus json ssl xml"
+ gcc: "cxx fortran graphite jit nptl openmp pch pie ssp"
+ clang: "static-analyzer"
+ python: "sqlite ssl readline ncurses xml"
+ nodejs: "ssl"
+ htop: "unicode lm-sensors"
+ openssh: "ssl kerberos ldap pam"
+ firefox: "dbus gtk3 pulseaudio startup-notification wifi"
+ mpv: "alsa pulseaudio lua drm wayland X"
+ gtk: "wayland X cups introspection"
+ pipewire: "alsa bluetooth jack pulseaudio sound-server"
+ ffmpeg: "alsa encode mp3 opus pulseaudio theora vorbis webp x264 x265"
+ networkmanager: "bluetooth dhclient introspection wifi"
+ bluez: "alsa cups obex readline"
+ qemu: "aio alsa bluetooth curl gtk jpeg ncurses opengl png pulseaudio sdl spice ssh usb vnc"
+ libvirt: "firewalld libssh nfs numa parted qemu sasl udev"
+
+#======================================
+# System tweaks and configurations
+#======================================
+system_tweaks:
+ gnome:
+ # Power management settings
+ - gsettings set org.gnome.desktop.session idle-delay 0
+ - gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-ac-type 'nothing'
+ - gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-battery-type 'nothing'
+ # Interface tweaks
+ - gsettings set org.gnome.desktop.interface clock-show-weekday true
+ - gsettings set org.gnome.desktop.interface show-battery-percentage true
+
+ kde:
+ # Power management
+ - kwriteconfig5 --file powermanagementprofilesrc --group AC --group DimDisplay --key idleTime 300000
+ - kwriteconfig5 --file powermanagementprofilesrc --group AC --group DPMSControl --key idleTime 600000
+
+ windows:
+ registry:
+ # Explorer settings
+ - path: "HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced"
+ name: "Hidden"
+ value: 1
+ type: "DWORD"
+ description: "Show hidden files"
+ - path: "HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced"
+ name: "HideFileExt"
+ value: 0
+ type: "DWORD"
+ description: "Show file extensions"
+ - path: "HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced"
+ name: "TaskbarGlomLevel"
+ value: 2
+ type: "DWORD"
+ description: "Never combine taskbar buttons"
+ - path: "HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced"
+ name: "TaskbarSmallIcons"
+ value: 1
+ type: "DWORD"
+ description: "Use small taskbar icons"
+
+ # Dark mode
+ - path: "HKCU:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"
+ name: "AppsUseLightTheme"
+ value: 0
+ type: "DWORD"
+ description: "Use dark theme for apps"
+ - path: "HKCU:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"
+ name: "SystemUsesLightTheme"
+ value: 0
+ type: "DWORD"
+ description: "Use dark theme for system"
+
+ # Search settings
+ - path: "HKCU:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Search"
+ name: "SearchBoxTaskbarMode"
+ value: 0
+ type: "DWORD"
+ description: "Hide search box from taskbar"
+
+ features:
+ - name: "Microsoft-Windows-Subsystem-Linux"
+ description: "Windows Subsystem for Linux"
+ requires_admin: true
+ - name: "VirtualMachinePlatform"
+ description: "Virtual Machine Platform"
+ requires_admin: true
+
+ hardening:
+ registry:
+ # Security hardening registry settings
+ - path: "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System"
+ name: "EnableLUA"
+ value: 1
+ type: "DWORD"
+ description: "Enable User Account Control"
+ - path: "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System"
+ name: "ConsentPromptBehaviorAdmin"
+ value: 2
+ type: "DWORD"
+ description: "UAC prompt for administrators"
+ - path: "HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"
+ name: "DisablePasswordCaching"
+ value: 1
+ type: "DWORD"
+ description: "Disable password caching"
+ - path: "HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Lsa"
+ name: "LimitBlankPasswordUse"
+ value: 1
+ type: "DWORD"
+ description: "Limit blank password use"
+ - path: "HKLM:\\SYSTEM\\CurrentControlSet\\Services\\lanmanserver\\parameters"
+ name: "AutoDisconnectTimeout"
+ value: 15
+ type: "DWORD"
+ description: "Auto disconnect timeout"
+ - path: "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System"
+ name: "DontDisplayLastUserName"
+ value: 1
+ type: "DWORD"
+ description: "Don't display last username"
+
+ services:
+ disable:
+ - "Fax"
+ - "TelnetD"
+ - "RemoteRegistry"
+ - "Messenger"
+ - "NetMeeting Remote Desktop Sharing"
+ - "Remote Desktop Help Session Manager"
+ - "Routing and Remote Access"
+ - "Simple TCP/IP Services"
+ - "SNMP Service"
+
+ firewall:
+ - "netsh advfirewall set allprofiles state on"
+ - "netsh advfirewall firewall set rule group=\"File and Printer Sharing\" new enable=No"
+ - "netsh advfirewall firewall set rule group=\"Network Discovery\" new enable=No"
+
+ linux:
+ hardening:
+ sysctl:
+ # Network security
+ - net.ipv4.ip_forward = 0
+ - net.ipv4.conf.all.send_redirects = 0
+ - net.ipv4.conf.default.send_redirects = 0
+ - net.ipv4.conf.all.accept_source_route = 0
+ - net.ipv4.conf.default.accept_source_route = 0
+ - net.ipv4.conf.all.accept_redirects = 0
+ - net.ipv4.conf.default.accept_redirects = 0
+ - net.ipv4.conf.all.secure_redirects = 0
+ - net.ipv4.conf.default.secure_redirects = 0
+ - net.ipv4.conf.all.log_martians = 1
+ - net.ipv4.conf.default.log_martians = 1
+ - net.ipv4.icmp_echo_ignore_broadcasts = 1
+ - net.ipv4.icmp_ignore_bogus_error_responses = 1
+ - net.ipv4.conf.all.rp_filter = 1
+ - net.ipv4.conf.default.rp_filter = 1
+ - net.ipv4.tcp_syncookies = 1
+ - net.ipv6.conf.all.accept_ra = 0
+ - net.ipv6.conf.default.accept_ra = 0
+ - net.ipv6.conf.all.accept_redirects = 0
+ - net.ipv6.conf.default.accept_redirects = 0
+
+ # Kernel security
+ - kernel.dmesg_restrict = 1
+ - kernel.kptr_restrict = 2
+ - kernel.yama.ptrace_scope = 1
+ - kernel.kexec_load_disabled = 1
+ - kernel.unprivileged_bpf_disabled = 1
+ - net.core.bpf_jit_harden = 2
+
+ # Memory protection
+ - kernel.randomize_va_space = 2
+ - vm.mmap_min_addr = 65536
+
+ packages:
+ security:
+ - fail2ban
+ - ufw
+ - rkhunter
+ - chkrootkit
+ - lynis
+ - aide
+ - apparmor
+ - apparmor-utils
+
+ services:
+ disable:
+ - avahi-daemon
+ - cups
+ - bluetooth
+ - whoopsie
+ - apport
+ enable:
+ - ufw
+ - fail2ban
+ - apparmor
+
+ filesystem:
+ - "chmod 700 /root"
+ - "chmod 644 /etc/passwd"
+ - "chmod 600 /etc/shadow"
+ - "chmod 644 /etc/group"
+ - "chmod 600 /etc/gshadow"
+ - "find /home -name '.netrc' -delete"
+ - "find /home -name '.rhosts' -delete"
+
+ macos:
+ hardening:
+ defaults:
+ # Security settings
+ - domain: "com.apple.screensaver"
+ key: "askForPassword"
+ value: 1
+ type: "int"
+ description: "Require password after screensaver"
+ - domain: "com.apple.screensaver"
+ key: "askForPasswordDelay"
+ value: 0
+ type: "int"
+ description: "Require password immediately"
+ - domain: "com.apple.Safari"
+ key: "SendDoNotTrackHTTPHeader"
+ value: 1
+ type: "bool"
+ description: "Enable Do Not Track"
+ - domain: "com.apple.Safari"
+ key: "AutoFillPasswords"
+ value: 0
+ type: "bool"
+ description: "Disable password autofill"
+ - domain: "com.apple.loginwindow"
+ key: "GuestEnabled"
+ value: 0
+ type: "bool"
+ description: "Disable guest account"
+ - domain: "com.apple.loginwindow"
+ key: "SHOWFULLNAME"
+ value: 1
+ type: "bool"
+ description: "Show full name in login window"
+
+ system:
+ - "sudo spctl --master-enable" # Enable Gatekeeper
+ - "sudo defaults write /Library/Preferences/com.apple.alf globalstate -int 1" # Enable firewall
+ - "sudo launchctl load /System/Library/LaunchDaemons/com.apple.locate.plist" # Enable locate database
+
+ services:
+ disable:
+ - "com.apple.AirPlayXPCHelper"
+ - "com.apple.RemoteDesktop.agent"
+
+ packages:
+ security:
+ - gpg
+ - gnupg
+ - pinentry-mac
+
+#======================================
+# Service configurations
+#======================================
+services:
+ enable:
+ all:
+ - sshd
+ - networkmanager
+ server:
+ - firewalld
+ - chronyd
+ desktop:
+ - bluetooth
+ - cups
+ disable:
+ server:
+ - bluetooth
+ - cups
+ - gdm
+ minimal:
+ - cups
+ - bluetooth
+
+#======================================
+# Development environment configurations
+#======================================
+development:
+ git_config:
+ - git config --global init.defaultBranch main
+ - git config --global pull.rebase false
+ - git config --global core.editor vim
+
+ rust:
+ components:
+ - rustc
+ - cargo
+ - clippy
+ - rustfmt
+
+ nodejs:
+ global_packages:
+ - typescript
+ - eslint
+ - prettier
+
+ python:
+ global_packages:
+ - black
+ - flake8
+ - mypy
+ - requests
+ - virtualenvwrapper
+
+#======================================
+# System update checks and maintenance
+#======================================
+system_updates:
+ linux:
+ kernel_check:
+ - "uname -r" # Current kernel
+ - "ls /boot/vmlinuz-* | tail -1 | sed 's/.*vmlinuz-//'" # Latest available
+
+ distro_updates:
+ arch:
+ check: "checkupdates"
+ update: "pacman -Syu"
+ kernel_update: "pacman -S linux linux-headers"
+ debian:
+ check: "apt list --upgradable"
+ update: "apt update && apt upgrade -y"
+ kernel_update: "apt install linux-image-generic linux-headers-generic"
+ rhel:
+ check: "dnf check-update"
+ update: "dnf update -y"
+ kernel_update: "dnf update kernel kernel-headers"
+ gentoo:
+ check: "emerge -pv --update --deep --newuse @world"
+ update: "emerge --update --deep --newuse @world"
+ kernel_update: "emerge gentoo-sources && genkernel all"
+
+ macos:
+ system_updates:
+ check: "softwareupdate -l"
+ update: "softwareupdate -ia"
+ major_check: "softwareupdate --list-full-installers"
+
+ windows:
+ system_updates:
+ check: "Get-WindowsUpdate -MicrosoftUpdate"
+ update: "Install-WindowsUpdate -MicrosoftUpdate -AcceptAll -AutoReboot"
+ feature_updates: "Get-WindowsUpdate -UpdateType Software"
+
+#======================================
+# Custom installation commands
+#======================================
+custom_installs:
+ yq:
+ condition: "! command -v yq"
+ linux: |
+ mkdir -p "$HOME/.local/bin"
+ YQ_VERSION=$(curl -s https://api.github.com/repos/mikefarah/yq/releases/latest | grep 'tag_name' | cut -d'"' -f4)
+ YQ_BINARY="yq_linux_amd64"
+ curl -L "https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/${YQ_BINARY}" -o "$HOME/.local/bin/yq"
+ chmod +x "$HOME/.local/bin/yq"
+ macos: "brew install yq"
+ windows: "choco install yq"
+
+ homebrew:
+ condition: "test $(uname) = Darwin && ! command -v brew"
+ macos: '/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'
+
+ system_updates:
+ condition: "true" # Always available
+ description: "Check and install system updates"
+ linux: |
+ case "$CFG_DISTRO" in
+ arch) checkupdates && sudo pacman -Syu ;;
+ debian|ubuntu) apt list --upgradable && sudo apt update && sudo apt upgrade -y ;;
+ rhel|fedora|centos) dnf check-update && sudo dnf update -y ;;
+ gentoo) emerge -pv --update --deep --newuse @world && sudo emerge --update --deep --newuse @world ;;
+ *) echo "Unsupported distribution for automatic updates" ;;
+ esac
+ macos: "softwareupdate -l && sudo softwareupdate -ia"
+ windows: |
+ if (Get-Module -ListAvailable -Name PSWindowsUpdate) {
+ Get-WindowsUpdate -MicrosoftUpdate
+ Install-WindowsUpdate -MicrosoftUpdate -AcceptAll -AutoReboot
+ } else {
+ Write-Host "PSWindowsUpdate module not installed. Install with: Install-Module PSWindowsUpdate"
+ }
+
+ zsh_plugins:
+ condition: "command -v zsh"
+ description: "Install common Zsh plugins"
+ linux: |
+ ZPLUG_DIR="$HOME/.config/zsh/plugins"; mkdir -p "$ZPLUG_DIR"; command -v git >/dev/null 2>&1 || exit 0; c(){ n="$1"; u="$2"; [ -d "$ZPLUG_DIR/$n" ] && return 0; env -i PATH="$PATH" HOME="$HOME" GIT_TERMINAL_PROMPT=0 GIT_ASKPASS=/bin/true git -c credential.helper= -c core.askPass= clone --depth 1 --single-branch "$u" "$ZPLUG_DIR/$n" 2>/dev/null || true; }; c zsh-you-should-use https://github.com/MichaelAquilina/zsh-you-should-use.git; c zsh-syntax-highlighting https://github.com/zsh-users/zsh-syntax-highlighting.git; c zsh-autosuggestions https://github.com/zsh-users/zsh-autosuggestions.git
+ macos: |
+ ZPLUG_DIR="$HOME/.config/zsh/plugins"; mkdir -p "$ZPLUG_DIR"; command -v git >/dev/null 2>&1 || exit 0; c(){ n="$1"; u="$2"; [ -d "$ZPLUG_DIR/$n" ] && return 0; env -i PATH="$PATH" HOME="$HOME" GIT_TERMINAL_PROMPT=0 GIT_ASKPASS=/bin/true git -c credential.helper= -c core.askPass= clone --depth 1 --single-branch "$u" "$ZPLUG_DIR/$n" 2>/dev/null || true; }; c zsh-you-should-use https://github.com/MichaelAquilina/zsh-you-should-use.git; c zsh-syntax-highlighting https://github.com/zsh-users/zsh-syntax-highlighting.git; c zsh-autosuggestions https://github.com/zsh-users/zsh-autosuggestions.git
+
+ vscode_extensions:
+ condition: "command -v code"
+ description: "Install template VSCode extensions"
+ linux: |
+ for e in ms-python.python ms-vscode.cpptools golang.Go rust-lang.rust-analyzer esbenp.prettier-vscode eamodio.gitlens ms-azuretools.vscode-docker hashicorp.terraform redhat.ansible; do code --install-extension "$e" --force >/dev/null 2>&1 || true; done
+ macos: |
+ for e in ms-python.python ms-vscode.cpptools golang.Go rust-lang.rust-analyzer esbenp.prettier-vscode eamodio.gitlens ms-azuretools.vscode-docker hashicorp.terraform redhat.ansible; do code --install-extension "$e" --force >/dev/null 2>&1 || true; done
+
+ nix_home_manager:
+ condition: "command -v nix-env"
+ description: "Bootstrap Home Manager if missing"
+ linux: |
+ if ! command -v home-manager >/dev/null 2>&1; then nix-channel --add https://github.com/nix-community/home-manager/archive/master.tar.gz home-manager || true; nix-channel --update || true; nix-shell '<home-manager>' -A install || true; fi
+ macos: |
+ if ! command -v home-manager >/dev/null 2>&1; then nix-channel --add https://github.com/nix-community/home-manager/archive/master.tar.gz home-manager || true; nix-channel --update || true; nix-shell '<home-manager>' -A install || true; fi
+
+
+#======================================
+# Profile-specific package lists
+#======================================
+profiles:
+ essentials:
+ description: "Essential packages only (git, curl, wget, vim, zsh)"
+ packages:
+ - common
+ - essentials
+
+ minimal:
+ description: "Minimal setup for basic development"
+ packages:
+ - common
+ - essentials
+ - minimal
+
+ dev:
+ description: "Full development environment"
+ packages:
+ - common
+ - essentials
+ - minimal
+ - dev
+ enable_development: true
+
+ server:
+ description: "Server configuration"
+ packages:
+ - common
+ - essentials
+ - minimal
+ - server
+ enable_services: server
+
+ full:
+ description: "Complete installation with all packages"
+ packages:
+ - common
+ - essentials
+ - minimal
+ - dev
+ - server
+ - desktop
+ - wm
+ - media
+ - fonts
+ enable_development: true
+ enable_services: desktop
+
+#======================================
+# Package management helpers
+#======================================
+package_managers:
+ arch:
+ update: "pacman -Syu"
+ install: "pacman -S --noconfirm"
+ search: "pacman -Ss"
+
+ debian:
+ update: "apt update && apt upgrade -y"
+ install: "apt install -y"
+ search: "apt search"
+
+ rhel:
+ update: "dnf update -y"
+ install: "dnf install -y"
+ search: "dnf search"
+
+ fedora:
+ update: "dnf update -y"
+ install: "dnf install -y"
+ search: "dnf search"
+
+ opensuse:
+ update: "zypper update -y"
+ install: "zypper install -y"
+ search: "zypper search"
+
+ gentoo:
+ update: "emerge --sync && emerge -uDN @world"
+ install: "emerge"
+ search: "emerge --search"
+
+ alpine:
+ update: "apk update && apk upgrade"
+ install: "apk add"
+ search: "apk search"
+
+ void:
+ update: "xbps-install -Su"
+ install: "xbps-install -y"
+ search: "xbps-query -Rs"
+
+ macos:
+ update: "brew update && brew upgrade"
+ install: "brew install"
+ search: "brew search"
+
+ windows:
+ update: "choco upgrade all -y"
+ install: "choco install -y"
+ search: "choco search"
+ nix:
+ update: "nix-channel --update && nix-env -u"
+ install: "nix-env -iA"
+ search: "nix-env -qaP"
diff --git a/linux/etc/X11/xorg.conf.d/70-synaptics.conf b/linux/etc/X11/xorg.conf.d/70-synaptics.conf
new file mode 100644
index 0000000..e073d94
--- /dev/null
+++ b/linux/etc/X11/xorg.conf.d/70-synaptics.conf
@@ -0,0 +1,15 @@
+Section "InputClass"
+ Identifier "touchpad"
+ Driver "synaptics"
+ MatchIsTouchpad "on"
+ Option "FingerHigh" "5"
+ Option "FingerLow" "5"
+ Option "TapButton1" "1"
+ Option "TapButton2" "3"
+ Option "TapButton3" "2"
+ Option "HorizTwoFingerScroll" "on"
+ Option "VertTwoFingerScroll" "on"
+ Option "PalmDetect" "1"
+ Option "PalmMinWidth" "8"
+ Option "PalmMinZ" "100"
+EndSection
diff --git a/linux/etc/issue b/linux/etc/issue
new file mode 100644
index 0000000..c165efa
--- /dev/null
+++ b/linux/etc/issue
@@ -0,0 +1,14 @@
+,---,---,---,---,---,---,---,---,---,---,---,---,---,-------,
+| ~ | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 | - | = | <- |
+|---'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-----|
+| ->| | Q | W | E | R | T | Y | U | I | O | P | [ | ] | \\ | TTY: \l
+|-----',--',--',--',--',--',--',--',--',--',--',--',--'-----| Host: \n
+| Caps | A | S | D | F | G | H | J | K | L | ; | ' | Enter | Arch: \m
+|------'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'--------| Kernel: \r
+| shift | Z | X | C | V | B | N | M | , | . | / | shift | Build: \v
+|---------'---'---'---'---'---'---'---'---'---'---'---------|
+| ctrl | + | alt | | alt | Fn | ctrl |
+'------'---'-----'------------------------'-----'----'------'
+
+
+
diff --git a/linux/etc/lightdm/lightdm-gtk-greeter.conf b/linux/etc/lightdm/lightdm-gtk-greeter.conf
new file mode 100644
index 0000000..9e50dd3
--- /dev/null
+++ b/linux/etc/lightdm/lightdm-gtk-greeter.conf
@@ -0,0 +1,17 @@
+[greeter]
+theme-name = WhiteSur-Dark
+icon-theme-name = WhiteSur-Dark
+font-name = SF Pro Regular 9
+cursor-theme-name=WhiteSur
+cursor-theme-size=<%= @cursor_size %>
+xft-dpi=<%= @dpi %>
+xft-antialias=true
+xft-hintstyle=hintslight
+xft-rgba=rgb
+indicators=~clock;~spacer;~layout;~language;~session;~ally;~power
+show-clock=true
+#clock-format=
+#default-user-image = /home/srdusr/.face
+hide-user-image = false
+user-background = true
+
diff --git a/linux/etc/lightdm/lightdm-webkit2-greeter.conf b/linux/etc/lightdm/lightdm-webkit2-greeter.conf
new file mode 100644
index 0000000..57785a8
--- /dev/null
+++ b/linux/etc/lightdm/lightdm-webkit2-greeter.conf
@@ -0,0 +1,49 @@
+#
+# [greeter]
+# debug_mode = Greeter theme debug mode.
+# detect_theme_errors = Provide an option to load a fallback theme when theme errors are detected.
+# screensaver_timeout = Blank the screen after this many seconds of inactivity.
+# secure_mode = Don't allow themes to make remote http requests.
+# time_format = A moment.js format string so the greeter can generate localized time for display.
+# time_language = Language to use when displaying the time or "auto" to use the system's language.
+# webkit_theme = Webkit theme to use.
+#
+# NOTE: See moment.js documentation for format string options: http://momentjs.com/docs/#/displaying/format/
+#
+
+[greeter]
+debug_mode = true
+detect_theme_errors = true
+screensaver_timeout = 300
+secure_mode = true
+time_format = LT
+time_language = auto
+webkit_theme = glorious
+#theme-name = WhiteSur-Dark
+#icon-theme-name = WhiteSur-Dark
+#font-name = SF Pro Regular 9
+#cursor-theme-name=WhiteSur
+#cursor-theme-size=<%= @cursor_size %>
+#xft-dpi=<%= @dpi %>
+#xft-antialias=true
+#xft-hintstyle=hintslight
+#xft-rgba=rgb
+#indicators=~clock;~spacer;~layout;~language;~session;~ally;~power
+#show-clock=true
+#clock-format=
+#default-user-image = /home/srdusr/.face
+hide-user-image = false
+user-background = true
+#
+# [branding]
+# background_images = Path to directory that contains background images for use by themes.
+# logo = Path to logo image for use by greeter themes.
+# user_image = Default user image/avatar. This is used by themes for users that have no .face image.
+#
+# NOTE: Paths must be accessible to the lightdm system user account (so they cannot be anywhere in /home)
+#
+
+#[branding]
+#background_images = /usr/share/backgrounds
+#logo = /usr/share/pixmaps/archlinux-logo.svg
+#user_image = /usr/share/pixmaps/archlinux-user.svg
diff --git a/linux/etc/lightdm/lightdm.conf b/linux/etc/lightdm/lightdm.conf
new file mode 100644
index 0000000..f751866
--- /dev/null
+++ b/linux/etc/lightdm/lightdm.conf
@@ -0,0 +1,161 @@
+#
+# General configuration
+#
+# start-default-seat = True to always start one seat if none are defined in the configuration
+# greeter-user = User to run greeter as
+# minimum-display-number = Minimum display number to use for X servers
+# minimum-vt = First VT to run displays on
+# lock-memory = True to prevent memory from being paged to disk
+# user-authority-in-system-dir = True if session authority should be in the system location
+# guest-account-script = Script to be run to setup guest account
+# logind-check-graphical = True to on start seats that are marked as graphical by logind
+# log-directory = Directory to log information to
+# run-directory = Directory to put running state in
+# cache-directory = Directory to cache to
+# sessions-directory = Directory to find sessions
+# remote-sessions-directory = Directory to find remote sessions
+# greeters-directory = Directory to find greeters
+# backup-logs = True to move add a .old suffix to old log files when opening new ones
+# dbus-service = True if LightDM provides a D-Bus service to control it
+#
+[LightDM]
+#start-default-seat=true
+#greeter-user=lightdm
+#minimum-display-number=0
+#minimum-vt=7 # Setting this to a value < 7 implies security issues, see FS#46799
+#lock-memory=true
+#user-authority-in-system-dir=false
+#guest-account-script=guest-account
+#logind-check-graphical=false
+#log-directory=/var/log/lightdm
+run-directory=/run/lightdm
+#cache-directory=/var/cache/lightdm
+#sessions-directory=/usr/share/lightdm/sessions:/usr/share/xsessions:/usr/share/wayland-sessions
+#remote-sessions-directory=/usr/share/lightdm/remote-sessions
+#greeters-directory=$XDG_DATA_DIRS/lightdm/greeters:$XDG_DATA_DIRS/xgreeters
+#backup-logs=true
+#dbus-service=true
+
+#
+# Seat configuration
+#
+# Seat configuration is matched against the seat name glob in the section, for example:
+# [Seat:*] matches all seats and is applied first.
+# [Seat:seat0] matches the seat named "seat0".
+# [Seat:seat-thin-client*] matches all seats that have names that start with "seat-thin-client".
+#
+# type = Seat type (local, xremote)
+# pam-service = PAM service to use for login
+# pam-autologin-service = PAM service to use for autologin
+# pam-greeter-service = PAM service to use for greeters
+# xserver-command = X server command to run (can also contain arguments e.g. X -special-option)
+# xmir-command = Xmir server command to run (can also contain arguments e.g. Xmir -special-option)
+# xserver-config = Config file to pass to X server
+# xserver-layout = Layout to pass to X server
+# xserver-allow-tcp = True if TCP/IP connections are allowed to this X server
+# xserver-share = True if the X server is shared for both greeter and session
+# xserver-hostname = Hostname of X server (only for type=xremote)
+# xserver-display-number = Display number of X server (only for type=xremote)
+# xdmcp-manager = XDMCP manager to connect to (implies xserver-allow-tcp=true)
+# xdmcp-port = XDMCP UDP/IP port to communicate on
+# xdmcp-key = Authentication key to use for XDM-AUTHENTICATION-1 (stored in keys.conf)
+# greeter-session = Session to load for greeter
+# greeter-hide-users = True to hide the user list
+# greeter-allow-guest = True if the greeter should show a guest login option
+# greeter-show-manual-login = True if the greeter should offer a manual login option
+# greeter-show-remote-login = True if the greeter should offer a remote login option
+# user-session = Session to load for users
+# allow-user-switching = True if allowed to switch users
+# allow-guest = True if guest login is allowed
+# guest-session = Session to load for guests (overrides user-session)
+# session-wrapper = Wrapper script to run session with
+# greeter-wrapper = Wrapper script to run greeter with
+# guest-wrapper = Wrapper script to run guest sessions with
+# display-stopped-script = Script to run after stopping the display server (runs as root)
+# session-setup-script = Script to run when starting a user session (runs as root)
+# session-cleanup-script = Script to run when quitting a user session (runs as root)
+# autologin-guest = True to log in as guest by default
+# autologin-user = User to log in with by default (overrides autologin-guest)
+# autologin-user-timeout = Number of seconds to wait before loading default user
+# autologin-session = Session to load for automatic login (overrides user-session)
+# autologin-in-background = True if autologin session should not be immediately activated
+# exit-on-failure = True if the daemon should exit if this seat fails
+#
+[Seat:*]
+#type=local
+#pam-service=lightdm
+#pam-autologin-service=lightdm-autologin
+#pam-greeter-service=lightdm-greeter
+#xserver-command=X
+#xmir-command=Xmir
+#xserver-config=
+#xserver-layout=
+#xserver-allow-tcp=false
+#xserver-share=true
+#xserver-hostname=
+#xserver-display-number=
+#xdmcp-manager=
+#xdmcp-port=177
+#xdmcp-key=
+greeter-session=lightdm-webkit2-greeter
+greeter-hide-users=false
+#greeter-allow-guest=true
+#greeter-show-manual-login=false
+#greeter-show-remote-login=true
+#user-session=bspwm.desktop
+#allow-user-switching=true
+#allow-guest=true
+#guest-session=
+session-wrapper=/etc/lightdm/Xsession
+#greeter-wrapper=
+#guest-wrapper=
+#display-stopped-script=
+#session-setup-script=
+#session-cleanup-script=
+#autologin-guest=false
+#autologin-user=
+#autologin-user-timeout=0
+#autologin-in-background=false
+#autologin-session=
+#exit-on-failure=false
+
+#
+# XDMCP Server configuration
+#
+# enabled = True if XDMCP connections should be allowed
+# port = UDP/IP port to listen for connections on
+# listen-address = Host/address to listen for XDMCP connections (use all addresses if not present)
+# key = Authentication key to use for XDM-AUTHENTICATION-1 or blank to not use authentication (stored in keys.conf)
+# hostname = Hostname to report to XDMCP clients (defaults to system hostname if unset)
+#
+# The authentication key is a 56 bit DES key specified in hex as 0xnnnnnnnnnnnnnn. Alternatively
+# it can be a word and the first 7 characters are used as the key.
+#
+[XDMCPServer]
+#enabled=false
+#port=177
+#listen-address=
+#key=
+#hostname=
+
+#
+# VNC Server configuration
+#
+# enabled = True if VNC connections should be allowed
+# command = Command to run Xvnc server with
+# port = TCP/IP port to listen for connections on
+# listen-address = Host/address to listen for VNC connections (use all addresses if not present)
+# width = Width of display to use
+# height = Height of display to use
+# depth = Color depth of display to use
+#
+[VNCServer]
+#enabled=false
+#command=Xvnc
+#port=5900
+#listen-address=
+#width=1024
+#height=768
+#depth=8
+[Seat:seat*]
+greeter-setup-script=xhost +LOCAL:
diff --git a/linux/etc/udev/rules.d/99-reload-monitor.rules b/linux/etc/udev/rules.d/99-reload-monitor.rules
new file mode 100644
index 0000000..948aba5
--- /dev/null
+++ b/linux/etc/udev/rules.d/99-reload-monitor.rules
@@ -0,0 +1 @@
+ACTION=="change", SUBSYSTEM=="drm", RUN+="/bin/su srdusr --command='systemctl --user start bspwm-reload.service'"
diff --git a/linux/home/.config/Code/User/keybindings.json b/linux/home/.config/Code/User/keybindings.json
new file mode 100644
index 0000000..9d04af3
--- /dev/null
+++ b/linux/home/.config/Code/User/keybindings.json
@@ -0,0 +1,274 @@
+[
+ {
+ "key": "ctrl+shift+r",
+ "command": "workbench.action.reloadWindow",
+ "when": "editorTextFocus"
+ },
+ {
+ "key": "ctrl+i",
+ "command": "workbench.action.terminal.sendSequence",
+ "when": "resourceExtname == .py",
+ "args": {
+ "text": "python \"${file}\" < ./i\n"
+ }
+ },
+ {
+ "key": "f5",
+ "command": "python.execInTerminal",
+ "when": "resourceExtname == .py",
+ },
+ {
+ "key": "f5",
+ "command": "workbench.action.terminal.sendSequence",
+ "when": "resourceExtname == .dart",
+ "args": {
+ "text": "dart \"${file}\"\n"
+ }
+ },
+ {
+ "key": "f5",
+ "command": "workbench.action.terminal.sendSequence",
+ "when": "resourceExtname == .js",
+ "args": {
+ "text": "node \"${file}\"\n"
+ }
+ },
+ {
+ "key": "f5",
+ "command": "workbench.action.terminal.sendSequence",
+ "when": "resourceExtname == .java",
+ "args": {
+ "text": "java \"${file}\"\n"
+ }
+ },
+ {
+ "key": "f5",
+ "command": "workbench.action.terminal.sendSequence",
+ "when": "resourceExtname == .sh",
+ "args": {
+ "text": "\"${file}\"\n"
+ }
+ },
+ {
+ "key": "f5",
+ "command": "workbench.action.terminal.sendSequence",
+ "when": "resourceExtname == .cpp",
+ "args": {
+ "text": "g++ -g \"${file}\" -o \"${fileDirname}/${fileBasenameNoExtension}\" && \"${fileDirname}/${fileBasenameNoExtension}\"\n"
+ }
+ },
+ {
+ "key": "f5",
+ "command": "workbench.action.terminal.sendSequence",
+ "when": "resourceExtname == .c",
+ "args": {
+ "text": "gcc -g \"${file}\" -o \"${fileDirname}/${fileBasenameNoExtension}\" && \"${fileDirname}/${fileBasenameNoExtension}\"\n"
+ }
+ },
+ {
+ "key": "f5",
+ "command": "workbench.action.terminal.sendSequence",
+ "when": "resourceExtname == .asm",
+ "args": {
+ "text": "nasm -f elf64 \"${file}\" && ld -o \"${fileDirname}/${fileBasenameNoExtension}\" \"${fileDirname}/${fileBasenameNoExtension}.o\" && \"${fileDirname}/${fileBasenameNoExtension}\"\n"
+ }
+ },
+ {
+ "key": "ctrl+i",
+ "command": "workbench.action.terminal.sendSequence",
+ "when": "resourceExtname == .cpp",
+ "args": {
+ "text": "g++ -g \"${file}\" -o \"${fileDirname}/${fileBasenameNoExtension}\" && \"${fileDirname}/${fileBasenameNoExtension}\" < ./i\n"
+ }
+ },
+ {
+ "key": "ctrl+g",
+ "command": "workbench.action.terminal.sendSequence",
+ "when": "resourceExtname == .cpp",
+ "args": {
+ "text": "g++ -g \"${file}\" -o \"${fileDirname}/${fileBasenameNoExtension}\" -lgraph -lGL -lGLU -lglut && \"${fileDirname}/${fileBasenameNoExtension}\" 2>/dev/null\n"
+ }
+ },
+ // {
+ // "key": "f5",
+ // "command": "workbench.action.tasks.runTask",
+ // "when": "resourceExtname == .cpp",
+ // "args": "Build and Run C++"
+ // },
+ {
+ "key": "shift+f5",
+ "command": "workbench.action.terminal.sendSequence",
+ "when": "resourceExtname == .cpp",
+ "args": {
+ "text": "g++ -g \"${fileDirname}/*.cpp\" -o \"${fileDirname}/${fileBasenameNoExtension}\" && \"${fileDirname}/${fileBasenameNoExtension}\"\n"
+ }
+ },
+ // {
+ // "key": "shift+f5",
+ // "command": "workbench.action.tasks.runTask",
+ // "when": "resourceExtname == .cpp",
+ // "args": "Build and Run C++ (Multiple cpp files)"
+ // },
+ {
+ "key": "f4",
+ "command": "workbench.action.debug.continue",
+ "when": "inDebugMode"
+ },
+ {
+ "key": "f4",
+ "command": "workbench.action.debug.start",
+ "when": "!inDebugMode"
+ },
+ {
+ "key": "f4",
+ "command": "workbench.action.debug.start",
+ "when": "debuggersAvailable && !inDebugMode"
+ },
+ {
+ "key": "f5",
+ "command": "-workbench.action.debug.continue",
+ "when": "inDebugMode"
+ },
+ {
+ "key": "f5",
+ "command": "-workbench.action.debug.start",
+ "when": "!inDebugMode"
+ },
+ {
+ "key": "f5",
+ "command": "-workbench.action.debug.start",
+ "when": "debuggersAvailable && !inDebugMode"
+ },
+ {
+ "key": "shift+f4",
+ "command": "workbench.action.debug.stop",
+ "when": "inDebugMode"
+ },
+ {
+ "key": "shift+f5",
+ "command": "-workbench.action.debug.stop",
+ "when": "inDebugMode"
+ },
+ {
+ "key": "ctrl+v",
+ "command": "workbench.action.terminal.paste",
+ "when": "terminalFocus && terminalProcessSupported"
+ },
+ {
+ "key": "ctrl+shift+v",
+ "command": "-workbench.action.terminal.paste",
+ "when": "terminalFocus && terminalProcessSupported"
+ },
+ {
+ "key": "ctrl+alt+up",
+ "command": "editor.action.copyLinesUpAction",
+ "when": "editorTextFocus && !editorReadonly"
+ },
+ {
+ "key": "ctrl+alt+down",
+ "command": "editor.action.copyLinesDownAction",
+ "when": "editorTextFocus && !editorReadonly"
+ },
+ {
+ "key": "ctrl+shift+alt+up",
+ "command": "-editor.action.copyLinesUpAction",
+ "when": "editorTextFocus && !editorReadonly"
+ },
+ {
+ "key": "ctrl+shift+alt+down",
+ "command": "-editor.action.copyLinesDownAction",
+ "when": "editorTextFocus && !editorReadonly"
+ },
+ {
+ "key": "ctrl+,",
+ "command": "-workbench.action.openSettings"
+ },
+ {
+ "key": "ctrl+,",
+ "command": "workbench.action.openSettingsJson"
+ },
+ {
+ "key": "ctrl+i",
+ "command": "-editor.action.triggerSuggest",
+ "when": "editorHasCompletionItemProvider && textInputFocus && !editorReadonly"
+ },
+ {
+ "key": "ctrl+c",
+ "command": "workbench.action.terminal.copySelection",
+ "when": "terminalFocus && terminalProcessSupported && terminalTextSelected && terminalTextSelected"
+ },
+ {
+ "key": "ctrl+shift+c",
+ "command": "-workbench.action.terminal.copySelection",
+ "when": "terminalFocus && terminalProcessSupported && terminalTextSelected && terminalTextSelected"
+ },
+ {
+ "key": "ctrl+k",
+ "command": "workbench.action.terminal.kill",
+ "when": "terminalIsOpen && terminalFocus"
+ },
+ {
+ "key": "delete",
+ "command": "-workbench.action.terminal.killInstance",
+ "when": "terminalIsOpen && terminalTabsFocus || terminalProcessSupported && terminalTabsFocus"
+ },
+ {
+ "key": "ctrl+shift+i",
+ "command": "-editor.action.formatDocument.none",
+ "when": "editorTextFocus && !editorHasDocumentFormattingProvider && !editorReadonly"
+ },
+ {
+ "key": "ctrl+shift+i",
+ "command": "-notebook.formatCell",
+ "when": "editorHasDocumentFormattingProvider && editorTextFocus && inCompositeEditor && notebookEditable && !editorReadonly && activeEditor == 'workbench.editor.notebook'"
+ },
+ {
+ "key": "ctrl+shift+i",
+ "command": "editor.action.formatDocument",
+ "when": "editorTextFocus"
+ },
+ {
+ "key": "ctrl+shift+i",
+ "command": "-editor.action.formatDocument",
+ "when": "editorHasDocumentFormattingProvider && editorTextFocus && !editorReadonly && !inCompositeEditor"
+ },
+ {
+ "key": "ctrl+shift+i",
+ "command": "workbench.action.toggleDevTools",
+ "when": "!editorTextFocus"
+ },
+ {
+ "key": "ctrl+shift+i",
+ "command": "-workbench.action.toggleDevTools",
+ "when": "isDevelopment"
+ },
+ {
+ "key": "ctrl+t",
+ "command": "-workbench.action.showAllSymbols"
+ },
+ {
+ "key": "ctrl+t",
+ "command": "workbench.action.files.newUntitledFile"
+ },
+ {
+ "key": "ctrl+n",
+ "command": "-workbench.action.files.newUntitledFile"
+ },
+ {
+ "key": "ctrl+shift+m",
+ "command": "github.cweijan.mysql.focus"
+ },
+ {
+ "key": "ctrl+shift+m",
+ "command": "-workbench.actions.view.problems",
+ "when": "workbench.panel.markers.view.active"
+ },
+ {
+ "key": "ctrl+shift+0",
+ "command": "workbench.action.zoomReset"
+ },
+ {
+ "key": "ctrl+numpad0",
+ "command": "-workbench.action.zoomReset"
+ }
+]
diff --git a/linux/home/.config/Code/User/settings.json b/linux/home/.config/Code/User/settings.json
new file mode 100644
index 0000000..8686b1b
--- /dev/null
+++ b/linux/home/.config/Code/User/settings.json
@@ -0,0 +1,734 @@
+{
+ "workbench.colorCustomizations": {
+ "[Chromodynamics]": {
+ "terminal.background": "#060606",
+ "terminal.foreground": "#e0e0e0",
+ "terminal.selectionBackground": "#555555",
+ "titleBar.activeBackground": "#020202",
+ "titleBar.activeForeground": "#e0e0e0",
+ "titleBar.inactiveBackground": "#020202",
+ "titleBar.inactiveForeground": "#e0e0e0",
+ "tab.inactiveBackground": "#0b0b0b",
+ "editor.background": "#060606",
+ "editor.findMatchBorder": "#0c0c0d",
+ "editor.findMatchHighlightBorder": "#0c0c0d",
+ "editor.lineHighlightBackground": "#060606",
+ "editor.selectionBackground": "#222222",
+ "editor.selectionHighlightBackground": "#e4dddd",
+ "editor.selectionHighlightBorder": "#0c0c0d",
+ "editorGroupHeader.tabsBackground": "#0b0b0b",
+ "editorGutter.background": "#000000",
+ "sideBar.background": "#060606",
+ "sideBarSectionHeader.background": "#0b0b0b",
+ "statusBar.background": "#060606"
+ },
+ "[Sweet Dracula]": {
+ "editorGroupHeader.tabsBackground": "#161925",
+ }
+ },
+ "editor.tokenColorCustomizations": {
+ "[Chromodynamics]": {
+ "textMateRules": [
+ {
+ "scope": [
+ "constant.language.python",
+ "variable.parameter.function.language.special.self.python"
+ ],
+ "settings": {
+ "foreground": "#66D9EF"
+ }
+ },
+ {
+ "scope": [
+ "entity.name.function.operator",
+ "keyword.operator"
+ ],
+ "settings": {
+ "foreground": "#E8364F",
+ "fontStyle": ""
+ }
+ },
+ {
+ "scope": [
+ "punctuation.definition.template-expression.begin.js",
+ "punctuation.definition.template-expression.end.js"
+ ],
+ "settings": {
+ "foreground": "#da70d6",
+ "fontStyle": "italic"
+ }
+ },
+ {
+ "scope": [
+ "variable.other.readwrite.js"
+ ],
+ "settings": {
+ "foreground": "#c6c6c6",
+ "fontStyle": ""
+ }
+ }
+ ]
+ },
+ "textMateRules": [
+ {
+ "scope": [
+ "entity.name.type.class",
+ "storage.type.class",
+ "storage.modifier",
+ "storage.type",
+ "constant",
+ "comment",
+ "keyword",
+ "invalid"
+ ],
+ "settings": {
+ "fontStyle": "italic"
+ }
+ }
+ ]
+ },
+ // Window Settings
+ "window.commandCenter": false,
+ "window.menuBarVisibility": "toggle",
+ "window.newWindowDimensions": "maximized",
+ "window.restoreWindows": "all",
+ "window.title": "${activeEditorShort}${separator}${rootName}",
+ "window.titleBarStyle": "native",
+ "window.titleSeparator": " ・ ・",
+ "workbench.editor.enablePreviewFromQuickOpen": false,
+ "workbench.editor.highlightModifiedTabs": true,
+ "workbench.editor.restoreViewState": false,
+ "workbench.editor.sharedViewState": false,
+ "workbench.editor.showTabs": "multiple",
+ "workbench.editor.tabCloseButton": "right",
+ "workbench.editor.tabSizing": "shrink",
+ "workbench.iconTheme": "sweet-vscode-icons",
+ "workbench.layoutControl.type": "menu",
+ "workbench.list.smoothScrolling": true,
+ "workbench.panel.defaultLocation": "right",
+ "workbench.startupEditor": "none",
+ "workbench.statusBar.visible": true,
+ "workbench.editorAssociations": {
+ "*.ipynb": "jupyter-notebook"
+ },
+ "workbench.reduceMotion": "off",
+ // Editor Settings
+ "editor.acceptSuggestionOnEnter": "smart",
+ "editor.autoClosingBrackets": "always",
+ "editor.bracketPairColorization.enabled": true,
+ "editor.codeActionsOnSave": {
+ "source.organizeImports": "explicit"
+ },
+ "editor.colorDecorators": false,
+ "editor.cursorBlinking": "phase",
+ "editor.cursorSmoothCaretAnimation": "on",
+ "editor.cursorStyle": "line-thin",
+ "editor.detectIndentation": false,
+ "editor.emptySelectionClipboard": false,
+ "editor.fontFamily": "'Operator Mono SSm Lig Book', 'FiraCode Nerd Font', 'Hack Nerd Font Mono', 'codicon'",
+ "editor.fontVariations": true,
+ "editor.fontLigatures": "'ss01', 'ss02', 'ss03', 'ss04', 'ss05', 'ss06', 'zero', 'onum'",
+ "editor.fontSize": 12,
+ "editor.fontWeight": "normal",
+ "editor.formatOnSave": true,
+ "editor.formatOnType": true,
+ "editor.glyphMargin": true,
+ "editor.guides.bracketPairs": "active",
+ "editor.guides.bracketPairsHorizontal": "active",
+ "editor.guides.highlightActiveBracketPair": true,
+ "editor.hover.above": false,
+ "editor.inlayHints.enabled": "on",
+ "editor.inlineSuggest.enabled": true,
+ "editor.insertSpaces": true,
+ "editor.linkedEditing": true,
+ "editor.matchBrackets": "never",
+ "editor.maxTokenizationLineLength": 99999999,
+ "editor.minimap.enabled": false,
+ "editor.mouseWheelScrollSensitivity": 1.5,
+ "editor.occurrencesHighlight": "off",
+ "editor.renderControlCharacters": true,
+ "editor.renderFinalNewline": "on",
+ "editor.renderLineHighlight": "gutter",
+ "editor.renderWhitespace": "none",
+ "editor.scrollbar.horizontal": "auto",
+ "editor.scrollbar.vertical": "auto",
+ "editor.selectionHighlight": true,
+ "editor.smoothScrolling": true,
+ "editor.stickyScroll.enabled": true,
+ "editor.stickyScroll.maxLineCount": 5,
+ "editor.stickyTabStops": false,
+ "editor.suggestSelection": "first",
+ "editor.suggest.insertMode": "replace",
+ "editor.suggest.preview": true,
+ "editor.suggest.snippetsPreventQuickSuggestions": false,
+ "editor.tabCompletion": "on",
+ "editor.tabSize": 4,
+ "editor.wordBasedSuggestions": "matchingDocuments",
+ "editor.wordBasedSuggestionsMode": "matchingDocuments",
+ "editor.wordWrap": "on",
+ "editor.quickSuggestions": {
+ "other": "on",
+ "comments": "on",
+ "strings": "on",
+ },
+ // Emmet Settings
+ "emmet.excludeLanguages": [],
+ "emmet.includeLanguages": {
+ "javascript": "javascriptreact",
+ "markdown": "html",
+ "vue-html": "html"
+ },
+ // Explorer Settings
+ "explorer.compactFolders": false,
+ "explorer.confirmDelete": false,
+ "explorer.confirmDragAndDrop": false,
+ // Customize UI
+ // "customizeUI.activityBar": "bottom",
+ // "customizeUI.activityBarHideSettings": true,
+ // "customizeUI.listRowHeight": 20,
+ // "customizeUI.font.monospace": "CaskaydiaCove Nerd Font",
+ // "customizeUI.font.regular": "CaskaydiaCove Nerd Font",
+ // "customizeUI.fontSizeMap": {
+ // "13px": "12px",
+ // "monospace": "12px",
+ // "tab-title": "12px",
+ // "window-title": "12.5px"
+ // },
+ // "customizeUI.titleBar": "inline",
+ // "customizeUI.stylesheet": {
+ // ":root,::after,::before": "--tab-height: 25px;--tab-radius: 8px;--ui-radius: 6px;--base-color: #bd93f9;--gradient-one: #c50ed2;--gradient-two: #8500f7;--side-pane-color: var(--vscode-editor-background);--focus-border: #3f4f81;--sash-size: 2px !important;--sash-hover-size: 2px !important;",
+ // ".editor-container": "-webkit-font-smoothing: antialiased;",
+ // ".monaco-workbench .part.editor > .content > .watermark": "display: none !important;",
+ // ".monaco-workbench .part.titlebar .window-controls-container .layout-dropdown-container": "margin-left: auto !important;",
+ // ".monaco-workbench .part.titlebar > .window-controls-container": "width: unset !important;",
+ // ".monaco-workbench .part.titlebar > .window-controls-container > .window-icon": "width: 30px !important;",
+ // ".monaco-workbench .part.titlebar > .titlebar-container .window-appicon > .home-bar-icon-badge, .monaco-workbench .part.titlebar > .titlebar-container > .window-appicon:not(.codicon)": "background-image: url(./vsc/vsc.svg) !important;",
+ // ".monaco-workbench .part.editor > .content .editor-group-container.empty .editor-group-letterpress": "background-image: url(./vsc/vsc-back.svg) !important;",
+ // ".monaco-workbench .part.editor.has-watermark > .content.empty .editor-group-container > .editor-group-letterpress": "background-position-y: 50% !important;",
+ // ".monaco-sash.hover:before, .monaco-sash.active:before": "background: linear-gradient(to bottom, var(--gradient-one), var(--gradient-two)) !important;",
+ // ".monaco-editor .squiggly-error": "background: none !important;border-bottom: 0.5px solid var(--vscode-editorError-foreground) !important;",
+ // ".monaco-editor .squiggly-warning": "background: none !important;border-bottom: 0.5px solid var(--vscode-editorWarning-foreground) !important;",
+ // ".monaco-editor .squiggly-info": "background: none !important;border-bottom: 0.5px solid var(--vscode-editorInfo-foreground) !important;",
+ // ".monaco-editor .squiggly-hint": "background: none !important;border-bottom: 0.5px solid var(--vscode-editorHint-foreground) !important;",
+ // "body.activity-bar-at-bottom .monaco-workbench .activitybar .active-item-indicator": "display: block !important;",
+ // ".monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator:before": "border: 0 !important;width: 100% !important;height: 3px !important;position: absolute !important;bottom: 0 !important;left: 0 !important;top: unset !important;background-image: linear-gradient(to top, var(--gradient-one), var(--gradient-two)) !important;border-radius: 100vmax !important;",
+ // ".monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab > .tab-border-bottom-container, .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab > .tab-border-top-container": "background-image: linear-gradient(to left, var(--gradient-one), var(--gradient-two)) !important;height: 3px !important;",
+ // ".monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .badge .badge-content": "background-image: linear-gradient(90deg, var(--gradient-one), var(--gradient-two)) !important;",
+ // ".monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.dirty > .tab-actions .action-label:not(:hover):before, .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty > .tab-actions .action-label:not(:hover):before": "content: '' !important;",
+ // ".codicon-git-branch:before": "background: linear-gradient(to bottom, var(--gradient-one), var(--gradient-two)) !important;-webkit-background-clip: text !important;background-clip: text !important;color: rgba(255, 255, 255, 0.4) !important;",
+ // ".monaco-editor, .split-view-view .monaco-editor-background": "background: url(./vsc/pek.png) bottom right / 10% auto no-repeat scroll;",
+ // ".monaco-scrollable-element > .scrollbar > .slider": "border-radius: 7px !important;",
+ // ".monaco-scrollable-element > .scrollbar > .slider.active": "background: linear-gradient(to top, var(--gradient-one), var(--gradient-two)) !important;",
+ // "canvas.decorationsOverviewRuler, .monaco-scrollable-element > .scrollbar.vertical, .monaco-scrollable-element > .scrollbar.vertical > .slider": "width: 7px !important;",
+ // ".monaco-scrollable-element > .scrollbar.horizontal, .monaco-scrollable-element > .scrollbar.horizontal > .slider": "height: 7px !important;",
+ // ".monaco-scrollable-element > .scrollbar.horizontal > .slider": "width: 135px !important;",
+ // ".monaco-editor .monaco-hover, .monaco-editor .suggest-widget, .monaco-editor .suggest-details": "border-radius: var(--ui-radius) !important;overflow: hidden !important;box-shadow: rgb(0 0 0 / 36%) 0px 2px calc(var(--ui-radius) + 2px) !important;",
+ // ".pane .pane-body, .pane .pane-body .monaco-list, .pane .pane-body .monaco-list .monaco-scrollable-element, .pane .pane-body .monaco-list .monaco-scrollable-element .monaco-list-rows, .pane .pane-body .monaco-list .monaco-scrollable-element .monaco-list-rows .monaco-list-row": "overflow: visible !important;",
+ // ".open-editors .monaco-list .monaco-list-row": "padding-left: 0px !important;",
+ // ".monaco-list-row:hover, .monaco-list-row.selected, .monaco-list-row.focused": "border-radius: 0 var(--ui-radius) var(--ui-radius) 0 !important;",
+ // ".monaco-workbench .monaco-list:not(.element-focused):focus:before, .monaco-select-box, .monaco-select-box-dropdown-container, .monaco-select-box-dropdown-container .monaco-list-row:hover, .monaco-select-box-dropdown-container .monaco-list-row.selected, .monaco-select-box-dropdown-container .monaco-list-row.focused": "border-radius: var(--ui-radius) !important;",
+ // ".monaco-list-row.selected::before, .monaco-list-row.selected::after": "--ui-radius: 6px;content: '' !important;display: block !important;position: absolute !important;width: var(--ui-radius) !important;height: var(--ui-radius) !important;left: 0 !important;pointer-events: none !important;",
+ // ".monaco-list-row.selected::before": "top: calc(0px - var(--ui-radius)) !important;background: radial-gradient(circle at 100% 0%, transparent 70.71%, var(--side-pane-color) 29.289%) no-repeat;background-position: 100% 0%;",
+ // ".monaco-list-row.selected::after": "bottom: calc(0px - var(--ui-radius)) !important;background: radial-gradient(circle at 100% 100%, transparent 70.71%, var(--side-pane-color) 29.289%) no-repeat;background-position: 100% 100%;",
+ // ".monaco-button.monaco-text-button": "border-radius: var(--ui-radius) !important;",
+ // ".monaco-editor .cursors-layer.cursor-smooth-caret-animation > .cursor": "transition: all 100ms ease-out;",
+ // ".tab:first-child": "margin-left: var(--tab-radius) !important;",
+ // ".tab:last-child": "margin-right: var(--tab-radius) !important;",
+ // ".tab.active": "border-radius: var(--tab-radius) var(--tab-radius) 0 0 !important;",
+ // ".tab.dirty-border-top > .tab-border-top-container": "border-radius: var(--tab-radius) var(--tab-radius) 0 0 !important;",
+ // ".tab.active::before, .tab.active::after": "content: '' !important;display: block !important;box-sizing: border-box !important;position: absolute !important;z-index: 1;width: var(--tab-radius) !important;height: var(--tab-radius) !important;bottom: 0px !important;pointer-events: none !important;",
+ // ".tab.active::before": "left: calc(0px - var(--tab-radius)) !important;border-bottom-right-radius: var(--tab-radius) !important;box-shadow: 3px 3px 0 3px var(--vscode-editor-background) !important;",
+ // ".tab.active::after": "right: 0 !important;transform: translateX(var(--tab-radius)) !important;border-bottom-left-radius: var(--tab-radius) !important;box-shadow: -3px 3px 0 3px var(--vscode-editor-background) !important;",
+ // ".monaco-workbench .part.sidebar>.title>.title-label h2": "font-weight: bold !important;",
+ // "body.activity-bar-at-bottom div.monaco-grid-view > div > div > div.monaco-scrollable-element > div.split-view-container > div.split-view-view.visible > div > div > div.monaco-scrollable-element > div.split-view-container > div:nth-child(1) > div > div > div.monaco-scrollable-element > div.split-view-container > div:nth-child(2)": "height: auto !important;",
+ // "body.activity-bar-at-bottom .monaco-workbench .part.activitybar": "border: none !important; padding: 0px 6px 6px 6px !important;margin-top: -8px !important; background-color: var(--vscode-editor-background) !important; position: relative !important; z-index: 10 !important;",
+ // "body.activity-bar-at-bottom .monaco-workbench .activitybar.bordered:before": "display: none !important;",
+ // "body.activity-bar-at-bottom .monaco-workbench .activitybar > .content": "justify-content: center !important; border-radius: 6px; width: unset !important; border: 1px solid #3f4f818c !important;",
+ // "body.activity-bar-at-bottom .monaco-workbench .activitybar>.content>.composite-bar": "margin-bottom: unset !important;",
+ // "body.activity-bar-at-bottom .monaco-action-bar .action-item.icon > .action-label": "color: var(--vscode-editor-foreground) !important;opacity: 0.4;",
+ // "body.activity-bar-at-bottom .monaco-action-bar .action-item.icon.checked > .action-label": "opacity: 1 !important;",
+ // ".monaco-workbench .part.editor .tabs-and-actions-container .window-controls-container": "display: flex;flex-grow: 0;flex-shrink: 0;text-align: center; -webkit-app-region: no-drag; height: 100%;",
+ // ".monaco-workbench .part.editor .tabs-and-actions-container .window-controls-container .window-icon": "width: 30px !important;height: 30px !important; display: flex; align-items: center; justify-content: center;",
+ // ".monaco-workbench .part.editor .tabs-and-actions-container .window-controls-container .window-icon.window-close:hover": "background-color: #FF5555",
+ // },
+ // Terminal Settings
+ "terminal.integrated.altClickMovesCursor": true,
+ "terminal.integrated.cursorBlinking": true,
+ "terminal.integrated.customGlyphs": true,
+ "terminal.integrated.cursorStyle": "line",
+ "terminal.integrated.enableMultiLinePasteWarning": false,
+ "terminal.integrated.fontFamily": "'CaskaydiaCove Nerd Font', 'FiraCode Nerd Font', 'Hack Nerd Font Mono'",
+ "terminal.integrated.fontSize": 13,
+ "terminal.integrated.gpuAcceleration": "auto",
+ "terminal.integrated.scrollback": 200000,
+ "terminal.integrated.smoothScrolling": true,
+ "terminal.integrated.tabs.enabled": false,
+ "terminal.integrated.tabs.enableAnimation": true,
+ "terminal.integrated.profiles.linux": {
+ "zsh": {
+ "path": "/usr/bin/zsh"
+ }
+ },
+ "terminal.integrated.shellIntegration.enabled": false,
+ "terminal.integrated.shellIntegration.decorationsEnabled": "never",
+ // Language Settings
+ "[python]": {
+ "editor.semanticHighlighting.enabled": false
+ },
+ "python.analysis.completeFunctionParens": true,
+ "python.formatting.provider": "black",
+ "python.formatting.blackArgs": [
+ "--line-length",
+ "120"
+ ],
+ "python.languageServer": "Pylance",
+ "python.linting.ignorePatterns": [
+ ".vscode/*.py",
+ "**/site-packages/**/*.py",
+ ".git"
+ ],
+ "python.linting.enabled": true,
+ "python.linting.pylintEnabled": true,
+ "python.linting.pylintArgs": [
+ "--rcfile",
+ "${env:HOME}/.pylintrc"
+ // "--load-plugins=pylint_django"
+ ],
+ "[cpp]": {
+ "editor.semanticHighlighting.enabled": false,
+ },
+ "C_Cpp.default.includePath": [
+ "${workspaceFolder}/**",
+ "/usr/include",
+ "/usr/local/include",
+ "/usr/include/c++/11.1.0"
+ ],
+ "C_Cpp.default.defines": [
+ "${default}"
+ ],
+ "C_Cpp.default.compilerPath": "/usr/bin/g++",
+ "C_Cpp.default.cppStandard": "c++20",
+ "C_Cpp.default.cStandard": "c17",
+ "C_Cpp.default.intelliSenseMode": "gcc-x64",
+ "C_Cpp.default.compilerArgs": [
+ "-g"
+ ],
+ "launch": {
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "(gdb) Launch",
+ "type": "cppdbg",
+ "request": "launch",
+ "program": "${fileDirname}/${fileBasenameNoExtension}",
+ "args": [],
+ "stopAtEntry": false,
+ "cwd": "${workspaceFolder}",
+ "environment": [],
+ "externalConsole": false,
+ "MIMode": "gdb",
+ "miDebuggerPath": "/usr/bin/gdb",
+ "preLaunchTask": "Build",
+ "setupCommands": [
+ {
+ "description": "Enable pretty-printing for gdb",
+ "text": "-enable-pretty-printing",
+ "ignoreFailures": true
+ }
+ ]
+ }
+ ]
+ },
+ "tasks": {
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "Build",
+ "type": "shell",
+ "command": "g++ -g \"${file}\" -o \"${fileDirname}/${fileBasenameNoExtension}\"",
+ "problemMatcher": [
+ "$gcc"
+ ],
+ "group": {
+ "kind": "build",
+ "isDefault": true
+ }
+ },
+ {
+ "label": "Build (Multiple cpp files)",
+ "type": "shell",
+ "command": "g++ -g \"${fileDirname}/*.cpp\" -o \"${fileDirname}/${fileBasenameNoExtension}\"",
+ "problemMatcher": [
+ "$gcc"
+ ],
+ "group": {
+ "kind": "build",
+ "isDefault": true
+ }
+ }
+ ]
+ },
+ "C_Cpp.autocompleteAddParentheses": true,
+ "C_Cpp.codeFolding": "enabled",
+ "C_Cpp.clang_format_fallbackStyle": "{ BasedOnStyle: Google, IndentWidth: 2, ColumnLimit: 0}",
+ "C_Cpp.inlayHints.autoDeclarationTypes.enabled": true,
+ "C_Cpp.inlayHints.autoDeclarationTypes.showOnLeft": true,
+ "C_Cpp.inlayHints.parameterNames.enabled": true,
+ "C_Cpp.inlayHints.parameterNames.hideLeadingUnderscores": true,
+ "C_Cpp.inlayHints.parameterNames.suppressWhenArgumentContainsName": true,
+ "C_Cpp.inlayHints.referenceOperator.enabled": true,
+ "C_Cpp.inlayHints.referenceOperator.showSpace": false,
+ "css.format.enable": true,
+ "css.format.newlineBetweenRules": true,
+ "css.format.newlineBetweenSelectors": true,
+ "css.format.spaceAroundSelectorSeparator": true,
+ "[css]": {
+ "editor.tabSize": 2
+ },
+ "java.jdt.ls.java.home": "/usr/lib/jvm/java-11-openjdk-amd64",
+ "java.configuration.runtimes": [
+ {
+ "name": "JavaSE-11",
+ "path": "/usr/lib/jvm/java-11-openjdk-amd64"
+ }
+ ],
+ "java.server.launchMode": "LightWeight",
+ "[html]": {
+ "editor.tabSize": 2
+ },
+ "html.autoClosingTags": true,
+ "html.completion.attributeDefaultValue": "singlequotes",
+ "[dart]": {
+ "editor.suggestSelection": "first",
+ "editor.tabCompletion": "onlySnippets",
+ "editor.wordBasedSuggestions": "off"
+ },
+ "[javascript]": {
+ "editor.tabSize": 2
+ },
+ "javascript.autoClosingTags": true,
+ "javascript.suggest.autoImports": true,
+ "javascript.suggest.completeFunctionCalls": true,
+ "javascript.suggest.enabled": true,
+ "javascript.updateImportsOnFileMove.enabled": "always",
+ "javascript.validate.enable": false,
+ "[json]": {
+ "editor.tabSize": 2
+ },
+ "json.maxItemsComputed": 100000,
+ "[markdown]": {
+ "editor.quickSuggestions": {
+ "other": "on",
+ "comments": "on",
+ "strings": "on",
+ }
+ },
+ "[scss]": {
+ "editor.tabSize": 2
+ },
+ "[typescript]": {
+ "editor.tabSize": 2
+ },
+ "typescript.autoClosingTags": true,
+ "typescript.suggest.autoImports": true,
+ "typescript.updateImportsOnFileMove.enabled": "always",
+ "typescript.validate.enable": false,
+ "[typescriptreact]": {
+ "editor.tabSize": 2,
+ "editor.formatOnSave": false,
+ },
+ "js/ts.implicitProjectConfig.checkJs": true,
+ //Latex
+ "[latex]": {
+ "editor.formatOnSave": false
+ },
+ // Empty-Indent Extension
+ "emptyIndent.highlightColor": "rgba(246,36,89,0.6)",
+ "emptyIndent.highlightIndent": false,
+ "emptyIndent.removeIndent": true,
+ // Github Co-pilot
+ "github.copilot.inlineSuggest.enable": true,
+ "github.copilot.enable": {
+ "*": true,
+ "yaml": false,
+ "plaintext": true,
+ "markdown": false
+ },
+ // Gitlens
+ "gitlens.currentLine.enabled": true,
+ "gitlens.hovers.currentLine.over": "line",
+ "gitlens.codeLens.enabled": false,
+ "gitlens.statusBar.enabled": true,
+ // General Setting\
+ "color-highlight.markRuler": false,
+ "color-highlight.markerType": "background",
+ "debug.onTaskErrors": "showErrors",
+ "debug.openDebug": "openOnDebugBreak",
+ "eslint.enable": true,
+ "extensions.ignoreRecommendations": true,
+ "files.autoSave": "onWindowChange",
+ "files.insertFinalNewline": true,
+ "files.restoreUndoStack": true,
+ "files.trimFinalNewlines": true,
+ "files.trimTrailingWhitespace": true,
+ "git.autofetch": true,
+ "grammarly.files.include": [
+ "**/*.md",
+ "**/*.txt",
+ "**/*.tex",
+ ],
+ "latex-workshop.latex.autoBuild.cleanAndRetry.enabled": true,
+ "latex-workshop.latex.autoBuild.run": "never",
+ "latex-workshop.latex.clean.fileTypes": [
+ "*/*.aux",
+ "*/*.synctex.gz"
+ ],
+ "latex-workshop.latex.outDir": "/tmp/Review-Paper/",
+ "latex-workshop.latex.recipe.default": "latexmk (lualatex)",
+ "latex-workshop.latex.recipes": [
+ {
+ "name": "latexmk 🔃",
+ "tools": [
+ "latexmk"
+ ]
+ },
+ {
+ "name": "latexmk (latexmkrc)",
+ "tools": [
+ "latexmk_rconly"
+ ]
+ },
+ {
+ "name": "latexmk (lualatex)",
+ "tools": [
+ "lualatexmk"
+ ]
+ },
+ {
+ "name": "latexmk (xelatex)",
+ "tools": [
+ "xelatexmk"
+ ]
+ },
+ {
+ "name": "pdflatex ➞ bibtex ➞ pdflatex × 2",
+ "tools": [
+ "pdflatex",
+ "bibtex",
+ "pdflatex",
+ "pdflatex"
+ ]
+ },
+ {
+ "name": "Compile Rnw files",
+ "tools": [
+ "rnw2tex",
+ "latexmk"
+ ]
+ },
+ {
+ "name": "Compile Jnw files",
+ "tools": [
+ "jnw2tex",
+ "latexmk"
+ ]
+ },
+ {
+ "name": "tectonic",
+ "tools": [
+ "tectonic"
+ ]
+ }
+ ],
+ "latex-workshop.latex.tools": [
+ {
+ "name": "latexmk",
+ "command": "latexmk",
+ "args": [
+ "-synctex=1",
+ "-interaction=nonstopmode",
+ "-file-line-error",
+ "-pdf",
+ "-outdir=%OUTDIR%",
+ "%DOC%"
+ ],
+ "env": {}
+ },
+ {
+ "name": "lualatexmk",
+ "command": "latexmk",
+ "args": [
+ "-synctex=1",
+ "-interaction=nonstopmode",
+ "-file-line-error",
+ "-lualatex",
+ "-outdir=%OUTDIR%",
+ "%DOC%"
+ ],
+ "env": {}
+ },
+ {
+ "name": "xelatexmk",
+ "command": "latexmk",
+ "args": [
+ "-synctex=1",
+ "-interaction=nonstopmode",
+ "-file-line-error",
+ "-xelatex",
+ "-outdir=%OUTDIR%",
+ "%DOC%"
+ ],
+ "env": {}
+ },
+ {
+ "name": "latexmk_rconly",
+ "command": "latexmk",
+ "args": [
+ "%DOC%"
+ ],
+ "env": {}
+ },
+ {
+ "name": "pdflatex",
+ "command": "pdflatex",
+ "args": [
+ "-synctex=1",
+ "-interaction=nonstopmode",
+ "-file-line-error",
+ "-output-directory=%OUTDIR%",
+ "%DOC%"
+ ],
+ "env": {}
+ },
+ {
+ "name": "bibtex",
+ "command": "bibtex",
+ "args": [
+ "%DOCFILE%"
+ ],
+ "env": {}
+ },
+ {
+ "name": "rnw2tex",
+ "command": "Rscript",
+ "args": [
+ "-e",
+ "knitr::opts_knit$set(concordance = TRUE); knitr::knit('%DOCFILE_EXT%')"
+ ],
+ "env": {}
+ },
+ {
+ "name": "jnw2tex",
+ "command": "julia",
+ "args": [
+ "-e",
+ "using Weave; weave(\"%DOC_EXT%\", doctype=\"tex\")"
+ ],
+ "env": {}
+ },
+ {
+ "name": "jnw2texmintex",
+ "command": "julia",
+ "args": [
+ "-e",
+ "using Weave; weave(\"%DOC_EXT%\", doctype=\"texminted\")"
+ ],
+ "env": {}
+ },
+ {
+ "name": "tectonic",
+ "command": "tectonic",
+ "args": [
+ "--synctex",
+ "--keep-logs",
+ "%DOC%.tex"
+ ],
+ "env": {}
+ }
+ ],
+ "liveServer.settings.donotShowInfoMsg": true,
+ "liveServer.settings.donotVerifyTags": true,
+ "liveServer.settings.file": "404.html",
+ // "liveServer.settings.https": {
+ // "enable": true,
+ // "cert": "/home/proxzima/.ssh/cert/cert.pem",
+ // "key": "/home/proxzima/.ssh/cert/key.pem",
+ // "passphrase": "1234"
+ // },
+ "liveServer.settings.useLocalIp": true,
+ "markdown.preview.markEditorSelection": true,
+ "markdown.preview.scrollEditorWithPreview": false,
+ "markdown.preview.scrollPreviewWithEditor": false,
+ "svgPreview.autoOpen": true,
+ "svgPreview.scaleToFit": true,
+ "svgPreview.style": {
+ "html": {
+ "background-position": "0 0, 13px 13px",
+ "background-size": "26px 26px",
+ "background": "rgba(255, 255, 255, 1)",
+ // "background-image": "linear-gradient(45deg, #141414 25%, transparent 25%, transparent 75%, #141414 75%, #141414), linear-gradient(45deg, #141414 25%, transparent 25%, transparent 75%, #141414 75%, #141414)"
+ }
+ },
+ "print.folder.exclude": [
+ "{bin,obj,out}",
+ "node_module",
+ "**/*.{bin,exe,dll,hex,pdb,pdf,pfx,png,jpg,gif,bmp,suo,pptx,ppt,jar,woff2,woff,ttf,eot,odt,otf,class}"
+ ],
+ "print.lineNumbers": "on",
+ "print.printAndClose": false,
+ "print.colourScheme": "XCode",
+ "print.alternateBrowser": true,
+ "print.browserPath": "/usr/bin/google-chrome",
+ "spellright.documentTypes": [
+ "markdown",
+ "latex",
+ "plaintext"
+ ],
+ "spellright.language": [
+ "en_GB"
+ ],
+ "spellright.notificationClass": "information",
+ "spellright.suggestionsInHints": false,
+ "spellright.useDocumentSymbolsInCode": true,
+ "telemetry.telemetryLevel": "off",
+ "search.exclude": {
+ "**/bower_components": true,
+ "**/*.code-search": true,
+ "**/node_modules": true,
+ "**/env": true,
+ "**/venv": true
+ },
+ "files.associations": {
+ "*.xml": "html",
+ "*.svg": "html",
+ "*.json": "jsonc"
+ },
+ "files.exclude": {
+ "**/.DS_Store": true,
+ "**/.git": true,
+ "**/.svn": true,
+ "**/.hg": true,
+ "**/CVS": true,
+ "**/.classpath": true,
+ "**/.project": true,
+ "**/.settings": true,
+ "**/.factorypath": true
+ },
+ "files.watcherExclude": {
+ "**/.DS_Store/**": true,
+ "**/.git/objects/**": true,
+ "**/.git/subtree-cache/**": true,
+ "**/.svn/**": true,
+ "**/.hg/**": true,
+ "**/CVS/**": true,
+ "**/node_modules/**": true,
+ "**/env/**": true,
+ "**/venv/**": true,
+ "env-*": true
+ },
+ "workbench.colorTheme": "Sweet Dracula",
+ "security.workspace.trust.untrustedFiles": "open",
+ "workbench.activityBar.location": "hidden",
+ "workbench.editor.tabActionLocation": "right",
+ "workbench.editor.empty.hint": "hidden"
+}
diff --git a/linux/home/.config/Code/User/snippets/snippet.code-snippets b/linux/home/.config/Code/User/snippets/snippet.code-snippets
new file mode 100644
index 0000000..16bdfed
--- /dev/null
+++ b/linux/home/.config/Code/User/snippets/snippet.code-snippets
@@ -0,0 +1,126 @@
+{
+ // Place your global snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
+ // description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
+ // is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
+ // used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
+ // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
+ // Placeholders with the same ids are connected.
+ // Example:
+ "if name is main": {
+ "scope": "python",
+ "prefix": "ifn",
+ "body": [
+ "if __name__ == '__main__':",
+ "\t$0"
+ ],
+ "description": ""
+ },
+ "for in": {
+ "scope": "python",
+ "prefix": "fori",
+ "body": [
+ "for ${1:item} in ${2:items}:",
+ "\t$0"
+ ],
+ "description": ""
+ },
+ "for in range": {
+ "scope": "python",
+ "prefix": "forr",
+ "body": [
+ "for ${1:i} in range($2):",
+ "\t$0"
+ ],
+ "description": ""
+ },
+ "lambda": {
+ "scope": "python",
+ "prefix": "lamb",
+ "body": [
+ "lambda ${1:arg} : $0",
+ ],
+ "description": ""
+ },
+ "map input": {
+ "scope": "python",
+ "prefix": "mapi",
+ "body": [
+ "map(${1:func}, input($2).split())"
+ ],
+ "description": ""
+ },
+ "html boiler plate": {
+ "scope": "html",
+ "prefix": "html",
+ "body": [
+ "<!DOCTYPE html>",
+ "<html lang=\"en\">",
+ "",
+ "<head>",
+ " <meta charset=\"utf-8\">",
+ " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">",
+ "",
+ " <title>${1:Basic HTML5}</title>",
+ " <meta name=\"author\" content=\"${2:Trevor Gray}\">",
+ " <meta name=\"description\" content=\"${3:HTML5 Template}\">",
+ "",
+ " <meta property=\"og:title\" content=\"${4:HTML5 Template}\">",
+ " <meta property=\"og:type\" content=\"website\">",
+ " <meta property=\"og:url\" content=\"https://$5\">",
+ " <meta property=\"og:description\" content=\"${6:HTML5 Template}\">",
+ " <meta property=\"og:image\" content=\"${7:./assets/preview.png}\">",
+ "",
+ " <link rel=\"icon\" href=\"${8:./assets/favicon.ico}\">",
+ " <link rel=\"icon\" href=\"${9:./assets/favicon.svg}\" type=\"image/svg+xml\">",
+ " <link rel=\"apple-touch-icon\" href=\"${10:./assets/apple-touch-icon.png}\">",
+ "",
+ " <link rel=\"stylesheet\" href=\"${11:./css/style.css}\">",
+ "",
+ "</head>",
+ "",
+ "<body>",
+ " ${0:<!-- your content here... -->}",
+ " <script src=\"${12:./js/main.js}\" defer></script>",
+ "</body>",
+ "",
+ "</html>",
+ ],
+ "description": ""
+ },
+ "css boiler plate": {
+ "scope": "css",
+ "prefix": "css",
+ "body": [
+ "html {",
+ " font-family: sans-serif;",
+ " font-size: 100%;",
+ " box-sizing: border-box;",
+ "}",
+ "",
+ "*, ::before, ::after {",
+ " box-sizing: inherit;",
+ "}",
+ "",
+ "html, body {",
+ " margin: 0;",
+ " padding: 0;",
+ " width: 100vw;",
+ " min-height: 100%;",
+ " text-rendering: optimizeLegibility;",
+ "}",
+ ""
+ ]
+ },
+ "consoleLog": {
+ "scope": "javascript",
+ "prefix": "clg",
+ "body": "console.log(${1:object});",
+ "description": "Displays a message in the console"
+ },
+ "consoleLogObject": {
+ "scope": "javascript",
+ "prefix": "clo",
+ "body": "console.log('${1:object} :>> ', ${1:object});",
+ "description": "Displays an object in the console with its name"
+ }
+}
diff --git a/linux/home/.config/Code/User/spellright.dict b/linux/home/.config/Code/User/spellright.dict
new file mode 100644
index 0000000..94f1e64
--- /dev/null
+++ b/linux/home/.config/Code/User/spellright.dict
@@ -0,0 +1,14 @@
+srdusr
+gui
+url
+yara
+backend
+txt
+Regex
+github
+args
+grep
+res
+yar
+shipit
+json
diff --git a/linux/home/.config/Code/User/vsc.css b/linux/home/.config/Code/User/vsc.css
new file mode 100644
index 0000000..8316df0
--- /dev/null
+++ b/linux/home/.config/Code/User/vsc.css
@@ -0,0 +1,408 @@
+/* *:not(.monaco-editor .margin):not(.monaco-editor .lines-content.monaco-editor-background):not(.monaco-editor .cursors-layer.cursor-smooth-caret-animation > .cursor):not(.monaco-scrollable-element > .scrollbar.vertical > .slider):not(.monaco-scrollable-element > .scrollbar.horizontal > .slider):not(.pane .pane-body .monaco-list .monaco-scrollable-element .monaco-list-rows) {
+ transition-duration: 100ms !important;
+} */
+
+:root,
+::after,
+::before {
+ --tab-height: 25px;
+ --tab-radius: 8px;
+ --ui-radius: 6px;
+ --base-color: #bd93f9;
+ --gradient-one: #cba6f7;
+ --gradient-two: #89b4fa;
+ --side-pane-color: var(--vscode-editor-background);
+ --focus-border: #3f4f81;
+ --sash-size: 2px !important;
+ --sash-hover-size: 2px !important;
+}
+
+body > .monaco-workbench > .monaco-grid-view > .monaco-grid-branch-node > .monaco-split-view2 > .monaco-scrollable-element > .split-view-container {
+ background: var(--vscode-editor-background) !important;
+}
+
+.editor-container {
+ -webkit-font-smoothing: antialiased;
+}
+
+.monaco-workbench .part.titlebar .window-controls-container .window-icon,
+.monaco-workbench .part.editor > .content > .watermark {
+ display: none !important;
+}
+
+.monaco-workbench .part.editor .tabs-and-actions-container .window-controls-container {
+ display: flex;
+ flex-grow: 0;
+ flex-shrink: 0;
+ text-align: center;
+ -webkit-app-region: no-drag;
+ height: 100%;
+}
+
+.monaco-workbench .part.editor .tabs-and-actions-container .window-controls-container .window-icon {
+ width: 30px !important;
+ height: 30px !important;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.monaco-workbench .part.editor .tabs-and-actions-container .window-controls-container .window-icon.window-close:hover {
+ background: #FF5555 !important;
+}
+
+.monaco-workbench .part.titlebar .window-controls-container .layout-dropdown-container {
+ margin-left: auto !important;
+}
+
+.monaco-workbench .part.titlebar > .window-controls-container {
+ width: unset !important;
+}
+
+.monaco-workbench .part.titlebar > .window-controls-container > .window-icon {
+ width: 30px !important;
+}
+
+.monaco-workbench.linux .part.titlebar > .window-title {
+ font-size: 12.5px !important;
+}
+
+.monaco-workbench .part.titlebar > .titlebar-container .window-appicon > .home-bar-icon-badge,
+.monaco-workbench .part.titlebar > .titlebar-container > .window-appicon:not(.codicon) {
+ background-image: url(./vsc/vsc.svg) !important;
+}
+
+.monaco-workbench .part.editor > .content .editor-group-container.empty .editor-group-letterpress {
+ background-image: url(./vsc/vsc-back.svg) !important;
+}
+
+.monaco-workbench .part.editor.has-watermark > .content.empty .editor-group-container > .editor-group-letterpress {
+ background-position-y: 50% !important;
+}
+
+.mac,
+.windows,
+.linux {
+ /* font-family: 'CaskaydiaCove Nerd Font' !important;
+ --monaco-monospace-font: 'CaskaydiaCove Nerd Font' !important; */
+ /* font-family: 'Delugia' !important;
+ --monaco-monospace-font: 'Delugia Mono' !important; */
+}
+
+
+.monaco-workbench .part.sidebar > .title > .title-label h2 {
+ font-weight: bold !important;
+}
+
+.monaco-sash.hover:before,
+.monaco-sash.active:before {
+ background: linear-gradient(to bottom, var(--gradient-one), var(--gradient-two)) !important;
+}
+
+/* Squiggly lines to straight lines */
+
+.monaco-editor .squiggly-error {
+ background: none !important;
+ border-bottom: 0.5px solid var(--vscode-editorError-foreground) !important;
+}
+
+.monaco-editor .squiggly-warning {
+ background: none !important;
+ border-bottom: 0.5px solid var(--vscode-editorWarning-foreground) !important;
+}
+
+.monaco-editor .squiggly-info {
+ background: none !important;
+ border-bottom: 0.5px solid var(--vscode-editorInfo-foreground) !important;
+}
+
+.monaco-editor .squiggly-hint {
+ background: none !important;
+ border-bottom: 0.5px solid var(--vscode-editorHint-foreground) !important;
+}
+
+/* */
+
+/* Gradient Tab border */
+body.activity-bar-at-bottom .monaco-workbench .activitybar .active-item-indicator {
+ display: block !important;
+}
+
+.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator:before {
+ border: 0 !important;
+ width: 3px !important;
+ height: 100% !important;
+ position: absolute !important;
+ top: 0 !important;
+ left: 0 !important;
+ background-image: linear-gradient(to top, var(--gradient-one), var(--gradient-two)) !important;
+ border-radius: 100vmax !important;
+}
+
+.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab > .tab-border-bottom-container,
+.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab > .tab-border-top-container {
+ background-image: linear-gradient(to left, var(--gradient-one), var(--gradient-two)) !important;
+ height: 3px !important;
+ display: none !important;
+}
+
+.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .badge .badge-content {
+ background-image: linear-gradient(90deg, var(--gradient-one), var(--gradient-two)) !important;
+}
+
+/* */
+
+/* Hides dirty tab white dot indicator */
+
+.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.dirty > .tab-actions .action-label:not(:hover):before,
+.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty > .tab-actions .action-label:not(:hover):before {
+ content: '' !important;
+}
+
+/* */
+
+/* Git icon background */
+
+.codicon-git-branch:before {
+ background: linear-gradient(to bottom, var(--gradient-one), var(--gradient-two)) !important;
+ -webkit-background-clip: text !important;
+ background-clip: text !important;
+ color: rgba(255, 255, 255, 0.4) !important;
+}
+
+/* */
+
+/* Editor Background */
+
+.monaco-editor,
+.split-view-view .lines-content.monaco-editor-background {
+ background: url(./vsc/pek.png) bottom right / 10% auto no-repeat scroll;
+ /*background: linear-gradient(rgba(0,0,0,.65), rgba(0,0,0,.65)), url(./vsc/bg.jpg) center center / auto 100vh no-repeat scroll;*/
+}
+
+/* */
+
+/* Scrollbar */
+.monaco-scrollable-element > .scrollbar > .slider {
+ border-radius: 7px !important;
+}
+
+.monaco-scrollable-element > .scrollbar > .slider.active {
+ background: linear-gradient(to top, var(--gradient-one), var(--gradient-two)) !important;
+}
+
+canvas.decorationsOverviewRuler,
+.monaco-scrollable-element > .scrollbar.vertical,
+.monaco-scrollable-element > .scrollbar.vertical > .slider {
+ width: 7px !important;
+}
+
+.monaco-scrollable-element > .scrollbar.vertical > .slider {
+ transition: all 100ms ease-out;
+}
+
+.monaco-scrollable-element > .scrollbar.horizontal,
+.monaco-scrollable-element > .scrollbar.horizontal > .slider {
+ height: 7px !important;
+}
+
+.monaco-scrollable-element > .scrollbar.horizontal > .slider {
+ transition: all 100ms ease-out;
+}
+
+.monaco-scrollable-element > .scrollbar.horizontal > .slider {
+ width: 135px !important;
+}
+
+/* */
+
+/* Rounded UI */
+
+/* Suggestions */
+.monaco-editor .monaco-hover,
+.monaco-editor .suggest-widget,
+.monaco-editor .suggest-details {
+ border-radius: var(--ui-radius) !important;
+ overflow: hidden !important;
+ box-shadow: rgb(0 0 0 / 36%) 0px 2px calc(var(--ui-radius) + 2px) !important;
+}
+
+/* Left pane selected file */
+.pane .pane-body,
+.pane .pane-body .monaco-list,
+.pane .pane-body .monaco-list .monaco-scrollable-element,
+.pane .pane-body .monaco-list .monaco-scrollable-element .monaco-list-rows,
+.pane .pane-body .monaco-list .monaco-scrollable-element .monaco-list-rows .monaco-list-row {
+ overflow: visible !important;
+}
+
+.open-editors .monaco-list .monaco-list-row {
+ padding-left: 0px !important;
+}
+
+.monaco-list-row:hover,
+.monaco-list-row.selected,
+.monaco-list-row.focused {
+ border-radius: 0 var(--ui-radius) var(--ui-radius) 0 !important;
+}
+
+.monaco-workbench .monaco-list:not(.element-focused):focus:before,
+.monaco-select-box,
+.monaco-select-box-dropdown-container,
+.monaco-select-box-dropdown-container .monaco-list-row:hover,
+.monaco-select-box-dropdown-container .monaco-list-row.selected,
+.monaco-select-box-dropdown-container .monaco-list-row.focused {
+ border-radius: var(--ui-radius) !important;
+}
+
+.monaco-list-row.selected::before,
+.monaco-list-row.selected::after {
+ content: '' !important;
+ display: block !important;
+ position: absolute !important;
+ width: var(--ui-radius) !important;
+ height: var(--ui-radius) !important;
+ left: 0 !important;
+ pointer-events: none !important;
+}
+
+.monaco-list-row.selected::before {
+ top: calc(0px - var(--ui-radius)) !important;
+ background: radial-gradient(circle at 100% 0%, transparent 70.71%, var(--side-pane-color) 29.289%) no-repeat;
+ background-position: 100% 0%;
+}
+
+.monaco-list-row.selected::after {
+ bottom: calc(0px - var(--ui-radius)) !important;
+ background: radial-gradient(circle at 100% 100%, transparent 70.71%, var(--side-pane-color) 29.289%) no-repeat;
+ background-position: 100% 100%;
+}
+
+.monaco-button.monaco-text-button {
+ border-radius: var(--ui-radius) !important;
+}
+
+/* */
+
+/* Smooth cursor */
+
+.monaco-editor .cursors-layer.cursor-smooth-caret-animation > .cursor {
+ transition: all 100ms ease-out;
+}
+
+/* */
+
+/* Active tab (Without border) */
+
+..tab:first-child {
+ margin-left: var(--tab-radius) !important;
+ /* margin-left: calc(var(--tab-radius) - 2px) !important; */
+}
+
+..tab:last-child {
+ margin-right: var(--tab-radius) !important;
+ /* margin-right: calc(var(--tab-radius) - 2px) !important; */
+}
+
+..tab.active {
+ /* border: 2px solid var(--base-color) !important; */
+ /* background-color: transparent !important; */
+ /* rgb(20, 24, 34) */
+ border-radius: var(--tab-radius) var(--tab-radius) 0 0 !important;
+ /* border-bottom: 2px solid #0c0e14 !important; */
+ /* z-index: 1 !important; */
+}
+
+.tab.dirty-border-top > .tab-border-top-container {
+ border-radius: var(--tab-radius) var(--tab-radius) 0 0 !important;
+}
+
+/* Border bottom radius*/
+..tab.active::before,
+..tab.active::after {
+ content: '' !important;
+ display: block !important;
+ box-sizing: border-box !important;
+ position: absolute !important;
+ z-index: 1;
+ width: var(--tab-radius) !important;
+ height: var(--tab-radius) !important;
+ bottom: 0px !important;
+ /* bottom: -2px !important; */
+ pointer-events: none !important;
+ /* border-bottom: 2px solid var(--base-color) !important; */
+}
+
+...tab.active::before {
+ left: calc(0px - var(--tab-radius)) !important;
+ /* transform: translateX(calc(0px - var(--tab-radius))) !important; */
+ /* border-right: 2px solid var(--base-color) !important; */
+ border-bottom-right-radius: var(--tab-radius) !important;
+ /* box-shadow: 3px 3px 0 3px var(--tab-color) !important; */
+ box-shadow: 3px 3px 0 3px var(--vscode-editor-background) !important;
+ /* background: radial-gradient(circle at 0% 0%, transparent 70.71%, var(--tab-color) 29.289%) no-repeat;
+ background-position: 0% 0%; */
+}
+
+..tab.active::after {
+ right: 0 !important;
+ transform: translateX(var(--tab-radius)) !important;
+ /* border-left: 2px solid var(--base-color) !important; */
+ border-bottom-left-radius: var(--tab-radius) !important;
+ /* box-shadow: -3px 3px 0 3px var(--tab-color) !important; */
+ box-shadow: -3px 3px 0 3px var(--vscode-editor-background) !important;
+ /* background: radial-gradient(circle at 100% 0%, transparent 70.71%, var(--tab-color) 29.289%) no-repeat;
+ background-position: 100% 0%; */
+}
+
+/* .tabs-container:before {
+ content: '' !important;
+ top: 0 !important;
+ left: 0 !important;
+ right: 0 !important;
+ bottom: 0 !important;
+ background: linear-gradient(90deg, rgb(12, 14, 20), rgb(189, 172, 255), rgb(189, 147, 249), rgb(189, 147, 249), rgb(189, 147, 249), rgb(189, 172, 249), rgb(12, 14, 20)) !important;
+ clip: rect(33px, 2600px, 33px, 0px) !important;
+ position: absolute !important;
+}
+
+.tabs-container:after {
+ content: '' !important;
+ top: 0 !important;
+ left: 0 !important;
+ right: 0 !important;
+ bottom: 0 !important;
+ background: linear-gradient(90deg, rgb(12, 14, 20), rgb(189, 172, 255), rgb(189, 147, 249), rgb(189, 147, 249), rgb(189, 147, 249), rgb(189, 172, 249), rgb(12, 14, 20)) !important;
+ clip: rect(33px, 2600px, 35px, 0px) !important;
+ position: absolute !important;
+}
+
+.tab-label > .monaco-icon-label-container::after {
+ background: transparent !important;
+} */
+
+/* */
+
+.tab{
+ border-radius: 12px !important;
+ margin: 5px !important;
+ height: 40px !important;
+ border:2px solid #313244 !important;
+ background-color: #161925 !important;
+}
+.tab.active {
+ border: 2px solid var(--gradient-one) !important;
+ color: var(--gradient-one) !important;
+ font-weight: bold !important;
+}
+.tab.dirty {
+ border: 2px solid var(--gradient-two) !important;
+
+}
+.tabs-container{
+ height: auto !important;
+ padding: 5px !important;
+}
+.editor-actions {
+ height: inherit !important;
+}
diff --git a/linux/home/.config/Code/User/vsc.js b/linux/home/.config/Code/User/vsc.js
new file mode 100644
index 0000000..a14bf5a
--- /dev/null
+++ b/linux/home/.config/Code/User/vsc.js
@@ -0,0 +1,100 @@
+function getOffset(el) {
+ const rect = el.getBoundingClientRect();
+ return {
+ left: rect.left + window.scrollX,
+ top: rect.top + window.scrollY,
+ };
+}
+
+const prevOffset = {};
+
+const displayEffect = (event) => {
+ if (prevOffset.top === undefined) {
+ let initialOffset = getOffset(document.getElementsByClassName("cursor")[0]);
+ prevOffset.top = initialOffset.top;
+ prevOffset.left = initialOffset.left;
+ }
+
+ const existingRect = document.getElementById("myRect");
+ if (existingRect !== null) {
+ existingRect.remove();
+ }
+
+ setTimeout(function () {
+ const currOffset = getOffset(document.getElementsByClassName("cursor")[0]);
+ const top = prevOffset.top;
+ const left = prevOffset.left;
+ const rect = document.createElement("div");
+
+ // console.log("prev: ", top, left);
+ // console.log("curr: ", currOffset.top, currOffset.left);
+
+ rect.id = "myRect";
+ rect.style.cssText = `
+ position:absolute;
+ top:${top}px;
+ left:${left}px;
+ width:9px;
+ height:20px;
+ z-index:10;
+ background-color:red;
+ opacity: 0.5;
+ `;
+ rect.animate(
+ [
+ // keyframes
+ { transform: "scale(1) skew(10deg)" },
+ { transform: "scale(0) skew(10deg)" },
+ ],
+ {
+ // timing options
+ duration: 500,
+ easing: "ease-in-out",
+ direction: "alternate",
+ iterations: Infinity,
+ }
+ );
+
+ document.body.appendChild(rect);
+ prevOffset.top = currOffset.top;
+ prevOffset.left = currOffset.left;
+ }, 24);
+};
+
+// NOTE: First enable editor.cursorSmoothCaretAnimation.
+// NOTE: Uncomment this to get a cursor trail effect.
+// document.addEventListener("keydown", displayEffect);
+
+const windowControls = document.createElement('div');
+windowControls.className = 'window-controls-container';
+const minimize = document.createElement('div');
+minimize.classList.add('window-icon', 'window-minimize', 'codicon', 'codicon-chrome-minimize');
+const restore = document.createElement('div');
+restore.classList.add('window-icon', 'window-max-restore', 'codicon', 'codicon-chrome-restore');
+const close = document.createElement('div');
+close.classList.add('window-icon', 'window-close', 'codicon', 'codicon-chrome-close');
+windowControls.appendChild(minimize);
+windowControls.appendChild(restore);
+windowControls.appendChild(close);
+// const html = '<div class="window-controls-container"><div class="window-icon window-minimize codicon codicon-chrome-minimize"></div><div class="window-icon window-max-restore codicon codicon-chrome-restore"></div><div class="window-icon window-close codicon codicon-chrome-close"></div></div>';
+// template.innerHTML = html;
+// const windowControls = template.content.firstChild;
+
+var observer = new MutationObserver(function (mutations, me) {
+ console.warn('Observing if window controls present');
+ // const windowControls = document.querySelector(".window-controls-container");
+ const tabContainer = document.querySelector(".tabs-and-actions-container");
+
+ if (windowControls && tabContainer) {
+ tabContainer.appendChild(windowControls);
+ console.warn('Observing completed');
+ me.disconnect();
+ return;
+ }
+});
+
+
+// observer.observe(document, {
+// childList: true,
+// subtree: true
+// });
diff --git a/linux/home/.config/X11/.Xresources b/linux/home/.config/X11/.Xresources
new file mode 100644
index 0000000..be509d1
--- /dev/null
+++ b/linux/home/.config/X11/.Xresources
@@ -0,0 +1,307 @@
+st.alpha: 255
+!! Transparency (0-1):
+*.alpha: 0.2
+
+urxvt*shading: 10
+urxvt*tintColor: #000000
+urxvt*blurRadius: 5
+urxvt*transparent: true
+urxvt*depth: 32
+urxvt*background: rgba:0000/0000/1111/dddd
+!urxvt*background: rgba:0000/0000/0200/c800
+!URxvt*scrollstyle: plain
+URxvt.scrollBar: False
+!URxvt.font: xft:DejaVu Sans Mono:pixelsize=11:antialias=true
+URxvt.font: xft:monospace:Bold:pixelsize=12:antialias=true
+!URxvt*transparent: true
+!URxvt*shading: 5
+! change to whateva background
+URxvt.keysym.C-7: command:\033]11;#ff0000\007
+!URxvt*reverseVideo: True
+
+URxvt*saveLines : 10000
+
+!URxvt.keysym.Control-Up: \033[1;5A
+!URxvt.keysym.Control-Down: \033[1;5B
+!URxvt.keysym.Control-Left: \033[1;5D
+!URxvt.keysym.Control-Right: \033[1;5C
+
+URxvt.keysym.Control-1: command:\007\033]711;xft:monospace:Bold:pixelsize=12:antialias=true\007
+URxvt.keysym.Control-2: command:\007\033]711;xft:monospace:Bold:pixelsize=18:antialias=true\007
+URxvt.keysym.Control-3: command:\007\033]711;xft:monospace:Bold:pixelsize=28:antialias=true\007
+!URxvt.keysym.Control-1: command:\033]710;xft:Terminus:pixelsize=10:antialias=false\007\033]711;xft:Terminus:Bold:pixelsize=10:antialias=false\007
+!URxvt.keysym.Control-2: command:\033]710;xft:Terminus:pixelsize=14:antialias=false\007\033]711;xft:Terminus:Bold:pixelsize=14:antialias=false\007
+!URxvt.keysym.Control-3: command:\033]710;xft:Terminus:pixelsize=18:antialias=false\007\033]711;xft:Terminus:Bold:pixelsize=18:antialias=false\007
+!URxvt.keysym.Control-4: command:\033]710;xft:Terminus:Bold:pixelsize=20:antialias=false\007\033]711;xft:Terminus:Bold:pixelsize=20:antialias=false\007
+!URxvt.keysym.Control-5: command:\033]710;xft:Terminus:Bold:pixelsize=28:antialias=false\007\033]711;xft:Terminus:Bold:pixelsize=28:antialias=false\007
+
+
+
+
+
+
+
+!URxvt.perl-ext-common: selection-to-clipboard
+!URxvt.keysym.Shift-Control-V: eval:paste_clipboard
+!URxvt.keysym.Shift-Control-C: eval:selection_to_clipboard
+
+
+URxvt.iso14755: false
+URxvt.iso14755_52: false
+
+
+!URxvt.scrollBar: true
+!URxvt.scrollBar_right: true
+!URxvt.scrollBar_floating: true
+!URxvt.searchable-scrollback: false
+URxvt.url-select.underline: true
+URxvt.url-select.autocopy: true
+URxvt.cutchars: `""()''*<>[]{|}
+URxvt.cursorBlink: True
+URxvt.cursorColor: green
+URxvt.colorBD: yellow
+URxvt.colorUL: green
+!URxvt.blurRadius: 5
+
+
+!! Copy Paste & Other Extensions
+!URxvt.perl-ext-common: default,clipboard,url-select,keyboard-select
+!URxvt.copyCommand: xclip -i -selection clipboard
+!URxvt.pasteCommand: xclip -o -selection clipboard
+!URxvt.keysym.M-c: perl:clipboard:copy
+!URxvt.keysym.M-v: perl:clipboard:paste
+!URxvt.keysym.M-C-v: perl:clipboard:paste_escaped
+!URxvt.keysym.M-Escape: perl:keyboard-select:activate
+!URxvt.keysym.M-s: perl:keyboard-select:search
+!URxvt.keysym.M-u: perl:url-select:select_next
+!URxvt.urlLauncher: firefox
+!URxvt.underlineURLs: true
+!URxvt.urlButton: 1
+
+
+
+
+URxvt.keysym.Control-v: eval:paste_clipboard
+URxvt.keysym.Control-c: eval:selection_to_clipboard
+URxvt.keysym.Control-Meta-c: builtin-string:
+URxvt.keysym.Control-Meta-v: builtin-string:
+
+
+URxvt.keysym.Control-k: command:\033]720;1\007
+URxvt.keysym.Control-j: command:\033]721;1\007
+
+
+URxvt.keysym.Meta-Page_Up: perl:pageup
+URxvt.keysym.Meta-Page_Down: perl:pagedown
+URxvt.perl-lib: /usr/local/lib/urxvt/perl
+URxvt.perl-ext: custom.pl
+
+
+!URxvt*loginShell: true
+!URxvt*termName: screen-256color
+
+!URxvt*perl-ext-common:
+!URxvt*perl-ext:
+
+
+!URxvt.keysym.Control-B: eval:scroll_up_pages 1
+!URxvt.keysym.Control-F: eval:scroll_down_pages 1
+
+
+URxvt.perl-ext-common: default,matcher,clipboard,tabbed
+URxvt.clipboard.autocopy: true
+URxvt.keysym.M-c: perl:clipboard:copy
+URxvt.keysym.M-v: perl:clipboard:paste
+
+!URxvt.url-launcher: /usr/bin/xdg-open
+!URxvt.matcher.button: 1
+!URxvt.keysym.C-Delete: perl:matcher:last
+!URxvt.keysym.M-Delete: perl:matcher:list
+!URxvt.matcher.rend.0: Uline Bold fg5
+
+!URxvt.perl-ext-common: ...,tabbed,...
+
+
+!Key Description
+!Shift+Down New tab
+!Shift+Left Go to left tab
+!Shift+Right Go to right tab
+!Ctrl+Left Move tab to the left
+!Ctrl+Right Move tab to the right
+!Ctrl+d Close tab
+
+
+!URxvt.keysym.Control-k: \033[1;5A
+!URxvt.keysym.Control-j: \033[1;5B
+!URxvt.keysym.Control-l: \033[1;5C
+!URxvt.keysym.Control-h: \033[1;5D
+
+!^[[5~
+
+
+rofi.kb-row-up: Up,Control+k,Shift+Tab,Shift+ISO_Left_Tab
+rofi.kb-row-down: Down,Control+j
+rofi.kb-accept-entry: Control+m,Return,KP_Enter
+rofi.terminal: st
+rofi.kb-remove-to-eol: Control+Shift+e
+rofi.kb-mode-next: Shift+Right,Control+Tab,Control+l
+rofi.kb-mode-previous: Shift+Left,Control+Shift+Tab,Control+h
+rofi.kb-remove-char-back: BackSpace
+
+
+
+
+!! Set a default font and font size as below:
+!*.font: monospace:size=10
+rofi.font: Noto Sans 11
+
+/* name dark light */
+/* black 0 8 */
+/* red 1 9 */
+/* green 2 10 */
+/* yellow 3 11 */
+/* blue 4 12 */
+/* purple 5 13 */
+/* cyan 6 14 */
+/* white 7 15 */
+
+/* !! gruvbox: */
+/* *.color0: #1d2021 */
+/* *.color1: #cc241d */
+/* *.color2: #98971a */
+/* *.color3: #d79921 */
+/* *.color4: #458588 */
+/* *.color5: #b16286 */
+/* *.color6: #689d6a */
+/* *.color7: #a89984 */
+/* *.color8: #928374 */
+/* *.color9: #fb4934 */
+/* *.color10: #b8bb26 */
+/* *.color11: #fabd2f */
+/* *.color12: #83a598 */
+/* *.color13: #d3869b */
+/* *.color14: #8ec07c */
+/* *.color15: #ebdbb2 */
+/* *.color256: #1d2021 */
+/* *.color257: #ebdbb2 */
+
+* !! gruvbox light: */
+* *.color0: #fbf1c7 */
+* *.color1: #cc241d */
+* *.color2: #98971a */
+* *.color3: #d79921 */
+* *.color4: #458588 */
+* *.color5: #b16286 */
+* *.color6: #689d6a */
+* *.color7: #7c6f64 */
+* *.color8: #928374 */
+* *.color9: #9d0006 */
+* *.color10: #79740e */
+* *.color11: #b57614 */
+* *.color12: #076678 */
+* *.color13: #8f3f71 */
+* *.color14: #427b58 */
+* *.color15: #3c3836 */
+* *.background: #fbf1c7 */
+* *.foreground: #282828 */
+* st.alpha: 0.2 */
+
+/* !! brogrammer: */
+/* *.foreground: #d6dbe5 */
+/* *.background: #131313 */
+/* *.color0: #1f1f1f */
+/* *.color8: #d6dbe5 */
+/* *.color1: #f81118 */
+/* *.color9: #de352e */
+/* *.color2: #2dc55e */
+/* *.color10: #1dd361 */
+/* *.color3: #ecba0f */
+/* *.color11: #f3bd09 */
+/* *.color4: #2a84d2 */
+/* *.color12: #1081d6 */
+/* *.color5: #4e5ab7 */
+/* *.color13: #5350b9 */
+/* *.color6: #1081d6 */
+/* *.color14: #0f7ddb */
+/* *.color7: #d6dbe5 */
+/* *.color15: #ffffff */
+/* *.colorBD: #d6dbe5 */
+
+/* ! base16 */
+/* *.color0: #181818 */
+/* *.color1: #ab4642 */
+/* *.color2: #a1b56c */
+/* *.color3: #f7ca88 */
+/* *.color4: #7cafc2 */
+/* *.color5: #ba8baf */
+/* *.color6: #86c1b9 */
+/* *.color7: #d8d8d8 */
+/* *.color8: #585858 */
+/* *.color9: #ab4642 */
+/* *.color10: #a1b56c */
+/* *.color11: #f7ca88 */
+/* *.color12: #7cafc2 */
+/* *.color13: #ba8baf */
+/* *.color14: #86c1b9 */
+/* *.color15: #f8f8f8 */
+
+/* !! solarized */
+/* *.color0: #073642 */
+/* *.color1: #dc322f */
+/* *.color2: #859900 */
+/* *.color3: #b58900 */
+/* *.color4: #268bd2 */
+/* *.color5: #d33682 */
+/* *.color6: #2aa198 */
+/* *.color7: #eee8d5 */
+/* *.color9: #cb4b16 */
+/* *.color8: #fdf6e3 */
+/* *.color10: #586e75 */
+/* *.color11: #657b83 */
+/* *.color12: #839496 */
+/* *.color13: #6c71c4 */
+/* *.color14: #93a1a1 */
+/* *.color15: #fdf6e3 */
+
+/* !! xterm */
+/* *.color0: #000000 */
+/* *.color1: #cd0000 */
+/* *.color2: #00cd00 */
+/* *.color3: #cdcd00 */
+/* *.color4: #0000cd */
+/* *.color5: #cd00cd */
+/* *.color6: #00cdcd */
+/* *.color7: #e5e5e5 */
+/* *.color8: #4d4d4d */
+/* *.color9: #ff0000 */
+/* *.color10: #00ff00 */
+/* *.color11: #ffff00 */
+/* *.color12: #0000ff */
+/* *.color13: #ff00ff */
+/* *.color14: #00ffff */
+/* *.color15: #aabac8 */
+/* *.background: #000000 */
+
+/* ! Dracula Xresources palette */
+/* *.foreground: #F8F8F2 */
+/* *.background: #282A36 */
+/* *.color0: #000000 */
+/* *.color8: #4D4D4D */
+/* *.color1: #FF5555 */
+/* *.color9: #FF6E67 */
+/* *.color2: #50FA7B */
+/* *.color10: #5AF78E */
+/* *.color3: #F1FA8C */
+/* *.color11: #F4F99D */
+/* *.color4: #BD93F9 */
+/* *.color12: #CAA9FA */
+/* *.color5: #FF79C6 */
+/* *.color13: #FF92D0 */
+/* *.color6: #8BE9FD */
+/* *.color14: #9AEDFE */
+/* *.color7: #BFBFBF */
+/* *.color15: #E6E6E6 */
+
+/* *.background: .color0 */
+/* *.color256: 0#1d2021 */
+/* *.color257: 15#ebdbb2 */
diff --git a/linux/home/.config/X11/.xbindkeysrc b/linux/home/.config/X11/.xbindkeysrc
new file mode 100644
index 0000000..481943d
--- /dev/null
+++ b/linux/home/.config/X11/.xbindkeysrc
@@ -0,0 +1,31 @@
+# # Up
+# "xdotool mousemove_relative --sync -- 0 -24"
+# alt + w
+#
+# # Left
+# "xdotool mousemove_relative --sync -- -24 0"
+# alt + a
+#
+# # Down
+# "xdotool mousemove_relative --sync -- 0 24"
+# alt + s
+#
+# # Right
+# "xdotool mousemove_relative --sync -- 24 0"
+# alt + d
+#
+# # left + up
+# "xdotool mousemove_relative --sync -- -24 -24"
+# Shift+alt + q
+#
+# # right + up
+# "xdotool mousemove_relative --sync -- 24 -24"
+# Shift+alt + e
+#
+# # right + down
+# "xdotool mousemove_relative --sync -- 24 24"
+# Shift+alt + d
+#
+# # left + down
+# "xdotool mousemove_relative --sync -- -24 24"
+# Shift+alt + a
diff --git a/linux/home/.config/X11/.xinitrc b/linux/home/.config/X11/.xinitrc
new file mode 100755
index 0000000..d500044
--- /dev/null
+++ b/linux/home/.config/X11/.xinitrc
@@ -0,0 +1,34 @@
+#!/bin/sh
+#
+#░█▀▀░▀█▀░█▀█░█▀▄░▀█▀░█░█
+#░▀▀█░░█░░█▀█░█▀▄░░█░░▄▀▄
+#░▀▀▀░░▀░░▀░▀░▀░▀░░▀░░▀░▀
+#
+
+# xinitrc.d
+if [ -d /etc/X11/xinit/xinitrc.d ]; then
+ for f in /etc/X11/xinit/xinitrc.d/*; do
+ [ -x "$f" ] && . "$f"
+ done
+ unset f
+fi
+
+# Keyboard layout
+setxkbmap -model pc105 -layout us -variant qwerty
+
+# xinitrc runs automatically when you run startx.
+
+# There are some small but important commands that need to be run when we start
+# the graphical environment. There is a link to this file in ~/.xprofile
+# because that file is run automatically if someone uses a display manager
+# (login screen) and so they are needed there. To prevent doubling up commands,
+# I source them here with the line below.
+
+# Profile
+[[ -f /etc/profile ]] && source /etc/profile
+
+if [ -f "${XDG_CONFIG_HOME:-$HOME/.config}/X11/.xprofile" ]; then
+ . "${XDG_CONFIG_HOME:-$HOME/.config}/X11/.xprofile"
+else
+ . "$HOME/.xprofile"
+fi
diff --git a/linux/home/.config/X11/.xprofile b/linux/home/.config/X11/.xprofile
new file mode 100644
index 0000000..bc2e5f8
--- /dev/null
+++ b/linux/home/.config/X11/.xprofile
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+# Xresources
+[[ -f ~/.config/X11/.Xresources ]] && xrdb -merge ~/.config/X11/.Xresources
+
+#Xset
+xset r rate 150 50
+#xset r rate 290 70
+
+# Xbindkeys
+xbindkeys -f "$XDG_CONFIG_HOME"/X11/xbindkeysrc
+
+# XKB
+setxkbmap -print | xkbcomp -I"$HOME"/.config/xkb - "$DISPLAY"
+
+#export XAUTHORITY="$XDG_RUNTIME_DIR/.Xauthority" # This line will break some DMs.
+export USERXSESSION="$XDG_CACHE_HOME/X11/xsession"
+export USERXSESSIONRC="$XDG_CACHE_HOME/X11/xsessionrc"
+export ALTUSERXSESSION="$XDG_CACHE_HOME/X11/Xsession"
+export ERRFILE="$XDG_CACHE_HOME/X11/xsession-errors"
+
+if [[ "$(tty)" = "/dev/tty1" ]]; then
+ pgrep bspwm || startx "$XDG_CONFIG_HOME/X11/.xinitrc"
+fi
diff --git a/linux/home/.config/ags/.eslintrc.yml b/linux/home/.config/ags/.eslintrc.yml
new file mode 100644
index 0000000..ff96a83
--- /dev/null
+++ b/linux/home/.config/ags/.eslintrc.yml
@@ -0,0 +1,130 @@
+env:
+ es2022: true
+extends:
+ - "eslint:recommended"
+ - "plugin:@typescript-eslint/recommended"
+parser: "@typescript-eslint/parser"
+parserOptions:
+ ecmaVersion: 2022
+ sourceType: "module"
+ project: "./tsconfig.json"
+ warnOnUnsupportedTypeScriptVersion: false
+root: true
+ignorePatterns:
+ - types/
+plugins:
+ - "@typescript-eslint"
+rules:
+ "@typescript-eslint/ban-ts-comment":
+ - "off"
+ "@typescript-eslint/no-non-null-assertion":
+ - "off"
+ # "@typescript-eslint/no-explicit-any":
+ # - "off"
+ "@typescript-eslint/no-unused-vars":
+ - error
+ - varsIgnorePattern: (^unused|_$)
+ argsIgnorePattern: ^(unused|_)
+ "@typescript-eslint/no-empty-interface":
+ - "off"
+
+ arrow-parens:
+ - error
+ - as-needed
+ comma-dangle:
+ - error
+ - always-multiline
+ comma-spacing:
+ - error
+ - before: false
+ after: true
+ comma-style:
+ - error
+ - last
+ curly:
+ - error
+ - multi-or-nest
+ - consistent
+ dot-location:
+ - error
+ - property
+ eol-last:
+ - error
+ eqeqeq:
+ - error
+ - always
+ indent:
+ - error
+ - 4
+ - SwitchCase: 1
+ keyword-spacing:
+ - error
+ - before: true
+ lines-between-class-members:
+ - error
+ - always
+ - exceptAfterSingleLine: true
+ padded-blocks:
+ - error
+ - never
+ - allowSingleLineBlocks: false
+ prefer-const:
+ - error
+ quotes:
+ - error
+ - double
+ - avoidEscape: true
+ semi:
+ - error
+ - never
+ nonblock-statement-body-position:
+ - error
+ - below
+ no-trailing-spaces:
+ - error
+ no-useless-escape:
+ - off
+ max-len:
+ - error
+ - code: 100
+ func-call-spacing:
+ - error
+ array-bracket-spacing:
+ - error
+ space-before-function-paren:
+ - error
+ - anonymous: never
+ named: never
+ asyncArrow: ignore
+ space-before-blocks:
+ - error
+ key-spacing:
+ - error
+ object-curly-spacing:
+ - error
+ - always
+globals:
+ Widget: readonly
+ Utils: readonly
+ App: readonly
+ Variable: readonly
+ Service: readonly
+ pkg: readonly
+ ARGV: readonly
+ Debugger: readonly
+ GIRepositoryGType: readonly
+ globalThis: readonly
+ imports: readonly
+ Intl: readonly
+ log: readonly
+ logError: readonly
+ print: readonly
+ printerr: readonly
+ window: readonly
+ TextEncoder: readonly
+ TextDecoder: readonly
+ console: readonly
+ setTimeout: readonly
+ setInterval: readonly
+ clearTimeout: readonly
+ clearInterval: readonly
diff --git a/linux/home/.config/ags/.gitignore b/linux/home/.config/ags/.gitignore
new file mode 100644
index 0000000..f56dbd1
--- /dev/null
+++ b/linux/home/.config/ags/.gitignore
@@ -0,0 +1,6 @@
+node_modules
+types
+package-lock.json
+bun.lockb
+flake.lock
+.weather
diff --git a/linux/home/.config/ags/assets/arrows-down.svg b/linux/home/.config/ags/assets/arrows-down.svg
new file mode 100644
index 0000000..5851aed
--- /dev/null
+++ b/linux/home/.config/ags/assets/arrows-down.svg
@@ -0,0 +1,10 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" version="1.1">
+ <defs>
+ <style id="current-color-scheme" type="text/css">
+ .ColorScheme-Text { color:#dfdfdf; } .ColorScheme-Highlight { color:#4285f4; } .ColorScheme-NeutralText { color:#ff9800; } .ColorScheme-PositiveText { color:#4caf50; } .ColorScheme-NegativeText { color:#f44336; }
+ </style>
+ </defs>
+ <g transform="matrix(1,0,0,1,4,4)">
+ <path class="ColorScheme-Text" d="M 7,2 V 10 L 3.5,6.5 2,8 8,14 14,8 12.5,6.5 9,10 V 2 Z" style="fill:currentColor"/>
+ </g>
+</svg>
diff --git a/linux/home/.config/ags/assets/arrows-left.svg b/linux/home/.config/ags/assets/arrows-left.svg
new file mode 100644
index 0000000..f3c1b2e
--- /dev/null
+++ b/linux/home/.config/ags/assets/arrows-left.svg
@@ -0,0 +1,10 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" version="1.1">
+ <defs>
+ <style id="current-color-scheme" type="text/css">
+ .ColorScheme-Text { color:#dfdfdf; } .ColorScheme-Highlight { color:#4285f4; } .ColorScheme-NeutralText { color:#ff9800; } .ColorScheme-PositiveText { color:#4caf50; } .ColorScheme-NegativeText { color:#f44336; }
+ </style>
+ </defs>
+ <g transform="matrix(1,0,0,1,4,4)">
+ <path class="ColorScheme-Text" d="M 14,7 H 6 L 9.5,3.5 8,2 2,8 8,14 9.5,12.5 6,9 H 14 Z" style="fill:currentColor"/>
+ </g>
+</svg>
diff --git a/linux/home/.config/ags/assets/arrows-right.svg b/linux/home/.config/ags/assets/arrows-right.svg
new file mode 100644
index 0000000..30baa8f
--- /dev/null
+++ b/linux/home/.config/ags/assets/arrows-right.svg
@@ -0,0 +1,10 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" version="1.1">
+ <defs>
+ <style id="current-color-scheme" type="text/css">
+ .ColorScheme-Text { color:#dfdfdf; } .ColorScheme-Highlight { color:#4285f4; } .ColorScheme-NeutralText { color:#ff9800; } .ColorScheme-PositiveText { color:#4caf50; } .ColorScheme-NegativeText { color:#f44336; }
+ </style>
+ </defs>
+ <g transform="matrix(1,0,0,1,4,4)">
+ <path class="ColorScheme-Text" d="M 2,9 H 10 L 6.5,12.5 8,14 14,8 8,2 6.5,3.5 10,7 H 2 Z" style="fill:currentColor"/>
+ </g>
+</svg>
diff --git a/linux/home/.config/ags/assets/arrows-up.svg b/linux/home/.config/ags/assets/arrows-up.svg
new file mode 100644
index 0000000..68b9944
--- /dev/null
+++ b/linux/home/.config/ags/assets/arrows-up.svg
@@ -0,0 +1,10 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" version="1.1">
+ <defs>
+ <style id="current-color-scheme" type="text/css">
+ .ColorScheme-Text { color:#dfdfdf; } .ColorScheme-Highlight { color:#4285f4; } .ColorScheme-NeutralText { color:#ff9800; } .ColorScheme-PositiveText { color:#4caf50; } .ColorScheme-NegativeText { color:#f44336; }
+ </style>
+ </defs>
+ <g transform="matrix(1,0,0,1,4,4)">
+ <path class="ColorScheme-Text" d="M 7,14 V 6 L 3.5,9.5 2,8 8,2 14,8 12.5,9.5 9,6 V 14 Z" style="fill:currentColor"/>
+ </g>
+</svg>
diff --git a/linux/home/.config/ags/assets/battery-flash-symbolic.svg b/linux/home/.config/ags/assets/battery-flash-symbolic.svg
new file mode 100644
index 0000000..21b5e33
--- /dev/null
+++ b/linux/home/.config/ags/assets/battery-flash-symbolic.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
+ <path d="m 8.96875 0 c -0.332031 0.0117188 -0.640625 0.1875 -0.816406 0.46875 l -5 8 c -0.105469 0.171875 -0.152344 0.355469 -0.152344 0.53125 v 1 h 3 v 5 c 0 1.003906 1.316406 1.378906 1.847656 0.53125 l 5 -8 c 0.105469 -0.171875 0.152344 -0.355469 0.152344 -0.53125 v -1 h -3 v -5 c 0 -0.5625 -0.464844 -1.015625 -1.03125 -1 z m 0 0"/>
+</svg>
diff --git a/linux/home/.config/ags/assets/bomb-kill.svg b/linux/home/.config/ags/assets/bomb-kill.svg
new file mode 100644
index 0000000..a6e9f09
--- /dev/null
+++ b/linux/home/.config/ags/assets/bomb-kill.svg
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ viewBox="0 0 22 22"
+ version="1.1"
+ id="svg1"
+ sodipodi:docname="bomb-kill.svg"
+ inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <sodipodi:namedview
+ id="namedview1"
+ pagecolor="#ffffff"
+ bordercolor="#000000"
+ borderopacity="0.25"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:zoom="28.545455"
+ inkscape:cx="11"
+ inkscape:cy="11"
+ inkscape:current-layer="svg1" />
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">.ColorScheme-Text { color: #fcfcfc; } </style>
+ </defs>
+ <path
+ style="fill:currentColor;fill-opacity:1;stroke:none;stroke-width:0.855269"
+ d="m 17.858728,4.1743879 c -1.13103,0.2314779 -2.107737,0.9080479 -2.727751,1.858046 -0.214576,-0.095764 -0.450425,-0.1516429 -0.701613,-0.1516429 -0.178764,0 -0.347261,0.034927 -0.509046,0.084986 -0.975172,-0.5918778 -2.11854,-0.9381881 -3.348989,-0.9381881 -3.5622531,0 -6.4300571,2.8539574 -6.4300571,6.3990111 0,3.545055 2.867804,6.399011 6.4300571,6.399011 3.562254,0 6.430058,-2.853956 6.430058,-6.39901 0,-1.224507 -0.347991,-2.3623553 -0.94274,-3.3328191 0.0503,-0.1610043 0.0854,-0.3286874 0.0854,-0.5065884 0,-0.3822616 -0.128156,-0.7309813 -0.339922,-1.0148431 0.459139,-0.7437673 1.188057,-1.2957452 2.054604,-1.5197652 V 4.1743879 M 6.9862372,7.1755908 C 6.173802,8.138787 5.6851553,9.38169 5.6851553,10.743373 c 0,3.07238 2.4854307,5.54581 5.5727167,5.54581 1.36784,0 2.61568,-0.486764 3.583418,-1.2948 -1.020672,1.209625 -2.551332,1.978028 -4.269961,1.978028 -3.0872862,0 -5.5727161,-2.47343 -5.5727161,-5.54581 0,-1.7106949 0.7716789,-3.2352661 1.9876243,-4.2510102"
+ class="ColorScheme-Text"
+ id="path1" />
+</svg>
diff --git a/linux/home/.config/ags/assets/chat-bubbles-symbolic.svg b/linux/home/.config/ags/assets/chat-bubbles-symbolic.svg
new file mode 100644
index 0000000..fdee0b3
--- /dev/null
+++ b/linux/home/.config/ags/assets/chat-bubbles-symbolic.svg
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
+ <path d="m 14 3.175781 v 3.824219 c 0 2.179688 -1.820312 4 -4 4 h -3.585938 l -2 2 h 5.585938 l 3 3 v -3 c 1.644531 0 3 -1.355469 3 -3 v -4 c 0 -1.292969 -0.839844 -2.40625 -2 -2.824219 z m 0 0" fill-opacity="0.34902"/>
+ <path d="m 3 0 c -1.644531 0 -3 1.355469 -3 3 v 4 c 0 1.644531 1.355469 3 3 3 v 3 l 3 -3 h 4 c 1.644531 0 3 -1.355469 3 -3 v -4 c 0 -1.644531 -1.355469 -3 -3 -3 z m 0 0"/>
+</svg>
diff --git a/linux/home/.config/ags/assets/controller-symbolic.svg b/linux/home/.config/ags/assets/controller-symbolic.svg
new file mode 100644
index 0000000..98bf5d6
--- /dev/null
+++ b/linux/home/.config/ags/assets/controller-symbolic.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
+ <path d="m 3.785156 2.03125 c -0.242187 0 -0.523437 0.066406 -0.804687 0.21875 c -1.039063 0.546875 -1.992188 2.335938 -2.511719 4.65625 c -0.4414062 1.972656 -0.605469 4.664062 -0.339844 5.75 c 0.226563 0.933594 0.625 1.34375 1.332032 1.34375 c 1.042968 -0.019531 2.359374 -1.183594 3.191406 -2.75 c 0.601562 -0.867188 2 -1.261719 3.347656 -1.21875 c 1.347656 -0.046875 2.746094 0.351562 3.347656 1.21875 c 0.832032 1.566406 2.148438 2.730469 3.191406 2.75 c 0.707032 0 1.105469 -0.410156 1.332032 -1.34375 c 0.265625 -1.085938 0.101562 -3.777344 -0.339844 -5.75 c -0.519531 -2.320312 -1.472656 -4.109375 -2.511719 -4.65625 c -0.566406 -0.304688 -1.039062 -0.296875 -1.453125 0 c -0.527344 0.375 -1.628906 0.78125 -3.566406 0.78125 c -1.9375 0.003906 -3.039062 -0.40625 -3.566406 -0.78125 c -0.207032 -0.148438 -0.40625 -0.21875 -0.648438 -0.21875 z m 0.246094 3 h 0.992188 v 1 h 0.992187 v 1 h -0.992187 v 1 h -0.992188 v -1 h -0.992188 v -1 h 0.992188 z m 7.441406 0 c 0.273438 0 0.496094 0.222656 0.496094 0.5 s -0.222656 0.5 -0.496094 0.5 c -0.273437 0 -0.496094 -0.222656 -0.496094 -0.5 s 0.222657 -0.5 0.496094 -0.5 z m -0.992187 1 c 0.273437 0 0.496093 0.222656 0.496093 0.5 s -0.222656 0.5 -0.496093 0.5 c -0.273438 0 -0.496094 -0.222656 -0.496094 -0.5 s 0.222656 -0.5 0.496094 -0.5 z m 1.984375 0 c 0.273437 0 0.496094 0.222656 0.496094 0.5 s -0.222657 0.5 -0.496094 0.5 c -0.273438 0 -0.496094 -0.222656 -0.496094 -0.5 s 0.222656 -0.5 0.496094 -0.5 z m -0.992188 1 c 0.273438 0 0.496094 0.222656 0.496094 0.5 s -0.222656 0.5 -0.496094 0.5 c -0.273437 0 -0.496094 -0.222656 -0.496094 -0.5 s 0.222657 -0.5 0.496094 -0.5 z m 0 0"/>
+</svg>
diff --git a/linux/home/.config/ags/assets/controls-symbolic.svg b/linux/home/.config/ags/assets/controls-symbolic.svg
new file mode 100644
index 0000000..7df5663
--- /dev/null
+++ b/linux/home/.config/ags/assets/controls-symbolic.svg
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
+ <path d="m 4.550781 1 c -1.9375 0 -3.5 1.5625 -3.5 3.5 s 1.5625 3.5 3.5 3.5 h 7 c 1.941407 0 3.5 -1.5625 3.5 -3.5 s -1.558593 -3.5 -3.5 -3.5 z m 7 1 c 1.386719 0 2.5 1.113281 2.5 2.5 c 0 1.382812 -1.113281 2.5 -2.5 2.5 c -1.382812 0 -2.5 -1.117188 -2.5 -2.5 c 0 -1.386719 1.117188 -2.5 2.5 -2.5 z m 0 0"/>
+ <path d="m 4.550781 9 c -1.9375 0 -3.5 1.5625 -3.5 3.5 s 1.5625 3.5 3.5 3.5 h 7 c 1.941407 0 3.5 -1.5625 3.5 -3.5 s -1.558593 -3.5 -3.5 -3.5 z m 0 1 c 1.386719 0 2.5 1.113281 2.5 2.5 c 0 1.382812 -1.113281 2.5 -2.5 2.5 c -1.382812 0 -2.5 -1.117188 -2.5 -2.5 c 0 -1.386719 1.117188 -2.5 2.5 -2.5 z m 0 0" fill-opacity="0.34902"/>
+</svg>
diff --git a/linux/home/.config/ags/assets/dark-mode-symbolic.svg b/linux/home/.config/ags/assets/dark-mode-symbolic.svg
new file mode 100644
index 0000000..9f2e6b4
--- /dev/null
+++ b/linux/home/.config/ags/assets/dark-mode-symbolic.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
+ <path d="m 0.917969 8.003906 c 0 3.914063 3.164062 7.078125 7.078125 7.078125 c 3.605468 -0.007812 6.617187 -2.703125 7.023437 -6.285156 c 0.042969 -0.378906 -0.136719 -0.75 -0.457031 -0.957031 c -0.324219 -0.203125 -0.738281 -0.207032 -1.0625 -0.003906 c -0.609375 0.375 -1.316406 0.578124 -2.03125 0.578124 c -2.140625 0 -3.882812 -1.742187 -3.882812 -3.882812 c 0 -0.714844 0.203124 -1.421875 0.578124 -2.03125 c 0.203126 -0.324219 0.199219 -0.738281 -0.003906 -1.0625 c -0.207031 -0.320312 -0.578125 -0.5 -0.957031 -0.457031 c -3.582031 0.40625 -6.277344 3.417969 -6.285156 7.023437 z m 4.667969 -3.472656 c 0 3.253906 2.628906 5.882812 5.886718 5.882812 c 1.085938 0 2.152344 -0.304687 3.078125 -0.878906 l -1.519531 -0.960937 c -0.289062 2.554687 -2.464844 4.503906 -5.035156 4.507812 c -2.796875 0 -5.078125 -2.28125 -5.078125 -5.078125 c 0.003906 -2.570312 1.953125 -4.746094 4.507812 -5.035156 l -0.960937 -1.519531 c -0.574219 0.925781 -0.875 1.992187 -0.878906 3.082031 z m 0 0"/>
+</svg>
diff --git a/linux/home/.config/ags/assets/float.svg b/linux/home/.config/ags/assets/float.svg
new file mode 100644
index 0000000..dc8078a
--- /dev/null
+++ b/linux/home/.config/ags/assets/float.svg
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ viewBox="0 0 16 16"
+ version="1.1"
+ id="svg2"
+ sodipodi:docname="float.svg"
+ inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <sodipodi:namedview
+ id="namedview2"
+ pagecolor="#ffffff"
+ bordercolor="#000000"
+ borderopacity="0.25"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:zoom="39.25"
+ inkscape:cx="8"
+ inkscape:cy="8"
+ inkscape:current-layer="svg2" />
+ <defs
+ id="defs1">
+ <style
+ type="text/css"
+ id="current-color-scheme">.ColorScheme-Text { color: #fcfcfc; } </style>
+ </defs>
+ <path
+ class="ColorScheme-Text"
+ d="M 7.8252389,2.2944865 A 5.6426155,5.4275975 0 0 0 2.1826234,7.722084 5.6426155,5.4275975 0 0 0 7.8252389,13.149682 5.6426155,5.4275975 0 0 0 13.467855,7.722084 5.6426155,5.4275975 0 0 0 7.8252389,2.2944865 m 0,0.9045996 A 4.7021795,4.5229979 0 0 1 12.527419,7.722084 4.7021795,4.5229979 0 0 1 7.8252389,12.245082 4.7021795,4.5229979 0 0 1 3.1230593,7.722084 4.7021795,4.5229979 0 0 1 7.8252389,3.1990861"
+ fill="currentColor"
+ id="path1"
+ style="stroke-width:0.922343" />
+ <path
+ class="ColorScheme-Text"
+ d="M 12.52741,7.7220642 A 4.7021795,4.5229979 0 0 1 7.8252308,12.245062 4.7021795,4.5229979 0 0 1 3.1230512,7.7220642 4.7021795,4.5229979 0 0 1 7.8252308,3.1990663 4.7021795,4.5229979 0 0 1 12.52741,7.7220642 Z"
+ fill="currentColor"
+ fill-opacity="0.5"
+ id="path2"
+ style="stroke-width:0.922343" />
+</svg>
diff --git a/linux/home/.config/ags/assets/fullscreen.svg b/linux/home/.config/ags/assets/fullscreen.svg
new file mode 100644
index 0000000..e9ab0b8
--- /dev/null
+++ b/linux/home/.config/ags/assets/fullscreen.svg
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ width="24"
+ height="24"
+ version="1.1"
+ id="svg1"
+ sodipodi:docname="fullscreen.svg"
+ inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <sodipodi:namedview
+ id="namedview1"
+ pagecolor="#ffffff"
+ bordercolor="#000000"
+ borderopacity="0.25"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:zoom="19.666667"
+ inkscape:cx="12"
+ inkscape:cy="12"
+ inkscape:current-layer="svg1" />
+ <defs
+ id="defs1">
+ <style
+ id="current-color-scheme"
+ type="text/css">
+ .ColorScheme-Text { color:#dfdfdf; } .ColorScheme-Highlight { color:#4285f4; } .ColorScheme-NeutralText { color:#ff9800; } .ColorScheme-PositiveText { color:#4caf50; } .ColorScheme-NegativeText { color:#f44336; }
+ </style>
+ </defs>
+ <g
+ transform="matrix(0.94763804,0,0,0.95290155,4.4188957,4.3767876)"
+ id="g1">
+ <path
+ style="fill:currentColor"
+ class="ColorScheme-Text"
+ d="M 4,10 1,8 4,6 Z m 8,0 3,-2 -3,-2 z m -4,5 2,-3 H 6 Z M 8,1 10,4 H 6 Z m 7,6 h 1 V 9 H 15 Z M 0,7 H 1 V 9 H 0 Z m 7,8 h 2 v 1 H 7 Z M 7,0 H 9 V 1 H 7 Z M 0,16 v -5 h 1 v 4 h 4 v 1 z m 16,0 v -4 h -1 v 3 h -4 v 1 z M 16,0 V 5 H 15 V 1 H 11 V 0 Z M 0,0 V 5 H 1 V 1 H 5 V 0 Z"
+ id="path1" />
+ </g>
+</svg>
diff --git a/linux/home/.config/ags/assets/hourglass-symbolic.svg b/linux/home/.config/ags/assets/hourglass-symbolic.svg
new file mode 100644
index 0000000..aa4f97c
--- /dev/null
+++ b/linux/home/.config/ags/assets/hourglass-symbolic.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
+ <path d="m 5 0 c -0.96875 0 -2 1.050781 -2 2 v 2.988281 c 0 0.429688 0.222656 0.675781 0.554688 1.007813 l 2.023437 2.003906 l -2.007813 1.992188 c -0.367187 0.363281 -0.570312 0.6875 -0.570312 1 v 3.007812 c 0 1.011719 0.988281 2 2 2 h 6 c 1.007812 0 2 -1.011719 2 -2.003906 v -3.003906 c 0 -0.3125 -0.222656 -0.628907 -0.570312 -0.976563 l -2.015626 -2.015625 l 1.988282 -1.988281 c 0.261718 -0.261719 0.585937 -0.6875 0.597656 -1.015625 v -2.996094 c 0 -1.003906 -1.007812 -2 -2 -2 z m 6 4 h -6 v -2 h 6 m -3.589844 7 h 1.175782 l 2.414062 2.414062 v 1.585938 h -6 v -1.613281 z m 0 0"/>
+</svg>
diff --git a/linux/home/.config/ags/assets/light-mode-symbolic.svg b/linux/home/.config/ags/assets/light-mode-symbolic.svg
new file mode 100644
index 0000000..d5fb271
--- /dev/null
+++ b/linux/home/.config/ags/assets/light-mode-symbolic.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
+ <path d="m 8 0 c -0.554688 0 -1 0.445312 -1 1 v 1 c 0 0.554688 0.445312 1 1 1 s 1 -0.445312 1 -1 v -1 c 0 -0.554688 -0.445312 -1 -1 -1 z m -4.996094 2.003906 c -0.253906 0 -0.507812 0.097656 -0.707031 0.296875 c -0.390625 0.390625 -0.390625 1.019531 0 1.414063 l 0.707031 0.707031 c 0.394532 0.390625 1.023438 0.390625 1.414063 0 c 0.394531 -0.394531 0.394531 -1.023437 0 -1.414063 l -0.707031 -0.707031 c -0.195313 -0.199219 -0.449219 -0.296875 -0.707032 -0.296875 z m 9.988282 0 c -0.253907 0 -0.507813 0.097656 -0.707032 0.296875 l -0.707031 0.707031 c -0.390625 0.390626 -0.390625 1.019532 0 1.414063 c 0.394531 0.390625 1.023437 0.390625 1.414063 0 l 0.707031 -0.707031 c 0.394531 -0.394532 0.394531 -1.023438 0 -1.414063 c -0.195313 -0.199219 -0.449219 -0.296875 -0.707031 -0.296875 z m -4.992188 1.996094 c -2.210938 0 -4 1.789062 -4 4 s 1.789062 4 4 4 s 4 -1.789062 4 -4 s -1.789062 -4 -4 -4 z m 0 2 c 1.105469 0 2 0.894531 2 2 s -0.894531 2 -2 2 s -2 -0.894531 -2 -2 s 0.894531 -2 2 -2 z m -7 1 c -0.554688 0 -1 0.445312 -1 1 s 0.445312 1 1 1 h 1 c 0.554688 0 1 -0.445312 1 -1 s -0.445312 -1 -1 -1 z m 13 0 c -0.554688 0 -1 0.445312 -1 1 s 0.445312 1 1 1 h 1 c 0.554688 0 1 -0.445312 1 -1 s -0.445312 -1 -1 -1 z m -10.335938 4.289062 c -0.238281 0.007813 -0.472656 0.105469 -0.660156 0.292969 l -0.707031 0.707031 c -0.390625 0.390626 -0.390625 1.019532 0 1.414063 c 0.394531 0.390625 1.023437 0.390625 1.414063 0 l 0.707031 -0.707031 c 0.394531 -0.394532 0.394531 -1.023438 0 -1.414063 c -0.207031 -0.210937 -0.484375 -0.308593 -0.753907 -0.292969 z m 8.574219 0 c -0.238281 0.007813 -0.472656 0.105469 -0.660156 0.292969 c -0.390625 0.390625 -0.390625 1.019531 0 1.414063 l 0.707031 0.707031 c 0.394532 0.390625 1.023438 0.390625 1.414063 0 c 0.394531 -0.394531 0.394531 -1.023437 0 -1.414063 l -0.707031 -0.707031 c -0.207032 -0.210937 -0.484376 -0.308593 -0.753907 -0.292969 z m -4.292969 1.710938 c -0.527343 0.027344 -0.945312 0.464844 -0.945312 1 v 1 c 0 0.554688 0.445312 1 1 1 s 1 -0.445312 1 -1 v -1 c 0 -0.554688 -0.445312 -1 -1 -1 c -0.015625 0 -0.035156 0 -0.050781 0 z m 0 0"/>
+</svg>
diff --git a/linux/home/.config/ags/assets/mixer-symbolic.svg b/linux/home/.config/ags/assets/mixer-symbolic.svg
new file mode 100644
index 0000000..ad6cfa8
--- /dev/null
+++ b/linux/home/.config/ags/assets/mixer-symbolic.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
+ <path d="m 11.5 1 c -1.921875 0 -3.5 1.578125 -3.5 3.5 s 1.578125 3.5 3.5 3.5 s 3.5 -1.578125 3.5 -3.5 s -1.578125 -3.5 -3.5 -3.5 z m 0 2 c 0.839844 0 1.5 0.660156 1.5 1.5 s -0.660156 1.5 -1.5 1.5 s -1.5 -0.660156 -1.5 -1.5 s 0.660156 -1.5 1.5 -1.5 z m 0 0"/>
+ <path d="m 4.5 8 c -1.921875 0 -3.5 1.578125 -3.5 3.5 s 1.578125 3.5 3.5 3.5 c 1.386719 0 2.59375 -0.820312 3.15625 -2 h 5.84375 c 0.832031 0 1.5 -0.667969 1.5 -1.5 s -0.667969 -1.5 -1.5 -1.5 h -5.84375 c -0.5625 -1.179688 -1.769531 -2 -3.15625 -2 z m 0 2 c 0.839844 0 1.5 0.660156 1.5 1.5 s -0.660156 1.5 -1.5 1.5 s -1.5 -0.660156 -1.5 -1.5 s 0.660156 -1.5 1.5 -1.5 z m 0 0"/>
+ <path d="m 2.5 3 c -0.832031 0 -1.5 0.667969 -1.5 1.5 s 0.667969 1.5 1.5 1.5 h 4.769531 c -0.175781 -0.480469 -0.265625 -0.988281 -0.269531 -1.5 c 0 -0.511719 0.09375 -1.019531 0.269531 -1.5 z m 0 0" fill-opacity="0.34902"/>
+</svg>
diff --git a/linux/home/.config/ags/assets/nix-snowflake-symbolic.svg b/linux/home/.config/ags/assets/nix-snowflake-symbolic.svg
new file mode 100644
index 0000000..7bb42ed
--- /dev/null
+++ b/linux/home/.config/ags/assets/nix-snowflake-symbolic.svg
@@ -0,0 +1,155 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ width="496"
+ height="496"
+ version="1"
+ id="svg6"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs10" />
+ <g
+ id="g946"
+ transform="matrix(0.97173996,0,0,0.97173996,4.043873,36.112138)">
+ <g
+ id="layer7"
+ style="display:none"
+ transform="translate(-23.75651,-24.84972)">
+ <rect
+ transform="translate(-132.5822,958.04022)"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ id="rect5389"
+ width="1543.4283"
+ height="483.7439"
+ x="132.5822"
+ y="-957.77832" />
+ </g>
+ <g
+ id="layer6"
+ style="display:none"
+ transform="translate(-156.33871,933.1905)">
+ <rect
+ y="-958.02759"
+ x="132.65129"
+ height="484.30399"
+ width="550.41602"
+ id="rect5379"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#5c201e;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+ <rect
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#c24a46;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ id="rect5372"
+ width="501.94415"
+ height="434.30405"
+ x="156.12303"
+ y="-933.02759" />
+ <rect
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#d98d8a;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ id="rect5381"
+ width="24.939611"
+ height="24.939611"
+ x="658.02826"
+ y="-958.04022" />
+ </g>
+ <g
+ id="layer3"
+ style="display:inline;opacity:1"
+ transform="translate(37.235605,912.8581)">
+ <g
+ id="g2072"
+ transform="matrix(0.99894325,0,0,0.99894325,-36.551621,-913.90743)"
+ style="fill:#cccccc;fill-opacity:1">
+ <g
+ style="display:none;fill:#cccccc;fill-opacity:1"
+ transform="matrix(0.09048806,0,0,0.09048806,-14.15991,84.454917)"
+ id="layer1-3">
+ <rect
+ y="-2102.4253"
+ x="-1045.6049"
+ height="7145.4614"
+ width="7947.0356"
+ id="rect995"
+ style="opacity:1;fill:#cccccc;fill-opacity:1;stroke-width:10.3605" />
+ </g>
+ <g
+ transform="translate(-156.48372,537.56136)"
+ style="display:inline;opacity:1;fill:#cccccc;fill-opacity:1"
+ id="layer3-6">
+ <g
+ style="fill:#cccccc;stroke-width:11.0512;fill-opacity:1"
+ transform="matrix(0.09048806,0,0,0.09048806,142.32381,-453.10644)"
+ id="g955">
+ <g
+ transform="matrix(11.047619,0,0,11.047619,-1572.2888,9377.7107)"
+ id="g869"
+ style="fill:#cccccc;fill-opacity:1">
+ <g
+ transform="rotate(-60,226.35754,-449.37199)"
+ id="g932"
+ style="fill:#cccccc;stroke-width:11.0512;fill-opacity:1">
+ <path
+ id="path3336-6-7"
+ d="m 449.71876,-420.51322 c 40.73228,70.55837 81.46455,141.11675 122.19683,211.67512 -18.71902,0.1756 -37.43804,0.3512 -56.15706,0.5268 -10.87453,-18.9564 -21.74907,-37.9128 -32.6236,-56.8692 -10.95215,18.8551 -21.9043,37.7102 -32.85645,56.5653 -9.30079,-0.004 -18.60158,-0.007 -27.90237,-0.011 -4.76362,-8.22987 -9.52724,-16.45973 -14.29086,-24.6896 15.60349,-26.83003 31.20698,-53.66007 46.81047,-80.4901 -11.07649,-19.27523 -22.15297,-38.55047 -33.22946,-57.8257 9.35083,-16.29387 18.70167,-32.58775 28.0525,-48.88162 z"
+ style="opacity:1;fill:#cccccc;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:33.1535;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ <path
+ id="path4260-0-5"
+ d="m 309.54892,-710.38827 c 40.73228,70.55837 81.46455,141.11675 122.19683,211.67512 -18.71902,0.1756 -37.43804,0.3512 -56.15706,0.5268 -10.87453,-18.9564 -21.74907,-37.9128 -32.6236,-56.8692 -10.95215,18.8551 -21.9043,37.7102 -32.85645,56.5653 -9.30079,-0.004 -18.60158,-0.007 -27.90237,-0.011 -4.76362,-8.22987 -9.52724,-16.45973 -14.29086,-24.6896 15.60349,-26.83003 31.20698,-53.66007 46.81047,-80.4901 -11.07649,-19.2752 -22.15297,-38.5504 -33.22946,-57.8256 9.35083,-16.29391 18.70167,-32.58781 28.0525,-48.88172 z"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#cccccc;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:33.1535;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+ <use
+ x="0"
+ y="0"
+ xlink:href="#path3336-6-7"
+ id="use3439-6-3"
+ transform="rotate(60,728.23563,-692.24036)"
+ width="100%"
+ height="100%"
+ style="fill:#cccccc;fill-opacity:1;stroke-width:11.0512" />
+ <use
+ x="0"
+ y="0"
+ xlink:href="#path3336-6-7"
+ id="use3449-5-5"
+ transform="rotate(180,477.5036,-570.81898)"
+ width="100%"
+ height="100%"
+ style="fill:#cccccc;fill-opacity:1;stroke-width:11.0512" />
+ <use
+ style="display:inline;fill:#cccccc;fill-opacity:1;stroke-width:11.0512"
+ x="0"
+ y="0"
+ xlink:href="#path4260-0-5"
+ id="use4354-5-6"
+ transform="rotate(120,407.33916,-716.08356)"
+ width="100%"
+ height="100%" />
+ <use
+ style="display:inline;fill:#cccccc;fill-opacity:1;stroke-width:11.0512"
+ x="0"
+ y="0"
+ xlink:href="#path4260-0-5"
+ id="use4362-2-2"
+ transform="rotate(-120,407.28823,-715.86995)"
+ width="100%"
+ height="100%" />
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/linux/home/.config/ags/assets/nixos-symbolic.svg b/linux/home/.config/ags/assets/nixos-symbolic.svg
new file mode 100644
index 0000000..7bb42ed
--- /dev/null
+++ b/linux/home/.config/ags/assets/nixos-symbolic.svg
@@ -0,0 +1,155 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ width="496"
+ height="496"
+ version="1"
+ id="svg6"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs10" />
+ <g
+ id="g946"
+ transform="matrix(0.97173996,0,0,0.97173996,4.043873,36.112138)">
+ <g
+ id="layer7"
+ style="display:none"
+ transform="translate(-23.75651,-24.84972)">
+ <rect
+ transform="translate(-132.5822,958.04022)"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ id="rect5389"
+ width="1543.4283"
+ height="483.7439"
+ x="132.5822"
+ y="-957.77832" />
+ </g>
+ <g
+ id="layer6"
+ style="display:none"
+ transform="translate(-156.33871,933.1905)">
+ <rect
+ y="-958.02759"
+ x="132.65129"
+ height="484.30399"
+ width="550.41602"
+ id="rect5379"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#5c201e;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+ <rect
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#c24a46;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ id="rect5372"
+ width="501.94415"
+ height="434.30405"
+ x="156.12303"
+ y="-933.02759" />
+ <rect
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#d98d8a;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ id="rect5381"
+ width="24.939611"
+ height="24.939611"
+ x="658.02826"
+ y="-958.04022" />
+ </g>
+ <g
+ id="layer3"
+ style="display:inline;opacity:1"
+ transform="translate(37.235605,912.8581)">
+ <g
+ id="g2072"
+ transform="matrix(0.99894325,0,0,0.99894325,-36.551621,-913.90743)"
+ style="fill:#cccccc;fill-opacity:1">
+ <g
+ style="display:none;fill:#cccccc;fill-opacity:1"
+ transform="matrix(0.09048806,0,0,0.09048806,-14.15991,84.454917)"
+ id="layer1-3">
+ <rect
+ y="-2102.4253"
+ x="-1045.6049"
+ height="7145.4614"
+ width="7947.0356"
+ id="rect995"
+ style="opacity:1;fill:#cccccc;fill-opacity:1;stroke-width:10.3605" />
+ </g>
+ <g
+ transform="translate(-156.48372,537.56136)"
+ style="display:inline;opacity:1;fill:#cccccc;fill-opacity:1"
+ id="layer3-6">
+ <g
+ style="fill:#cccccc;stroke-width:11.0512;fill-opacity:1"
+ transform="matrix(0.09048806,0,0,0.09048806,142.32381,-453.10644)"
+ id="g955">
+ <g
+ transform="matrix(11.047619,0,0,11.047619,-1572.2888,9377.7107)"
+ id="g869"
+ style="fill:#cccccc;fill-opacity:1">
+ <g
+ transform="rotate(-60,226.35754,-449.37199)"
+ id="g932"
+ style="fill:#cccccc;stroke-width:11.0512;fill-opacity:1">
+ <path
+ id="path3336-6-7"
+ d="m 449.71876,-420.51322 c 40.73228,70.55837 81.46455,141.11675 122.19683,211.67512 -18.71902,0.1756 -37.43804,0.3512 -56.15706,0.5268 -10.87453,-18.9564 -21.74907,-37.9128 -32.6236,-56.8692 -10.95215,18.8551 -21.9043,37.7102 -32.85645,56.5653 -9.30079,-0.004 -18.60158,-0.007 -27.90237,-0.011 -4.76362,-8.22987 -9.52724,-16.45973 -14.29086,-24.6896 15.60349,-26.83003 31.20698,-53.66007 46.81047,-80.4901 -11.07649,-19.27523 -22.15297,-38.55047 -33.22946,-57.8257 9.35083,-16.29387 18.70167,-32.58775 28.0525,-48.88162 z"
+ style="opacity:1;fill:#cccccc;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:33.1535;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ <path
+ id="path4260-0-5"
+ d="m 309.54892,-710.38827 c 40.73228,70.55837 81.46455,141.11675 122.19683,211.67512 -18.71902,0.1756 -37.43804,0.3512 -56.15706,0.5268 -10.87453,-18.9564 -21.74907,-37.9128 -32.6236,-56.8692 -10.95215,18.8551 -21.9043,37.7102 -32.85645,56.5653 -9.30079,-0.004 -18.60158,-0.007 -27.90237,-0.011 -4.76362,-8.22987 -9.52724,-16.45973 -14.29086,-24.6896 15.60349,-26.83003 31.20698,-53.66007 46.81047,-80.4901 -11.07649,-19.2752 -22.15297,-38.5504 -33.22946,-57.8256 9.35083,-16.29391 18.70167,-32.58781 28.0525,-48.88172 z"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#cccccc;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:33.1535;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+ <use
+ x="0"
+ y="0"
+ xlink:href="#path3336-6-7"
+ id="use3439-6-3"
+ transform="rotate(60,728.23563,-692.24036)"
+ width="100%"
+ height="100%"
+ style="fill:#cccccc;fill-opacity:1;stroke-width:11.0512" />
+ <use
+ x="0"
+ y="0"
+ xlink:href="#path3336-6-7"
+ id="use3449-5-5"
+ transform="rotate(180,477.5036,-570.81898)"
+ width="100%"
+ height="100%"
+ style="fill:#cccccc;fill-opacity:1;stroke-width:11.0512" />
+ <use
+ style="display:inline;fill:#cccccc;fill-opacity:1;stroke-width:11.0512"
+ x="0"
+ y="0"
+ xlink:href="#path4260-0-5"
+ id="use4354-5-6"
+ transform="rotate(120,407.33916,-716.08356)"
+ width="100%"
+ height="100%" />
+ <use
+ style="display:inline;fill:#cccccc;fill-opacity:1;stroke-width:11.0512"
+ x="0"
+ y="0"
+ xlink:href="#path4260-0-5"
+ id="use4362-2-2"
+ transform="rotate(-120,407.28823,-715.86995)"
+ width="100%"
+ height="100%" />
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/linux/home/.config/ags/assets/nixos.svg b/linux/home/.config/ags/assets/nixos.svg
new file mode 100644
index 0000000..1a756ed
--- /dev/null
+++ b/linux/home/.config/ags/assets/nixos.svg
@@ -0,0 +1,277 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ width="496"
+ height="496"
+ version="1"
+ id="svg6"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs10">
+ <linearGradient
+ id="linearGradient5562">
+ <stop
+ style="stop-color:#699ad7;stop-opacity:1"
+ offset="0"
+ id="stop5564" />
+ <stop
+ id="stop5566"
+ offset="0.24345198"
+ style="stop-color:#7eb1dd;stop-opacity:1" />
+ <stop
+ style="stop-color:#7ebae4;stop-opacity:1"
+ offset="1"
+ id="stop5568" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5053">
+ <stop
+ style="stop-color:#415e9a;stop-opacity:1"
+ offset="0"
+ id="stop5055" />
+ <stop
+ id="stop5057"
+ offset="0.23168644"
+ style="stop-color:#4a6baf;stop-opacity:1" />
+ <stop
+ style="stop-color:#5277c3;stop-opacity:1"
+ offset="1"
+ id="stop5059" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5960">
+ <stop
+ id="stop5962"
+ offset="0"
+ style="stop-color:#637ddf;stop-opacity:1" />
+ <stop
+ style="stop-color:#649afa;stop-opacity:1"
+ offset="0.23168644"
+ id="stop5964" />
+ <stop
+ id="stop5966"
+ offset="1"
+ style="stop-color:#719efa;stop-opacity:1" />
+ </linearGradient>
+ <linearGradient
+ y2="515.97058"
+ x2="282.26105"
+ y1="338.62445"
+ x1="213.95642"
+ gradientTransform="translate(983.36076,293.12113)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient5855"
+ xlink:href="#linearGradient5960" />
+ <linearGradient
+ xlink:href="#linearGradient5562"
+ id="linearGradient4328"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(70.650339,-1055.1511)"
+ x1="200.59668"
+ y1="351.41116"
+ x2="290.08701"
+ y2="506.18814" />
+ <linearGradient
+ xlink:href="#linearGradient5053"
+ id="linearGradient4330"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(864.69589,-1491.3405)"
+ x1="-584.19934"
+ y1="782.33563"
+ x2="-496.29703"
+ y2="937.71399" />
+ </defs>
+ <g
+ id="g946"
+ transform="matrix(0.97173996,0,0,0.97173996,4.043873,36.112138)">
+ <g
+ id="layer7"
+ style="display:none"
+ transform="translate(-23.75651,-24.84972)">
+ <rect
+ transform="translate(-132.5822,958.04022)"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ id="rect5389"
+ width="1543.4283"
+ height="483.7439"
+ x="132.5822"
+ y="-957.77832" />
+ </g>
+ <g
+ id="layer6"
+ style="display:none"
+ transform="translate(-156.33871,933.1905)">
+ <rect
+ y="-958.02759"
+ x="132.65129"
+ height="484.30399"
+ width="550.41602"
+ id="rect5379"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#5c201e;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+ <rect
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#c24a46;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ id="rect5372"
+ width="501.94415"
+ height="434.30405"
+ x="156.12303"
+ y="-933.02759" />
+ <rect
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#d98d8a;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ id="rect5381"
+ width="24.939611"
+ height="24.939611"
+ x="658.02826"
+ y="-958.04022" />
+ </g>
+ <g
+ id="layer1"
+ style="display:inline"
+ transform="translate(-156.33871,933.1905)">
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#5277c3;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 309.40365,-710.2521 c 40.73228,70.55837 81.46455,141.11673 122.19683,211.6751 -18.71902,0.1756 -37.43804,0.3512 -56.15706,0.5268 -10.87453,-18.9564 -21.74907,-37.9128 -32.6236,-56.8692 -10.95215,18.8551 -21.9043,37.7102 -32.85645,56.5653 -9.30079,-0.004 -18.60158,-0.007 -27.90237,-0.011 -4.76362,-8.22987 -9.52724,-16.45973 -14.29086,-24.6896 15.60349,-26.83007 31.20698,-53.66013 46.81047,-80.4902 -11.07649,-19.2752 -22.15297,-38.5504 -33.22946,-57.8256 9.35083,-16.29387 18.70167,-32.58773 28.0525,-48.8816 z"
+ id="path4861" />
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#7ebae4;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 353.50926,-797.4433 c -40.73919,70.55437 -81.47837,141.10873 -122.21756,211.6631 -9.51159,-16.12333 -19.02318,-32.24667 -28.53477,-48.37 10.97946,-18.89583 21.95893,-37.79167 32.93839,-56.6875 -21.80507,-0.0573 -43.61014,-0.1146 -65.41521,-0.1719 -4.64713,-8.0566 -9.29427,-16.1132 -13.9414,-24.1698 4.74546,-8.24033 9.49091,-16.48067 14.23637,-24.721 31.03726,0.098 62.07451,0.19593 93.11177,0.2939 11.15457,-19.2301 22.30914,-38.4602 33.46371,-57.6903 18.78623,-0.0488 37.57247,-0.0977 56.3587,-0.1465 z"
+ id="use4863" />
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#7ebae4;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 362.88537,-628.243 c 81.47146,0.004 162.94293,0.008 244.41439,0.012 -9.20743,16.29893 -18.41486,32.59787 -27.62229,48.8968 -21.854,-0.0606 -43.70799,-0.12113 -65.56199,-0.1817 10.85292,18.91237 21.70584,37.82473 32.55876,56.7371 -4.65366,8.05283 -9.30732,16.10567 -13.96098,24.1585 -9.50907,0.0107 -19.01815,0.0213 -28.52722,0.032 -15.43377,-26.92803 -30.86753,-53.85607 -46.3013,-80.7841 -22.23106,-0.0451 -44.46211,-0.0902 -66.69317,-0.1353 -9.4354,-16.2451 -18.8708,-32.4902 -28.3062,-48.7353 z"
+ id="use4865" />
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#7ebae4;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 505.14318,-720.9886 c -40.73228,-70.55837 -81.46455,-141.11673 -122.19683,-211.6751 18.71902,-0.1756 37.43804,-0.3512 56.15706,-0.5268 10.87453,18.9564 21.74907,37.9128 32.6236,56.8692 10.95215,-18.8551 21.9043,-37.7102 32.85645,-56.5653 9.30079,0.004 18.60158,0.007 27.90237,0.011 4.76362,8.22987 9.52724,16.45973 14.29086,24.6896 -15.60349,26.83007 -31.20698,53.66013 -46.81047,80.4902 11.07649,19.2752 22.15297,38.5504 33.22946,57.8256 -9.35083,16.29387 -18.70167,32.58773 -28.0525,48.8816 z"
+ id="use4867" />
+ <path
+ id="path4873"
+ d="m 309.40365,-710.2521 c 40.73228,70.55837 81.46455,141.11673 122.19683,211.6751 -18.71902,0.1756 -37.43804,0.3512 -56.15706,0.5268 -10.87453,-18.9564 -21.74907,-37.9128 -32.6236,-56.8692 -10.95215,18.8551 -21.9043,37.7102 -32.85645,56.5653 -9.30079,-0.004 -18.60158,-0.007 -27.90237,-0.011 -4.76362,-8.22987 -9.52724,-16.45973 -14.29086,-24.6896 15.60349,-26.83007 31.20698,-53.66013 46.81047,-80.4902 -11.07649,-19.2752 -22.15297,-38.5504 -33.22946,-57.8256 9.35083,-16.29387 18.70167,-32.58773 28.0525,-48.8816 z"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#5277c3;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+ <path
+ id="use4875"
+ d="m 451.3364,-803.53264 c -81.47147,-0.004 -162.94293,-0.008 -244.4144,-0.012 9.20743,-16.29895 18.41486,-32.5979 27.62229,-48.89685 21.854,0.0606 43.70799,0.12117 65.56199,0.18175 -10.85292,-18.91239 -21.70583,-37.82478 -32.55875,-56.73717 4.65366,-8.05284 9.30731,-16.10567 13.96097,-24.15851 9.50907,-0.0105 19.01815,-0.021 28.52722,-0.0315 15.43377,26.92805 30.86753,53.85609 46.3013,80.78414 22.23106,0.0451 44.46211,0.0902 66.69317,0.13524 9.4354,16.24497 18.87081,32.48993 28.30621,48.7349 z"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#5277c3;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+ <path
+ id="use4877"
+ d="m 460.87178,-633.8425 c 40.73919,-70.55435 81.47838,-141.10869 122.21757,-211.66304 9.51159,16.12334 19.02318,32.24669 28.53477,48.37003 -10.97946,18.89584 -21.95893,37.79167 -32.93839,56.68751 21.80507,0.0573 43.61013,0.11453 65.4152,0.1718 4.64713,8.0566 9.29427,16.1132 13.9414,24.1698 -4.74545,8.24037 -9.49091,16.48073 -14.23636,24.7211 -31.03726,-0.098 -62.07451,-0.196 -93.11177,-0.294 -11.15457,19.23013 -22.30914,38.46027 -33.46371,57.6904 -18.78624,0.0488 -37.57247,0.0976 -56.35871,0.1464 z"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#5277c3;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+ <g
+ id="layer2"
+ style="display:none"
+ transform="translate(72.039038,-1799.4476)">
+ <path
+ d="M 460.60629,594.72881 209.74183,594.7288 84.309616,377.4738 209.74185,160.21882 l 250.86446,1e-5 125.43222,217.255 z"
+ id="path6032"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.236;fill:#4e4d52;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" />
+ <path
+ transform="translate(0,-308.26772)"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#4e4d52;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="path5875"
+ d="m 385.59154,773.06721 -100.83495,0 -50.41747,-87.32564 50.41748,-87.32563 100.83495,10e-6 50.41748,87.32563 z" />
+ <path
+ id="path5851"
+ d="m 1216.5591,630.26623 c 41.0182,76.04675 82.0363,152.09355 123.0545,228.14035 -14.2269,-0.4205 -28.4538,-0.8411 -42.6807,-1.2616 -14.4941,-26.5908 -28.9882,-53.1817 -43.4823,-79.7725 -13.2169,26.7756 -26.4337,53.5511 -39.6506,80.3267 -10.8958,-6.5995 -21.7917,-13.1989 -32.6875,-19.7984 17.8246,-33.4283 35.6491,-66.8565 53.4737,-100.2848 -12.3719,-24.6298 -24.7438,-49.2597 -37.1157,-73.88955 6.3629,-11.1534 12.7257,-22.3068 19.0886,-33.4602 z"
+ style="fill:url(#linearGradient5855);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <rect
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.415;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#c53a3a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ id="rect5884"
+ width="48.834862"
+ height="226.22897"
+ x="-34.74221"
+ y="446.17056"
+ transform="rotate(-30)" />
+ <path
+ transform="translate(0,-308.26772)"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.509;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ id="path3428"
+ d="m 251.98568,878.63831 -14.02447,24.29109 h -28.04894 l -14.02447,-24.29109 14.02447,-24.2911 h 28.04894 z" />
+ <use
+ x="0"
+ y="0"
+ xlink:href="#rect5884"
+ id="use4252"
+ transform="rotate(60,268.29786,489.4515)"
+ width="100%"
+ height="100%" />
+ <rect
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:0.650794;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ id="rect4254"
+ width="5.3947482"
+ height="115.12564"
+ x="545.71014"
+ y="467.07007"
+ transform="rotate(30,575.23539,-154.13386)" />
+ </g>
+ </g>
+ <g
+ id="layer3"
+ style="display:inline;opacity:1"
+ transform="translate(-156.33871,933.1905)">
+ <path
+ id="path3336-6"
+ d="m 309.54892,-710.38827 c 40.73228,70.55837 81.46455,141.11675 122.19683,211.67512 -18.71902,0.1756 -37.43804,0.3512 -56.15706,0.5268 -10.87453,-18.9564 -21.74907,-37.9128 -32.6236,-56.8692 -10.95215,18.8551 -21.9043,37.7102 -32.85645,56.5653 -9.30079,-0.004 -18.60158,-0.007 -27.90237,-0.011 -4.76362,-8.22987 -9.52724,-16.45973 -14.29086,-24.6896 15.60349,-26.83003 31.20698,-53.66007 46.81047,-80.4901 -11.07649,-19.27523 -22.15297,-38.55047 -33.22946,-57.8257 9.35083,-16.29387 18.70167,-32.58775 28.0525,-48.88162 z"
+ style="opacity:1;fill:url(#linearGradient4328);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <use
+ height="100%"
+ width="100%"
+ transform="rotate(60,407.11155,-715.78724)"
+ id="use3439-6"
+ xlink:href="#path3336-6"
+ y="0"
+ x="0" />
+ <use
+ height="100%"
+ width="100%"
+ transform="rotate(-60,407.31177,-715.70016)"
+ id="use3445-0"
+ xlink:href="#path3336-6"
+ y="0"
+ x="0" />
+ <use
+ height="100%"
+ width="100%"
+ transform="rotate(180,407.41868,-715.7565)"
+ id="use3449-5"
+ xlink:href="#path3336-6"
+ y="0"
+ x="0" />
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:url(#linearGradient4330);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 309.54892,-710.38827 c 40.73228,70.55837 81.46455,141.11675 122.19683,211.67512 -18.71902,0.1756 -37.43804,0.3512 -56.15706,0.5268 -10.87453,-18.9564 -21.74907,-37.9128 -32.6236,-56.8692 -10.95215,18.8551 -21.9043,37.7102 -32.85645,56.5653 -10.54113,-2.26829 -26.58606,6.01638 -31.9377,-7.5219 -4.39393,-6.91787 -12.57856,-15.53043 -6.85074,-23.97221 8.26178,-12.05394 14.90093,-25.28023 22.52611,-37.79439 6.95986,-11.9674 13.91971,-23.9348 20.87957,-35.9022 -11.07649,-19.2752 -22.15297,-38.5504 -33.22946,-57.8256 9.35083,-16.29391 18.70167,-32.58781 28.0525,-48.88172 z"
+ id="path4260-0" />
+ <use
+ height="100%"
+ width="100%"
+ transform="rotate(120,407.33916,-716.08356)"
+ id="use4354-5"
+ xlink:href="#path4260-0"
+ y="0"
+ x="0"
+ style="display:inline" />
+ <use
+ height="100%"
+ width="100%"
+ transform="rotate(-120,407.28823,-715.86995)"
+ id="use4362-2"
+ xlink:href="#path4260-0"
+ y="0"
+ x="0"
+ style="display:inline" />
+ </g>
+ </g>
+</svg>
diff --git a/linux/home/.config/ags/assets/osk.svg b/linux/home/.config/ags/assets/osk.svg
new file mode 100644
index 0000000..511420a
--- /dev/null
+++ b/linux/home/.config/ags/assets/osk.svg
@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ width="24"
+ height="24"
+ version="1"
+ id="svg20"
+ sodipodi:docname="osk.svg"
+ inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <defs
+ id="defs20" />
+ <sodipodi:namedview
+ id="namedview20"
+ pagecolor="#ffffff"
+ bordercolor="#000000"
+ borderopacity="0.25"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:zoom="26.166667"
+ inkscape:cx="12"
+ inkscape:cy="12"
+ inkscape:current-layer="svg20" />
+ <rect
+ style="fill:#4f4f4f;stroke-width:0.871697"
+ width="11.938859"
+ height="17.820761"
+ x="-18.006235"
+ y="-20.910379"
+ rx="0.85277563"
+ ry="0.891038"
+ transform="matrix(0,-1,-1,0,0,0)"
+ id="rect1" />
+ <path
+ style="opacity:0.2;stroke-width:0.871697"
+ d="m 9.3268859,13.31597 a 0.89103802,0.85277564 0 0 1 -0.891038,0.852775 0.89103802,0.85277564 0 0 1 -0.891038,-0.852775 0.89103802,0.85277564 0 0 1 0.891038,-0.852776 0.89103802,0.85277564 0 0 1 0.891038,0.852776 z"
+ id="path1" />
+ <path
+ style="opacity:0.2;stroke-width:0.871697"
+ d="M 12,13.31597 A 0.89103802,0.85277564 0 0 1 11.108962,14.168745 0.89103802,0.85277564 0 0 1 10.217924,13.31597 0.89103802,0.85277564 0 0 1 11.108962,12.463194 0.89103802,0.85277564 0 0 1 12,13.31597 Z"
+ id="path2" />
+ <path
+ style="opacity:0.2;stroke-width:0.871697"
+ d="m 14.673114,13.31597 a 0.89103802,0.85277564 0 0 1 -0.891038,0.852775 0.89103802,0.85277564 0 0 1 -0.891038,-0.852775 0.89103802,0.85277564 0 0 1 0.891038,-0.852776 0.89103802,0.85277564 0 0 1 0.891038,0.852776 z"
+ id="path3" />
+ <path
+ style="opacity:0.2;stroke-width:0.871697"
+ d="M 17.346228,13.31597 A 0.89103802,0.85277564 0 0 1 16.45519,14.168745 0.89103802,0.85277564 0 0 1 15.564152,13.31597 0.89103802,0.85277564 0 0 1 16.45519,12.463194 0.89103802,0.85277564 0 0 1 17.346228,13.31597 Z"
+ id="path4" />
+ <rect
+ style="opacity:0.2;stroke-width:0.871697"
+ width="12.474532"
+ height="1.7055513"
+ x="5.7627339"
+ y="15.874296"
+ rx="0.41611475"
+ ry="0.42638782"
+ id="rect4" />
+ <rect
+ style="fill:#e4e4e4;stroke-width:0.871697"
+ width="12.474532"
+ height="1.7055513"
+ x="5.7627339"
+ y="15.447908"
+ rx="0.41611475"
+ ry="0.42638782"
+ id="rect5" />
+ <path
+ style="opacity:0.2;stroke-width:0.871697"
+ d="M 15.564152,9.904867 A 0.89103802,0.85277564 0 0 1 14.673114,10.757643 0.89103802,0.85277564 0 0 1 13.782076,9.904867 0.89103802,0.85277564 0 0 1 14.673114,9.0520913 0.89103802,0.85277564 0 0 1 15.564152,9.904867 Z"
+ id="path5" />
+ <path
+ style="opacity:0.2;stroke-width:0.871697"
+ d="M 7.5448099,9.904867 A 0.89103802,0.85277564 0 0 1 6.6537719,10.757643 0.89103802,0.85277564 0 0 1 5.7627339,9.904867 0.89103802,0.85277564 0 0 1 6.6537719,9.0520913 0.89103802,0.85277564 0 0 1 7.5448099,9.904867 Z"
+ id="path6" />
+ <path
+ style="opacity:0.2;stroke-width:0.871697"
+ d="M 10.217924,9.904867 A 0.89103802,0.85277564 0 0 1 9.3268859,10.757643 0.89103802,0.85277564 0 0 1 8.4358479,9.904867 0.89103802,0.85277564 0 0 1 9.3268859,9.0520913 0.89103802,0.85277564 0 0 1 10.217924,9.904867 Z"
+ id="path7" />
+ <path
+ style="opacity:0.2;stroke-width:0.871697"
+ d="M 12.891038,9.904867 A 0.89103802,0.85277564 0 0 1 12,10.757643 0.89103802,0.85277564 0 0 1 11.108962,9.904867 0.89103802,0.85277564 0 0 1 12,9.0520913 0.89103802,0.85277564 0 0 1 12.891038,9.904867 Z"
+ id="path8" />
+ <path
+ style="opacity:0.2;stroke-width:0.871697"
+ d="M 18.237266,9.904867 A 0.89103802,0.85277564 0 0 1 17.346228,10.757643 0.89103802,0.85277564 0 0 1 16.45519,9.904867 0.89103802,0.85277564 0 0 1 17.346228,9.0520913 0.89103802,0.85277564 0 0 1 18.237266,9.904867 Z"
+ id="path9" />
+ <g
+ style="fill:#e4e4e4"
+ id="g18"
+ transform="matrix(0.89103802,0,0,0.85277564,1.3075438,1.8034984)">
+ <path
+ d="M 9,13 A 1,1 0 0 1 8,14 1,1 0 0 1 7,13 1,1 0 0 1 8,12 1,1 0 0 1 9,13 Z"
+ id="path10" />
+ <path
+ d="m 12,13 a 1,1 0 0 1 -1,1 1,1 0 0 1 -1,-1 1,1 0 0 1 1,-1 1,1 0 0 1 1,1 z"
+ id="path11" />
+ <path
+ d="m 15,13 a 1,1 0 0 1 -1,1 1,1 0 0 1 -1,-1 1,1 0 0 1 1,-1 1,1 0 0 1 1,1 z"
+ id="path12" />
+ <path
+ d="m 18,13 a 1,1 0 0 1 -1,1 1,1 0 0 1 -1,-1 1,1 0 0 1 1,-1 1,1 0 0 1 1,1 z"
+ id="path13" />
+ <path
+ d="m 16,9 a 1,1 0 0 1 -1,1 1,1 0 0 1 -1,-1 1,1 0 0 1 1,-1 1,1 0 0 1 1,1 z"
+ id="path14" />
+ <path
+ d="M 7,9 A 1,1 0 0 1 6,10 1,1 0 0 1 5,9 1,1 0 0 1 6,8 1,1 0 0 1 7,9 Z"
+ id="path15" />
+ <path
+ d="M 10,9 A 1,1 0 0 1 9,10 1,1 0 0 1 8,9 1,1 0 0 1 9,8 1,1 0 0 1 10,9 Z"
+ id="path16" />
+ <path
+ d="m 13,9 a 1,1 0 0 1 -1,1 1,1 0 0 1 -1,-1 1,1 0 0 1 1,-1 1,1 0 0 1 1,1 z"
+ id="path17" />
+ <path
+ d="m 19,9 a 1,1 0 0 1 -1,1 1,1 0 0 1 -1,-1 1,1 0 0 1 1,-1 1,1 0 0 1 1,1 z"
+ id="path18" />
+ </g>
+ <path
+ style="opacity:0.1;fill:#ffffff;stroke-width:0.871697"
+ d="m 3.9806578,6.0673766 c -0.493635,0 -0.891038,0.3803379 -0.891038,0.8527756 v 0.4263879 c 0,-0.4724377 0.397403,-0.8527757 0.891038,-0.8527757 H 20.019342 c 0.493635,0 0.891038,0.380338 0.891038,0.8527757 V 6.9201522 c 0,-0.4724377 -0.397403,-0.8527756 -0.891038,-0.8527756 z"
+ id="path19" />
+ <path
+ style="opacity:0.2;stroke-width:0.871697"
+ d="m 3.0896198,17.15346 v 0.426388 c 0,0.472437 0.397403,0.852775 0.891038,0.852775 H 20.019342 c 0.493635,0 0.891038,-0.380338 0.891038,-0.852775 V 17.15346 c 0,0.472438 -0.397403,0.852776 -0.891038,0.852776 H 3.9806578 c -0.493635,0 -0.891038,-0.380338 -0.891038,-0.852776 z"
+ id="path20" />
+</svg>
diff --git a/linux/home/.config/ags/assets/pinned.svg b/linux/home/.config/ags/assets/pinned.svg
new file mode 100644
index 0000000..ce8efb3
--- /dev/null
+++ b/linux/home/.config/ags/assets/pinned.svg
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ viewBox="0 0 22 22"
+ version="1.1"
+ id="svg1"
+ sodipodi:docname="pinned.svg"
+ inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <sodipodi:namedview
+ id="namedview1"
+ pagecolor="#ffffff"
+ bordercolor="#000000"
+ borderopacity="0.25"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:zoom="21.454545"
+ inkscape:cx="11"
+ inkscape:cy="11"
+ inkscape:current-layer="svg1" />
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">.ColorScheme-Text { color: #fcfcfc; } </style>
+ </defs>
+ <path
+ style="fill:currentColor;fill-opacity:1;stroke:none;stroke-width:0.906415"
+ d="m 11,3.7453986 c -4.015418,0 -7.2480469,3.2355522 -7.2480469,7.2546014 0,4.019049 3.2326289,7.254601 7.2480469,7.254601 4.015418,0 7.248047,-3.235552 7.248047,-7.254601 0,-4.0190492 -3.232629,-7.2546014 -7.248047,-7.2546014 z m 0,0.9068251 c 3.513491,0 6.342041,2.8311083 6.342041,6.3477763 0,3.516668 -2.82855,6.347776 -6.342041,6.347776 C 7.4865093,17.347776 4.657959,14.516668 4.657959,11 4.657959,7.483332 7.4865093,4.6522237 11,4.6522237 Z m 0,4.5341259 c -1.0038533,0 -1.8120117,0.8088893 -1.8120117,1.8136504 0,1.004761 0.8081584,1.81365 1.8120117,1.81365 1.003854,0 1.812012,-0.808889 1.812012,-1.81365 0,-1.0047611 -0.808158,-1.8136504 -1.812012,-1.8136504 z m 0,0.9068254 c 0.501926,0 0.906006,0.404445 0.906006,0.906825 0,0.50238 -0.40408,0.906825 -0.906006,0.906825 -0.501926,0 -0.906006,-0.404445 -0.906006,-0.906825 0,-0.50238 0.40408,-0.906825 0.906006,-0.906825 z"
+ class="ColorScheme-Text"
+ id="path1" />
+</svg>
diff --git a/linux/home/.config/ags/assets/preferences-desktop-theme-symbolic.svg b/linux/home/.config/ags/assets/preferences-desktop-theme-symbolic.svg
new file mode 100644
index 0000000..4461454
--- /dev/null
+++ b/linux/home/.config/ags/assets/preferences-desktop-theme-symbolic.svg
@@ -0,0 +1,321 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ height="16px"
+ viewBox="0 0 16 16"
+ width="16px"
+ version="1.1"
+ id="svg3533"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <defs
+ id="defs3537" />
+ <filter
+ id="a"
+ height="1"
+ width="1"
+ x="0"
+ y="0">
+ <feColorMatrix
+ in="SourceGraphic"
+ type="matrix"
+ values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"
+ id="feColorMatrix3414" />
+ </filter>
+ <mask
+ id="b">
+ <g
+ filter="url(#a)"
+ id="g3419">
+ <image
+ height="800"
+ width="1024"
+ xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABAAAAAMgCAYAAAC9FhA7AAAABmJLR0QA/wD/AP+gvaeTAAAMr0lEQVR4nO3YsQ2AMAwAwcB8GZcBWQFEIoq/qy3b9Y8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQdi/bMl/PXorsAAADAA+ffDwAAAAD7CQAAAAAQIAAAAABAgAAAAAAAAQIAAAAABAgAAAAAECAAAAAAQIAAAAAAAAECAAAAAAQIAAAAABAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8dQMXagEYjYWofQAAAABJRU5ErkJggg=="
+ id="image3417" />
+ </g>
+ </mask>
+ <clipPath
+ id="c">
+ <path
+ d="m 0 0 h 1024 v 800 h -1024 z"
+ id="path3422" />
+ </clipPath>
+ <mask
+ id="d">
+ <g
+ filter="url(#a)"
+ id="g3427">
+ <image
+ height="800"
+ width="1024"
+ xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABAAAAAMgCAYAAAC9FhA7AAAABmJLR0QA/wD/AP+gvaeTAAAMr0lEQVR4nO3YsQ2AMAwAwUCd/SdlAFYAkYji72rLdv1jAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBx6I98+X8teguAAAA8MD59wMAAADAfgIAAAAABAgAAAAAECAAAAAAQIAAAAAAAAECAAAAAAQIAAAAABAgAAAAAECAAAAAAAABAgAAAAAECAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAX90LagEYFVKkEwAAAABJRU5ErkJggg=="
+ id="image3425" />
+ </g>
+ </mask>
+ <clipPath
+ id="e">
+ <path
+ d="m 0 0 h 1024 v 800 h -1024 z"
+ id="path3430" />
+ </clipPath>
+ <mask
+ id="f">
+ <g
+ filter="url(#a)"
+ id="g3435">
+ <image
+ height="800"
+ width="1024"
+ xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABAAAAAMgCAYAAAC9FhA7AAAABmJLR0QA/wD/AP+gvaeTAAAMr0lEQVR4nO3YsQ2AMAwAwUCd/SdlAFYAkYji72rLdv1jAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBx6I98+X8teguAAAA8MD59wMAAADAfgIAAAAABAgAAAAAECAAAAAAQIAAAAAAAAECAAAAAAQIAAAAABAgAAAAAECAAAAAAAABAgAAAAAECAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAX90LagEYFVKkEwAAAABJRU5ErkJggg=="
+ id="image3433" />
+ </g>
+ </mask>
+ <clipPath
+ id="g">
+ <path
+ d="m 0 0 h 1024 v 800 h -1024 z"
+ id="path3438" />
+ </clipPath>
+ <mask
+ id="h">
+ <g
+ filter="url(#a)"
+ id="g3443">
+ <image
+ height="800"
+ width="1024"
+ xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABAAAAAMgCAYAAAC9FhA7AAAABmJLR0QA/wD/AP+gvaeTAAAMr0lEQVR4nO3YsQ2AMAwAwUCd/SdlAFYAkYji72rLdv1jAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBx6I98+X8teguAAAA8MD59wMAAADAfgIAAAAABAgAAAAAECAAAAAAQIAAAAAAAAECAAAAAAQIAAAAABAgAAAAAECAAAAAAAABAgAAAAAECAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAX90LagEYFVKkEwAAAABJRU5ErkJggg=="
+ id="image3441" />
+ </g>
+ </mask>
+ <clipPath
+ id="i">
+ <path
+ d="m 0 0 h 1024 v 800 h -1024 z"
+ id="path3446" />
+ </clipPath>
+ <mask
+ id="j">
+ <g
+ filter="url(#a)"
+ id="g3451">
+ <image
+ height="800"
+ width="1024"
+ xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABAAAAAMgCAYAAAC9FhA7AAAABmJLR0QA/wD/AP+gvaeTAAAMr0lEQVR4nO3YsQ2AMAwAwUCd/SdlAFYAkYji72rLdv1jAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBx6I98+X8teguAAAA8MD59wMAAADAfgIAAAAABAgAAAAAECAAAAAAQIAAAAAAAAECAAAAAAQIAAAAABAgAAAAAECAAAAAAAABAgAAAAAECAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAX90LagEYFVKkEwAAAABJRU5ErkJggg=="
+ id="image3449" />
+ </g>
+ </mask>
+ <clipPath
+ id="k">
+ <path
+ d="m 0 0 h 1024 v 800 h -1024 z"
+ id="path3454" />
+ </clipPath>
+ <mask
+ id="l">
+ <g
+ filter="url(#a)"
+ id="g3459">
+ <image
+ height="800"
+ width="1024"
+ xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABAAAAAMgCAYAAAC9FhA7AAAABmJLR0QA/wD/AP+gvaeTAAAMr0lEQVR4nO3YsQ2AMAwAwUCd/SdlAFYAkYji72rLdv1jAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBx6I98+X8teguAAAA8MD59wMAAADAfgIAAAAABAgAAAAAECAAAAAAQIAAAAAAAAECAAAAAAQIAAAAABAgAAAAAECAAAAAAAABAgAAAAAECAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAX90LagEYFVKkEwAAAABJRU5ErkJggg=="
+ id="image3457" />
+ </g>
+ </mask>
+ <clipPath
+ id="m">
+ <path
+ d="m 0 0 h 1024 v 800 h -1024 z"
+ id="path3462" />
+ </clipPath>
+ <mask
+ id="n">
+ <g
+ filter="url(#a)"
+ id="g3467">
+ <image
+ height="800"
+ width="1024"
+ xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABAAAAAMgCAYAAAC9FhA7AAAABmJLR0QA/wD/AP+gvaeTAAAMr0lEQVR4nO3YsQ2AMAwAwUCd/SdlAFYAkYji72rLdv1jAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBx6I98+X8teguAAAA8MD59wMAAADAfgIAAAAABAgAAAAAECAAAAAAQIAAAAAAAAECAAAAAAQIAAAAABAgAAAAAECAAAAAAAABAgAAAAAECAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAX90LagEYFVKkEwAAAABJRU5ErkJggg=="
+ id="image3465" />
+ </g>
+ </mask>
+ <clipPath
+ id="o">
+ <path
+ d="m 0 0 h 1024 v 800 h -1024 z"
+ id="path3470" />
+ </clipPath>
+ <mask
+ id="p">
+ <g
+ filter="url(#a)"
+ id="g3475">
+ <image
+ height="800"
+ width="1024"
+ xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABAAAAAMgCAYAAAC9FhA7AAAABmJLR0QA/wD/AP+gvaeTAAAMr0lEQVR4nO3YsQ2AMAwAwcB8GZcBWQFEIoq/qy3b9Y8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQdi/bMl/PXorsAAADAA+ffDwAAAAD7CQAAAAAQIAAAAABAgAAAAAAAAQIAAAAABAgAAAAAECAAAAAAQIAAAAAAAAECAAAAAAQIAAAAABAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8dQMXagEYjYWofQAAAABJRU5ErkJggg=="
+ id="image3473" />
+ </g>
+ </mask>
+ <clipPath
+ id="q">
+ <path
+ d="m 0 0 h 1024 v 800 h -1024 z"
+ id="path3478" />
+ </clipPath>
+ <mask
+ id="r">
+ <g
+ filter="url(#a)"
+ id="g3483">
+ <image
+ height="800"
+ width="1024"
+ xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABAAAAAMgCAYAAAC9FhA7AAAABmJLR0QA/wD/AP+gvaeTAAAMqUlEQVR4nO3YsQ3AMAwEMSeTe/QM4QgujqwF4etbCwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIen76s4fvAQAAgAPv7QEAAADAPAEAAAAAAgQAAAAACBAAAAAAIEAAAAAAgAABAAAAAAIEAAAAAAgQAAAAACBAAAAAAIAAAQAAAAACBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAUx8hKgEYqUBSPgAAAABJRU5ErkJggg=="
+ id="image3481" />
+ </g>
+ </mask>
+ <clipPath
+ id="s">
+ <path
+ d="m 0 0 h 1024 v 800 h -1024 z"
+ id="path3486" />
+ </clipPath>
+ <path
+ d="m 3 1 c -1.644531 0 -3 1.355469 -3 3 v 6 c 0 1.644531 1.355469 3 3 3 h 10 c 1.644531 0 3 -1.355469 3 -3 v -6 c 0 -0.570312 -0.167969 -1.101562 -0.449219 -1.558594 l -1.550781 1.554688 v 6.003906 c 0 0.570312 -0.429688 1 -1 1 h -10 c -0.570312 0 -1 -0.429688 -1 -1 v -6 c 0 -0.570312 0.429688 -1 1 -1 h 5.96875 l 2.007812 -2 z m 0 0"
+ fill="#2e3436"
+ id="path3489"
+ style="fill:#000000" />
+ <g
+ clip-path="url(#c)"
+ mask="url(#b)"
+ transform="matrix(1 0 0 1 -40 -620)"
+ id="g3493"
+ style="fill:#000000">
+ <path
+ d="m 439.105469 225.78125 h 7.839843 c -0.890624 0.371094 -0.972656 1.847656 0 2.25 h -7.839843 z m 0 0"
+ fill="#2e3436"
+ id="path3491"
+ style="fill:#000000" />
+ </g>
+ <g
+ clip-path="url(#e)"
+ mask="url(#d)"
+ transform="matrix(1 0 0 1 -40 -620)"
+ id="g3497"
+ style="fill:#000000">
+ <path
+ d="m 29.25 627.75 h 0.75 v 0.75 h -0.75 z m 0 0"
+ fill="#2e3436"
+ fill-rule="evenodd"
+ id="path3495"
+ style="fill:#000000" />
+ </g>
+ <g
+ clip-path="url(#g)"
+ mask="url(#f)"
+ transform="matrix(1 0 0 1 -40 -620)"
+ id="g3501"
+ style="fill:#000000">
+ <path
+ d="m 30 627 h 0.75 v 0.75 h -0.75 z m 0 0"
+ fill="#2e3436"
+ fill-rule="evenodd"
+ id="path3499"
+ style="fill:#000000" />
+ </g>
+ <g
+ clip-path="url(#i)"
+ mask="url(#h)"
+ transform="matrix(1 0 0 1 -40 -620)"
+ id="g3505"
+ style="fill:#000000">
+ <path
+ d="m 30.75 629.25 h 0.75 v 0.75 h -0.75 z m 0 0"
+ fill="#2e3436"
+ fill-rule="evenodd"
+ id="path3503"
+ style="fill:#000000" />
+ </g>
+ <g
+ clip-path="url(#k)"
+ mask="url(#j)"
+ transform="matrix(1 0 0 1 -40 -620)"
+ id="g3509"
+ style="fill:#000000">
+ <path
+ d="m 29.25 629.25 h 0.75 v 0.75 h -0.75 z m 0 0"
+ fill="#2e3436"
+ fill-rule="evenodd"
+ id="path3507"
+ style="fill:#000000" />
+ </g>
+ <g
+ clip-path="url(#m)"
+ mask="url(#l)"
+ transform="matrix(1 0 0 1 -40 -620)"
+ id="g3513"
+ style="fill:#000000">
+ <path
+ d="m 30 630 h 0.75 v 0.75 h -0.75 z m 0 0"
+ fill="#2e3436"
+ fill-rule="evenodd"
+ id="path3511"
+ style="fill:#000000" />
+ </g>
+ <g
+ clip-path="url(#o)"
+ mask="url(#n)"
+ transform="matrix(1 0 0 1 -40 -620)"
+ id="g3517"
+ style="fill:#000000">
+ <path
+ d="m 31.5 630 h 0.75 v 0.75 h -0.75 z m 0 0"
+ fill="#2e3436"
+ fill-rule="evenodd"
+ id="path3515"
+ style="fill:#000000" />
+ </g>
+ <g
+ clip-path="url(#q)"
+ mask="url(#p)"
+ transform="matrix(1 0 0 1 -40 -620)"
+ id="g3521"
+ style="fill:#000000">
+ <path
+ d="m 119.253906 648.75 v 5.25 h 5.25 v -5.25 z m 0 0"
+ fill="#2e3436"
+ id="path3519"
+ style="fill:#000000" />
+ </g>
+ <path
+ d="m 11 7 c 0 1.65625 -1.339844 3.007812 -3 3 h -3 v -3 c 0 -1.660156 1.34375 -3 3 -3 c 1.660156 0 3 1.339844 3 3 z m 0 0"
+ fill="#2e3436"
+ id="path3523"
+ style="fill:#000000" />
+ <path
+ d="m 13.398438 0 l -3.46875 3.457031 c 0.683593 0.355469 1.234374 0.910157 1.589843 1.589844 l 0.171875 -0.171875 l 0.007813 0.007812 l 4.300781 -4.300781 v -0.582031 z m 0 0"
+ fill="#2e3436"
+ id="path3525"
+ style="fill:#000000" />
+ <g
+ clip-path="url(#s)"
+ mask="url(#r)"
+ transform="matrix(1 0 0 1 -40 -620)"
+ id="g3529"
+ style="fill:#000000">
+ <path
+ d="m 181.503906 635.25 h 2.25 v 9 h -2.25 z m 0 0"
+ fill="#2e3436"
+ id="path3527"
+ style="fill:#000000" />
+ </g>
+ <path
+ d="m 5 14 c -1.105469 0 -2 0.894531 -2 2 h 10 c 0 -1.105469 -0.894531 -2 -2 -2 z m 0 0"
+ fill="#2e3436"
+ id="path3531"
+ style="fill:#000000" />
+</svg>
diff --git a/linux/home/.config/ags/assets/processor-symbolic.svg b/linux/home/.config/ags/assets/processor-symbolic.svg
new file mode 100644
index 0000000..832dbaf
--- /dev/null
+++ b/linux/home/.config/ags/assets/processor-symbolic.svg
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
+ <path d="m 5 5 h 6 v 6 h -6 z m 0 0"/>
+ <path d="m 13 5 h 3 v 1 h -3 z m 0 0"/>
+ <path d="m 13 7 h 3 v 1 h -3 z m 0 0"/>
+ <path d="m 13 9 h 3 v 1 h -3 z m 0 0"/>
+ <path d="m 0 6 h 3 v 1 h -3 z m 0 0"/>
+ <path d="m 0 8 h 3 v 1 h -3 z m 0 0"/>
+ <path d="m 0 10 h 3 v 1 h -3 z m 0 0"/>
+ <path d="m 5 0 h 1 v 3 h -1 z m 0 0"/>
+ <path d="m 7 0 h 1 v 3 h -1 z m 0 0"/>
+ <path d="m 9 0 h 1 v 3 h -1 z m 0 0"/>
+ <path d="m 10 13 h 1 v 3 h -1 z m 0 0"/>
+ <path d="m 8 13 h 1 v 3 h -1 z m 0 0"/>
+ <path d="m 6 13 h 1 v 3 h -1 z m 0 0"/>
+ <path d="m 5 2 c -1.644531 0 -3 1.355469 -3 3 v 6 c 0 1.644531 1.355469 3 3 3 h 6 c 1.644531 0 3 -1.355469 3 -3 v -6 c 0 -1.644531 -1.355469 -3 -3 -3 z m 0 2 h 6 c 0.570312 0 1 0.429688 1 1 v 6 c 0 0.570312 -0.429688 1 -1 1 h -6 c -0.570312 0 -1 -0.429688 -1 -1 v -6 c 0 -0.570312 0.429688 -1 1 -1 z m 0 0"/>
+</svg>
diff --git a/linux/home/.config/ags/assets/rotation.svg b/linux/home/.config/ags/assets/rotation.svg
new file mode 100644
index 0000000..7ed7e34
--- /dev/null
+++ b/linux/home/.config/ags/assets/rotation.svg
@@ -0,0 +1,8 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <defs>
+ <style id="current-color-scheme" type="text/css">
+ .ColorScheme-Text { color:#dfdfdf; } .ColorScheme-Highlight { color:#4285f4; } .ColorScheme-NeutralText { color:#ff9800; } .ColorScheme-PositiveText { color:#4caf50; } .ColorScheme-NegativeText { color:#f44336; }
+ </style>
+ </defs>
+ <path style="fill:currentColor" class="ColorScheme-Text" d="M 1 1 L 1 10 L 7 10 L 7 5 L 9 5 C 10.108 5 11 5.89199 11 7 L 11 9 L 8 9 L 8 11 L 6 11 L 6 15 L 15 15 L 15 9 L 12 9 L 12 7 C 12 5.338 10.662 4 9 4 L 7 4 L 7 1 L 1 1 z" transform="translate(4 4)"/>
+</svg>
diff --git a/linux/home/.config/ags/assets/swapnext.svg b/linux/home/.config/ags/assets/swapnext.svg
new file mode 100644
index 0000000..0690cb9
--- /dev/null
+++ b/linux/home/.config/ags/assets/swapnext.svg
@@ -0,0 +1,8 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <defs>
+ <style id="current-color-scheme" type="text/css">
+ .ColorScheme-Text { color:#dfdfdf; } .ColorScheme-Highlight { color:#4285f4; } .ColorScheme-NeutralText { color:#ff9800; } .ColorScheme-PositiveText { color:#4caf50; } .ColorScheme-NegativeText { color:#f44336; }
+ </style>
+ </defs>
+ <path style="fill:currentColor" class="ColorScheme-Text" d="M 3 1 C 1.892 1 1 1.892 1 3 L 1 13 C 1 14.108 1.892 15 3 15 L 13 15 C 14.108 15 15 14.108 15 13 L 15 3 C 15 1.892 14.108 1 13 1 L 3 1 z M 3 3 L 13 3 L 13 13 L 3 13 L 3 3 z M 5 4 A 1 1 0 0 0 4 5 A 1 1 0 0 0 5 6 A 1 1 0 0 0 6 5 A 1 1 0 0 0 5 4 z M 11 4 A 1 1 0 0 0 10 5 A 1 1 0 0 0 11 6 A 1 1 0 0 0 12 5 A 1 1 0 0 0 11 4 z M 8 7 A 1 1 0 0 0 7 8 A 1 1 0 0 0 8 9 A 1 1 0 0 0 9 8 A 1 1 0 0 0 8 7 z M 5 10 A 1 1 0 0 0 4 11 A 1 1 0 0 0 5 12 A 1 1 0 0 0 6 11 A 1 1 0 0 0 5 10 z M 11 10 A 1 1 0 0 0 10 11 A 1 1 0 0 0 11 12 A 1 1 0 0 0 12 11 A 1 1 0 0 0 11 10 z" transform="translate(4 4)"/>
+</svg>
diff --git a/linux/home/.config/ags/assets/tbox-close.svg b/linux/home/.config/ags/assets/tbox-close.svg
new file mode 100644
index 0000000..f325d3c
--- /dev/null
+++ b/linux/home/.config/ags/assets/tbox-close.svg
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ width="24"
+ height="24"
+ version="1.1"
+ id="svg1"
+ sodipodi:docname="tbox-close.svg"
+ xml:space="preserve"
+ inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
+ id="namedview1"
+ pagecolor="#ffffff"
+ bordercolor="#000000"
+ borderopacity="0.25"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:zoom="26.708333"
+ inkscape:cx="11.981279"
+ inkscape:cy="11.981279"
+ inkscape:current-layer="svg1" /><defs
+ id="defs1"><style
+ id="current-color-scheme"
+ type="text/css">
+ .ColorScheme-Text { color:#dfdfdf; } .ColorScheme-Highlight { color:#4285f4; } .ColorScheme-NeutralText { color:#ff9800; } .ColorScheme-PositiveText { color:#4caf50; } .ColorScheme-NegativeText { color:#f44336; }
+ </style></defs><g
+ id="g2"
+ transform="matrix(0.43167414,0,0,0.42254065,1.6211,1.3192164)"><path
+ style="opacity:0.2"
+ d="m 34.158694,7.001891 c -2.339941,-0.051114 -4.593769,0.9363666 -6.144072,2.6910582 -0.0012,0.0014 -0.0027,0.0025 -0.0039,0.0039 l -4.015616,4.4240828 -4.01174,-4.4162958 c -1.424465,-1.6183716 -3.450682,-2.592914 -5.603207,-2.6949526 -0.260519,-0.012616 -0.51772,-0.013001 -0.778224,0 a 5.977352,5.9824591 0 0 0 -0.01945,0 C 7.2433432,7.3455998 3.8467314,15.780704 8.1816032,20.422133 L 13.224489,25.975605 8.2594255,31.4434 c -1.5192901,1.580887 -2.4400962,4.078962 -2.2296097,6.266155 0.2104868,2.187194 1.2500876,3.924619 2.6187213,5.167923 1.3686339,1.243304 3.1976469,2.112373 5.3930859,2.110788 2.19544,-0.0016 4.590268,-1.161185 6.015666,-2.827365 l 3.937809,-4.330618 4.054544,4.463029 -0.147863,-0.167461 c 1.418645,1.683037 3.819828,2.866353 6.02734,2.874097 2.20751,0.0078 4.047562,-0.863731 5.420324,-2.110787 1.372761,-1.247055 2.417086,-2.995369 2.622612,-5.195185 0.205525,-2.199813 -0.739595,-4.705945 -2.276302,-6.281731 l -4.933937,-5.43664 5.050669,-5.561261 c 2.191468,-2.3538 2.626913,-5.893796 1.513644,-8.536614 C 40.212861,9.2349127 37.376326,7.0748944 34.162585,7.001891 a 5.977352,5.9824591 0 0 0 -0.0039,0 z"
+ id="path1-3" /><path
+ style="fill:#4f4f4f"
+ d="m 34.158694,6.0018906 c -2.339941,-0.051114 -4.593769,0.9363666 -6.144072,2.6910586 -0.0012,0.00139 -0.0027,0.0025 -0.0039,0.0039 L 23.995106,13.120932 19.983366,8.7046361 C 18.558901,7.0862642 16.532684,6.1117218 14.380159,6.0096832 c -0.260519,-0.012616 -0.51772,-0.013001 -0.778224,0 a 5.977352,5.9824591 0 0 0 -0.01945,0 C 7.2433432,6.3455994 3.8467314,14.780704 8.1816032,19.422133 L 13.224489,24.975605 8.2594255,30.4434 c -1.5192901,1.580887 -2.4400962,4.078962 -2.2296097,6.266155 0.2104868,2.187194 1.2500876,3.924619 2.6187213,5.167923 1.3686339,1.243304 3.1976469,2.112373 5.3930859,2.110788 2.19544,-0.0016 4.590268,-1.161185 6.015666,-2.827365 l 3.937809,-4.330618 4.054544,4.463029 -0.147863,-0.167461 c 1.418645,1.683037 3.819828,2.866353 6.02734,2.874097 2.20751,0.0078 4.047562,-0.863731 5.420324,-2.110787 1.372761,-1.247055 2.417086,-2.995369 2.622612,-5.195185 0.205525,-2.199813 -0.739595,-4.705945 -2.276302,-6.281731 l -4.933937,-5.43664 5.050669,-5.561261 c 2.191468,-2.3538 2.626913,-5.893796 1.513644,-8.536614 C 40.212861,8.2349123 37.376326,6.074894 34.162585,6.0018906 a 5.977352,5.9824591 0 0 0 -0.0039,0 z"
+ id="path2" /><path
+ style="opacity:0.2"
+ d="m 13.875,12.980111 a 2.0002,2.0002 0 0 0 -1.355469,3.363282 l 8.789063,9.667968 -8.769532,9.644532 A 2.0002,2.0002 0 1 0 15.5,38.343393 l 8.507812,-9.359374 8.51172,9.359374 a 2.0006762,2.0006762 0 1 0 2.960936,-2.6875 L 26.710938,26.011361 35.5,16.343393 a 2.0002,2.0002 0 0 0 -1.417968,-3.363282 2.0002,2.0002 0 0 0 -1.54297,0.675782 l -8.53125,9.382812 -8.527343,-9.382812 a 2.0002,2.0002 0 0 0 -1.40625,-0.675782 2.0002,2.0002 0 0 0 -0.199219,0 z"
+ id="path3" /><path
+ style="fill:#ffffff"
+ transform="scale(2)"
+ d="M 6.9375,5.9902344 A 1.0001,1.0001 0 0 0 6.2597656,7.671875 l 4.3945314,4.833984 -4.3847658,4.822266 A 1.0001,1.0001 0 1 0 7.75,18.671875 l 4.253906,-4.679687 4.25586,4.679687 a 1.0003381,1.0003381 0 1 0 1.480468,-1.34375 L 13.355469,12.505859 17.75,7.671875 A 1.0001,1.0001 0 0 0 17.041016,5.9902344 1.0001,1.0001 0 0 0 16.269531,6.328125 L 12.003906,11.019531 7.7402344,6.328125 a 1.0001,1.0001 0 0 0 -0.703125,-0.3378906 1.0001,1.0001 0 0 0 -0.099609,0 z"
+ id="path4" /><path
+ style="opacity:0.2;fill:#ffffff"
+ transform="scale(2)"
+ d="m 17.080078,3 c -1.16997,-0.025557 -2.297114,0.4683571 -3.072266,1.3457031 0,0 -0.002,0.00195 -0.002,0.00195 L 11.998047,6.5605469 9.9921875,4.3515625 C 9.279955,3.5423765 8.2657156,3.0549255 7.1894531,3.0039062 c -0.1302595,-0.00631 -0.2584199,-0.0065 -0.3886719,0 a 2.988676,2.9912295 0 0 0 -0.00977,0 C 4.516087,3.1244563 3.008504,5.3315905 3.1347612,7.4257812 3.2542394,5.4946775 4.6972247,3.6148577 6.7910112,3.5039062 a 2.988676,2.9912295 0 0 1 0.00977,0 c 0.130252,-0.0065 0.2584124,-0.00631 0.3886719,0 1.0762625,0.051019 2.0905019,0.5384705 2.8027344,1.3476563 l 2.0058595,2.2089844 2.007812,-2.2128907 c 0,0 0.002,-0.00195 0.002,-0.00195 C 14.782964,3.9683573 15.910108,3.474443 17.080078,3.5 a 2.988676,2.9912295 0 0 0 0.002,0 c 1.606871,0.036502 3.023444,1.1180442 3.580078,2.4394531 0.16598,0.3940229 0.255721,0.8292143 0.28125,1.2753907 0.03853,-0.6193247 -0.05357,-1.2348847 -0.28125,-1.7753907 C 20.105475,4.1180443 18.688902,3.0365017 17.082031,3 a 2.988676,2.9912295 0 0 0 -0.002,0 z M 6.3847656,12.738281 4.1289062,15.222656 c -0.7462708,0.776527 -1.199139,1.994686 -1.1152343,3.074219 0.063557,-0.939898 0.4824865,-1.915817 1.1152343,-2.574219 l 2.4824219,-2.734375 z m 11.2226564,0 -0.226563,0.25 2.466797,2.71875 c 0.646214,0.662647 1.079159,1.652595 1.140625,2.605469 0.08979,-1.091494 -0.380318,-2.325827 -1.140625,-3.105469 z"
+ id="path5" /></g></svg>
diff --git a/linux/home/.config/ags/assets/terminal-symbolic.svg b/linux/home/.config/ags/assets/terminal-symbolic.svg
new file mode 100644
index 0000000..9f82bcf
--- /dev/null
+++ b/linux/home/.config/ags/assets/terminal-symbolic.svg
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
+ <path d="m 2.199219 0 c -1.207031 0 -2.199219 1.007812 -2.199219 2.207031 v 10.585938 c 0 1.199219 0.992188 2.207031 2.199219 2.207031 h 11.601562 c 1.207031 0 2.199219 -1.007812 2.199219 -2.207031 v -10.585938 c 0 -1.199219 -0.992188 -2.207031 -2.199219 -2.207031 z m 0 2 h 11.601562 c 0.121094 0 0.199219 0.070312 0.199219 0.207031 v 10.585938 c 0 0.136719 -0.078125 0.207031 -0.199219 0.207031 h -11.601562 c -0.121094 0 -0.199219 -0.070312 -0.199219 -0.207031 v -10.585938 c 0 -0.136719 0.078125 -0.207031 0.199219 -0.207031 z m 0 0"/>
+ <path d="m 4.515625 5.898438 c -0.164063 -0.003907 -0.324219 0.0625 -0.441406 0.175781 c -0.230469 0.234375 -0.230469 0.617187 0 0.851562 l 1.578125 1.574219 l -1.578125 1.574219 c -0.230469 0.234375 -0.230469 0.617187 0 0.851562 c 0.234375 0.230469 0.617187 0.230469 0.851562 0 l 2 -2 c 0.230469 -0.234375 0.230469 -0.617187 0 -0.851562 l -2 -2 c -0.109375 -0.105469 -0.257812 -0.167969 -0.410156 -0.175781 z m 3.484375 4.101562 v 1 h 3 v -1 z m 0 0"/>
+</svg>
diff --git a/linux/home/.config/ags/assets/togglesplit.svg b/linux/home/.config/ags/assets/togglesplit.svg
new file mode 100644
index 0000000..15d5011
--- /dev/null
+++ b/linux/home/.config/ags/assets/togglesplit.svg
@@ -0,0 +1,10 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" version="1.1">
+ <defs>
+ <style id="current-color-scheme" type="text/css">
+ .ColorScheme-Text { color:#dfdfdf; } .ColorScheme-Highlight { color:#4285f4; } .ColorScheme-NeutralText { color:#ff9800; } .ColorScheme-PositiveText { color:#4caf50; } .ColorScheme-NegativeText { color:#f44336; }
+ </style>
+ </defs>
+ <g transform="translate(4,4)">
+ <path style="fill:currentColor" class="ColorScheme-Text" d="M 1,1 V 10 H 5 V 5 H 10 V 1 Z M 6,6 H 15 V 15 H 6 Z"/>
+ </g>
+</svg>
diff --git a/linux/home/.config/ags/assets/toolbars-symbolic.svg b/linux/home/.config/ags/assets/toolbars-symbolic.svg
new file mode 100644
index 0000000..9f4c564
--- /dev/null
+++ b/linux/home/.config/ags/assets/toolbars-symbolic.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
+ <path d="m 2 0 c -1.214844 0 -2 0.828125 -2 2 v 12 c 0 1 1 2 2 2 h 11.984375 c 1 0 2 -1 2 -2 v -12 c 0 -1.238281 -0.828125 -2 -2 -2 z m 0 2 h 2 v 2 h -2 z m 3 0 h 2 v 2 h -2 z m 3 0 h 2 v 2 h -2 z m -6 4 h 11.984375 v 8 h -11.984375 z m 0 0"/>
+</svg>
diff --git a/linux/home/.config/ags/assets/wp-next.svg b/linux/home/.config/ags/assets/wp-next.svg
new file mode 100644
index 0000000..ac0245d
--- /dev/null
+++ b/linux/home/.config/ags/assets/wp-next.svg
@@ -0,0 +1,10 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" version="1.1">
+ <defs>
+ <style id="current-color-scheme" type="text/css">
+ .ColorScheme-Text { color:#dfdfdf; } .ColorScheme-Highlight { color:#4285f4; } .ColorScheme-NeutralText { color:#ff9800; } .ColorScheme-PositiveText { color:#4caf50; } .ColorScheme-NegativeText { color:#f44336; }
+ </style>
+ </defs>
+ <g transform="translate(4,4)">
+ <path style="fill:currentColor" class="ColorScheme-Text" d="M 3,2 V 14 L 14,8 Z"/>
+ </g>
+</svg>
diff --git a/linux/home/.config/ags/assets/wp-prev.svg b/linux/home/.config/ags/assets/wp-prev.svg
new file mode 100644
index 0000000..12ed8dd
--- /dev/null
+++ b/linux/home/.config/ags/assets/wp-prev.svg
@@ -0,0 +1,10 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" version="1.1">
+ <defs>
+ <style id="current-color-scheme" type="text/css">
+ .ColorScheme-Text { color:#dfdfdf; } .ColorScheme-Highlight { color:#4285f4; } .ColorScheme-NeutralText { color:#ff9800; } .ColorScheme-PositiveText { color:#4caf50; } .ColorScheme-NegativeText { color:#f44336; }
+ </style>
+ </defs>
+ <g transform="translate(4,4)">
+ <path style="fill:currentColor" class="ColorScheme-Text" d="M 13,2 2,8 13,14 Z"/>
+ </g>
+</svg>
diff --git a/linux/home/.config/ags/config.js b/linux/home/.config/ags/config.js
new file mode 100644
index 0000000..2864dec
--- /dev/null
+++ b/linux/home/.config/ags/config.js
@@ -0,0 +1,46 @@
+import GLib from "gi://GLib"
+
+const main = "/tmp/ags/main.js"
+const entry = `${App.configDir}/main.ts`
+const bundler = GLib.getenv("AGS_BUNDLER") || "bun"
+
+const v = {
+ ags: pkg.version?.split(".").map(Number) || [],
+ expect: [1, 8, 0],
+}
+
+try {
+ switch (bundler) {
+ case "bun": await Utils.execAsync([
+ "bun", "build", entry,
+ "--outfile", main,
+ "--external", "resource://*",
+ "--external", "gi://*",
+ "--external", "file://*",
+ ]); break
+
+ case "esbuild": await Utils.execAsync([
+ "esbuild", "--bundle", entry,
+ "--format=esm",
+ `--outfile=${main}`,
+ "--external:resource://*",
+ "--external:gi://*",
+ "--external:file://*",
+ ]); break
+
+ default:
+ throw `"${bundler}" is not a valid bundler`
+ }
+
+ if (v.ags[1] < v.expect[1] || v.ags[2] < v.expect[2]) {
+ print(`my config needs at least v${v.expect.join(".")}, yours is v${v.ags.join(".")}`)
+ App.quit()
+ }
+
+ await import(`file://${main}`)
+} catch (error) {
+ console.error(error)
+ App.quit()
+}
+
+export { }
diff --git a/linux/home/.config/ags/default.nix b/linux/home/.config/ags/default.nix
new file mode 100644
index 0000000..f0e0c41
--- /dev/null
+++ b/linux/home/.config/ags/default.nix
@@ -0,0 +1,104 @@
+{
+ inputs,
+ writeShellScript,
+ system,
+ stdenv,
+ cage,
+ swww,
+ esbuild,
+ dart-sass,
+ fd,
+ fzf,
+ brightnessctl,
+ accountsservice,
+ slurp,
+ wf-recorder,
+ wl-clipboard,
+ wayshot,
+ swappy,
+ hyprpicker,
+ pavucontrol,
+ networkmanager,
+ gtk3,
+ which,
+}: let
+ name = "asztal";
+
+ ags = inputs.ags.packages.${system}.default.override {
+ extraPackages = [accountsservice];
+ };
+
+ dependencies = [
+ which
+ dart-sass
+ fd
+ fzf
+ brightnessctl
+ swww
+ inputs.matugen.packages.${system}.default
+ inputs.hyprland.packages.${system}.default
+ slurp
+ wf-recorder
+ wl-clipboard
+ wayshot
+ swappy
+ hyprpicker
+ pavucontrol
+ networkmanager
+ gtk3
+ ];
+
+ addBins = list: builtins.concatStringsSep ":" (builtins.map (p: "${p}/bin") list);
+
+ greeter = writeShellScript "greeter" ''
+ export PATH=$PATH:${addBins dependencies}
+ ${cage}/bin/cage -ds -m last ${ags}/bin/ags -- -c ${config}/greeter.js
+ '';
+
+ desktop = writeShellScript name ''
+ export PATH=$PATH:${addBins dependencies}
+ ${ags}/bin/ags -b ${name} -c ${config}/config.js $@
+ '';
+
+ config = stdenv.mkDerivation {
+ inherit name;
+ src = ./.;
+
+ buildPhase = ''
+ ${esbuild}/bin/esbuild \
+ --bundle ./main.ts \
+ --outfile=main.js \
+ --format=esm \
+ --external:resource://\* \
+ --external:gi://\* \
+
+ ${esbuild}/bin/esbuild \
+ --bundle ./greeter/greeter.ts \
+ --outfile=greeter.js \
+ --format=esm \
+ --external:resource://\* \
+ --external:gi://\* \
+ '';
+
+ installPhase = ''
+ mkdir -p $out
+ cp -r assets $out
+ cp -r style $out
+ cp -r greeter $out
+ cp -r widget $out
+ cp -f main.js $out/config.js
+ cp -f greeter.js $out/greeter.js
+ '';
+ };
+in
+ stdenv.mkDerivation {
+ inherit name;
+ src = config;
+
+ installPhase = ''
+ mkdir -p $out/bin
+ cp -r . $out
+ cp ${desktop} $out/bin/${name}
+ cp ${greeter} $out/bin/greeter
+ '';
+ }
diff --git a/linux/home/.config/ags/greeter.js b/linux/home/.config/ags/greeter.js
new file mode 100644
index 0000000..5c8e369
--- /dev/null
+++ b/linux/home/.config/ags/greeter.js
@@ -0,0 +1,18 @@
+const main = "/tmp/ags/greeter.js"
+const entry = `${App.configDir}/greeter/greeter.ts`
+
+try {
+ await Utils.execAsync([
+ "bun", "build", entry,
+ "--outfile", main,
+ "--external", "resource://*",
+ "--external", "gi://*",
+ "--external", "file://*",
+ ])
+ await import(`file://${main}`)
+} catch (error) {
+ console.error(error)
+ App.quit()
+}
+
+export { }
diff --git a/linux/home/.config/ags/greeter/auth.ts b/linux/home/.config/ags/greeter/auth.ts
new file mode 100644
index 0000000..23477eb
--- /dev/null
+++ b/linux/home/.config/ags/greeter/auth.ts
@@ -0,0 +1,109 @@
+import AccountsService from "gi://AccountsService?version=1.0"
+import GLib from "gi://GLib?version=2.0"
+import icons from "lib/icons"
+
+const { iconFile, realName, userName } = AccountsService.UserManager
+ .get_default().list_users()[0]
+
+const loggingin = Variable(false)
+
+const CMD = GLib.getenv("ASZTAL_DM_CMD")
+ || "Hyprland"
+
+const ENV = GLib.getenv("ASZTAL_DM_ENV")
+ || "WLR_NO_HARDWARE_CURSORS=1 _JAVA_AWT_WM_NONREPARENTING=1"
+
+async function login(pw: string) {
+ loggingin.value = true
+ const greetd = await Service.import("greetd")
+ return greetd.login(userName, pw, CMD, ENV.split(/\s+/))
+ .catch(res => {
+ loggingin.value = false
+ response.label = res?.description || JSON.stringify(res)
+ password.text = ""
+ revealer.reveal_child = true
+ })
+}
+
+const avatar = Widget.Box({
+ class_name: "avatar",
+ hpack: "center",
+ css: `background-image: url('${iconFile}')`,
+})
+
+const password = Widget.Entry({
+ placeholder_text: "Password",
+ hexpand: true,
+ visibility: false,
+ on_accept: ({ text }) => { login(text || "") },
+})
+
+const response = Widget.Label({
+ class_name: "response",
+ wrap: true,
+ max_width_chars: 35,
+ hpack: "center",
+ hexpand: true,
+ xalign: .5,
+})
+
+const revealer = Widget.Revealer({
+ transition: "slide_down",
+ child: response,
+})
+
+export default Widget.Box({
+ class_name: "auth",
+ attribute: { password },
+ vertical: true,
+ children: [
+ Widget.Overlay({
+ child: Widget.Box(
+ {
+ css: "min-width: 200px; min-height: 200px;",
+ vertical: true,
+ },
+ Widget.Box({
+ class_name: "wallpaper",
+ css: `background-image: url('${WALLPAPER}')`,
+ }),
+ Widget.Box({
+ class_name: "wallpaper-contrast",
+ vexpand: true,
+ }),
+ ),
+ overlay: Widget.Box(
+ {
+ vpack: "end",
+ vertical: true,
+ },
+ avatar,
+ Widget.Box({
+ hpack: "center",
+ children: [
+ Widget.Icon(icons.ui.avatar),
+ Widget.Label(realName || userName),
+ ],
+ }),
+ Widget.Box(
+ {
+ class_name: "password",
+ },
+ Widget.Spinner({
+ visible: loggingin.bind(),
+ active: true,
+ }),
+ Widget.Icon({
+ visible: loggingin.bind().as(b => !b),
+ icon: icons.ui.lock,
+ }),
+ password,
+ ),
+ ),
+ }),
+ Widget.Box(
+ { class_name: "response-box" },
+ revealer,
+ ),
+ ],
+})
diff --git a/linux/home/.config/ags/greeter/greeter.scss b/linux/home/.config/ags/greeter/greeter.scss
new file mode 100644
index 0000000..e3a5cd8
--- /dev/null
+++ b/linux/home/.config/ags/greeter/greeter.scss
@@ -0,0 +1,64 @@
+@import "../style/mixins/floating-widget.scss";
+@import "../style/mixins/widget.scss";
+@import "../style/mixins/spacing.scss";
+@import "../style/mixins/unset.scss";
+@import "../style/mixins/a11y-button.scss";
+@import "../widget/bar/bar.scss";
+
+window#greeter {
+ background-color: lighten($bg, 6%);
+ color: $fg;
+
+ .bar {
+ background-color: transparent;
+
+ .date {
+ @include unset($rec: true);
+ @include panel-button($flat: true, $reactive: false);
+ }
+ }
+
+ .auth {
+ @include floating_widget;
+ border-radius: $radius;
+ min-width: 400px;
+ padding: 0;
+
+ .wallpaper {
+ min-height: 220px;
+ background-size: cover;
+ border-top-left-radius: $radius;
+ border-top-right-radius: $radius;
+ }
+
+ .wallpaper-contrast {
+ min-height: 100px;
+ }
+
+ .avatar {
+ border-radius: 99px;
+ min-width: 140px;
+ min-height: 140px;
+ background-size: cover;
+ box-shadow: 3px 3px 6px 0 $shadow-color;
+ margin-bottom: $spacing;
+ }
+
+
+ .password {
+ entry {
+ @include button;
+ padding: $padding*.7 $padding;
+ margin-left: $spacing*.5;
+ }
+
+ margin: 0 $padding*4;
+ margin-top: $spacing;
+ }
+
+ .response-box {
+ color: $error-bg;
+ margin: $spacing 0;
+ }
+ }
+}
diff --git a/linux/home/.config/ags/greeter/greeter.ts b/linux/home/.config/ags/greeter/greeter.ts
new file mode 100644
index 0000000..eb1493f
--- /dev/null
+++ b/linux/home/.config/ags/greeter/greeter.ts
@@ -0,0 +1,37 @@
+import "./session"
+import "style/style"
+import GLib from "gi://GLib?version=2.0"
+import RegularWindow from "widget/RegularWindow"
+import statusbar from "./statusbar"
+import auth from "./auth"
+
+const win = RegularWindow({
+ name: "greeter",
+ setup: self => {
+ self.set_default_size(500, 500)
+ self.show_all()
+ auth.attribute.password.grab_focus()
+ },
+ child: Widget.Overlay({
+ child: Widget.Box({ expand: true }),
+ overlays: [
+ Widget.Box({
+ vpack: "start",
+ hpack: "fill",
+ hexpand: true,
+ child: statusbar,
+ }),
+ Widget.Box({
+ vpack: "center",
+ hpack: "center",
+ child: auth,
+ }),
+ ],
+ }),
+})
+
+App.config({
+ icons: "./assets",
+ windows: [win],
+ cursorTheme: GLib.getenv("XCURSOR_THEME")!,
+})
diff --git a/linux/home/.config/ags/greeter/session.ts b/linux/home/.config/ags/greeter/session.ts
new file mode 100644
index 0000000..092a5c2
--- /dev/null
+++ b/linux/home/.config/ags/greeter/session.ts
@@ -0,0 +1,20 @@
+import GLib from "gi://GLib?version=2.0"
+import AccountsService from "gi://AccountsService?version=1.0"
+
+const { userName } = AccountsService.UserManager.get_default().list_users()[0]
+
+declare global {
+ const WALLPAPER: string
+}
+
+Object.assign(globalThis, {
+ TMP: `${GLib.get_tmp_dir()}/greeter`,
+ OPTIONS: "/var/cache/greeter/options.json",
+ WALLPAPER: "/var/cache/greeter/background",
+ // TMP: "/tmp/ags",
+ // OPTIONS: Utils.CACHE_DIR + "/options.json",
+ // WALLPAPER: Utils.HOME + "/.config/background",
+ USER: userName,
+})
+
+Utils.ensureDirectory(TMP)
diff --git a/linux/home/.config/ags/greeter/statusbar.ts b/linux/home/.config/ags/greeter/statusbar.ts
new file mode 100644
index 0000000..8076011
--- /dev/null
+++ b/linux/home/.config/ags/greeter/statusbar.ts
@@ -0,0 +1,46 @@
+import { clock } from "lib/variables"
+import options from "options"
+import icons from "lib/icons"
+import BatteryBar from "widget/bar/buttons/BatteryBar"
+import PanelButton from "widget/bar/PanelButton"
+
+const { scheme } = options.theme
+const { monochrome } = options.bar.powermenu
+const { format } = options.bar.date
+
+const poweroff = PanelButton({
+ class_name: "powermenu",
+ child: Widget.Icon(icons.powermenu.shutdown),
+ on_clicked: () => Utils.exec("shutdown now"),
+ setup: self => self.hook(monochrome, () => {
+ self.toggleClassName("colored", !monochrome.value)
+ self.toggleClassName("box")
+ }),
+})
+
+const date = PanelButton({
+ class_name: "date",
+ child: Widget.Label({
+ label: clock.bind().as(c => c.format(`${format}`)!),
+ }),
+})
+
+const darkmode = PanelButton({
+ class_name: "darkmode",
+ child: Widget.Icon({ icon: scheme.bind().as(s => icons.color[s]) }),
+ on_clicked: () => scheme.value = scheme.value === "dark" ? "light" : "dark",
+})
+
+export default Widget.CenterBox({
+ class_name: "bar",
+ hexpand: true,
+ center_widget: date,
+ end_widget: Widget.Box({
+ hpack: "end",
+ children: [
+ darkmode,
+ BatteryBar(),
+ poweroff,
+ ],
+ }),
+})
diff --git a/linux/home/.config/ags/lib/battery.ts b/linux/home/.config/ags/lib/battery.ts
new file mode 100644
index 0000000..3817260
--- /dev/null
+++ b/linux/home/.config/ags/lib/battery.ts
@@ -0,0 +1,16 @@
+import icons from "./icons"
+
+export default async function init() {
+ const bat = await Service.import("battery")
+ bat.connect("notify::percent", ({ percent, charging }) => {
+ const low = 30
+ if (percent !== low || percent !== low / 2 || !charging)
+ return
+
+ Utils.notify({
+ summary: `${percent}% Battery Percentage`,
+ iconName: icons.battery.warning,
+ urgency: "critical",
+ })
+ })
+}
diff --git a/linux/home/.config/ags/lib/client.js b/linux/home/.config/ags/lib/client.js
new file mode 100644
index 0000000..9fb9164
--- /dev/null
+++ b/linux/home/.config/ags/lib/client.js
@@ -0,0 +1,134 @@
+import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
+import { find_icon } from "./iconUtils.js";
+import { lookUpIcon, timeout } from 'resource:///com/github/Aylur/ags/utils.js';
+
+
+export let clientMapWorkSpace = {};
+
+export function substitute(str) {
+ const subs = [
+ { from: "code-url-handler", to: "visual-studio-code" },
+ { from: "Code", to: "visual-studio-code" },
+ { from: "GitHub Desktop", to: "github-desktop" },
+ { from: "wpsoffice", to: "wps-office2019-kprometheus" },
+ { from: "gnome-tweaks", to: "org.gnome.tweaks" },
+ { from: "Minecraft* 1.20.1", to: "minecraft" },
+ { from: "", to: "image-missing" },
+ ];
+
+ for (const { from, to } of subs) {
+ if (from === str) {
+ return to;
+ }
+ }
+
+ return str;
+}
+
+function titleToClient(title, className) {
+ const subs = [
+ { from: "musicfox", to: "musicfox" },
+ ];
+
+ for (const { from, to } of subs) {
+ if (title.indexOf(from) !== -1) {
+ return to;
+ }
+ }
+
+ return className
+}
+
+export const getClientByAdrees = function(address) {
+
+ const clients = Hyprland.clients
+
+ const client = clients.find(item => {
+ return item.address === address
+ })
+
+ return client
+}
+
+//Fullscreen client
+export const getFullScreenClientAddress = function(workspace_id) {
+
+ const clients = Hyprland.clients
+ const client = clients.find(item => {
+ return item.fullscreen && item.workspace.id === workspace_id
+ })
+ return client
+}
+
+export const ignoreAppsClass = [
+ 'image-missing',
+ 'fcitx',
+ 'rofi'
+]
+
+export const getClientIcon = (clientClass, title = "") => {
+
+ clientClass.toLowerCase()
+ clientClass = clientClass.replace(" ", "_");
+
+
+ if (title.length > 0) {
+ clientClass = titleToClient(title, clientClass)
+ }
+
+ const awesome_icon = find_icon(clientClass)
+ if (awesome_icon) {
+ return awesome_icon
+ }
+
+ if (lookUpIcon(clientClass)) {
+ return clientClass
+ }
+
+ if (find_icon('system')) {
+ return find_icon('system')
+ }
+
+ return ""
+}
+
+
+export const focus = (client) => {
+ //client
+ const { address } = client;
+ const liveClient = getClientByAdrees(address);
+
+ //special window
+ if (liveClient.workspace.id < 0) {
+ const oldWorkSpace = clientMapWorkSpace[address];
+ if (oldWorkSpace) {
+ Utils.exec(
+ `hyprctl dispatch movetoworkspace ${oldWorkSpace},address:${address}`,
+ );
+ Utils.exec(`hyprctl dispatch workspace ${oldWorkSpace}`);
+ }
+ }
+
+ //fullscreen
+ if (liveClient.fullscreen) {
+ Utils.exec("hyprctl dispatch focuswindow address:" + address);
+ return;
+ }
+
+ //workspace fullscreen client
+ const currentFullScreenAddress = getFullScreenClientAddress(
+ liveClient.workspace.id,
+ );
+ if (currentFullScreenAddress) {
+ const fullScreenAdress = currentFullScreenAddress.address;
+ Utils.exec("hyprctl dispatch focuswindow address:" + fullScreenAdress);
+ Utils.exec("hyprctl dispatch fullscreen 1");
+ }
+
+ Utils.exec("hyprctl dispatch focuswindow address:" + address);
+ // Utils.exec('hyprctl dispatch cyclenext')
+ Utils.exec("hyprctl dispatch alterzorder top,address:" + address);
+ if (currentFullScreenAddress) {
+ Utils.exec("hyprctl dispatch fullscreen 1");
+ }
+};
diff --git a/linux/home/.config/ags/lib/cursorhover.js b/linux/home/.config/ags/lib/cursorhover.js
new file mode 100644
index 0000000..d93d021
--- /dev/null
+++ b/linux/home/.config/ags/lib/cursorhover.js
@@ -0,0 +1,86 @@
+const { Gdk, Gtk } = imports.gi;
+
+const CLICK_BRIGHTEN_AMOUNT = 0.13;
+
+export function setupCursorHover(button) {
+ const display = Gdk.Display.get_default();
+ button.connect('enter-notify-event', () => {
+ const cursor = Gdk.Cursor.new_from_name(display, 'pointer');
+ button.get_window().set_cursor(cursor);
+ });
+
+ button.connect('leave-notify-event', () => {
+ const cursor = Gdk.Cursor.new_from_name(display, 'default');
+ button.get_window().set_cursor(cursor);
+ });
+
+}
+
+export function setupCursorHoverAim(button) {
+ button.connect('enter-notify-event', () => {
+ const display = Gdk.Display.get_default();
+ const cursor = Gdk.Cursor.new_from_name(display, 'crosshair');
+ button.get_window().set_cursor(cursor);
+ });
+
+ button.connect('leave-notify-event', () => {
+ const display = Gdk.Display.get_default();
+ const cursor = Gdk.Cursor.new_from_name(display, 'default');
+ button.get_window().set_cursor(cursor);
+ });
+}
+
+export function setupCursorHoverGrab(button) {
+ button.connect('enter-notify-event', () => {
+ const display = Gdk.Display.get_default();
+ const cursor = Gdk.Cursor.new_from_name(display, 'grab');
+ button.get_window().set_cursor(cursor);
+ });
+
+ button.connect('leave-notify-event', () => {
+ const display = Gdk.Display.get_default();
+ const cursor = Gdk.Cursor.new_from_name(display, 'default');
+ button.get_window().set_cursor(cursor);
+ });
+}
+
+// failed radial ripple experiment
+//
+// var clicked = false;
+// var dummy = false;
+// var cursorX = 0;
+// var cursorY = 0;
+// const styleContext = button.get_style_context();
+// var clickColor = styleContext.get_property('background-color', Gtk.StateFlags.HOVER);
+// clickColor.green += CLICK_BRIGHTEN_AMOUNT;
+// clickColor.blue += CLICK_BRIGHTEN_AMOUNT;
+// clickColor.red += CLICK_BRIGHTEN_AMOUNT;
+// clickColor = clickColor.to_string();
+// button.add_events(Gdk.EventMask.POINTER_MOTION_MASK);
+// button.connect('motion-notify-event', (widget, event) => {
+// [dummy, cursorX, cursorY] = event.get_coords(); // Get the mouse coordinates relative to the widget
+// if(!clicked) widget.css = `
+// background-image: radial-gradient(circle at ${cursorX}px ${cursorY}px, rgba(0,0,0,0), rgba(0,0,0,0) 0%, rgba(0,0,0,0) 0%, ${clickColor} 0%, ${clickColor} 0%, ${clickColor} 0%, ${clickColor} 0%, rgba(0,0,0,0) 0%, rgba(0,0,0,0) 0%);
+// `;
+// });
+
+// button.connect('button-press-event', (widget, event) => {
+// clicked = true;
+// [dummy, cursorX, cursorY] = event.get_coords(); // Get the mouse coordinates relative to the widget
+// cursorX = Math.round(cursorX); cursorY = Math.round(cursorY);
+// widget.css = `
+// background-image: radial-gradient(circle at ${cursorX}px ${cursorY}px, rgba(0,0,0,0), rgba(0,0,0,0) 0%, rgba(0,0,0,0) 0%, ${clickColor} 0%, ${clickColor} 0%, ${clickColor} 0%, ${clickColor} 0%, rgba(0,0,0,0) 0%, rgba(0,0,0,0) 0%);
+// `;
+// widget.toggleClassName('growingRadial', true);
+// widget.css = `
+// background-image: radial-gradient(circle at ${cursorX}px ${cursorY}px, rgba(0,0,0,0), rgba(0,0,0,0) 0%, rgba(0,0,0,0) 0%, ${clickColor} 0%, ${clickColor} 0%, ${clickColor} 70%, ${clickColor} 70%, rgba(0,0,0,0) 70%, rgba(0,0,0,0) 70%);
+// `
+// });
+// button.connect('button-release-event', (widget, event) => {
+// widget.toggleClassName('growingRadial', false);
+// widget.toggleClassName('fadingRadial', false);
+// widget.css = `
+// background-image: radial-gradient(circle at ${cursorX}px ${cursorY}px, rgba(0,0,0,0), rgba(0,0,0,0) 0%, rgba(0,0,0,0) 0%, rgba(0,0,0,0) 0%, rgba(0,0,0,0) 0%, rgba(0,0,0,0) 70%, rgba(0,0,0,0) 70%, rgba(0,0,0,0) 70%, rgba(0,0,0,0) 70%);
+// `
+// clicked = false;
+// });
diff --git a/linux/home/.config/ags/lib/gtk.ts b/linux/home/.config/ags/lib/gtk.ts
new file mode 100644
index 0000000..8cd60a3
--- /dev/null
+++ b/linux/home/.config/ags/lib/gtk.ts
@@ -0,0 +1,16 @@
+import Gio from "gi://Gio"
+import options from "options"
+
+const settings = new Gio.Settings({
+ schema: "org.gnome.desktop.interface",
+})
+
+function gtk() {
+ const scheme = options.theme.scheme.value
+ settings.set_string("color-scheme", `prefer-${scheme}`)
+}
+
+export default function init() {
+ options.theme.scheme.connect("changed", gtk)
+ gtk()
+}
diff --git a/linux/home/.config/ags/lib/hyprland.ts b/linux/home/.config/ags/lib/hyprland.ts
new file mode 100644
index 0000000..7f6a68c
--- /dev/null
+++ b/linux/home/.config/ags/lib/hyprland.ts
@@ -0,0 +1,80 @@
+import options from "options"
+const { messageAsync } = await Service.import("hyprland")
+
+const {
+ hyprland,
+ theme: {
+ spacing,
+ radius,
+ border: { width },
+ blur,
+ shadows,
+ dark: {
+ primary: { bg: darkActive },
+ },
+ light: {
+ primary: { bg: lightActive },
+ },
+ scheme,
+ },
+} = options
+
+const deps = [
+ "hyprland",
+ spacing.id,
+ radius.id,
+ blur.id,
+ width.id,
+ shadows.id,
+ darkActive.id,
+ lightActive.id,
+ scheme.id,
+]
+
+function activeBorder() {
+ const color = scheme.value === "dark"
+ ? darkActive.value
+ : lightActive.value
+
+ return color.replace("#", "")
+}
+
+function sendBatch(batch: string[]) {
+ const cmd = batch
+ .filter(x => !!x)
+ .map(x => `keyword ${x}`)
+ .join("; ")
+
+ return messageAsync(`[[BATCH]]/${cmd}`)
+}
+
+async function setupHyprland() {
+ const wm_gaps = Math.floor(hyprland.gaps.value * spacing.value)
+
+ //sendBatch([
+ // `general:border_size ${width}`,
+ // `general:gaps_out ${wm_gaps}`,
+ // `general:gaps_in ${Math.floor(wm_gaps / 2)}`,
+ // `general:col.active_border rgba(${activeBorder()}ff)`,
+ // `general:col.inactive_border rgba(${hyprland.inactiveBorder.value})`,
+ // `decoration:rounding ${radius}`,
+ // `decoration:drop_shadow ${shadows.value ? "yes" : "no"}`,
+ // `dwindle:no_gaps_when_only ${hyprland.gapsWhenOnly.value ? 0 : 1}`,
+ // `master:no_gaps_when_only ${hyprland.gapsWhenOnly.value ? 0 : 1}`,
+ //])
+
+ //await sendBatch(App.windows.map(({ name }) => `layerrule unset, ${name}`))
+
+ if (blur.value > 0) {
+ sendBatch(App.windows.flatMap(({ name }) => [
+ `layerrule unset, ${name}`,
+ `layerrule blur, ${name}`,
+ `layerrule ignorealpha ${/* based on shadow color */.29}, ${name}`,
+ ]))
+ }
+}
+
+export default function init() {
+ options.handler(deps, setupHyprland)
+ setupHyprland()
+}
diff --git a/linux/home/.config/ags/lib/iconUtils.js b/linux/home/.config/ags/lib/iconUtils.js
new file mode 100644
index 0000000..baba660
--- /dev/null
+++ b/linux/home/.config/ags/lib/iconUtils.js
@@ -0,0 +1,46 @@
+const { Gio, Gdk, Gtk } = imports.gi;
+
+function fileExists(filePath) {
+ let file = Gio.File.new_for_path(filePath);
+ return file.query_exists(null);
+}
+
+function cartesianProduct(arrays) {
+ if (arrays.length === 0) {
+ return [[]];
+ }
+
+ const [head, ...tail] = arrays;
+ const tailCartesian = cartesianProduct(tail);
+ const result = [];
+
+ for (const item of head) {
+ for (const tailItem of tailCartesian) {
+ result.push([item, ...tailItem]);
+ }
+ }
+ return result;
+}
+import { HOME } from '../utils.ts';
+export const find_icon = app_class => {
+ const themPath = [
+ [`${HOME}/.local/share/icons/WhiteSur/`, `${HOME}/.local/share//icons/WhiteSur-dark/`],
+ ['512x512/', '128x128/', '64x64/', '96x96/', '72x72/', '48x48/', '36x36/'],
+ ['apps/', ''],
+ [app_class + '.png', app_class + '.svg', app_class + '.xpm'],
+ ];
+
+ let real_path = '';
+ const all_icon_dir = cartesianProduct(themPath);
+
+ for (let index = 0; index < all_icon_dir.length; index++) {
+ const pathItem = all_icon_dir[index];
+ const icon_path = pathItem.join('');
+ if (fileExists(icon_path)) {
+ real_path = icon_path;
+ break;
+ }
+ }
+
+ return real_path;
+};
diff --git a/linux/home/.config/ags/lib/icons.ts b/linux/home/.config/ags/lib/icons.ts
new file mode 100644
index 0000000..dfc9e18
--- /dev/null
+++ b/linux/home/.config/ags/lib/icons.ts
@@ -0,0 +1,186 @@
+export const substitutes = {
+ "transmission-gtk": "transmission",
+ "blueberry.py": "blueberry",
+ "Caprine": "facebook-messenger",
+ "phototonic": "terminal-symbolic",
+ "com.raggesilver.BlackBox-symbolic": "terminal-symbolic",
+ "org.wezfurlong.wezterm-symbolic": "terminal-symbolic",
+ "audio-headset-bluetooth": "audio-headphones-symbolic",
+ "audio-card-analog-usb": "audio-speakers-symbolic",
+ "audio-card-analog-pci": "audio-volume-medium-symbolic",
+ "preferences-system": "emblem-system-symbolic",
+ "com.github.Aylur.ags-symbolic": "controls-symbolic",
+ "com.github.Aylur.ags": "controls-symbolic",
+}
+
+export default {
+ missing: "image-missing-symbolic",
+ nix: {
+ nix: "nix-snowflake-symbolic",
+ },
+ app: {
+ terminal: "terminal-symbolic",
+ },
+ fallback: {
+ executable: "application-x-executable",
+ notification: "dialog-information-symbolic",
+ video: "video-x-generic-symbolic",
+ // audio: "audio-x-generic-symbolic",
+ audio: "audio-volume-medium-symbolic",
+ },
+ ui: {
+ close: "window-close-symbolic",
+ colorpicker: "color-select-symbolic",
+ info: "info-symbolic",
+ link: "external-link-symbolic",
+ lock: "system-lock-screen-symbolic",
+ menu: "open-menu-symbolic",
+ refresh: "view-refresh-symbolic",
+ search: "system-search-symbolic",
+ settings: "emblem-system-symbolic",
+ themes: "preferences-desktop-theme-symbolic",
+ tick: "object-select-symbolic",
+ time: "hourglass-symbolic",
+ toolbars: "toolbars-symbolic",
+ warning: "dialog-warning-symbolic",
+ avatar: "avatar-default-symbolic",
+ arrow: {
+ right: "pan-end-symbolic",
+ left: "pan-start-symbolic",
+ down: "pan-down-symbolic",
+ up: "pan-up-symbolic",
+ },
+ },
+ audio: {
+ mic: {
+ muted: "microphone-disabled-symbolic",
+ low: "microphone-sensitivity-low-symbolic",
+ medium: "microphone-sensitivity-medium-symbolic",
+ high: "microphone-sensitivity-high-symbolic",
+ },
+ volume: {
+ muted: "audio-volume-muted-symbolic",
+ low: "audio-volume-low-symbolic",
+ medium: "audio-volume-medium-symbolic",
+ high: "audio-volume-high-symbolic",
+ overamplified: "audio-volume-medium-symbolic",
+ },
+ type: {
+ headset: "audio-headphones-symbolic",
+ speaker: "audio-speakers-symbolic",
+ card: "audio-card-symbolic",
+ },
+ mixer: "mixer-symbolic",
+ },
+ powerprofile: {
+ balanced: "power-profile-balanced-symbolic",
+ "power-saver": "power-profile-power-saver-symbolic",
+ performance: "power-profile-performance-symbolic",
+ },
+ asusctl: {
+ profile: {
+ Balanced: "power-profile-balanced-symbolic",
+ Quiet: "power-profile-power-saver-symbolic",
+ Performance: "power-profile-performance-symbolic",
+ },
+ mode: {
+ Integrated: "processor-symbolic",
+ Hybrid: "controller-symbolic",
+ },
+ },
+ battery: {
+ charging: "battery-flash-symbolic",
+ warning: "battery-empty-symbolic",
+ },
+ bluetooth: {
+ enabled: "bluetooth-active-symbolic",
+ disabled: "bluetooth-disabled-symbolic",
+ },
+ brightness: {
+ indicator: "display-brightness-symbolic",
+ keyboard: "keyboard-brightness-symbolic",
+ screen: "display-brightness-symbolic",
+ },
+ powermenu: {
+ sleep: "weather-clear-night-symbolic",
+ reboot: "system-reboot-symbolic",
+ logout: "system-log-out-symbolic",
+ shutdown: "system-shutdown-symbolic",
+ },
+ recorder: {
+ recording: "media-record-symbolic",
+ },
+ notifications: {
+ noisy: "org.gnome.Settings-notifications-symbolic",
+ silent: "notifications-disabled-symbolic",
+ message: "chat-bubbles-symbolic",
+ },
+ trash: {
+ full: "user-trash-full-symbolic",
+ empty: "user-trash-symbolic",
+ },
+ mpris: {
+ shuffle: {
+ enabled: "media-playlist-shuffle-symbolic",
+ disabled: "media-playlist-consecutive-symbolic",
+ },
+ loop: {
+ none: "media-playlist-repeat-symbolic",
+ track: "media-playlist-repeat-song-symbolic",
+ playlist: "media-playlist-repeat-symbolic",
+ },
+ playing: "media-playback-pause-symbolic",
+ paused: "media-playback-start-symbolic",
+ stopped: "media-playback-start-symbolic",
+ prev: "media-skip-backward-symbolic",
+ next: "media-skip-forward-symbolic",
+ },
+ system: {
+ cpu: "org.gnome.SystemMonitor-symbolic",
+ ram: "drive-harddisk-solidstate-symbolic",
+ temp: "temperature-symbolic",
+ },
+ color: {
+ dark: "dark-mode-symbolic",
+ light: "light-mode-symbolic",
+ },
+ ui: {
+ arch: "archlinux-logo",
+ close: "window-close",
+ colorpicker: "color-select",
+ info: "info",
+ link: "external-link",
+ lock: "system-lock-screen",
+ menu: "open-menu",
+ refresh: "view-refresh",
+ search: "system-search",
+ settings: "emblem-system",
+ themes: "preferences-desktop-theme",
+ tick: "object-select",
+ time: "hourglass",
+ toolbars: "toolbars-symbolic",
+ warning: "dialog-warning",
+ avatar: "avatar-default",
+ tbox_osk: "osk",
+ tbox_appkill: "bomb-kill",
+ tbox_close: "tbox-close",
+ tbox_rotate: "rotation",
+ tbox_moveup: "arrows-up",
+ tbox_movedown: "arrows-down",
+ tbox_moveleft: "arrows-left",
+ tbox_moveright: "arrows-right",
+ tbox_workspacenext: "wp-next",
+ tbox_workspaceprev: "wp-prev",
+ tbox_fullscreen: "fullscreen",
+ tbox_swapnext: "swapnext",
+ tbox_float: "float",
+ tbox_pinned: "pinned",
+ tbox_split: "togglesplit",
+ arrow: {
+ right: "pan-end",
+ left: "pan-start",
+ down: "pan-down",
+ up: "pan-up",
+ },
+ },
+}
diff --git a/linux/home/.config/ags/lib/init.ts b/linux/home/.config/ags/lib/init.ts
new file mode 100644
index 0000000..e9e396c
--- /dev/null
+++ b/linux/home/.config/ags/lib/init.ts
@@ -0,0 +1,19 @@
+import matugen from './matugen';
+import hyprland from './hyprland';
+import tmux from './tmux';
+import gtk from './gtk';
+import lowBattery from './battery';
+import notifications from './notifications';
+
+export default function init() {
+ try {
+ gtk();
+ tmux();
+ matugen();
+ lowBattery();
+ notifications();
+ hyprland();
+ } catch (error) {
+ logError(error);
+ }
+}
diff --git a/linux/home/.config/ags/lib/matugen.ts b/linux/home/.config/ags/lib/matugen.ts
new file mode 100644
index 0000000..dfccccf
--- /dev/null
+++ b/linux/home/.config/ags/lib/matugen.ts
@@ -0,0 +1,113 @@
+import wallpaper from "service/wallpaper"
+import options from "options"
+import { sh, dependencies } from "./utils"
+
+export default function init() {
+ wallpaper.connect("changed", () => matugen())
+ options.autotheme.connect("changed", () => matugen())
+}
+
+function animate(...setters: Array<() => void>) {
+ const delay = options.transition.value / 2
+ setters.forEach((fn, i) => Utils.timeout(delay * i, fn))
+}
+
+export async function matugen(
+ type: "image" | "color" = "image",
+ arg = wallpaper.wallpaper,
+) {
+ if (!options.autotheme.value || !dependencies("matugen"))
+ return
+
+ const colors = await sh(`matugen --dry-run -j hex ${type} ${arg}`)
+ const c = JSON.parse(colors).colors as { light: Colors, dark: Colors }
+ const { dark, light } = options.theme
+
+ animate(
+ () => {
+ dark.widget.value = c.dark.on_surface
+ light.widget.value = c.light.on_surface
+ },
+ () => {
+ dark.border.value = c.dark.outline
+ light.border.value = c.light.outline
+ },
+ () => {
+ dark.bg.value = c.dark.surface
+ light.bg.value = c.light.surface
+ },
+ () => {
+ dark.fg.value = c.dark.on_surface
+ light.fg.value = c.light.on_surface
+ },
+ () => {
+ dark.primary.bg.value = c.dark.primary
+ light.primary.bg.value = c.light.primary
+ options.bar.battery.charging.value = options.theme.scheme.value === "dark"
+ ? c.dark.primary : c.light.primary
+ },
+ () => {
+ dark.primary.fg.value = c.dark.on_primary
+ light.primary.fg.value = c.light.on_primary
+ },
+ () => {
+ dark.error.bg.value = c.dark.error
+ light.error.bg.value = c.light.error
+ },
+ () => {
+ dark.error.fg.value = c.dark.on_error
+ light.error.fg.value = c.light.on_error
+ },
+ )
+}
+
+type Colors = {
+ background: string
+ error: string
+ error_container: string
+ inverse_on_surface: string
+ inverse_primary: string
+ inverse_surface: string
+ on_background: string
+ on_error: string
+ on_error_container: string
+ on_primary: string
+ on_primary_container: string
+ on_primary_fixed: string
+ on_primary_fixed_variant: string
+ on_secondary: string
+ on_secondary_container: string
+ on_secondary_fixed: string
+ on_secondary_fixed_variant: string
+ on_surface: string
+ on_surface_variant: string
+ on_tertiary: string
+ on_tertiary_container: string
+ on_tertiary_fixed: string
+ on_tertiary_fixed_variant: string
+ outline: string
+ outline_variant: string
+ primary: string
+ primary_container: string
+ primary_fixed: string
+ primary_fixed_dim: string
+ scrim: string
+ secondary: string
+ secondary_container: string
+ secondary_fixed: string
+ secondary_fixed_dim: string
+ shadow: string
+ surface: string
+ surface_bright: string
+ surface_container: string
+ surface_container_high: string
+ surface_container_highest: string
+ surface_container_low: string
+ surface_container_lowest: string
+ surface_dim: string
+ surface_variant: string
+ tertiary: string
+ tertiary_container: string
+ tertiary_fixed: string
+ tertiary_fixed_dim: string
+}
diff --git a/linux/home/.config/ags/lib/notifications.ts b/linux/home/.config/ags/lib/notifications.ts
new file mode 100644
index 0000000..0000831
--- /dev/null
+++ b/linux/home/.config/ags/lib/notifications.ts
@@ -0,0 +1,16 @@
+import options from "options"
+const notifs = await Service.import("notifications")
+
+// TODO: consider adding this to upstream
+
+const { blacklist } = options.notifications
+
+export default function init() {
+ const notify = notifs.constructor.prototype.Notify.bind(notifs)
+ notifs.constructor.prototype.Notify = function(appName: string, ...rest: unknown[]) {
+ if (blacklist.value.includes(appName))
+ return Number.MAX_SAFE_INTEGER
+
+ return notify(appName, ...rest)
+ }
+}
diff --git a/linux/home/.config/ags/lib/option.ts b/linux/home/.config/ags/lib/option.ts
new file mode 100644
index 0000000..2d73978
--- /dev/null
+++ b/linux/home/.config/ags/lib/option.ts
@@ -0,0 +1,115 @@
+import { Variable } from "resource:///com/github/Aylur/ags/variable.js"
+
+type OptProps = {
+ persistent?: boolean
+}
+
+export class Opt<T = unknown> extends Variable<T> {
+ static { Service.register(this) }
+
+ constructor(initial: T, { persistent = false }: OptProps = {}) {
+ super(initial)
+ this.initial = initial
+ this.persistent = persistent
+ }
+
+ initial: T
+ id = ""
+ persistent: boolean
+ toString() { return `${this.value}` }
+ toJSON() { return `opt:${this.value}` }
+
+ getValue = (): T => {
+ return super.getValue()
+ }
+
+ init(cacheFile: string) {
+ const cacheV = JSON.parse(Utils.readFile(cacheFile) || "{}")[this.id]
+ if (cacheV !== undefined)
+ this.value = cacheV
+
+ this.connect("changed", () => {
+ const cache = JSON.parse(Utils.readFile(cacheFile) || "{}")
+ cache[this.id] = this.value
+ Utils.writeFileSync(JSON.stringify(cache, null, 2), cacheFile)
+ })
+ }
+
+ reset() {
+ if (this.persistent)
+ return
+
+ if (JSON.stringify(this.value) !== JSON.stringify(this.initial)) {
+ this.value = this.initial
+ return this.id
+ }
+ }
+}
+
+export const opt = <T>(initial: T, opts?: OptProps) => new Opt(initial, opts)
+
+function getOptions(object: object, path = ""): Opt[] {
+ return Object.keys(object).flatMap(key => {
+ const obj: Opt = object[key]
+ const id = path ? path + "." + key : key
+
+ if (obj instanceof Variable) {
+ obj.id = id
+ return obj
+ }
+
+ if (typeof obj === "object")
+ return getOptions(obj, id)
+
+ return []
+ })
+}
+
+export function mkOptions<T extends object>(cacheFile: string, object: T) {
+ for (const opt of getOptions(object))
+ opt.init(cacheFile)
+
+ Utils.ensureDirectory(cacheFile.split("/").slice(0, -1).join("/"))
+
+ const configFile = `${TMP}/config.json`
+ const values = getOptions(object).reduce((obj, { id, value }) => ({ [id]: value, ...obj }), {})
+ Utils.writeFileSync(JSON.stringify(values, null, 2), configFile)
+ Utils.monitorFile(configFile, () => {
+ const cache = JSON.parse(Utils.readFile(configFile) || "{}")
+ for (const opt of getOptions(object)) {
+ if (JSON.stringify(cache[opt.id]) !== JSON.stringify(opt.value))
+ opt.value = cache[opt.id]
+ }
+ })
+
+ function sleep(ms = 0) {
+ return new Promise(r => setTimeout(r, ms))
+ }
+
+ async function reset(
+ [opt, ...list] = getOptions(object),
+ id = opt?.reset(),
+ ): Promise<Array<string>> {
+ if (!opt)
+ return sleep().then(() => [])
+
+ return id
+ ? [id, ...(await sleep(50).then(() => reset(list)))]
+ : await sleep().then(() => reset(list))
+ }
+
+ return Object.assign(object, {
+ configFile,
+ array: () => getOptions(object),
+ async reset() {
+ return (await reset()).join("\n")
+ },
+ handler(deps: string[], callback: () => void) {
+ for (const opt of getOptions(object)) {
+ if (deps.some(i => opt.id.startsWith(i)))
+ opt.connect("changed", callback)
+ }
+ },
+ })
+}
+
diff --git a/linux/home/.config/ags/lib/session.ts b/linux/home/.config/ags/lib/session.ts
new file mode 100644
index 0000000..0e3e0cf
--- /dev/null
+++ b/linux/home/.config/ags/lib/session.ts
@@ -0,0 +1,16 @@
+import GLib from "gi://GLib?version=2.0"
+
+declare global {
+ const OPTIONS: string
+ const TMP: string
+ const USER: string
+}
+
+Object.assign(globalThis, {
+ OPTIONS: `${GLib.get_user_cache_dir()}/ags/options.json`,
+ TMP: `${GLib.get_tmp_dir()}/asztal`,
+ USER: GLib.get_user_name(),
+})
+
+Utils.ensureDirectory(TMP)
+App.addIcons(`${App.configDir}/assets`)
diff --git a/linux/home/.config/ags/lib/tmux.ts b/linux/home/.config/ags/lib/tmux.ts
new file mode 100644
index 0000000..1372eb2
--- /dev/null
+++ b/linux/home/.config/ags/lib/tmux.ts
@@ -0,0 +1,14 @@
+import options from "options"
+import { sh } from "./utils"
+
+export async function tmux() {
+ const { scheme, dark, light } = options.theme
+ const hex = scheme.value === "dark" ? dark.primary.bg.value : light.primary.bg.value
+ if (await sh("which tmux"))
+ sh(`tmux set @main_accent "${hex}"`)
+}
+
+export default function init() {
+ options.theme.dark.primary.bg.connect("changed", tmux)
+ options.theme.light.primary.bg.connect("changed", tmux)
+}
diff --git a/linux/home/.config/ags/lib/utils.ts b/linux/home/.config/ags/lib/utils.ts
new file mode 100644
index 0000000..f3ff2e3
--- /dev/null
+++ b/linux/home/.config/ags/lib/utils.ts
@@ -0,0 +1,113 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import { type Application } from "types/service/applications"
+import icons, { substitutes } from "./icons"
+import Gtk from "gi://Gtk?version=3.0"
+import Gdk from "gi://Gdk"
+import GLib from "gi://GLib?version=2.0"
+
+export const HOME = GLib.get_home_dir();
+
+export type Binding<T> = import("types/service").Binding<any, any, T>
+
+/**
+ * @returns substitute icon || name || fallback icon
+ */
+export function icon(name: string | null, fallback = icons.missing) {
+ if (!name)
+ return fallback || ""
+
+ if (GLib.file_test(name, GLib.FileTest.EXISTS))
+ return name
+
+ const icon = (substitutes[name] || name)
+ if (Utils.lookUpIcon(icon))
+ return icon
+
+ print(`no icon substitute "${icon}" for "${name}", fallback: "${fallback}"`)
+ return fallback
+}
+
+/**
+ * @returns execAsync(["bash", "-c", cmd])
+ */
+export async function bash(strings: TemplateStringsArray | string, ...values: unknown[]) {
+ const cmd = typeof strings === "string" ? strings : strings
+ .flatMap((str, i) => str + `${values[i] ?? ""}`)
+ .join("")
+
+ return Utils.execAsync(["bash", "-c", cmd]).catch(err => {
+ console.error(cmd, err)
+ return ""
+ })
+}
+
+/**
+ * @returns execAsync(cmd)
+ */
+export async function sh(cmd: string | string[]) {
+ return Utils.execAsync(cmd).catch(err => {
+ console.error(typeof cmd === "string" ? cmd : cmd.join(" "), err)
+ return ""
+ })
+}
+
+export function forMonitors(widget: (monitor: number) => Gtk.Window) {
+ const n = Gdk.Display.get_default()?.get_n_monitors() || 1
+ return range(n, 0).map(widget).flat(1)
+}
+
+/**
+ * @returns [start...length]
+ */
+export function range(length: number, start = 1) {
+ return Array.from({ length }, (_, i) => i + start)
+}
+
+/**
+ * @returns true if all of the `bins` are found
+ */
+export function dependencies(...bins: string[]) {
+ const missing = bins.filter(bin => {
+ return !Utils.exec(`which ${bin}`)
+ })
+
+ if (missing.length > 0) {
+ console.warn("missing dependencies:", missing.join(", "))
+ Utils.notify(`missing dependencies: ${missing.join(", ")}`)
+ }
+
+ return missing.length === 0
+}
+
+/**
+ * run app detached
+ */
+export function launchApp(app: Application) {
+ const exe = app.executable
+ .split(/\s+/)
+ .filter(str => !str.startsWith("%") && !str.startsWith("@"))
+ .join(" ")
+
+ bash(`${exe} &`)
+ app.frequency += 1
+}
+
+/**
+ * to use with drag and drop
+ */
+export function createSurfaceFromWidget(widget: Gtk.Widget) {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const cairo = imports.gi.cairo as any
+ const alloc = widget.get_allocation()
+ const surface = new cairo.ImageSurface(
+ cairo.Format.ARGB32,
+ alloc.width,
+ alloc.height,
+ )
+ const cr = new cairo.Context(surface)
+ cr.setSourceRGBA(255, 255, 255, 0)
+ cr.rectangle(0, 0, alloc.width, alloc.height)
+ cr.fill()
+ widget.draw(cr)
+ return surface
+}
diff --git a/linux/home/.config/ags/lib/variables.ts b/linux/home/.config/ags/lib/variables.ts
new file mode 100644
index 0000000..78d8793
--- /dev/null
+++ b/linux/home/.config/ags/lib/variables.ts
@@ -0,0 +1,47 @@
+import GLib from "gi://GLib"
+// import options from "options"
+//
+// const intval = options.system.fetchInterval.value
+// const tempPath = options.system.temperature.value
+
+export const clock = Variable(GLib.DateTime.new_now_local(), {
+ poll: [1000, () => GLib.DateTime.new_now_local()],
+})
+
+export const uptime = Variable(0, {
+ poll: [60_000, "cat /proc/uptime", line =>
+ Number.parseInt(line.split(".")[0]) / 60,
+ ],
+})
+
+
+export const user = {
+ name: GLib.get_user_name()
+}
+
+export const distro = {
+ id: GLib.get_os_info("ID"),
+ logo: GLib.get_os_info("LOGO"),
+}
+
+// const divide = ([total, free]: string[]) => Number.parseInt(free) / Number.parseInt(total)
+//
+// export const cpu = Variable(0, {
+// poll: [intval, "top -b -n 1", out => divide(["100", out.split("\n")
+// .find(line => line.includes("Cpu(s)"))
+// ?.split(/\s+/)[1]
+// .replace(",", ".") || "0"])],
+// })
+//
+// export const ram = Variable(0, {
+// poll: [intval, "free", out => divide(out.split("\n")
+// .find(line => line.includes("Mem:"))
+// ?.split(/\s+/)
+// .splice(1, 2) || ["1", "1"])],
+// })
+//
+// export const temperature = Variable(0, {
+// poll: [intval, `cat ${tempPath}`, n => {
+// return Number.parseInt(n) / 100_000
+// }],
+// })
diff --git a/linux/home/.config/ags/main.ts b/linux/home/.config/ags/main.ts
new file mode 100644
index 0000000..fa18846
--- /dev/null
+++ b/linux/home/.config/ags/main.ts
@@ -0,0 +1,47 @@
+import "lib/session"
+import "style/style"
+import init from "lib/init"
+import options from "options"
+import Bar from "widget/bar/Bar"
+import Launcher from "widget/launcher/Launcher"
+import NotificationPopups from "widget/notifications/NotificationPopups"
+import OSD from "widget/osd/OSD"
+import Overview from "widget/overview/Overview"
+import PowerMenu from "widget/powermenu/PowerMenu"
+import ScreenCorners from "widget/bar/ScreenCorners"
+import SettingsDialog from "widget/settings/SettingsDialog"
+import Verification from "widget/powermenu/Verification"
+import { forMonitors } from "lib/utils"
+import { setupQuickSettings } from "widget/quicksettings/QuickSettings"
+import { setupDateMenu } from "widget/datemenu/DateMenu"
+//import Dock from "widget/dock/Dock";
+import FloatingDock from "widget/dock/FloatingDock"
+import ToolBoxDock from "widget/dock/ToolBoxDock"
+
+App.config({
+ onConfigParsed: () => {
+ setupQuickSettings()
+ setupDateMenu()
+ init()
+ },
+ closeWindowDelay: {
+ "launcher": options.transition.value,
+ "overview": options.transition.value,
+ "quicksettings": options.transition.value,
+ "datemenu": options.transition.value,
+ },
+ windows: () => [
+ ...forMonitors(Bar),
+ //...forMonitors(Dock),
+ ...forMonitors(FloatingDock),
+ //...forMonitors(ToolBoxDock),
+ ...forMonitors(NotificationPopups),
+ ...forMonitors(ScreenCorners),
+ ...forMonitors(OSD),
+ Launcher(),
+ Overview(),
+ PowerMenu(),
+ SettingsDialog(),
+ Verification(),
+ ],
+})
diff --git a/linux/home/.config/ags/options.ts b/linux/home/.config/ags/options.ts
new file mode 100644
index 0000000..4cf5a53
--- /dev/null
+++ b/linux/home/.config/ags/options.ts
@@ -0,0 +1,261 @@
+import { opt, mkOptions } from 'lib/option';
+import { distro } from 'lib/variables';
+import { icon } from 'lib/utils';
+import { icons } from 'assets';
+import icons from 'lib/icons';
+//import Dock from "./widgets/dock/index.js";
+
+const options = mkOptions(OPTIONS, {
+ autotheme: opt(false),
+
+ wallpaper: {
+ enable: opt(false),
+ resolution: opt<import('service/wallpaper').Resolution>(1920),
+ market: opt<import('service/wallpaper').Market>('random'),
+ },
+
+ theme: {
+ dark: {
+ primary: {
+ bg: opt('#51a4e7'),
+ fg: opt('#141414'),
+ },
+ error: {
+ bg: opt('#e55f86'),
+ fg: opt('#141414'),
+ },
+ bg: opt('#171717'),
+ fg: opt('#eeeeee'),
+ widget: opt('#eeeeee'),
+ border: opt('#eeeeee'),
+ },
+ light: {
+ primary: {
+ bg: opt('#426ede'),
+ fg: opt('#eeeeee'),
+ },
+ error: {
+ bg: opt('#b13558'),
+ fg: opt('#eeeeee'),
+ },
+ bg: opt('#fffffa'),
+ fg: opt('#080808'),
+ widget: opt('#080808'),
+ border: opt('#080808'),
+ },
+
+ blur: opt(0),
+ scheme: opt<'dark' | 'light'>('dark'),
+ widget: { opacity: opt(94) },
+ border: {
+ width: opt(1),
+ opacity: opt(100),
+ },
+
+ shadows: opt(true),
+ padding: opt(7),
+ spacing: opt(12),
+ radius: opt(11),
+ },
+
+ transition: opt(200),
+
+ font: {
+ size: opt(13),
+ name: opt('Ubuntu Nerd Font'),
+ },
+ bar: {
+ flatButtons: opt(true),
+ position: opt<'top' | 'bottom'>('top'),
+ corners: opt(false),
+ layout: {
+ start: opt<Array<import('widget/bar/Bar').BarWidget>>([
+ 'launcher',
+ 'workspaces',
+ //"taskbar",
+ 'expander',
+ 'messages',
+ ]),
+ center: opt<Array<import('widget/bar/Bar').BarWidget>>(['date']),
+ end: opt<Array<import('widget/bar/Bar').BarWidget>>([
+ 'media',
+ 'expander',
+ //"colorpicker",
+ 'screenrecord',
+ 'battery',
+ 'systray',
+ 'system',
+ 'powermenu',
+ ]),
+ },
+ launcher: {
+ icon: {
+ colored: opt(true),
+ icon: opt(icon(distro.logo, icons.ui.search)),
+ },
+ label: {
+ colored: opt(false),
+ label: opt(''),
+ //label: opt(" Applications"),
+ },
+ action: opt(() => App.toggleWindow('launcher')),
+ },
+ date: {
+ format: opt('%a %d %b %Y %H:%M:%S'),
+ action: opt(() => App.toggleWindow('datemenu')),
+ },
+ battery: {
+ bar: opt<'hidden' | 'regular' | 'whole'>('regular'),
+ charging: opt('#00D787'),
+ percentage: opt(true),
+ blocks: opt(7),
+ width: opt(50),
+ low: opt(30),
+ },
+ workspaces: {
+ workspaces: opt(6),
+ },
+ taskbar: {
+ iconSize: opt(0),
+ monochrome: opt(false),
+ exclusive: opt(false),
+ },
+ messages: {
+ action: opt(() => App.toggleWindow('datemenu')),
+ },
+ systray: {
+ ignore: opt([
+ 'KDE Connect Indicator',
+ //"spotify-client",
+ ]),
+ },
+ media: {
+ monochrome: opt(false),
+ preferred: opt('spotify'),
+ direction: opt<'left' | 'right'>('right'),
+ format: opt('{artists} - {title}'),
+ length: opt(40),
+ },
+ powermenu: {
+ monochrome: opt(false),
+ action: opt(() => App.toggleWindow('powermenu')),
+ },
+ },
+
+ dock: {
+ iconSize: opt(44),
+ pinnedApps: opt([
+ 'nemo',
+ 'firefox',
+ 'mullvad',
+ 'qbittorrent',
+ 'com.obsproject.Studio',
+ 'vlc',
+ 'spotify',
+ //"viewnior",
+ //"phototonic",
+ 'gthumb',
+ 'nomachine',
+ 'lutris',
+ 'steam',
+ 'discord',
+ 'vscode',
+ 'wezterm',
+ 'obsidian',
+ ]),
+ toolbox: {
+ icons: [opt(icon(icons.ui.tbox_close)), opt(icon(icons.ui.tbox_appkill)), opt(icon(icons.ui.tbox_rotate)), opt(icon(icons.ui.tbox_workspaceprev)), opt(icon(icons.ui.tbox_workspacenext)), opt(icon(icons.ui.tbox_moveleft)), opt(icon(icons.ui.tbox_moveright)), opt(icon(icons.ui.tbox_moveup)), opt(icon(icons.ui.tbox_movedown)), opt(icon(icons.ui.tbox_swapnext)), opt(icon(icons.ui.tbox_split)), opt(icon(icons.ui.tbox_float)), opt(icon(icons.ui.tbox_pinned)), opt(icon(icons.ui.tbox_fullscreen)), opt(icon(icons.ui.tbox_osk))],
+ },
+ },
+ launcher: {
+ width: opt(0),
+ margin: opt(80),
+ nix: {
+ pkgs: opt('nixpkgs/nixos-unstable'),
+ max: opt(8),
+ },
+ sh: {
+ max: opt(16),
+ },
+ apps: {
+ iconSize: opt(62),
+ max: opt(6),
+ favorites: opt([['firefox', 'nemo', 'obsidian', 'discord', 'spotify']]),
+ },
+ },
+
+ overview: {
+ scale: opt(9),
+ workspaces: opt(6),
+ monochromeIcon: opt(false),
+ },
+
+ powermenu: {
+ //sleep: opt('systemctl suspend'),
+ sleep: opt('loginctl suspend'),
+ //reboot: opt('reboot'),
+ reboot: opt('loginctl reboot'),
+ logout: opt('pkill Hyprland'),
+ //shutdown: opt('shutdown now'),
+ shutdown: opt('loginctl poweroff'),
+ layout: opt<'line' | 'box'>('line'),
+ labels: opt(true),
+ },
+
+ quicksettings: {
+ avatar: {
+ image: opt(`/var/lib/AccountsService/icons/${Utils.USER}`),
+ size: opt(40),
+ },
+ width: opt(380),
+ position: opt<'left' | 'center' | 'right'>('right'),
+ networkSettings: opt('gtk-launch nm-connection-editor'),
+ //networkSettings: opt('gtk-launch gnome-control-center'),
+ media: {
+ monochromeIcon: opt(true),
+ coverSize: opt(100),
+ },
+ },
+
+ datemenu: {
+ position: opt<'left' | 'center' | 'right'>('center'),
+ weather: {
+ interval: opt(60_000),
+ unit: opt<'metric' | 'imperial' | 'standard'>('metric'),
+ key: opt<string>(JSON.parse(Utils.readFile(`${App.configDir}/.weather`) || '{}')?.key || ''),
+ cities: opt<Array<number>>(JSON.parse(Utils.readFile(`${App.configDir}/.weather`) || '{}')?.cities || []),
+ },
+ },
+
+ osd: {
+ progress: {
+ vertical: opt(true),
+ pack: {
+ h: opt<'start' | 'center' | 'end'>('end'),
+ v: opt<'start' | 'center' | 'end'>('center'),
+ },
+ },
+ microphone: {
+ pack: {
+ h: opt<'start' | 'center' | 'end'>('center'),
+ v: opt<'start' | 'center' | 'end'>('end'),
+ },
+ },
+ },
+
+ notifications: {
+ position: opt<Array<'top' | 'bottom' | 'left' | 'right'>>(['top', 'right']),
+ blacklist: opt(['']),
+ //blacklist: opt(["Spotify"]),
+ width: opt(440),
+ },
+
+ hyprland: {
+ gaps: opt(2.4),
+ inactiveBorder: opt('333333ff'),
+ gapsWhenOnly: opt(false),
+ },
+});
+
+globalThis['options'] = options;
+export default options;
diff --git a/linux/home/.config/ags/package.json b/linux/home/.config/ags/package.json
new file mode 100644
index 0000000..e0586f7
--- /dev/null
+++ b/linux/home/.config/ags/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "ags",
+ "author": "srdusr",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/srdusr/dotfiles.git"
+ },
+ "devDependencies": {
+ "@girs/accountsservice-1.0": "^1.0.0-3.2.7",
+ "@typescript-eslint/eslint-plugin": "^6.20.0",
+ "eslint": "^8.56.0",
+ "eslint-config-standard-with-typescript": "^43.0.1",
+ "eslint-plugin-import": "^2.29.1",
+ "eslint-plugin-n": "^16.6.2",
+ "eslint-plugin-promise": "^6.1.1",
+ "typescript": "^5.3.3"
+ }
+}
diff --git a/linux/home/.config/ags/service/asusctl.ts b/linux/home/.config/ags/service/asusctl.ts
new file mode 100644
index 0000000..16acbd7
--- /dev/null
+++ b/linux/home/.config/ags/service/asusctl.ts
@@ -0,0 +1,52 @@
+import { sh } from "lib/utils"
+
+type Profile = "Performance" | "Balanced" | "Quiet"
+type Mode = "Hybrid" | "Integrated"
+
+class Asusctl extends Service {
+ static {
+ Service.register(this, {}, {
+ "profile": ["string", "r"],
+ "mode": ["string", "r"],
+ })
+ }
+
+ available = !!Utils.exec("which asusctl")
+ #profile: Profile = "Balanced"
+ #mode: Mode = "Hybrid"
+
+ async nextProfile() {
+ await sh("asusctl profile -n")
+ const profile = await sh("asusctl profile -p")
+ const p = profile.split(" ")[3] as Profile
+ this.#profile = p
+ this.changed("profile")
+ }
+
+ async setProfile(prof: Profile) {
+ await sh(`asusctl profile --profile-set ${prof}`)
+ this.#profile = prof
+ this.changed("profile")
+ }
+
+ async nextMode() {
+ await sh(`supergfxctl -m ${this.#mode === "Hybrid" ? "Integrated" : "Hybrid"}`)
+ this.#mode = await sh("supergfxctl -g") as Mode
+ this.changed("profile")
+ }
+
+ constructor() {
+ super()
+
+ if (this.available) {
+ sh("asusctl profile -p").then(p => this.#profile = p.split(" ")[3] as Profile)
+ sh("supergfxctl -g").then(m => this.#mode = m as Mode)
+ }
+ }
+
+ get profiles(): Profile[] { return ["Performance", "Balanced", "Quiet"] }
+ get profile() { return this.#profile }
+ get mode() { return this.#mode }
+}
+
+export default new Asusctl
diff --git a/linux/home/.config/ags/service/brightness.ts b/linux/home/.config/ags/service/brightness.ts
new file mode 100644
index 0000000..a0b8eb5
--- /dev/null
+++ b/linux/home/.config/ags/service/brightness.ts
@@ -0,0 +1,69 @@
+import { bash, dependencies, sh } from "lib/utils"
+
+if (!dependencies("brightnessctl"))
+ App.quit()
+
+const get = (args: string) => Number(Utils.exec(`brightnessctl ${args}`))
+const screen = await bash`ls -w1 /sys/class/backlight | head -1`
+const kbd = await bash`ls -w1 /sys/class/leds | head -1`
+
+class Brightness extends Service {
+ static {
+ Service.register(this, {}, {
+ "screen": ["float", "rw"],
+ "kbd": ["int", "rw"],
+ })
+ }
+
+ #kbdMax = get(`--device ${kbd} max`)
+ #kbd = get(`--device ${kbd} get`)
+ #screenMax = get("max")
+ #screen = get("get") / get("max")
+
+ get kbd() { return this.#kbd }
+ get screen() { return this.#screen }
+
+ set kbd(value) {
+ if (value < 0 || value > this.#kbdMax)
+ return
+
+ sh(`brightnessctl -d ${kbd} s ${value} -q`).then(() => {
+ this.#kbd = value
+ this.changed("kbd")
+ })
+ }
+
+ set screen(percent) {
+ if (percent < 0)
+ percent = 0
+
+ if (percent > 1)
+ percent = 1
+
+ sh(`brightnessctl set ${Math.floor(percent * 100)}% -q`).then(() => {
+ this.#screen = percent
+ this.changed("screen")
+ })
+ }
+
+ constructor() {
+ super()
+
+ const screenPath = `/sys/class/backlight/${screen}/brightness`
+ const kbdPath = `/sys/class/leds/${kbd}/brightness`
+
+ Utils.monitorFile(screenPath, async f => {
+ const v = await Utils.readFileAsync(f)
+ this.#screen = Number(v) / this.#screenMax
+ this.changed("screen")
+ })
+
+ Utils.monitorFile(kbdPath, async f => {
+ const v = await Utils.readFileAsync(f)
+ this.#kbd = Number(v) / this.#kbdMax
+ this.changed("kbd")
+ })
+ }
+}
+
+export default new Brightness
diff --git a/linux/home/.config/ags/service/colorpicker.ts b/linux/home/.config/ags/service/colorpicker.ts
new file mode 100644
index 0000000..5918f31
--- /dev/null
+++ b/linux/home/.config/ags/service/colorpicker.ts
@@ -0,0 +1,56 @@
+import icons from "lib/icons"
+import { bash, dependencies } from "lib/utils"
+
+const COLORS_CACHE = Utils.CACHE_DIR + "/colorpicker.json"
+const MAX_NUM_COLORS = 10
+
+class ColorPicker extends Service {
+ static {
+ Service.register(this, {}, {
+ "colors": ["jsobject"],
+ })
+ }
+
+ #notifID = 0
+ #colors = JSON.parse(Utils.readFile(COLORS_CACHE) || "[]") as string[]
+
+ get colors() { return [...this.#colors] }
+ set colors(colors) {
+ this.#colors = colors
+ this.changed("colors")
+ }
+
+ // TODO: doesn't work?
+ async wlCopy(color: string) {
+ if (dependencies("wl-copy"))
+ bash(`wl-copy ${color}`)
+ }
+
+ readonly pick = async () => {
+ if (!dependencies("hyprpicker"))
+ return
+
+ const color = await bash("hyprpicker -a -r")
+ if (!color)
+ return
+
+ this.wlCopy(color)
+ const list = this.colors
+ if (!list.includes(color)) {
+ list.push(color)
+ if (list.length > MAX_NUM_COLORS)
+ list.shift()
+
+ this.colors = list
+ Utils.writeFile(JSON.stringify(list, null, 2), COLORS_CACHE)
+ }
+
+ this.#notifID = await Utils.notify({
+ id: this.#notifID,
+ iconName: icons.ui.colorpicker,
+ summary: color,
+ })
+ }
+}
+
+export default new ColorPicker
diff --git a/linux/home/.config/ags/service/nix.ts b/linux/home/.config/ags/service/nix.ts
new file mode 100644
index 0000000..3bde9fc
--- /dev/null
+++ b/linux/home/.config/ags/service/nix.ts
@@ -0,0 +1,109 @@
+import icons from "lib/icons"
+import { bash, dependencies } from "lib/utils"
+import options from "options"
+
+const CACHE = `${Utils.CACHE_DIR}/nixpkgs`
+const PREFIX = "legacyPackages.x86_64-linux."
+const MAX = options.launcher.nix.max
+const nixpkgs = options.launcher.nix.pkgs
+
+export type Nixpkg = {
+ name: string
+ description: string
+ pname: string
+ version: string
+}
+
+class Nix extends Service {
+ static {
+ Service.register(this, {}, {
+ "available": ["boolean", "r"],
+ "ready": ["boolean", "rw"],
+ })
+ }
+
+ #db: { [name: string]: Nixpkg } = {}
+ #ready = true
+
+ private set ready(r: boolean) {
+ this.#ready = r
+ this.changed("ready")
+ }
+
+ get db() { return this.#db }
+ get ready() { return this.#ready }
+ get available() { return Utils.exec("which nix") }
+
+ constructor() {
+ super()
+ if (!this.available)
+ return this
+
+ this.#updateList()
+ nixpkgs.connect("changed", this.#updateList)
+ }
+
+ query = async (filter: string) => {
+ if (!dependencies("fzf", "nix") || !this.#ready)
+ return [] as string[]
+
+ return bash(`cat ${CACHE} | fzf -f ${filter} -e | head -n ${MAX} `)
+ .then(str => str.split("\n").filter(i => i))
+ }
+
+ nix(cmd: string, bin: string, args: string) {
+ return Utils.execAsync(`nix ${cmd} ${nixpkgs}#${bin} --impure ${args}`)
+ }
+
+ run = async (input: string) => {
+ if (!dependencies("nix"))
+ return
+
+ try {
+ const [bin, ...args] = input.trim().split(/\s+/)
+
+ this.ready = false
+ await this.nix("shell", bin, "--command sh -c 'exit'")
+ this.ready = true
+
+ this.nix("run", bin, ["--", ...args].join(" "))
+ } catch (err) {
+ if (typeof err === "string")
+ Utils.notify("NixRun Error", err, icons.nix.nix)
+ else
+ logError(err)
+ } finally {
+ this.ready = true
+ }
+ }
+
+ #updateList = async () => {
+ if (!dependencies("nix"))
+ return
+
+ this.ready = false
+ this.#db = {}
+
+ // const search = await bash(`nix search ${nixpkgs} --json`)
+ const search = ""
+ if (!search) {
+ this.ready = true
+ return
+ }
+
+ const json = Object.entries(JSON.parse(search) as {
+ [name: string]: Nixpkg
+ })
+
+ for (const [pkg, info] of json) {
+ const name = pkg.replace(PREFIX, "")
+ this.#db[name] = { ...info, name }
+ }
+
+ const list = Object.keys(this.#db).join("\n")
+ await Utils.writeFile(list, CACHE)
+ this.ready = true
+ }
+}
+
+export default new Nix
diff --git a/linux/home/.config/ags/service/powermenu.ts b/linux/home/.config/ags/service/powermenu.ts
new file mode 100644
index 0000000..fd16bc1
--- /dev/null
+++ b/linux/home/.config/ags/service/powermenu.ts
@@ -0,0 +1,43 @@
+import options from "options"
+
+const { sleep, reboot, logout, shutdown } = options.powermenu
+
+export type Action = "sleep" | "reboot" | "logout" | "shutdown"
+
+class PowerMenu extends Service {
+ static {
+ Service.register(this, {}, {
+ "title": ["string"],
+ "cmd": ["string"],
+ })
+ }
+
+ #title = ""
+ #cmd = ""
+
+ get title() { return this.#title }
+ get cmd() { return this.#cmd }
+
+ action(action: Action) {
+ [this.#cmd, this.#title] = {
+ sleep: [sleep.value, "Sleep"],
+ reboot: [reboot.value, "Reboot"],
+ logout: [logout.value, "Log Out"],
+ shutdown: [shutdown.value, "Shutdown"],
+ }[action]
+
+ this.notify("cmd")
+ this.notify("title")
+ this.emit("changed")
+ App.closeWindow("powermenu")
+ App.openWindow("verification")
+ }
+
+ readonly shutdown = () => {
+ this.action("shutdown")
+ }
+}
+
+const powermenu = new PowerMenu
+Object.assign(globalThis, { powermenu })
+export default powermenu
diff --git a/linux/home/.config/ags/service/screenrecord.ts b/linux/home/.config/ags/service/screenrecord.ts
new file mode 100644
index 0000000..58721d2
--- /dev/null
+++ b/linux/home/.config/ags/service/screenrecord.ts
@@ -0,0 +1,102 @@
+import GLib from "gi://GLib"
+import icons from "lib/icons"
+import { dependencies, sh, bash } from "lib/utils"
+
+const now = () => GLib.DateTime.new_now_local().format("%Y-%m-%d_%H-%M-%S")
+
+class Recorder extends Service {
+ static {
+ Service.register(this, {}, {
+ "timer": ["int"],
+ "recording": ["boolean"],
+ })
+ }
+
+ #recordings = Utils.HOME + "/Videos/Screencasting"
+ #screenshots = Utils.HOME + "/Pictures/Screenshots"
+ #file = ""
+ #interval = 0
+
+ recording = false
+ timer = 0
+
+ async start() {
+ if (!dependencies("slurp", "wf-recorder"))
+ return
+
+ if (this.recording)
+ return
+
+ Utils.ensureDirectory(this.#recordings)
+ this.#file = `${this.#recordings}/${now()}.mp4`
+ sh(`wf-recorder -g "${await sh("slurp")}" -f ${this.#file} --pixel-format yuv420p`)
+
+ this.recording = true
+ this.changed("recording")
+
+ this.timer = 0
+ this.#interval = Utils.interval(1000, () => {
+ this.changed("timer")
+ this.timer++
+ })
+ }
+
+ async stop() {
+ if (!this.recording)
+ return
+
+ await bash("killall -INT wf-recorder")
+ this.recording = false
+ this.changed("recording")
+ GLib.source_remove(this.#interval)
+
+ Utils.notify({
+ iconName: icons.fallback.video,
+ summary: "Screenrecord",
+ body: this.#file,
+ actions: {
+ "Show in Files": () => sh(`xdg-open ${this.#recordings}`),
+ "View": () => sh(`xdg-open ${this.#file}`),
+ },
+ })
+ }
+
+ async screenshot(full = false) {
+ if (!dependencies("slurp", "wayshot"))
+ return
+
+ const file = `${this.#screenshots}/${now()}.png`
+ Utils.ensureDirectory(this.#screenshots)
+
+ if (full) {
+ await sh(`wayshot -f ${file}`)
+ }
+ else {
+ const size = await sh("slurp")
+ if (!size)
+ return
+
+ await sh(`wayshot -f ${file} -s "${size}"`)
+ }
+
+ bash(`wl-copy < ${file}`)
+
+ Utils.notify({
+ image: file,
+ summary: "Screenshot",
+ body: file,
+ actions: {
+ "Show in Files": () => sh(`xdg-open ${this.#screenshots}`),
+ "View": () => sh(`xdg-open ${file}`),
+ "Edit": () => {
+ if (dependencies("swappy"))
+ sh(`swappy -f ${file}`)
+ },
+ },
+ })
+ }
+}
+
+const recorder = new Recorder
+Object.assign(globalThis, { recorder })
+export default recorder
diff --git a/linux/home/.config/ags/service/wallpaper.ts b/linux/home/.config/ags/service/wallpaper.ts
new file mode 100644
index 0000000..865c6d9
--- /dev/null
+++ b/linux/home/.config/ags/service/wallpaper.ts
@@ -0,0 +1,127 @@
+import options from 'options';
+import { dependencies, sh } from 'lib/utils';
+
+export type Resolution = 1920 | 1366 | 3840;
+export type Market = 'random' | 'en-US' | 'ja-JP' | 'en-AU' | 'en-GB' | 'de-DE' | 'en-NZ' | 'en-CA';
+
+const WP = `${Utils.HOME}/pictures/wallpapers`;
+const Cache = `${Utils.HOME}/Pictures/Wallpapers/Bing`;
+
+class Wallpaper extends Service {
+ static {
+ Service.register(
+ this,
+ {},
+ {
+ wallpaper: ['string'],
+ },
+ );
+ }
+
+ #blockMonitor = false;
+
+ #wallpaper() {
+ if (!dependencies('swww')) return;
+
+ sh('hyprctl cursorpos').then(pos => {
+ sh(['swww', 'img', '--transition-type', 'grow', '--transition-pos', pos.replace(' ', ''), WP]).then(() => {
+ this.changed('wallpaper');
+ });
+ });
+ }
+
+ async #setWallpaper(path: string) {
+ this.#blockMonitor = true;
+
+ await sh(`cp "${path}" "${WP}"`);
+ this.#wallpaper();
+
+ this.#blockMonitor = false;
+ }
+
+ async #fetchBing() {
+ // Check if wallpaper functionality is enabled
+ if (!options.wallpaper.enable.value) {
+ console.log('Wallpaper functionality is disabled.');
+ return;
+ }
+
+ try {
+ const res = await Utils.fetch('https://bing.biturl.top/', {
+ params: {
+ resolution: options.wallpaper.resolution.value,
+ format: 'json',
+ image_format: 'jpg',
+ index: 'random',
+ mkt: options.wallpaper.market.value,
+ },
+ });
+
+ if (!res.ok) {
+ console.warn('Failed to fetch from Bing:', res.statusText);
+ return;
+ }
+
+ const data = await res.json();
+ const { url } = data;
+
+ if (!url) {
+ console.warn('No URL found in Bing response:', data);
+ return;
+ }
+
+ const file = `${Cache}/${url.replace('https://www.bing.com/th?id=', '')}`;
+
+ Utils.ensureDirectory(Cache);
+
+ if (!(await Utils.fileExists(file))) {
+ await sh(`curl "${url}" --output "${file}"`);
+ await this.#setWallpaper(file);
+ } else {
+ console.log(`Wallpaper already exists: ${file}`);
+ }
+ } catch (error) {
+ console.error('Error fetching wallpaper:', error);
+ }
+ }
+
+ readonly random = () => {
+ // Check if wallpaper functionality is enabled
+ if (!options.wallpaper.enable.value) {
+ console.log('Wallpaper functionality is disabled.');
+ return;
+ }
+ this.#fetchBing();
+ };
+
+ readonly set = (path: string) => {
+ this.#setWallpaper(path);
+ };
+
+ get wallpaper() {
+ return WP;
+ }
+ constructor() {
+ super();
+
+ // Respect wallpaper.enable option
+ if (!options.wallpaper.enable.value) {
+ console.log('Wallpaper functionality is disabled, not starting swww-daemon.');
+ return;
+ }
+
+ if (!dependencies('swww')) return;
+
+ // Monitor and set wallpaper if enabled
+ Utils.monitorFile(WP, () => {
+ if (!this.#blockMonitor) this.#wallpaper();
+ });
+
+ // Start swww-daemon only when wallpaper is enabled
+ Utils.execAsync('swww-daemon')
+ .then(this.#wallpaper)
+ .catch(() => null);
+ }
+}
+
+export default new Wallpaper();
diff --git a/linux/home/.config/ags/service/weather.ts b/linux/home/.config/ags/service/weather.ts
new file mode 100644
index 0000000..14f2df2
--- /dev/null
+++ b/linux/home/.config/ags/service/weather.ts
@@ -0,0 +1,59 @@
+import options from "options"
+
+const { interval, key, cities, unit } = options.datemenu.weather
+
+class Weather extends Service {
+ static {
+ Service.register(this, {}, {
+ "forecasts": ["jsobject"],
+ })
+ }
+
+ #forecasts: Forecast[] = []
+ get forecasts() { return this.#forecasts }
+
+ async #fetch(placeid: number) {
+ const url = "https://api.openweathermap.org/data/2.5/forecast"
+ const res = await Utils.fetch(url, {
+ params: {
+ id: placeid,
+ appid: key.value,
+ untis: unit.value,
+ },
+ })
+ return await res.json()
+ }
+
+ constructor() {
+ super()
+ if (!key.value)
+ return this
+
+ Utils.interval(interval.value, () => {
+ Promise.all(cities.value.map(this.#fetch)).then(forecasts => {
+ this.#forecasts = forecasts as Forecast[]
+ this.changed("forecasts")
+ })
+ })
+ }
+}
+
+export default new Weather
+
+type Forecast = {
+ city: {
+ name: string,
+ }
+ list: Array<{
+ dt: number
+ main: {
+ temp: number
+ feels_like: number
+ },
+ weather: Array<{
+ main: string,
+ description: string,
+ icon: string,
+ }>
+ }>
+}
diff --git a/linux/home/.config/ags/style/extra.scss b/linux/home/.config/ags/style/extra.scss
new file mode 100644
index 0000000..e7f9d44
--- /dev/null
+++ b/linux/home/.config/ags/style/extra.scss
@@ -0,0 +1,67 @@
+@import './mixins/button.scss';
+
+* {
+ font-size: $font-size;
+ font-family: $font-name;
+}
+
+separator {
+ &.horizontal {
+ min-height: $border-width;
+ }
+
+ &.vertical {
+ min-width: $border-width;
+ }
+}
+
+window.popup {
+ >* {
+ border: none;
+ box-shadow: none;
+ }
+
+ menu {
+ border-radius: $popover-radius;
+ background-color: $bg;
+ padding: $popover-padding;
+ border: $border-width solid $popover-border-color;
+
+ separator {
+ background-color: $border-color;
+ }
+
+ menuitem {
+ @include button;
+ padding: $spacing * .5;
+ margin: ($spacing * .5) 0;
+
+ &:first-child {
+ margin-top: 0;
+ }
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+ }
+}
+
+tooltip {
+ * {
+ all: unset;
+ }
+
+ background-color: transparent;
+ border: none;
+
+ >*>* {
+ background-color: $bg;
+ border-radius: $radius;
+ border: $border-width solid $popover-border-color;
+ color: $fg;
+ padding: 8px;
+ margin: 4px;
+ box-shadow: 0 0 3px 0 $shadow-color;
+ }
+}
diff --git a/linux/home/.config/ags/style/mixins/a11y-button.scss b/linux/home/.config/ags/style/mixins/a11y-button.scss
new file mode 100644
index 0000000..00b24c6
--- /dev/null
+++ b/linux/home/.config/ags/style/mixins/a11y-button.scss
@@ -0,0 +1,48 @@
+@import './button';
+
+@mixin accs-button($flat: false, $reactive: true) {
+ @include unset;
+ color: $fg;
+
+ >* {
+ border-radius: $radius;
+ transition: $transition;
+
+ @if $flat {
+ background-color: transparent;
+ box-shadow: none;
+ }
+
+ @else {
+ background-color: $widget-bg;
+ box-shadow: inset 0 0 0 $border-width $border-color;
+ }
+ }
+
+
+ @if $reactive {
+
+ &:focus>*,
+ &.focused>* {
+ @include button-focus;
+ }
+
+ &:hover>* {
+ @include button-hover;
+ }
+
+ &:active,
+ &.active,
+ &.on,
+ &:checked {
+ >* {
+ @include button-active;
+ }
+
+ &:hover>* {
+ box-shadow: inset 0 0 0 $border-width $border-color,
+ inset 0 0 0 99px $hover-bg;
+ }
+ }
+ }
+}
diff --git a/linux/home/.config/ags/style/mixins/button.scss b/linux/home/.config/ags/style/mixins/button.scss
new file mode 100644
index 0000000..79ec275
--- /dev/null
+++ b/linux/home/.config/ags/style/mixins/button.scss
@@ -0,0 +1,70 @@
+@mixin button-focus() {
+ box-shadow: inset 0 0 0 $border-width $primary-bg;
+ background-color: $hover-bg;
+ color: $hover-fg;
+}
+
+@mixin button-hover() {
+ box-shadow: inset 0 0 0 $border-width $border-color;
+ background-color: $hover-bg;
+ color: $hover-fg;
+}
+
+@mixin button-active() {
+ box-shadow: inset 0 0 0 $border-width $border-color;
+ background-image: $active-gradient;
+ background-color: $primary-bg;
+ color: $primary-fg;
+}
+
+@mixin button-disabled() {
+ box-shadow: none;
+ background-color: transparent;
+ color: transparentize($fg, 0.7);
+}
+
+@mixin button($flat: false, $reactive: true, $radius: $radius, $focusable: true) {
+ all: unset;
+ transition: $transition;
+ border-radius: $radius;
+ color: $fg;
+
+ @if $flat {
+ background-color: transparent;
+ background-image: none;
+ box-shadow: none;
+ }
+
+ @else {
+ background-color: $widget-bg;
+ box-shadow: inset 0 0 0 $border-width $border-color;
+ }
+
+ @if $reactive {
+ @if $focusable {
+ &:focus {
+ @include button-focus;
+ }
+ }
+
+ &:hover {
+ @include button-hover;
+ }
+
+ &:active,
+ &.on,
+ &.active,
+ &:checked {
+ @include button-active;
+
+ &:hover {
+ box-shadow: inset 0 0 0 $border-width $border-color,
+ inset 0 0 0 99px $hover-bg;
+ }
+ }
+ }
+
+ &:disabled {
+ @include button-disabled;
+ }
+}
diff --git a/linux/home/.config/ags/style/mixins/floating-widget.scss b/linux/home/.config/ags/style/mixins/floating-widget.scss
new file mode 100644
index 0000000..613668d
--- /dev/null
+++ b/linux/home/.config/ags/style/mixins/floating-widget.scss
@@ -0,0 +1,12 @@
+@mixin floating-widget {
+ @if $shadows {
+ box-shadow: 0 0 5px 0 $shadow-color;
+ }
+
+ margin: max($spacing, 8px);
+ border: $border-width solid $popover-border-color;
+ background-color: $bg;
+ color: $fg;
+ border-radius: $popover-radius;
+ padding: $popover-padding;
+}
diff --git a/linux/home/.config/ags/style/mixins/hidden.scss b/linux/home/.config/ags/style/mixins/hidden.scss
new file mode 100644
index 0000000..ea6a42c
--- /dev/null
+++ b/linux/home/.config/ags/style/mixins/hidden.scss
@@ -0,0 +1,15 @@
+@mixin hidden {
+ background-color: transparent;
+ background-image: none;
+ border-color: transparent;
+ box-shadow: none;
+ -gtk-icon-transform: scale(0);
+
+ * {
+ background-color: transparent;
+ background-image: none;
+ border-color: transparent;
+ box-shadow: none;
+ -gtk-icon-transform: scale(0);
+ }
+}
diff --git a/linux/home/.config/ags/style/mixins/media.scss b/linux/home/.config/ags/style/mixins/media.scss
new file mode 100644
index 0000000..3178029
--- /dev/null
+++ b/linux/home/.config/ags/style/mixins/media.scss
@@ -0,0 +1,42 @@
+@mixin media() {
+ @include widget;
+ padding: $padding;
+
+ .cover {
+ @if $shadows {
+ box-shadow: 2px 2px 2px 0 $shadow-color;
+ }
+
+ background-size: cover;
+ background-position: center;
+ border-radius: $radius*0.8;
+ margin-right: $spacing;
+ }
+
+ button {
+ @include button($flat: true);
+ padding: $padding * .5;
+
+ &.play-pause {
+ margin: 0 ($spacing * .5);
+ }
+
+ image {
+ font-size: 1.2em;
+ }
+ }
+
+ .artist {
+ color: transparentize($fg, .2);
+ font-size: .9em;
+ }
+
+ scale {
+ @include slider($width: .5em, $slider: false, $gradient: linear-gradient($fg, $fg));
+ margin-bottom: $padding * .5;
+
+ trough {
+ border: none;
+ }
+ }
+}
diff --git a/linux/home/.config/ags/style/mixins/scrollable.scss b/linux/home/.config/ags/style/mixins/scrollable.scss
new file mode 100644
index 0000000..b66f246
--- /dev/null
+++ b/linux/home/.config/ags/style/mixins/scrollable.scss
@@ -0,0 +1,42 @@
+@mixin scrollable($top: false, $bottom: false) {
+
+ @if $top and $shadows {
+ undershoot.top {
+ background: linear-gradient(to bottom, $shadow-color, transparent, transparent, transparent, transparent, transparent);
+ }
+ }
+
+ @if $bottom and $shadows {
+ undershoot.bottom {
+ background: linear-gradient(to top, $shadow-color, transparent, transparent, transparent, transparent, transparent);
+ }
+ }
+
+ scrollbar,
+ scrollbar * {
+ all: unset;
+ }
+
+ scrollbar.vertical {
+ transition: $transition;
+ background-color: transparentize($bg, 0.7);
+
+ &:hover {
+ background-color: transparentize($bg, 0.3);
+
+ slider {
+ background-color: transparentize($fg, 0.3);
+ min-width: .6em;
+ }
+ }
+ }
+
+
+ scrollbar.vertical slider {
+ background-color: transparentize($fg, 0.5);
+ border-radius: $radius;
+ min-width: .4em;
+ min-height: 2em;
+ transition: $transition;
+ }
+}
diff --git a/linux/home/.config/ags/style/mixins/slider.scss b/linux/home/.config/ags/style/mixins/slider.scss
new file mode 100644
index 0000000..b90e566
--- /dev/null
+++ b/linux/home/.config/ags/style/mixins/slider.scss
@@ -0,0 +1,74 @@
+@import './unset';
+
+@mixin slider($width: 0.7em, $slider-width: .5em, $gradient: $active-gradient, $slider: true, $focusable: true, $radius: $radius) {
+ @include unset($rec: true);
+
+ trough {
+ transition: $transition;
+ border-radius: $radius;
+ border: $border;
+ background-color: $widget-bg;
+ min-height: $width;
+ min-width: $width;
+
+ highlight,
+ progress {
+ border-radius: max($radius - $border-width, 0);
+ background-image: $gradient;
+ min-height: $width;
+ min-width: $width;
+ }
+ }
+
+ slider {
+ box-shadow: none;
+ background-color: transparent;
+ border: $border-width solid transparent;
+ transition: $transition;
+ border-radius: $radius;
+ min-height: $width;
+ min-width: $width;
+ margin: -$slider-width;
+ }
+
+ &:hover {
+ trough {
+ background-color: $hover-bg;
+ }
+
+ slider {
+ @if $slider {
+ background-color: $fg;
+ border-color: $border-color;
+
+ @if $shadows {
+ box-shadow: 0 0 3px 0 $shadow-color;
+ }
+ }
+ }
+ }
+
+ &:disabled {
+
+ highlight,
+ progress {
+ background-color: transparentize($fg, 0.4);
+ background-image: none;
+ }
+ }
+
+ @if $focusable {
+ trough:focus {
+ background-color: $hover-bg;
+ box-shadow: inset 0 0 0 $border-width $primary-bg;
+
+ slider {
+ @if $slider {
+ background-color: $fg;
+ box-shadow: inset 0 0 0 $border-width $primary-bg;
+ }
+ }
+ }
+
+ }
+}
diff --git a/linux/home/.config/ags/style/mixins/spacing.scss b/linux/home/.config/ags/style/mixins/spacing.scss
new file mode 100644
index 0000000..4096fba
--- /dev/null
+++ b/linux/home/.config/ags/style/mixins/spacing.scss
@@ -0,0 +1,53 @@
+@mixin spacing($multiplier: 1, $spacing: $spacing, $rec: false) {
+ &.horizontal>* {
+ margin: 0 calc($spacing * $multiplier / 2);
+
+ &:first-child {
+ margin-left: 0;
+ }
+
+ &:last-child {
+ margin-right: 0;
+ }
+ }
+
+ &.vertical>* {
+ margin: calc($spacing * $multiplier / 2) 0;
+
+ &:first-child {
+ margin-top: 0;
+ }
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+
+ @if $rec {
+ box {
+ &.horizontal>* {
+ margin: 0 $spacing * $multiplier / 2;
+
+ &:first-child {
+ margin-left: 0;
+ }
+
+ &:last-child {
+ margin-right: 0;
+ }
+ }
+
+ &.vertical>* {
+ margin: $spacing * $multiplier / 2 0;
+
+ &:first-child {
+ margin-top: 0;
+ }
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+ }
+ }
+}
diff --git a/linux/home/.config/ags/style/mixins/switch.scss b/linux/home/.config/ags/style/mixins/switch.scss
new file mode 100644
index 0000000..2abf360
--- /dev/null
+++ b/linux/home/.config/ags/style/mixins/switch.scss
@@ -0,0 +1,16 @@
+@import './button';
+
+@mixin switch {
+ @include button;
+
+ slider {
+ background-color: $primary-fg;
+ border-radius: $radius;
+ min-width: 24px;
+ min-height: 24px;
+ }
+
+ image {
+ color: transparent;
+ }
+}
diff --git a/linux/home/.config/ags/style/mixins/unset.scss b/linux/home/.config/ags/style/mixins/unset.scss
new file mode 100644
index 0000000..eb80af5
--- /dev/null
+++ b/linux/home/.config/ags/style/mixins/unset.scss
@@ -0,0 +1,9 @@
+@mixin unset($rec: false) {
+ all: unset;
+
+ @if $rec {
+ * {
+ all: unset
+ }
+ }
+}
diff --git a/linux/home/.config/ags/style/mixins/widget.scss b/linux/home/.config/ags/style/mixins/widget.scss
new file mode 100644
index 0000000..053f1aa
--- /dev/null
+++ b/linux/home/.config/ags/style/mixins/widget.scss
@@ -0,0 +1,7 @@
+@mixin widget {
+ transition: $transition;
+ border-radius: $radius;
+ color: $fg;
+ background-color: $widget-bg;
+ border: $border;
+}
diff --git a/linux/home/.config/ags/style/style.ts b/linux/home/.config/ags/style/style.ts
new file mode 100644
index 0000000..a9b94fe
--- /dev/null
+++ b/linux/home/.config/ags/style/style.ts
@@ -0,0 +1,103 @@
+/* eslint-disable max-len */
+import { type Opt } from "lib/option"
+import options from "options"
+import { bash, dependencies, sh } from "lib/utils"
+
+const deps = [
+ "font",
+ "theme",
+ "bar.flatButtons",
+ "bar.position",
+ "bar.battery.charging",
+ "bar.battery.blocks",
+]
+
+const {
+ dark,
+ light,
+ blur,
+ scheme,
+ padding,
+ spacing,
+ radius,
+ shadows,
+ widget,
+ border,
+} = options.theme
+
+const popoverPaddingMultiplier = 1.6
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+const t = (dark: Opt<any> | string, light: Opt<any> | string) => scheme.value === "dark"
+ ? `${dark}` : `${light}`
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+const $ = (name: string, value: string | Opt<any>) => `$${name}: ${value};`
+
+const variables = () => [
+ $("bg", blur.value ? `transparentize(${t(dark.bg, light.bg)}, ${blur.value / 100})` : t(dark.bg, light.bg)),
+ $("fg", t(dark.fg, light.fg)),
+
+ $("primary-bg", t(dark.primary.bg, light.primary.bg)),
+ $("primary-fg", t(dark.primary.fg, light.primary.fg)),
+
+ $("error-bg", t(dark.error.bg, light.error.bg)),
+ $("error-fg", t(dark.error.fg, light.error.fg)),
+
+ $("scheme", scheme),
+ $("padding", `${padding}pt`),
+ $("spacing", `${spacing}pt`),
+ $("radius", `${radius}px`),
+ $("transition", `${options.transition}ms`),
+
+ $("shadows", `${shadows}`),
+
+ $("widget-bg", `transparentize(${t(dark.widget, light.widget)}, ${widget.opacity.value / 100})`),
+
+ $("hover-bg", `transparentize(${t(dark.widget, light.widget)}, ${(widget.opacity.value * .9) / 100})`),
+ $("hover-fg", `lighten(${t(dark.fg, light.fg)}, 8%)`),
+
+ $("border-width", `${border.width}px`),
+ $("border-color", `transparentize(${t(dark.border, light.border)}, ${border.opacity.value / 100})`),
+ $("border", "$border-width solid $border-color"),
+
+ $("active-gradient", `linear-gradient(to right, ${t(dark.primary.bg, light.primary.bg)}, darken(${t(dark.primary.bg, light.primary.bg)}, 4%))`),
+ $("shadow-color", t("rgba(0,0,0,.6)", "rgba(0,0,0,.4)")),
+ $("text-shadow", t("2pt 2pt 2pt $shadow-color", "none")),
+
+ $("popover-border-color", `transparentize(${t(dark.border, light.border)}, ${Math.max(((border.opacity.value - 1) / 100), 0)})`),
+ $("popover-padding", `$padding * ${popoverPaddingMultiplier}`),
+ $("popover-radius", radius.value === 0 ? "0" : "$radius + $popover-padding"),
+
+ $("font-size", `${options.font.size}pt`),
+ $("font-name", options.font.name),
+
+ // etc
+ $("charging-bg", options.bar.battery.charging),
+ $("bar-battery-blocks", options.bar.battery.blocks),
+ $("bar-position", options.bar.position),
+ $("hyprland-gaps-multiplier", options.hyprland.gaps),
+]
+
+async function resetCss() {
+ if (!dependencies("sass", "fd"))
+ return
+
+ try {
+ const vars = `${TMP}/variables.scss`
+ await Utils.writeFile(variables().join("\n"), vars)
+
+ const fd = await sh(`fd ".scss" ${App.configDir}`)
+ const files = fd.split(/\s+/).map(f => `@import '${f}';`)
+ const scss = [`@import '${vars}';`, ...files].join("\n")
+ const css = await bash`echo "${scss}" | sass --stdin`
+
+ App.applyCss(css, true)
+ } catch (error) {
+ logError(error)
+ }
+}
+
+Utils.monitorFile(App.configDir, resetCss)
+options.handler(deps, resetCss)
+await resetCss()
diff --git a/linux/home/.config/ags/tsconfig.json b/linux/home/.config/ags/tsconfig.json
new file mode 100644
index 0000000..1708aa3
--- /dev/null
+++ b/linux/home/.config/ags/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "ES2022",
+ "lib": [
+ "ES2022"
+ ],
+ "allowJs": true,
+ "checkJs": true,
+ "strict": true,
+ "noImplicitAny": false,
+ "baseUrl": ".",
+ "typeRoots": [
+ "./types",
+ "./node_modules/@girs"
+ ],
+ "skipLibCheck": true
+ }
+}
diff --git a/linux/home/.config/ags/widget/PopupWindow.ts b/linux/home/.config/ags/widget/PopupWindow.ts
new file mode 100644
index 0000000..b53b6fd
--- /dev/null
+++ b/linux/home/.config/ags/widget/PopupWindow.ts
@@ -0,0 +1,156 @@
+import { type WindowProps } from "types/widgets/window"
+import { type RevealerProps } from "types/widgets/revealer"
+import { type EventBoxProps } from "types/widgets/eventbox"
+import type Gtk from "gi://Gtk?version=3.0"
+import options from "options"
+
+type Transition = RevealerProps["transition"]
+type Child = WindowProps["child"]
+
+type PopupWindowProps = Omit<WindowProps, "name"> & {
+ name: string
+ layout?: keyof ReturnType<typeof Layout>
+ transition?: Transition,
+}
+
+export const Padding = (name: string, {
+ css = "",
+ hexpand = true,
+ vexpand = true,
+}: EventBoxProps = {}) => Widget.EventBox({
+ hexpand,
+ vexpand,
+ can_focus: false,
+ child: Widget.Box({ css }),
+ setup: w => w.on("button-press-event", () => App.toggleWindow(name)),
+})
+
+const PopupRevealer = (
+ name: string,
+ child: Child,
+ transition: Transition = "slide_down",
+) => Widget.Box(
+ { css: "padding: 1px;" },
+ Widget.Revealer({
+ transition,
+ child: Widget.Box({
+ class_name: "window-content",
+ child,
+ }),
+ transitionDuration: options.transition.bind(),
+ setup: self => self.hook(App, (_, wname, visible) => {
+ if (wname === name)
+ self.reveal_child = visible
+ }),
+ }),
+)
+
+const Layout = (name: string, child: Child, transition?: Transition) => ({
+ "center": () => Widget.CenterBox({},
+ Padding(name),
+ Widget.CenterBox(
+ { vertical: true },
+ Padding(name),
+ PopupRevealer(name, child, transition),
+ Padding(name),
+ ),
+ Padding(name),
+ ),
+ "top": () => Widget.CenterBox({},
+ Padding(name),
+ Widget.Box(
+ { vertical: true },
+ PopupRevealer(name, child, transition),
+ Padding(name),
+ ),
+ Padding(name),
+ ),
+ "top-right": () => Widget.Box({},
+ Padding(name),
+ Widget.Box(
+ {
+ hexpand: false,
+ vertical: true,
+ },
+ PopupRevealer(name, child, transition),
+ Padding(name),
+ ),
+ ),
+ "top-center": () => Widget.Box({},
+ Padding(name),
+ Widget.Box(
+ {
+ hexpand: false,
+ vertical: true,
+ },
+ PopupRevealer(name, child, transition),
+ Padding(name),
+ ),
+ Padding(name),
+ ),
+ "top-left": () => Widget.Box({},
+ Widget.Box(
+ {
+ hexpand: false,
+ vertical: true,
+ },
+ PopupRevealer(name, child, transition),
+ Padding(name),
+ ),
+ Padding(name),
+ ),
+ "bottom-left": () => Widget.Box({},
+ Widget.Box(
+ {
+ hexpand: false,
+ vertical: true,
+ },
+ Padding(name),
+ PopupRevealer(name, child, transition),
+ ),
+ Padding(name),
+ ),
+ "bottom-center": () => Widget.Box({},
+ Padding(name),
+ Widget.Box(
+ {
+ hexpand: false,
+ vertical: true,
+ },
+ Padding(name),
+ PopupRevealer(name, child, transition),
+ ),
+ Padding(name),
+ ),
+ "bottom-right": () => Widget.Box({},
+ Padding(name),
+ Widget.Box(
+ {
+ hexpand: false,
+ vertical: true,
+ },
+ Padding(name),
+ PopupRevealer(name, child, transition),
+ ),
+ ),
+})
+
+export default ({
+ name,
+ child,
+ layout = "center",
+ transition,
+ exclusivity = "ignore",
+ ...props
+}: PopupWindowProps) => Widget.Window<Gtk.Widget>({
+ name,
+ class_names: [name, "popup-window"],
+ setup: w => w.keybind("Escape", () => App.closeWindow(name)),
+ visible: false,
+ keymode: "on-demand",
+ exclusivity,
+ layer: "top",
+ anchor: ["top", "bottom", "right", "left"],
+ child: Layout(name, child, transition)[layout](),
+ ...props,
+})
diff --git a/linux/home/.config/ags/widget/RegularWindow.ts b/linux/home/.config/ags/widget/RegularWindow.ts
new file mode 100644
index 0000000..1e4225d
--- /dev/null
+++ b/linux/home/.config/ags/widget/RegularWindow.ts
@@ -0,0 +1,3 @@
+import Gtk from "gi://Gtk?version=3.0"
+
+export default Widget.subclass<typeof Gtk.Window, Gtk.Window.ConstructorProperties>(Gtk.Window)
diff --git a/linux/home/.config/ags/widget/bar/Bar.ts b/linux/home/.config/ags/widget/bar/Bar.ts
new file mode 100644
index 0000000..9343a36
--- /dev/null
+++ b/linux/home/.config/ags/widget/bar/Bar.ts
@@ -0,0 +1,57 @@
+import BatteryBar from "./buttons/BatteryBar"
+import ColorPicker from "./buttons/ColorPicker"
+import Date from "./buttons/Date"
+import Launcher from "./buttons/Launcher"
+import Media from "./buttons/Media"
+import PowerMenu from "./buttons/PowerMenu"
+import SysTray from "./buttons/SysTray"
+import SystemIndicators from "./buttons/SystemIndicators"
+import Taskbar from "./buttons/Taskbar"
+import Workspaces from "./buttons/Workspaces"
+import ScreenRecord from "./buttons/ScreenRecord"
+import Messages from "./buttons/Messages"
+import options from "options"
+
+const { start, center, end } = options.bar.layout
+const pos = options.bar.position.bind()
+
+export type BarWidget = keyof typeof widget
+
+const widget = {
+ battery: BatteryBar,
+ colorpicker: ColorPicker,
+ date: Date,
+ launcher: Launcher,
+ media: Media,
+ powermenu: PowerMenu,
+ systray: SysTray,
+ system: SystemIndicators,
+ taskbar: Taskbar,
+ workspaces: Workspaces,
+ screenrecord: ScreenRecord,
+ messages: Messages,
+ expander: () => Widget.Box({ expand: true }),
+}
+
+export default (monitor: number) => Widget.Window({
+ monitor,
+ class_name: "bar",
+ name: `bar${monitor}`,
+ exclusivity: "exclusive",
+ anchor: pos.as(pos => [pos, "right", "left"]),
+ child: Widget.CenterBox({
+ css: "min-width: 2px; min-height: 2px;",
+ startWidget: Widget.Box({
+ hexpand: true,
+ children: start.bind().as(s => s.map(w => widget[w]())),
+ }),
+ centerWidget: Widget.Box({
+ hpack: "center",
+ children: center.bind().as(c => c.map(w => widget[w]())),
+ }),
+ endWidget: Widget.Box({
+ hexpand: true,
+ children: end.bind().as(e => e.map(w => widget[w]())),
+ }),
+ }),
+})
diff --git a/linux/home/.config/ags/widget/bar/PanelButton.ts b/linux/home/.config/ags/widget/bar/PanelButton.ts
new file mode 100644
index 0000000..332b46d
--- /dev/null
+++ b/linux/home/.config/ags/widget/bar/PanelButton.ts
@@ -0,0 +1,46 @@
+import options from "options"
+import { ButtonProps } from "types/widgets/button"
+
+type PanelButtonProps = ButtonProps & {
+ window?: string,
+ flat?: boolean
+}
+
+export default ({
+ window = "",
+ flat,
+ child,
+ setup,
+ ...rest
+}: PanelButtonProps) => Widget.Button({
+ child: Widget.Box({ child }),
+ setup: self => {
+ let open = false
+
+ self.toggleClassName("panel-button")
+ self.toggleClassName(window)
+
+ self.hook(options.bar.flatButtons, () => {
+ self.toggleClassName("flat", flat ?? options.bar.flatButtons.value)
+ })
+
+ self.hook(App, (_, win, visible) => {
+ if (win !== window)
+ return
+
+ if (open && !visible) {
+ open = false
+ self.toggleClassName("active", false)
+ }
+
+ if (visible) {
+ open = true
+ self.toggleClassName("active")
+ }
+ })
+
+ if (setup)
+ setup(self)
+ },
+ ...rest,
+})
diff --git a/linux/home/.config/ags/widget/bar/ScreenCorners.ts b/linux/home/.config/ags/widget/bar/ScreenCorners.ts
new file mode 100644
index 0000000..1b35e50
--- /dev/null
+++ b/linux/home/.config/ags/widget/bar/ScreenCorners.ts
@@ -0,0 +1,25 @@
+import options from "options"
+
+const { corners } = options.bar
+
+export default (monitor: number) => Widget.Window({
+ monitor,
+ name: `corner${monitor}`,
+ class_name: "screen-corner",
+ anchor: ["top", "bottom", "right", "left"],
+ click_through: true,
+ child: Widget.Box({
+ class_name: "shadow",
+ child: Widget.Box({
+ class_name: "border",
+ expand: true,
+ child: Widget.Box({
+ class_name: "corner",
+ expand: true,
+ }),
+ }),
+ }),
+ setup: self => self.hook(corners, () => {
+ self.toggleClassName("corners", corners.value)
+ }),
+})
diff --git a/linux/home/.config/ags/widget/bar/bar.scss b/linux/home/.config/ags/widget/bar/bar.scss
new file mode 100644
index 0000000..5c6c2cd
--- /dev/null
+++ b/linux/home/.config/ags/widget/bar/bar.scss
@@ -0,0 +1,242 @@
+@use 'sass:color';
+
+$bar-spacing: $spacing * 0.3;
+$button-radius: $radius;
+
+@mixin panel-button($flat: true, $reactive: true) {
+ @include accs-button($flat, $reactive);
+
+ >* {
+ border-radius: $button-radius;
+ margin: $bar-spacing;
+ background-color: $bg;
+ }
+
+ label,
+ image {
+ font-weight: bold;
+ }
+
+ >* {
+ padding: $padding * 0.8 $padding * 1.2;
+ }
+}
+
+.bar {
+ .panel-button {
+ @include panel-button;
+
+ &:not(.flat) {
+ @include accs-button($flat: false);
+ }
+ }
+
+ .launcher {
+ .colored {
+ color: transparentize($primary-bg, 0.2);
+ }
+
+ &:hover .colored {
+ color: $primary-bg;
+ }
+
+ &:active .colored,
+ &.active .colored {
+ color: $primary-fg;
+ }
+ }
+
+ .workspaces {
+ label {
+ font-size: 0;
+ min-width: 5pt;
+ min-height: 5pt;
+ border-radius: $radius * 0.6;
+ box-shadow: inset 0 0 0 $border-width $border-color;
+ margin: 0 $padding * 0.5;
+ transition: $transition * 0.5;
+ background-color: transparentize($fg, 0.8);
+
+ &.occupied {
+ background-color: transparentize($fg, 0.2);
+ min-width: 7pt;
+ min-height: 7pt;
+ }
+
+ &.active {
+ // background-color: $primary-bg;
+ background-image: $active-gradient;
+ min-width: 20pt;
+ min-height: 12pt;
+ }
+
+ &.inctive {
+ // background-color: $primary-bg;
+ background-image: $active-gradient;
+ min-width: 20pt;
+ min-height: 12pt;
+ }
+ }
+
+ &.inactive,
+ &:active {
+ label {
+ background-color: transparentize($primary-fg, 0.3);
+
+ &.occupied {
+ background-color: transparentize($primary-fg, 0.15);
+ }
+
+ &.active {
+ background-color: $primary-fg;
+ }
+
+ &.inactive {
+ background-color: $primary-fg;
+ }
+ }
+ }
+ }
+
+ .media label {
+ margin: 0 ($spacing * 0.5);
+ }
+
+ .taskbar .indicator.active {
+ background-color: $primary-bg;
+ border-radius: $radius;
+ min-height: 4pt;
+ min-width: 6pt;
+ margin: 2pt;
+ }
+
+ .powermenu.colored,
+ .recorder {
+ image {
+ color: transparentize($error-bg, 0.3);
+ }
+
+ &:hover image {
+ color: transparentize($error-bg, 0.15);
+ }
+
+ &:active image {
+ color: $primary-fg;
+ }
+ }
+
+ .quicksettings>box>box {
+ @include spacing($spacing: if($bar-spacing==0, $padding / 2, $bar-spacing));
+ }
+
+ .quicksettings:not(.active):not(:active) {
+ .bluetooth {
+ color: $primary-bg;
+
+ label {
+ font-size: $font-size * 0.7;
+ color: $fg;
+ text-shadow: $text-shadow;
+ }
+ }
+ }
+
+ .battery-bar {
+ >* {
+ padding: 0;
+ }
+
+ &.bar-hidden>box {
+ padding: 0 $spacing * 0.5;
+
+ image {
+ margin: 0;
+ }
+ }
+
+ levelbar * {
+ all: unset;
+ transition: $transition;
+ }
+
+ .whole {
+ @if $shadows {
+ image {
+ -gtk-icon-shadow: $text-shadow;
+ }
+
+ label {
+ text-shadow: $text-shadow;
+ }
+ }
+ }
+
+ .regular image {
+ margin-left: $spacing * 0.5;
+ }
+
+ trough {
+ @include widget;
+ min-height: 12pt;
+ min-width: 12pt;
+ }
+
+ .regular trough {
+ margin-right: $spacing * 0.5;
+ }
+
+ block {
+ margin: 0;
+
+ &:last-child {
+ border-radius: 0 $button-radius $button-radius 0;
+ }
+
+ &:first-child {
+ border-radius: $button-radius 0 0 $button-radius;
+ }
+ }
+
+ .vertical {
+ block {
+ &:last-child {
+ border-radius: 0 0 $button-radius $button-radius;
+ }
+
+ &:first-child {
+ border-radius: $button-radius $button-radius 0 0;
+ }
+ }
+ }
+
+ @for $i from 1 through $bar-battery-blocks {
+ block:nth-child(#{$i}).filled {
+ background-color: color.mix($bg, $primary-bg, $i * 3);
+ }
+
+ &.low block:nth-child(#{$i}).filled {
+ background-color: color.mix($bg, $error-bg, $i * 3);
+ }
+
+ &.charging block:nth-child(#{$i}).filled {
+ background-color: color.mix($bg, $charging-bg, $i * 3);
+ }
+
+ &:active .regular block:nth-child(#{$i}).filled {
+ background-color: color.mix($bg, $primary-fg, $i * 3);
+ }
+ }
+
+ &.low image {
+ color: $error-bg;
+ }
+
+ &.charging image {
+ color: $charging-bg;
+ }
+
+ &:active image {
+ color: $primary-fg;
+ }
+ }
+}
diff --git a/linux/home/.config/ags/widget/bar/buttons/BatteryBar.ts b/linux/home/.config/ags/widget/bar/buttons/BatteryBar.ts
new file mode 100644
index 0000000..18de329
--- /dev/null
+++ b/linux/home/.config/ags/widget/bar/buttons/BatteryBar.ts
@@ -0,0 +1,94 @@
+import icons from "lib/icons"
+import options from "options"
+import PanelButton from "../PanelButton"
+
+const battery = await Service.import("battery")
+const { bar, percentage, blocks, width, low } = options.bar.battery
+
+const Indicator = () => Widget.Icon({
+ setup: self => self.hook(battery, () => {
+ self.icon = battery.charging || battery.charged
+ ? icons.battery.charging
+ : battery.icon_name
+ }),
+})
+
+const PercentLabel = () => Widget.Revealer({
+ transition: "slide_right",
+ click_through: true,
+ reveal_child: percentage.bind(),
+ child: Widget.Label({
+ label: battery.bind("percent").as(p => `${p}%`),
+ }),
+})
+
+const LevelBar = () => {
+ const level = Widget.LevelBar({
+ bar_mode: "discrete",
+ max_value: blocks.bind(),
+ visible: bar.bind().as(b => b !== "hidden"),
+ value: battery.bind("percent").as(p => (p / 100) * blocks.value),
+ })
+ const update = () => {
+ level.value = (battery.percent / 100) * blocks.value
+ level.css = `block { min-width: ${width.value / blocks.value}pt; }`
+ }
+ return level
+ .hook(width, update)
+ .hook(blocks, update)
+ .hook(bar, () => {
+ level.vpack = bar.value === "whole" ? "fill" : "center"
+ level.hpack = bar.value === "whole" ? "fill" : "center"
+ })
+}
+
+const WholeButton = () => Widget.Overlay({
+ vexpand: true,
+ child: LevelBar(),
+ class_name: "whole",
+ pass_through: true,
+ overlay: Widget.Box({
+ hpack: "center",
+ children: [
+ Widget.Icon({
+ icon: icons.battery.charging,
+ visible: Utils.merge([
+ battery.bind("charging"),
+ battery.bind("charged"),
+ ], (ing, ed) => ing || ed),
+ }),
+ Widget.Box({
+ hpack: "center",
+ vpack: "center",
+ child: PercentLabel(),
+ }),
+ ],
+ }),
+})
+
+const Regular = () => Widget.Box({
+ class_name: "regular",
+ children: [
+ Indicator(),
+ PercentLabel(),
+ LevelBar(),
+ ],
+})
+
+export default () => PanelButton({
+ class_name: "battery-bar",
+ hexpand: false,
+ on_clicked: () => { percentage.value = !percentage.value },
+ visible: battery.bind("available"),
+ child: Widget.Box({
+ expand: true,
+ visible: battery.bind("available"),
+ child: bar.bind().as(b => b === "whole" ? WholeButton() : Regular()),
+ }),
+ setup: self => self
+ .hook(bar, w => w.toggleClassName("bar-hidden", bar.value === "hidden"))
+ .hook(battery, w => {
+ w.toggleClassName("charging", battery.charging || battery.charged)
+ w.toggleClassName("low", battery.percent < low.value)
+ }),
+})
diff --git a/linux/home/.config/ags/widget/bar/buttons/ColorPicker.ts b/linux/home/.config/ags/widget/bar/buttons/ColorPicker.ts
new file mode 100644
index 0000000..5b1f3f6
--- /dev/null
+++ b/linux/home/.config/ags/widget/bar/buttons/ColorPicker.ts
@@ -0,0 +1,37 @@
+import PanelButton from "../PanelButton"
+import colorpicker from "service/colorpicker"
+import Gdk from "gi://Gdk"
+
+const css = (color: string) => `
+* {
+ background-color: ${color};
+ color: transparent;
+}
+*:hover {
+ color: white;
+ text-shadow: 2px 2px 3px rgba(0,0,0,.8);
+}`
+
+export default () => {
+ const menu = Widget.Menu({
+ class_name: "colorpicker",
+ children: colorpicker.bind("colors").as(c => c.map(color => Widget.MenuItem({
+ child: Widget.Label(color),
+ css: css(color),
+ on_activate: () => colorpicker.wlCopy(color),
+ }))),
+ })
+
+ return PanelButton({
+ class_name: "color-picker",
+ child: Widget.Icon("color-select-symbolic"),
+ tooltip_text: colorpicker.bind("colors").as(v => `${v.length} colors`),
+ on_clicked: colorpicker.pick,
+ on_secondary_click: self => {
+ if (colorpicker.colors.length === 0)
+ return
+
+ menu.popup_at_widget(self, Gdk.Gravity.SOUTH, Gdk.Gravity.NORTH, null)
+ },
+ })
+}
diff --git a/linux/home/.config/ags/widget/bar/buttons/Date.ts b/linux/home/.config/ags/widget/bar/buttons/Date.ts
new file mode 100644
index 0000000..44c2540
--- /dev/null
+++ b/linux/home/.config/ags/widget/bar/buttons/Date.ts
@@ -0,0 +1,15 @@
+import { clock } from "lib/variables"
+import PanelButton from "../PanelButton"
+import options from "options"
+
+const { format, action } = options.bar.date
+const time = Utils.derive([clock, format], (c, f) => c.format(f) || "")
+
+export default () => PanelButton({
+ window: "datemenu",
+ on_clicked: action.bind(),
+ child: Widget.Label({
+ justification: "center",
+ label: time.bind(),
+ }),
+})
diff --git a/linux/home/.config/ags/widget/bar/buttons/Launcher.ts b/linux/home/.config/ags/widget/bar/buttons/Launcher.ts
new file mode 100644
index 0000000..f3fee6b
--- /dev/null
+++ b/linux/home/.config/ags/widget/bar/buttons/Launcher.ts
@@ -0,0 +1,49 @@
+import PanelButton from "../PanelButton"
+import options from "options"
+import nix from "service/nix"
+
+const { icon, label, action } = options.bar.launcher
+
+function Spinner() {
+ const child = Widget.Icon({
+ icon: icon.icon.bind(),
+ class_name: Utils.merge([
+ icon.colored.bind(),
+ nix.bind("ready"),
+ ], (c, r) => `${c ? "colored" : ""} ${r ? "" : "spinning"}`),
+ css: `
+ @keyframes spin {
+ to { -gtk-icon-transform: rotate(1turn); }
+ }
+
+ image.spinning {
+ animation-name: spin;
+ animation-duration: 1s;
+ animation-timing-function: linear;
+ animation-iteration-count: infinite;
+ }
+ `,
+ })
+
+ return Widget.Revealer({
+ transition: "slide_left",
+ child,
+ reveal_child: Utils.merge([
+ icon.icon.bind(),
+ nix.bind("ready"),
+ ], (i, r) => Boolean(i || r)),
+ })
+}
+
+export default () => PanelButton({
+ window: "launcher",
+ on_clicked: action.bind(),
+ child: Widget.Box([
+ Spinner(),
+ Widget.Label({
+ class_name: label.colored.bind().as(c => c ? "colored" : ""),
+ visible: label.label.bind().as(v => !!v),
+ label: label.label.bind(),
+ }),
+ ]),
+})
diff --git a/linux/home/.config/ags/widget/bar/buttons/Media.ts b/linux/home/.config/ags/widget/bar/buttons/Media.ts
new file mode 100644
index 0000000..b3aab61
--- /dev/null
+++ b/linux/home/.config/ags/widget/bar/buttons/Media.ts
@@ -0,0 +1,92 @@
+import { type MprisPlayer } from "types/service/mpris"
+import PanelButton from "../PanelButton"
+import options from "options"
+import icons from "lib/icons"
+import { icon } from "lib/utils"
+
+const mpris = await Service.import("mpris")
+const { length, direction, preferred, monochrome, format } = options.bar.media
+
+const getPlayer = (name = preferred.value) =>
+ mpris.getPlayer(name) || mpris.players[0] || null
+
+const Content = (player: MprisPlayer) => {
+ const revealer = Widget.Revealer({
+ click_through: true,
+ visible: length.bind().as(l => l > 0),
+ transition: direction.bind().as(d => `slide_${d}` as const),
+ setup: self => {
+ let current = ""
+ self.hook(player, () => {
+ if (current === player.track_title)
+ return
+
+ current = player.track_title
+ self.reveal_child = true
+ Utils.timeout(3000, () => {
+ !self.is_destroyed && (self.reveal_child = false)
+ })
+ })
+ },
+ child: Widget.Label({
+ truncate: "end",
+ max_width_chars: length.bind().as(n => n > 0 ? n : -1),
+ label: Utils.merge([
+ player.bind("track_title"),
+ player.bind("track_artists"),
+ format.bind(),
+ ], () => `${format}`
+ .replace("{title}", player.track_title)
+ .replace("{artists}", player.track_artists.join(", "))
+ .replace("{artist}", player.track_artists[0] || "")
+ .replace("{album}", player.track_album)
+ .replace("{name}", player.name)
+ .replace("{identity}", player.identity),
+ ),
+ }),
+ })
+
+ const playericon = Widget.Icon({
+ icon: Utils.merge([player.bind("entry"), monochrome.bind()], (entry => {
+ const name = `${entry}${monochrome.value ? "-symbolic" : ""}`
+ return icon(name, icons.fallback.audio)
+ })),
+ })
+
+ return Widget.Box({
+ attribute: { revealer },
+ children: direction.bind().as(d => d === "right"
+ ? [playericon, revealer] : [revealer, playericon]),
+ })
+}
+
+export default () => {
+ let player = getPlayer()
+
+ const btn = PanelButton({
+ class_name: "media",
+ child: Widget.Icon(icons.fallback.audio),
+ })
+
+ const update = () => {
+ player = getPlayer()
+ btn.visible = !!player
+
+ if (!player)
+ return
+
+ const content = Content(player)
+ const { revealer } = content.attribute
+ btn.child = content
+ btn.on_primary_click = () => { player.playPause() }
+ btn.on_secondary_click = () => { player.playPause() }
+ btn.on_scroll_up = () => { player.next() }
+ btn.on_scroll_down = () => { player.previous() }
+ btn.on_hover = () => { revealer.reveal_child = true }
+ btn.on_hover_lost = () => { revealer.reveal_child = false }
+ }
+
+ return btn
+ .hook(preferred, update)
+ .hook(mpris, update, "notify::players")
+}
diff --git a/linux/home/.config/ags/widget/bar/buttons/Messages.ts b/linux/home/.config/ags/widget/bar/buttons/Messages.ts
new file mode 100644
index 0000000..a8971e9
--- /dev/null
+++ b/linux/home/.config/ags/widget/bar/buttons/Messages.ts
@@ -0,0 +1,16 @@
+import icons from "lib/icons"
+import PanelButton from "../PanelButton"
+import options from "options"
+
+const n = await Service.import("notifications")
+const notifs = n.bind("notifications")
+const action = options.bar.messages.action.bind()
+
+export default () => PanelButton({
+ class_name: "messages",
+ on_clicked: action,
+ visible: notifs.as(n => n.length > 0),
+ child: Widget.Box([
+ Widget.Icon(icons.notifications.message),
+ ]),
+})
diff --git a/linux/home/.config/ags/widget/bar/buttons/PowerMenu.ts b/linux/home/.config/ags/widget/bar/buttons/PowerMenu.ts
new file mode 100644
index 0000000..4432ade
--- /dev/null
+++ b/linux/home/.config/ags/widget/bar/buttons/PowerMenu.ts
@@ -0,0 +1,15 @@
+import icons from "lib/icons"
+import PanelButton from "../PanelButton"
+import options from "options"
+
+const { monochrome, action } = options.bar.powermenu
+
+export default () => PanelButton({
+ window: "powermenu",
+ on_clicked: action.bind(),
+ child: Widget.Icon(icons.powermenu.shutdown),
+ setup: self => self.hook(monochrome, () => {
+ self.toggleClassName("colored", !monochrome.value)
+ self.toggleClassName("box")
+ }),
+})
diff --git a/linux/home/.config/ags/widget/bar/buttons/ScreenRecord.ts b/linux/home/.config/ags/widget/bar/buttons/ScreenRecord.ts
new file mode 100644
index 0000000..1d6eb36
--- /dev/null
+++ b/linux/home/.config/ags/widget/bar/buttons/ScreenRecord.ts
@@ -0,0 +1,21 @@
+import PanelButton from "../PanelButton"
+import screenrecord from "service/screenrecord"
+import icons from "lib/icons"
+
+export default () => PanelButton({
+ class_name: "recorder",
+ on_clicked: () => screenrecord.stop(),
+ visible: screenrecord.bind("recording"),
+ child: Widget.Box({
+ children: [
+ Widget.Icon(icons.recorder.recording),
+ Widget.Label({
+ label: screenrecord.bind("timer").as(time => {
+ const sec = time % 60
+ const min = Math.floor(time / 60)
+ return `${min}:${sec < 10 ? "0" + sec : sec}`
+ }),
+ }),
+ ],
+ }),
+})
diff --git a/linux/home/.config/ags/widget/bar/buttons/SysTray.ts b/linux/home/.config/ags/widget/bar/buttons/SysTray.ts
new file mode 100644
index 0000000..9f569d1
--- /dev/null
+++ b/linux/home/.config/ags/widget/bar/buttons/SysTray.ts
@@ -0,0 +1,39 @@
+import { type TrayItem } from "types/service/systemtray"
+import PanelButton from "../PanelButton"
+import Gdk from "gi://Gdk"
+import options from "options"
+
+const systemtray = await Service.import("systemtray")
+const { ignore } = options.bar.systray
+
+const SysTrayItem = (item: TrayItem) => PanelButton({
+ class_name: "tray-item",
+ child: Widget.Icon({ icon: item.bind("icon") }),
+ tooltip_markup: item.bind("tooltip_markup"),
+ setup: self => {
+ const { menu } = item
+ if (!menu)
+ return
+
+ const id = menu.connect("popped-up", () => {
+ self.toggleClassName("active")
+ menu.connect("notify::visible", () => {
+ self.toggleClassName("active", menu.visible)
+ })
+ menu.disconnect(id!)
+ })
+
+ self.connect("destroy", () => menu.disconnect(id))
+ },
+
+ on_primary_click: btn => item.menu?.popup_at_widget(
+ btn, Gdk.Gravity.SOUTH, Gdk.Gravity.NORTH, null),
+
+ on_secondary_click: btn => item.menu?.popup_at_widget(
+ btn, Gdk.Gravity.SOUTH, Gdk.Gravity.NORTH, null),
+})
+
+export default () => Widget.Box()
+ .bind("children", systemtray, "items", i => i
+ .filter(({ id }) => !ignore.value.includes(id))
+ .map(SysTrayItem))
diff --git a/linux/home/.config/ags/widget/bar/buttons/SystemIndicators.ts b/linux/home/.config/ags/widget/bar/buttons/SystemIndicators.ts
new file mode 100644
index 0000000..cc98548
--- /dev/null
+++ b/linux/home/.config/ags/widget/bar/buttons/SystemIndicators.ts
@@ -0,0 +1,107 @@
+import PanelButton from '../PanelButton';
+import icons from 'lib/icons';
+import asusctl from 'service/asusctl';
+
+const notifications = await Service.import('notifications');
+const bluetooth = await Service.import('bluetooth');
+const audio = await Service.import('audio');
+const network = await Service.import('network');
+const powerprof = await Service.import('powerprofiles');
+
+const ProfileIndicator = () => {
+ const visible = asusctl.available ? asusctl.bind('profile').as(p => p !== 'Balanced') : powerprof.bind('active_profile').as(p => p !== 'balanced');
+
+ const icon = asusctl.available ? asusctl.bind('profile').as(p => icons.asusctl.profile[p]) : powerprof.bind('active_profile').as(p => icons.powerprofile[p]);
+
+ return Widget.Icon({ visible, icon });
+};
+
+const ModeIndicator = () => {
+ if (!asusctl.available) {
+ return Widget.Icon({
+ setup(self) {
+ Utils.idle(() => (self.visible = false));
+ },
+ });
+ }
+
+ return Widget.Icon({
+ visible: asusctl.bind('mode').as(m => m !== 'Hybrid'),
+ icon: asusctl.bind('mode').as(m => icons.asusctl.mode[m]),
+ });
+};
+
+const MicrophoneIndicator = () =>
+ Widget.Icon()
+ .hook(audio, self => (self.visible = audio.recorders.length > 0 || audio.microphone.is_muted || false))
+ .hook(audio.microphone, self => {
+ const vol = audio.microphone.is_muted ? 0 : audio.microphone.volume;
+ const { muted, low, medium, high } = icons.audio.mic;
+ const cons = [
+ [67, high],
+ [34, medium],
+ [1, low],
+ [0, muted],
+ ] as const;
+ self.icon = cons.find(([n]) => n <= vol * 100)?.[1] || '';
+ });
+
+const DNDIndicator = () =>
+ Widget.Icon({
+ visible: notifications.bind('dnd'),
+ icon: icons.notifications.silent,
+ });
+
+const BluetoothIndicator = () =>
+ Widget.Overlay({
+ class_name: 'bluetooth',
+ passThrough: true,
+ child: Widget.Icon({
+ icon: icons.bluetooth.enabled,
+ visible: bluetooth.bind('enabled'),
+ }),
+ overlay: Widget.Label({
+ hpack: 'end',
+ vpack: 'start',
+ label: bluetooth.bind('connected_devices').as(c => `${c.length}`),
+ visible: bluetooth.bind('connected_devices').as(c => c.length > 0),
+ }),
+ });
+
+const NetworkIndicator = () =>
+ Widget.Icon().hook(network, self => {
+ const icon = network[network.primary || 'wifi']?.icon_name;
+ self.icon = icon || '';
+ self.visible = !!icon;
+ });
+
+const AudioIndicator = () =>
+ Widget.Icon().hook(audio.speaker, self => {
+ const vol = audio.speaker.is_muted ? 0 : audio.speaker.volume;
+ const { muted, low, medium, high, overamplified } = icons.audio.volume;
+ const cons = [
+ [101, overamplified],
+ [67, high],
+ [34, medium],
+ [1, low],
+ [0, muted],
+ ] as const;
+ self.icon = cons.find(([n]) => n <= vol * 100)?.[1] || '';
+ });
+
+export default () =>
+ PanelButton({
+ window: 'quicksettings',
+ on_clicked: () => App.toggleWindow('quicksettings'),
+ on_scroll_up: () => (audio.speaker.volume += 0.02),
+ on_scroll_down: () => (audio.speaker.volume -= 0.02),
+ child: Widget.Box([
+ //ProfileIndicator(),
+ ModeIndicator(),
+ DNDIndicator(),
+ BluetoothIndicator(),
+ MicrophoneIndicator(),
+ AudioIndicator(),
+ NetworkIndicator(),
+ ]),
+ });
diff --git a/linux/home/.config/ags/widget/bar/buttons/Taskbar.ts b/linux/home/.config/ags/widget/bar/buttons/Taskbar.ts
new file mode 100644
index 0000000..b9c65fa
--- /dev/null
+++ b/linux/home/.config/ags/widget/bar/buttons/Taskbar.ts
@@ -0,0 +1,90 @@
+import { launchApp, icon } from "lib/utils"
+import icons from "lib/icons"
+import options from "options"
+import PanelButton from "../PanelButton"
+
+const hyprland = await Service.import("hyprland")
+const apps = await Service.import("applications")
+const { monochrome, exclusive, iconSize } = options.bar.taskbar
+const { position } = options.bar
+
+const focus = (address: string) => hyprland.messageAsync(
+ `dispatch focuswindow address:${address}`)
+
+const DummyItem = (address: string) => Widget.Box({
+ attribute: { address },
+ visible: false,
+})
+
+const AppItem = (address: string) => {
+ const client = hyprland.getClient(address)
+ if (!client || client.class === "")
+ return DummyItem(address)
+
+ const app = apps.list.find(app => app.match(client.class))
+
+ const btn = PanelButton({
+ class_name: "panel-button",
+ tooltip_text: Utils.watch(client.title, hyprland, () =>
+ hyprland.getClient(address)?.title || "",
+ ),
+ on_primary_click: () => focus(address),
+ on_middle_click: () => app && launchApp(app),
+ child: Widget.Icon({
+ size: iconSize.bind(),
+ icon: monochrome.bind().as(m => icon(
+ (app?.icon_name || client.class) + (m ? "-symbolic" : ""),
+ icons.fallback.executable + (m ? "-symbolic" : ""),
+ )),
+ }),
+ })
+
+ return Widget.Box(
+ {
+ attribute: { address },
+ visible: Utils.watch(true, [exclusive, hyprland], () => {
+ return exclusive.value
+ ? hyprland.active.workspace.id === client.workspace.id
+ : true
+ }),
+ },
+ Widget.Overlay({
+ child: btn,
+ pass_through: true,
+ overlay: Widget.Box({
+ className: "indicator",
+ hpack: "center",
+ vpack: position.bind().as(p => p === "top" ? "start" : "end"),
+ setup: w => w.hook(hyprland, () => {
+ w.toggleClassName("active", hyprland.active.client.address === address)
+ }),
+ }),
+ }),
+ )
+}
+
+function sortItems<T extends { attribute: { address: string } }>(arr: T[]) {
+ return arr.sort(({ attribute: a }, { attribute: b }) => {
+ const aclient = hyprland.getClient(a.address)!
+ const bclient = hyprland.getClient(b.address)!
+ return aclient.workspace.id - bclient.workspace.id
+ })
+}
+
+export default () => Widget.Box({
+ class_name: "taskbar",
+ children: sortItems(hyprland.clients.map(c => AppItem(c.address))),
+ setup: w => w
+ .hook(hyprland, (w, address?: string) => {
+ if (typeof address === "string")
+ w.children = w.children.filter(ch => ch.attribute.address !== address)
+ }, "client-removed")
+ .hook(hyprland, (w, address?: string) => {
+ if (typeof address === "string")
+ w.children = sortItems([...w.children, AppItem(address)])
+ }, "client-added")
+ .hook(hyprland, (w, event?: string) => {
+ if (event === "movewindow")
+ w.children = sortItems(w.children)
+ }, "event"),
+})
diff --git a/linux/home/.config/ags/widget/bar/buttons/Workspaces.ts b/linux/home/.config/ags/widget/bar/buttons/Workspaces.ts
new file mode 100644
index 0000000..a59f61b
--- /dev/null
+++ b/linux/home/.config/ags/widget/bar/buttons/Workspaces.ts
@@ -0,0 +1,66 @@
+import PanelButton from '../PanelButton';
+import options from 'options';
+import { sh, range } from 'lib/utils';
+
+const hyprland = await Service.import('hyprland');
+const { workspaces } = options.bar.workspaces;
+
+const dispatch = arg => {
+ sh(`hyprctl dispatch workspace ${arg}`);
+};
+
+const Workspaces = ws =>
+ Widget.Box({
+ children: range(ws || 20).map(i =>
+ Widget.Label({
+ attribute: i,
+ vpack: 'center',
+ label: `${i}`,
+ setup: self => {
+ const updateState = () => {
+ const monitorData = JSON.parse(hyprland.message('j/monitors'));
+ const activeWorkspaceId = monitorData[0]?.activeWorkspace?.id;
+ const workspaceData = hyprland.getWorkspace(i);
+
+ if (activeWorkspaceId !== undefined) {
+ self.toggleClassName('active', activeWorkspaceId === i);
+ }
+ self.toggleClassName('occupied', (workspaceData?.windows || 0) > 0);
+ };
+
+ // Hook to Hyprland for updates
+ self.hook(hyprland, updateState);
+
+ // Initial update
+ updateState();
+ },
+ }),
+ ),
+ setup: box => {
+ box.hook(hyprland, () => {
+ const monitorData = JSON.parse(hyprland.message('j/monitors'));
+ const activeWorkspaceId = monitorData[0]?.activeWorkspace?.id;
+
+ if (activeWorkspaceId !== undefined) {
+ for (const btn of box.children) {
+ const workspaceId = btn.attribute;
+ btn.toggleClassName('active', workspaceId === activeWorkspaceId);
+
+ if (ws === 0) {
+ btn.visible = hyprland.workspaces.some(workspace => workspace.id === workspaceId);
+ }
+ }
+ }
+ });
+ },
+ });
+
+export default () =>
+ PanelButton({
+ window: 'overview',
+ class_name: 'workspaces',
+ on_scroll_up: () => dispatch('m+1'),
+ on_scroll_down: () => dispatch('m-1'),
+ on_clicked: () => App.toggleWindow('overview'),
+ child: workspaces.bind().as(Workspaces),
+ });
diff --git a/linux/home/.config/ags/widget/bar/screencorner.scss b/linux/home/.config/ags/widget/bar/screencorner.scss
new file mode 100644
index 0000000..93cd459
--- /dev/null
+++ b/linux/home/.config/ags/widget/bar/screencorner.scss
@@ -0,0 +1,51 @@
+$_shadow-size: $padding;
+$_radius: $radius * $hyprland-gaps-multiplier;
+$_margin: 99px;
+
+window.screen-corner {
+ box.shadow {
+ margin-right: $_margin * -1;
+ margin-left: $_margin * -1;
+
+ @if $shadows {
+ box-shadow: inset 0 0 $_shadow-size 0 transparent;
+ }
+
+ @if $bar-position =='top' {
+ margin-bottom: $_margin * -1;
+ }
+
+ @if $bar-position =='bottom' {
+ margin-top: $_margin * -1;
+ }
+ }
+
+ box.border {
+ @if $bar-position =='top' {
+ border-top: $border-width none $bg;
+ //border-top: $border-width solid $bg;
+ }
+
+ @if $bar-position =='bottom' {
+ border-bottom: $border-width solid $bg;
+ }
+
+ margin-right: $_margin;
+ margin-left: $_margin;
+ }
+
+ box.corner {
+ box-shadow: 0 0 0 $border-width $border-color;
+ }
+
+ &.corners {
+ box.border {
+ border-radius: if($radius>0, $radius * $hyprland-gaps-multiplier, 0);
+ box-shadow: 0 0 0 $_radius $bg;
+ }
+
+ box.corner {
+ border-radius: if($radius>0, $radius * $hyprland-gaps-multiplier, 0);
+ }
+ }
+}
diff --git a/linux/home/.config/ags/widget/datemenu/DateColumn.ts b/linux/home/.config/ags/widget/datemenu/DateColumn.ts
new file mode 100644
index 0000000..a462302
--- /dev/null
+++ b/linux/home/.config/ags/widget/datemenu/DateColumn.ts
@@ -0,0 +1,58 @@
+import { clock, uptime } from 'lib/variables';
+import GLib from 'gi://GLib';
+import Gtk from 'gi://Gtk';
+
+function up(up: number) {
+ const h = Math.floor(up / 60);
+ const m = Math.floor(up % 60);
+ return `uptime: ${h}:${m < 10 ? '0' + m : m}`;
+}
+
+export default () =>
+ Widget.Box({
+ vertical: true,
+ class_name: 'date-column vertical',
+ children: [
+ Widget.Box({
+ class_name: 'clock-box',
+ vertical: true,
+ children: [
+ Widget.Label({
+ class_name: 'clock',
+ label: clock.bind().as(t => t.format('%H:%M')!),
+ }),
+ Widget.Label({
+ class_name: 'uptime',
+ label: uptime.bind().as(up),
+ }),
+ ],
+ }),
+ Widget.Box({
+ class_name: 'calendar',
+ children: [
+ (() => {
+ const calendar = Widget.Calendar({
+ hexpand: true,
+ hpack: 'center',
+ });
+
+ // Get today's date and mark it
+ const today = new Date();
+ calendar.select_day(today.getDate());
+ calendar.select_month(today.getMonth(), today.getFullYear());
+ calendar.mark_day(today.getDate()); // This should trigger styling
+
+ // Prevent scrolling from triggering GNOME Calendar
+ const eventBox = Widget.EventBox({
+ child: calendar,
+ onPrimaryClick: () => {
+ GLib.spawn_command_line_async('gnome-calendar');
+ },
+ });
+
+ return eventBox;
+ })(),
+ ],
+ }),
+ ],
+ });
diff --git a/linux/home/.config/ags/widget/datemenu/DateMenu.ts b/linux/home/.config/ags/widget/datemenu/DateMenu.ts
new file mode 100644
index 0000000..f7fdf6d
--- /dev/null
+++ b/linux/home/.config/ags/widget/datemenu/DateMenu.ts
@@ -0,0 +1,36 @@
+import PopupWindow from "widget/PopupWindow"
+import NotificationColumn from "./NotificationColumn"
+import DateColumn from "./DateColumn"
+import options from "options"
+
+const { bar, datemenu } = options
+const pos = bar.position.bind()
+const layout = Utils.derive([bar.position, datemenu.position], (bar, qs) =>
+ `${bar}-${qs}` as const,
+)
+
+const Settings = () => Widget.Box({
+ class_name: "datemenu horizontal",
+ vexpand: false,
+ children: [
+ NotificationColumn(),
+ Widget.Separator({ orientation: 1 }),
+ DateColumn(),
+ ],
+})
+
+const DateMenu = () => PopupWindow({
+ name: "datemenu",
+ exclusivity: "exclusive",
+ transition: pos.as(pos => pos === "top" ? "slide_down" : "slide_up"),
+ layout: layout.value,
+ child: Settings(),
+})
+
+export function setupDateMenu() {
+ App.addWindow(DateMenu())
+ layout.connect("changed", () => {
+ App.removeWindow("datemenu")
+ App.addWindow(DateMenu())
+ })
+}
diff --git a/linux/home/.config/ags/widget/datemenu/NotificationColumn.ts b/linux/home/.config/ags/widget/datemenu/NotificationColumn.ts
new file mode 100644
index 0000000..07d6829
--- /dev/null
+++ b/linux/home/.config/ags/widget/datemenu/NotificationColumn.ts
@@ -0,0 +1,113 @@
+import { type Notification as Notif } from "types/service/notifications"
+import Notification from "widget/notifications/Notification"
+import options from "options"
+import icons from "lib/icons"
+
+const notifications = await Service.import("notifications")
+const notifs = notifications.bind("notifications")
+
+const Animated = (n: Notif) => Widget.Revealer({
+ transition_duration: options.transition.value,
+ transition: "slide_down",
+ child: Notification(n),
+ setup: self => Utils.timeout(options.transition.value, () => {
+ if (!self.is_destroyed)
+ self.reveal_child = true
+ }),
+})
+
+const ClearButton = () => Widget.Button({
+ on_clicked: notifications.clear,
+ sensitive: notifs.as(n => n.length > 0),
+ child: Widget.Box({
+ children: [
+ Widget.Label("Clear "),
+ Widget.Icon({
+ icon: notifs.as(n => icons.trash[n.length > 0 ? "full" : "empty"]),
+ }),
+ ],
+ }),
+})
+
+const Header = () => Widget.Box({
+ class_name: "header",
+ children: [
+ Widget.Label({ label: "Notifications", hexpand: true, xalign: 0 }),
+ ClearButton(),
+ ],
+})
+
+const NotificationList = () => {
+ const map: Map<number, ReturnType<typeof Animated>> = new Map
+ const box = Widget.Box({
+ vertical: true,
+ children: notifications.notifications.map(n => {
+ const w = Animated(n)
+ map.set(n.id, w)
+ return w
+ }),
+ visible: notifs.as(n => n.length > 0),
+ })
+
+ function remove(_: unknown, id: number) {
+ const n = map.get(id)
+ if (n) {
+ n.reveal_child = false
+ Utils.timeout(options.transition.value, () => {
+ n.destroy()
+ map.delete(id)
+ })
+ }
+ }
+
+ return box
+ .hook(notifications, remove, "closed")
+ .hook(notifications, (_, id: number) => {
+ if (id !== undefined) {
+ if (map.has(id))
+ remove(null, id)
+
+ const n = notifications.getNotification(id)!
+
+ const w = Animated(n)
+ map.set(id, w)
+ box.children = [w, ...box.children]
+ }
+ }, "notified")
+}
+
+const Placeholder = () => Widget.Box({
+ class_name: "placeholder",
+ vertical: true,
+ vpack: "center",
+ hpack: "center",
+ vexpand: true,
+ hexpand: true,
+ visible: notifs.as(n => n.length === 0),
+ children: [
+ Widget.Icon(icons.notifications.silent),
+ Widget.Label("Your inbox is empty"),
+ ],
+})
+
+export default () => Widget.Box({
+ class_name: "notifications",
+ css: options.notifications.width.bind().as(w => `min-width: ${w}px`),
+ vertical: true,
+ children: [
+ Header(),
+ Widget.Scrollable({
+ vexpand: true,
+ hscroll: "never",
+ class_name: "notification-scrollable",
+ child: Widget.Box({
+ class_name: "notification-list vertical",
+ vertical: true,
+ children: [
+ NotificationList(),
+ Placeholder(),
+ ],
+ }),
+ }),
+ ],
+})
diff --git a/linux/home/.config/ags/widget/datemenu/datemenu.scss b/linux/home/.config/ags/widget/datemenu/datemenu.scss
new file mode 100644
index 0000000..6fd9257
--- /dev/null
+++ b/linux/home/.config/ags/widget/datemenu/datemenu.scss
@@ -0,0 +1,110 @@
+@import "../notifications/notifications.scss";
+
+@mixin calendar {
+ @include widget;
+ padding: $padding*2 $padding*2 0;
+
+ calendar {
+ all: unset;
+
+ &.button {
+ @include button($flat: true);
+ }
+
+ &:selected {
+ box-shadow: inset 0 -8px 0 0 transparentize($primary-bg, 0.5),
+ inset 0 0 0 1px $primary-bg;
+ border-radius: $radius*0.6;
+ }
+
+ &.header {
+ background-color: transparent;
+ border: none;
+ color: transparentize($fg, 0.5);
+ }
+
+ &.highlight {
+ background-color: transparent;
+ color: transparentize($primary-bg, 0.5);
+ }
+
+ &:indeterminate {
+ color: transparentize($fg, 0.9);
+ }
+
+ font-size: 1.1em;
+ padding: .2em;
+ }
+}
+
+window#datemenu .datemenu {
+ @include floating-widget;
+
+ .notifications {
+ .header {
+ margin-bottom: $spacing;
+ margin-right: $spacing;
+
+ >label {
+ margin-left: $radius * .5;
+ }
+
+ button {
+ @include button;
+ padding: $padding*.7 $padding;
+ }
+ }
+
+ .notification-scrollable {
+ @include scrollable($top: true, $bottom: true);
+ }
+
+ .notification-list {
+ margin-right: $spacing;
+ }
+
+ .notification {
+ @include notification;
+ @include widget;
+ padding: $padding;
+ margin-bottom: $spacing;
+ }
+
+ .placeholder {
+ image {
+ font-size: 7em;
+ }
+
+ label {
+ font-size: 1.2em;
+ }
+ }
+ }
+
+
+ separator {
+ background-color: $popover-border-color;
+ border-radius: $radius;
+ margin-right: $spacing;
+ }
+
+ .datemenu {
+ @include spacing;
+ }
+
+ .clock-box {
+ padding: $padding;
+
+ .clock {
+ font-size: 5em;
+ }
+
+ .uptime {
+ color: transparentize($fg, 0.2);
+ }
+ }
+
+ .calendar {
+ @include calendar;
+ }
+}
diff --git a/linux/home/.config/ags/widget/desktop/Desktop.ts b/linux/home/.config/ags/widget/desktop/Desktop.ts
new file mode 100644
index 0000000..f711967
--- /dev/null
+++ b/linux/home/.config/ags/widget/desktop/Desktop.ts
@@ -0,0 +1,40 @@
+import options from "options"
+import { matugen } from "lib/matugen"
+const mpris = await Service.import("mpris")
+
+const pref = () => options.bar.media.preferred.value
+
+export default (monitor: number) => Widget.Window({
+ monitor,
+ layer: "bottom",
+ name: `desktop${monitor}`,
+ class_name: "desktop",
+ anchor: ["top", "bottom", "left", "right"],
+ child: Widget.Box({
+ expand: true,
+ css: options.theme.dark.primary.bg.bind().as(c => `
+ transition: 500ms;
+ background-color: ${c}`),
+ child: Widget.Box({
+ class_name: "wallpaper",
+ expand: true,
+ vpack: "center",
+ hpack: "center",
+ setup: self => self
+ .hook(mpris, () => {
+ const img = mpris.getPlayer(pref())!.cover_path
+ matugen("image", img)
+ Utils.timeout(500, () => self.css = `
+ background-image: url('${img}');
+ background-size: contain;
+ background-repeat: no-repeat;
+ transition: 200ms;
+ min-width: 700px;
+ min-height: 700px;
+ border-radius: 30px;
+ box-shadow: 25px 25px 30px 0 rgba(0,0,0,0.5);`,
+ )
+ }),
+ }),
+ }),
+})
diff --git a/linux/home/.config/ags/widget/dock/Dock.ts b/linux/home/.config/ags/widget/dock/Dock.ts
new file mode 100644
index 0000000..c55f89f
--- /dev/null
+++ b/linux/home/.config/ags/widget/dock/Dock.ts
@@ -0,0 +1,150 @@
+import { launchApp } from "lib/utils";
+import options from "options";
+import * as Gtk from "gi://Gtk?version=3.0";
+import { type ButtonProps } from "types/widgets/button";
+import { type BoxProps } from "types/widgets/box";
+
+const hyprland = await Service.import("hyprland");
+const applications = await Service.import("applications");
+
+const focus = (address: string) => hyprland.messageAsync(`dispatch focuswindow address:${address}`);
+
+const AppButton = ({ icon, pinned = false, term, ...rest }: ButtonProps & { term?: string }): Gtk.Button & ButtonProps => {
+ const { iconSize } = options.dock;
+
+ const buttonBox = Widget.Box({
+ class_name: 'box',
+ child: Widget.Icon({
+ icon,
+ size: iconSize,
+ }),
+ });
+
+ const button = Widget.Button({
+ ...rest,
+ class_name: '',
+ child: pinned ? buttonBox : Widget.Overlay({
+ child: buttonBox,
+ pass_through: false,
+ overlays: [],
+ }),
+ });
+
+ return Object.assign(button, {});
+};
+
+const createAppButton = ({ app, term, ...params }) => {
+ return AppButton({
+ icon: app.icon_name || '',
+ term,
+ ...params,
+ });
+};
+
+const filterValidClients = (clients: any[]) => {
+ return clients.filter(client => (
+ client.mapped &&
+ [client.class, client.title, client.initialClass].every(prop => typeof prop === 'string' && prop !== '')
+ ));
+};
+
+const Taskbar = (): Gtk.Box & BoxProps => {
+ const addedApps = new Set<string>();
+
+ const updateTaskbar = (clients: any[]) => {
+ const validClients = filterValidClients(clients);
+
+ const focusedAddress = hyprland?.active.client?.address;
+ const running = validClients.filter(client => client.mapped);
+ const focused = running.find(client => client.address === focusedAddress);
+
+ return validClients.map(client => {
+ if (![client.class, client.title, client.initialClass].every(prop => typeof prop === 'string' && prop !== '')) {
+ return null;
+ }
+
+ if (addedApps.has(client.title)) {
+ return null;
+ }
+
+ for (const appName of options.dock.pinnedApps.value) {
+ if (!appName || typeof appName !== 'string') {
+ continue;
+ }
+
+ if (client.class.includes(appName) || client.title.includes(appName)
+ || client.initialClass.includes(appName)) {
+ return null;
+ }
+ }
+
+ const matchingApp = applications?.list.find(app => (
+ app.match(client.title) || app.match(client.class) || app.match(client.initialClass)
+ ));
+
+ if (matchingApp) {
+ addedApps.add(client.title);
+ return createAppButton({
+ app: matchingApp,
+ term: matchingApp.title,
+ on_primary_click: () => {
+ const clickAddress = client.address || focusedAddress;
+ clickAddress && focus(clickAddress);
+ },
+ on_secondary_click: () => launchApp(matchingApp),
+ });
+ a
+ }
+ return null;
+ });
+ };
+
+ return Widget.Box({
+ vertical: false,
+ })
+ .bind('children', hyprland, 'clients', updateTaskbar);
+};
+
+const PinnedApps = (): Gtk.Box & BoxProps => {
+ const updatePinnedApps = (pinnedApps: string[]) => {
+ return pinnedApps
+ .map(term => ({ app: applications?.query(term)?.[0], term }))
+ .filter(({ app }) => app)
+ .map(({ app, term = true }) => createAppButton({
+ app,
+ term,
+ pinned: true,
+ on_primary_click: () => {
+ const matchingClients = hyprland?.clients.filter(client => (
+ typeof client.class === 'string' &&
+ typeof client.title === 'string' &&
+ typeof client.initialClass === 'string' &&
+ (client.class.includes(term) || client.title.includes(term) || client.initialClass.includes(term))
+ ));
+
+ if (matchingClients.length > 0) {
+ const { address } = matchingClients[0];
+ address && focus(address);
+ } else {
+ launchApp(app);
+ }
+ },
+ on_secondary_click: () => launchApp(app),
+ }));
+ };
+
+ return Widget.Box({
+ class_name: 'pins',
+ vertical: false,
+ homogeneous: true,
+ })
+ .bind('children', options.dock.pinnedApps, 'value', updatePinnedApps);
+};
+
+const Dock = (): Gtk.Box & BoxProps => Widget.Box({
+ class_name: 'dock',
+ vertical: false,
+ children: [PinnedApps(), Taskbar()],
+});
+
+export default Dock;
diff --git a/linux/home/.config/ags/widget/dock/FloatingDock.ts b/linux/home/.config/ags/widget/dock/FloatingDock.ts
new file mode 100644
index 0000000..369f56f
--- /dev/null
+++ b/linux/home/.config/ags/widget/dock/FloatingDock.ts
@@ -0,0 +1,70 @@
+import options from 'options';
+import Dock from './Dock.ts';
+const hyprland = await Service.import('hyprland');
+const apps = await Service.import('applications');
+
+const { Gdk, Gtk } = imports.gi;
+import type Gtk from 'gi://Gtk?version=3.0';
+import { type WindowProps } from 'types/widgets/window';
+import { type RevealerProps } from 'types/widgets/revealer';
+import { type EventBoxProps } from 'types/widgets/eventbox';
+
+/** @param {number} monitor */
+const FloatingDock = (monitor: number): Gtk.Window & WindowProps => {
+ const update = () => {
+ const ws = Hyprland.getWorkspace(Hyprland.active.workspace.id);
+ if (Hyprland.getMonitor(monitor)?.name === ws?.monitor) self.reveal_child = ws?.windows === 0;
+ };
+ const revealer: Gtk.Revealer & RevealerProps = Widget.Revealer({
+ transition: 'slide_up',
+ transitionDuration: 90,
+ child: Dock(),
+ setup: self => self.hook(hyprland, update, 'client-added').hook(hyprland, update, 'client-removed').hook(hyprland.active.workspace, update),
+ });
+
+ const window = Widget.Window({
+ monitor,
+ //halign: 'fill',
+ halign: 'end',
+ //layer: "overlay",
+ layer: 'dock',
+ name: `dock${monitor}`,
+ click_through: false,
+ class_name: 'floating-dock',
+ // class_name: 'floating-dock-no-gap',
+ // class_name: "f-dock-wrap",
+
+ typeHint: Gdk.WindowTypeHint.DOCK,
+ exclusivity: 'false',
+
+ anchor: ['bottom'],
+ child: Widget.Box({
+ vertical: false,
+ halign: 'bottom',
+ hpack: 'start',
+ children: [
+ revealer,
+ Widget.Box({
+ class_name: 'padding',
+ css: 'padding: 9px; margin: 0;',
+ vertical: false,
+ halign: 'bottom',
+ hpack: 'start',
+ }),
+ ],
+ }),
+ });
+
+ window
+ .on('enter-notify-event', () => {
+ revealer.reveal_child = true;
+ })
+ .on('leave-notify-event', () => {
+ revealer.reveal_child = false;
+ })
+ .bind('visible', options.bar.position, 'value', v => v !== 'left');
+
+ return window;
+};
+
+export default FloatingDock;
diff --git a/linux/home/.config/ags/widget/dock/ToolBox.ts b/linux/home/.config/ags/widget/dock/ToolBox.ts
new file mode 100644
index 0000000..51fda72
--- /dev/null
+++ b/linux/home/.config/ags/widget/dock/ToolBox.ts
@@ -0,0 +1,122 @@
+import options from "options";
+import { sh } from "lib/utils";
+import * as Gtk from "gi://Gtk?version=3.0";
+import { type ButtonProps } from "types/widgets/button";
+import { type BoxProps } from "types/widgets/box";
+
+const hyprland = await Service.import("hyprland");
+const { icons } = options.dock.toolbox;
+const buttonToggles = {};
+
+const dispatch = (action: string, arg: string) => {
+ //console.log(`Performing action: ${action} with argument: ${arg}`);
+ sh(`hyprctl dispatch ${action} ${arg}`);
+};
+
+const keyword = (action: string, arg: string) => {
+ //console.log(`Performing action: ${action} with argument: ${arg}`);
+ sh(`hyprctl keyword ${action} ${arg}`);
+};
+
+const ToggleSwitch = (buttonIndex, actionOn, argOn, actionOff, argOff, actionExec) => {
+ buttonToggles[buttonIndex] = !buttonToggles[buttonIndex];
+ const { action, arg } = buttonToggles[buttonIndex] ? { action: actionOn, arg: argOn } : { action: actionOff, arg: argOff };
+ actionExec(action, arg);
+};
+
+const ToggleOnMulti = (buttonIndex, actionOn, argOn, actionOff, argOff, actionExec) => {
+ buttonToggles[buttonIndex] = !buttonToggles[buttonIndex];
+ if (buttonToggles[buttonIndex]) {
+ actionOn.forEach(({ action, arg }) => {
+ actionExec(action, arg);
+ });
+ } else {
+ actionExec(actionOff, argOff);
+ }
+};
+
+const execAction = (trigger, actionIndex, actionOn, argOn, actionOff, argOff, action, arg, actionExec) => {
+ switch (trigger) {
+ case 'toggleOn-multi':
+ ToggleOnMulti(actionIndex, actionOn, argOn, actionOff, argOff, actionExec);
+ break;
+ case 'toggle-switch':
+ ToggleSwitch(actionIndex, actionOn, argOn, actionOff, argOff, actionExec);
+ break;
+ case 'oneshot':
+ actionExec(action, arg);
+ break;
+ default:
+ break;
+ }
+};
+
+const buttonConfigs = [
+ { actionExec: dispatch, trigger: 'oneshot', actionIndex: 0, action: 'killactive', arg: '' },
+ { actionExec: dispatch, trigger: 'oneshot', actionIndex: 1, action: 'exec hyprctl', arg: 'kill' },
+ {
+ actionExec: keyword,
+ trigger: 'toggle-switch',
+ actionIndex: 2,
+ actionOn: 'monitor', argOn: 'eDP-1,2736x1824,0x0,0,transform,1',
+ actionOff: 'monitor', argOff: 'eDP-1,2736x1824,0x0,0,transform,0'
+ },
+ { actionExec: dispatch, trigger: 'oneshot', actionIndex: 3, action: 'workspace', arg: 'r-1' },
+ { actionExec: dispatch, trigger: 'oneshot', actionIndex: 4, action: 'workspace', arg: 'r+1' },
+ { actionExec: dispatch, trigger: 'oneshot', actionIndex: 5, action: 'movewindow', arg: 'l' },
+ { actionExec: dispatch, trigger: 'oneshot', actionIndex: 6, action: 'movewindow', arg: 'r' },
+ { actionExec: dispatch, trigger: 'oneshot', actionIndex: 7, action: 'movewindow', arg: 'u' },
+ { actionExec: dispatch, trigger: 'oneshot', actionIndex: 8, action: 'movewindow', arg: 'd' },
+ { actionExec: dispatch, trigger: 'oneshot', actionIndex: 9, action: 'swapnext', arg: 'next' },
+ { actionExec: dispatch, trigger: 'oneshot', actionIndex: 10, action: 'togglesplit', arg: '' },
+ {
+ actionExec: dispatch,
+ trigger: 'toggleOn-multi',
+ actionIndex: 11,
+ actionOn: [
+ { action: 'setfloating', arg: 'active' },
+ { action: 'resizeactive', arg: 'exact 90% 90%' },
+ { action: 'centerwindow', arg: '' },
+ ],
+ actionOff: 'settiled', argOff: 'active'
+ },
+ { actionExec: dispatch, trigger: 'oneshot', actionIndex: 12, action: 'pin', arg: '' },
+ { actionExec: dispatch, trigger: 'oneshot', actionIndex: 13, action: 'fullscreen', arg: '0' },
+ {
+ actionExec: dispatch,
+ trigger: 'toggle-switch',
+ actionIndex: 14,
+ actionOn: 'exec', argOn: 'wvctl 1',
+ actionOff: 'exec', argOff: 'wvctl 0'
+ },
+];
+
+const ToolBox = () => {
+ const ToolBoxButtons = () => {
+ const buttons = buttonConfigs.map(({ actionIndex, actionOn, argOn, actionOff, argOff, actionExec, trigger, action, arg }) => {
+ const execActionWrapper = () => execAction(trigger, actionIndex, actionOn, argOn, actionOff, argOff, action, arg, actionExec);
+
+ return Widget.Button({
+ child: Widget.Icon({
+ icon: icons[actionIndex].bind(),
+ }),
+ on_clicked: execActionWrapper,
+ });
+ });
+
+ return Widget.Box({
+ vertical: true,
+ homogeneous: true,
+ children: buttons,
+ });
+ };
+
+ return Widget.Box({
+ class_name: "toolbox",
+ vertical: true,
+ homogeneous: true,
+ children: [ToolBoxButtons()],
+ });
+};
+
+export default ToolBox;
diff --git a/linux/home/.config/ags/widget/dock/ToolBoxDock.ts b/linux/home/.config/ags/widget/dock/ToolBoxDock.ts
new file mode 100644
index 0000000..21beaeb
--- /dev/null
+++ b/linux/home/.config/ags/widget/dock/ToolBoxDock.ts
@@ -0,0 +1,57 @@
+import options from "options";
+import ToolBox from "./ToolBox.ts";
+const hyprland = await Service.import("hyprland");
+const apps = await Service.import("applications");
+
+import type Gtk from "gi://Gtk?version=3.0";
+import { type WindowProps } from "types/widgets/window";
+import { type RevealerProps } from "types/widgets/revealer";
+import { type EventBoxProps } from "types/widgets/eventbox";
+
+/** @param {number} monitor */
+const ToolBoxDock = (monitor: number): Gtk.Window & WindowProps => {
+
+ const revealer: Gtk.Revealer & RevealerProps = Widget.Revealer({
+ transition: 'slide_left',
+ transitionDuration: 50,
+ child: ToolBox(),
+ });
+
+ const window = Widget.Window({
+ monitor,
+ halign: 'fill',
+ layer: "overlay",
+ name: `toolbox${monitor}`,
+ click_through: false,
+ class_name: 'floating-toolbox',
+ anchor: ['right'],
+ child: Widget.Box({
+ vertical: true,
+ halign: 'top',
+ hpack: 'fill',
+ children: [
+ revealer,
+ Widget.Box({
+ class_name: 'padding',
+ css: 'padding: 14px;',
+ vertical: true,
+ halign: 'top',
+ hpack: 'fill',
+ }),
+ ],
+ }),
+ });
+
+ window
+ .on('enter-notify-event', () => {
+ revealer.reveal_child = true;
+ })
+ .on('leave-notify-event', () => {
+ revealer.reveal_child = false;
+ })
+ .bind('visible', options.bar.position, 'value', v => v !== 'left');
+
+ return window;
+};
+
+export default ToolBoxDock;
diff --git a/linux/home/.config/ags/widget/dock/dock.scss b/linux/home/.config/ags/widget/dock/dock.scss
new file mode 100644
index 0000000..9dc6256
--- /dev/null
+++ b/linux/home/.config/ags/widget/dock/dock.scss
@@ -0,0 +1,73 @@
+@use 'sass:color';
+
+.floating-dock {
+ padding-left: 0.2rem;
+ padding-right: 0.2rem;
+ padding-top: 0.3rem;
+ padding-bottom: 0.3rem;
+ border-radius: 1rem;
+}
+
+.dock {
+ // @include floating-widget;
+ border-radius: $radius;
+ background-color: transparentize($bg, 0.07);
+ min-width: 0;
+ padding: 6;
+
+ // Common styles for both PinnedApps and Taskbar buttons
+ button {
+ @include button($flat: true);
+ border-radius: 4;
+ padding: 2;
+
+ .box {
+ margin: 0em;
+ min-width: 1em;
+ min-height: 0em;
+ padding: 0;
+ }
+
+ image {
+ margin: 0px;
+ }
+
+ .indicator {
+ background-color: transparentize($primary-bg, 0.3);
+ border-radius: $radius;
+ min-height: 1pt;
+ min-width: 16pt;
+ margin: 1pt;
+ }
+ }
+}
+
+.toolbox {
+ // @include floating-widget;
+ border-radius: $radius;
+ background-color: transparentize($bg, 0.07);
+ min-width: 0;
+ padding: 6;
+
+ // Common styles for both PinnedApps and Taskbar buttons
+ button {
+ @include button($flat: true);
+ border-radius: 0;
+ padding: 0;
+
+ image {
+ margin: 0px;
+ font-size: 32px;
+ }
+
+ &:hover,
+ &:active,
+ &:focus {
+ // Override hover, active, and focus styles for buttons in .toolbox
+ background-color: transparent;
+ border: none;
+ box-shadow: none;
+ outline: none;
+ }
+ }
+}
diff --git a/linux/home/.config/ags/widget/launcher/AppLauncher.ts b/linux/home/.config/ags/widget/launcher/AppLauncher.ts
new file mode 100644
index 0000000..08258de
--- /dev/null
+++ b/linux/home/.config/ags/widget/launcher/AppLauncher.ts
@@ -0,0 +1,125 @@
+import { type Application } from 'types/service/applications';
+import { launchApp, icon } from 'lib/utils';
+import options from 'options';
+import icons from 'lib/icons';
+
+const apps = await Service.import('applications');
+const { query } = apps;
+const { iconSize } = options.launcher.apps;
+
+const QuickAppButton = (app: Application) =>
+ Widget.Button({
+ hexpand: true,
+ tooltip_text: app.name,
+ on_clicked: () => {
+ App.closeWindow('launcher');
+ launchApp(app);
+ },
+ child: Widget.Icon({
+ size: iconSize.bind(),
+ icon: icon(app.icon_name, icons.fallback.executable),
+ }),
+ });
+
+const AppItem = (app: Application) => {
+ const title = Widget.Label({
+ class_name: 'title',
+ label: app.name,
+ hexpand: true,
+ xalign: 0,
+ vpack: 'center',
+ truncate: 'end',
+ });
+
+ const description = Widget.Label({
+ class_name: 'description',
+ label: app.description || '',
+ hexpand: true,
+ wrap: true,
+ max_width_chars: 30,
+ xalign: 0,
+ justification: 'left',
+ vpack: 'center',
+ });
+
+ const appicon = Widget.Icon({
+ icon: icon(app.icon_name, icons.fallback.executable),
+ size: iconSize.bind(),
+ });
+
+ const textBox = Widget.Box({
+ vertical: true,
+ vpack: 'center',
+ children: app.description ? [title, description] : [title],
+ });
+
+ return Widget.Button({
+ class_name: 'app-item',
+ attribute: { app },
+ child: Widget.Box({
+ children: [appicon, textBox],
+ }),
+ on_clicked: () => {
+ App.closeWindow('launcher');
+ launchApp(app);
+ },
+ });
+};
+export function Favorites() {
+ const favs = options.launcher.apps.favorites.bind();
+ return Widget.Revealer({
+ visible: favs.as(f => f.length > 0),
+ child: Widget.Box({
+ vertical: true,
+ children: favs.as(favs =>
+ favs.flatMap(fs => [
+ Widget.Separator(),
+ Widget.Box({
+ class_name: 'quicklaunch horizontal',
+ children: fs
+ .map(f => query(f)?.[0])
+ .filter(f => f)
+ .map(QuickAppButton),
+ }),
+ ]),
+ ),
+ }),
+ });
+}
+
+export function Launcher() {
+ const applist = Variable(query(''));
+ const max = options.launcher.apps.max;
+ let first = applist.value[0];
+
+ function SeparatedAppItem(app: Application) {
+ return Widget.Revealer({ attribute: { app } }, Widget.Box({ vertical: true }, Widget.Separator(), AppItem(app)));
+ }
+
+ const list = Widget.Box({
+ vertical: true,
+ children: applist.bind().as(list => list.map(SeparatedAppItem)),
+ setup: self => self.hook(apps, () => (applist.value = query('')), 'notify::frequents'),
+ });
+
+ return Object.assign(list, {
+ filter(text: string | null) {
+ first = query(text || '')[0];
+ list.children.reduce((i, item) => {
+ if (!text || i >= max.value) {
+ item.reveal_child = false;
+ return i;
+ }
+ if (item.attribute.app.match(text)) {
+ item.reveal_child = true;
+ return ++i;
+ }
+ item.reveal_child = false;
+ return i;
+ }, 0);
+ },
+ launchFirst() {
+ launchApp(first);
+ },
+ });
+}
diff --git a/linux/home/.config/ags/widget/launcher/Launcher.ts b/linux/home/.config/ags/widget/launcher/Launcher.ts
new file mode 100644
index 0000000..90b4d58
--- /dev/null
+++ b/linux/home/.config/ags/widget/launcher/Launcher.ts
@@ -0,0 +1,134 @@
+import { type Binding } from 'lib/utils';
+import PopupWindow, { Padding } from 'widget/PopupWindow';
+import icons from 'lib/icons';
+import options from 'options';
+import nix from 'service/nix';
+import * as AppLauncher from './AppLauncher';
+import * as NixRun from './NixRun';
+import * as ShRun from './ShRun';
+
+const { width, margin } = options.launcher;
+const isnix = nix.available;
+
+function Launcher() {
+ const favs = AppLauncher.Favorites();
+ const applauncher = AppLauncher.Launcher();
+ const sh = ShRun.ShRun();
+ const shicon = ShRun.Icon();
+ const nix = NixRun.NixRun();
+ const nixload = NixRun.Spinner();
+
+ function HelpButton(cmd: string, desc: string | Binding<string>) {
+ return Widget.Box(
+ { vertical: true },
+ Widget.Separator(),
+ Widget.Button(
+ {
+ class_name: 'help',
+ on_clicked: () => {
+ entry.grab_focus();
+ entry.text = `:${cmd} `;
+ entry.set_position(-1);
+ },
+ },
+ Widget.Box([
+ Widget.Label({
+ class_name: 'name',
+ label: `:${cmd}`,
+ }),
+ Widget.Label({
+ hexpand: true,
+ hpack: 'end',
+ class_name: 'description',
+ label: desc,
+ }),
+ ]),
+ ),
+ );
+ }
+
+ const help = Widget.Revealer({
+ child: Widget.Box(
+ { vertical: true },
+ HelpButton('sh', 'run a binary'),
+ isnix
+ ? HelpButton(
+ 'nx',
+ options.launcher.nix.pkgs.bind().as(pkg => `run a nix package from ${pkg}`),
+ )
+ : Widget.Box(),
+ ),
+ });
+
+ const entry = Widget.Entry({
+ hexpand: true,
+ primary_icon_name: icons.ui.search,
+ on_accept: ({ text }) => {
+ if (text?.startsWith(':nx')) nix.run(text.substring(3));
+ else if (text?.startsWith(':sh')) sh.run(text.substring(3));
+ else applauncher.launchFirst();
+
+ App.toggleWindow('launcher');
+ entry.text = '';
+ },
+ on_change: ({ text }) => {
+ text ||= '';
+ favs.reveal_child = text === '';
+ help.reveal_child = text.split(' ').length === 1 && text?.startsWith(':');
+
+ if (text?.startsWith(':nx')) nix.filter(text.substring(3));
+ else nix.filter('');
+
+ if (text?.startsWith(':sh')) sh.filter(text.substring(3));
+ else sh.filter('');
+
+ if (!text?.startsWith(':')) applauncher.filter(text);
+ },
+ });
+
+ function focus() {
+ entry.text = '';
+ entry.set_position(-1);
+ entry.select_region(0, -1);
+ entry.grab_focus();
+ favs.reveal_child = true;
+ }
+
+ const layout = Widget.Box({
+ css: width.bind().as(v => `min-width: ${v}pt;`),
+ class_name: 'launcher',
+ vertical: true,
+ vpack: 'start',
+ setup: self =>
+ self.hook(App, (_, win, visible) => {
+ if (win !== 'launcher') return;
+
+ entry.text = '';
+ if (visible) focus();
+ }),
+ children: [
+ Widget.Box([entry, nixload, shicon]),
+ favs,
+ help,
+ applauncher,
+ //nix,
+ sh,
+ ],
+ });
+
+ return Widget.Box(
+ { vertical: true, css: 'padding: 1px' },
+ Padding('applauncher', {
+ css: margin.bind().as(v => `min-height: ${v}pt;`),
+ vexpand: false,
+ }),
+ layout,
+ );
+}
+
+export default () =>
+ PopupWindow({
+ name: 'launcher',
+ layout: 'top',
+ child: Launcher(),
+ });
diff --git a/linux/home/.config/ags/widget/launcher/NixRun.ts b/linux/home/.config/ags/widget/launcher/NixRun.ts
new file mode 100644
index 0000000..cec9e09
--- /dev/null
+++ b/linux/home/.config/ags/widget/launcher/NixRun.ts
@@ -0,0 +1,118 @@
+import icons from "lib/icons"
+import nix, { type Nixpkg } from "service/nix"
+
+const iconVisible = Variable(false)
+
+function Item(pkg: Nixpkg) {
+ const name = Widget.Label({
+ class_name: "name",
+ label: pkg.name.split(".").at(-1),
+ })
+
+ const subpkg = pkg.name.includes(".") ? Widget.Label({
+ class_name: "description",
+ hpack: "end",
+ hexpand: true,
+ label: ` ${pkg.name.split(".").slice(0, -1).join(".")}`,
+ }) : null
+
+ const version = Widget.Label({
+ class_name: "version",
+ label: pkg.version,
+ hexpand: true,
+ hpack: "end",
+ })
+
+ const description = pkg.description ? Widget.Label({
+ class_name: "description",
+ label: pkg.description,
+ justification: "left",
+ wrap: true,
+ hpack: "start",
+ max_width_chars: 40,
+ }) : null
+
+ return Widget.Box(
+ {
+ attribute: { name: pkg.name },
+ vertical: true,
+ },
+ Widget.Separator(),
+ Widget.Button(
+ {
+ class_name: "nix-item",
+ on_clicked: () => {
+ nix.run(pkg.name)
+ App.closeWindow("launcher")
+ },
+ },
+ Widget.Box(
+ { vertical: true },
+ Widget.Box([name, version]),
+ Widget.Box([
+ description as ReturnType<typeof Widget.Label>,
+ subpkg as ReturnType<typeof Widget.Label>,
+ ]),
+ ),
+ ),
+ )
+}
+
+export function Spinner() {
+ const icon = Widget.Icon({
+ icon: icons.nix.nix,
+ class_name: "spinner",
+ css: `
+ @keyframes spin {
+ to { -gtk-icon-transform: rotate(1turn); }
+ }
+
+ image.spinning {
+ animation-name: spin;
+ animation-duration: 1s;
+ animation-timing-function: linear;
+ animation-iteration-count: infinite;
+ }
+ `,
+ setup: self => self.hook(nix, () => {
+ self.toggleClassName("spinning", !nix.ready)
+ }),
+ })
+
+ return Widget.Revealer({
+ transition: "slide_left",
+ child: icon,
+ reveal_child: Utils.merge([
+ nix.bind("ready"),
+ iconVisible.bind(),
+ ], (ready, show) => !ready || show),
+ })
+}
+
+export function NixRun() {
+ const list = Widget.Box<ReturnType<typeof Item>>({
+ vertical: true,
+ })
+
+ const revealer = Widget.Revealer({
+ child: list,
+ })
+
+ async function filter(term: string) {
+ iconVisible.value = Boolean(term)
+
+ if (!term)
+ revealer.reveal_child = false
+
+ if (term.trim()) {
+ const found = await nix.query(term)
+ list.children = found.map(k => Item(nix.db[k]))
+ revealer.reveal_child = true
+ }
+ }
+
+ return Object.assign(revealer, {
+ filter,
+ run: nix.run,
+ })
+}
diff --git a/linux/home/.config/ags/widget/launcher/ShRun.ts b/linux/home/.config/ags/widget/launcher/ShRun.ts
new file mode 100644
index 0000000..c4215ef
--- /dev/null
+++ b/linux/home/.config/ags/widget/launcher/ShRun.ts
@@ -0,0 +1,89 @@
+import icons from "lib/icons"
+import options from "options"
+import { bash, dependencies } from "lib/utils"
+
+const iconVisible = Variable(false)
+
+const MAX = options.launcher.sh.max
+const BINS = `${Utils.CACHE_DIR}/binaries`
+bash("{ IFS=:; ls -H $PATH; } | sort ")
+ .then(bins => Utils.writeFile(bins, BINS))
+
+async function query(filter: string) {
+ if (!dependencies("fzf"))
+ return [] as string[]
+
+ return bash(`cat ${BINS} | fzf -f ${filter} | head -n ${MAX}`)
+ .then(str => Array.from(new Set(str.split("\n").filter(i => i)).values()))
+ .catch(err => { print(err); return [] })
+}
+
+function run(args: string) {
+ Utils.execAsync(args)
+ .then(out => {
+ print(`:sh ${args.trim()}:`)
+ print(out)
+ })
+ .catch(err => {
+ Utils.notify("ShRun Error", err, icons.app.terminal)
+ })
+}
+
+function Item(bin: string) {
+ return Widget.Box(
+ {
+ attribute: { bin },
+ vertical: true,
+ },
+ Widget.Separator(),
+ Widget.Button({
+ child: Widget.Label({
+ label: bin,
+ hpack: "start",
+ }),
+ class_name: "sh-item",
+ on_clicked: () => {
+ Utils.execAsync(bin)
+ App.closeWindow("launcher")
+ },
+ }),
+ )
+}
+
+export function Icon() {
+ const icon = Widget.Icon({
+ icon: icons.app.terminal,
+ class_name: "spinner",
+ })
+
+ return Widget.Revealer({
+ transition: "slide_left",
+ child: icon,
+ reveal_child: iconVisible.bind(),
+ })
+}
+
+export function ShRun() {
+ const list = Widget.Box<ReturnType<typeof Item>>({
+ vertical: true,
+ })
+
+ const revealer = Widget.Revealer({
+ child: list,
+ })
+
+ async function filter(term: string) {
+ iconVisible.value = Boolean(term)
+
+ if (!term)
+ revealer.reveal_child = false
+
+ if (term.trim()) {
+ const found = await query(term)
+ list.children = found.map(Item)
+ revealer.reveal_child = true
+ }
+ }
+
+ return Object.assign(revealer, { filter, run })
+}
diff --git a/linux/home/.config/ags/widget/launcher/launcher.scss b/linux/home/.config/ags/widget/launcher/launcher.scss
new file mode 100644
index 0000000..926abc3
--- /dev/null
+++ b/linux/home/.config/ags/widget/launcher/launcher.scss
@@ -0,0 +1,143 @@
+@use "sass:math";
+@use "sass:color";
+
+window#launcher .launcher {
+ @include floating_widget;
+
+ .quicklaunch {
+ @include spacing;
+
+ button {
+ @include button($flat: true);
+ padding: $padding;
+ }
+ }
+
+ entry {
+ @include button;
+ padding: $padding;
+ margin: $spacing;
+
+ selection {
+ color: color.mix($fg, $bg, 50%);
+ background-color: transparent;
+ }
+
+ label,
+ image {
+ color: $fg;
+ }
+ }
+
+ image.spinner {
+ color: $primary-bg;
+ margin-right: $spacing;
+ }
+
+ separator {
+ margin: 4pt 0;
+ background-color: $popover-border-color;
+ }
+
+ button.app-item {
+ @include button($flat: true, $reactive: false);
+
+ >box {
+ @include spacing(0.5);
+ }
+
+ transition: $transition;
+ padding: $padding;
+
+ label {
+ transition: $transition;
+
+ &.title {
+ color: $fg;
+ }
+
+ &.description {
+ color: transparentize($fg, 0.3);
+ }
+ }
+
+ image {
+ transition: $transition;
+ }
+
+ &:hover,
+ &:focus {
+ .title {
+ color: $primary-bg;
+ }
+
+ .description {
+ color: transparentize($primary-bg, .4);
+ }
+
+ image {
+ -gtk-icon-shadow: 2px 2px $primary-bg;
+ }
+ }
+
+ &:active {
+ background-color: transparentize($primary-bg, 0.5);
+ border-radius: $radius;
+ box-shadow: inset 0 0 0 $border-width $border-color;
+
+ .title {
+ color: $fg;
+ }
+ }
+ }
+
+ button.help,
+ button.nix-item {
+ @include button($flat: true, $reactive: false);
+ padding: 0 ($padding * .5);
+
+ label {
+ transition: $transition;
+ color: $fg;
+ }
+
+ .name {
+ font-size: 1.2em;
+ font-weight: bold;
+ }
+
+ .description {
+ color: transparentize($fg, .3)
+ }
+
+ &:hover,
+ &:focus {
+ label {
+ text-shadow: $text-shadow;
+ }
+
+ .name,
+ .version {
+ color: $primary-bg;
+ }
+
+ .description {
+ color: transparentize($primary-bg, .3)
+ }
+ }
+ }
+
+ button.sh-item {
+ @include button($flat: true, $reactive: false);
+ padding: 0 ($padding * .5);
+
+ transition: $transition;
+ color: $fg;
+
+ &:hover,
+ &:focus {
+ color: $primary-bg;
+ text-shadow: $text-shadow;
+ }
+ }
+}
diff --git a/linux/home/.config/ags/widget/notifications/Notification.ts b/linux/home/.config/ags/widget/notifications/Notification.ts
new file mode 100644
index 0000000..c1c8dd8
--- /dev/null
+++ b/linux/home/.config/ags/widget/notifications/Notification.ts
@@ -0,0 +1,138 @@
+import { type Notification } from "types/service/notifications"
+import GLib from "gi://GLib"
+import icons from "lib/icons"
+
+const time = (time: number, format = "%H:%M") => GLib.DateTime
+ .new_from_unix_local(time)
+ .format(format)
+
+const NotificationIcon = ({ app_entry, app_icon, image }: Notification) => {
+ if (image) {
+ return Widget.Box({
+ vpack: "start",
+ hexpand: false,
+ class_name: "icon img",
+ css: `
+ background-image: url("${image}");
+ background-size: cover;
+ background-repeat: no-repeat;
+ background-position: center;
+ min-width: 78px;
+ min-height: 78px;
+ `,
+ })
+ }
+
+ let icon = icons.fallback.notification
+ if (Utils.lookUpIcon(app_icon))
+ icon = app_icon
+
+ if (Utils.lookUpIcon(app_entry || ""))
+ icon = app_entry || ""
+
+ return Widget.Box({
+ vpack: "start",
+ hexpand: false,
+ class_name: "icon",
+ css: `
+ min-width: 78px;
+ min-height: 78px;
+ `,
+ child: Widget.Icon({
+ icon,
+ size: 58,
+ hpack: "center", hexpand: true,
+ vpack: "center", vexpand: true,
+ }),
+ })
+}
+
+export default (notification: Notification) => {
+ const content = Widget.Box({
+ class_name: "content",
+ children: [
+ NotificationIcon(notification),
+ Widget.Box({
+ hexpand: true,
+ vertical: true,
+ children: [
+ Widget.Box({
+ children: [
+ Widget.Label({
+ class_name: "title",
+ xalign: 0,
+ justification: "left",
+ hexpand: true,
+ max_width_chars: 24,
+ truncate: "end",
+ wrap: true,
+ label: notification.summary.trim(),
+ use_markup: true,
+ }),
+ Widget.Label({
+ class_name: "time",
+ vpack: "start",
+ label: time(notification.time),
+ }),
+ Widget.Button({
+ class_name: "close-button",
+ vpack: "start",
+ child: Widget.Icon("window-close-symbolic"),
+ on_clicked: notification.close,
+ }),
+ ],
+ }),
+ Widget.Label({
+ class_name: "description",
+ hexpand: true,
+ use_markup: true,
+ xalign: 0,
+ justification: "left",
+ label: notification.body.trim(),
+ max_width_chars: 24,
+ wrap: true,
+ }),
+ ],
+ }),
+ ],
+ })
+
+ const actionsbox = notification.actions.length > 0 ? Widget.Revealer({
+ transition: "slide_down",
+ child: Widget.EventBox({
+ child: Widget.Box({
+ class_name: "actions horizontal",
+ children: notification.actions.map(action => Widget.Button({
+ class_name: "action-button",
+ on_clicked: () => notification.invoke(action.id),
+ hexpand: true,
+ child: Widget.Label(action.label),
+ })),
+ }),
+ }),
+ }) : null
+
+ const eventbox = Widget.EventBox({
+ vexpand: false,
+ on_primary_click: notification.dismiss,
+ on_hover() {
+ if (actionsbox)
+ actionsbox.reveal_child = true
+ },
+ on_hover_lost() {
+ if (actionsbox)
+ actionsbox.reveal_child = true
+
+ notification.dismiss()
+ },
+ child: Widget.Box({
+ vertical: true,
+ children: actionsbox ? [content, actionsbox] : [content],
+ }),
+ })
+
+ return Widget.Box({
+ class_name: `notification ${notification.urgency}`,
+ child: eventbox,
+ })
+}
diff --git a/linux/home/.config/ags/widget/notifications/NotificationPopups.ts b/linux/home/.config/ags/widget/notifications/NotificationPopups.ts
new file mode 100644
index 0000000..a4a2b54
--- /dev/null
+++ b/linux/home/.config/ags/widget/notifications/NotificationPopups.ts
@@ -0,0 +1,90 @@
+import Notification from "./Notification"
+import options from "options"
+
+const notifications = await Service.import("notifications")
+const { transition } = options
+const { position } = options.notifications
+const { timeout, idle } = Utils
+
+function Animated(id: number) {
+ const n = notifications.getNotification(id)!
+ const widget = Notification(n)
+
+ const inner = Widget.Revealer({
+ transition: "slide_left",
+ transition_duration: transition.value,
+ child: widget,
+ })
+
+ const outer = Widget.Revealer({
+ transition: "slide_down",
+ transition_duration: transition.value,
+ child: inner,
+ })
+
+ const box = Widget.Box({
+ hpack: "end",
+ child: outer,
+ })
+
+ idle(() => {
+ outer.reveal_child = true
+ timeout(transition.value, () => {
+ inner.reveal_child = true
+ })
+ })
+
+ return Object.assign(box, {
+ dismiss() {
+ inner.reveal_child = false
+ timeout(transition.value, () => {
+ outer.reveal_child = false
+ timeout(transition.value, () => {
+ box.destroy()
+ })
+ })
+ },
+ })
+}
+
+function PopupList() {
+ const map: Map<number, ReturnType<typeof Animated>> = new Map
+ const box = Widget.Box({
+ hpack: "end",
+ vertical: true,
+ css: options.notifications.width.bind().as(w => `min-width: ${w}px;`),
+ })
+
+ function remove(_: unknown, id: number) {
+ map.get(id)?.dismiss()
+ map.delete(id)
+ }
+
+ return box
+ .hook(notifications, (_, id: number) => {
+ if (id !== undefined) {
+ if (map.has(id))
+ remove(null, id)
+
+ if (notifications.dnd)
+ return
+
+ const w = Animated(id)
+ map.set(id, w)
+ box.children = [w, ...box.children]
+ }
+ }, "notified")
+ .hook(notifications, remove, "dismissed")
+ .hook(notifications, remove, "closed")
+}
+
+export default (monitor: number) => Widget.Window({
+ monitor,
+ name: `notifications${monitor}`,
+ anchor: position.bind(),
+ class_name: "notifications",
+ child: Widget.Box({
+ css: "padding: 2px;",
+ child: PopupList(),
+ }),
+})
diff --git a/linux/home/.config/ags/widget/notifications/notifications.scss b/linux/home/.config/ags/widget/notifications/notifications.scss
new file mode 100644
index 0000000..369932f
--- /dev/null
+++ b/linux/home/.config/ags/widget/notifications/notifications.scss
@@ -0,0 +1,79 @@
+@mixin notification() {
+ &.critical {
+ box-shadow: inset 0 0 0.5em 0 $error-bg;
+ }
+
+ &:hover button.close-button {
+ @include button-hover;
+ background-color: transparentize($error-bg, 0.5);
+ }
+
+ .content {
+ .title {
+ margin-right: $spacing;
+ color: $fg;
+ font-size: 1.1em;
+ }
+
+ .time {
+ color: transparentize($fg, 0.2);
+ }
+
+ .description {
+ font-size: 0.9em;
+ color: transparentize($fg, 0.2);
+ }
+
+ .icon {
+ border-radius: $radius * 0.8;
+ margin-right: $spacing;
+
+ &.img {
+ border: $border;
+ }
+ }
+ }
+
+ box.actions {
+ @include spacing(0.5);
+ margin-top: $spacing;
+
+ button {
+ @include button;
+ border-radius: $radius * 0.8;
+ font-size: 1.2em;
+ padding: $padding * 0.7;
+ }
+ }
+
+ button.close-button {
+ @include button($flat: true);
+ margin-left: $spacing / 2;
+ border-radius: $radius * 0.8;
+ min-width: 1.2em;
+ min-height: 1.2em;
+
+ &:hover {
+ background-color: transparentize($error-bg, 0.2);
+ }
+
+ &:active {
+ background-image: none;
+ background-color: $error-bg;
+ }
+ }
+}
+
+window.notifications {
+ @include unset;
+
+ .notification {
+ @include notification;
+ @include floating-widget;
+ border-radius: $radius;
+
+ .description {
+ min-width: 200px;
+ }
+ }
+}
diff --git a/linux/home/.config/ags/widget/osd/OSD.ts b/linux/home/.config/ags/widget/osd/OSD.ts
new file mode 100644
index 0000000..8239a08
--- /dev/null
+++ b/linux/home/.config/ags/widget/osd/OSD.ts
@@ -0,0 +1,111 @@
+import { icon } from "lib/utils"
+import icons from "lib/icons"
+import Progress from "./Progress"
+import brightness from "service/brightness"
+import options from "options"
+
+const audio = await Service.import("audio")
+const { progress, microphone } = options.osd
+
+const DELAY = 2500
+
+function OnScreenProgress(vertical: boolean) {
+ const indicator = Widget.Icon({
+ size: 42,
+ vpack: "start",
+ })
+ const progress = Progress({
+ vertical,
+ width: vertical ? 42 : 300,
+ height: vertical ? 300 : 42,
+ child: indicator,
+ })
+
+ const revealer = Widget.Revealer({
+ transition: "slide_left",
+ child: progress,
+ })
+
+ let count = 0
+ function show(value: number, icon: string) {
+ revealer.reveal_child = true
+ indicator.icon = icon
+ progress.setValue(value)
+ count++
+ Utils.timeout(DELAY, () => {
+ count--
+
+ if (count === 0)
+ revealer.reveal_child = false
+ })
+ }
+
+ return revealer
+ .hook(brightness, () => show(
+ brightness.screen,
+ icons.brightness.screen,
+ ), "notify::screen")
+ .hook(brightness, () => show(
+ brightness.kbd,
+ icons.brightness.keyboard,
+ ), "notify::kbd")
+ .hook(audio.speaker, () => show(
+ audio.speaker.volume,
+ icon(audio.speaker.icon_name || "", icons.audio.type.speaker),
+ ), "notify::volume")
+}
+
+function MicrophoneMute() {
+ const icon = Widget.Icon({
+ class_name: "microphone",
+ })
+
+ const revealer = Widget.Revealer({
+ transition: "slide_up",
+ child: icon,
+ })
+
+ let count = 0
+ let mute = audio.microphone.stream?.is_muted ?? false
+
+ return revealer.hook(audio.microphone, () => Utils.idle(() => {
+ if (mute !== audio.microphone.stream?.is_muted) {
+ mute = audio.microphone.stream!.is_muted
+ icon.icon = icons.audio.mic[mute ? "muted" : "high"]
+ revealer.reveal_child = true
+ count++
+
+ Utils.timeout(DELAY, () => {
+ count--
+ if (count === 0)
+ revealer.reveal_child = false
+ })
+ }
+ }))
+}
+
+export default (monitor: number) => Widget.Window({
+ monitor,
+ name: `indicator${monitor}`,
+ class_name: "indicator",
+ layer: "overlay",
+ click_through: true,
+ anchor: ["right", "left", "top", "bottom"],
+ child: Widget.Box({
+ css: "padding: 2px;",
+ expand: true,
+ child: Widget.Overlay(
+ { child: Widget.Box({ expand: true }) },
+ Widget.Box({
+ hpack: progress.pack.h.bind(),
+ vpack: progress.pack.v.bind(),
+ child: progress.vertical.bind().as(OnScreenProgress),
+ }),
+ Widget.Box({
+ hpack: microphone.pack.h.bind(),
+ vpack: microphone.pack.v.bind(),
+ child: MicrophoneMute(),
+ }),
+ ),
+ }),
+})
diff --git a/linux/home/.config/ags/widget/osd/Progress.ts b/linux/home/.config/ags/widget/osd/Progress.ts
new file mode 100644
index 0000000..bcf27da
--- /dev/null
+++ b/linux/home/.config/ags/widget/osd/Progress.ts
@@ -0,0 +1,74 @@
+import type Gtk from "gi://Gtk?version=3.0"
+import GLib from "gi://GLib?version=2.0"
+import { range } from "lib/utils"
+import options from "options"
+
+type ProgressProps = {
+ height?: number
+ width?: number
+ vertical?: boolean
+ child: Gtk.Widget
+}
+
+export default ({
+ height = 18,
+ width = 180,
+ vertical = false,
+ child,
+}: ProgressProps) => {
+ const fill = Widget.Box({
+ class_name: "fill",
+ hexpand: vertical,
+ vexpand: !vertical,
+ hpack: vertical ? "fill" : "start",
+ vpack: vertical ? "end" : "fill",
+ child,
+ })
+
+ const container = Widget.Box({
+ class_name: "progress",
+ child: fill,
+ css: `
+ min-width: ${width}px;
+ min-height: ${height}px;
+ `,
+ })
+
+ let fill_size = 0
+ let animations: number[] = []
+
+ return Object.assign(container, {
+ setValue(value: number) {
+ if (value < 0)
+ return
+
+ if (animations.length > 0) {
+ for (const id of animations)
+ GLib.source_remove(id)
+
+ animations = []
+ }
+
+ const axis = vertical ? "height" : "width"
+ const axisv = vertical ? height : width
+ const min = vertical ? width : height
+ const preferred = (axisv - min) * value + min
+
+ if (!fill_size) {
+ fill_size = preferred
+ fill.css = `min-${axis}: ${preferred}px;`
+ return
+ }
+
+ const frames = options.transition.value / 10
+ const goal = preferred - fill_size
+ const step = goal / frames
+
+ animations = range(frames, 0).map(i => Utils.timeout(5 * i, () => {
+ fill_size += step
+ fill.css = `min-${axis}: ${fill_size}px`
+ animations.shift()
+ }))
+ },
+ })
+}
diff --git a/linux/home/.config/ags/widget/osd/osd.scss b/linux/home/.config/ags/widget/osd/osd.scss
new file mode 100644
index 0000000..33df6d2
--- /dev/null
+++ b/linux/home/.config/ags/widget/osd/osd.scss
@@ -0,0 +1,26 @@
+window.indicator {
+ .progress {
+ @include floating-widget;
+ padding: $padding * 0.5;
+ border-radius: if($radius >0, calc($radius + $padding * 0.5), 0);
+ @debug $radius;
+
+ .fill {
+ border-radius: $radius;
+ background-color: $primary-bg;
+ color: $primary-fg;
+
+ image {
+ -gtk-icon-transform: scale(0.7);
+ }
+ }
+ }
+
+ .microphone {
+ @include floating-widget;
+ margin: $spacing * 2;
+ padding: $popover-padding * 2;
+ font-size: 58px;
+ color: transparentize($fg, 0.1);
+ }
+}
diff --git a/linux/home/.config/ags/widget/overview/Overview.ts b/linux/home/.config/ags/widget/overview/Overview.ts
new file mode 100644
index 0000000..8911920
--- /dev/null
+++ b/linux/home/.config/ags/widget/overview/Overview.ts
@@ -0,0 +1,41 @@
+import PopupWindow from "widget/PopupWindow"
+import Workspace from "./Workspace"
+import options from "options"
+import { range } from "lib/utils"
+
+const hyprland = await Service.import("hyprland")
+
+const Overview = (ws: number) => Widget.Box({
+ class_name: "overview horizontal",
+ children: ws > 0
+ ? range(ws).map(Workspace)
+ : hyprland.workspaces
+ .map(({ id }) => Workspace(id))
+ .sort((a, b) => a.attribute.id - b.attribute.id),
+
+ setup: w => {
+ if (ws > 0)
+ return
+
+ w.hook(hyprland, (w, id?: string) => {
+ if (id === undefined)
+ return
+
+ w.children = w.children
+ .filter(ch => ch.attribute.id !== Number(id))
+ }, "workspace-removed")
+ w.hook(hyprland, (w, id?: string) => {
+ if (id === undefined)
+ return
+
+ w.children = [...w.children, Workspace(Number(id))]
+ .sort((a, b) => a.attribute.id - b.attribute.id)
+ }, "workspace-added")
+ },
+})
+
+export default () => PopupWindow({
+ name: "overview",
+ layout: "center",
+ child: options.overview.workspaces.bind().as(Overview),
+})
diff --git a/linux/home/.config/ags/widget/overview/Window.ts b/linux/home/.config/ags/widget/overview/Window.ts
new file mode 100644
index 0000000..02f71eb
--- /dev/null
+++ b/linux/home/.config/ags/widget/overview/Window.ts
@@ -0,0 +1,48 @@
+import { type Client } from "types/service/hyprland"
+import { createSurfaceFromWidget, icon } from "lib/utils"
+import Gdk from "gi://Gdk"
+import Gtk from "gi://Gtk?version=3.0"
+import options from "options"
+import icons from "lib/icons"
+
+const monochrome = options.overview.monochromeIcon
+const TARGET = [Gtk.TargetEntry.new("text/plain", Gtk.TargetFlags.SAME_APP, 0)]
+const hyprland = await Service.import("hyprland")
+const apps = await Service.import("applications")
+const dispatch = (args: string) => hyprland.messageAsync(`dispatch ${args}`)
+
+export default ({ address, size: [w, h], class: c, title }: Client) => Widget.Button({
+ class_name: "client",
+ attribute: { address },
+ tooltip_text: `${title}`,
+ child: Widget.Icon({
+ css: options.overview.scale.bind().as(v => `
+ min-width: ${(v / 100) * w}px;
+ min-height: ${(v / 100) * h}px;
+ `),
+ icon: monochrome.bind().as(m => {
+ const app = apps.list.find(app => app.match(c))
+ if (!app)
+ return icons.fallback.executable + (m ? "-symbolic" : "")
+
+
+ return icon(
+ app.icon_name + (m ? "-symbolic" : ""),
+ icons.fallback.executable + (m ? "-symbolic" : ""),
+ )
+ }),
+ }),
+ on_secondary_click: () => dispatch(`closewindow address:${address}`),
+ on_clicked: () => {
+ dispatch(`focuswindow address:${address}`)
+ App.closeWindow("overview")
+ },
+ setup: btn => btn
+ .on("drag-data-get", (_w, _c, data) => data.set_text(address, address.length))
+ .on("drag-begin", (_, context) => {
+ Gtk.drag_set_icon_surface(context, createSurfaceFromWidget(btn))
+ btn.toggleClassName("hidden", true)
+ })
+ .on("drag-end", () => btn.toggleClassName("hidden", false))
+ .drag_source_set(Gdk.ModifierType.BUTTON1_MASK, TARGET, Gdk.DragAction.COPY),
+})
diff --git a/linux/home/.config/ags/widget/overview/Workspace.ts b/linux/home/.config/ags/widget/overview/Workspace.ts
new file mode 100644
index 0000000..1b8d60b
--- /dev/null
+++ b/linux/home/.config/ags/widget/overview/Workspace.ts
@@ -0,0 +1,76 @@
+import Window from "./Window"
+import Gdk from "gi://Gdk"
+import Gtk from "gi://Gtk?version=3.0"
+import options from "options"
+
+const TARGET = [Gtk.TargetEntry.new("text/plain", Gtk.TargetFlags.SAME_APP, 0)]
+const scale = (size: number) => (options.overview.scale.value / 100) * size
+const hyprland = await Service.import("hyprland")
+
+const dispatch = (args: string) => hyprland.messageAsync(`dispatch ${args}`)
+
+const size = (id: number) => {
+ const def = { h: 1080, w: 1920 }
+ const ws = hyprland.getWorkspace(id)
+ if (!ws)
+ return def
+
+ const mon = hyprland.getMonitor(ws.monitorID)
+ return mon ? { h: mon.height, w: mon.width } : def
+}
+
+export default (id: number) => {
+ const fixed = Widget.Fixed()
+
+ // TODO: early return if position is unchaged
+ async function update() {
+ const json = await hyprland.messageAsync("j/clients").catch(() => null)
+ if (!json)
+ return
+
+ fixed.get_children().forEach(ch => ch.destroy())
+ const clients = JSON.parse(json) as typeof hyprland.clients
+ clients
+ .filter(({ workspace }) => workspace.id === id)
+ .forEach(c => {
+ const x = c.at[0] - (hyprland.getMonitor(c.monitor)?.x || 0)
+ const y = c.at[1] - (hyprland.getMonitor(c.monitor)?.y || 0)
+ c.mapped && fixed.put(Window(c), scale(x), scale(y))
+ })
+ fixed.show_all()
+ }
+
+ return Widget.Box({
+ attribute: { id },
+ tooltipText: `${id}`,
+ class_name: "workspace",
+ vpack: "center",
+ css: options.overview.scale.bind().as(v => `
+ min-width: ${(v / 100) * size(id).w}px;
+ min-height: ${(v / 100) * size(id).h}px;
+ `),
+ setup(box) {
+ box.hook(options.overview.scale, update)
+ box.hook(hyprland, update, "notify::clients")
+ box.hook(hyprland.active.client, update)
+ box.hook(hyprland.active.workspace, () => {
+ box.toggleClassName("active", hyprland.active.workspace.id === id)
+ })
+ },
+ child: Widget.EventBox({
+ expand: true,
+ on_primary_click: () => {
+ App.closeWindow("overview")
+ dispatch(`workspace ${id}`)
+ },
+ setup: eventbox => {
+ eventbox.drag_dest_set(Gtk.DestDefaults.ALL, TARGET, Gdk.DragAction.COPY)
+ eventbox.connect("drag-data-received", (_w, _c, _x, _y, data) => {
+ const address = new TextDecoder().decode(data.get_data())
+ dispatch(`movetoworkspacesilent ${id},address:${address}`)
+ })
+ },
+ child: fixed,
+ }),
+ })
+}
diff --git a/linux/home/.config/ags/widget/overview/overview.scss b/linux/home/.config/ags/widget/overview/overview.scss
new file mode 100644
index 0000000..4665b52
--- /dev/null
+++ b/linux/home/.config/ags/widget/overview/overview.scss
@@ -0,0 +1,34 @@
+window#overview .overview {
+ @include floating-widget;
+ @include spacing;
+
+ .workspace {
+ &.active>widget {
+ border-color: $primary-bg;
+ }
+
+ >widget {
+ @include widget;
+ border-radius: if($radius ==0, 0, $radius + $padding);
+
+ &:hover {
+ background-color: $hover-bg;
+ }
+
+ &:drop(active) {
+ border-color: $primary-bg;
+ }
+ }
+ }
+
+ .client {
+ @include button;
+ border-radius: $radius;
+ margin: $padding;
+
+ &.hidden {
+ @include hidden;
+ transition: 0;
+ }
+ }
+}
diff --git a/linux/home/.config/ags/widget/powermenu/PowerMenu.ts b/linux/home/.config/ags/widget/powermenu/PowerMenu.ts
new file mode 100644
index 0000000..fe0a0e9
--- /dev/null
+++ b/linux/home/.config/ags/widget/powermenu/PowerMenu.ts
@@ -0,0 +1,56 @@
+import PopupWindow from "widget/PopupWindow"
+import powermenu, { type Action } from "service/powermenu"
+import icons from "lib/icons"
+import options from "options"
+import type Gtk from "gi://Gtk?version=3.0"
+
+const { layout, labels } = options.powermenu
+
+const SysButton = (action: Action, label: string) => Widget.Button({
+ on_clicked: () => powermenu.action(action),
+ child: Widget.Box({
+ vertical: true,
+ class_name: "system-button",
+ children: [
+ Widget.Icon(icons.powermenu[action]),
+ Widget.Label({
+ label,
+ visible: labels.bind(),
+ }),
+ ],
+ }),
+})
+
+export default () => PopupWindow({
+ name: "powermenu",
+ transition: "crossfade",
+ child: Widget.Box<Gtk.Widget>({
+ class_name: "powermenu horizontal",
+ setup: self => self.hook(layout, () => {
+ self.toggleClassName("box", layout.value === "box")
+ self.toggleClassName("line", layout.value === "line")
+ }),
+ children: layout.bind().as(layout => {
+ switch (layout) {
+ case "line": return [
+ SysButton("shutdown", "Shutdown"),
+ SysButton("logout", "Log Out"),
+ SysButton("reboot", "Reboot"),
+ SysButton("sleep", "Sleep"),
+ ]
+ case "box": return [
+ Widget.Box(
+ { vertical: true },
+ SysButton("shutdown", "Shutdown"),
+ SysButton("logout", "Log Out"),
+ ),
+ Widget.Box(
+ { vertical: true },
+ SysButton("reboot", "Reboot"),
+ SysButton("sleep", "Sleep"),
+ ),
+ ]
+ }
+ }),
+ }),
+})
diff --git a/linux/home/.config/ags/widget/powermenu/Verification.ts b/linux/home/.config/ags/widget/powermenu/Verification.ts
new file mode 100644
index 0000000..e85c81a
--- /dev/null
+++ b/linux/home/.config/ags/widget/powermenu/Verification.ts
@@ -0,0 +1,47 @@
+import PopupWindow from "widget/PopupWindow"
+import powermenu from "service/powermenu"
+
+export default () => PopupWindow({
+ name: "verification",
+ transition: "crossfade",
+ child: Widget.Box({
+ class_name: "verification",
+ vertical: true,
+ children: [
+ Widget.Box({
+ class_name: "text-box",
+ vertical: true,
+ children: [
+ Widget.Label({
+ class_name: "title",
+ label: powermenu.bind("title"),
+ }),
+ Widget.Label({
+ class_name: "desc",
+ label: "Are you sure?",
+ }),
+ ],
+ }),
+ Widget.Box({
+ class_name: "buttons horizontal",
+ vexpand: true,
+ vpack: "end",
+ homogeneous: true,
+ children: [
+ Widget.Button({
+ child: Widget.Label("No"),
+ on_clicked: () => App.toggleWindow("verification"),
+ setup: self => self.hook(App, (_, name: string, visible: boolean) => {
+ if (name === "verification" && visible)
+ self.grab_focus()
+ }),
+ }),
+ Widget.Button({
+ child: Widget.Label("Yes"),
+ on_clicked: () => Utils.exec(powermenu.cmd),
+ }),
+ ],
+ }),
+ ],
+ }),
+})
diff --git a/linux/home/.config/ags/widget/powermenu/powermenu.scss b/linux/home/.config/ags/widget/powermenu/powermenu.scss
new file mode 100644
index 0000000..d5ce0de
--- /dev/null
+++ b/linux/home/.config/ags/widget/powermenu/powermenu.scss
@@ -0,0 +1,110 @@
+window#powermenu,
+window#verification {
+ // the fraction has to be more than hyprland ignorealpha
+ background-color: rgba(0, 0, 0, .4);
+}
+
+window#verification .verification {
+ @include floating-widget;
+ padding: $popover-padding * 1.5;
+ min-width: 300px;
+ min-height: 100px;
+
+ .text-box {
+ margin-bottom: $spacing;
+
+ .title {
+ font-size: 1.6em;
+ }
+
+ .desc {
+ color: transparentize($fg, 0.1);
+ font-size: 1.1em;
+ }
+ }
+
+ .buttons {
+ @include spacing;
+ margin-top: $padding;
+
+ button {
+ @include button;
+ font-size: 1.5em;
+ padding: $padding;
+ }
+ }
+}
+
+window#powermenu .powermenu {
+ @include floating-widget;
+
+ &.line {
+ padding: $popover-padding * 1.5;
+
+ button {
+ padding: $popover-padding;
+ }
+
+ label {
+ margin-bottom: $spacing * -.5;
+ }
+ }
+
+ &.box {
+ padding: $popover-padding * 2;
+
+ button {
+ padding: $popover-padding * 1.5;
+ }
+
+ label {
+ margin-bottom: $spacing * -1;
+ }
+ }
+
+ button {
+ @include unset;
+
+ image {
+ @include button;
+ border-radius: $radius + ($popover-padding * 1.4);
+ min-width: 1.7em;
+ min-height: 1.7em;
+ font-size: 4em;
+ }
+
+ label,
+ image {
+ color: transparentize($fg, 0.1);
+ }
+
+ label {
+ margin-top: $spacing * .3;
+ }
+
+ &:hover {
+ image {
+ @include button-hover;
+ }
+
+ label {
+ color: $fg;
+ }
+ }
+
+ &:focus image {
+ @include button-focus;
+ }
+
+ &:active image {
+ @include button-active;
+ }
+
+ &:focus,
+ &:active {
+ label {
+ color: $primary-bg;
+ }
+ }
+ }
+}
diff --git a/linux/home/.config/ags/widget/quicksettings/QuickSettings.ts b/linux/home/.config/ags/widget/quicksettings/QuickSettings.ts
new file mode 100644
index 0000000..2c0d6ac
--- /dev/null
+++ b/linux/home/.config/ags/widget/quicksettings/QuickSettings.ts
@@ -0,0 +1,84 @@
+import type Gtk from "gi://Gtk?version=3.0"
+import { ProfileSelector, ProfileToggle } from "./widgets/PowerProfile"
+import { Header } from "./widgets/Header"
+import { Volume, Microhone, SinkSelector, AppMixer } from "./widgets/Volume"
+import { Brightness } from "./widgets/Brightness"
+import { NetworkToggle, WifiSelection } from "./widgets/Network"
+import { BluetoothToggle, BluetoothDevices } from "./widgets/Bluetooth"
+import { DND } from "./widgets/DND"
+import { DarkModeToggle } from "./widgets/DarkMode"
+import { MicMute } from "./widgets/MicMute"
+import { Media } from "./widgets/Media"
+import PopupWindow from "widget/PopupWindow"
+import options from "options"
+
+const { bar, quicksettings } = options
+const media = (await Service.import("mpris")).bind("players")
+const layout = Utils.derive([bar.position, quicksettings.position], (bar, qs) =>
+ `${bar}-${qs}` as const,
+)
+
+const Row = (
+ toggles: Array<() => Gtk.Widget> = [],
+ menus: Array<() => Gtk.Widget> = [],
+) => Widget.Box({
+ vertical: true,
+ children: [
+ Widget.Box({
+ homogeneous: true,
+ class_name: "row horizontal",
+ children: toggles.map(w => w()),
+ }),
+ ...menus.map(w => w()),
+ ],
+})
+
+const Settings = () => Widget.Box({
+ vertical: true,
+ class_name: "quicksettings vertical",
+ css: quicksettings.width.bind().as(w => `min-width: ${w}px;`),
+ children: [
+ Header(),
+ Widget.Box({
+ class_name: "sliders-box vertical",
+ vertical: true,
+ children: [
+ Row(
+ [Volume],
+ [SinkSelector, AppMixer],
+ ),
+ Microhone(),
+ Brightness(),
+ ],
+ }),
+ Row(
+ [NetworkToggle, BluetoothToggle],
+ [WifiSelection, BluetoothDevices],
+ ),
+ Row(
+ [ProfileToggle, DarkModeToggle],
+ [ProfileSelector],
+ ),
+ Row([MicMute, DND]),
+ Widget.Box({
+ visible: media.as(l => l.length > 0),
+ child: Media(),
+ }),
+ ],
+})
+
+const QuickSettings = () => PopupWindow({
+ name: "quicksettings",
+ exclusivity: "exclusive",
+ transition: bar.position.bind().as(pos => pos === "top" ? "slide_down" : "slide_up"),
+ layout: layout.value,
+ child: Settings(),
+})
+
+export function setupQuickSettings() {
+ App.addWindow(QuickSettings())
+ layout.connect("changed", () => {
+ App.removeWindow("quicksettings")
+ App.addWindow(QuickSettings())
+ })
+}
diff --git a/linux/home/.config/ags/widget/quicksettings/ToggleButton.ts b/linux/home/.config/ags/widget/quicksettings/ToggleButton.ts
new file mode 100644
index 0000000..62a2e67
--- /dev/null
+++ b/linux/home/.config/ags/widget/quicksettings/ToggleButton.ts
@@ -0,0 +1,154 @@
+import { type Props as IconProps } from "types/widgets/icon"
+import { type Props as LabelProps } from "types/widgets/label"
+import type GObject from "gi://GObject?version=2.0"
+import type Gtk from "gi://Gtk?version=3.0"
+import icons from "lib/icons"
+
+export const opened = Variable("")
+App.connect("window-toggled", (_, name: string, visible: boolean) => {
+ if (name === "quicksettings" && !visible)
+ Utils.timeout(500, () => opened.value = "")
+})
+
+export const Arrow = (name: string, activate?: false | (() => void)) => {
+ let deg = 0
+ let iconOpened = false
+ const icon = Widget.Icon(icons.ui.arrow.right).hook(opened, () => {
+ if (opened.value === name && !iconOpened || opened.value !== name && iconOpened) {
+ const step = opened.value === name ? 10 : -10
+ iconOpened = !iconOpened
+ for (let i = 0; i < 9; ++i) {
+ Utils.timeout(15 * i, () => {
+ deg += step
+ icon.setCss(`-gtk-icon-transform: rotate(${deg}deg);`)
+ })
+ }
+ }
+ })
+ return Widget.Button({
+ child: icon,
+ class_name: "arrow",
+ on_clicked: () => {
+ opened.value = opened.value === name ? "" : name
+ if (typeof activate === "function")
+ activate()
+ },
+ })
+}
+
+type ArrowToggleButtonProps = {
+ name: string
+ icon: IconProps["icon"]
+ label: LabelProps["label"]
+ activate: () => void
+ deactivate: () => void
+ activateOnArrow?: boolean
+ connection: [GObject.Object, () => boolean]
+}
+export const ArrowToggleButton = ({
+ name,
+ icon,
+ label,
+ activate,
+ deactivate,
+ activateOnArrow = true,
+ connection: [service, condition],
+}: ArrowToggleButtonProps) => Widget.Box({
+ class_name: "toggle-button",
+ setup: self => self.hook(service, () => {
+ self.toggleClassName("active", condition())
+ }),
+ children: [
+ Widget.Button({
+ child: Widget.Box({
+ hexpand: true,
+ children: [
+ Widget.Icon({
+ class_name: "icon",
+ icon,
+ }),
+ Widget.Label({
+ class_name: "label",
+ max_width_chars: 10,
+ truncate: "end",
+ label,
+ }),
+ ],
+ }),
+ on_clicked: () => {
+ if (condition()) {
+ deactivate()
+ if (opened.value === name)
+ opened.value = ""
+ } else {
+ activate()
+ }
+ },
+ }),
+ Arrow(name, activateOnArrow && activate),
+ ],
+})
+
+type MenuProps = {
+ name: string
+ icon: IconProps["icon"]
+ title: LabelProps["label"]
+ content: Gtk.Widget[]
+}
+export const Menu = ({ name, icon, title, content }: MenuProps) => Widget.Revealer({
+ transition: "slide_down",
+ reveal_child: opened.bind().as(v => v === name),
+ child: Widget.Box({
+ class_names: ["menu", name],
+ vertical: true,
+ children: [
+ Widget.Box({
+ class_name: "title-box",
+ children: [
+ Widget.Icon({
+ class_name: "icon",
+ icon,
+ }),
+ Widget.Label({
+ class_name: "title",
+ truncate: "end",
+ label: title,
+ }),
+ ],
+ }),
+ Widget.Separator(),
+ Widget.Box({
+ vertical: true,
+ class_name: "content vertical",
+ children: content,
+ }),
+ ],
+ }),
+})
+
+type SimpleToggleButtonProps = {
+ icon: IconProps["icon"]
+ label: LabelProps["label"]
+ toggle: () => void
+ connection: [GObject.Object, () => boolean]
+}
+export const SimpleToggleButton = ({
+ icon,
+ label,
+ toggle,
+ connection: [service, condition],
+}: SimpleToggleButtonProps) => Widget.Button({
+ on_clicked: toggle,
+ class_name: "simple-toggle",
+ setup: self => self.hook(service, () => {
+ self.toggleClassName("active", condition())
+ }),
+ child: Widget.Box([
+ Widget.Icon({ icon }),
+ Widget.Label({
+ max_width_chars: 10,
+ truncate: "end",
+ label,
+ }),
+ ]),
+})
diff --git a/linux/home/.config/ags/widget/quicksettings/quicksettings.scss b/linux/home/.config/ags/widget/quicksettings/quicksettings.scss
new file mode 100644
index 0000000..bd18ff1
--- /dev/null
+++ b/linux/home/.config/ags/widget/quicksettings/quicksettings.scss
@@ -0,0 +1,177 @@
+window#quicksettings .quicksettings {
+ @include floating-widget;
+ @include spacing;
+
+ padding: $popover-padding * 1.4;
+
+ .avatar {
+ @include widget;
+ border-radius: $radius * 3;
+ }
+
+ .header {
+ @include spacing(.5);
+ color: transparentize($fg, .15);
+
+ button {
+ @include button;
+ padding: $padding;
+
+ image {
+ font-size: 1.4em;
+ }
+ }
+ }
+
+ .sliders-box {
+ @include widget;
+ padding: $padding;
+
+ button {
+ @include button($flat: true);
+ padding: $padding * .5;
+ }
+
+ .volume button.arrow:last-child {
+ margin-left: $spacing * .4;
+ }
+
+ .volume,
+ .brightness {
+ padding: $padding * .5;
+ }
+
+ scale {
+ @include slider;
+ margin: 0 ($spacing * .5);
+
+ &.muted highlight {
+ background-image: none;
+ background-color: transparentize($fg, $amount: .2);
+ }
+ }
+ }
+
+ .row {
+ @include spacing;
+ }
+
+ .menu {
+ @include unset;
+ @include widget;
+ padding: $padding;
+ margin-top: $spacing;
+
+ .icon {
+ margin: 0 ($spacing * .5);
+ margin-left: $spacing * .2;
+ }
+
+ .title {
+ font-weight: bold;
+ }
+
+ separator {
+ margin: ($radius * .5);
+ background-color: $border-color;
+ }
+
+ button {
+ @include button($flat: true);
+ padding: ($padding * .5);
+
+ image:first-child {
+ margin-right: $spacing * .5;
+ }
+ }
+
+ .bluetooth-devices {
+ @include spacing(.5);
+ }
+
+ switch {
+ @include switch;
+ }
+ }
+
+ .sliders-box .menu {
+ margin: ($spacing * .5) 0;
+
+ &.app-mixer {
+ .mixer-item {
+ padding: $padding * .5;
+ padding-left: 0;
+ padding-right: $padding * 2;
+
+ scale {
+ @include slider($width: .5em);
+ }
+
+ image {
+ font-size: 1.2em;
+ margin: 0 $padding;
+ }
+ }
+ }
+ }
+
+ .toggle-button {
+ @include button;
+ font-weight: bold;
+
+ image {
+ font-size: 1.3em;
+ }
+
+ label {
+ margin-left: $spacing * .3;
+ }
+
+ button {
+ @include button($flat: true);
+
+ &:first-child {
+ padding: $padding * 1.2;
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+ }
+
+ &:last-child {
+ padding: $padding * .5;
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+ }
+ }
+
+ &.active {
+ background-color: $primary-bg;
+
+ label,
+ image {
+ color: $primary-fg;
+ }
+ }
+ }
+
+ .simple-toggle {
+ @include button;
+ font-weight: bold;
+ padding: $padding * 1.2;
+
+ label {
+ margin-left: $spacing * .3;
+ }
+
+ image {
+ font-size: 1.3em;
+ }
+ }
+
+ .media {
+ @include spacing;
+
+ .player {
+ @include media;
+ }
+ }
+}
diff --git a/linux/home/.config/ags/widget/quicksettings/widgets/Bluetooth.ts b/linux/home/.config/ags/widget/quicksettings/widgets/Bluetooth.ts
new file mode 100644
index 0000000..649e654
--- /dev/null
+++ b/linux/home/.config/ags/widget/quicksettings/widgets/Bluetooth.ts
@@ -0,0 +1,61 @@
+import { type BluetoothDevice } from "types/service/bluetooth"
+import { Menu, ArrowToggleButton } from "../ToggleButton"
+import icons from "lib/icons"
+
+const bluetooth = await Service.import("bluetooth")
+
+export const BluetoothToggle = () => ArrowToggleButton({
+ name: "bluetooth",
+ icon: bluetooth.bind("enabled").as(p => icons.bluetooth[p ? "enabled" : "disabled"]),
+ label: Utils.watch("Disabled", bluetooth, () => {
+ if (!bluetooth.enabled)
+ return "Disabled"
+
+ if (bluetooth.connected_devices.length === 1)
+ return bluetooth.connected_devices[0].alias
+
+ return `${bluetooth.connected_devices.length} Connected`
+ }),
+ connection: [bluetooth, () => bluetooth.enabled],
+ deactivate: () => bluetooth.enabled = false,
+ activate: () => bluetooth.enabled = true,
+})
+
+const DeviceItem = (device: BluetoothDevice) => Widget.Box({
+ children: [
+ Widget.Icon(device.icon_name + "-symbolic"),
+ Widget.Label(device.name),
+ Widget.Label({
+ label: `${device.battery_percentage}%`,
+ visible: device.bind("battery_percentage").as(p => p > 0),
+ }),
+ Widget.Box({ hexpand: true }),
+ Widget.Spinner({
+ active: device.bind("connecting"),
+ visible: device.bind("connecting"),
+ }),
+ Widget.Switch({
+ active: device.connected,
+ visible: device.bind("connecting").as(p => !p),
+ setup: self => self.on("notify::active", () => {
+ device.setConnection(self.active)
+ }),
+ }),
+ ],
+})
+
+export const BluetoothDevices = () => Menu({
+ name: "bluetooth",
+ icon: icons.bluetooth.disabled,
+ title: "Bluetooth",
+ content: [
+ Widget.Box({
+ class_name: "bluetooth-devices",
+ hexpand: true,
+ vertical: true,
+ children: bluetooth.bind("devices").as(ds => ds
+ .filter(d => d.name)
+ .map(DeviceItem)),
+ }),
+ ],
+})
diff --git a/linux/home/.config/ags/widget/quicksettings/widgets/Brightness.ts b/linux/home/.config/ags/widget/quicksettings/widgets/Brightness.ts
new file mode 100644
index 0000000..a3ce565
--- /dev/null
+++ b/linux/home/.config/ags/widget/quicksettings/widgets/Brightness.ts
@@ -0,0 +1,23 @@
+import icons from "lib/icons"
+import brightness from "service/brightness"
+
+const BrightnessSlider = () => Widget.Slider({
+ draw_value: false,
+ hexpand: true,
+ value: brightness.bind("screen"),
+ on_change: ({ value }) => brightness.screen = value,
+})
+
+export const Brightness = () => Widget.Box({
+ class_name: "brightness",
+ children: [
+ Widget.Button({
+ vpack: "center",
+ child: Widget.Icon(icons.brightness.indicator),
+ on_clicked: () => brightness.screen = 0,
+ tooltip_text: brightness.bind("screen").as(v =>
+ `Screen Brightness: ${Math.floor(v * 100)}%`),
+ }),
+ BrightnessSlider(),
+ ],
+})
diff --git a/linux/home/.config/ags/widget/quicksettings/widgets/DND.ts b/linux/home/.config/ags/widget/quicksettings/widgets/DND.ts
new file mode 100644
index 0000000..7fc1fd0
--- /dev/null
+++ b/linux/home/.config/ags/widget/quicksettings/widgets/DND.ts
@@ -0,0 +1,12 @@
+import { SimpleToggleButton } from "../ToggleButton"
+import icons from "lib/icons"
+
+const n = await Service.import("notifications")
+const dnd = n.bind("dnd")
+
+export const DND = () => SimpleToggleButton({
+ icon: dnd.as(dnd => icons.notifications[dnd ? "silent" : "noisy"]),
+ label: dnd.as(dnd => dnd ? "Silent" : "Noisy"),
+ toggle: () => n.dnd = !n.dnd,
+ connection: [n, () => n.dnd],
+})
diff --git a/linux/home/.config/ags/widget/quicksettings/widgets/DarkMode.ts b/linux/home/.config/ags/widget/quicksettings/widgets/DarkMode.ts
new file mode 100644
index 0000000..9ec94df
--- /dev/null
+++ b/linux/home/.config/ags/widget/quicksettings/widgets/DarkMode.ts
@@ -0,0 +1,12 @@
+import { SimpleToggleButton } from "../ToggleButton"
+import icons from "lib/icons"
+import options from "options"
+
+const { scheme } = options.theme
+
+export const DarkModeToggle = () => SimpleToggleButton({
+ icon: scheme.bind().as(s => icons.color[s]),
+ label: scheme.bind().as(s => s === "dark" ? "Dark" : "Light"),
+ toggle: () => scheme.value = scheme.value === "dark" ? "light" : "dark",
+ connection: [scheme, () => scheme.value === "dark"],
+})
diff --git a/linux/home/.config/ags/widget/quicksettings/widgets/Header.ts b/linux/home/.config/ags/widget/quicksettings/widgets/Header.ts
new file mode 100644
index 0000000..44c26f2
--- /dev/null
+++ b/linux/home/.config/ags/widget/quicksettings/widgets/Header.ts
@@ -0,0 +1,69 @@
+import icons from "lib/icons"
+import { uptime } from "lib/variables"
+import options from "options"
+import powermenu, { Action } from "service/powermenu"
+
+const battery = await Service.import("battery")
+const { image, size } = options.quicksettings.avatar
+
+function up(up: number) {
+ const h = Math.floor(up / 60)
+ const m = Math.floor(up % 60)
+ return `${h}h ${m < 10 ? "0" + m : m}m`
+}
+
+const Avatar = () => Widget.Box({
+ class_name: "avatar",
+ css: Utils.merge([image.bind(), size.bind()], (img, size) => `
+ min-width: ${size}px;
+ min-height: ${size}px;
+ background-image: url('${img}');
+ background-size: cover;
+ `),
+})
+
+const SysButton = (action: Action) => Widget.Button({
+ vpack: "center",
+ child: Widget.Icon(icons.powermenu[action]),
+ on_clicked: () => powermenu.action(action),
+})
+
+export const Header = () => Widget.Box(
+ { class_name: "header horizontal" },
+ Avatar(),
+ Widget.Box({
+ vertical: true,
+ vpack: "center",
+ children: [
+ Widget.Box({
+ visible: battery.bind("available"),
+ children: [
+ Widget.Icon({ icon: battery.bind("icon_name") }),
+ Widget.Label({ label: battery.bind("percent").as(p => `${p}%`) }),
+ ],
+ }),
+ Widget.Box([
+ Widget.Icon({ icon: icons.ui.time }),
+ Widget.Label({ label: uptime.bind().as(up) }),
+
+ //Widget.Label({ label: `${user.name}\n` }),
+ // //Widget.Label({ label: uptime.bind().value }),
+ // Widget.Label({ label: `${user.name}\n ${uptime.bind().value}` }),
+ // //Widget.Icon({ icon: icons.ui.time }),
+ ]),
+
+ ],
+ }),
+ Widget.Box({ hexpand: true }),
+ Widget.Button({
+ vpack: "center",
+ child: Widget.Icon(icons.ui.settings),
+ on_clicked: () => {
+ App.closeWindow("quicksettings")
+ App.closeWindow("settings-dialog")
+ App.openWindow("settings-dialog")
+ },
+ }),
+ SysButton("logout"),
+ SysButton("shutdown"),
+)
diff --git a/linux/home/.config/ags/widget/quicksettings/widgets/Media.ts b/linux/home/.config/ags/widget/quicksettings/widgets/Media.ts
new file mode 100644
index 0000000..52254ea
--- /dev/null
+++ b/linux/home/.config/ags/widget/quicksettings/widgets/Media.ts
@@ -0,0 +1,153 @@
+import { type MprisPlayer } from "types/service/mpris"
+import icons from "lib/icons"
+import options from "options"
+import { icon } from "lib/utils"
+
+const mpris = await Service.import("mpris")
+const players = mpris.bind("players")
+const { media } = options.quicksettings
+
+function lengthStr(length: number) {
+ const min = Math.floor(length / 60)
+ const sec = Math.floor(length % 60)
+ const sec0 = sec < 10 ? "0" : ""
+ return `${min}:${sec0}${sec}`
+}
+
+const Player = (player: MprisPlayer) => {
+ const cover = Widget.Box({
+ class_name: "cover",
+ vpack: "start",
+ css: Utils.merge([
+ player.bind("cover_path"),
+ player.bind("track_cover_url"),
+ media.coverSize.bind(),
+ ], (path, url, size) => `
+ min-width: ${size}px;
+ min-height: ${size}px;
+ background-image: url('${path || url}');
+ `),
+ })
+
+ const title = Widget.Label({
+ class_name: "title",
+ max_width_chars: 20,
+ truncate: "end",
+ hpack: "start",
+ label: player.bind("track_title"),
+ })
+
+ const artist = Widget.Label({
+ class_name: "artist",
+ max_width_chars: 20,
+ truncate: "end",
+ hpack: "start",
+ label: player.bind("track_artists").as(a => a.join(", ")),
+ })
+
+ const positionSlider = Widget.Slider({
+ class_name: "position",
+ draw_value: false,
+ on_change: ({ value }) => player.position = value * player.length,
+ setup: self => {
+ const update = () => {
+ const { length, position } = player
+ self.visible = length > 0
+ self.value = length > 0 ? position / length : 0
+ }
+ self.hook(player, update)
+ self.hook(player, update, "position")
+ self.poll(1000, update)
+ },
+ })
+
+ const positionLabel = Widget.Label({
+ class_name: "position",
+ hpack: "start",
+ setup: self => {
+ const update = (_: unknown, time?: number) => {
+ self.label = lengthStr(time || player.position)
+ self.visible = player.length > 0
+ }
+ self.hook(player, update, "position")
+ self.poll(1000, update)
+ },
+ })
+
+ const lengthLabel = Widget.Label({
+ class_name: "length",
+ hpack: "end",
+ visible: player.bind("length").as(l => l > 0),
+ label: player.bind("length").as(lengthStr),
+ })
+
+ const playericon = Widget.Icon({
+ class_name: "icon",
+ hexpand: true,
+ hpack: "end",
+ vpack: "start",
+ tooltip_text: player.identity || "",
+ icon: Utils.merge([player.bind("entry"), media.monochromeIcon.bind()], (e, s) => {
+ const name = `${e}${s ? "-symbolic" : ""}`
+ return icon(name, icons.fallback.audio)
+ }),
+ })
+
+ const playPause = Widget.Button({
+ class_name: "play-pause",
+ on_clicked: () => player.playPause(),
+ visible: player.bind("can_play"),
+ child: Widget.Icon({
+ icon: player.bind("play_back_status").as(s => {
+ switch (s) {
+ case "Playing": return icons.mpris.playing
+ case "Paused":
+ case "Stopped": return icons.mpris.stopped
+ }
+ }),
+ }),
+ })
+
+ const prev = Widget.Button({
+ on_clicked: () => player.previous(),
+ visible: player.bind("can_go_prev"),
+ child: Widget.Icon(icons.mpris.prev),
+ })
+
+ const next = Widget.Button({
+ on_clicked: () => player.next(),
+ visible: player.bind("can_go_next"),
+ child: Widget.Icon(icons.mpris.next),
+ })
+
+ return Widget.Box(
+ { class_name: "player", vexpand: false },
+ cover,
+ Widget.Box(
+ { vertical: true },
+ Widget.Box([
+ title,
+ playericon,
+ ]),
+ artist,
+ Widget.Box({ vexpand: true }),
+ positionSlider,
+ Widget.CenterBox({
+ class_name: "footer horizontal",
+ start_widget: positionLabel,
+ center_widget: Widget.Box([
+ prev,
+ playPause,
+ next,
+ ]),
+ end_widget: lengthLabel,
+ }),
+ ),
+ )
+}
+
+export const Media = () => Widget.Box({
+ vertical: true,
+ class_name: "media vertical",
+ children: players.as(p => p.map(Player)),
+})
diff --git a/linux/home/.config/ags/widget/quicksettings/widgets/MicMute.ts b/linux/home/.config/ags/widget/quicksettings/widgets/MicMute.ts
new file mode 100644
index 0000000..b6e9454
--- /dev/null
+++ b/linux/home/.config/ags/widget/quicksettings/widgets/MicMute.ts
@@ -0,0 +1,18 @@
+import { SimpleToggleButton } from "../ToggleButton"
+import icons from "lib/icons"
+const { microphone } = await Service.import("audio")
+
+const icon = () => microphone.is_muted || microphone.stream?.is_muted
+ ? icons.audio.mic.muted
+ : icons.audio.mic.high
+
+const label = () => microphone.is_muted || microphone.stream?.is_muted
+ ? "Muted"
+ : "Unmuted"
+
+export const MicMute = () => SimpleToggleButton({
+ icon: Utils.watch(icon(), microphone, icon),
+ label: Utils.watch(label(), microphone, label),
+ toggle: () => microphone.is_muted = !microphone.is_muted,
+ connection: [microphone, () => microphone?.is_muted || false],
+})
diff --git a/linux/home/.config/ags/widget/quicksettings/widgets/Network.ts b/linux/home/.config/ags/widget/quicksettings/widgets/Network.ts
new file mode 100644
index 0000000..eb14ab4
--- /dev/null
+++ b/linux/home/.config/ags/widget/quicksettings/widgets/Network.ts
@@ -0,0 +1,61 @@
+import { Menu, ArrowToggleButton } from "../ToggleButton"
+import icons from "lib/icons.js"
+import { dependencies, sh } from "lib/utils"
+import options from "options"
+const { wifi } = await Service.import("network")
+
+export const NetworkToggle = () => ArrowToggleButton({
+ name: "network",
+ icon: wifi.bind("icon_name"),
+ label: wifi.bind("ssid").as(ssid => ssid || "Not Connected"),
+ connection: [wifi, () => wifi.enabled],
+ deactivate: () => wifi.enabled = false,
+ activate: () => {
+ wifi.enabled = true
+ wifi.scan()
+ },
+})
+
+export const WifiSelection = () => Menu({
+ name: "network",
+ icon: wifi.bind("icon_name"),
+ title: "Wifi Selection",
+ content: [
+ Widget.Box({
+ vertical: true,
+ setup: self => self.hook(wifi, () => self.children =
+ wifi.access_points.map(ap => Widget.Button({
+ on_clicked: () => {
+ if (dependencies("nmcli"))
+ Utils.execAsync(`nmcli device wifi connect ${ap.bssid}`)
+ },
+ child: Widget.Box({
+ children: [
+ Widget.Icon(ap.iconName),
+ Widget.Label(ap.ssid || ""),
+ Widget.Icon({
+ icon: icons.ui.tick,
+ hexpand: true,
+ hpack: "end",
+ setup: self => Utils.idle(() => {
+ if (!self.is_destroyed)
+ self.visible = ap.active
+ }),
+ }),
+ ],
+ }),
+ })),
+ ),
+ }),
+ Widget.Separator(),
+ Widget.Button({
+ on_clicked: () => sh(options.quicksettings.networkSettings.value),
+ child: Widget.Box({
+ children: [
+ Widget.Icon(icons.ui.settings),
+ Widget.Label("Network"),
+ ],
+ }),
+ }),
+ ],
+})
diff --git a/linux/home/.config/ags/widget/quicksettings/widgets/PowerProfile.ts b/linux/home/.config/ags/widget/quicksettings/widgets/PowerProfile.ts
new file mode 100644
index 0000000..f566aaf
--- /dev/null
+++ b/linux/home/.config/ags/widget/quicksettings/widgets/PowerProfile.ts
@@ -0,0 +1,99 @@
+import { ArrowToggleButton, Menu } from "../ToggleButton"
+import icons from "lib/icons"
+
+import asusctl from "service/asusctl"
+const asusprof = asusctl.bind("profile")
+
+const AsusProfileToggle = () => ArrowToggleButton({
+ name: "asusctl-profile",
+ icon: asusprof.as(p => icons.asusctl.profile[p]),
+ label: asusprof,
+ connection: [asusctl, () => asusctl.profile !== "Balanced"],
+ activate: () => asusctl.setProfile("Quiet"),
+ deactivate: () => asusctl.setProfile("Balanced"),
+ activateOnArrow: false,
+})
+
+const AsusProfileSelector = () => Menu({
+ name: "asusctl-profile",
+ icon: asusprof.as(p => icons.asusctl.profile[p]),
+ title: "Profile Selector",
+ content: [
+ Widget.Box({
+ vertical: true,
+ hexpand: true,
+ children: [
+ Widget.Box({
+ vertical: true,
+ children: asusctl.profiles.map(prof => Widget.Button({
+ on_clicked: () => asusctl.setProfile(prof),
+ child: Widget.Box({
+ children: [
+ Widget.Icon(icons.asusctl.profile[prof]),
+ Widget.Label(prof),
+ ],
+ }),
+ })),
+ }),
+ ],
+ }),
+ Widget.Separator(),
+ Widget.Button({
+ on_clicked: () => Utils.execAsync("rog-control-center"),
+ child: Widget.Box({
+ children: [
+ Widget.Icon(icons.ui.settings),
+ Widget.Label("Rog Control Center"),
+ ],
+ }),
+ }),
+ ],
+})
+
+
+const pp = await Service.import("powerprofiles")
+const profile = pp.bind("active_profile")
+const profiles = pp.profiles.map(p => p.Profile)
+
+const pretty = (str: string) => str
+ .split("-")
+ .map(str => `${str.at(0)?.toUpperCase()}${str.slice(1)}`)
+ .join(" ")
+
+const PowerProfileToggle = () => ArrowToggleButton({
+ name: "asusctl-profile",
+ icon: profile.as(p => icons.powerprofile[p]),
+ label: profile.as(pretty),
+ connection: [pp, () => pp.active_profile !== profiles[1]],
+ activate: () => pp.active_profile = profiles[0],
+ deactivate: () => pp.active_profile = profiles[1],
+ activateOnArrow: false,
+})
+
+const PowerProfileSelector = () => Menu({
+ name: "asusctl-profile",
+ icon: profile.as(p => icons.powerprofile[p]),
+ title: "Profile Selector",
+ content: [Widget.Box({
+ vertical: true,
+ hexpand: true,
+ child: Widget.Box({
+ vertical: true,
+ children: profiles.map(prof => Widget.Button({
+ on_clicked: () => pp.active_profile = prof,
+ child: Widget.Box({
+ children: [
+ Widget.Icon(icons.powerprofile[prof]),
+ Widget.Label(pretty(prof)),
+ ],
+ }),
+ })),
+ }),
+ })],
+})
+
+export const ProfileToggle = asusctl.available
+ ? AsusProfileToggle : PowerProfileToggle
+
+export const ProfileSelector = asusctl.available
+ ? AsusProfileSelector : PowerProfileSelector
diff --git a/linux/home/.config/ags/widget/quicksettings/widgets/Volume.ts b/linux/home/.config/ags/widget/quicksettings/widgets/Volume.ts
new file mode 100644
index 0000000..077439a
--- /dev/null
+++ b/linux/home/.config/ags/widget/quicksettings/widgets/Volume.ts
@@ -0,0 +1,161 @@
+import { type Stream } from "types/service/audio"
+import { Arrow, Menu } from "../ToggleButton"
+import { dependencies, icon, sh } from "lib/utils"
+import icons from "lib/icons.js"
+const audio = await Service.import("audio")
+
+type Type = "microphone" | "speaker"
+
+const VolumeIndicator = (type: Type = "speaker") => Widget.Button({
+ vpack: "center",
+ on_clicked: () => audio[type].is_muted = !audio[type].is_muted,
+ child: Widget.Icon({
+ icon: audio[type].bind("icon_name")
+ .as(i => icon(i || "", icons.audio.volume.medium)),
+ tooltipText: audio[type].bind("volume")
+ .as(vol => `Volume: ${Math.floor(vol * 100)}%`),
+ }),
+})
+
+const micIndicator = (type: Type = "microphone") => Widget.Button({
+ vpack: "center",
+ on_clicked: () => audio[type].is_muted = !audio[type].is_muted,
+ child: Widget.Icon({
+ icon: audio[type].bind("icon_name")
+ .as(i => icon(i || "", icons.audio.mic.medium)),
+ tooltipText: audio[type].bind("volume")
+ .as(vol => `Volume: ${Math.floor(vol * 100)}%`),
+ }),
+})
+
+const VolumeSlider = (type: Type = "speaker") => Widget.Slider({
+ hexpand: true,
+ draw_value: false,
+ on_change: ({ value, dragging }) => {
+ if (dragging) {
+ audio[type].volume = value
+ audio[type].is_muted = false
+ }
+ },
+ value: audio[type].bind("volume"),
+ class_name: audio[type].bind("is_muted").as(m => m ? "muted" : ""),
+})
+
+export const Volume = () => Widget.Box({
+ class_name: "volume",
+ children: [
+ VolumeIndicator("speaker"),
+ VolumeSlider("speaker"),
+ Widget.Box({
+ vpack: "center",
+ child: Arrow("sink-selector"),
+ }),
+ Widget.Box({
+ vpack: "center",
+ child: Arrow("app-mixer"),
+ visible: audio.bind("apps").as(a => a.length > 0),
+ }),
+ ],
+})
+
+export const Microhone = () => Widget.Box({
+ class_name: "slider horizontal",
+ visible: audio.bind("recorders").as(a => a.length > 0),
+ children: [
+ micIndicator("microphone"),
+ VolumeSlider("microphone"),
+ ],
+})
+
+const MixerItem = (stream: Stream) => Widget.Box(
+ {
+ hexpand: true,
+ class_name: "mixer-item horizontal",
+ },
+ Widget.Icon({
+ tooltip_text: stream.bind("name").as(n => n || ""),
+ icon: stream.bind("name").as(n => {
+ return Utils.lookUpIcon(n || "")
+ ? (n || "")
+ : icons.fallback.audio
+ }),
+ }),
+ Widget.Box(
+ { vertical: true },
+ Widget.Label({
+ xalign: 0,
+ truncate: "end",
+ max_width_chars: 28,
+ label: stream.bind("description").as(d => d || ""),
+ }),
+ Widget.Slider({
+ hexpand: true,
+ draw_value: false,
+ value: stream.bind("volume"),
+ on_change: ({ value }) => stream.volume = value,
+ }),
+ ),
+)
+
+const SinkItem = (stream: Stream) => Widget.Button({
+ hexpand: true,
+ on_clicked: () => audio.speaker = stream,
+ child: Widget.Box({
+ children: [
+ Widget.Icon({
+ icon: icon(stream.icon_name || "", icons.fallback.audio),
+ tooltip_text: stream.icon_name || "",
+ }),
+ Widget.Label((stream.description || "").split(" ").slice(0, 4).join(" ")),
+ Widget.Icon({
+ icon: icons.ui.tick,
+ hexpand: true,
+ hpack: "end",
+ visible: audio.speaker.bind("stream").as(s => s === stream.stream),
+ }),
+ ],
+ }),
+})
+
+const SettingsButton = () => Widget.Button({
+ on_clicked: () => {
+ if (dependencies("pavucontrol"))
+ sh("pavucontrol")
+ },
+ hexpand: true,
+ child: Widget.Box({
+ children: [
+ Widget.Icon(icons.ui.settings),
+ Widget.Label("Settings"),
+ ],
+ }),
+})
+
+export const AppMixer = () => Menu({
+ name: "app-mixer",
+ icon: icons.audio.mixer,
+ title: "App Mixer",
+ content: [
+ Widget.Box({
+ vertical: true,
+ class_name: "vertical mixer-item-box",
+ children: audio.bind("apps").as(a => a.map(MixerItem)),
+ }),
+ Widget.Separator(),
+ SettingsButton(),
+ ],
+})
+
+export const SinkSelector = () => Menu({
+ name: "sink-selector",
+ icon: icons.audio.type.headset,
+ title: "Sink Selector",
+ content: [
+ Widget.Box({
+ vertical: true,
+ children: audio.bind("speakers").as(a => a.map(SinkItem)),
+ }),
+ Widget.Separator(),
+ SettingsButton(),
+ ],
+})
diff --git a/linux/home/.config/ags/widget/settings/Group.ts b/linux/home/.config/ags/widget/settings/Group.ts
new file mode 100644
index 0000000..e9356e0
--- /dev/null
+++ b/linux/home/.config/ags/widget/settings/Group.ts
@@ -0,0 +1,34 @@
+import icons from "lib/icons"
+import Row from "./Row"
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export default (title: string, ...rows: ReturnType<typeof Row<any>>[]) => Widget.Box(
+ {
+ class_name: "group",
+ vertical: true,
+ },
+ Widget.Box([
+ Widget.Label({
+ hpack: "start",
+ vpack: "end",
+ class_name: "group-title",
+ label: title,
+ setup: w => Utils.idle(() => w.visible = !!title),
+ }),
+ title ? Widget.Button({
+ hexpand: true,
+ hpack: "end",
+ child: Widget.Icon(icons.ui.refresh),
+ class_name: "group-reset",
+ sensitive: Utils.merge(
+ rows.map(({ attribute: { opt } }) => opt.bind().as(v => v !== opt.initial)),
+ (...values) => values.some(b => b),
+ ),
+ on_clicked: () => rows.forEach(row => row.attribute.opt.reset()),
+ }) : Widget.Box(),
+ ]),
+ Widget.Box({
+ vertical: true,
+ children: rows,
+ }),
+)
diff --git a/linux/home/.config/ags/widget/settings/Page.ts b/linux/home/.config/ags/widget/settings/Page.ts
new file mode 100644
index 0000000..220e560
--- /dev/null
+++ b/linux/home/.config/ags/widget/settings/Page.ts
@@ -0,0 +1,19 @@
+import Group from "./Group"
+
+export default <T>(
+ name: string,
+ icon: string,
+ ...groups: ReturnType<typeof Group<T>>[]
+) => Widget.Box({
+ class_name: "page",
+ attribute: { name, icon },
+ child: Widget.Scrollable({
+ css: "min-height: 300px;",
+ child: Widget.Box({
+ class_name: "page-content",
+ vexpand: true,
+ vertical: true,
+ children: groups,
+ }),
+ }),
+})
diff --git a/linux/home/.config/ags/widget/settings/Row.ts b/linux/home/.config/ags/widget/settings/Row.ts
new file mode 100644
index 0000000..1e17096
--- /dev/null
+++ b/linux/home/.config/ags/widget/settings/Row.ts
@@ -0,0 +1,55 @@
+import { Opt } from "lib/option"
+import Setter from "./Setter"
+import icons from "lib/icons"
+
+export type RowProps<T> = {
+ opt: Opt<T>
+ title: string
+ note?: string
+ type?:
+ | "number"
+ | "color"
+ | "float"
+ | "object"
+ | "string"
+ | "enum"
+ | "boolean"
+ | "img"
+ | "font"
+ enums?: string[]
+ max?: number
+ min?: number
+}
+
+export default <T>(props: RowProps<T>) => Widget.Box(
+ {
+ attribute: { opt: props.opt },
+ class_name: "row",
+ tooltip_text: props.note ? `note: ${props.note}` : "",
+ },
+ Widget.Box(
+ { vertical: true, vpack: "center" },
+ Widget.Label({
+ xalign: 0,
+ class_name: "row-title",
+ label: props.title,
+ }),
+ Widget.Label({
+ xalign: 0,
+ class_name: "id",
+ label: props.opt.id,
+ }),
+ ),
+ Widget.Box({ hexpand: true }),
+ Widget.Box(
+ { vpack: "center" },
+ Setter(props),
+ ),
+ Widget.Button({
+ vpack: "center",
+ class_name: "reset",
+ child: Widget.Icon(icons.ui.refresh),
+ on_clicked: () => props.opt.reset(),
+ sensitive: props.opt.bind().as(v => v !== props.opt.initial),
+ }),
+)
diff --git a/linux/home/.config/ags/widget/settings/Setter.ts b/linux/home/.config/ags/widget/settings/Setter.ts
new file mode 100644
index 0000000..7e455c9
--- /dev/null
+++ b/linux/home/.config/ags/widget/settings/Setter.ts
@@ -0,0 +1,93 @@
+import { type RowProps } from "./Row"
+import { Opt } from "lib/option"
+import icons from "lib/icons"
+import Gdk from "gi://Gdk"
+
+function EnumSetter(opt: Opt<string>, values: string[]) {
+ const lbl = Widget.Label({ label: opt.bind().as(v => `${v}`) })
+ const step = (dir: 1 | -1) => {
+ const i = values.findIndex(i => i === lbl.label)
+ opt.setValue(dir > 0
+ ? i + dir > values.length - 1 ? values[0] : values[i + dir]
+ : i + dir < 0 ? values[values.length - 1] : values[i + dir],
+ )
+ }
+ const next = Widget.Button({
+ child: Widget.Icon(icons.ui.arrow.right),
+ on_clicked: () => step(+1),
+ })
+ const prev = Widget.Button({
+ child: Widget.Icon(icons.ui.arrow.left),
+ on_clicked: () => step(-1),
+ })
+ return Widget.Box({
+ class_name: "enum-setter",
+ children: [lbl, prev, next],
+ })
+}
+
+export default function Setter<T>({
+ opt,
+ type = typeof opt.value as RowProps<T>["type"],
+ enums,
+ max = 1000,
+ min = 0,
+}: RowProps<T>) {
+ switch (type) {
+ case "number": return Widget.SpinButton({
+ setup(self) {
+ self.set_range(min, max)
+ self.set_increments(1, 5)
+ self.on("value-changed", () => opt.value = self.value as T)
+ self.hook(opt, () => self.value = opt.value as number)
+ },
+ })
+
+ case "float":
+ case "object": return Widget.Entry({
+ on_accept: self => opt.value = JSON.parse(self.text || ""),
+ setup: self => self.hook(opt, () => self.text = JSON.stringify(opt.value)),
+ })
+
+ case "string": return Widget.Entry({
+ on_accept: self => opt.value = self.text as T,
+ setup: self => self.hook(opt, () => self.text = opt.value as string),
+ })
+
+ case "enum": return EnumSetter(opt as unknown as Opt<string>, enums!)
+ case "boolean": return Widget.Switch()
+ .on("notify::active", self => opt.value = self.active as T)
+ .hook(opt, self => self.active = opt.value as boolean)
+
+ case "img": return Widget.FileChooserButton({
+ on_file_set: ({ uri }) => { opt.value = uri!.replace("file://", "") as T },
+ })
+
+ case "font": return Widget.FontButton({
+ show_size: false,
+ use_size: false,
+ setup: self => self
+ .hook(opt, () => self.font = opt.value as string)
+ .on("font-set", ({ font }) => opt.value = font!
+ .split(" ").slice(0, -1).join(" ") as T),
+ })
+
+ case "color": return Widget.ColorButton()
+ .hook(opt, self => {
+ const rgba = new Gdk.RGBA()
+ rgba.parse(opt.value as string)
+ self.rgba = rgba
+ })
+ .on("color-set", ({ rgba: { red, green, blue } }) => {
+ const hex = (n: number) => {
+ const c = Math.floor(255 * n).toString(16)
+ return c.length === 1 ? `0${c}` : c
+ }
+ opt.value = `#${hex(red)}${hex(green)}${hex(blue)}` as T
+ })
+
+ default: return Widget.Label({
+ label: `no setter with type ${type}`,
+ })
+ }
+}
diff --git a/linux/home/.config/ags/widget/settings/SettingsDialog.ts b/linux/home/.config/ags/widget/settings/SettingsDialog.ts
new file mode 100644
index 0000000..be0c35e
--- /dev/null
+++ b/linux/home/.config/ags/widget/settings/SettingsDialog.ts
@@ -0,0 +1,63 @@
+import RegularWindow from "widget/RegularWindow"
+import layout from "./layout"
+import icons from "lib/icons"
+import options from "options"
+
+const current = Variable(layout[0].attribute.name)
+
+const Header = () => Widget.CenterBox({
+ class_name: "header",
+ start_widget: Widget.Button({
+ class_name: "reset",
+ on_clicked: options.reset,
+ hpack: "start",
+ vpack: "start",
+ child: Widget.Icon(icons.ui.refresh),
+ tooltip_text: "Reset",
+ }),
+ center_widget: Widget.Box({
+ class_name: "pager horizontal",
+ children: layout.map(({ attribute: { name, icon } }) => Widget.Button({
+ xalign: 0,
+ class_name: current.bind().as(v => `${v === name ? "active" : ""}`),
+ on_clicked: () => current.value = name,
+ child: Widget.Box([
+ Widget.Icon(icon),
+ Widget.Label(name),
+ ]),
+ })),
+ }),
+ end_widget: Widget.Button({
+ class_name: "close",
+ hpack: "end",
+ vpack: "start",
+ child: Widget.Icon(icons.ui.close),
+ on_clicked: () => App.closeWindow("settings-dialog"),
+ }),
+})
+
+const PagesStack = () => Widget.Stack({
+ transition: "slide_left_right",
+ children: layout.reduce((obj, page) => ({ ...obj, [page.attribute.name]: page }), {}),
+ shown: current.bind() as never,
+})
+
+export default () => RegularWindow({
+ name: "settings-dialog",
+ class_name: "settings-dialog",
+ title: "Settings",
+ setup(win) {
+ win.on("delete-event", () => {
+ win.hide()
+ return true
+ })
+ win.set_default_size(500, 600)
+ },
+ child: Widget.Box({
+ vertical: true,
+ children: [
+ Header(),
+ PagesStack(),
+ ],
+ }),
+})
diff --git a/linux/home/.config/ags/widget/settings/Wallpaper.ts b/linux/home/.config/ags/widget/settings/Wallpaper.ts
new file mode 100644
index 0000000..998f3b7
--- /dev/null
+++ b/linux/home/.config/ags/widget/settings/Wallpaper.ts
@@ -0,0 +1,31 @@
+import wallpaper from "service/wallpaper"
+
+export default () => Widget.Box(
+ { class_name: "row wallpaper" },
+ Widget.Box(
+ { vertical: true },
+ Widget.Label({
+ xalign: 0,
+ class_name: "row-title",
+ label: "Wallpaper",
+ vpack: "start",
+ }),
+ Widget.Button({
+ on_clicked: wallpaper.random,
+ label: "Random",
+ }),
+ Widget.FileChooserButton({
+ on_file_set: ({ uri }) => wallpaper.set(uri!.replace("file://", "")),
+ }),
+ ),
+ Widget.Box({ hexpand: true }),
+ Widget.Box({
+ class_name: "preview",
+ css: wallpaper.bind("wallpaper").as(wp => `
+ min-height: 120px;
+ min-width: 200px;
+ background-image: url('${wp}');
+ background-size: cover;
+ `),
+ }),
+)
diff --git a/linux/home/.config/ags/widget/settings/layout.ts b/linux/home/.config/ags/widget/settings/layout.ts
new file mode 100644
index 0000000..2b45810
--- /dev/null
+++ b/linux/home/.config/ags/widget/settings/layout.ts
@@ -0,0 +1,147 @@
+/* eslint-disable max-len */
+import Row from "./Row"
+import Group from "./Group"
+import Page from "./Page"
+import Wallpaper from "./Wallpaper"
+import options from "options"
+import icons from "lib/icons"
+
+const {
+ autotheme: at,
+ font,
+ theme,
+ bar: b,
+ launcher: l,
+ overview: ov,
+ powermenu: pm,
+ quicksettings: qs,
+ osd,
+ hyprland: h,
+} = options
+
+const {
+ dark,
+ light,
+ blur,
+ scheme,
+ padding,
+ spacing,
+ radius,
+ shadows,
+ widget,
+ border,
+} = theme
+
+export default [
+ Page("Theme", icons.ui.themes,
+ Group("",
+ Wallpaper() as ReturnType<typeof Row>,
+ Row({ opt: at, title: "Auto Generate Color Scheme" }),
+ Row({ opt: scheme, title: "Color Scheme", type: "enum", enums: ["dark", "light"] }),
+ ),
+ Group("Dark Colors",
+ Row({ opt: dark.bg, title: "Background", type: "color" }),
+ Row({ opt: dark.fg, title: "Foreground", type: "color" }),
+ Row({ opt: dark.primary.bg, title: "Primary", type: "color" }),
+ Row({ opt: dark.primary.fg, title: "On Primary", type: "color" }),
+ Row({ opt: dark.error.bg, title: "Error", type: "color" }),
+ Row({ opt: dark.error.fg, title: "On Error", type: "color" }),
+ Row({ opt: dark.widget, title: "Widget", type: "color" }),
+ Row({ opt: dark.border, title: "Border", type: "color" }),
+ ),
+ Group("Light Colors",
+ Row({ opt: light.bg, title: "Background", type: "color" }),
+ Row({ opt: light.fg, title: "Foreground", type: "color" }),
+ Row({ opt: light.primary.bg, title: "Primary", type: "color" }),
+ Row({ opt: light.primary.fg, title: "On Primary", type: "color" }),
+ Row({ opt: light.error.bg, title: "Error", type: "color" }),
+ Row({ opt: light.error.fg, title: "On Error", type: "color" }),
+ Row({ opt: light.widget, title: "Widget", type: "color" }),
+ Row({ opt: light.border, title: "Border", type: "color" }),
+ ),
+ Group("Theme",
+ Row({ opt: shadows, title: "Shadows" }),
+ Row({ opt: widget.opacity, title: "Widget Opacity", max: 100 }),
+ Row({ opt: border.opacity, title: "Border Opacity", max: 100 }),
+ Row({ opt: border.width, title: "Border Width" }),
+ Row({ opt: blur, title: "Blur", note: "0 to disable", max: 70 }),
+ ),
+ Group("UI",
+ Row({ opt: padding, title: "Padding" }),
+ Row({ opt: spacing, title: "Spacing" }),
+ Row({ opt: radius, title: "Roundness" }),
+ Row({ opt: font.size, title: "Font Size" }),
+ Row({ opt: font.name, title: "Font Name", type: "font" }),
+ ),
+ ),
+ Page("Bar", icons.ui.toolbars,
+ Group("General",
+ Row({ opt: b.flatButtons, title: "Flat Buttons" }),
+ Row({ opt: b.position, title: "Position", type: "enum", enums: ["top", "bottom"] }),
+ Row({ opt: b.corners, title: "Corners" }),
+ ),
+ Group("Launcher",
+ Row({ opt: b.launcher.icon.icon, title: "Icon" }),
+ Row({ opt: b.launcher.icon.colored, title: "Colored Icon" }),
+ Row({ opt: b.launcher.label.label, title: "Label" }),
+ Row({ opt: b.launcher.label.colored, title: "Colored Label" }),
+ ),
+ Group("Workspaces",
+ Row({ opt: b.workspaces.workspaces, title: "Number of Workspaces", note: "0 to make it dynamic" }),
+ ),
+ Group("Taskbar",
+ Row({ opt: b.taskbar.iconSize, title: "Icon Size" }),
+ Row({ opt: b.taskbar.monochrome, title: "Monochrome" }),
+ Row({ opt: b.taskbar.exclusive, title: "Exclusive to workspaces" }),
+ ),
+ Group("Date",
+ Row({ opt: b.date.format, title: "Date Format" }),
+ ),
+ Group("Media",
+ Row({ opt: b.media.monochrome, title: "Monochrome" }),
+ Row({ opt: b.media.preferred, title: "Preferred Player" }),
+ Row({ opt: b.media.direction, title: "Slide Direction", type: "enum", enums: ["left", "right"] }),
+ Row({ opt: b.media.format, title: "Format of the Label" }),
+ Row({ opt: b.media.length, title: "Max Length of Label" }),
+ ),
+ Group("Battery",
+ Row({ opt: b.battery.bar, title: "Style", type: "enum", enums: ["hidden", "regular", "whole"] }),
+ Row({ opt: b.battery.blocks, title: "Number of Blocks" }),
+ Row({ opt: b.battery.width, title: "Width of Bar" }),
+ Row({ opt: b.battery.charging, title: "Charging Color", type: "color" }),
+ ),
+ Group("Powermenu",
+ Row({ opt: b.powermenu.monochrome, title: "Monochrome" }),
+ ),
+ ),
+ Page("General", icons.ui.settings,
+ Group("Hyprland",
+ Row({ opt: h.gapsWhenOnly, title: "Gaps When Only" }),
+ ),
+ Group("Launcher",
+ Row({ opt: l.width, title: "Width" }),
+ Row({ opt: l.apps.iconSize, title: "Icon Size" }),
+ Row({ opt: l.apps.max, title: "Max Items" }),
+ ),
+ Group("Overview",
+ Row({ opt: ov.scale, title: "Scale", max: 100 }),
+ Row({ opt: ov.workspaces, title: "Workspaces", max: 11, note: "set this to 0 to make it dynamic" }),
+ Row({ opt: ov.monochromeIcon, title: "Monochrome Icons" }),
+ ),
+ Group("Powermenu",
+ Row({ opt: pm.layout, title: "Layout", type: "enum", enums: ["box", "line"] }),
+ Row({ opt: pm.labels, title: "Show Labels" }),
+ ),
+ Group("Quicksettings",
+ Row({ opt: qs.avatar.image, title: "Avatar", type: "img" }),
+ Row({ opt: qs.avatar.size, title: "Avatar Size" }),
+ Row({ opt: qs.media.monochromeIcon, title: "Media Monochrome Icons" }),
+ Row({ opt: qs.media.coverSize, title: "Media Cover Art Size" }),
+ ),
+ Group("On Screen Indicator",
+ Row({ opt: osd.progress.vertical, title: "Vertical" }),
+ Row({ opt: osd.progress.pack.h, title: "Horizontal Alignment", type: "enum", enums: ["start", "center", "end"] }),
+ Row({ opt: osd.progress.pack.v, title: "Vertical Alignment", type: "enum", enums: ["start", "center", "end"] }),
+ ),
+ ),
+] as const
diff --git a/linux/home/.config/ags/widget/settings/settingsdialog.scss b/linux/home/.config/ags/widget/settings/settingsdialog.scss
new file mode 100644
index 0000000..b8c9820
--- /dev/null
+++ b/linux/home/.config/ags/widget/settings/settingsdialog.scss
@@ -0,0 +1,144 @@
+window.settings-dialog {
+ background-color: $bg;
+ color: $fg;
+
+ .header {
+ .pager {
+ @include spacing(.5);
+ }
+
+ padding: $padding;
+
+ button {
+ @include button;
+ font-weight: bold;
+ padding: $padding*.5 $padding;
+
+ box {
+ @include spacing($spacing: .3em);
+ }
+ }
+
+ button.close {
+ padding: $padding * .5;
+ }
+
+ button.reset {
+ @include button($flat: true);
+ padding: $padding*.5;
+ }
+ }
+
+ .page {
+ @include scrollable($top: true);
+
+ .page-content {
+ padding: $padding*2;
+ padding-top: 0;
+ }
+ }
+
+ .group {
+ .group-title {
+ color: $primary-bg;
+ margin-bottom: $spacing*.5;
+ }
+
+ .group-reset {
+ @include button($flat: true);
+ margin: $spacing * .5;
+ padding: $padding * .5;
+
+ &:disabled {
+ color: transparent;
+ }
+ }
+
+ &:not(:first-child) {
+ margin-top: $spacing;
+ }
+ }
+
+ .row {
+ background-color: $widget-bg;
+ padding: $padding;
+ border: $border;
+ border-top: none;
+
+ &:first-child {
+ border-radius: $radius $radius 0 0;
+ border: $border;
+ }
+
+ &:last-child {
+ border-radius: 0 0 $radius $radius;
+ }
+
+ &:first-child:last-child {
+ border-radius: $radius;
+ border: $border;
+ }
+
+ button.reset {
+ margin-left: $spacing;
+ }
+
+ label.id,
+ label.note {
+ color: transparentize($fg, .4)
+ }
+
+ entry,
+ button {
+ @include button;
+ padding: $padding;
+ }
+
+ switch {
+ @include switch;
+ }
+
+ spinbutton {
+ @include unset;
+
+ entry {
+ border-radius: $radius 0 0 $radius;
+ }
+
+ button {
+ border-radius: 0;
+ }
+
+ button:last-child {
+ border-radius: 0 $radius $radius 0;
+ }
+ }
+
+ .enum-setter {
+ label {
+ background-color: $widget-bg;
+ border: $border;
+ padding: 0 $padding;
+ border-radius: $radius 0 0 $radius;
+ }
+
+ button {
+ border-radius: 0;
+ }
+
+ button:last-child {
+ border-radius: 0 $radius $radius 0;
+ }
+ }
+
+ &.wallpaper {
+ button {
+ margin-top: $spacing * .5;
+ }
+
+ .preview {
+ border-radius: $radius;
+ }
+ }
+ }
+}
diff --git a/linux/home/.config/betterlockscreen/betterlockscreenrc b/linux/home/.config/betterlockscreen/betterlockscreenrc
new file mode 100644
index 0000000..4cdfbe8
--- /dev/null
+++ b/linux/home/.config/betterlockscreen/betterlockscreenrc
@@ -0,0 +1,37 @@
+# ~/.config/betterlockscreenrc
+
+# default options
+display_on=0
+span_image=false
+lock_timeout=300
+fx_list=(dim blur dimblur pixel dimpixel color)
+dim_level=40
+blur_level=1
+pixel_scale=10,1000
+solid_color=333333
+wallpaper_cmd="feh --bg-fill"
+quiet=false
+# i3lockcolor_bin="i3lock-color" # Manually set command for i3lock-color
+
+# default theme
+loginbox=00000066
+loginshadow=00000000
+locktext="Type password to unlock..."
+font="sans-serif"
+ringcolor=ffffffff
+insidecolor=00000000
+separatorcolor=00000000
+ringvercolor=ffffffff
+insidevercolor=00000000
+ringwrongcolor=ffffffff
+insidewrongcolor=d23c3dff
+timecolor=ffffffff
+time_format="%H:%M:%S"
+greetercolor=ffffffff
+layoutcolor=ffffffff
+keyhlcolor=d23c3dff
+bshlcolor=d23c3dff
+verifcolor=ffffffff
+wrongcolor=d23c3dff
+modifcolor=d23c3dff
+bgcolor=000000ff
diff --git a/linux/home/.config/bspwm/bspwmrc b/linux/home/.config/bspwm/bspwmrc
new file mode 100755
index 0000000..d30d6db
--- /dev/null
+++ b/linux/home/.config/bspwm/bspwmrc
@@ -0,0 +1,275 @@
+#! /bin/sh
+
+################################################################################
+# ██████╗ ███████╗██████╗ ██╗ ██╗███╗ ███╗ #
+# ██╔══██╗██╔════╝██╔══██╗██║ ██║████╗ ████║ #
+# ██████╔╝███████╗██████╔╝██║ █╗ ██║██╔████╔██║ #
+# ██╔══██╗╚════██║██╔═══╝ ██║███╗██║██║╚██╔╝██║ #
+# ██████╔╝███████║██║ ╚███╔███╔╝██║ ╚═╝ ██║ #
+# ╚═════╝ ╚══════╝╚═╝ ╚══╝╚══╝ ╚═╝ ╚═╝ #
+################################## By: srdusr ##################################
+
+# ##############################################################################
+# # ENV VARS #
+# ##############################################################################
+
+## Environments
+export PATH="${PATH}:${HOME}/.config/bspwm/bin"
+
+# Get the name of the primary monitor
+mainmonitor=$(xrandr --query | awk '/ primary/{print $1}')
+
+## Monitors
+# If no primary monitor is identified, use the first connected monitor
+if [ "$mainmonitor" = "" ]; then
+ mainmonitor=$(xrandr --query | awk '/ connected/ {print $1; exit}')
+fi
+
+# Set up workspaces on the primary monitor
+bspc monitor "$mainmonitor" -d 󰲡 󰲣 󰲥 󰲧 󰲩 #1 2 3 4 5
+
+# Check the number of connected monitors
+connected_monitors=$(xrandr --query | grep -c " connected")
+
+if [ "$connected_monitors" -gt 1 ]; then
+ # Get the name of the secondary monitor (exclude the primary monitor)
+ secondmonitor=$(xrandr --query | awk '/ connected/ && $1 != "'"$mainmonitor"'" {print $1; exit}')
+
+ # Set up workspaces on the secondary monitor
+ bspc monitor "$secondmonitor" -d 󰲫 󰲭 󰲯 󰲱 󰿭 #6 7 8 9 10
+ # Check if the secondary monitor is connected and configure the layout
+ if [ "$secondmonitor" != "" ]; then
+ xrandr --output "$mainmonitor" --primary --auto --output "$secondmonitor" --auto --right-of "$mainmonitor"
+ fi
+fi
+
+#INTERNAL_MONITOR="LVDS-1"
+#EXTERNAL_MONITOR="HDMI-1"
+## on first load setup default workspaces
+#if [[ "$1" = 0 ]]; then
+# if [[ $(xrandr -q | grep "${EXTERNAL_MONITOR} connected") ]]; then
+# bspc monitor "$EXTERNAL_MONITOR" -d 1 2 3 4 5
+# bspc monitor "$INTERNAL_MONITOR" -d 6 7 8 9 10
+# bspc wm -O "$EXTERNAL_MONITOR" "$INTERNAL_MONITOR"
+# else
+# bspc monitor "$INTERNAL_MONITOR" -d 1 2 3 4 5 6 7 8 9 10
+# fi
+#fi
+
+# ##############################################################################
+# # FUNCTIONS #
+# ##############################################################################
+
+config() { bspc config "$@" & }
+rule() { bspc rule -a "$@" & }
+run_once() {
+ if [ ! "$(pgrep -f "$1")" ]; then
+ "$@" &
+ fi
+}
+
+# ##############################################################################
+# # WINDOW RULES #
+# ##############################################################################
+
+## Rules
+bspc rule -r *:* # remove all rules first
+rule '*' --one-shot state=below private=border_width:10
+#rule '*:Tiled' --one-shot state=tiled rectangle=50x50+0+50
+#rule '*' --one-shot state=floating rectangle=1028x374+0+50
+#rule \* rectangle=680x700+340+40
+rule '*:*:Picture-in-Picture' state=floating sticky=on layer=above
+rule '*:*:Picture in picture' state=floating sticky=on layer=above
+rule firefox:Toolkit focus=on state=floating sticky=on layer=above rectangle=400x280+955+475 #320x190+1030+480 #522x316-10+280
+rule "https://www.youtube.com - Enhancer for YouTube™ — Mozilla Firefox" state=floating sticky=on layer=above
+rule Wezterm state=floating
+rule Zathura state=tiled
+rule Pavucontrol state=floating rectangle=490x260+862+37
+rule Blueman-manager state=floating rectangle=536x420+818+37 #490x260-9+37
+rule scratchpad sticky=on state=floating # SCRATCHPAD
+rule heads-up-display sticky=on state=floating rectangle=360x160+990+35 # Heads Up Display (scratchpad)
+rule Onboard sticky=on state=floating rectangle=700x205+480-89 # Virtual keyboard
+rule Plank layer=above border=off
+rule Protonvpn state=floating
+rule qBittorrent desktop='^2'
+rule discord desktop='^4'
+rule firefox -o desktop=^1
+rule stalonetray state=floating manage=off
+#bspc rule -a Spotify:spotify desktop='^󰲥' state=tiled
+#bspc rule -a '*:spotify' desktop='^3' state=tiled
+
+# ##############################################################################
+# # AUTOSTART APPS #
+# ##############################################################################
+
+# Clear cache
+#rm "$HOME"/.cache/dunst.log
+#rm "$HOME"/.cache/fake_battery_capacity
+#rm "$HOME"/.cache/eww-calendar.lock
+#rm "$HOME"/.cache/eww-escreen.lock
+#rm "$HOME"/.cache/eww-control-center.lock
+#rm -r "$HOME"/.cache/dunst/
+
+# Autostart applications
+#"$HOME"/.config/bspwm/scripts/bspwm_setup_monitors &
+pgrep -x sxhkd > /dev/null || sxhkd &
+pgrep -x plank > /dev/null || plank &
+pgrep -x jgmenu > /dev/null || bspc rule -a jgmenu desktop='^H' state=floating hidden=on && jgmenu --hide-on-startup &
+picom --config "$HOME"/.config/picom/picom.conf &
+rm "$HOME"/.jgmenu-lockfile
+"$HOME"/.config/polybar/launch.sh &
+run_once unclutter & # Remove mouse when idle
+run_once "$HOME"/.scripts/lockscreen-wallpaper &
+run_once xss-lock -- betterlockscreen -l &
+nitrogen --force-setter=xinerama --restore &
+run_once redshift &
+run_once low-bat-notifier &
+pkill persistentQuickUtilities.sh; "$HOME"/.config/bspwm/scripts/persistentQuickUtilities.sh &
+#xfce4-panel --disable-wm-check &
+
+# Start polkit agent
+#[ "$(pidof xfce-polkit)" != "" ] || /usr/lib/xfce-polkit/xfce-polkit &
+run_once /usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1 &
+
+# Volume and brightness indicator (xob)
+source "$HOME"/.virtualenvs/bin/activate # Activate virtual environment
+run_once "$HOME"/.config/xob/launch.sh &
+deactivate # Deactivate virtual environment
+
+# Eww
+pkill eww
+eww daemon
+
+xset m 0 0 # Disable mouse drift
+
+pgrep -x plank.sh > /dev/null || plank.sh &
+
+#wmname LG3D # Fixes Java applications
+# Solve java apps issues (e.g. JetBrains IDEs like PyCharm, CLion, etc). #
+#export _JAVA_AWT_WM_NONREPARENTING=1
+
+#export QT_QPA_PLATFORMTHEME="qt5ct" # Use qt5ct to set Qt theme
+
+# Start MPD and mpDris2
+#exec mpd &
+#exec mpDris2 &
+
+# Systray
+#run_once signal-desktop --start-in-tray & #--use-tray-icon
+#run_once onboard --not-show-in=DESKTOPS &
+if ! pgrep -x "stalonetray" > /dev/null; then
+ stalonetray &
+fi
+
+declare -a restart=(clipit blueman-applet caffeine)
+for i in "${restart[@]}"; do
+ pgrep -x "$i" | xargs kill
+ sleep 0.5
+ eval "$i" &
+done
+
+while ! pgrep -x "clipit" > /dev/null || ! pgrep -x "blueman-applet" > /dev/null || ! pgrep -x "caffeine" > /dev/null; do
+ #sleep 0.5
+ systray &
+done
+xdo hide -N stalonetray
+touch "/tmp/syshide.lock"
+
+# ##############################################################################
+# # CONFIGURATION #
+# ##############################################################################
+
+## Config
+PANEL_HEIGHT=24
+config top_padding "$PANEL_HEIGHT"
+config honor_size_hints true
+config automatic_scheme alternate
+config initial_polarity second_child
+config pointer_modifier mod4
+config click_to_focus none
+config pointer_action1 move
+config pointer_action2 resize_side
+config pointer_action3 resize_corner
+config focus_follows_pointer true
+config remove_disabled_monitors true
+config remove_unplugged_monitors true
+config merge_overlapping_monitors true
+config border_width 4
+config border_radius 10
+config window_gap 10
+config split_ratio 0.52
+config borderless_monocle true
+config gapless_monocle true
+config swallow_first_click false
+config normal_border_color "#000000"
+config focused_border_color "#000000"
+config active_border_color "#000000"
+config presel_feedback_color "#BF616A"
+
+# ##############################################################################
+# # MISCELLANEOUS #
+# ##############################################################################
+
+## Fullscreen
+bspc subscribe node_state | while read -r _ _ _ _ state flag; do
+ if [[ "$state" != fullscreen ]]; then continue; fi
+ if [[ "$flag" == on ]]; then
+ xdo lower -N Plank
+ #"$(which eww)" -c "$HOME"/.config/eww close-all
+ else
+ xdo raise -N Plank
+ #"$(which eww)" -c "$HOME"/.config/eww open bar
+ fi
+done &
+
+## Title-bar
+rm -rf /tmp/title-bar_debug.log
+rm -rf /tmp/title-bar.lock
+
+processes=("title-bar" "update-title" "lemonbar")
+
+for process in "${processes[@]}"; do
+ if pidof -q "$process"; then
+ pkill -x "$process" > /dev/null; sleep 0.1
+ fi
+done
+
+# Check if title-bar is already running
+if ! pgrep -x "title-bar" >/dev/null; then
+ # Create a lock file
+ lockfile="/tmp/title-bar.lock"
+
+ # Check if the lock file exists
+ if [ ! -e "$lockfile" ]; then
+ # Create the lock file
+ touch "$lockfile"
+
+ # Function to handle BSPWM events
+ handle_bspwm_events() {
+ while read; do
+ if ! pgrep -x "title-bar" >/dev/null; then
+ bash "$HOME/.scripts/title-bar" &
+ fi
+ done
+ }
+
+ # Start bspc subscribe in the background and pass events to the handler function
+ bspc subscribe | handle_bspwm_events &
+
+ # Remove the lock file when the script exits
+ trap 'rm -f "$lockfile"' EXIT
+ else
+ echo "title-bar is already running."
+ fi
+fi
+
+start_dunst() {
+ # stop dunst if it has been started by any application that called notify-send
+ killall -q dunst
+ # Wait until the processes have been shut down
+ while pgrep -u "$UID" -x dunst >/dev/null; do sleep .05; done
+ while ! pgrep -u "$UID" -x dunst >/dev/null; do sleep .05; done
+}
+start_dunst &
+
+config external_rules_command ~/.config/bspwm/scripts/external_rules.sh &
diff --git a/linux/home/.config/bspwm/scripts/bspdragtofloat b/linux/home/.config/bspwm/scripts/bspdragtofloat
new file mode 100755
index 0000000..e2f88a2
--- /dev/null
+++ b/linux/home/.config/bspwm/scripts/bspdragtofloat
@@ -0,0 +1,46 @@
+#!/bin/env bash
+
+: "${BSPWM_DIR:="${XDG_CONFIG_HOME:-$HOME/.config}/bspwm"}"
+
+status_file="$BSPWM_DIR/tmp/drag_to_float"
+
+[[ "$1" = stop ]] && {
+ [[ -e "$status_file" ]] \
+ && rm -r -- "$status_file"
+ exit
+}
+
+[[ -e "$status_file" ]] \
+ && exit
+
+< <(bspc query -T -n pointed.window | jq -r '"\(.id) \(.client.state)"') read -r node node_state
+
+[[ -z "$node" ]] \
+ && exit
+
+case "$node_state" in
+ floating)
+ ;;
+ tiled|pseudo_tiled)
+ node_tiled_rect=($(bspc query -T -n "$node" | jq -r '.client.tiledRectangle[]'))
+ bspc node "$node" -t floating
+ xdo move -x "${node_tiled_rect[0]}" -y "${node_tiled_rect[1]}" "$node"
+ xdo resize -w "${node_tiled_rect[2]}" -h "${node_tiled_rect[3]}" "$node" ;;
+ *) # fullscreen
+ exit ;;
+esac
+
+eval "$(xdotool getmouselocation --shell)"
+x="$X" y="$Y"
+touch -- "$status_file"
+while [[ -e "$status_file" ]]; do
+ eval "$(xdotool getmouselocation --shell)"
+ (( X != x || Y != y )) && {
+ bspc node "$node" -v "$((X - x))" "$((Y - y))"
+ x="$X" y="$Y"
+ }
+ sleep .01
+done
+
+[[ -e "$status_file" ]] \
+ && rm -r -- "$status_file"
diff --git a/linux/home/.config/bspwm/scripts/bspswallow b/linux/home/.config/bspwm/scripts/bspswallow
new file mode 100755
index 0000000..dea343f
--- /dev/null
+++ b/linux/home/.config/bspwm/scripts/bspswallow
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+NODE_CURRENT=$(bspc query -N -n focused)
+$@ &
+PID_COMMAND=$!
+WATCH=$(bspc subscribe -c 1 node_add)
+NODE_NEW=${WATCH%% *}
+bspc node -s $NODE_CURRENT
+bspc node $NODE_CURRENT --flag hidden=on
+wait $PID_COMMAND
+bspc node $NODE_CURRENT --flag hidden=off
+bspc node $NODE_CURRENT --focus
diff --git a/linux/home/.config/bspwm/scripts/bspwindows b/linux/home/.config/bspwm/scripts/bspwindows
new file mode 100755
index 0000000..26deeab
--- /dev/null
+++ b/linux/home/.config/bspwm/scripts/bspwindows
@@ -0,0 +1,14 @@
+#!/bin/sh
+# bspwindows
+# get targets for drawing borders/whatever on in bspwm
+
+target="${1:-active}"
+
+case "$target" in
+ active)
+ bspc query -N -n .local.descendant_of.window.leaf.!fullscreen
+ ;;
+ inactive)
+ bspc query -N -n .local.!descendant_of.window.leaf.!fullscreen
+ ;;
+esac
diff --git a/linux/home/.config/bspwm/scripts/bspwm-monitor-setup b/linux/home/.config/bspwm/scripts/bspwm-monitor-setup
new file mode 100755
index 0000000..6e38bb7
--- /dev/null
+++ b/linux/home/.config/bspwm/scripts/bspwm-monitor-setup
@@ -0,0 +1,59 @@
+#!/usr/bin/env bash
+
+INTERNAL_MONITOR="eDP"
+EXTERNAL_MONITOR="HDMI-A-0"
+
+monitor_add() {
+ # Move first 5 desktops to external monitor
+ for desktop in $(bspc query -D --names -m "$INTERNAL_MONITOR" | sed 5q); do
+ bspc desktop "$desktop" --to-monitor "$EXTERNAL_MONITOR"
+ done
+ # Remove default desktop created by bspwm
+ bspc desktop Desktop --remove
+ # reorder monitors
+ bspc wm -O "$EXTERNAL_MONITOR" "$INTERNAL_MONITOR"
+}
+
+monitor_remove() {
+ # Add default temp desktop because a minimum of one desktop is required per monitor
+ bspc monitor "$EXTERNAL_MONITOR" -a Desktop
+
+ # Move all desktops except the last default desktop to internal monitor
+ for desktop in $(bspc query -D -m "$EXTERNAL_MONITOR"); do
+ bspc desktop "$desktop" --to-monitor "$INTERNAL_MONITOR"
+ done
+
+ # delete default desktops
+ bspc desktop Desktop --remove
+ # reorder desktops
+ bspc monitor "$INTERNAL_MONITOR" -o 1 2 3 4 5 6 7 8 9 10
+}
+
+if [[ $(xrandr -q | grep "${EXTERNAL_MONITOR} connected") ]]; then
+ # set xrandr rules for docked setup
+ xrandr --output "$INTERNAL_MONITOR" --mode 1920x1080 --pos 0x0 --rotate normal --output "$EXTERNAL_MONITOR" --primary --mode 1920x1080 --pos 1920x780 --rotate normal
+ if [[ $(bspc query -D -m "${EXTERNAL_MONITOR}" | wc -l) -ne 5 ]]; then
+ monitor_add
+ fi
+ bspc wm -O "$EXTERNAL_MONITOR" "$INTERNAL_MONITOR"
+else
+ # set xrandr rules for mobile setup
+ xrandr --output "$INTERNAL_MONITOR" --primary --mode 1920x1080 --pos 0x0 --rotate normal --output "$EXTERNAL_MONITOR" --off
+ if [[ $(bspc query -D -m "${INTERNAL_MONITOR}" | wc -l) -ne 10 ]]; then
+ monitor_remove
+ fi
+fi
+
+# Set wallpaper
+~/.local/bin/setbg.sh &
+
+# Kill and relaunch polybar
+kill -9 $(pgrep -f 'polybar') >/dev/null 2>&1
+polybar-msg cmd quit >/dev/null 2>&1
+while pgrep -u $UID -x polybar >/dev/null; do sleep 1; done
+if [[ $(xrandr -q | grep "${EXTERNAL_MONITOR} connected") ]]; then
+ polybar --reload primary -c ~/.config/polybar/config.ini </dev/null >/var/tmp/polybar-primary.log 2>&1 200>&- &
+ polybar --reload secondary -c ~/.config/polybar/config.ini </dev/null >/var/tmp/polybar-secondary.log 2>&1 200>&- &
+else
+ polybar --reload primary -c ~/.config/polybar/config.ini </dev/null >/var/tmp/polybar-primary.log 2>&1 200>&- &
+fi
diff --git a/linux/home/.config/bspwm/scripts/bspwm-toggle-visibility.sh b/linux/home/.config/bspwm/scripts/bspwm-toggle-visibility.sh
new file mode 100755
index 0000000..45a4c53
--- /dev/null
+++ b/linux/home/.config/bspwm/scripts/bspwm-toggle-visibility.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+
+# Created By: srdusr
+# Created On: Mon 18 Sep 2023 18:37:21 CAT
+# Project: Bspwm script to toggle visibility of initial window and bring focus back to it
+
+# Get the ID of the currently focused desktop
+current_desktop_id=$(bspc query -D -d focused --names)
+
+# Get the ID of the first hidden window in the current desktop
+hidden_window_id=$(bspc query -N -d "$current_desktop_id" -n .hidden | head -n 1)
+
+# Check if there's a hidden window in the current desktop
+if [[ -n "$hidden_window_id" ]]; then
+ # There's a hidden window, so unhide it
+ bspc node "$hidden_window_id" -g hidden=off
+ # Bring focus back to the previously hidden window
+ bspc node -f "$hidden_window_id"
+else
+ # There's no hidden window in the current desktop, hide the first available window
+ first_window_id=$(bspc query -N -n focused.window)
+ bspc node "$first_window_id" -g hidden=on
+fi
diff --git a/linux/home/.config/bspwm/scripts/close-window b/linux/home/.config/bspwm/scripts/close-window
new file mode 100755
index 0000000..8f7f36b
--- /dev/null
+++ b/linux/home/.config/bspwm/scripts/close-window
@@ -0,0 +1,11 @@
+#!/bin/sh
+#
+# close or kill a bspwm window
+
+# show wallpaper if last tile
+bspc query -N -n .focused.tiled.window &> /dev/null \
+ && ! bspc query -N -n .!focused.local.tiled.window &> /dev/null \
+ && show-wallpaper -d
+
+# close focused window
+[ "$1" = "kill" ] && bspc node -k || bspc node -c
diff --git a/linux/home/.config/bspwm/scripts/drag-float b/linux/home/.config/bspwm/scripts/drag-float
new file mode 100755
index 0000000..788e978
--- /dev/null
+++ b/linux/home/.config/bspwm/scripts/drag-float
@@ -0,0 +1,42 @@
+#!/bin/env bash
+
+: "${BUTTON:=1}"
+
+node="$(bspc query -N -n pointed)"
+
+die() {
+ jobs -p | xargs -r -n1 -I{} kill {}
+ exit
+}
+
+trap 'die' USR1
+
+{ bspc subscribe node_focus | while read -r _ _ _ wid; do
+ (( wid != node )) && break; done; kill -USR1 "$$" ;} &
+{ while xinput list \
+ | sed -nE 's,.*id=([0-9]+).*slave\s+pointer.*,\1,p' \
+ | xargs -r -n1 -I{} xinput query-state {} 2> /dev/null \
+ | grep -qF "button[${BUTTON}]=down"; do sleep .3; done; kill -USR1 "$$" ;} &
+
+if bspc node "$node.tiled" -f; then
+ node_tiled_rect=($(bspc query -T -n "$node" | jq -r '.client.tiledRectangle[]'))
+ bspc node "$node" -t floating
+ xdo move -x "${node_tiled_rect[0]}" -y "${node_tiled_rect[1]}" "$node"
+ xdo resize -w "${node_tiled_rect[2]}" -h "${node_tiled_rect[3]}" "$node"
+elif bspc node "$node.floating" -f; then
+ :
+else
+ die
+fi
+
+eval "$(xdotool getmouselocation --shell)"
+x="$X" y="$Y"
+while :; do
+ eval "$(xdotool getmouselocation --shell)"
+ (( X != x || Y != y )) && {
+ bspc node "$node" -v "$((X - x))" "$((Y - y))"
+ x="$X" y="$Y"
+ }
+done
+
+wait
diff --git a/linux/home/.config/bspwm/scripts/external_rules.sh b/linux/home/.config/bspwm/scripts/external_rules.sh
new file mode 100755
index 0000000..7b07ae8
--- /dev/null
+++ b/linux/home/.config/bspwm/scripts/external_rules.sh
@@ -0,0 +1,71 @@
+#!/bin/env bash
+#
+# external_rules_command
+#
+# Absolute path to the command used to retrieve rule consequences.
+# The command will receive the following arguments: window ID, class
+# name, instance name, and intermediate consequences. The output of
+# that command must have the following format: key1=value1
+# key2=value2 ... (the valid key/value pairs are given in the
+# description of the rule command).
+#
+#
+# Rule
+# General Syntax
+# rule COMMANDS
+#
+# Commands
+# -a, --add (<class_name>|*)[:(<instance_name>|*)] [-o|--one-shot]
+# [monitor=MONITOR_SEL|desktop=DESKTOP_SEL|node=NODE_SEL]
+# [state=STATE] [layer=LAYER] [split_dir=DIR] [split_ratio=RATIO]
+# [(hidden|sticky|private|locked|marked|center|follow|manage|focus|border)=(on|off)]
+# [rectangle=WxH+X+Y]
+# Create a new rule.
+#
+# -r, --remove
+# ^<n>|head|tail|(<class_name>|*)[:(<instance_name>|*)]...
+# Remove the given rules.
+#
+# -l, --list
+# List the rules.
+
+# Programs to specific desktops
+wid=$1
+class=$2
+instance=$3
+consequences=$4
+
+main() {
+ case "$class" in
+ firefox)
+ if [ "$instance" = "Toolkit" ]; then
+ echo "state=floating sticky=on"
+ fi
+ ;;
+ Spotify)
+ echo desktop=^5 follow=on focus=on
+ ;;
+ "")
+ sleep 0.5
+
+ wm_class=("$(xprop -id "$wid" | grep "WM_CLASS" | grep -Po '"\K[^,"]+')")
+
+ class=${wm_class[-1]}
+
+ [[ ${#wm_class[@]} == "2" ]] && instance=${wm_class[0]}
+
+ [[ -n "$class" ]] && main
+ ;;
+ esac
+}
+
+main
+
+# Allow floating windows over fullscreen
+wid="$1"
+class="$2"
+instance="$3"
+eval "$4"
+
+[[ "$state" = floating ]] &&
+ echo 'layer=above'
diff --git a/linux/home/.config/bspwm/scripts/hide-window b/linux/home/.config/bspwm/scripts/hide-window
new file mode 100755
index 0000000..c350a0e
--- /dev/null
+++ b/linux/home/.config/bspwm/scripts/hide-window
@@ -0,0 +1,38 @@
+#!/bin/bash
+
+CMD=${1:-help}
+shift
+
+help() {
+ echo "Available commands:"
+ echo " * unhide - select and unhide window"
+}
+
+unhide() {
+ action=${1:-list}
+ case $action in
+ "list")
+ selection=$(for id in "$(bspc query -N -n .hidden)"; do
+ title=$(xtitle "$id")
+ [[ -z "$title" ]] && title="<unnamed>"
+ echo "$id" "$title"
+ done | rofi -dmenu -i -p "Hidden windows" | cut -f1 -d' ')
+
+ [[ -z "$selection" ]] && exit 1
+
+ bspc node "$selection" -g hidden=off
+ ;;
+ esac
+ }
+
+ case $CMD in
+ "help")
+ help
+ ;;
+ "unhide")
+ unhide "$1"
+ ;;
+ *)
+ help
+ ;;
+ esac
diff --git a/linux/home/.config/dunst/assets/notification/fallback.png b/linux/home/.config/dunst/assets/notification/fallback.png
new file mode 100644
index 0000000..204aeda
--- /dev/null
+++ b/linux/home/.config/dunst/assets/notification/fallback.png
Binary files differ
diff --git a/linux/home/.config/dunst/assets/notification/music.png b/linux/home/.config/dunst/assets/notification/music.png
new file mode 100644
index 0000000..ae91be3
--- /dev/null
+++ b/linux/home/.config/dunst/assets/notification/music.png
Binary files differ
diff --git a/linux/home/.config/dunst/assets/notification/scrot.png b/linux/home/.config/dunst/assets/notification/scrot.png
new file mode 100644
index 0000000..1e12f38
--- /dev/null
+++ b/linux/home/.config/dunst/assets/notification/scrot.png
Binary files differ
diff --git a/linux/home/.config/dunst/assets/ui/volume-high.svg b/linux/home/.config/dunst/assets/ui/volume-high.svg
new file mode 100644
index 0000000..43152c9
--- /dev/null
+++ b/linux/home/.config/dunst/assets/ui/volume-high.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#eceff4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-volume-2"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon><path d="M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07"></path></svg> \ No newline at end of file
diff --git a/linux/home/.config/dunst/assets/ui/volume-low.svg b/linux/home/.config/dunst/assets/ui/volume-low.svg
new file mode 100644
index 0000000..09b3650
--- /dev/null
+++ b/linux/home/.config/dunst/assets/ui/volume-low.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#eceff4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-volume"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon></svg> \ No newline at end of file
diff --git a/linux/home/.config/dunst/assets/ui/volume-medium.svg b/linux/home/.config/dunst/assets/ui/volume-medium.svg
new file mode 100644
index 0000000..6b3c1fa
--- /dev/null
+++ b/linux/home/.config/dunst/assets/ui/volume-medium.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#eceff4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-volume-1"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon><path d="M15.54 8.46a5 5 0 0 1 0 7.07"></path></svg> \ No newline at end of file
diff --git a/linux/home/.config/dunst/assets/ui/volume-muted.svg b/linux/home/.config/dunst/assets/ui/volume-muted.svg
new file mode 100644
index 0000000..50434d4
--- /dev/null
+++ b/linux/home/.config/dunst/assets/ui/volume-muted.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#BF616A" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-volume-x"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon><line x1="23" y1="9" x2="17" y2="15"></line><line x1="17" y1="9" x2="23" y2="15"></line></svg> \ No newline at end of file
diff --git a/linux/home/.config/dunst/dunstrc b/linux/home/.config/dunst/dunstrc
new file mode 100644
index 0000000..8989a2d
--- /dev/null
+++ b/linux/home/.config/dunst/dunstrc
@@ -0,0 +1,456 @@
+# See dunst(5) for all configuration options
+
+[global]
+ ### Display ###
+
+ # Which monitor should the notifications be displayed on.
+ monitor = 0
+
+ # Display notification on focused monitor. Possible modes are:
+ # mouse: follow mouse pointer
+ # keyboard: follow window with keyboard focus
+ # none: don't follow anything
+ #
+ # "keyboard" needs a window manager that exports the
+ # _NET_ACTIVE_WINDOW property.
+ # This should be the case for almost all modern window managers.
+ #
+ # If this option is set to mouse or keyboard, the monitor option
+ # will be ignored.
+ follow = none
+
+ ### Geometry ###
+
+ # dynamic width from 0 to 300
+ width = (200, 300)
+ # constant width of 300
+ # width = 300
+
+ # The maximum height of a single notification, excluding the frame.
+ height = 300
+
+ # Position the notification in the top right corner
+ origin = bottom-right
+
+ # Offset from the origin
+ offset = 6x30
+ # offset = 10x35 # i3 gaps = 20x20
+ # (H_Right_Gap/2) x (25+V_Bottom_Gap/2)
+
+ # Scale factor. It is auto-detected if value is 0.
+ scale = 0
+
+ # Maximum number of notification (0 means no limit)
+ notification_limit = 0
+
+ ### Progress bar ###
+
+ # Turn on the progess bar. It appears when a progress hint is passed with
+ # for example dunstify -h int:value:12
+ progress_bar = true
+
+ # Set the progress bar height. This includes the frame, so make sure
+ # it's at least twice as big as the frame width.
+ progress_bar_height = 10
+
+ # Set the frame width of the progress bar
+ progress_bar_frame_width = 1
+
+ # Set the minimum width for the progress bar
+ progress_bar_min_width = 150
+
+ # Set the maximum width for the progress bar
+ progress_bar_max_width = 300
+
+ # Show how many messages are currently hidden (because of
+ # notification_limit).
+ indicate_hidden = yes
+
+ # The transparency of the window. Range: [0; 100].
+ # This option will only work if a compositing window manager is
+ # present (e.g. xcompmgr, compiz, etc.). (X11 only)
+ transparency = 0
+
+ # Draw a line of "separator_height" pixel height between two
+ # notifications.
+ # Set to 0 to disable.
+ separator_height = 2
+
+ # Padding between text and separator.
+ padding = 11
+
+ # Horizontal padding.
+ horizontal_padding = 11
+
+ # Padding between text and icon.
+ text_icon_padding = 0
+
+ # Defines width in pixels of frame around the notification window.
+ # Set to 0 to disable.
+ frame_width = 2
+
+ # Define a color for the separator.
+ # possible values are:
+ # * auto: dunst tries to find a color fitting to the background;
+ # * foreground: use the same color as the foreground;
+ # * frame: use the same color as the frame;
+ # * anything else will be interpreted as a X color.
+ separator_color = "#1a1b26"
+
+ # Sort messages by urgency.
+ sort = yes
+
+ # Don't remove messages, if the user is idle (no mouse or keyboard input)
+ # for longer than idle_threshold seconds.
+ # Set to 0 to disable.
+ # A client can set the 'transient' hint to bypass this. See the rules
+ # section for how to disable this if necessary
+ idle_threshold = 120
+
+ ### Text ###
+
+ font = JetBrainsMono Nerd Font Medium 9
+
+ # The spacing between lines. If the height is smaller than the
+ # font height, it will get raised to the font height.
+ line_height = 5
+
+ # Possible values are:
+ # full: Allow a small subset of html markup in notifications:
+ # <b>bold</b>
+ # <i>italic</i>
+ # <s>strikethrough</s>
+ # <u>underline</u>
+ #
+ # For a complete reference see
+ # <https://docs.gtk.org/Pango/pango_markup.html>.
+ #
+ # strip: This setting is provided for compatibility with some broken
+ # clients that send markup even though it's not enabled on the
+ # server. Dunst will try to strip the markup but the parsing is
+ # simplistic so using this option outside of matching rules for
+ # specific applications *IS GREATLY DISCOURAGED*.
+ #
+ # no: Disable markup parsing, incoming notifications will be treated as
+ # plain text. Dunst will not advertise that it has the body-markup
+ # capability if this is set as a global setting.
+ #
+ # It's important to note that markup inside the format option will be parsed
+ # regardless of what this is set to.
+ markup = full
+
+ # The format of the message. Possible variables are:
+ # %a appname
+ # %s summary
+ # %b body
+ # %i iconname (including its path)
+ # %I iconname (without its path)
+ # %p progress value if set ([ 0%] to [100%]) or nothing
+ # %n progress value if set without any extra characters
+ # %% Literal %
+ # Markup is allowed
+ format = "<b>%s</b>\n%b"
+
+ # Alignment of message text.
+ # Possible values are "left", "center" and "right".
+ alignment = center
+
+ # Vertical alignment of message text and icon.
+ # Possible values are "top", "center" and "bottom".
+ vertical_alignment = center
+
+ # Show age of message if message is older than show_age_threshold
+ # seconds.
+ # Set to -1 to disable.
+ show_age_threshold = 60
+
+ # Specify where to make an ellipsis in long lines.
+ # Possible values are "start", "middle" and "end".
+ ellipsize = middle
+
+ # Ignore newlines '\n' in notifications.
+ ignore_newline = no
+
+ # Stack together notifications with the same content
+ stack_duplicates = true
+
+ # Hide the count of stacked notifications with the same content
+ hide_duplicate_count = true
+
+ # Display indicators for URLs (U) and actions (A).
+ show_indicators = yes
+
+ ### Icons ###
+
+ # Align icons left/right/off
+ icon_position = left
+
+ # Scale small icons up to this size, set to 0 to disable. Helpful
+ # for e.g. small files or high-dpi screens. In case of conflict,
+ # max_icon_size takes precedence over this.
+ min_icon_size = 0
+
+ # Scale larger icons down to this size, set to 0 to disable
+ max_icon_size = 32
+
+ # Paths to default icons.
+ #icon_path = /usr/share/icons/gnome/16x16/status/:/usr/share/icons/gnome/16x16/devices/
+ #icon_path = /usr/share/icons/Qogir/16/status:/usr/share/icons/Qogir/16/devices/:/usr/share/icons/Qogir/16/apps/:/usr/share/pixmaps/
+ icon_path = /usr/share/icons/gnome/16x16/status/:/usr/share/icons/gnome/16x16/devices/
+
+ ### History ###
+
+ # Should a notification popped up from history be sticky or timeout
+ # as if it would normally do.
+ sticky_history = yes
+
+ #history_file = ~/.cache/dunst/history
+
+ # Maximum amount of notifications kept in history
+ rhistory_length = 100
+
+ ### Misc/Advanced ###
+
+ # dmenu path.
+ dmenu = /usr/bin/dmenu -p dunst:
+
+ # Browser for opening urls in context menu.
+ browser = /usr/bin/brave
+
+ # Always run rule-defined scripts, even if the notification is suppressed
+ always_run_script = true
+
+ # Define the title of the windows spawned by dunst
+ title = Dunst
+
+ # Define the class of the windows spawned by dunst
+ class = Dunst
+
+ # Define the corner radius of the notification window
+ # in pixel size. If the radius is 0, you have no rounded
+ # corners.
+ # The radius will be automatically lowered if it exceeds half of the
+ # notification height to avoid clipping text and/or icons.
+ corner_radius = 10
+
+ # Ignore the dbus closeNotification message.
+ # Useful to enforce the timeout set by dunst configuration. Without this
+ # parameter, an application may close the notification sent before the
+ # user defined timeout.
+ ignore_dbusclose = false
+
+ ### Wayland ###
+ # These settings are Wayland-specific. They have no effect when using X11
+
+ # Uncomment this if you want to let notications appear under fullscreen
+ # applications (default: overlay)
+ # layer = top
+
+ # Set this to true to use X11 output on Wayland.
+ force_xwayland = false
+
+ ### Legacy
+
+ # Use the Xinerama extension instead of RandR for multi-monitor support.
+ # This setting is provided for compatibility with older nVidia drivers that
+ # do not support RandR and using it on systems that support RandR is highly
+ # discouraged.
+ #
+ # By enabling this setting dunst will not be able to detect when a monitor
+ # is connected or disconnected which might break follow mode if the screen
+ # layout changes.
+ force_xinerama = false
+
+ ### mouse
+
+ # Defines list of actions for each mouse event
+ # Possible values are:
+ # * none: Don't do anything.
+ # * do_action: Invoke the action determined by the action_name rule. If there is no
+ # such action, open the context menu.
+ # * open_url: If the notification has exactly one url, open it. If there are multiple
+ # ones, open the context menu.
+ # * close_current: Close current notification.
+ # * close_all: Close all notifications.
+ # * context: Open context menu for the notification.
+ # * context_all: Open context menu for all notifications.
+ # These values can be strung together for each mouse event, and
+ # will be executed in sequence.
+ mouse_left_click = close_current
+ mouse_middle_click = do_action, close_current
+ mouse_right_click = close_all
+
+# Experimental features that may or may not work correctly. Do not expect them
+# to have a consistent behaviour across releases.
+[experimental]
+ # Calculate the dpi to use on a per-monitor basis.
+ # If this setting is enabled the Xft.dpi value will be ignored and instead
+ # dunst will attempt to calculate an appropriate dpi value for each monitor
+ # using the resolution and physical size. This might be useful in setups
+ # where there are multiple screens with very different dpi values.
+ per_monitor_dpi = false
+
+# Every section that isn't one of the above is interpreted as a rules to
+# override settings for certain messages.
+#
+# Messages can be matched by
+# appname (discouraged, see desktop_entry)
+# body
+# category
+# desktop_entry
+# icon
+# match_transient
+# msg_urgency
+# stack_tag
+# summary
+#
+# and you can override the
+# background
+# foreground
+# format
+# frame_color
+# fullscreen
+# new_icon
+# set_stack_tag
+# set_transient
+# set_category
+# timeout
+# urgency
+# skip_display
+# history_ignore
+# action_name
+# word_wrap
+# ellipsize
+# alignment
+#
+# Shell-like globbing will get expanded.
+#
+# Instead of the appname filter, it's recommended to use the desktop_entry filter.
+# GLib based applications export their desktop-entry name. In comparison to the appname,
+# the desktop-entry won't get localized.
+#
+# SCRIPTING
+# You can specify a script that gets run when the rule matches by
+# setting the "script" option.
+# The script will be called as follows:
+# script appname summary body icon urgency
+# where urgency can be "LOW", "NORMAL" or "CRITICAL".
+#
+# NOTE: It might be helpful to run dunst -print in a terminal in order
+# to find fitting options for rules.
+
+# Disable the transient hint so that idle_threshold cannot be bypassed from the
+# client
+#[transient_disable]
+# match_transient = yes
+# set_transient = no
+#
+# Make the handling of transient notifications more strict by making them not
+# be placed in history.
+#[transient_history_ignore]
+# match_transient = yes
+# history_ignore = yes
+
+# fullscreen values
+# show: show the notifications, regardless if there is a fullscreen window opened
+# delay: displays the new notification, if there is no fullscreen window active
+# If the notification is already drawn, it won't get undrawn.
+# pushback: same as delay, but when switching into fullscreen, the notification will get
+# withdrawn from screen again and will get delayed like a new notification
+#[fullscreen_delay_everything]
+# fullscreen = delay
+[fullscreen_show_critical]
+ msg_urgency = critical
+ fullscreen = show
+[fullscreen_show_normal]
+ msg_urgency = normal
+ fullscreen = show
+
+
+#[espeak]
+# summary = "*"
+# script = dunst_espeak.sh
+
+#[script-test]
+# summary = "*script*"
+# script = dunst_test.sh
+
+#[ignore]
+# # This notification will not be displayed
+# summary = "foobar"
+# skip_display = true
+
+#[history-ignore]
+# # This notification will not be saved in history
+# summary = "foobar"
+# history_ignore = yes
+
+#[skip-display]
+# # This notification will not be displayed, but will be included in the history
+# summary = "foobar"
+# skip_display = yes
+
+#[signed_on]
+# appname = Pidgin
+# summary = "*signed on*"
+# urgency = low
+#
+#[signed_off]
+# appname = Pidgin
+# summary = *signed off*
+# urgency = low
+#
+#[says]
+# appname = Pidgin
+# summary = *says*
+# urgency = critical
+#
+#[twitter]
+# appname = Pidgin
+# summary = *twitter.com*
+# urgency = normal
+#
+
+[openEwwPopup]
+ script = ~/.config/dunst/scripts/openEwwPopup.sh
+
+[songArtLogger]
+ script = ~/.config/dunst/scripts/songArtLogger.sh
+
+[stack-volumes]
+ appname = "some_volume_notifiers"
+ set_stack_tag = "volume"
+
+[flameshot-urgency]
+ appname = flameshot
+ urgency = low
+
+[whatsapp-alignment]
+ appname = whatsapp-nativefier-d40211
+ alignment = left
+
+[discord-alignment]
+ appname = discord
+ alignment = left
+
+[signal-alignment]
+ appname = Signal
+ alignment = left
+
+[urgency_low]
+ background = "#16161e"
+ foreground = "#c0caf5"
+ frame_color = "#1f2335"
+ timeout = 3
+
+[urgency_normal]
+ background = "#16161e"
+ foreground = "#c0caf5"
+ frame_color = "#3d59a1"
+ timeout = 15
+
+[urgency_critical]
+ background = "#191D24"
+ foreground = "#c0caf5"
+ frame_color = "#db4b4b"
+ timeout = 60
diff --git a/linux/home/.config/dunst/scripts/openEwwPopup.sh b/linux/home/.config/dunst/scripts/openEwwPopup.sh
new file mode 100755
index 0000000..d22e981
--- /dev/null
+++ b/linux/home/.config/dunst/scripts/openEwwPopup.sh
@@ -0,0 +1,36 @@
+#!/bin/bash
+
+DND_LOCK_FILE="$HOME/.cache/dnd-lock.lock"
+EWW_BIN="$HOME/.local/bin/eww"
+
+finish() {
+ ${EWW_BIN} update noti=false; sleep 0.075
+ ${EWW_BIN} close notification-popup
+}
+
+# Run eww daemon if not running
+if [[ ! `pidof eww` ]]; then
+ ${EWW_BIN} daemon
+ sleep 1
+else
+ if [[ ! -f "$DND_LOCK_FILE" ]]; then
+ KILLED=false
+ for pid in $(pidof -x openEwwPopup.sh); do
+ if [ $pid != $$ ]; then
+ kill -9 $pid
+ KILLED=true
+ fi
+ done >/dev/nullx
+
+ if ! $KILLED; then
+ sleep 0.5
+ ${EWW_BIN} update noti=true
+ ${EWW_BIN} open notification-popup
+ canberra-gtk-play -i message
+ fi
+
+ sleep 5
+ finish
+ unset KILLED
+ fi
+fi
diff --git a/linux/home/.config/dunst/scripts/songArtLogger.sh b/linux/home/.config/dunst/scripts/songArtLogger.sh
new file mode 100755
index 0000000..f73beb4
--- /dev/null
+++ b/linux/home/.config/dunst/scripts/songArtLogger.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+TMP_DIR="$HOME/.cache/dunst"
+TMP_COVER_PATH="$TMP_DIR/$DUNST_SUMMARY.png"
+TMP_TEMP_PATH="$TMP_DIR/temp.png"
+
+if [ ! -d "$TMP_DIR" ]; then
+ mkdir -p "$TMP_DIR"
+fi
+
+ART_FROM_SPOTIFY="$(playerctl -p %any,spotify metadata mpris:artUrl | sed -e 's/open.spotify.com/i.scdn.co/g')"
+
+if [[ $(playerctl -p spotify,%any,firefox,chromium,brave,mpd metadata mpris:artUrl) ]]; then
+ curl -s "$ART_FROM_SPOTIFY" --output "$TMP_COVER_PATH"
+fi
+
+cp "$TMP_TEMP_PATH" "$TMP_COVER_PATH" \ No newline at end of file
diff --git a/linux/home/.config/eww/eww.scss b/linux/home/.config/eww/eww.scss
new file mode 100644
index 0000000..9405a6a
--- /dev/null
+++ b/linux/home/.config/eww/eww.scss
@@ -0,0 +1,11 @@
+@import "./src/scss/variables";
+@import "./src/scss/overrides";
+@import "./src/scss/modules";
+
+@import "./src/scss/bar/index.scss";
+@import "./src/scss/control-center/index.scss";
+@import "./src/scss/exitscreen/index.scss";
+@import "./src/scss/info-center/index.scss";
+@import "./src/scss/lockscreen/index.scss";
+@import "./src/scss/notification-center/index.scss";
+@import "./src/scss/notification-popup/index.scss";
diff --git a/linux/home/.config/eww/eww.yuck b/linux/home/.config/eww/eww.yuck
new file mode 100644
index 0000000..191b30c
--- /dev/null
+++ b/linux/home/.config/eww/eww.yuck
@@ -0,0 +1,23 @@
+(include "./src/yuck/_variables.yuck")
+(include "./src/yuck/_modules.yuck")
+
+(include "./src/yuck/bar/_widgets.yuck")
+(include "./src/yuck/bar/_windows.yuck")
+
+(include "./src/yuck/control-center/_widgets.yuck")
+(include "./src/yuck/control-center/_windows.yuck")
+
+(include "./src/yuck/exitscreen/_widgets.yuck")
+(include "./src/yuck/exitscreen/_windows.yuck")
+
+(include "./src/yuck/info-center/_widgets.yuck")
+(include "./src/yuck/info-center/_windows.yuck")
+
+(include "./src/yuck/lockscreen/_widgets.yuck")
+(include "./src/yuck/lockscreen/_windows.yuck")
+
+(include "./src/yuck/notification-center/_widgets.yuck")
+(include "./src/yuck/notification-center/_windows.yuck")
+
+(include "./src/yuck/notification-popup/_widgets.yuck")
+(include "./src/yuck/notification-popup/_windows.yuck")
diff --git a/linux/home/.config/hypr/autostart b/linux/home/.config/hypr/autostart
new file mode 100755
index 0000000..2805e42
--- /dev/null
+++ b/linux/home/.config/hypr/autostart
@@ -0,0 +1,34 @@
+#!/usr/bin/bash
+
+# Policy Authentication Agent
+/usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1 &
+
+eval "$(/usr/bin/gnome-keyring-daemon --start --components=gpg,pkcs11,secrets,ssh)"
+export "$(gnome-keyring-daemon --start --components=gpg,pkcs11,secrets,ssh)"
+
+# Setup Environment
+systemctl --user import-environment WAYLAND_DISPLAY XDG_CURRENT_DESKTOP &
+dbus-update-activation-environment --systemd DISPLAY WAYLAND_DISPLAY XDG_CURRENT_DESKTOP --all &
+#dbus-update-activation-environment DISPLAY XAUTHORITY WAYLAND_DISPLAY &
+
+# variables
+scripts=~/.scripts
+
+# gsettings
+#gsettings set org.gnome.desktop.interface gtk-theme 'Tokyonight-Dark-BL-LB'
+#gsettings set org.gnome.desktop.interface font-name 'CaskaydiaCove Nerd Font 9'
+#gsettings set org.gnome.desktop.interface icon-theme 'Tokyonight-Moon'
+#gsettings set org.gnome.desktop.interface cursor-theme 'Sweet-cursors'
+
+# For nemo
+gsettings set org.cinnamon.desktop.default-applications.terminal exec wezterm
+
+# music daemon
+mpd &
+
+# other
+hyprctl setcursor Sweet-cursors 24
+wl-paste --watch cliphist store &
+notify-send -a aurora "hello $(whoami)" &
+sleep 2
+mpd-mpris &
diff --git a/linux/home/.config/hypr/hyprland.conf b/linux/home/.config/hypr/hyprland.conf
new file mode 100644
index 0000000..620dc55
--- /dev/null
+++ b/linux/home/.config/hypr/hyprland.conf
@@ -0,0 +1,175 @@
+# Sourcing external config files
+source=~/.config/hypr/user/monitors.conf
+source=~/.config/hypr/user/exec.conf
+source=~/.config/hypr/user/env.conf
+source=~/.config/hypr/user/binds.conf
+source=~/.config/hypr/user/window_rules.conf
+
+
+# Defaults
+$term = wezterm
+$browser = firefox
+#$gmail = firefox --new-instance -P app "https://mail.google.com/" --class appProfile
+$editor = nvim
+#$explorer = nemo
+#$music = g4music
+$notepad = code --profile notepad --unity-launch ~/Templates
+$launcher = wofi --show drun -n
+$launcher_alt = wofi --show run -n
+#$discord = discord
+#env = GTK_THEME,Breeze-Dark
+
+
+general {
+ gaps_in = 5
+ gaps_out = 5
+ border_size = 2
+ col.active_border = rgba(cba6f7ff) rgba(89b4faff) rgba(94e2d5ff) 10deg
+ col.inactive_border = 0xff313244
+ # whether to apply the sensitivity to raw input (e.g. used by games where you aim using your mouse)
+ apply_sens_to_raw = 0
+ layout = "master";
+ resize_on_border = yes
+ extend_border_grab_area = 20
+}
+
+
+input {
+ kb_layout = custom-us
+ #sensitivity = 0.75 # for mouse cursor
+ sensitivity = 0
+ follow_mouse = 0
+ scroll_method = 2fg
+
+ touchpad {
+ natural_scroll = false
+ disable_while_typing = true
+ tap-to-click = true
+ }
+}
+
+
+decoration {
+ rounding = 10
+ active_opacity = 0.95
+ inactive_opacity = 0.9
+ fullscreen_opacity = 0.95
+
+ dim_inactive = false
+ dim_strength = 0.05
+
+ blur {
+ enabled = true
+ #enabled = false
+ size = 3
+ passes = 1
+
+ vibrancy = 0.1696
+ }
+
+ drop_shadow = true
+ shadow_range = 4
+ shadow_render_power = 3
+ col.shadow = rgba(1a1a1aee)
+ #blur = true
+ #blur_size = 5
+ #blur_passes = 4
+ #blur_new_optimizations = true
+ #blur_xray = true
+ #blur_ignore_opacity = true
+
+ #drop_shadow = true
+ #shadow_ignore_window = true
+ #shadow_range = 20
+ #shadow_render_power = 3
+ #col.shadow = 0x55161925
+ col.shadow_inactive = 0x22161925
+ # Your blur "amount" is blur_size * blur_passes, but high blur_size (over around 5-ish) will produce artifacts.
+ # if you want heavy blur, you need to up the blur_passes.
+ # the more passes, the more you can up the blur_size without noticing artifacts.
+
+ # Blurring layerSurfaces
+ # blurls = gtk-layer-shell
+ # blurls = waybar
+ # blurls = lockscreen
+ blurls = rofi
+ blurls = wofi
+ blurls = firefox
+}
+
+
+animations {
+ enabled = true
+ # bezier = overshot, 0.05, 0.9, 0.1, 1.1
+ bezier = overshot, 0.13, 0.99, 0.29, 1.1
+ animation = windows, 1, 4, overshot, slide
+ animation = border, 1, 10, default
+ animation = fade, 1, 10, default
+ animation = workspaces, 1, 6, overshot, slidevert
+}
+
+
+dwindle {
+ pseudotile = true # enable pseudotiling on dwindle
+ force_split = 0
+ #col.group_border = 0xff89dceb
+ #col.group_border_active = 0xfff9e2af
+ preserve_split = true
+}
+
+
+master {
+ new_on_top = true
+ no_gaps_when_only = false
+}
+
+
+gestures {
+ workspace_swipe = true
+ workspace_swipe_invert = false
+ workspace_swipe_fingers = 3
+}
+
+
+misc {
+ disable_hyprland_logo = true
+
+ focus_on_activate = true
+
+ enable_swallow = true
+ #swallow_regex = ^(scratchpad)$
+}
+
+
+binds {
+ allow_workspace_cycles = true
+}
+
+
+custom {
+
+}
+
+
+layerrule = blur, bar0
+layerrule = noanim, bar0
+layerrule = ignorealpha 0.2, bar0
+layerrule = blur, dock0
+layerrule = noanim, dock0
+layerrule = ignorealpha 0.2, dock0
+layerrule = blur, indicator0
+layerrule = ignorealpha 0.2, indicator0
+layerrule = blur, toolbox0
+layerrule = noanim, toolbox0
+layerrule = ignorealpha 0.2, toolbox0
+layerrule = blur, applauncher
+layerrule = ignorealpha 0.2, applauncher
+layerrule = blur, datemenu
+layerrule = ignorealpha 0.2, datemenu
+layerrule = blur, quicksettings
+layerrule = ignorealpha 0.2, quicksettings
+layerrule = blur, wlroots
+layerrule = ignorealpha 0.2, wlroots
+layerrule = blur, notifications0
+layerrule = ignorealpha 0.2, notifications0
+
diff --git a/linux/home/.config/hypr/scripts/move-scratchpad.sh b/linux/home/.config/hypr/scripts/move-scratchpad.sh
new file mode 100755
index 0000000..a8d0731
--- /dev/null
+++ b/linux/home/.config/hypr/scripts/move-scratchpad.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+# Number of pixels to move down
+pixels_to_move=10
+
+# Get the PID of each scratchpad window and move it down
+for pid in "$(hyprctl -j clients | jq -r '.[] | select(.class == "scratchpad") | .pid')"; do
+ hyprctl dispatch movewindowpixel 0 "$pixels_to_move",pid:"$pid"
+done
diff --git a/linux/home/.config/hypr/user/binds.conf b/linux/home/.config/hypr/user/binds.conf
new file mode 100644
index 0000000..a337a0e
--- /dev/null
+++ b/linux/home/.config/hypr/user/binds.conf
@@ -0,0 +1,167 @@
+# Window Manager keybindings
+
+# Scratchpad
+bind = SUPER, semicolon, exec, ~/.scripts/scratchpad && bash ~/.config/hypr/scripts/move-scratchpad.sh
+bind = SUPER, X, exec, ~/.scripts/scratchpad && bash ~/.config/hypr/scripts/move-scratchpad.sh
+#bind = SUPER, semicolon, exec, ~/.scripts/scratchpad
+#bind = SUPER, X, exec, ~/.scripts/scratchpad
+
+# HUD
+bind = SUPER, E, exec, ~/.scripts/heads-up-display
+
+# Package manager Terminal
+bind = SUPER, Q, exec, ~/.scripts/pac
+
+# Mouse binds
+bindm = SUPER, mouse:272, movewindow
+bindm = SUPER, mouse:273, resizewindow
+bind = SUPER, mouse_down, workspace, e-1
+bind = SUPER, mouse_up, workspace, e+1
+
+# Screenshot binds
+bind = , Print, exec,~/.scripts/screenshot_full
+bind = ALT, Print, exec,~/.scripts/screenshot
+
+# Application binds
+$term=wezterm
+bind = SUPER, T, exec, $term -e tmux new-session -A -s term
+bind = SUPER, Enter, exec, $term -e tmux new-session -A -s term
+bind = SUPER, W, exec, $browser
+#bind = SUPER, G, exec, $notepad
+
+# ags
+bind = SUPER_SHIFT, R, exec, ags -q; ags; notify-send "ags reloaded"
+bind=SUPER, Space, exec, ags -t launcher
+bind=,XF86PowerOff, exec, ags -t powermenu
+bind=SUPER, escape, exec, ags -t powermenu
+bind=SUPER, Tab, exec, ags -t overview
+#bind = SUPER, Space, exec, ags -r "toggleLauncher()"
+
+# Clipboard
+bind = SUPER, V, exec, pkill wofi || cliphist list | wofi --dmenu -p clippick -l top_right -x -15 -y 10 -n | cliphist decode | wl-copy
+
+
+# Hyprland keys
+bind=SUPER_SHIFT,Escape,exec,hyprctl reload; notify-send "Config Reloaded"
+bind = SUPER, D, killactive,
+#bind = SUPER_SHIFT, S, movetoworkspace,special
+#bind = SUPER, S, togglespecialworkspace,
+
+
+# Other dispatchers
+bind = ALT, Tab, cyclenext
+bind = SUPER, C, cyclenext
+bind = SUPER, F, fullscreen
+#bind = SUPER, T, exec, hyprctl dispatch centerwindow none
+bind = SUPER, M, fullscreen, 1
+bind = SUPER_SHIFT, M, fakefullscreen
+
+
+bind = SUPER, P, pseudo,
+bind = SUPER, S, togglefloating,
+bind = SUPER_SHIFT, G, togglegroup,
+bind = SUPER, tab, changegroupactive,
+bind = SUPER_SHIFT, I, togglesplit, # dwindle
+
+
+# Move window with SUPER + Shift + arrow keys
+# [↑]
+# [←] [↓] [→]
+bind = SUPER_SHIFT, left, movewindow, l
+bind = SUPER_SHIFT, right, movewindow, r
+bind = SUPER_SHIFT, up, movewindow, u
+bind = SUPER_SHIFT, down, movewindow, d
+bind = SUPER_SHIFT, H, movewindow, l
+bind = SUPER_SHIFT, L, movewindow, r
+bind = SUPER_SHIFT, K, movewindow, u
+bind = SUPER_SHIFT, J, movewindow, d
+
+# Move window focus with SUPER + arrow keys
+# [↑]
+# [←] [↓] [→]
+bind = SUPER, left, movefocus, l
+bind = SUPER, right, movefocus, r
+bind = SUPER, up, movefocus, u
+bind = SUPER, down, movefocus, d
+bind = SUPER, H, movefocus, l
+bind = SUPER, L, movefocus, r
+bind = SUPER, K, movefocus, u
+bind = SUPER, J, movefocus, d
+
+# Move To a workspaces
+bind = SUPER, 1, workspace, 1
+bind = SUPER, 2, workspace, 2
+bind = SUPER, 3, workspace, 3
+bind = SUPER, 4, workspace, 4
+bind = SUPER, 5, workspace, 5
+bind = SUPER, 6, workspace, 6
+bind = SUPER, 7, workspace, 7
+bind = SUPER, 8, workspace, 8
+bind = SUPER, 9, workspace, 9
+
+# Move windows between workspaces
+bind = SUPER_SHIFT, 1, movetoworkspace, 1
+bind = SUPER_SHIFT, 2, movetoworkspace, 2
+bind = SUPER_SHIFT, 3, movetoworkspace, 3
+bind = SUPER_SHIFT, 4, movetoworkspace, 4
+bind = SUPER_SHIFT, 5, movetoworkspace, 5
+bind = SUPER_SHIFT, 6, movetoworkspace, 6
+bind = SUPER_SHIFT, 7, movetoworkspace, 7
+bind = SUPER_SHIFT, 8, movetoworkspace, 8
+bind = SUPER_SHIFT, 9, movetoworkspace, 9
+
+
+# Use this to get thw XF86 bind (FN + Fx combination) for your keyboard
+# xev | grep -A2 --line-buffered '^KeyRelease' | sed -n '/keycode /s/^.*keycode \([0-9]*\).* (.*, \(.*\)).*$/\1 \2/p'
+
+# Volume Control
+#binde = , XF86AudioRaiseVolume, exec, pactl set-sink-volume @DEFAULT_SINK@ +1%
+#binde = , XF86AudioLowerVolume, exec, pactl set-sink-volume @DEFAULT_SINK@ -1%
+bind = , XF86AudioMute, exec, pactl set-sink-mute @DEFAULT_SINK@ toggle
+bind = , XF86AudioMicMute, exec, pactl set-source-mute @DEFAULT_SOURCE@ toggle
+bind = ALT, down, exec, pactl set-sink-volume @DEFAULT_SINK@ -5%
+bind = ALT, up, exec, pactl set-sink-volume @DEFAULT_SINK@ +5%
+
+# Media Control
+bind = , XF86AudioMedia, exec, playerctl play-pause
+bind = , XF86AudioPlay, exec, playerctl play-pause
+bind = , XF86AudioStop, exec, playerctl stop
+bind = , XF86AudioPrev, exec, playerctl previous
+bind = , XF86AudioNext, exec, playerctl next
+
+# Use arrow keys as multimedia keys
+bind = ALT_SHIFT, left, exec, playerctl previous
+bind = ALT_SHIFT, up, exec, playerctl play-pause
+bind = ALT_SHIFT, right, exec, playerctl next
+
+# Brightness Control
+binde = , XF86MonBrightnessUp, exec, light -A 1
+binde = , XF86MonBrightnessDown, exec, light -U 1
+
+bind = ALT, left, exec, brightnessctl set 10%- # Screen brightness down FN+F7
+bind = ALT, right, exec, brightnessctl set 10%+ # Screen brightness up FN+F8
+
+# Screensaver key
+bind = , XF86ScreenSaver, exec,~/.scripts/lock
+
+bind = SUPER_SHIFT, Y, exec, spotify
+bind = SUPER_SHIFT, D, exec, discord
+bind = SUPER , W, exec, firefox
+bind = SUPER_SHIFT, B, exec, rofi-rbw
+
+# Others
+#bind = , XF86Mail, exec,$gmail
+#bind = , XF86HomePage, exec, $explorer ~/
+#bind = , XF86Calculator, exec, qalculate-gtk
+#bind = , XF86Search, exec, wofi
+
+$mainMod = SUPER
+bind = $mainMod, tilde, exec, ~/.scripts/translate.sh
+bind = $mainMod CONTROL, k, exec, ~/.scripts/killmenu
+
+
+
+# trigger when the switch is turning on
+# bindl = , switch:on:Lid Switch, exec, hyprctl keyword monitor ", 1920x1080@60, auto, 1"
+# trigger when the switch is turning off
+bindl = , switch:off:Lid Switch, exec, playerctl --all-players stop; ~/.scripts/lock; systemctl suspend
diff --git a/linux/home/.config/hypr/user/env.conf b/linux/home/.config/hypr/user/env.conf
new file mode 100644
index 0000000..93f7694
--- /dev/null
+++ b/linux/home/.config/hypr/user/env.conf
@@ -0,0 +1,23 @@
+#Environment Variables for the Hyprland session
+
+# XDG
+env = XDG_CURRENT_DESKTOP,Hyprland
+env = XDG_SESSION_TYPE,wayland
+env = XDG_SESSION_DESKTOP,Hyprland
+
+# QT
+env = QT_AUTO_SCREEN_SCALE_FACTOR,1
+env = QT_QPA_PLATFORM=wayland;xcb # Not yet working for onlyoffice-editor
+env = QT_WAYLAND_DISABLE_WINDOWDECORATION,1
+env = QT_QPA_PLATFORMTHEME,qt6ct
+
+# GTK
+#env = GDK_BACKEND=wayland
+
+# Toolkit
+#env = SDL_VIDEODRIVER,wayland
+env = _JAVA_AWT_WM_NONEREPARENTING,1
+env = CLUTTER_BACKEND,wayland
+env = GDK_BACKEND,wayland,x11
+env = MOZ_ENABLE_WAYLAND,1
+
diff --git a/linux/home/.config/hypr/user/exec.conf b/linux/home/.config/hypr/user/exec.conf
new file mode 100644
index 0000000..22846d9
--- /dev/null
+++ b/linux/home/.config/hypr/user/exec.conf
@@ -0,0 +1,4 @@
+exec-once=$HOME/.config/hypr/autostart
+exec-once = swww kill; swww init --format xrgb
+exec = ags
+exec =$HOME/.scripts/move-qemu.sh
diff --git a/linux/home/.config/hypr/user/monitors.conf b/linux/home/.config/hypr/user/monitors.conf
new file mode 100644
index 0000000..7464786
--- /dev/null
+++ b/linux/home/.config/hypr/user/monitors.conf
@@ -0,0 +1,4 @@
+monitor=auto,preferred,auto, 1, bitdepth,10
+#monitor=, 1920x1080@60, auto, 1
+#monitor=, 1366x768@60, auto, 1
+#monitor=LVDS-1, 1366x768@60, auto, 1, bitdepth,10
diff --git a/linux/home/.config/hypr/user/window_rules.conf b/linux/home/.config/hypr/user/window_rules.conf
new file mode 100644
index 0000000..3964de2
--- /dev/null
+++ b/linux/home/.config/hypr/user/window_rules.conf
@@ -0,0 +1,70 @@
+# {{@@ header() @@}}
+# vim:fileencoding=utf-8:ft=conf:foldmethod=marker
+
+# Workspaces
+windowrulev2 = workspace 1 silent, class:firefox
+windowrulev2 = workspace 4 silent, class:discord
+windowrulev2 = workspace 5 silent, class:Spotify
+
+# Scratchpad
+$scratchpad = class:^(scratchpad)$
+windowrulev2 = opacity 1 0.9,class:^(scratchpad)$
+windowrulev2 = float,$scratchpad
+#windowrule = float,^(scratchpad)$
+#windowrule = move 15 40, ^(scratchpad)$
+#windowrule = move center,^(scratchpad)$
+#$scratchpadsize = size 98% 93%
+#windowrulev2 = tile,$scratchpad
+#windowrulev2 = pin,$scratchpad
+#windowrulev2=windowdance,$scratchpad
+#windowrulev2 = move 100%-20,$scratchpad
+#windowrulev2 = $scratchpadsize,$scratchpad
+
+# HUD
+windowrule = float, ^(heads-up-display)$
+windowrule = pin, ^(heads-up-display)$
+windowrule = size 325 160, ^(heads-up-display)$
+windowrule = move 1020 50, ^(heads-up-display)$
+
+# Package manager Terminal
+windowrule = float, ^(pac)$
+windowrule = pin, ^(pac)$
+windowrule = size 325 160, ^(pac)$
+windowrule = move 50 50, ^(pac)$
+
+# Picture-in-Picture
+windowrulev2 = idleinhibit fullscreen, title:^Picture-in-Picture$
+windowrulev2 = float, title:^Picture-in-Picture$
+windowrulev2 = pin, title:^Picture-in-Picture$
+windowrulev2 = move 920 480, title:^Picture-in-Picture$
+windowrulev2 = size 425 260, title:^Picture-in-Picture$
+
+# Firefox
+windowrulev2 = float, class:^(firefox)$, title:^(Firefox — Sharing Indicator)$
+windowrulev2 = opacity 1 1,class:^(firefox)$
+
+# Applications
+windowrule = float,^(rlr)$
+windowrule = float,^(pavucontrol)$
+windowrule = float,^(blueman-manager)$
+windowrule = float,^(nm-connection-editor)$
+windowrule = float,^(mediainfo-gui)$
+windowrulev2 = float, class:^(nemo)$, title:^(.*Properties)$
+windowrulev2 = float, class:^(Nemo-preview-start)$
+windowrulev2 = move 100%-433 53, class:^(wofi)$, title:^(clippick)$
+windowrulev2 = animation popin, class:^(wlogout)$, title:^(wlogout)$
+windowrulev2 = float, class:^(wlogout)$, title:^(wlogout)$
+windowrulev2 = animation slide, class:^(wofi)$
+#windowrulev2 = float, class:^(steam)$
+windowrule = float,^(com.github.neithern.g4music)$
+windowrule = size 670 635,^(com.github.neithern.g4music)$
+windowrule = move center,^(com.github.neithern.g4music)$
+windowrulev2 = opacity 0.0 override,class:^(xwaylandvideobridge)$
+windowrulev2 = noanim,class:^(xwaylandvideobridge)$
+windowrulev2 = noinitialfocus,class:^(xwaylandvideobridge)$
+windowrulev2 = maxsize 1 1,class:^(xwaylandvideobridge)$
+windowrulev2 = noblur,class:^(xwaylandvideobridge)$
+
+# Blur
+windowrule = noblur,^(firefox)$ # disables blur for firefox
+windowrule = noblur,^(scratchpad)$ # disables blur for firefox
diff --git a/linux/home/.config/inputrc b/linux/home/.config/inputrc
new file mode 100644
index 0000000..adfeec4
--- /dev/null
+++ b/linux/home/.config/inputrc
@@ -0,0 +1,86 @@
+$include /etc/inputrc
+
+"\f": clear-screen
+
+set bell-style none
+set meta-flag on
+set input-meta on
+set convert-meta off
+set output-meta on
+set show-all-if-ambiguous on # set show-all-if-unmodified on
+
+# Color files by types
+# Note that this may cause completion text blink in some terminals (e.g. xterm).
+set colored-stats On
+# Append char to indicate type
+set visible-stats On
+# Mark symlinked directories
+set mark-symlinked-directories On
+# Color the common prefix
+set colored-completion-prefix On
+# Color the common prefix in menu-complete
+set menu-complete-display-prefix On
+
+# set editing-mode vi
+set show-mode-in-prompt on
+set keyseq-timeout 0 # Reduce the delay between pressing escape and the cursor change
+set vi-cmd-mode-string "\1\e[2 q\2"
+set vi-ins-mode-string "\1\e[6 q\2"
+
+$if mode=vi
+ set keymap vi-command
+ # these are for vi-command mode
+ "\e[A": history-search-backward
+ "\e[B": history-search-forward
+ j: history-search-forward
+ k: history-search-backward
+ set keymap vi-insert
+ # these are for vi-insert mode
+ "\e[A": history-search-backward
+ "\e[B": history-search-forward
+ "jk" # escape
+$endif
+
+$if mode=emacs
+
+ "\C-P": history-search-backward
+ "\C-N": history-search-forward
+
+ # for linux console and RH/Debian xterm
+ "\e[1~": beginning-of-line
+ "\e[4~": end-of-line
+ "\e[5~": beginning-of-history
+ "\e[6~": end-of-history
+ "\e[7~": beginning-of-line
+ "\e[3~": delete-char
+ "\e[2~": quoted-insert
+ "\e[5C": forward-word
+ "\e[5D": backward-word
+ "\e\e[C": forward-word
+ "\e\e[D": backward-word
+ "\e[1;5C": forward-word
+ "\e[1;5D": backward-word
+
+ # for rxvt
+ "\e[8~": end-of-line
+
+ # for non RH/Debian xterm, can't hurt for RH/DEbian xterm
+ "\eOH": beginning-of-line
+ "\eOF": end-of-line
+
+ # for freebsd console
+ "\e[H": beginning-of-line
+ "\e[F": end-of-line
+
+$endif
+
+#set editing-mode emacs
+set editing-mode vi
+
+# # switch between vi or emacs
+# set keymap emacs
+# "\e[": vi-editing-mode
+# set keymap vi-insert
+# "\e[": emacs-editing-mode
+# set keymap vi-command
+# "\e[": emacs-editing-mode
diff --git a/linux/home/.config/jgmenu/jgmenurc b/linux/home/.config/jgmenu/jgmenurc
new file mode 100644
index 0000000..d35eee6
--- /dev/null
+++ b/linux/home/.config/jgmenu/jgmenurc
@@ -0,0 +1,32 @@
+stay_alive = 1
+tint2_look = 0
+position_mode = pointer
+terminal_exec = wezterm
+terminal_args = -x
+sub_spacing = 5
+menu_width = 200
+menu_padding_top = 5
+menu_padding_right = 2
+menu_padding_bottom = 5
+menu_padding_left = 2
+menu_radius = 7
+menu_border = 0
+menu_halign = left
+sub_hover_action = 1
+item_margin_y = 5
+item_height = 30
+item_padding_x = 8
+item_radius = 6
+item_border = 0
+sep_height = 5
+font = Roboto Medium 15px
+icon_theme = Papirus
+icon_size = 24
+color_menu_bg = #171B20 80
+color_menu_border = #c0caf5
+color_norm_bg = #171B20 0
+color_norm_fg = #b6beca 100
+color_sel_bg = #74bee9 50
+color_sel_fg = #b6beca 100
+color_sep_fg = b6beca 80
+csv_cmd = cat ~/.config/jgmenu/menu.csv
diff --git a/linux/home/.config/jgmenu/menu.csv b/linux/home/.config/jgmenu/menu.csv
new file mode 100644
index 0000000..dbece5a
--- /dev/null
+++ b/linux/home/.config/jgmenu/menu.csv
@@ -0,0 +1,32 @@
+Terminal,scratchpad,utilities-terminal
+Web-browser,firefox,web-browser
+Neovim,kitty -e nvim,nvim
+Ranger,wezterm -e ranger,stock_folder
+^sep()
+
+Extras,^checkout(extras),add
+^sep()
+
+Critical,^checkout(critical),gtk-dialog-warning
+^sep()
+
+Lock,betterlockscreen --lock,system-lock-screen
+Logout,pkill -KILL -u "$USER" &,system-log-out
+
+Exit,^checkout(exit),exit
+
+^tag(exit)
+Suspend,systemctl suspend,system-suspend
+Reboot,reboot,system-reboot
+Poweroff,poweroff,system-shutdown
+
+^tag(extras)
+Change wallpaper,nitrogen,nitrogen
+Randomize Wallpaper,random-wall
+Control-center,~/.config/eww/scripts/openControlCenter.sh
+Reload eww,pkill -f eww && eww daemon
+
+^tag(critical)
+Restart BSPWM,control_box -bspres
+Restart SXHKD,control_box -kbres,input-keyboard
+Restart Pipewire,control_box -soundres,audio-speakers
diff --git a/linux/home/.config/kitty/kitty.conf b/linux/home/.config/kitty/kitty.conf
new file mode 100644
index 0000000..cbf9f70
--- /dev/null
+++ b/linux/home/.config/kitty/kitty.conf
@@ -0,0 +1,827 @@
+
+# vim:fileencoding=utf-8:ft=conf:foldmethod=marker
+
+#: Fonts {{{
+
+#: kitty has very powerful font management. You can configure
+#: individual font faces and even specify special fonts for particular
+#: characters.
+
+font_family JetBrains Mono Medium
+bold_font auto
+italic_font auto
+bold_italic_font auto
+
+#: You can specify different fonts for the bold/italic/bold-italic
+#: variants. By default they are derived automatically, by the OSes
+#: font system. Setting them manually is useful for font families that
+#: have many weight variants like Book, Medium, Thick, etc. For
+#: example:
+
+#: font_family Operator Mono Book
+#: bold_font Operator Mono Medium
+#: italic_font Operator Mono Book Italic
+#: bold_italic_font Operator Mono Medium Italic
+
+font_size 9.0
+
+#: Font size (in pts)
+
+# adjust_line_height 0
+# adjust_column_width 0
+
+#: Change the size of each character cell kitty renders. You can use
+#: either numbers, which are interpreted as pixels or percentages
+#: (number followed by %), which are interpreted as percentages of the
+#: unmodified values. You can use negative pixels or percentages less
+#: than 100% to reduce sizes (but this might cause rendering
+#: artifacts).
+
+# symbol_map U+E0A0-U+E0A2,U+E0B0-U+E0B3 PowerlineSymbols
+
+#: Map the specified unicode codepoints to a particular font. Useful
+#: if you need special rendering for some symbols, such as for
+#: Powerline. Avoids the need for patched fonts. Each unicode code
+#: point is specified in the form U+<code point in hexadecimal>. You
+#: can specify multiple code points, separated by commas and ranges
+#: separated by hyphens. symbol_map itself can be specified multiple
+#: times. Syntax is::
+
+#: symbol_map codepoints Font Family Name
+
+# box_drawing_scale 0.001, 1, 1.5, 2
+
+#: Change the sizes of the lines used for the box drawing unicode
+#: characters These values are in pts. They will be scaled by the
+#: monitor DPI to arrive at a pixel value. There must be four values
+#: corresponding to thin, normal, thick, and very thick lines.
+
+#: }}}
+
+#: Cursor customization {{{
+
+# cursor magenta
+cursor white
+
+#: Default cursor color
+
+# cursor_shape block
+
+#: The cursor shape can be one of (block, beam, underline)
+
+cursor_blink_interval 0.5
+cursor_stop_blinking_after 15.0
+
+#: The interval (in seconds) at which to blink the cursor. Set to zero
+#: to disable blinking. Note that numbers smaller than repaint_delay
+#: will be limited to repaint_delay. Stop blinking cursor after the
+#: specified number of seconds of keyboard inactivity. Set to zero to
+#: never stop blinking.
+
+#: }}}
+
+#: Scrollback {{{
+
+scrollback_lines 20000
+
+#: Number of lines of history to keep in memory for scrolling back.
+#: Memory is allocated on demand.
+
+# scrollback_pager less --chop-long-lines --RAW-CONTROL-CHARS +INPUT_LINE_NUMBER
+
+#: Program with which to view scrollback in a new window. The
+#: scrollback buffer is passed as STDIN to this program. If you change
+#: it, make sure the program you use can handle ANSI escape sequences
+#: for colors and text formatting. INPUT_LINE_NUMBER in the command
+#: line above will be replaced by an integer representing which line
+#: should be at the top of the screen.
+
+wheel_scroll_multiplier 5
+
+#: Modify the amount scrolled by the mouse wheel or touchpad. Use
+#: negative numbers to change scroll direction.
+
+#: }}}
+
+#: Mouse {{{
+
+url_color #0087BD
+url_style curly
+
+#: The color and style for highlighting URLs on mouse-over. url_style
+#: can be one of: none, single, double, curly
+
+open_url_modifiers kitty_mod
+
+#: The modifier keys to press when clicking with the mouse on URLs to
+#: open the URL
+
+open_url_with default
+
+#: The program with which to open URLs that are clicked on. The
+#: special value default means to use the operating system's default
+#: URL handler.
+
+copy_on_select yes
+
+#: Copy to clipboard on select. With this enabled, simply selecting
+#: text with the mouse will cause the text to be copied to clipboard.
+#: Useful on platforms such as macOS/Wayland that do not have the
+#: concept of primary selectons. Note that this is a security risk,
+#: as all programs, including websites open in your browser can read
+#: the contents of the clipboard.
+
+# rectangle_select_modifiers ctrl+alt
+
+#: The modifiers to use rectangular selection (i.e. to select text in
+#: a rectangular block with the mouse)
+
+# select_by_word_characters :@-./_~?&=%+#
+
+#: Characters considered part of a word when double clicking. In
+#: addition to these characters any character that is marked as an
+#: alpha-numeric character in the unicode database will be matched.
+
+click_interval 0.5
+
+#: The interval between successive clicks to detect double/triple
+#: clicks (in seconds)
+
+# mouse_hide_wait 3.0
+
+#: Hide mouse cursor after the specified number of seconds of the
+#: mouse not being used. Set to zero to disable mouse cursor hiding.
+
+# focus_follows_mouse no
+
+#: Set the active window to the window under the mouse when moving the
+#: mouse around
+
+#: }}}
+
+#: Performance tuning {{{
+
+repaint_delay 10
+
+#: Delay (in milliseconds) between screen updates. Decreasing it,
+#: increases frames-per-second (FPS) at the cost of more CPU usage.
+#: The default value yields ~100 FPS which is more than sufficient for
+#: most uses. Note that to actually achieve 100 FPS you have to either
+#: set sync_to_monitor to no or use a monitor with a high refresh
+#: rate.
+
+# input_delay 3
+
+#: Delay (in milliseconds) before input from the program running in
+#: the terminal is processed. Note that decreasing it will increase
+#: responsiveness, but also increase CPU usage and might cause flicker
+#: in full screen programs that redraw the entire screen on each loop,
+#: because kitty is so fast that partial screen updates will be drawn.
+
+# sync_to_monitor yes
+
+#: Sync screen updates to the refresh rate of the monitor. This
+#: prevents tearing (https://en.wikipedia.org/wiki/Screen_tearing)
+#: when scrolling. However, it limits the rendering speed to the
+#: refresh rate of your monitor. With a very high speed mouse/high
+#: keyboard repeat rate, you may notice some slight input latency. If
+#: so, set this to no.
+
+#: }}}
+
+#: Terminal bell {{{
+
+enable_audio_bell no
+
+#: Enable/disable the audio bell. Useful in environments that require
+#: silence.
+
+# visual_bell_duration 0.5
+
+#: Visual bell duration. Flash the screen when a bell occurs for the
+#: specified number of seconds. Set to zero to disable.
+
+window_alert_on_bell yes
+
+#: Request window attention on bell. Makes the dock icon bounce on
+#: macOS or the taskbar flash on linux.
+
+bell_on_tab yes
+
+#: Show a bell symbol on the tab if a bell occurs in one of the
+#: windows in the tab and the window is not the currently focused
+#: window
+
+#: }}}
+
+#: Window layout {{{
+
+# remember_window_size yes
+# initial_window_width 640
+# initial_window_height 400
+
+#: If enabled, the window size will be remembered so that new
+#: instances of kitty will have the same size as the previous
+#: instance. If disabled, the window will initially have size
+#: configured by initial_window_width/height, in pixels. You can use a
+#: suffix of "c" on the width/height values to have them interpreted
+#: as number of cells instead of pixels.
+
+enabled_layouts *
+
+#: The enabled window layouts. A comma separated list of layout names.
+#: The special value * means all layouts. The first listed layout will
+#: be used as the startup layout. For a list of available layouts, see
+#: the layouts.
+
+# window_resize_step_cells 2
+# window_resize_step_lines 2
+
+#: The step size (in units of cell width/cell height) to use when
+#: resizing windows. The cells value is used for horizontal resizing
+#: and the lines value for vertical resizing.
+
+window_border_width 1
+
+#: The width (in pts) of window borders. Will be rounded to the
+#: nearest number of pixels based on screen resolution. Note that
+#: borders are displayed only when more than one window is visible.
+#: They are meant to separate multiple windows.
+
+window_margin_width 0
+
+#: The window margin (in pts) (blank area outside the border)
+
+# single_window_margin_width -1000.0
+
+#: The window margin (in pts) to use when only a single window is
+#: visible. Negative values will cause the value of
+#: window_margin_width to be used instead.
+
+window_padding_width 2
+
+#: The window padding (in pts) (blank area between the text and the
+#: window border)
+
+active_border_color #282c34
+
+#: The color for the border of the active window
+
+inactive_border_color #22262d
+
+#: The color for the border of inactive windows
+
+# bell_border_color #ff5a00
+
+#: The color for the border of inactive windows in which a bell has
+#: occurred
+
+inactive_text_alpha .6
+
+#: Fade the text in inactive windows by the specified amount (a number
+#: between zero and one, with zero being fully faded).
+
+#: }}}
+
+#: Tab bar {{{
+
+# tab_bar_edge bottom
+
+#: Which edge to show the tab bar on, top or bottom
+
+tab_bar_margin_width 4
+
+#: The margin to the left and right of the tab bar (in pts)
+
+tab_bar_style fade
+
+#: The tab bar style, can be one of: fade or separator. In the fade
+#: style, each tab's edges fade into the background color, in the
+#: separator style, tabs are separated by a configurable separator.
+
+# tab_fade 0.25 0.5 0.75 1
+tab_fade 1 1 1
+
+#: Control how each tab fades into the background when using fade for
+#: the tab_bar_style. Each number is an alpha (between zero and one)
+#: that controls how much the corresponding cell fades into the
+#: background, with zero being no fade and one being full fade. You
+#: can change the number of cells used by adding/removing entries to
+#: this list.
+
+# tab_separator " "
+
+#: The separator between tabs in the tab bar when using separator as
+#: the tab_bar_style.
+
+active_tab_foreground #282c34
+active_tab_background #abb2bf
+active_tab_font_style bold
+inactive_tab_foreground #5c6370
+inactive_tab_background #22262d
+inactive_tab_font_style normal
+
+#: Tab bar colors and styles
+
+#: }}}
+
+#: Color scheme {{{
+
+foreground #d8dee9
+#background #1d1f21
+background #000000
+
+#: The foreground and background colors
+
+background_opacity 0.7
+dynamic_background_opacity yes
+# Increase background opacity ctrl+shift+a>m
+
+# Decrease background opacity ctrl+shift+a>l
+
+# Full background opacity ctrl+shift+a>1
+
+# Reset background opacity ctrl+shift+a>d
+
+#: The opacity of the background. A number between 0 and 1, where 1 is
+#: opaque and 0 is fully transparent. This will only work if
+#: supported by the OS (for instance, when using a compositor under
+#: X11). Note that it only sets the default background color's
+#: opacity. This is so that things like the status bar in vim,
+#: powerline prompts, etc. still look good. But it means that if you
+#: use a color theme with a background color in your editor, it will
+#: not be rendered as transparent. Instead you should change the
+#: default background color in your kitty config and not use a
+#: background color in the editor color scheme. Or use the escape
+#: codes to set the terminals default colors in a shell script to
+#: launch your editor. Be aware that using a value less than 1.0 is a
+#: (possibly significant) performance hit. If you want to dynamically
+#: change transparency of windows set dynamic_background_opacity to
+#: yes (this is off by default as it has a performance cost)
+
+dim_opacity 1.0
+
+#: How much to dim text that has the DIM/FAINT attribute set. One
+#: means no dimming and zero means fully dimmed (i.e. invisible).
+
+selection_foreground #000000
+selection_background #FFFACD
+
+#: The foreground and background for text selected with the mouse
+
+
+#: The 16 terminal colors. There are 8 basic colors, each color has a
+#: dull and bright version. You can also set the remaining colors from
+#: the 256 color table as color16 to color255.
+
+#: black
+color0 #313539
+color8 #676f78
+
+#: red
+color1 #b02626
+color9 #b55454
+
+#: green
+color2 #40a62f
+color10 #78a670
+
+#: yellow
+color3 #f2e635
+color11 #faf380
+
+#: blue
+color4 #314ad0
+color12 #707fd0
+
+#: magenta
+color5 #b30ad0
+color13 #c583d0
+
+#: cyan
+color6 #32d0fc
+color14 #8adaf1
+
+#: white
+color7 #acadb1
+color15 #e0e3e7
+
+
+#: }}}
+
+#: Advanced {{{
+
+# shell zsh
+
+#: The shell program to execute. The default value of . means to use
+#: whatever shell is set as the default shell for the current user.
+#: Note that on macOS if you change this, you might need to add
+#: --login to ensure that the shell starts in interactive mode and
+#: reads its startup rc files.
+
+editor .
+
+#: The console editor to use when editing the kitty config file or
+#: similar tasks. A value of . means to use the environment variable
+#: EDITOR. Note that this environment variable has to be set not just
+#: in your shell startup scripts but system-wide, otherwise kitty will
+#: not see it.
+
+# Confirm when closing where 0 disables it; -1 enables it
+confirm_os_window_close 0
+
+# close_on_child_death nvim
+
+#: Close the window when the child process (shell) exits. If no (the
+#: default), the terminal will remain open when the child exits as
+#: long as there are still processes outputting to the terminal (for
+#: example disowned or backgrounded processes). If yes, the window
+#: will close as soon as the child process exits. Note that setting it
+#: to yes means that any background processes still using the terminal
+#: can fail silently because their stdout/stderr/stdin no longer work.
+
+# allow_remote_control no
+
+#: Allow other programs to control kitty. If you turn this on other
+#: programs can control all aspects of kitty, including sending text
+#: to kitty windows, opening new windows, closing windows, reading the
+#: content of windows, etc. Note that this even works over ssh
+#: connections.
+
+# startup_session none
+
+#: Path to a session file to use for all kitty instances. Can be
+#: overridden by using the kitty --session command line option for
+#: individual instances. See sessions in the kitty documentation for
+#: details. Note that relative paths are interpreted with respect to
+#: the kitty config directory. Environment variables in the path are
+#: expanded.
+
+# clipboard_control write-clipboard write-primary
+clipboard_control write-primary write-clipboard no-append
+
+#: Allow programs running in kitty to read and write from the
+#: clipboard. You can control exactly which actions are allowed. The
+#: set of possible actions is: write-clipboard read-clipboard write-
+#: primary read-primary The default is to allow writing to the
+#: clipboard and primary selection. Note that enabling the read
+#: functionality is a security risk as it means that any program, even
+#: one running on a remote server via SSH can read your clipboard.
+
+#term xterm-kitty
+
+#: The value of the TERM environment variable to set. Changing this
+#: can break many terminal programs, only change it if you know what
+#: you are doing, not because you read some advice on Stack Overflow
+#: to change it.
+
+#: }}}
+
+#: OS specific tweaks {{{
+
+# macos_titlebar_color #22262d
+
+#: Change the color of the kitty window's titlebar on macOS. A value
+#: of system means to use the default system color, a value of
+#: background means to use the background color of the currently
+#: active window and finally you can use an arbitrary color, such as
+#: #12af59 or red. WARNING: This option works by using a hack, as
+#: there is no proper Cocoa API for it. It sets the background color
+#: of the entire window and makes the titlebar transparent. As such it
+#: is incompatible with background_opacity. If you want to use both,
+#: you are probably better off just hiding the titlebar with
+#: macos_hide_titlebar.
+
+# macos_hide_titlebar no
+
+#: Hide the kitty window's title bar on macOS.
+
+hide_window_decorations yes
+
+#: Hide the window decorations (title bar and window borders) on X11
+#: and Wayland. Whether this works and exactly what effect it has
+#: depends on the window manager, as it is the job of the window
+#: manager/compositor to draw window decorations.
+
+# macos_option_as_alt yes
+
+#: Use the option key as an alt key. With this set to no, kitty will
+#: use the macOS native Option+Key = unicode character behavior. This
+#: will break any Alt+key keyboard shortcuts in your terminal
+#: programs, but you can use the macOS unicode input technique.
+
+# macos_hide_from_tasks no
+
+#: Hide the kitty window from running tasks (Option+Tab) on macOS.
+
+# macos_quit_when_last_window_closed no
+
+#: Have kitty quit when all the top-level windows are closed. By
+#: default, kitty will stay running, even with no open windows, as is
+#: the expected behavior on macOS.
+
+linux_display_server x11
+
+#: Choose between Wayland and X11 backends. By default, an appropriate
+#: backend based on the system state is chosen automatically. Set it
+#: to x11 or wayland to force the choice.
+
+#: }}}
+
+#: Keyboard shortcuts {{{
+
+#: For a list of key names, see: GLFW keys
+#: <http://www.glfw.org/docs/latest/group__keys.html>. The name to use
+#: is the part after the GLFW_KEY_ prefix. For a list of modifier
+#: names, see: GLFW mods
+#: <http://www.glfw.org/docs/latest/group__mods.html>
+
+#: On Linux you can also use XKB key names to bind keys that are not
+#: supported by GLFW. See XKB keys
+#: <https://github.com/xkbcommon/libxkbcommon/blob/master/xkbcommon/xkbcommon-
+#: keysyms.h> for a list of key names. The name to use is the part
+#: after the XKB_KEY_ prefix. Note that you should only use an XKB key
+#: name for keys that are not present in the list of GLFW keys.
+
+#: You can use the special action no_op to unmap a keyboard shortcut
+#: that is assigned in the default configuration.
+
+#: You can combine multiple actions to be triggered by a single
+#: shortcut, using the syntax below::
+
+#: map key combine <separator> action1 <separator> action2 <separator> action3 ...
+
+#: For example::
+
+#: map kitty_mod+e combine : new_window : next_layout
+
+#: this will create a new window and switch to the next available
+#: layout
+
+#: You can use multi-key shortcuts using the syntax shown below::
+
+#: map key1>key2>key3 action
+
+#: For example::
+
+#: map ctrl+f>2 set_font_size 20
+
+# kitty_mod ctrl+shift
+
+#: The value of kitty_mod is used as the modifier for all default
+#: shortcuts, you can change it in your kitty.conf to change the
+#: modifiers for all the default shortcuts.
+
+# clear_all_shortcuts no
+
+#: You can have kitty remove all shortcut definition seen up to this
+#: point. Useful, for instance, to remove the default shortcuts.
+
+#: Clipboard {{{
+
+# map cmd+c copy_to_clipboard
+#map kitty_mod+c copy_to_clipboard
+# map cmd+v paste_from_clipboard
+#map kitty_mod+v paste_from_clipboard
+#map kitty_mod+s paste_from_selection
+map shift+insert paste_from_selection
+# map kitty_mod+o pass_selection_to_program
+map ctrl+c copy_to_clipboard
+map ctrl+v paste_from_clipboard
+
+#: You can also pass the contents of the current selection to any
+#: program using pass_selection_to_program. By default, the system's
+#: open program is used, but you can specify your own, for example::
+
+#: map kitty_mod+o pass_selection_to_program firefox
+
+#: You can pass the current selection to a terminal program running in
+#: a new kitty window, by using the @selection placeholder::
+
+#: map kitty_mod+y new_window less @selection
+
+#: }}}
+
+#: Scrolling {{{
+
+#map kitty_mod+up scroll_line_up
+#map ctrl+k scroll_line_up
+#map kitty_mod+k scroll_line_up
+#map kitty_mod+down scroll_line_down
+#map ctrl+j scroll_line_down
+#map kitty_mod+j scroll_line_down
+map kitty_mod+page_up scroll_page_up
+map kitty_mod+page_down scroll_page_down
+map kitty_mod+b scroll_page_up
+map kitty_mod+f scroll_page_down
+# map kitty_mod+home scroll_home
+# map kitty_mod+end scroll_end
+# map kitty_mod+h show_scrollback
+
+#: You can send the contents of the current screen + history buffer as
+#: stdin to an arbitrary program using the placeholders @text (which
+#: is the plain text) and @ansi (which includes text styling escape
+#: codes). For only the current screen, use @screen or @ansi_screen.
+#: For example, the following command opens the scrollback buffer in
+#: less in a new window::
+
+#: map kitty_mod+y new_window @ansi less +G -R
+
+#: }}}
+
+#: Window management {{{
+
+# map kitty_mod+enter new_window
+map kitty_mod+enter no_op
+map kitty_mod+enter new_window_with_cwd
+
+#: You can open a new window running an arbitrary program, for
+#: example::
+
+#: map kitty_mod+y new_window mutt
+
+#: You can open a new window with the current working directory set to
+#: the working directory of the current window using::
+
+#: map ctrl+alt+enter new_window_with_cwd
+
+# map cmd+n new_os_window
+# map kitty_mod+n new_os_window
+# map kitty_mod+w close_window
+# map kitty_mod+] next_window
+# map kitty_mod+[ previous_window
+map kitty_mod+j previous_window
+map kitty_mod+k next_window
+map kitty_mod+up move_window_forward
+map kitty_mod+down move_window_backward
+# map kitty_mod+f move_window_forward
+# map kitty_mod+b move_window_backward
+# map kitty_mod+` move_window_to_top
+# map kitty_mod+r start_resizing_window
+# map kitty_mod+1 first_window
+# map kitty_mod+2 second_window
+# map kitty_mod+3 third_window
+# map kitty_mod+4 fourth_window
+# map kitty_mod+5 fifth_window
+# map kitty_mod+6 sixth_window
+# map kitty_mod+7 seventh_window
+# map kitty_mod+8 eighth_window
+# map kitty_mod+9 ninth_window
+# map kitty_mod+0 tenth_window
+#: }}}
+
+#: Tab management {{{
+
+# map kitty_mod+right next_tab
+# map kitty_mod+left previous_tab
+map kitty_mod+] no_op
+map kitty_mod+] next_tab
+map kitty_mod+[ no_op
+map kitty_mod+[ previous_tab
+# map kitty_mod+t new_tab
+# map kitty_mod+q close_tab
+# map kitty_mod+. move_tab_forward
+# map kitty_mod+, move_tab_backward
+map kitty_mod+right no_op
+map kitty_mod+right move_tab_forward
+map kitty_mod+left no_op
+map kitty_mod+left move_tab_backward
+# map kitty_mod+alt+t set_tab_title
+map kitty_mod+t no_op
+map kitty_mod+t new_tab_with_cwd
+
+#: You can also create shortcuts to go to specific tabs, with 1 being
+#: the first tab::
+
+#: map ctrl+alt+1 goto_tab 1
+#: map ctrl+alt+2 goto_tab 2
+
+#: Just as with new_window above, you can also pass the name of
+#: arbitrary commands to run when using new_tab and use
+#: new_tab_with_cwd.
+#: }}}
+
+#: Layout management {{{
+
+# map kitty_mod+l next_layout
+
+#: You can also create shortcuts to switch to specific layouts::
+
+map kitty_mod+0 no_op
+map kitty_mod+0 goto_layout stack
+map kitty_mod+9 no_op
+map kitty_mod+9 goto_layout tall
+map kitty_mod+8 no_op
+map kitty_mod+8 goto_layout fat
+#: map ctrl+alt+t goto_layout tall
+#: map ctrl+alt+s goto_layout stack
+#: }}}
+
+#: Font sizes {{{
+
+#: You can change the font size for all top-level kitty windows at a
+#: time or only the current one.
+
+map kitty_mod+equal change_font_size all +2.0
+map kitty_mod+minus change_font_size all -2.0
+map kitty_mod+backspace change_font_size all 0
+
+#: To setup shortcuts for specific font sizes::
+
+#: map kitty_mod+f6 change_font_size all 10.0
+
+#: To setup shortcuts to change only the current window's font size::
+
+#: map kitty_mod+f6 change_font_size current 10.0
+#: }}}
+
+#: Select and act on visible text {{{
+
+#: Use the hints kitten to select text and either pass it to an
+#: external program or insert it into the terminal or copy it to the
+#: clipboard.
+
+# map kitty_mod+e kitten hints
+
+#: Open a currently visible URL using the keyboard. The program used
+#: to open the URL is specified in open_url_with.
+
+# map kitty_mod+p>f kitten hints --type path --program -
+
+#: Select a path/filename and insert it into the terminal. Useful, for
+#: instance to run git commands on a filename output from a previous
+#: git command.
+
+# map kitty_mod+p>shift+f kitten hints --type path
+
+#: Select a path/filename and open it with the default open program.
+
+# map kitty_mod+p>l kitten hints --type line --program -
+
+#: Select a line of text and insert it into the terminal. Use for the
+#: output of things like: ls -1
+
+# map kitty_mod+p>w kitten hints --type word --program -
+
+#: Select words and insert into terminal.
+
+# map kitty_mod+p>h kitten hints --type hash --program -
+
+#: Select something that looks like a hash and insert it into the
+#: terminal. Useful with git, which uses sha1 hashes to identify
+#: commits
+
+
+#: The hints kitten has many more modes of operation that you can map
+#: to different shortcuts. For a full description see kittens/hints.
+#: }}}
+
+#: Miscellaneous {{{
+
+
+# map kitty_mod+f11 toggle_fullscreen
+# map kitty_mod+u input_unicode_character
+# map kitty_mod+f2 edit_config_file
+# map kitty_mod+escape kitty_shell window
+
+#: Open the kitty shell in a new window/tab/overlay/os_window to
+#: control kitty using commands.
+
+# map kitty_mod+a>m set_background_opacity +0.1
+# map kitty_mod+a>l set_background_opacity -0.1
+# map kitty_mod+a>1 set_background_opacity 1
+# map kitty_mod+a>d set_background_opacity default
+#
+# map kitty_mod+a>m set_background_opacity +0.1
+# map kitty_mod+a>l set_background_opacity -0.1
+map kitty_mod+, set_background_opacity 1
+map kitty_mod+. set_background_opacity default
+
+#: You can tell kitty to send arbitrary (UTF-8) encoded text to the
+#: client program when pressing specified shortcut keys. For example::
+
+#: map ctrl+alt+a send_text all Special text
+
+#: This will send "Special text" when you press the ctrl+alt+a key
+#: combination. The text to be sent is a python string literal so you
+#: can use escapes like \x1b to send control codes or \u21fb to send
+#: unicode characters (or you can just input the unicode characters
+#: directly as UTF-8 text). The first argument to send_text is the
+#: keyboard modes in which to activate the shortcut. The possible
+#: values are normal or application or kitty or a comma separated
+#: combination of them. The special keyword all means all modes. The
+#: modes normal and application refer to the DECCKM cursor key mode
+#: for terminals, and kitty refers to the special kitty extended
+#: keyboard protocol.
+
+#: Another example, that outputs a word and then moves the cursor to
+#: the start of the line (same as pressing the Home key)::
+
+#: map ctrl+alt+a send_text normal Word\x1b[H
+#: map ctrl+alt+a send_text application Word\x1bOH
+
+#: }}}
+
+map ctrl+space send_text all \x10
+
+# }}}
diff --git a/linux/home/.config/mimeapps.list b/linux/home/.config/mimeapps.list
new file mode 100644
index 0000000..85d088c
--- /dev/null
+++ b/linux/home/.config/mimeapps.list
@@ -0,0 +1,14 @@
+[Default Applications]
+application/pdf=zathura.desktop;
+application/tex=zathura.desktop;
+image/png=phototonic.desktop
+image/jpeg=phototonic.desktop;
+video/mp4=mpv.desktop;
+video/mkv=mpv.desktop;
+inode/directory=pcmanfm.desktop
+
+[Added Associations]
+image/png=phototonic.desktop;
+
+[Removed Associations]
+
diff --git a/linux/home/.config/picom/picom.conf b/linux/home/.config/picom/picom.conf
new file mode 100644
index 0000000..d189670
--- /dev/null
+++ b/linux/home/.config/picom/picom.conf
@@ -0,0 +1,494 @@
+#################################
+# Animations #
+#################################
+# requires https://github.com/jonaburg/picom
+# (These are also the default values)
+transition-length = 300
+transition-pow-x = 0.1
+transition-pow-y = 0.1
+transition-pow-w = 0.1
+transition-pow-h = 0.1
+size-transition = true
+
+
+#################################
+# Corners #
+#################################
+# requires: https://github.com/sdhand/compton or https://github.com/jonaburg/picom
+corner-radius = 10.0;
+rounded-corners-exclude = [
+#"window_type = 'normal'",
+# "class_g = 'Bspwm' && class_i = 'presel_feedback'",
+# "class_g = 'URxvt'",
+# "class_g = 'alacritty'",
+# "class_g = 'Org.gnome.Nautilus'",
+# "class_g = 'Nemo'",
+# "class_g = 'firefox'",
+# "class_g = 'Rofi'",
+# "class_g = 'Spotify'",
+# "class_g = 'discord'",
+# "class_g = 'Code'",
+# "class_g = 'TelegramDesktop'",
+# "class_g = 'YouTube Music'",
+ "class_g = 'Polybar'"
+# "class_g = 'qutebrowser'",
+# "class_g = 'Zathura'",
+# "class_g = 'Pavucontrol'"
+
+];
+round-borders = 1;
+#round-borders-exclude = [
+# "class_g = 'TelegramDesktop'",
+#];
+
+
+
+#################################
+# Shadows #
+#################################
+
+
+# Enabled client-side shadows on windows. Note desktop windows
+# (windows with '_NET_WM_WINDOW_TYPE_DESKTOP') never get shadow,
+# unless explicitly requested using the wintypes option.
+#
+shadow = false;
+#shadow = true;
+
+# The blur radius for shadows, in pixels. (defaults to 12)
+# shadow-radius = 12
+shadow-radius = 20;
+
+# The opacity of shadows. (0.0 - 1.0, defaults to 0.75)
+# shadow-opacity = .75
+
+# The left offset for shadows, in pixels. (defaults to -15)
+# shadow-offset-x = -15
+shadow-offset-x = -5;
+
+# The top offset for shadows, in pixels. (defaults to -15)
+# shadow-offset-y = -15
+shadow-offset-y = -5;
+
+# Avoid drawing shadows on dock/panel windows. This option is deprecated,
+# you should use the *wintypes* option in your config file instead.
+#
+# no-dock-shadow = false
+
+# Don't draw shadows on drag-and-drop windows. This option is deprecated,
+# you should use the *wintypes* option in your config file instead.
+#
+# no-dnd-shadow = false
+
+# Red color value of shadow (0.0 - 1.0, defaults to 0).
+# shadow-red = 0
+
+# Green color value of shadow (0.0 - 1.0, defaults to 0).
+# shadow-green = 0
+
+# Blue color value of shadow (0.0 - 1.0, defaults to 0).
+# shadow-blue = 0
+
+# Do not paint shadows on shaped windows. Note shaped windows
+# here means windows setting its shape through X Shape extension.
+# Those using ARGB background is beyond our control.
+# Deprecated, use
+# shadow-exclude = 'bounding_shaped'
+# or
+# shadow-exclude = 'bounding_shaped && !rounded_corners'
+# instead.
+#
+# shadow-ignore-shaped = ''
+
+# Specify a list of conditions of windows that should have no shadow.
+#
+# examples:
+# shadow-exclude = "n:e:Notification";
+#
+# shadow-exclude = []
+shadow-exclude = [
+ "name = 'Notification'",
+ "class_g = 'Conky'",
+ #"class_g ?= 'Notify-osd'",
+ "class_g = 'Cairo-clock'",
+ "class_g = 'Polybar'",
+ "class_g = 'Nwg-menu'",
+ # "class_g = 'Rofi'"
+ "_GTK_FRAME_EXTENTS@:c"
+ ];
+
+# Specify a X geometry that describes the region in which shadow should not
+# be painted in, such as a dock window region. Use
+# shadow-exclude-reg = "x10+0+0"
+# for example, if the 10 pixels on the bottom of the screen should not have shadows painted on.
+#
+# shadow-exclude-reg = ""
+
+# Crop shadow of a window fully on a particular Xinerama screen to the screen.
+# xinerama-shadow-crop = false
+
+
+#################################
+# Fading #
+#################################
+
+
+# Fade windows in/out when opening/closing and when opacity changes,
+# unless no-fading-openclose is used.
+# fading = false
+fading = true
+
+# Opacity change between steps while fading in. (0.01 - 1.0, defaults to 0.028)
+# fade-in-step = 0.028
+fade-in-step = 0.08;
+
+# Opacity change between steps while fading out. (0.01 - 1.0, defaults to 0.03)
+# fade-out-step = 0.03
+fade-out-step = 0.08;
+
+# The time between steps in fade step, in milliseconds. (> 0, defaults to 10)
+# fade-delta = 10
+
+# Specify a list of conditions of windows that should not be faded.
+# fade-exclude = []
+
+# Do not fade on window open/close.
+# no-fading-openclose = false
+
+# Do not fade destroyed ARGB windows with WM frame. Workaround of bugs in Openbox, Fluxbox, etc.
+# no-fading-destroyed-argb = false
+
+
+#################################
+# Transparency / Opacity #
+#################################
+
+# Opacity of inactive windows. (0.1 - 1.0, defaults to 1.0)
+# inactive-opacity = 1
+inactive-opacity = 1;
+
+# Opacity of window titlebars and borders. (0.1 - 1.0, disabled by default)
+# frame-opacity = 1.0
+frame-opacity = 1;
+
+# Default opacity for dropdown menus and popup menus. (0.0 - 1.0, defaults to 1.0)
+# menu-opacity = 1.0
+
+# Let inactive opacity set by -i override the '_NET_WM_OPACITY' values of windows.
+# inactive-opacity-override = true
+inactive-opacity-override = false;
+
+# Default opacity for active windows. (0.0 - 1.0, defaults to 1.0)
+active-opacity = 1.0;
+
+# Dim inactive windows. (0.0 - 1.0, defaults to 0.0)
+inactive-dim = 0.2
+
+# Specify a list of conditions of windows that should always be considered focused.
+# focus-exclude = []
+focus-exclude = ["class_g = 'Plank'", "class_g = 'Nwg-menu'", "class_g = 'st'", "class_g = 'kitty'", "class_g = 'wezterm'", "class_g = 'Alacritty'", "class_g = 'firefox'"];
+
+# Use fixed inactive dim value, instead of adjusting according to window opacity.
+# inactive-dim-fixed = 1.0
+
+# Specify a list of opacity rules, in the format `PERCENT:PATTERN`,
+# like `50:name *= "Firefox"`. picom-trans is recommended over this.
+# Note we don't make any guarantee about possible conflicts with other
+# programs that set '_NET_WM_WINDOW_OPACITY' on frame or client windows.
+# example:
+# opacity-rule = [ "80:class_g = 'URxvt'" ];
+opacity-rule = ["80:class_g = 'betterlockscreen'", "80:class_g = 'URxvt'", "100:class_g = 'firefox'", "100:class_g = 'Zathura'", "80:class_g = 'Spotify'", "80:class_g *?= 'Rofi'", "100:class_g = 'kitty' && focused", "100:class_g = 'kitty' && !focused", "100:class_g = 'Alacritty' && focused", "100:class_g = 'Alacritty' && !focused", "100:class_g = 'wezterm' && focused", "100:class_g = 'wezterm' && !focused"];
+#
+#opacity-rule = ["85:class_g ?= 'Alacritty' && focused"];
+
+#blur-background-exclude = ["class_g = 'scratchpad'"];
+
+#################################
+# Background-Blurring #
+#################################
+
+
+# Parameters for background blurring, see the *BLUR* section for more information.
+blur-method = "dual_kawase";
+#blur-method = "gaussian";
+blur-strength = 6;
+# blur-size = 12
+#
+# blur-deviation = false
+
+# Blur background of semi-transparent / ARGB windows.
+# Bad in performance, with driver-dependent behavior.
+# The name of the switch may change without prior notifications.
+#
+blur-background = false;
+
+# Blur background of windows when the window frame is not opaque.
+# Implies:
+# blur-background
+# Bad in performance, with driver-dependent behavior. The name may change.
+#
+# blur-background-frame = false
+
+
+# Use fixed blur strength rather than adjusting according to window opacity.
+#blur-background-fixed = false
+
+
+# Specify the blur convolution kernel, with the following format:
+# example:
+# blur-kern = "5,5,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1";
+#
+# blur-kern = ''
+#blur-kern = "3x3box";
+blur-kern = "7x7box";
+
+# Exclude conditions for background blur.
+# blur-background-exclude = []
+blur-background-exclude = [
+ "window_type = 'dock'",
+ "window_type = 'desktop'",
+ "_GTK_FRAME_EXTENTS@:c",
+ "class_g = 'kitty'",
+ "class_g = 'Alacritty'",
+ "class_g = 'wezterm'",
+# "class_g = 'Polybar'",
+ "class_g = 'URxvt'",
+ "class_g = 'scratchpad'",
+ "class_g = 'heads-up-display'",
+ "class_g = 'Firefox'",
+ "class_g = 'firefox'",
+ "class_g = 'discord'",
+ "class_g = 'Rofi'",
+ "class_g = 'Zathura'",
+ "class_g = 'Notification'",
+];
+
+#################################
+# General Settings #
+#################################
+
+# Daemonize process. Fork to background after initialization. Causes issues with certain (badly-written) drivers.
+# daemon = false
+
+# Specify the backend to use: `xrender`, `glx`, or `xr_glx_hybrid`.
+# `xrender` is the default one.
+#
+#experimental-backends = true;
+#backend = 'glx'
+backend = "glx";
+#backend = "xrender";
+
+# Enable/disable VSync.
+# vsync = false
+vsync = true
+
+# Enable remote control via D-Bus. See the *D-BUS API* section below for more details.
+# dbus = false
+
+# Try to detect WM windows (a non-override-redirect window with no
+# child that has 'WM_STATE') and mark them as active.
+#
+# mark-wmwin-focused = false
+mark-wmwin-focused = true;
+
+# Mark override-redirect windows that doesn't have a child window with 'WM_STATE' focused.
+mark-ovredir-focused = false;
+#mark-ovredir-focused = true;
+
+# Try to detect windows with rounded corners and don't consider them
+# shaped windows. The accuracy is not very high, unfortunately.
+#
+# detect-rounded-corners = false
+detect-rounded-corners = true;
+
+# Detect '_NET_WM_OPACITY' on client windows, useful for window managers
+# not passing '_NET_WM_OPACITY' of client windows to frame windows.
+#
+# detect-client-opacity = false
+detect-client-opacity = true;
+
+# Specify refresh rate of the screen. If not specified or 0, picom will
+# try detecting this with X RandR extension.
+#
+# refresh-rate = 60
+refresh-rate = 0
+
+# Limit picom to repaint at most once every 1 / 'refresh_rate' second to
+# boost performance. This should not be used with
+# vsync drm/opengl/opengl-oml
+# as they essentially does sw-opti's job already,
+# unless you wish to specify a lower refresh rate than the actual value.
+#
+# sw-opti =
+
+# Use EWMH '_NET_ACTIVE_WINDOW' to determine currently focused window,
+# rather than listening to 'FocusIn'/'FocusOut' event. Might have more accuracy,
+# provided that the WM supports it.
+#
+# use-ewmh-active-win = false
+
+# Unredirect all windows if a full-screen opaque window is detected,
+# to maximize performance for full-screen windows. Known to cause flickering
+# when redirecting/unredirecting windows.
+#
+# unredir-if-possible = false
+
+# Delay before unredirecting the window, in milliseconds. Defaults to 0.
+# unredir-if-possible-delay = 0
+
+# Conditions of windows that shouldn't be considered full-screen for unredirecting screen.
+# unredir-if-possible-exclude = []
+
+# Use 'WM_TRANSIENT_FOR' to group windows, and consider windows
+# in the same group focused at the same time.
+#
+# detect-transient = false
+detect-transient = true
+
+# Use 'WM_CLIENT_LEADER' to group windows, and consider windows in the same
+# group focused at the same time. 'WM_TRANSIENT_FOR' has higher priority if
+# detect-transient is enabled, too.
+#
+# detect-client-leader = false
+detect-client-leader = true
+
+# Resize damaged region by a specific number of pixels.
+# A positive value enlarges it while a negative one shrinks it.
+# If the value is positive, those additional pixels will not be actually painted
+# to screen, only used in blur calculation, and such. (Due to technical limitations,
+# with use-damage, those pixels will still be incorrectly painted to screen.)
+# Primarily used to fix the line corruption issues of blur,
+# in which case you should use the blur radius value here
+# (e.g. with a 3x3 kernel, you should use `--resize-damage 1`,
+# with a 5x5 one you use `--resize-damage 2`, and so on).
+# May or may not work with *--glx-no-stencil*. Shrinking doesn't function correctly.
+#
+# resize-damage = 1
+
+# Specify a list of conditions of windows that should be painted with inverted color.
+# Resource-hogging, and is not well tested.
+#
+# invert-color-include = []
+
+# GLX backend: Avoid using stencil buffer, useful if you don't have a stencil buffer.
+# Might cause incorrect opacity when rendering transparent content (but never
+# practically happened) and may not work with blur-background.
+# My tests show a 15% performance boost. Recommended.
+#
+# glx-no-stencil = false
+
+# GLX backend: Avoid rebinding pixmap on window damage.
+# Probably could improve performance on rapid window content changes,
+# but is known to break things on some drivers (LLVMpipe, xf86-video-intel, etc.).
+# Recommended if it works.
+#
+# glx-no-rebind-pixmap = false
+
+# Disable the use of damage information.
+# This cause the whole screen to be redrawn everytime, instead of the part of the screen
+# has actually changed. Potentially degrades the performance, but might fix some artifacts.
+# The opposing option is use-damage
+#
+# no-use-damage = false
+use-damage = true
+
+# Use X Sync fence to sync clients' draw calls, to make sure all draw
+# calls are finished before picom starts drawing. Needed on nvidia-drivers
+# with GLX backend for some users.
+#
+# xrender-sync-fence = false
+
+# GLX backend: Use specified GLSL fragment shader for rendering window contents.
+# See `compton-default-fshader-win.glsl` and `compton-fake-transparency-fshader-win.glsl`
+# in the source tree for examples.
+#
+# glx-fshader-win = ''
+
+# Force all windows to be painted with blending. Useful if you
+# have a glx-fshader-win that could turn opaque pixels transparent.
+#
+# force-win-blend = false
+
+# Do not use EWMH to detect fullscreen windows.
+# Reverts to checking if a window is fullscreen based only on its size and coordinates.
+#
+# no-ewmh-fullscreen = false
+
+# Dimming bright windows so their brightness doesn't exceed this set value.
+# Brightness of a window is estimated by averaging all pixels in the window,
+# so this could comes with a performance hit.
+# Setting this to 1.0 disables this behaviour. Requires --use-damage to be disabled. (default: 1.0)
+#
+# max-brightness = 1.0
+
+# Make transparent windows clip other windows like non-transparent windows do,
+# instead of blending on top of them.
+#
+# transparent-clipping = false
+
+# Set the log level. Possible values are:
+# "trace", "debug", "info", "warn", "error"
+# in increasing level of importance. Case doesn't matter.
+# If using the "TRACE" log level, it's better to log into a file
+# using *--log-file*, since it can generate a huge stream of logs.
+#
+# log-level = "debug"
+log-level = "warn";
+
+# Set the log file.
+# If *--log-file* is never specified, logs will be written to stderr.
+# Otherwise, logs will to written to the given file, though some of the early
+# logs might still be written to the stderr.
+# When setting this option from the config file, it is recommended to use an absolute path.
+#
+# log-file = '/path/to/your/log/file'
+
+# Show all X errors (for debugging)
+# show-all-xerrors = false
+
+# Write process ID to a file.
+# write-pid-path = '/path/to/your/log/file'
+
+# Window type settings
+#
+# 'WINDOW_TYPE' is one of the 15 window types defined in EWMH standard:
+# "unknown", "desktop", "dock", "toolbar", "menu", "utility",
+# "splash", "dialog", "normal", "dropdown_menu", "popup_menu",
+# "tooltip", "notification", "combo", and "dnd".
+#
+# Following per window-type options are available: ::
+#
+# fade, shadow:::
+# Controls window-type-specific shadow and fade settings.
+#
+# opacity:::
+# Controls default opacity of the window type.
+#
+# focus:::
+# Controls whether the window of this type is to be always considered focused.
+# (By default, all window types except "normal" and "dialog" has this on.)
+#
+# full-shadow:::
+# Controls whether shadow is drawn under the parts of the window that you
+# normally won't be able to see. Useful when the window has parts of it
+# transparent, and you want shadows in those areas.
+#
+# redir-ignore:::
+# Controls whether this type of windows should cause screen to become
+# redirected again after been unredirected. If you have unredir-if-possible
+# set, and doesn't want certain window to cause unnecessary screen redirection,
+# you can set this to `true`.
+#
+wintypes:
+{
+ #normal = { full-shadow = true; };
+ #menu = { full-shadow = true; };
+ tooltip = { fade = true; shadow = true; opacity = 0.75; focus = true; full-shadow = false; };
+ dock = { shadow = false; };
+ dnd = { shadow = false; };
+ popup_menu = { full-shadow = false opacity = 0.8; };
+ #utility = { full-shadow = true; };
+ #toolbar = { full-shadow = true; };
+ notification = { opacity = 0.75; }
+};
+
diff --git a/linux/home/.config/plank/dock/launchers/Alacritty.dockitem b/linux/home/.config/plank/dock/launchers/Alacritty.dockitem
new file mode 100644
index 0000000..b3091c5
--- /dev/null
+++ b/linux/home/.config/plank/dock/launchers/Alacritty.dockitem
@@ -0,0 +1,2 @@
+[PlankDockItemPreferences]
+Launcher=file:///usr/share/applications/Alacritty.desktop
diff --git a/linux/home/.config/plank/dock/launchers/com.obsproject.Studio.dockitem b/linux/home/.config/plank/dock/launchers/com.obsproject.Studio.dockitem
new file mode 100644
index 0000000..7626308
--- /dev/null
+++ b/linux/home/.config/plank/dock/launchers/com.obsproject.Studio.dockitem
@@ -0,0 +1,2 @@
+[PlankDockItemPreferences]
+Launcher=file:///usr/share/applications/com.obsproject.Studio.desktop
diff --git a/linux/home/.config/plank/dock/launchers/discord.dockitem b/linux/home/.config/plank/dock/launchers/discord.dockitem
new file mode 100644
index 0000000..c6736ba
--- /dev/null
+++ b/linux/home/.config/plank/dock/launchers/discord.dockitem
@@ -0,0 +1,2 @@
+[PlankDockItemPreferences]
+Launcher=file:///usr/share/applications/discord.desktop
diff --git a/linux/home/.config/plank/dock/launchers/firefox.dockitem b/linux/home/.config/plank/dock/launchers/firefox.dockitem
new file mode 100644
index 0000000..0dbfff7
--- /dev/null
+++ b/linux/home/.config/plank/dock/launchers/firefox.dockitem
@@ -0,0 +1,2 @@
+[PlankDockItemPreferences]
+Launcher=file:///usr/share/applications/firefox.desktop
diff --git a/linux/home/.config/plank/dock/launchers/gimp.dockitem b/linux/home/.config/plank/dock/launchers/gimp.dockitem
new file mode 100644
index 0000000..c9f9438
--- /dev/null
+++ b/linux/home/.config/plank/dock/launchers/gimp.dockitem
@@ -0,0 +1,2 @@
+[PlankDockItemPreferences]
+Launcher=file:///usr/share/applications/gimp.desktop
diff --git a/linux/home/.config/plank/dock/launchers/google-chrome.dockitem b/linux/home/.config/plank/dock/launchers/google-chrome.dockitem
new file mode 100644
index 0000000..cd04294
--- /dev/null
+++ b/linux/home/.config/plank/dock/launchers/google-chrome.dockitem
@@ -0,0 +1,2 @@
+[PlankDockItemPreferences]
+Launcher=file:///usr/share/applications/google-chrome.desktop
diff --git a/linux/home/.config/plank/dock/launchers/obsidian.dockitem b/linux/home/.config/plank/dock/launchers/obsidian.dockitem
new file mode 100644
index 0000000..0170dbd
--- /dev/null
+++ b/linux/home/.config/plank/dock/launchers/obsidian.dockitem
@@ -0,0 +1,2 @@
+[PlankDockItemPreferences]
+Launcher=file:///usr/share/applications/obsidian.desktop
diff --git a/linux/home/.config/plank/dock/launchers/org.gnome.Nautilus.dockitem b/linux/home/.config/plank/dock/launchers/org.gnome.Nautilus.dockitem
new file mode 100644
index 0000000..ae5f8d9
--- /dev/null
+++ b/linux/home/.config/plank/dock/launchers/org.gnome.Nautilus.dockitem
@@ -0,0 +1,2 @@
+[PlankDockItemPreferences]
+Launcher=file:///usr/share/applications/org.gnome.Nautilus.desktop
diff --git a/linux/home/.config/plank/dock/launchers/spotify.dockitem b/linux/home/.config/plank/dock/launchers/spotify.dockitem
new file mode 100644
index 0000000..8ae95b4
--- /dev/null
+++ b/linux/home/.config/plank/dock/launchers/spotify.dockitem
@@ -0,0 +1,2 @@
+[PlankDockItemPreferences]
+Launcher=file:///usr/share/applications/spotify.desktop
diff --git a/linux/home/.config/plank/dock/launchers/steam.dockitem b/linux/home/.config/plank/dock/launchers/steam.dockitem
new file mode 100644
index 0000000..143d3b4
--- /dev/null
+++ b/linux/home/.config/plank/dock/launchers/steam.dockitem
@@ -0,0 +1,2 @@
+[PlankDockItemPreferences]
+Launcher=file:///usr/share/applications/steam.desktop
diff --git a/linux/home/.config/plank/dock/launchers/trash.dockitem b/linux/home/.config/plank/dock/launchers/trash.dockitem
new file mode 100644
index 0000000..567a63e
--- /dev/null
+++ b/linux/home/.config/plank/dock/launchers/trash.dockitem
@@ -0,0 +1,2 @@
+[PlankDockItemPreferences]
+Launcher=docklet://trash
diff --git a/linux/home/.config/plank/dock/launchers/virtualbox.dockitem b/linux/home/.config/plank/dock/launchers/virtualbox.dockitem
new file mode 100644
index 0000000..fa95934
--- /dev/null
+++ b/linux/home/.config/plank/dock/launchers/virtualbox.dockitem
@@ -0,0 +1,2 @@
+[PlankDockItemPreferences]
+Launcher=file:///usr/share/applications/virtualbox.desktop
diff --git a/linux/home/.config/plank/dock/launchers/vlc.dockitem b/linux/home/.config/plank/dock/launchers/vlc.dockitem
new file mode 100644
index 0000000..c20771a
--- /dev/null
+++ b/linux/home/.config/plank/dock/launchers/vlc.dockitem
@@ -0,0 +1,2 @@
+[PlankDockItemPreferences]
+Launcher=file:///usr/share/applications/vlc.desktop
diff --git a/linux/home/.config/plank/dock1/launchers/code-oss.dockitem b/linux/home/.config/plank/dock1/launchers/code-oss.dockitem
new file mode 100644
index 0000000..3d8af2b
--- /dev/null
+++ b/linux/home/.config/plank/dock1/launchers/code-oss.dockitem
@@ -0,0 +1,2 @@
+[PlankDockItemPreferences]
+Launcher=file:///usr/share/applications/code-oss.desktop
diff --git a/linux/home/.config/plank/dock1/launchers/com.obsproject.Studio.dockitem b/linux/home/.config/plank/dock1/launchers/com.obsproject.Studio.dockitem
new file mode 100644
index 0000000..39554a1
--- /dev/null
+++ b/linux/home/.config/plank/dock1/launchers/com.obsproject.Studio.dockitem
@@ -0,0 +1,2 @@
+[PlankDockItemPreferences]
+Launcher=file:///home/srdusr/.local/share/applications/com.obsproject.Studio.desktop
diff --git a/linux/home/.config/plank/dock1/launchers/discord.dockitem b/linux/home/.config/plank/dock1/launchers/discord.dockitem
new file mode 100644
index 0000000..c6736ba
--- /dev/null
+++ b/linux/home/.config/plank/dock1/launchers/discord.dockitem
@@ -0,0 +1,2 @@
+[PlankDockItemPreferences]
+Launcher=file:///usr/share/applications/discord.desktop
diff --git a/linux/home/.config/plank/dock1/launchers/firefox.dockitem b/linux/home/.config/plank/dock1/launchers/firefox.dockitem
new file mode 100644
index 0000000..0dbfff7
--- /dev/null
+++ b/linux/home/.config/plank/dock1/launchers/firefox.dockitem
@@ -0,0 +1,2 @@
+[PlankDockItemPreferences]
+Launcher=file:///usr/share/applications/firefox.desktop
diff --git a/linux/home/.config/plank/dock1/launchers/obsidian.dockitem b/linux/home/.config/plank/dock1/launchers/obsidian.dockitem
new file mode 100644
index 0000000..0170dbd
--- /dev/null
+++ b/linux/home/.config/plank/dock1/launchers/obsidian.dockitem
@@ -0,0 +1,2 @@
+[PlankDockItemPreferences]
+Launcher=file:///usr/share/applications/obsidian.desktop
diff --git a/linux/home/.config/plank/dock1/launchers/org.qbittorrent.qBittorrent.dockitem b/linux/home/.config/plank/dock1/launchers/org.qbittorrent.qBittorrent.dockitem
new file mode 100644
index 0000000..7475952
--- /dev/null
+++ b/linux/home/.config/plank/dock1/launchers/org.qbittorrent.qBittorrent.dockitem
@@ -0,0 +1,2 @@
+[PlankDockItemPreferences]
+Launcher=file:///usr/share/applications/org.qbittorrent.qBittorrent.desktop
diff --git a/linux/home/.config/plank/dock1/launchers/pcmanfm.dockitem b/linux/home/.config/plank/dock1/launchers/pcmanfm.dockitem
new file mode 100644
index 0000000..bc3af9d
--- /dev/null
+++ b/linux/home/.config/plank/dock1/launchers/pcmanfm.dockitem
@@ -0,0 +1,2 @@
+[PlankDockItemPreferences]
+Launcher=file:///usr/share/applications/pcmanfm.desktop
diff --git a/linux/home/.config/plank/dock1/launchers/phototonic.dockitem b/linux/home/.config/plank/dock1/launchers/phototonic.dockitem
new file mode 100644
index 0000000..e391893
--- /dev/null
+++ b/linux/home/.config/plank/dock1/launchers/phototonic.dockitem
@@ -0,0 +1,2 @@
+[PlankDockItemPreferences]
+Launcher=file:///home/srdusr/.local/share/applications/phototonic.desktop
diff --git a/linux/home/.config/plank/dock1/launchers/steam.dockitem b/linux/home/.config/plank/dock1/launchers/steam.dockitem
new file mode 100644
index 0000000..143d3b4
--- /dev/null
+++ b/linux/home/.config/plank/dock1/launchers/steam.dockitem
@@ -0,0 +1,2 @@
+[PlankDockItemPreferences]
+Launcher=file:///usr/share/applications/steam.desktop
diff --git a/linux/home/.config/plank/dock1/launchers/trash.dockitem b/linux/home/.config/plank/dock1/launchers/trash.dockitem
new file mode 100644
index 0000000..567a63e
--- /dev/null
+++ b/linux/home/.config/plank/dock1/launchers/trash.dockitem
@@ -0,0 +1,2 @@
+[PlankDockItemPreferences]
+Launcher=docklet://trash
diff --git a/linux/home/.config/plank/dock1/launchers/vlc.dockitem b/linux/home/.config/plank/dock1/launchers/vlc.dockitem
new file mode 100644
index 0000000..c20771a
--- /dev/null
+++ b/linux/home/.config/plank/dock1/launchers/vlc.dockitem
@@ -0,0 +1,2 @@
+[PlankDockItemPreferences]
+Launcher=file:///usr/share/applications/vlc.desktop
diff --git a/linux/home/.config/polybar/config.ini b/linux/home/.config/polybar/config.ini
new file mode 100644
index 0000000..afb14a8
--- /dev/null
+++ b/linux/home/.config/polybar/config.ini
@@ -0,0 +1,671 @@
+;==========================================================
+;
+;
+; ██████╗ ██████╗ ██╗ ██╗ ██╗██████╗ █████╗ ██████╗
+; ██╔══██╗██╔═══██╗██║ ╚██╗ ██╔╝██╔══██╗██╔══██╗██╔══██╗
+; ██████╔╝██║ ██║██║ ╚████╔╝ ██████╔╝███████║██████╔╝
+; ██╔═══╝ ██║ ██║██║ ╚██╔╝ ██╔══██╗██╔══██║██╔══██╗
+; ██║ ╚██████╔╝███████╗██║ ██████╔╝██║ ██║██║ ██║
+; ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝
+;
+;
+; To learn more about how to configure Polybar
+; go to https://github.com/polybar/polybar
+;
+; The README contains a lot of information
+;
+;==========================================================
+
+[settings]
+screenchange-reload = true
+pseudo-transparency = true
+;throttle-output = 5
+;throttle-output-for = 10
+;compositing-background = source
+;compositing-foreground = over
+;compositing-overline = over
+;compositing-underline = over
+;compositing-border = over
+
+[colors]
+foreground = #fafafa
+foreground-alt = #aaCECCC9
+;background = #aa000000
+background = #dd000000
+background-alt = #aaCECCC9
+disabled = #707880
+alert = #ff8989
+blue = #61afef
+dark_blue = #42A5F5
+light_blue = #ADD8E6
+nord = #81a1c1
+purple = #c882e7
+orange = #E57C46
+gray = #676E95
+red = #EC7875
+pink = #EC407A
+yellow = #FDD835
+amber = #FBC02D
+indigo = #6C77BB
+green = #61C766
+lime = #B9C244
+
+[fonts]
+font0 = "SF Pro Mono:style=Display Bold:size=10;2"
+font1 = "Material Design Icons:style=Bold:size=11.5;2"
+font2 = "Material Design Icons:style=Bold:size=13.5;2"
+font3 = "SF Pro:style=Medium:size=10.5;2"
+font4 = "SF Pro:style=Regular:size=13;2"
+font5 = "UbuntuMono Nerd font:size=11;2"
+font6 = "Feather:style=Regular:size=15;4"
+font7 = "Font Awesome 6 Pro Solid:style=Solid:size=14;4"
+font8 = "JetBrainsMono Nerd Font:size=9;2"
+font9 = "Fira Nerd font:size=11;2"
+font10 = "RobotoMono Nerd Font:weight=bold:size=9;2"
+font11 = "RobotoMono Nerd Font:size=10;3"
+font12 = "RobotoMono Nerd Font:size=11;3"
+
+[common]
+line-size = 1pt
+enable-ipc = true
+wm-restack = bspwm
+dpi = 96
+font-0 = ${fonts.font0}
+font-1 = ${fonts.font1}
+font-2 = ${fonts.font2}
+font-3 = ${fonts.font3}
+font-4 = ${fonts.font4}
+font-5 = ${fonts.font5}
+font-6 = ${fonts.font6}
+font-7 = ${fonts.font7}
+font-8 = ${fonts.font8}
+font-9 = ${fonts.font9}
+font-10 = ${fonts.font10}
+font-11 = ${fonts.font11}
+font-12 = ${fonts.font12}
+background = ${colors.background}
+foreground = ${colors.foreground}
+
+[bar/main-0]
+monitor = ${env:MONITOR:}
+width = 99%
+offset-x = 0.5%
+offset-y = 0.5%
+height = 20pt
+radius = 10.0
+fixed-center = true
+inherit = common
+;modules-left = space space menu space space space bspwm space space
+modules-left = space space menu space space space bspwm space space big_space space space cpu space sep space memory space sep space temperature space sep space battery
+modules-center = space space date space space
+;modules-center = space space cpu space sep space memory space sep space temperature space sep space battery big_space date big_space wireless-network space sep space netspeed space sep space vpn space space
+;modules-right = space space mic space sep space volume space sep space backlight space sep space inbox space sep space systray space space sep control space space
+modules-right = space space wireless-network space sep space netspeed space sep space vpn space space big_space space space mic space sep space volume space sep space backlight space sep space inbox space sep space systray space space sep control space space
+
+[bar/main-1]
+monitor = ${env:MONITOR:}
+;width = 260px
+width = 180px
+offset-x = 0%:+10px
+offset-y = 0.5%
+height = 20pt
+radius = 10.0
+fixed-center = true
+inherit = common
+modules-left = space space menu space space space bspwm space space
+
+[bar/main-2]
+monitor = ${env:MONITOR:}
+;width = 230px
+;offset-x = 33.3333%:-130px
+width = 295px
+offset-x = 33.3333%:-195px
+offset-y = 0.5%
+height = 20pt
+radius = 10.0
+fixed-center = true
+inherit = common
+modules-center = space space cpu space sep space memory space sep space temperature space sep space battery space space
+
+[bar/main-3]
+monitor = ${env:MONITOR:}
+;width = 220px
+;width = 150px
+width = 170px
+;offset-x = 50%:-110px
+;offset-x = 50%:-110px
+offset-x = 50%:-85px
+offset-y = 0.5%
+height = 20pt
+radius = 10.0
+fixed-center = true
+inherit = common
+font-0 = "RobotoMono Nerd Font:weight=bold:size=9;2"
+font-1 = "RobotoMono Nerd Font:size=10;3"
+font-2 = "RobotoMono Nerd Font:size=11;3"
+;modules-center = space space day space sep space date space sep space time space space
+modules-center = space space date space space
+
+[bar/main-4]
+monitor = ${env:MONITOR:}
+;width = 230px
+;offset-x = 66.6667%:-100px
+width = 295px
+offset-x = 66.6667%:-100px
+offset-y = 0.5%
+height = 20pt
+radius = 10.0
+fixed-center = true
+;padding-right = 4
+inherit = common
+;modules-center = space space space sep space vpn space space
+modules-center = space space wireless-network space sep space netspeed space sep space vpn space space
+;modules-center = space space wireless-network netspeed space sep space space space
+
+[bar/main-5]
+monitor = ${env:MONITOR:}
+;width = 260px
+width = 180px
+offset-x = 100%:-190px
+offset-y = 0.5%
+height = 20pt
+radius = 10.0
+fixed-center = true
+padding-left = 2
+;padding-right = 2
+inherit = common
+modules-right = space space mic space sep space volume space sep space backlight space sep space inbox space sep space systray space space sep control space space
+
+
+;; Modules
+
+[module/bspwm]
+type = internal/bspwm
+format = <label-state>
+format-padding = 2
+format-foreground = ${colors.foreground}
+index-sort = true
+enable-click = true
+reverse-scroll = false
+label-focused = ●
+label-focused-padding = 1
+label-occupied = "%name%"
+label-occupied-foreground = ${colors.foreground}
+label-occupied-padding = 1
+;label-empty = ○
+label-empty="%name%"
+label-empty-foreground = ${colors.disabled}
+label-empty-padding = 1
+format-font = 5
+
+[module/xwindow]
+type = internal/xwindow
+format = <label>
+format-background = ${colors.background}
+format-foreground = ${colors.foreground}
+format-padding = 2
+label = %title%
+label-maxlen = 40
+label-empty = ~/
+label-empty-foreground = ${colors.disabled}
+
+[module/volume]
+type = internal/pulseaudio
+;format-volume = <label-volume> <bar-volume>
+;label-volume = 
+;label-volume-foreground = ${colors.foreground}
+;label-muted =  muted
+;bar-volume-width = 10
+;bar-volume-foreground-0 = #55aa55
+;bar-volume-foreground-1 = #55aa55
+;bar-volume-foreground-2 = #55aa55
+;bar-volume-foreground-3 = #55aa55
+;bar-volume-foreground-4 = #55aa55
+;bar-volume-foreground-5 = #f5a70a
+;bar-volume-foreground-6 = #ff5555
+;bar-volume-gradient = false
+;bar-volume-indicator = │
+;bar-volume-indicator-font = 2
+;bar-volume-indicator-foreground = #ff
+;bar-volume-fill = ─
+;bar-volume-fill-font = 2
+;bar-volume-empty = ─
+;bar-volume-empty-font = 2
+;bar-volume-empty-foreground =
+
+;format-volume = <ramp-volume> <label-volume
+format-volume = <ramp-volume>
+format-volume-font = 9
+format-volume-padding = 0
+label-volume = %percentage:2%%
+label-volume-padding = 1
+;label-muted-foreground = ${colors.foreground}
+;format-volume-foreground = ${colors.purple}
+format-muted-foreground = ${colors.red}
+format-muted-underline = ${colors.red}
+ramp-volume-foreground = ${colors.foreground}
+;label-muted = "muted"
+label-muted = "  "
+label-muted-foreground = ${colors.disabled}
+ramp-volume-0 = "  "
+ramp-volume-1 = "  "
+ramp-volume-2 = "  "
+ramp-volume-3 = "  "
+ramp-volume-4 = " "
+ramp-volume-5 = " "
+ramp-volume-6 = " "
+click-right = "pavucontrol"
+scroll-interval = 10
+
+[module/inbox]
+type = custom/text
+content-foreground = ${colors.foreground}
+;content-padding = 1
+;content-font = 3
+content = "󰮒"
+;content = " "
+;󰧬󰮒󰻨
+click-left = ~/.config/eww/scripts/openNotificationCenter.sh
+;click-left = notification-center
+
+[module/day]
+type = internal/date
+interval = 1
+date = %a
+label = %date%
+label-foreground = ${colors.foreground}
+
+;[module/day]
+;type = internal/date
+;interval = 1
+;date = %A
+;label = %date:8%
+;label-foreground = ${colors.foreground}
+;
+;[module/date]
+;type = internal/date
+;interval = 1
+;;date = %d-%m-%Y
+;date = %d %b %Y
+;label = %date%
+;label-foreground = ${colors.foreground}
+;format = %{A1:$HOME/.config/eww/scripts/popup calendar &:}<label>%{A}
+
+[module/date]
+type = internal/date
+interval = 1
+label = %date% %time%
+;label-padding = 2.5
+label-background =
+date = %a %d %b %Y
+time = %H:%M:%S
+format-font = 11
+
+
+[module/calendar]
+type = custom/text
+content = 󰸗
+;content-font = 1
+content-padding = 1
+content-foreground = ${colors.primary}
+enable-click = true
+click-left = ~/.config/eww/scripts/popup calendar &
+
+[module/time]
+type = internal/date
+interval = 1
+date = %H:%M:%S
+label = %date%
+label-foreground = ${colors.foreground}
+
+[module/memory]
+type=internal/memory
+interval=5
+format=<label>
+format-font=8
+format-prefix="󰨅"
+;󰘚󰥜󰥠󰨅
+format-foreground=${colors.foreground}
+format-prefix-foreground=${colors.foreground}
+label-font=2
+label-foreground=${colors.foreground}
+label="%{A1:alacritty -e htop &:} %gb_used%%{A}"
+
+[module/cpu]
+type=internal/cpu
+interval=5
+format-prefix-font=4
+format-prefix="󰍛 "
+format-padding=0
+;format-prefix-foreground=${colors.green}
+format-prefix-foreground=${colors.foreground}
+format-foreground=${colors.foreground}
+label="%percentage%%"
+label-foreground=${colors.foreground}
+label-font=2
+
+;[module/temperature]
+;type=internal/temperature
+;; Seconds to sleep between updates
+;; Default: 1
+;interval=10
+;; Thermal zone to use
+;; To list all the zone types, run
+;; $ for i in /sys/class/thermal/thermal_zone*; do echo "$i: $(<$i/type)"; done
+;; Default: 0
+;thermal-zone=0
+;; Full path of temperature sysfs path
+;; Use `sensors` to find preferred temperature source, then run
+;; $ for i in /sys/class/hwmon/hwmon*/temp*_input; do echo "$(<$(dirname $i)/name): $(cat ${i%_*}_label 2>/dev/null || echo $(basename ${i%_*})) $(readlink -f $i)"; done
+;; to find path to desired file
+;; Default reverts to thermal zone setting
+;;hwmon-path=/sys/devices/platform/dell_smm_hwmon/hwmon/hwmon2/temp1_input
+;hwmon-path=/sys/devices/platform/dell_smm_hwmon/hwmon/hwmon1/temp1_input
+;; Base temperature for where to start the ramp (in degrees celsius)
+;; Default: 0
+;base-temperature=20
+;warn-temperature=60
+;; Threshold temperature to display warning label (in degrees celsius)
+;; Default: 80
+;format-prefix="  "
+;format-warn-prefix="  "
+;format-warn-foreground=${colors.red}
+;format-foreground=${colors.foreground}
+;format-font=4
+;format-warn-font=4
+;label-warn-font=2
+;label-foreground=${colors.foreground}
+;format = "<label>"
+;label-font=2
+
+[module/temperature]
+type = custom/script
+interval = 5
+format = <label>
+format-prefix = "  "
+format-prefix-foreground = ${colors.foreground}
+exec = ~/.config/polybar/scripts/temperature.sh
+
+
+[module/battery]
+type = custom/script
+exec = $HOME/.scripts/battery.sh
+format-font = 1
+format-prefix = ""
+interval = 10
+;click-right = xfce4-power-manager-settings
+
+[module/backlight]
+type = internal/backlight
+; Use the following command to list available cards:
+; $ ls -1 /sys/class/backlight/
+; Default: first usable card in /sys/class/backlight (new in version 3.7.0)
+card = intel_backlight
+; Use the `/sys/class/backlight/.../actual-brightness` file
+; rather than the regular `brightness` file.
+; New in version 3.6.0
+; Changed in version: 3.7.0: Defaults to true also on amdgpu backlights
+; Default: true
+;use-actual-brightness = true
+; Interval in seconds after which after which the current brightness is read
+; (even if no update is detected).
+; Use this as a fallback if brightness updates are not registering in polybar
+; (which happens if the use-actual-brightness is false).
+; There is no guarantee on the precisio of this timing.
+; Set to 0 to turn off
+; New in version 3.7.0
+; Default: 0 (5 if use-actual-brightness is false)
+;poll-interval = 0
+; Enable changing the backlight with the scroll wheel
+; NOTE: This may require additional configuration on some systems. Polybar will
+; write to `/sys/class/backlight/${self.card}/brightness` which requires polybar
+; to have write access to that file.
+; DO NOT RUN POLYBAR AS ROOT.
+; The recommended way is to add the user to the
+; `video` group and give that group write-privileges for the `brightness` file.
+; See the ArchWiki for more information:
+; https://wiki.archlinux.org/index.php/Backlight#ACPI
+; Default: false
+enable-scroll = true
+; Interval for changing the brightness (in percentage points).
+; New in version 3.7.0
+; Default: 5
+scroll-interval = 10
+; Available tags:
+; <label> (default)
+; <ramp>
+; <bar>
+format = <ramp>
+format-foreground = {colors.foreground}
+; Available tokens:
+; %percentage% (default)
+label = %percentage:2%%
+label-font=7
+;; Only applies if <ramp> is used
+ramp-0 = 󰃞
+ramp-1 = 󰃝
+ramp-2 = 󰃟
+ramp-3 = 󰃠
+;; Only applies if <bar> is used
+;bar-width = 10
+;bar-indicator = |
+;bar-fill = ─
+;bar-empty = ─
+
+[module/bluetooth]
+type = custom/text
+content = ""
+format = <label>
+content-foreground = ${colors.foreground}
+; click-middle = bspc rule -a '*' -o state=floating rectangle=400x120+775+48 && kitty -e sudo polybarblue.sh
+ click-left = blueman-manager
+
+[module/control]
+type = custom/script
+exec = echo 􀜊
+format = <label>
+format-padding = 1
+label-padding =
+content-background =
+format-foreground = ${colors.foreground}
+click-left = ~/.config/eww/scripts/openControlCenter.sh
+;click-left = control-center
+;click-left = $HOME/.scripts/toggle-control &
+
+[module/wireless-network]
+type = internal/network
+interface = wlan0
+interval = 3.0
+unknown-as-up = true
+format-connected-background = ${colors.background}
+format-connected-foreground = ${colors.foreground}
+format-connected-padding = 1
+format-connected = %{A1:$HOME/.scripts/rofi-network-manager.sh:}<ramp-signal> <label-connected>%{A}
+label-connected = "%essid:03:5%/%local_ip%"
+#label-connected = "ESSID/127.0.0.1"
+format-disconnected-background = ${colors.background}
+format-disconnected-foreground = ${colors.foreground}
+format-disconnected-padding = 1
+format-disconnected = %{A1:$HOME/.scripts//rofi-network-manager.sh:}<label-disconnected>%{A}
+;label-disconnected ="Network Disconnected 󱍢 ......"
+;label-disconnected =" 󰤮 Network Disconnected ......... 󱍢 .......... "
+label-disconnected ="󰤮 Net Disconnected"
+ramp-signal-0 = "󰤯"
+ramp-signal-1 = "󰤟"
+ramp-signal-2 = "󰤢"
+ramp-signal-3 = "󰤥"
+ramp-signal-4 = "󰤨"
+ramp-signal-foreground = ${colors.white}
+enable-click = true
+click-left = $HOME/.scripts/rofi-network-manager.sh &
+
+[module/wifi]
+type = custom/script
+tail = true
+interval = 1
+format = <label>
+format-prefix = " "
+wifi = wifi
+wifi-alt = iwgetid -r
+exec = iwgetid -r
+click-left = kitty nmtui
+click-right = nm-connection-editor
+label-disconnected = %{A1:nm-connection-editor:}%essid%%{A}
+
+[module/wlan-signal]
+type = custom/script
+label = %output%
+exec = awk 'NR==3 {print $4 "00 dBm"}' /proc/net/wireless
+format-prefix = "ﴽ "
+format-prefix-foreground = ${colors.yellow}
+format-background = ${colors.background}
+format-foreground = ${colors.foreground}
+interval = 1
+
+[module/netspeed]
+type = internal/network
+;interface = ${system.sys_network_interface}
+interface-type = wireless
+interval = 3.0
+accumulate-stats = true
+;unknown-as-up = true
+format-connected = <label-connected>
+format-disconnected = <label-disconnected>
+;label-disconnected = ""
+label-disconnected = " 0 KB/s "
+format-disconnected-prefix = "󰯎"
+format-connected-prefix = "󰯎"
+speed-unit = ""
+label-connected = "%netspeed:5%B/s "
+
+[module/upspeed]
+type = internal/network
+interface-type = wireless
+interval = 1
+format-connected = <label-connected>
+format-disconnected = <label-disconnected>
+label-disconnected = ""
+format-disconnected-prefix = ""
+format-connected-prefix = " "
+label-connected = " %upspeed:8%"
+
+[module/downspeed]
+type = internal/network
+interface-type = wireless
+interval = 1
+format-connected = <label-connected>
+format-disconnected = <label-disconnected>
+label-disconnected = ""
+format-disconnected-prefix = ""
+format-connected-prefix = ""
+label-connected = " %downspeed:8%"
+
+[module/vpn]
+type = custom/script
+#exec = protonvpn status
+exec = ~/.config/polybar/scripts/vpn.sh
+;tail = true
+interval = 1
+label-font = 6
+format-prefix = " "
+format = <label>
+click-left = sudo protonvpn c -f
+click-right = sudo protonvpn disconnect
+;
+
+[module/gpu-nvidia]
+type = custom/script
+exec = $HOME/.config/polybar/scripts/gpu-nvidia.sh
+interval = 2
+format-font = 2
+format-foreground = #69F0AE
+
+[module/gpu-intel]
+type = custom/script
+#exec = $HOME/.config/polybar/scripts/gpu-intel.sh
+interval = 2
+
+[module/spotify]
+type = custom/script
+tail = true
+interval = 1
+format-prefix = " "
+format = <label>
+exec = ~/.config/polybar/scripts/get_spotify_status.sh
+
+[module/menu]
+type = custom/text
+content = 󱎂
+;󰣇󰈷󰨝󰵆􀇸􀘸􀟒􀣺􀦲
+content-font = 3
+content-padding = 1
+content-foreground = ${colors.foreground}
+enable-click = true
+click-left = ~/.config/jgmenu/scripts/startmenu.sh
+click-right = $HOME/.scripts/menu_full.sh
+
+[module/power]
+type = custom/text
+content = 󰐥
+;content = 襤
+content-foreground = ${colors.red}
+content-padding = 1
+label-margin = 3
+click-left = ~/.scripts/sysmenu.sh
+
+[module/systray]
+type=custom/ipc
+hook-0=echo " "
+hook-1=echo " "
+click-left=systray
+initial=2
+format-font=2
+format-foreground=${colors.blue}
+;format-foreground=${colors.foreground}
+
+[module/weather]
+type = custom/script
+exec = "sh ~/.config/polybar/weather.sh"
+interval = 700
+
+[module/tray]
+type = internal/tray
+format-margin = 8px
+tray-spacing = 8px
+
+[module/updates]
+type = custom/script
+tail = true
+interval = 1
+format-prefix = " "
+format = <label>
+exec = checkupdates | wc -l
+click-left = kitty yay -Syu --noconfirm
+
+[module/mic]
+type = custom/script
+interval = 0.5
+exec = $HOME/.config/polybar/scripts/microphone.sh
+format = <label>
+format-font = 9
+click-left = pamixer --source 1 -t
+scroll-up = pamixer --source 1 -i 5
+scroll-down = pamixer --source 1 -d 5
+
+
+;; decor
+
+[module/sep]
+type = custom/text
+content = "|"
+content-foreground = ${colors.disabled}
+
+[module/space]
+type = custom/text
+content = " "
+
+[module/big_space]
+type = custom/text
+content = " "
+
+; vim:ft=dosini
diff --git a/linux/home/.config/polybar/launch.sh b/linux/home/.config/polybar/launch.sh
new file mode 100755
index 0000000..5468c8e
--- /dev/null
+++ b/linux/home/.config/polybar/launch.sh
@@ -0,0 +1,31 @@
+#!/usr/bin/env sh
+
+# Terminate already running bar instances
+killall -q polybar
+
+# Wait until the processes have been shut down
+while pgrep -u "$UID" -x polybar >/dev/null; do sleep 1; done
+
+
+# Launch bar
+#polybar main-0 &
+polybar main-1 &
+polybar main-2 &
+polybar main-3 &
+polybar main-4 &
+polybar main-5 &
+
+# Define bars per monitors
+#declare -A ARRANGEMENTS=(["$mainmonitor"]="main-0" ["$secondmonitor"]="main-0")
+declare -A ARRANGEMENTS=(["$mainmonitor"]="main-1,main-2,main-3,main-4,main-5" ["$secondmonitor"]="main-1,main-2,main-3,main-4,main-5")
+
+# Each key
+for MONITOR in "${!ARRANGEMENTS[@]}"; do
+ # split at `,` into array
+ while IFS=',' read -ra BARLIST; do
+ # for each bar (seperated by `,`) at current key
+ for BAR in "${BARLIST[@]}"; do
+ MONITOR="$MONITOR" polybar --reload "$BAR" &
+ done
+ done <<< "${ARRANGEMENTS[$MONITOR]}"
+done
diff --git a/linux/home/.config/polybar/scripts/bluetooth.sh b/linux/home/.config/polybar/scripts/bluetooth.sh
new file mode 100755
index 0000000..061604b
--- /dev/null
+++ b/linux/home/.config/polybar/scripts/bluetooth.sh
@@ -0,0 +1,12 @@
+#!/bin/sh
+if [ $(bluetoothctl show | grep "Powered: yes" | wc -c) -eq 0 ]
+then
+ echo ""
+else
+ if [ $(echo info | bluetoothctl | grep 'Device' | wc -c) -eq 0 ]
+ then
+ echo ""
+ fi
+ echo ""
+fi
+
diff --git a/linux/home/.config/polybar/scripts/check-network.sh b/linux/home/.config/polybar/scripts/check-network.sh
new file mode 100755
index 0000000..dabe74c
--- /dev/null
+++ b/linux/home/.config/polybar/scripts/check-network.sh
@@ -0,0 +1,21 @@
+#!/usr/bin/env bash
+
+count=0
+disconnected="睊"
+wireless_connected="直"
+ethernet_connected="泌"
+
+ID="$(ip link | awk '/state UP/ {print $2}')"
+
+while true; do
+ if (ping -c 1 archlinux.org || ping -c 1 google.com || ping -c 1 bitbucket.org || ping -c 1 github.com || ping -c 1 sourceforge.net) &>/dev/null; then
+ if [[ $ID == e* ]]; then
+ echo "$ethernet_connected" ; sleep 25
+ else
+ echo "$wireless_connected" ; sleep 25
+ fi
+ else
+ echo "$disconnected" ; sleep 0.5
+ fi
+done
+
diff --git a/linux/home/.config/polybar/scripts/check_updates.sh b/linux/home/.config/polybar/scripts/check_updates.sh
new file mode 100755
index 0000000..52e51a9
--- /dev/null
+++ b/linux/home/.config/polybar/scripts/check_updates.sh
@@ -0,0 +1,118 @@
+
+#!/usr/bin/bash
+#
+# checkupdates: Safely print a list of pending updates.
+#
+# Copyright (c) 2013 Kyle Keen <keenerd@gmail.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+declare -r myname='checkupdates'
+declare -r myver='1.0.0'
+
+plain() {
+ (( QUIET )) && return
+ local mesg=$1; shift
+ printf "${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&1
+}
+
+msg() {
+ (( QUIET )) && return
+ local mesg=$1; shift
+ printf "${GREEN}==>${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&1
+}
+
+msg2() {
+ (( QUIET )) && return
+ local mesg=$1; shift
+ printf "${BLUE} ->${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&1
+}
+
+ask() {
+ local mesg=$1; shift
+ printf "${BLUE}::${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}" "$@" >&1
+}
+
+warning() {
+ local mesg=$1; shift
+ printf "${YELLOW}==> $(gettext "WARNING:")${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2
+}
+
+error() {
+ local mesg=$1; shift
+ printf "${RED}==> $(gettext "ERROR:")${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2
+}
+
+# check if messages are to be printed using color
+unset ALL_OFF BOLD BLUE GREEN RED YELLOW
+if [[ -t 2 && ! $USE_COLOR = "n" ]]; then
+ # prefer terminal safe colored and bold text when tput is supported
+ if tput setaf 0 &>/dev/null; then
+ ALL_OFF="$(tput sgr0)"
+ BOLD="$(tput bold)"
+ BLUE="${BOLD}$(tput setaf 4)"
+ GREEN="${BOLD}$(tput setaf 2)"
+ RED="${BOLD}$(tput setaf 1)"
+ YELLOW="${BOLD}$(tput setaf 3)"
+ else
+ ALL_OFF="\e[1;0m"
+ BOLD="\e[1;1m"
+ BLUE="${BOLD}\e[1;34m"
+ GREEN="${BOLD}\e[1;32m"
+ RED="${BOLD}\e[1;31m"
+ YELLOW="${BOLD}\e[1;33m"
+ fi
+fi
+readonly ALL_OFF BOLD BLUE GREEN RED YELLOW
+
+
+if (( $# > 0 )); then
+ echo "${myname} v${myver}"
+ echo
+ echo "Safely print a list of pending updates"
+ echo
+ echo "Usage: ${myname}"
+ echo
+ echo 'Note: Export the "CHECKUPDATES_DB" variable to change the path of the temporary database.'
+ exit 0
+fi
+
+if ! type -P fakeroot >/dev/null; then
+ error 'Cannot find the fakeroot binary.'
+ exit 1
+fi
+
+if [[ -z $CHECKUPDATES_DB ]]; then
+ CHECKUPDATES_DB="${TMPDIR:-/tmp}/checkup-db-${USER}/"
+fi
+
+trap 'rm -f $CHECKUPDATES_DB/db.lck' INT TERM EXIT
+
+DBPath="$(pacman-conf DBPath)"
+if [[ -z "$DBPath" ]] || [[ ! -d "$DBPath" ]]; then
+ DBPath="/var/lib/pacman/"
+fi
+
+mkdir -p "$CHECKUPDATES_DB"
+ln -s "${DBPath}/local" "$CHECKUPDATES_DB" &> /dev/null
+if ! fakeroot -- pacman -Sy --dbpath "$CHECKUPDATES_DB" --logfile /dev/null &> /dev/null; then
+ error 'Cannot fetch updates'
+ exit 1
+fi
+pacman -Qu --dbpath "$CHECKUPDATES_DB" 2> /dev/null | grep -v '\[.*\]'
+
+exit 0
+
+# vim: set noet:
diff --git a/linux/home/.config/polybar/scripts/cmus.sh b/linux/home/.config/polybar/scripts/cmus.sh
new file mode 100755
index 0000000..2f42c63
--- /dev/null
+++ b/linux/home/.config/polybar/scripts/cmus.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+
+prepend_zero () {
+ seq -f "%02g" $1 $1
+}
+
+artist=$(echo -n $(cmus-remote -C status | grep "tag artist" | cut -c 12-))
+
+if [[ $artist = *[!\ ]* ]]; then
+ song=$(echo -n $(cmus-remote -C status | grep title | cut -c 11-))
+ position=$(cmus-remote -C status | grep position | cut -c 10-)
+ minutes1=$(prepend_zero $(($position / 60)))
+ seconds1=$(prepend_zero $(($position % 60)))
+ duration=$(cmus-remote -C status | grep duration | cut -c 10-)
+ minutes2=$(prepend_zero $(($duration / 60)))
+ seconds2=$(prepend_zero $(($duration % 60)))
+ echo -n "$artist - $song "
+else
+ echo
+fi
+
+#prepend_zero () {
+# seq -f "%02g" $1 $1
+#}
+#
+#artist=$(echo -n $(cmus-remote -C status | grep "tag artist" | cut -c 12-))
+#
+#if [[ $artist = *[!\ ]* ]]; then
+# song=$(echo -n $(cmus-remote -C status | grep title | cut -c 11-))
+# position=$(cmus-remote -C status | grep position | cut -c 10-)
+# minutes1=$(prepend_zero $(($position / 60)))
+# seconds1=$(prepend_zero $(($position % 60)))
+# duration=$(cmus-remote -C status | grep duration | cut -c 10-)
+# minutes2=$(prepend_zero $(($duration / 60)))
+# seconds2=$(prepend_zero $(($duration % 60)))
+# echo -n "$artist - $song [$minutes1:$seconds1/$minutes2:$seconds2]"
+#else
+# echo
+#fi
diff --git a/linux/home/.config/polybar/scripts/get_spotify_status.sh b/linux/home/.config/polybar/scripts/get_spotify_status.sh
new file mode 100755
index 0000000..f04400d
--- /dev/null
+++ b/linux/home/.config/polybar/scripts/get_spotify_status.sh
@@ -0,0 +1,52 @@
+#!/bin/bash
+
+# The name of polybar bar which houses the main spotify module and the control modules.
+PARENT_BAR="now-playing"
+PARENT_BAR_PID=$(pgrep -a "polybar" | grep "$PARENT_BAR" | cut -d" " -f1)
+
+# Set the source audio player here.
+# Players supporting the MPRIS spec are supported.
+# Examples: spotify, vlc, chrome, mpv and others.
+# Use `playerctld` to always detect the latest player.
+# See more here: https://github.com/altdesktop/playerctl/#selecting-players-to-control
+#PLAYER="spotify"
+PLAYER="playerctld"
+
+# Format of the information displayed
+# Eg. {{ artist }} - {{ album }} - {{ title }}
+# See more attributes here: https://github.com/altdesktop/playerctl/#printing-properties-and-metadata
+FORMAT="{{ title }} - {{ artist }}"
+
+# Sends $2 as message to all polybar PIDs that are part of $1
+update_hooks() {
+ while IFS= read -r id
+ do
+ polybar-msg -p "$id" hook spotify-play-pause $2 1>/dev/null 2>&1
+ done < <(echo "$1")
+}
+
+PLAYERCTL_STATUS=$(playerctl --player=$PLAYER status 2>/dev/null)
+EXIT_CODE=$?
+
+if [ $EXIT_CODE -eq 0 ]; then
+ STATUS=$PLAYERCTL_STATUS
+else
+ STATUS="No player is running"
+fi
+
+if [ "$1" == "--status" ]; then
+ echo "$STATUS"
+else
+ if [ "$STATUS" = "Stopped" ]; then
+ echo "No music is playing"
+ elif [ "$STATUS" = "Paused" ]; then
+ update_hooks "$PARENT_BAR_PID" 2
+ playerctl --player=$PLAYER metadata --format "$FORMAT"
+ elif [ "$STATUS" = "No player is running" ]; then
+ echo ""
+ else
+ update_hooks "$PARENT_BAR_PID" 1
+ playerctl --player=$PLAYER metadata --format "$FORMAT"
+ fi
+fi
+
diff --git a/linux/home/.config/polybar/scripts/menu.sh b/linux/home/.config/polybar/scripts/menu.sh
new file mode 100755
index 0000000..cd95ee0
--- /dev/null
+++ b/linux/home/.config/polybar/scripts/menu.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+# Specify the path to the Rofi configuration file
+config_file="$HOME/.config/rofi/styles/appmenu.rasi"
+
+rofi -no-lazy-grab -show drun -display-drun "Applications " -drun-display-format "{name}" -sep -config "$config_file"
diff --git a/linux/home/.config/polybar/scripts/menu.shsave b/linux/home/.config/polybar/scripts/menu.shsave
new file mode 100755
index 0000000..ea5bf8e
--- /dev/null
+++ b/linux/home/.config/polybar/scripts/menu.shsave
@@ -0,0 +1,63 @@
+#!/bin/bash
+
+# Custom Rofi Script
+
+BORDER="#1F1F1F"
+SEPARATOR="#1F1F1F"
+FOREGROUND="#A9ABB0"
+BACKGROUND="#1F1F1F"
+BACKGROUND_ALT="#252525"
+HIGHLIGHT_BACKGROUND="#FF6F00"
+HIGHLIGHT_FOREGROUND="#FFFFFF"
+
+BLACK="#000000"
+WHITE="#ffffff"
+RED="#e53935"
+GREEN="#43a047"
+YELLOW="#fdd835"
+BLUE="#1e88e5"
+MAGENTA="#00897b"
+CYAN="#00acc1"
+PINK="#d81b60"
+PURPLE="#8e24aa"
+INDIGO="#3949ab"
+TEAL="#00897b"
+LIME="#c0ca33"
+AMBER="#ffb300"
+ORANGE="#fb8c00"
+BROWN="#6d4c41"
+GREY="#757575"
+BLUE_GREY="#546e7a"
+DEEP_PURPLE="#5e35b1"
+DEEP_ORANGE="#f4511e"
+LIGHT_BLUE="#039be5"
+LIGHT_GREEN="#7cb342"
+
+# Launch Rofi
+rofi -no-lazy-grab -show drun -display-drun "Applications " -drun-display-format "{name}" -hide-scrollbar false \
+-hide-sidebar-mode true \
+-bw 0 \
+-lines 15 \
+-line-padding 10 \
+-padding 0 \
+-width 20 \
+-xoffset 7 -yoffset 28 \
+-location 1 \
+-columns 1 \
+-show-icons -icon-theme "Papirus" \
+-color-enabled true \
+-color-window "$BACKGROUND,$BORDER,$SEPARATOR" \
+-color-normal "$BACKGROUND_ALT,$FOREGROUND,$BACKGROUND_ALT,$HIGHLIGHT_BACKGROUND,$HIGHLIGHT_FOREGROUND" \
+-color-active "$BACKGROUND,$MAGENTA,$BACKGROUND_ALT,$HIGHLIGHT_BACKGROUND,$HIGHLIGHT_FOREGROUND" \
+-color-urgent "$BACKGROUND,$YELLOW,$BACKGROUND_ALT,$HIGHLIGHT_BACKGROUND,$HIGHLIGHT_FOREGROUND"
+#rofi -no-lazy-grab -show drun -display-drun "Applications " -drun-display-format "{name}" -hide-scrollbar true -sidebar-mode false -bw 0 -lines 6 -line-padding 10 -padding 20 -width 30 -xoffset 7 -yoffset 25 -location 1 -columns 2 -show-icons -icon-theme "Papirus"
+
+# More Options
+# -fullscreen \
+
+# Theming help
+# color window = background, border, separator
+# color normal = background, foreground, background-alt, highlight-background, highlight-foreground
+# color active = background, foreground, background-alt, highlight-background, highlight-foreground
+# color urgent = background, foreground, background-alt, highlight-background, highlight-foreground
+
diff --git a/linux/home/.config/polybar/scripts/menu_full.sh b/linux/home/.config/polybar/scripts/menu_full.sh
new file mode 100755
index 0000000..9f898a4
--- /dev/null
+++ b/linux/home/.config/polybar/scripts/menu_full.sh
@@ -0,0 +1,65 @@
+
+#!/bin/bash
+
+# Custom Rofi Script
+
+BORDER="#1F1F1F"
+SEPARATOR="#1F1F1F"
+FOREGROUND="#A9ABB0"
+BACKGROUND="#1F1F1F"
+BACKGROUND_ALT="#252525"
+HIGHLIGHT_BACKGROUND="#FF6F00"
+HIGHLIGHT_FOREGROUND="#FFFFFF"
+
+BLACK="#000000"
+WHITE="#ffffff"
+RED="#e53935"
+GREEN="#43a047"
+YELLOW="#fdd835"
+BLUE="#1e88e5"
+MAGENTA="#00897b"
+CYAN="#00acc1"
+PINK="#d81b60"
+PURPLE="#8e24aa"
+INDIGO="#3949ab"
+TEAL="#00897b"
+LIME="#c0ca33"
+AMBER="#ffb300"
+ORANGE="#fb8c00"
+BROWN="#6d4c41"
+GREY="#757575"
+BLUE_GREY="#546e7a"
+DEEP_PURPLE="#5e35b1"
+DEEP_ORANGE="#f4511e"
+LIGHT_BLUE="#039be5"
+LIGHT_GREEN="#7cb342"
+
+# Launch Rofi
+rofi -no-lazy-grab -show drun \
+-display-drun "Applications " -drun-display-format "{name}" \
+-hide-scrollbar true \
+-bw 0 \
+-lines 10 \
+-line-padding 15 \
+-padding 60 \
+-width 30 \
+-xoffset 10 -yoffset 40 \
+-location 1 \
+-fullscreen \
+-columns 4 \
+-show-icons -icon-theme "Papirus" \
+-font "Fantasque Sans Mono 10" \
+-color-enabled true \
+-color-window "$BACKGROUND,$BORDER,$SEPARATOR" \
+-color-normal "$BACKGROUND_ALT,$FOREGROUND,$BACKGROUND_ALT,$HIGHLIGHT_BACKGROUND,$HIGHLIGHT_FOREGROUND" \
+-color-active "$BACKGROUND,$MAGENTA,$BACKGROUND_ALT,$HIGHLIGHT_BACKGROUND,$HIGHLIGHT_FOREGROUND" \
+-color-urgent "$BACKGROUND,$YELLOW,$BACKGROUND_ALT,$HIGHLIGHT_BACKGROUND,$HIGHLIGHT_FOREGROUND"
+
+# More Options
+# -fullscreen \
+
+# Theming help
+# color window = background, border, separator
+# color normal = background, foreground, background-alt, highlight-background, highlight-foreground
+# color active = background, foreground, background-alt, highlight-background, highlight-foreground
+# color urgent = background, foreground, background-alt, highlight-background, highlight-foreground
diff --git a/linux/home/.config/polybar/scripts/now-playing.sh b/linux/home/.config/polybar/scripts/now-playing.sh
new file mode 100755
index 0000000..8fa6000
--- /dev/null
+++ b/linux/home/.config/polybar/scripts/now-playing.sh
@@ -0,0 +1,217 @@
+#!/usr/bin/env python3
+import dbus
+import signal
+import time
+from unicodedata import east_asian_width
+
+# Config options
+
+# (int) : Length of media info string. If length of string exceedes this value, the text will scroll. Default value is 20
+message_display_len = 20
+
+# (int) : Font index of polybar. this value should be 1 more than the font value specified in polybar config.
+font_index = 1
+
+# (float) : Update speed of the text in seconds.
+update_delay = 0.3
+
+# (list) : list of chars containing previous, play, pause, next glyphs for media controls in respective order
+control_chars = ['','','','']
+
+# (dict) : dict of char icons to display as prefix.
+# If player name is available as key, then use the corressponding icon,
+# else default key value.
+# example:
+display_player_prefix = {
+ "spotify": ' ',
+ "firefox": ' ',
+ "default": ' '
+}
+
+# (list) : list of metadata fields based on mpris sepecification.
+# For more details/ field names, refer [mpris sepecification](https://www.freedesktop.org/wiki/Specifications/mpris-spec/metadata/)
+metadata_fields = ["xesam:title", "xesam:artist"]
+
+# (char) : separator for metadata fields
+metadata_separator = "-"
+
+# (bool) : Hide text when no player is available? True disables the output for no players.
+hide_output = False
+
+# Defult initialization
+current_player = None
+player_names = None
+players = None
+message = None
+display_text = ""
+display_prefix = " "
+display_suffix = ""
+last_player_name = None
+
+session_bus = dbus.SessionBus()
+
+def get_name(player_name ):
+ if player_name not in player_names:
+ return
+ name = ".".join(player_name.split(".")[3:])
+ return name
+
+def get_name_by_index(index):
+ if index >= len(player_names):
+ return
+ return get_name(player_names[index])
+
+def get_status(player):
+ status = ""
+ try:
+ status = player.Get('org.mpris.MediaPlayer2.Player', 'PlaybackStatus', dbus_interface='org.freedesktop.DBus.Properties')
+ except Exception as e:
+ pass
+ return status
+
+def get_metadata(player):
+ metadata = {}
+ try:
+ metadata = player.Get('org.mpris.MediaPlayer2.Player', 'Metadata', dbus_interface='org.freedesktop.DBus.Properties')
+ except Exception as e:
+ pass
+ return metadata
+
+def update_prefix_suffix(player_name="", status=""):
+ global display_prefix, display_suffix, status_paused
+
+ player_option = ""
+ if player_name != "":
+ player_option = "-p " + player_name
+
+ prev_button = "%%{A:playerctl %s previous :}%c%%{A}" %(player_option,control_chars[0])
+ play_button = "%%{A:playerctl %s play :}%c%%{A}" %(player_option,control_chars[1])
+ pause_button = "%%{A:playerctl %s pause :}%c%%{A}" %(player_option,control_chars[2])
+ next_button = "%%{A:playerctl %s next :}%c%%{A}" %(player_option,control_chars[3])
+
+ suffix = "| " + prev_button
+ if status == "Playing":
+ suffix += " "+pause_button
+ status_paused = False
+ else:
+ suffix += " "+play_button
+ status_paused = True
+ suffix += " "+next_button
+ # print(suffix)
+ display_suffix = suffix
+
+ for key in display_player_prefix.keys():
+ if key in player_name:
+ display_prefix = display_player_prefix[key]
+ break
+ else:
+ display_prefix = display_player_prefix["default"]
+
+def update_players():
+ global player_names, players, session_bus, current_player, last_player_name
+ player_names = [service for service in session_bus.list_names() if service.startswith('org.mpris.MediaPlayer2.')]
+ players = [session_bus.get_object(service, '/org/mpris/MediaPlayer2') for service in player_names]
+ if last_player_name != get_name(current_player):
+ for index, player in enumerate(player_names):
+ if get_name(player) == last_player_name:
+ current_player = index
+
+def handle_event(*args):
+ global current_player, players, last_player_name
+ update_players()
+ if len(players) == 0:
+ return
+ current_player += 1
+ current_player %= len(players)
+ last_player_name = getname_by_index(current_player)
+# print("SIGUSR1: updated values - current_player = %d players len = %d"%(current_player,len(players)))
+
+def update_message():
+ global players, current_player,player_names, message, display_text, message_display_len, display_suffix, last_player_name
+ if len(players) == 0:
+ tmp_message = "No player available"
+ update_prefix_suffix()
+ else:
+ name = get_name_by_index(current_player)
+ status = get_status(players[current_player])
+ metadata_obj = get_metadata(players[current_player])
+ metadata_string_list = []
+ for field in metadata_fields:
+ result = metadata_obj.get(field)
+ if type(result) == dbus.Array:
+ result = result[0]
+ if not result:
+ result = "No "+field.split(":")[1]
+ metadata_string_list.append(str(result))
+ metadata_string = (" "+metadata_separator+" ").join(metadata_string_list)
+ if visual_len(metadata_string) > message_display_len:
+ metadata_string = " " + metadata_string + " |"
+ update_prefix_suffix(name,status)
+ tmp_message = ""
+ if metadata_string:
+ tmp_message += str(metadata_string)
+ last_player_name = name
+ if message != tmp_message:
+ message = tmp_message
+ display_text = message
+
+def scroll():
+ global display_text, message_display_len, status_paused
+ if not status_paused:
+ if len(display_text) > message_display_len:
+ display_text = display_text[1:] + display_text[0]
+ elif len(display_text) < message_display_len:
+ display_text += " "*(message_display_len - len(display_text))
+
+def visual_len(text):
+ visual_length = 0
+ for ch in text:
+ width = east_asian_width(ch)
+ if width == 'W' or width == 'F':
+ visual_length += 1
+ visual_length += 1
+ return visual_length
+
+def make_visual_len(text, visual_desired_length):
+ visual_length = 0
+ altered_text = ''
+ for char in text:
+ if visual_length < visual_desired_length:
+ width = east_asian_width(char)
+ if width == 'W' or width == 'F':
+ visual_length += 2
+ else:
+ visual_length += 1
+ altered_text += char
+ else:
+ break
+ if visual_length == visual_desired_length + 1:
+ altered_text = altered_text[:-1] + ' '
+ elif visual_length < visual_desired_length:
+ altered_text += ' ' * (visual_desired_length - visual_length)
+ return altered_text
+
+def print_text():
+ global display_text, message_display_len, players, player_names, display_prefix, display_suffix
+ if hide_output and len(players)==0:
+ print("", flush = True)
+ return
+ scroll()
+ print(display_prefix + " " +
+ "%%{T%d}" % (font_index) +
+ make_visual_len(display_text, message_display_len) +
+ "%{T-}" + display_suffix, flush=True)
+
+def main():
+ global current_player, players
+ update_players()
+ current_player = 0
+ while True:
+ time.sleep(update_delay)
+ update_players()
+ update_message()
+ print_text()
+
+if __name__ == '__main__':
+ signal.signal(signal.SIGUSR1, handle_event)
+ main()
diff --git a/linux/home/.config/polybar/scripts/polybar_wrapper b/linux/home/.config/polybar/scripts/polybar_wrapper
new file mode 100755
index 0000000..901bb28
--- /dev/null
+++ b/linux/home/.config/polybar/scripts/polybar_wrapper
@@ -0,0 +1,92 @@
+#!/bin/bash
+
+DIR=$(dirname $(realpath $0))
+
+WINDOW_ID_CONKY=/tmp/conky_window_id
+WINDOW_ID_TOP=/tmp/polybar_top_window_id
+WINDOW_ID_EXPANDED=/tmp/polybar_expanded_window_id
+
+conky_launch() {
+ # Hacky X11 magic to make Conky appear above polybar
+ killall conky
+ # xdotool search can't find Conky's window but fortunately Conky outputs it
+ conky -c ~/.config/conky/config 2> /tmp/conky_out
+ # Extract the hex window id from Conky's output
+ HEX=$(awk '/drawing to created window/ {print $NF}' /tmp/conky_out | tr -d '()' | awk -Fx '{print $2}')
+ WIN_ID=$(( 16#$HEX )) # convert to decimal
+ xdotool windowunmap $WIN_ID
+ echo $WIN_ID > $WINDOW_ID_CONKY
+}
+
+polybar_launch() {
+ killall polybar
+
+ polybar top &
+ xdotool search --sync --pid $! > $WINDOW_ID_TOP
+
+ polybar expanded &
+ xdotool search --sync --pid $! > $WINDOW_ID_EXPANDED
+
+ bar_collapse
+}
+
+launch() {
+ # Temporarily disable conky until I update the config
+ # conky_launch
+ # sleep 0.2
+ polybar_launch
+}
+
+bar_expand() {
+ xdotool windowmap $(cat $WINDOW_ID_EXPANDED)
+ xdotool windowunmap $(cat $WINDOW_ID_TOP)
+}
+
+bar_collapse() {
+ xdotool windowunmap $(cat $WINDOW_ID_EXPANDED)
+ xdotool windowmap $(cat $WINDOW_ID_TOP)
+}
+
+rofi_open() {
+ options_close
+ bar_expand &
+ rofi -modi run -show run
+ bar_collapse
+}
+
+drun_open() {
+ bar_expand &
+ rofi -theme drun -modi drun -show drun -drun-categories Custom
+ bar_collapse
+}
+
+search_open() {
+ options_close
+ bar_expand &
+ rofi -theme window -modi window -show window
+ bar_collapse
+}
+
+options_open() {
+ bar_expand
+ $DIR/rofi_option_menu
+ bar_collapse
+ # echo "open" > /tmp/polybar_side_panel_state
+ # ID_CONKY=$(cat $WINDOW_ID_CONKY)
+ # xdotool windowmap $ID_CONKY
+ # xdotool windowraise $ID_CONKY
+ # ~/.config/i3/scripts/music_player show_applet
+}
+
+case "$1" in
+ rofi)
+ rofi_open;;
+ search)
+ search_open;;
+ drun)
+ drun_open;;
+ options)
+ options_open;;
+ launch)
+ launch;;
+esac
diff --git a/linux/home/.config/polybar/scripts/popup-calendar.sh b/linux/home/.config/polybar/scripts/popup-calendar.sh
new file mode 100755
index 0000000..4e5303c
--- /dev/null
+++ b/linux/home/.config/polybar/scripts/popup-calendar.sh
@@ -0,0 +1,41 @@
+#!/bin/sh
+
+BAR_HEIGHT=22 # polybar height
+BORDER_SIZE=1 # border size from your wm settings
+YAD_WIDTH=222 # 222 is minimum possible value
+YAD_HEIGHT=193 # 193 is minimum possible value
+DATE="$(date +"%a %d %b, %H:%M")"
+
+case "$1" in
+--popup)
+ if [ "$(xdotool getwindowfocus getwindowname)" = "yad-calendar" ]; then
+ exit 0
+ fi
+
+ eval "$(xdotool getmouselocation --shell)"
+ eval "$(xdotool getdisplaygeometry --shell)"
+
+ # X
+ if [ "$((X + YAD_WIDTH / 2 + BORDER_SIZE))" -gt "$WIDTH" ]; then #Right side
+ : $((pos_x = WIDTH - YAD_WIDTH - BORDER_SIZE))
+ elif [ "$((X - YAD_WIDTH / 2 - BORDER_SIZE))" -lt 0 ]; then #Left side
+ : $((pos_x = BORDER_SIZE))
+ else #Center
+ : $((pos_x = X - YAD_WIDTH / 2))
+ fi
+
+ # Y
+ if [ "$Y" -gt "$((HEIGHT / 2))" ]; then #Bottom
+ : $((pos_y = HEIGHT - YAD_HEIGHT - BAR_HEIGHT - BORDER_SIZE))
+ else #Top
+ : $((pos_y = BAR_HEIGHT + BORDER_SIZE))
+ fi
+
+ yad --calendar --undecorated --fixed --close-on-unfocus --no-buttons \
+ --width="$YAD_WIDTH" --height="$YAD_HEIGHT" --posx="$pos_x" --posy="$pos_y" \
+ --title="yad-calendar" --borders=0 >/dev/null &
+ ;;
+*)
+ echo "$DATE"
+ ;;
+esac
diff --git a/linux/home/.config/polybar/scripts/rofi-power.sh b/linux/home/.config/polybar/scripts/rofi-power.sh
new file mode 100755
index 0000000..87ac92c
--- /dev/null
+++ b/linux/home/.config/polybar/scripts/rofi-power.sh
@@ -0,0 +1,45 @@
+#!/usr/bin/env sh
+#
+# A rofi powered menu to execute power related action.
+# Uses: amixer mpc poweroff reboot rofi rofi-prompt
+
+power_off=''
+reboot=''
+lock=''
+suspend='鈴'
+log_out=''
+
+chosen=$(printf '%s;%s;%s;%s;%s\n' "$power_off" "$reboot" "$lock" "$suspend" \
+ "$log_out" \
+ | rofi -theme '~/.config/rofi/themes/power.rasi' \
+ -dmenu \
+ -sep ';' \
+ -selected-row 2)
+
+case "$chosen" in
+ "$power_off")
+ rofi-prompt --query 'Shutdown?' && poweroff
+ ;;
+
+ "$reboot")
+ rofi-prompt --query 'Reboot?' && reboot
+ ;;
+
+ "$lock")
+ # TODO Add your lockscreen command.
+ ;;
+
+ "$suspend")
+ # Pause music and mute volume before suspending.
+ mpc --quiet pause
+ amixer set Master mute
+ # TODO Add your suspend command.
+ ;;
+
+ "$log_out")
+ # TODO Add your log out command.
+ ;;
+
+ *) exit 1 ;;
+esac
+
diff --git a/linux/home/.config/polybar/scripts/scroll_spotify_status.sh b/linux/home/.config/polybar/scripts/scroll_spotify_status.sh
new file mode 100755
index 0000000..74e0bfd
--- /dev/null
+++ b/linux/home/.config/polybar/scripts/scroll_spotify_status.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+# see man zscroll for documentation of the following parameters
+zscroll -l 20 \
+ --delay 0.1 \
+ --scroll-padding " " \
+ --match-command "$HOME/.config/polybar/scripts/get_spotify_status.sh --status" \
+ --match-text "Playing" "--scroll 1" \
+ --match-text "Paused" "--scroll 0" \
+ --update-check true "$HOME/.config/polybar/scripts/get_spotify_status.sh" &
+
+wait
diff --git a/linux/home/.config/polybar/scripts/sysmenu.sh b/linux/home/.config/polybar/scripts/sysmenu.sh
new file mode 100755
index 0000000..a3a7a2a
--- /dev/null
+++ b/linux/home/.config/polybar/scripts/sysmenu.sh
@@ -0,0 +1,42 @@
+#!/bin/bash
+
+# Display a power menu to: shutdown, reboot,
+# lock, logout, and suspend. This script can be
+# executed by clicking on the polybar powermenu module
+# or with a keyboard shortcut
+
+# Options to be displayed
+shutdown=" Shutdown"
+reboot=" Reboot"
+lock=" Lock"
+logout=" Logout"
+suspend=" Suspend"
+
+uptime=$(uptime -p | sed -e 's/up //g')
+
+# Options passed into variable
+options="$shutdown\n$reboot\n$lock\n$logout\n$suspend"
+
+# Specify the path to the Rofi configuration file
+config_file="$HOME/.config/rofi/styles/powermenu.rasi"
+
+# Show Rofi with the specified configuration file
+chosen="$(echo -e "$options" | rofi -no-lazy-grab -sep -config "$config_file" -dmenu -p 'System ' "$uptime")"
+
+case $chosen in
+ $shutdown)
+ shutdown now
+ ;;
+ $reboot)
+ systemctl reboot
+ ;;
+ $lock)
+ betterlockscreen --lock dimblur
+ ;;
+ $logout)
+ bspc quit
+ ;;
+ $suspend)
+ systemctl suspend
+ ;;
+esac
diff --git a/linux/home/.config/polybar/scripts/sysmenu.shsave b/linux/home/.config/polybar/scripts/sysmenu.shsave
new file mode 100755
index 0000000..00ce125
--- /dev/null
+++ b/linux/home/.config/polybar/scripts/sysmenu.shsave
@@ -0,0 +1,40 @@
+#!/bin/bash
+
+# display a power menu to: shutdown, reboot,
+# lock, logout, and suspend. This script can be
+# executed by clicking on the polybar powermenu module
+# or with a keyboard shortcut
+
+
+# options to be displayed
+shutdown=" Shutdown"
+reboot=" Reboot"
+lock=" Lock"
+logout=" Logout"
+suspend=" Suspend"
+
+uptime=$(uptime -p | sed -e 's/up //g')
+
+# options passed into variable
+options="$shutdown\n$reboot\n$lock\n$logout\n$suspend"
+
+chosen="$(echo -e "$options" | rofi -no-lazy-grab -sep -lines 5 -hide-scrollbar true -border 0 -padding 0 -height 2px -width 15 -xoffset -10 -yoffset 28 -location 3 -columns 1 -dmenu -p 'System ' "$uptime")"
+
+case $chosen in
+$shutdown)
+ systemctl poweroff
+ ;;
+$reboot)
+ systemctl reboot
+ ;;
+$lock)
+ betterlockscreen --lock dimblur
+ ;;
+$logout)
+ bspc quit
+ ;;
+$suspend)
+ systemctl suspend
+ ;;
+esac
+
diff --git a/linux/home/.config/polybar/scripts/system-usb-mount.sh b/linux/home/.config/polybar/scripts/system-usb-mount.sh
new file mode 100755
index 0000000..63e9187
--- /dev/null
+++ b/linux/home/.config/polybar/scripts/system-usb-mount.sh
@@ -0,0 +1,53 @@
+#!/bin/sh
+
+devices=$(lsblk -Jplno NAME,TYPE,RM,SIZE,MOUNTPOINT,VENDOR)
+
+case "$1" in
+ --mount)
+ for mount in $(echo "$devices" | jq -r '.blockdevices[] | select(.type == "part") | select(.rm == true) | select(.mountpoint == null) | .name'); do
+ udisksctl mount --no-user-interaction -b "$mount"
+
+ mountpoint=$(udisksctl mount --no-user-interaction -b $mount)
+ mountpoint=$(echo $mountpoint | cut -d " " -f 4- | tr -d ".")
+ kitty -e "bash -lc 'pcmanfm $mountpoint'" &
+ done
+ ;;
+ --unmount)
+ for unmount in $(echo "$devices" | jq -r '.blockdevices[] | select(.type == "part") | select(.rm == true) | select(.mountpoint != null) | .name'); do
+ udisksctl unmount --no-user-interaction -b "$unmount"
+ udisksctl power-off --no-user-interaction -b "$unmount"
+ done
+ ;;
+ *)
+ output=""
+ counter=0
+
+ for unmounted in $(echo "$devices" | jq -r '.blockdevices[] | select(.type == "part") | select(.rm == true) | select(.mountpoint == null) | .name'); do
+ unmounted=$(echo "$unmounted" | tr -d "[:digit:]")
+ unmounted=$(echo "$devices" | jq -r '.blockdevices[] | select(.name == "'"$unmounted"'") | .vendor')
+ unmounted=$(echo "$unmounted" | tr -d ' ')
+
+ if [ $counter -eq 0 ]; then
+ space=""
+ else
+ space=" "
+ fi
+ counter=$((counter + 1))
+
+ output="$output$space#1 $unmounted"
+ done
+
+ for mounted in $(echo "$devices" | jq -r '.blockdevices[] | select(.type == "part") | select(.rm == true) | select(.mountpoint != null) | .size'); do
+ if [ $counter -eq 0 ]; then
+ space=""
+ else
+ space=" "
+ fi
+ counter=$((counter + 1))
+
+ output="$output$space#2 $mounted"
+ done
+
+ echo "$output"
+ ;;
+esac
diff --git a/linux/home/.config/polybar/scripts/temperature.sh b/linux/home/.config/polybar/scripts/temperature.sh
new file mode 100755
index 0000000..7ef6abb
--- /dev/null
+++ b/linux/home/.config/polybar/scripts/temperature.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+# Pulls CPU temps, averages them, and outputs them
+
+count=0
+sum=0.0
+
+# Iterate over each temperature reading
+for temp in "$(sensors | grep "^Core" | grep -e '+.*C' | cut -f 2 -d '+' | cut -f 1 -d ' ' | sed 's/°C//')"; do
+ sum=$(echo "$sum + $temp" | bc)
+ ((count++))
+done
+
+# Calculate the average
+avg=$(echo "scale=0; $sum / $count" | bc)
+
+# Output the average temperature without decimal points
+echo " ${avg%.*}°C"
diff --git a/linux/home/.config/polybar/scripts/toggle_bluetooth.sh b/linux/home/.config/polybar/scripts/toggle_bluetooth.sh
new file mode 100755
index 0000000..899d5ec
--- /dev/null
+++ b/linux/home/.config/polybar/scripts/toggle_bluetooth.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+if [ $(bluetoothctl show | grep "Powered: yes" | wc -c) -eq 0 ]
+then
+ bluetoothctl power on
+else
+ bluetoothctl power off
+fi
+
diff --git a/linux/home/.config/polybar/scripts/vpn.sh b/linux/home/.config/polybar/scripts/vpn.sh
new file mode 100755
index 0000000..ab1eb9d
--- /dev/null
+++ b/linux/home/.config/polybar/scripts/vpn.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+# Set a default message
+default_message=" vpn"
+
+# Check if Protonvpn service is running
+if pgrep -x "openvpn" >/dev/null; then
+ # If Protonvpn service is running, get the country
+ country=$(protonvpn s | grep Country)
+ # Extract the connection ID
+ connection=$(pgrep -a openvpn$ | head -n 1 | awk '{print $NF }' | cut -d '.' -f 1)
+ # Output vpn status with the country if connected
+ echo " vpn" #"$country"
+else
+ # If Protonvpn service is not running, output default message
+ echo "$default_message"
+fi
+
+#
+#proton_status=$(protonvpn s)
+#current_status=$(protonvpn s | wc -l)
+#current_server=$(protonvpn s | awk '/Server:/ {print "VPN "$2}')
+#
+#if [ "$current_status" -gt 2 ]; then
+# echo "$current_server"
+#else
+# echo "%{F#bf616a}NO VPN"
+#fi
diff --git a/linux/home/.config/pypoetry/config.toml b/linux/home/.config/pypoetry/config.toml
new file mode 100644
index 0000000..53b35d3
--- /dev/null
+++ b/linux/home/.config/pypoetry/config.toml
@@ -0,0 +1,3 @@
+[virtualenvs]
+create = true
+in-project = true
diff --git a/linux/home/.config/rofi/Notif.rasi b/linux/home/.config/rofi/Notif.rasi
new file mode 100644
index 0000000..47bc1af
--- /dev/null
+++ b/linux/home/.config/rofi/Notif.rasi
@@ -0,0 +1,153 @@
+configuration {
+ display-drun: " Apps";
+ display-window: "缾 Windows";
+ show-icons:true;
+ font: "Fira Code 10";
+}
+
+* {
+ background-color: #fffff7;
+ bg: #fffff7;
+ text-color: #927f70;
+ selbg: #927f70;
+ actbg: #eee8da;
+ urgbg: #eee8da;
+ winbg: #eee8da;
+
+ selected-normal-foreground: @text-color;
+ normal-foreground: @text-color;
+ selected-normal-background: @actbg;
+ normal-background: @background-color;
+
+ selected-urgent-foreground: @urgbg;
+ urgent-foreground: @text-color;
+ selected-urgent-background: @actbg;
+ urgent-background: @background-color;
+ urgent-foreground: @urgbg;
+
+ selected-active-foreground: @selbg;
+ active-foreground: @selbg;
+ selected-active-background: @actbg;
+ active-background: @background-color;
+
+ line-margin: 2;
+ line-padding: 2;
+ separator-style: "none";
+ hide-scrollbar: "true";
+ margin: 0;
+ padding: 5;
+}
+
+window {
+ location: northeast;
+ anchor: northeast;
+ //height: 40%;
+ y-offset: 60px;
+ x-offset: -20px;
+ width: 16%;
+ orientation: horizontal;
+ children: [mainbox];
+ border: 2px solid;
+ border-radius: 10px;
+ border-color: #eee8da;
+}
+
+mainbox {
+ spacing: 0.8em;
+ orientation: vertical;
+ children: [ inputbar, listview ];
+}
+
+button { padding: 2px 2px; }
+
+button selected {
+ background-color: @active-background;
+ text-color: @background-color;
+}
+
+inputbar {
+ padding: 2px;
+ spacing: 5px;
+}
+
+listview {
+ spacing: 0.5em;
+ dynamic: true;
+ cycle: false;
+}
+
+element {
+ padding: 10px;
+}
+
+prompt {
+ padding: 10px 0px 0px 20px;
+ font: "Fira Code 10";
+}
+
+entry {
+ expand: true;
+ text-color: @normal-foreground;
+ vertical-align: 0;
+ padding: 5px 0px 0px 20px;
+ enabled: false;
+}
+
+element normal.normal {
+ background-color: @bg;
+ border-radius: 8px;
+ text-color: @normal-foreground;
+}
+
+element normal.urgent {
+ background-color: @bg;
+ border-radius: 8px;
+ text-color: @urgent-foreground;
+}
+
+element normal.active {
+ background-color: @bg;
+ border-radius: 8px;
+ text-color: @active-foreground;
+}
+
+element selected.normal {
+ background-color: @selected-normal-background;
+ text-color: @selected-normal-foreground;
+}
+
+element selected.urgent {
+ background-color: @selected-urgent-background;
+ text-color: @selected-urgent-foreground;
+}
+
+element selected.active {
+ background-color: @selected-active-background;
+ text-color: @selected-active-foreground;
+}
+
+element alternate.normal {
+ background-color: @bg;
+ border-radius: 8px;
+ text-color: @normal-foreground;
+}
+
+element alternate.urgent {
+ background-color: @bg;
+ border-radius: 8px;
+ text-color: @urgent-foreground;
+}
+
+element alternate.active {
+ background-color: @bg;
+ border-radius: 8px;
+ text-color: @active-foreground;
+}
+element-icon {
+ size: 7ch;
+}
+element.selected {
+ border-radius: 8px;
+ border: 0 0 0 5px solid;
+ border-color: @winbg;
+}
diff --git a/linux/home/.config/rofi/colors/gruvbox.rasi b/linux/home/.config/rofi/colors/gruvbox.rasi
new file mode 100644
index 0000000..f5c9169
--- /dev/null
+++ b/linux/home/.config/rofi/colors/gruvbox.rasi
@@ -0,0 +1,10 @@
+/* colors */
+
+* {
+ al: #00000000;
+ bg: #32302f;
+ pg: #7c6f64;
+ se: #101010ff;
+ fg: #FFFFFFff;
+ ac: #fe8019;
+}
diff --git a/linux/home/.config/rofi/colors/nord.rasi b/linux/home/.config/rofi/colors/nord.rasi
new file mode 100644
index 0000000..2e72da1
--- /dev/null
+++ b/linux/home/.config/rofi/colors/nord.rasi
@@ -0,0 +1,10 @@
+/* colors */
+
+* {
+ al: #00000000;
+ bg: #2e3440;
+ pg: #4c566a;
+ se: #101010ff;
+ fg: #FFFFFFff;
+ ac: #88c0d0;
+}
diff --git a/linux/home/.config/rofi/colors/simple.rasi b/linux/home/.config/rofi/colors/simple.rasi
new file mode 100644
index 0000000..31f260e
--- /dev/null
+++ b/linux/home/.config/rofi/colors/simple.rasi
@@ -0,0 +1,10 @@
+/* colors */
+
+* {
+ al: #00000000;
+ bg: #2c2f31;
+ pg: #666666;
+ se: #6cb6eb;
+ fg: #FFFFFFff;
+ ac: #3b4041;
+}
diff --git a/linux/home/.config/rofi/config.rasi b/linux/home/.config/rofi/config.rasi
new file mode 100644
index 0000000..dfe7afd
--- /dev/null
+++ b/linux/home/.config/rofi/config.rasi
@@ -0,0 +1,178 @@
+configuration {
+ bw: 0;
+ columns: 1;
+ location: 0;
+ lines: 5;
+ padding: 0;
+ fixed-num-lines: true;
+ show-icons: true;
+ sidebar-mode: false;
+ separator-style: "beam";
+ hide-scrollbar: false;
+ scroll-method: 0;
+ click-to-exit: true;
+ show-match: true;
+ combi-hide-mode-prefix: false;
+ display-combi: "Combi";
+ display-drun: "Start";
+ display-window: "Window";
+ display-windowcd: "Windowcd";
+ display-run: "Commands";
+ display-ssh: "Ssh";
+ modi: "drun,window,run,ssh,";
+ opacity: "0";
+ fake-transparency: false;
+ kb-row-up: "Up,Control+k,Shift+Tab,Shift+ISO_Left_Tab";
+ kb-row-down: "Down,Control+j";
+ kb-accept-entry: "Control+m,Return,KP_Enter";
+ me-select-entry: "";
+ me-accept-entry: "MousePrimary";
+ terminal: "kitty";
+ kb-remove-to-eol: "Control+Shift+e";
+ kb-mode-next: "Shift+Right,Control+Tab,Control+l";
+ kb-mode-previous: "Shift+Left,Control+Shift+Tab,Control+h";
+ kb-remove-char-back: "BackSpace";
+ kb-mode-complete: "Control+c";
+}
+
+* {
+ background-color: @background;
+ font: "Source Code Pro Semibold 9";
+ spacing: 2;
+}
+
+#window {
+ background-color: @background;
+ border: 3;
+ border-color: @cyber;
+ padding: 0.5ch;
+}
+
+#mainbox {
+ border: 0;
+ border-color: @ac;
+ padding: 0;
+}
+
+#message {
+ border: 0px 0px 0px;
+ border-color: @ac;
+ padding: 1px;
+}
+
+#textbox {
+ text-color: @foreground;
+}
+
+#inputbar {
+ children: [ prompt,textbox-prompt-colon,entry,case-indicator ];
+}
+
+#textbox-prompt-colon {
+ expand: false;
+ str: ":";
+ margin: 0px 0.3em 0em 0em;
+ text-color: inherit;
+}
+
+#listview {
+ fixed-height: 0;
+ border: 0px 0px 0px;
+ border-color: @ac;
+ spacing: 2px;
+ scrollbar: true;
+ padding: 2px 0px 0px;
+}
+
+#element {
+ border: 0;
+ padding: 1px;
+}
+
+#element.normal.normal {
+ background-color: @background;
+ text-color: @foreground;
+}
+
+#element.normal.urgent {
+ background-color: @background;
+ text-color: @red;
+}
+
+#element.normal.active {
+ background-color: @foreground;
+ text-color: @background;
+}
+
+#element.selected.normal {
+ background-color: @foreground;
+ text-color: @black;
+}
+
+#element.selected.urgent {
+ background-color: @foreground;
+ text-color: @black;
+}
+
+#element.selected.active {
+ background-color: @foreground;
+ text-color: @black;
+}
+
+#element.alternate.normal {
+ background-color: @background;
+ text-color: @foreground;
+}
+
+#element.alternate.urgent {
+ background-color: @background;
+ text-color: @foreground;
+}
+
+#element.alternate.active {
+ background-color: @background;
+ text-color: @foreground;
+}
+
+#scrollbar {
+ width: 0px;
+ border: 0;
+ handle-width: 0px;
+ padding: 0;
+}
+
+#sidebar {
+ border: 2px 0px 0px;
+ border-color: @ac;
+}
+
+#button {
+ text-color: @background;
+}
+
+#button.selected {
+ background-color: @ac;
+ text-color: @foreground;
+}
+
+#inputbar {
+ spacing: 0;
+ text-color: @foreground;
+ padding: 1px;
+}
+
+#case-indicator {
+ spacing: 0;
+ text-color: @foreground;
+}
+
+#entry {
+ spacing: 0;
+ text-color: @foreground;
+}
+
+#prompt {
+ spacing: 0;
+ text-color: @foreground;
+}
+@import "~/.config/rofi/themes/colors.rasi"
diff --git a/linux/home/.config/rofi/options_menu.rasi b/linux/home/.config/rofi/options_menu.rasi
new file mode 100644
index 0000000..173da88
--- /dev/null
+++ b/linux/home/.config/rofi/options_menu.rasi
@@ -0,0 +1,71 @@
+configuration {
+ show-icons: false;
+ font: "Font Awesome 17";
+}
+
+window {
+ height: 300;
+ width: 300;
+ location: northeast;
+ anchor: northeast;
+ x-offset: -10;
+ y-offset: 10;
+ transparency: "real";
+ background-color: #00000000;
+ border: 4px 4px solid 4px 4px;
+ border-radius: 8;
+ border-color: @primary;
+}
+
+mainbox {
+ children: [message, listview];
+ padding: 0px 0px;
+}
+
+
+textbox {
+ horizontal-align: 0.5;
+ border: 0px 0px 2px;
+ border-color: @primary;
+ margin: 12px;
+ padding: 12px;
+}
+
+listview {
+ padding: 8px 32px;
+}
+
+element {
+ padding: 8px;
+ orientation: horizontal;
+}
+
+element normal.urgent, element alternate.urgent {
+ background-color: @urgent;
+}
+
+element normal.active, element alternate.active {
+ background-color: @background-alt;
+ border: 4px 4px solid 4px 4px;
+ border-radius: 8;
+ border-color: transparent;
+}
+
+element selected {
+ border: 4px 4px solid 4px 4px;
+ border-radius: 8;
+ border-color: @primary;
+}
+
+element selected.urgent {
+ background-color: @urgent;
+}
+
+element selected.active {
+ background-color: @background-alt;
+}
+
+element-text {
+ horizontal-align: 0;
+ padding: 0px 8px;
+}
diff --git a/linux/home/.config/rofi/rofi-network-manager.conf b/linux/home/.config/rofi/rofi-network-manager.conf
new file mode 100644
index 0000000..e182dc1
--- /dev/null
+++ b/linux/home/.config/rofi/rofi-network-manager.conf
@@ -0,0 +1,41 @@
+# Location
+# +---------- +
+# | 1 | 2 | 3 |
+# | 8 | 0 | 4 |
+# | 7 | 6 | 5 |
+# +-----------+
+#The grid represents the screen with the numbers indicating the location of the window.
+#If you want the window to be in the upper right corner, set location to 3.
+LOCATION=3
+#This sets the anchor point for the window displaying the QR code.
+QRCODE_LOCATION=$LOCATION
+#X, Y Offset
+#This sets the distance of the window from the edge of the screen on the X and Y axis.
+Y_AXIS=35
+X_AXIS=-10
+#X_AXIS=-315
+#Use notifications or not
+#Values can be "true" or "false"
+NOTIFICATIONS="false"
+#Location of qrcode wifi image
+QRCODE_DIR="/tmp/"
+#WIDTH_FIX_MAIN/WIDTH_FIX_STATUS
+#These values can be adjusted if the text doesn't fit or
+#if there is too much space at the end when you launch the script.
+#It will depend on the font type and size.
+WIDTH_FIX_MAIN=1
+WIDTH_FIX_STATUS=10
+#Values can be "true" or "false"
+#Set it to true, if the script outputs the signal strength with asterisks
+#and you want bars.
+ASCII_OUT=false
+#Values can be "true" or "false"
+#Set it to true if you want to use custom icons
+#for the signal strength instead of the default ones.
+CHANGE_BARS=false
+#Custom signal strength indicators
+SIGNAL_STRENGTH_0="0"
+SIGNAL_STRENGTH_1="1"
+SIGNAL_STRENGTH_2="12"
+SIGNAL_STRENGTH_3="123"
+SIGNAL_STRENGTH_4="1234"
diff --git a/linux/home/.config/rofi/rofi-network-manager.rasi b/linux/home/.config/rofi/rofi-network-manager.rasi
new file mode 100644
index 0000000..7e2da9b
--- /dev/null
+++ b/linux/home/.config/rofi/rofi-network-manager.rasi
@@ -0,0 +1,127 @@
+configuration {
+ show-icons: false;
+ sidebar-mode: false;
+ hover-select: true;
+ me-select-entry: "";
+ me-accept-entry: [MousePrimary];
+}
+*{
+ font: "DejaVu Sans Mono 9"; //Font
+ //Colors
+ foreground:#f8f8f2; //Text
+ background:#0A1229; //Background
+ accent:#00BCD4; //Highlight
+ foreground-selection:@foreground; //Selection_fg
+ background-selection:#e34039; //Selection_bg
+
+ transparent: #ffffff00;
+ background-color: @transparent;
+ text-color: @foreground;
+ selected-normal-foreground: @foreground-selection;
+ normal-foreground: @foreground;
+ alternate-normal-background: @transparent;
+ selected-urgent-foreground: @foreground;
+ urgent-foreground: @foreground;
+ alternate-urgent-background: @background;
+ active-foreground: @accent;
+ selected-active-foreground: @background-selection;
+ alternate-normal-foreground: @foreground;
+ alternate-active-background: @background;
+ bordercolor: @background;
+ normal-background: @transparent;
+ selected-normal-background: @background-selection;
+ separatorcolor: @accent;
+ urgent-background: @accent;
+ alternate-urgent-foreground: @foreground;
+ selected-urgent-background: @accent;
+ alternate-active-foreground: @foreground;
+ selected-active-background: @transparent;
+ active-background: @transparent;
+}
+window {
+ text-color: @foreground;
+ background-color: @background;
+ border-radius: 6px;
+ padding: 10;
+}
+mainbox {
+ border: 0;
+ padding: 0;
+}
+textbox {
+ text-color: @foreground;
+}
+listview {
+ spacing: 4px;
+ dynamic: true;
+ fixed-height: false;
+ border: 0;
+ scrollbar: false;
+ text-color: @separatorcolor;
+}
+element {
+ border: 0;
+ padding: 0;
+ border-radius: 4px;
+}
+element-text {
+ background-color: inherit;
+ text-color: inherit;
+}
+element.normal.normal {
+ text-color: @normal-foreground;
+ background-color: @normal-background;
+}
+element.normal.urgent {
+ text-color: @urgent-foreground;
+ background-color: @urgent-background;
+}
+element.normal.active {
+ text-color: @active-foreground;
+ background-color: @active-background;
+}
+element.selected.normal {
+ text-color: @selected-normal-foreground;
+ background-color: @selected-normal-background;
+}
+element.selected.urgent {
+ text-color: @selected-urgent-foreground;
+ background-color: @selected-urgent-background;
+}
+element.selected.active {
+ text-color: @selected-active-foreground;
+ background-color: @selected-active-background;
+}
+element.alternate.normal {
+ text-color: @alternate-normal-foreground;
+ background-color: @alternate-normal-background;
+}
+element.alternate.urgent {
+ text-color: @alternate-urgent-foreground;
+ background-color: @alternate-urgent-background;
+}
+element.alternate.active {
+ text-color: @alternate-active-foreground;
+ background-color: @alternate-active-background;
+}
+mode-switcher {
+ border: 0;
+}
+button selected {
+ text-color: @selected-normal-foreground;
+ background-color: @selected-normal-background;
+}
+button normal {
+ text-color: @foreground;
+}
+inputbar {
+ children: [textbox-prompt-colon,entry];
+}
+textbox-prompt-colon{
+ expand: false;
+ margin: 0;
+ str: ":";
+}
+entry {
+ placeholder: "";
+}
diff --git a/linux/home/.config/rofi/styles/appmenu.rasi b/linux/home/.config/rofi/styles/appmenu.rasi
new file mode 100644
index 0000000..83445be
--- /dev/null
+++ b/linux/home/.config/rofi/styles/appmenu.rasi
@@ -0,0 +1,186 @@
+configuration {
+ bw: 0;
+ columns: 1;
+ location: 0;
+ lines: 15;
+ padding: 0;
+ fixed-num-lines: true;
+ show-icons: true;
+ sidebar-mode: false;
+ separator-style: "beam";
+ hide-scrollbar: false;
+ scroll-method: 0;
+ click-to-exit: true;
+ show-match: true;
+ combi-hide-mode-prefix: false;
+ display-combi: "Combi";
+ display-drun: "Start";
+ display-window: "Window";
+ display-windowcd: "Windowcd";
+ display-run: "Commands";
+ display-ssh: "Ssh";
+ modi: "drun,window,run,ssh,";
+ opacity: "0";
+ fake-transparency: false;
+ kb-row-up: "Up,Control+k,Shift+Tab,Shift+ISO_Left_Tab";
+ kb-row-down: "Down,Control+j";
+ kb-accept-entry: "Control+m,Return,KP_Enter";
+ me-select-entry: "";
+ me-accept-entry: "MousePrimary";
+ terminal: "kitty";
+ kb-remove-to-eol: "Control+Shift+e";
+ kb-mode-next: "Shift+Right,Control+Tab,Control+l";
+ kb-mode-previous: "Shift+Left,Control+Shift+Tab,Control+h";
+ kb-remove-char-back: "BackSpace";
+ kb-mode-complete: "Control+c";
+}
+
+* {
+ padding: 0;
+ hide-scrollbar: true;
+ border: 0;
+ width: 15%;
+ columns: 1;
+ background-color: @background;
+ font: "Source Code Pro Semibold 9";
+ spacing: 2;
+}
+
+#window {
+ background-color: @background;
+ border: 3;
+ border-color: @cyber;
+ padding: 0.5ch;
+ location: northwest;
+ margin: 28px 0 0 8px;
+}
+
+#mainbox {
+ border: 0;
+ border-color: @ac;
+ padding: 0;
+}
+
+#message {
+ border: 0px 0px 0px;
+ border-color: @ac;
+ padding: 1px;
+}
+
+#textbox {
+ text-color: @foreground;
+}
+
+#inputbar {
+ children: [ prompt, textbox-prompt-colon, entry, case-indicator ];
+}
+
+#textbox-prompt-colon {
+ expand: false;
+ str: ":";
+ margin: 0px 0.3em 0em 0em;
+ text-color: inherit;
+}
+
+#listview {
+ fixed-height: 0;
+ border: 0px 0px 0px;
+ border-color: @ac;
+ spacing: 2px;
+ scrollbar: true;
+ padding: 2px 0px 0px;
+}
+
+#element {
+ border: 0;
+ padding: 1px;
+}
+
+#element.normal.normal {
+ background-color: @background;
+ text-color: @foreground;
+}
+
+#element.normal.urgent {
+ background-color: @background;
+ text-color: @red;
+}
+
+#element.normal.active {
+ background-color: @foreground;
+ text-color: @background;
+}
+
+#element.selected.normal {
+ background-color: @foreground;
+ text-color: @black;
+}
+
+#element.selected.urgent {
+ background-color: @foreground;
+ text-color: @black;
+}
+
+#element.selected.active {
+ background-color: @foreground;
+ text-color: @black;
+}
+
+#element.alternate.normal {
+ background-color: @background;
+ text-color: @foreground;
+}
+
+#element.alternate.urgent {
+ background-color: @background;
+ text-color: @foreground;
+}
+
+#element.alternate.active {
+ background-color: @background;
+ text-color: @foreground;
+}
+
+#scrollbar {
+ width: 0px;
+ border: 0;
+ handle-width: 0px;
+ padding: 0;
+}
+
+#sidebar {
+ border: 2px 0px 0px;
+ border-color: @ac;
+}
+
+#button {
+ text-color: @background;
+}
+
+#button.selected {
+ background-color: @ac;
+ text-color: @foreground;
+}
+
+#inputbar {
+ spacing: 0;
+ text-color: @foreground;
+ padding: 1px;
+}
+
+#case-indicator {
+ spacing: 0;
+ text-color: @foreground;
+}
+
+#entry {
+ spacing: 0;
+ text-color: @foreground;
+}
+
+#prompt {
+ spacing: 0;
+ text-color: @foreground;
+}
+
+@import "~/.config/rofi/themes/colors.rasi"
diff --git a/linux/home/.config/rofi/styles/powermenu.rasi b/linux/home/.config/rofi/styles/powermenu.rasi
new file mode 100644
index 0000000..1b7219d
--- /dev/null
+++ b/linux/home/.config/rofi/styles/powermenu.rasi
@@ -0,0 +1,187 @@
+configuration {
+ bw: 0;
+ columns: 1;
+ location: 0;
+ lines: 5;
+ padding: 0;
+ fixed-num-lines: true;
+ show-icons: true;
+ sidebar-mode: false;
+ separator-style: "beam";
+ hide-scrollbar: false;
+ scroll-method: 0;
+ click-to-exit: true;
+ show-match: true;
+ combi-hide-mode-prefix: false;
+ display-combi: "Combi";
+ display-drun: "Start";
+ display-window: "Window";
+ display-windowcd: "Windowcd";
+ display-run: "Commands";
+ display-ssh: "Ssh";
+ modi: "drun,window,run,ssh,";
+ opacity: "0";
+ fake-transparency: false;
+ kb-row-up: "Up,Control+k,Shift+Tab,Shift+ISO_Left_Tab";
+ kb-row-down: "Down,Control+j";
+ kb-accept-entry: "Control+m,Return,KP_Enter";
+ me-select-entry: "";
+ me-accept-entry: "MousePrimary";
+ terminal: "kitty";
+ kb-remove-to-eol: "Control+Shift+e";
+ kb-mode-next: "Shift+Right,Control+Tab,Control+l";
+ kb-mode-previous: "Shift+Left,Control+Shift+Tab,Control+h";
+ kb-remove-char-back: "BackSpace";
+ kb-mode-complete: "Control+c";
+}
+
+* {
+ lines: 5;
+ padding: 0;
+ hide-scrollbar: true;
+ border: 0;
+ width: 15%;
+ columns: 1;
+ background-color: @background;
+ font: "Source Code Pro Semibold 9";
+ spacing: 2;
+}
+
+#window {
+ background-color: @background;
+ border: 3;
+ border-color: @cyber;
+ padding: 0.5ch;
+ location: northeast;
+ margin: 28px 8px 0 0; /* Adjust these values for spacing and positioning */
+}
+
+#mainbox {
+ border: 0;
+ border-color: @ac;
+ padding: 0;
+}
+
+#message {
+ border: 0px 0px 0px;
+ border-color: @ac;
+ padding: 1px;
+}
+
+#textbox {
+ text-color: @foreground;
+}
+
+#inputbar {
+ children: [ prompt, textbox-prompt-colon, entry, case-indicator ];
+}
+
+#textbox-prompt-colon {
+ expand: false;
+ str: ":";
+ margin: 0px 0.3em 0em 0em;
+ text-color: inherit;
+}
+
+#listview {
+ fixed-height: 0;
+ border: 0px 0px 0px;
+ border-color: @ac;
+ spacing: 2px;
+ scrollbar: true;
+ padding: 2px 0px 0px;
+}
+
+#element {
+ border: 0;
+ padding: 1px;
+}
+
+#element.normal.normal {
+ background-color: @background;
+ text-color: @foreground;
+}
+
+#element.normal.urgent {
+ background-color: @background;
+ text-color: @red;
+}
+
+#element.normal.active {
+ background-color: @foreground;
+ text-color: @background;
+}
+
+#element.selected.normal {
+ background-color: @foreground;
+ text-color: @black;
+}
+
+#element.selected.urgent {
+ background-color: @foreground;
+ text-color: @black;
+}
+
+#element.selected.active {
+ background-color: @foreground;
+ text-color: @black;
+}
+
+#element.alternate.normal {
+ background-color: @background;
+ text-color: @foreground;
+}
+
+#element.alternate.urgent {
+ background-color: @background;
+ text-color: @foreground;
+}
+
+#element.alternate.active {
+ background-color: @background;
+ text-color: @foreground;
+}
+
+#scrollbar {
+ width: 0px;
+ border: 0;
+ handle-width: 0px;
+ padding: 0;
+}
+
+#sidebar {
+ border: 2px 0px 0px;
+ border-color: @ac;
+}
+
+#button {
+ text-color: @background;
+}
+
+#button.selected {
+ background-color: @ac;
+ text-color: @foreground;
+}
+
+#inputbar {
+ spacing: 0;
+ text-color: @foreground;
+ padding: 1px;
+}
+
+#case-indicator {
+ spacing: 0;
+ text-color: @foreground;
+}
+
+#entry {
+ spacing: 0;
+ text-color: @foreground;
+}
+
+#prompt {
+ spacing: 0;
+ text-color: @foreground;
+}
+
+@import "~/.config/rofi/themes/colors.rasi"
diff --git a/linux/home/.config/rofi/themes/colors.rasi b/linux/home/.config/rofi/themes/colors.rasi
new file mode 100644
index 0000000..d1419e4
--- /dev/null
+++ b/linux/home/.config/rofi/themes/colors.rasi
@@ -0,0 +1,18 @@
+/* colors */
+
+* {
+ alternative: #00101212;
+ background: #101010ff;
+ seperator: #00101212;
+ foreground: #FAFAFA;
+ ac: #00000000;
+ red: #CECCC9;
+ green: #CECCC9;
+ yellow: #CECCC9;
+ blue: #CECCC9;
+ purple: #CECCC9;
+ cyan: #CECCC9;
+ black: #101212;
+ cyber: #53E2AE;
+ orange: #fe8019;
+}
diff --git a/linux/home/.config/rofi/themes/dmenu.rasi b/linux/home/.config/rofi/themes/dmenu.rasi
new file mode 100644
index 0000000..1e8f319
--- /dev/null
+++ b/linux/home/.config/rofi/themes/dmenu.rasi
@@ -0,0 +1,38 @@
+
+* {
+ background-color: #1a2026;
+ border-color: #29343d;
+ text-color: #ffffff;
+ font: "Fira Code Nerd Font Mono 11";
+}
+
+window {
+ anchor: north;
+ location: north;
+ width: 100%;
+ padding: 4px;
+ children: [ horibox ];
+}
+
+horibox {
+ orientation: horizontal;
+ children: [ prompt, entry, listview ];
+}
+
+listview {
+ layout: horizontal;
+ spacing: 10px;
+ lines: 100;
+}
+
+entry {
+ expand: false;
+ width: 14em;
+}
+
+element {
+ padding: 2px 5px;
+}
+element selected {
+ color: #a9bcef;
+}
diff --git a/linux/home/.config/rofi/themes/power.rasi b/linux/home/.config/rofi/themes/power.rasi
new file mode 100644
index 0000000..209a9ac
--- /dev/null
+++ b/linux/home/.config/rofi/themes/power.rasi
@@ -0,0 +1,34 @@
+/**
+ * This theme is intended for a 5 items wide menu
+ * on a 1366x768 pixels resolution.
+ */
+@import "colors.rasi"
+#window {
+ width: 1366px;
+ height: 768px;
+ /* vertical horizontal */
+ padding: 270px 88px;
+ children: [ horibox ];
+}
+#horibox {
+ children: [ listview ];
+}
+#listview {
+ layout: horizontal;
+ spacing: 56px;
+ lines: 5;
+}
+#element {
+ /**
+ * Values bellow are 'no-padding' ones, to which we add 70
+ * top right bottom left
+ * -14px 0px -14px -93px */
+ padding: 56px 70px 56px -23px;
+ background-color: @background-light;
+}
+#element.selected {
+ background-color: @accent;
+ text-color: @background;
+}
+
+
diff --git a/linux/home/.config/sxhkd/show_help.sh b/linux/home/.config/sxhkd/show_help.sh
new file mode 100755
index 0000000..bd0cd6c
--- /dev/null
+++ b/linux/home/.config/sxhkd/show_help.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+cat ~/.config/sxhkd/sxhkdrc | awk '/^[a-z]/ && last {print $0,"\t",last} {last=""} /^#/{last=$0}' | column -t -s $'\t' | rofi -dmenu -i -no-show-icons -width 1000
+
diff --git a/linux/home/.config/sxhkd/sxhkdrc b/linux/home/.config/sxhkd/sxhkdrc
new file mode 100755
index 0000000..1b276c7
--- /dev/null
+++ b/linux/home/.config/sxhkd/sxhkdrc
@@ -0,0 +1,490 @@
+#################################################
+# ███████╗██╗ ██╗██╗ ██╗██╗ ██╗██████╗ #
+# ██╔════╝╚██╗██╔╝██║ ██║██║ ██╔╝██╔══██╗ #
+# ███████╗ ╚███╔╝ ███████║█████╔╝ ██║ ██║ #
+# ╚════██║ ██╔██╗ ██╔══██║██╔═██╗ ██║ ██║ #
+# ███████║██╔╝ ██╗██║ ██║██║ ██╗██████╔╝ #
+# ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝ #
+#################################################
+
+#---------------------------------------------------------------
+#
+# WM independent hotkeys
+#
+
+# Help
+super + slash
+ ~/.config/sxhkd/show_help.sh -m -3
+
+# Application menu
+super + a
+ ~/.config/polybar/scripts/menu.sh
+
+# Shutdown menu
+#super + alt + Delete
+# ~/.config/polybar/scripts/sysmenu.sh
+
+# lockscreen
+super + Escape
+ betterlockscreen -l
+
+# Program launcher
+super + @space
+ rofi -show drun
+
+# Run launcher (commands)
+super + r
+ rofi -show run
+
+# Task switcher
+alt + Tab
+ rofi -show window
+
+# Turn off compositor
+super + shift + p
+ toggleprogram "picom" "--experimental-backends"
+
+#picom-trans -c -10
+#picom-trans -c +10
+
+# Close all active notifications.
+# ~button1
+# bspc query -D -d .focused.!occupied && $HOME/.local/bin/eww update noti=false; sleep 0.270; $HOME/.local/bin/eww close notification-popup; pkill openEwwPopup.sh
+
+# Toggle control center using middle click.
+~button2
+ xqp 0 $(xdo id -N Bspwm -n root) && sh $HOME/.config/jgmenu/scripts/windows.sh
+#bspc query -D -d .focused.!occupied && sh $HOME/.config/eww/scripts/openControlCenter.sh
+
+# Right click menu
+~button3
+ xqp 0 $(xdo id -N Bspwm -n root) && sh $HOME/.config/jgmenu/scripts/jgmenu.sh
+
+# Toggle control center.
+super + shift + c
+ sh $HOME/.config/eww/scripts/openControlCenter.sh
+
+# Toggle notification center.
+super + shift + n
+ sh $HOME/.config/eww/scripts/openNotificationCenter.sh
+
+## Toggle info center.
+#super + shift + i
+# sh $HOME/.config/eww/scripts/openInfoCenter.sh
+#
+## Toggle color picker.
+#super + shift + x
+# sh $HOME/.local/bin/xcolor-pick
+#
+## Toggle bar.
+#super + shift + b
+# sh $HOME/.local/bin/tglbar
+#
+## Toggle exit screen.
+#super + Escape
+# sh $HOME/.config/eww/scripts/openExitScreen.sh
+#
+## Close exit screen when it's enabled.
+#~Escape
+# [[ -f "$HOME/.cache/eww-escreen.lock" ]] && sh $HOME/.config/eww/scripts/openExitScreen.sh
+
+## Open web browser, and file manager.
+#super + shift + {w,f}
+# {firefox-developer-edition,thunar}
+
+# Terminal emulator
+#super + Return
+# $TERMINAL
+
+# Browser
+super + w
+ $BROWSER
+
+# Keyboard
+super + o
+ onboard
+
+# Show clipmenu
+#alt + x
+# parcellite
+ #~/.scripts/clip.sh
+
+# Screenshots tool (selection)
+ctrl + Print
+ screenshot crop
+
+# Screenshots tool (screen)
+shift + Print
+ screenshot full
+
+# make sxhkd reload its configuration files:
+super + ctrl + x
+ pkill -USR1 -x sxhkd; dunstify "Sxhkd configuration reloaded"
+
+# Kill window
+ctrl + alt + Escape
+ xkill
+
+# Un/mount drives
+ctrl + alt + {m,u}
+ {_,u}mnt
+
+
+#---------------------------------------------------------------
+#
+# bspwm hotkeys
+#
+
+# Quit/restart bspwm
+super + alt + shift + {q,r}
+ bspc {quit,wm -r}
+
+# Close and kill
+super + d
+ bspc node -c
+
+# Alternate between the tiled and monocle layout
+super + m
+ bspc desktop -l next
+super + z
+ bspc desktop -l next
+
+# Send the newest marked node to the newest preselected node
+super + y
+ bspc node newest.marked.local -n newest.!automatic.local
+
+# Swap the current node and the biggest window
+super + g
+ bspc node -s biggest.window
+
+
+#---------------------------------------------------------------
+#
+# state/flags
+#
+
+# Set the window state
+#super + {t,shift + t,s,f}
+# bspc node -t {tiled,pseudo_tiled,floating,fullscreen}
+
+# Floating into monocle layout
+super + shift + m
+ bspc node -t pseudo_tiled; bspc node -t floating && wtp 10 36 $(($(bspc query -T -m | jq '.rectangle.width') * 98 / 100)) $(($(($(bspc query -T -m | jq '.rectangle.height') - $(bspc config top_padding))) * 96 / 100)) "$(pfw)"
+
+# Floating window pane left
+super + shift + h
+ bspc node -t pseudo_tiled; bspc node -t floating && wtp 10 36 $(($(($(bspc query -T -m | jq '.rectangle.width') / 2)) - 5)) $(($(($(bspc query -T -m | jq '.rectangle.height') - $(bspc config top_padding))) * 96 / 100)) "$(pfw)"
+
+# Floating window pane right
+super + shift + l
+ bspc node -t pseudo_tiled && bspc node -t floating && wtp $(($(bspc query -T -m | jq '.rectangle.width') - $(($(bspc query -T -m | jq '.rectangle.width') / 2)))) 36 $(($(($(bspc query -T -m | jq '.rectangle.width') / 2)) - 15)) $(($(($(bspc query -T -m | jq '.rectangle.height') - $(bspc config top_padding))) * 96 / 100)) "$(pfw)"
+
+# Set the window state
+super + {t,shift + t,s}
+ bspc node -t {tiled,pseudo_tiled,floating}; \
+ xdo raise -N Plank; \
+ xdo raise -N '*:*:Picture in picture'; \
+ xdo raise -N "Picture-in-Picture"
+
+# Toggle fullscreen
+super + {f}
+ bspc node -t \~fullscreen
+
+# Toggle sticky
+#super + q
+# bspc node -g sticky=on
+
+# Set the node flags
+super + ctrl + {m,x,y,z}
+ bspc node -g {marked,locked,sticky,private}
+
+# Move layers of windows above/below each other (script)
+super + {equal,minus}
+ layer.sh {+,-}
+
+
+#---------------------------------------------------------------
+#
+# Focus/Swap
+#
+
+# Focus the node in the given direction
+super + {_,shift + }{h,j,k,l}
+ bspc node -{f,s} {west,south,north,east}
+
+# Focus the node for the given path jump
+#super + {p,b,comma,period}
+# bspc node -f @{parent,brother,first,second}
+
+# Focus the next/previous window in the current desktop
+super + {_,shift + }c
+ bspc node -f {next,prev}.local.!hidden.window
+
+# Focus the next/previous desktop in the current monitor
+super + bracket{left,right}
+ bspc desktop -f {prev,next}.local
+
+# Focus the last node/desktop
+super + {grave,Tab}
+ bspc {node,desktop} -f last
+
+# Focus the older or newer node in the focus history
+#super + {o,i}
+# bspc wm -h off; \
+# bspc node {older,newer} -f; \
+# bspc wm -h on
+
+# Focus or send to the given desktop
+super + {_,shift + }{1-9,0}
+ bspc {desktop -f,node -d} '^{1-9,10}'
+
+# Hide window
+super + comma
+ bspc node -g hidden
+
+# Unhide window (script)
+super + period
+ ${HOME}/.config/bspwm/scripts/hide-window unhide
+
+# Toggle the hidden state of the focused node
+super + q
+ ~/.scripts/bspwm-toggle-visibility.sh
+
+#---------------------------------------------------------------
+#
+# Preselect
+#
+
+# Preselect the direction
+super + ctrl + {h,j,k,l}
+ bspc node -p {west,south,north,east}
+
+# Preselect the ratio
+super + ctrl + {1-9}
+ bspc node -o 0.{1-9}
+
+# Cancel the preselection for the focused node
+super + ctrl + space
+ bspc node -p cancel
+
+# Cancel the preselection for the focused desktop
+super + ctrl + shift + space
+ bspc query -N -d | xargs -I id -n 1 bspc node id -p cancel
+
+# Close all receptacle
+super + shift + i
+ for win in `bspc query -N -n .leaf.\!window`; do bspc node $win -k ; done;
+
+# Insert receptacle
+super + i; {h,j,k,l}
+ bspc node --presel-dir {west,south,north,east} -i
+
+# Move to rectacle
+super + ctrl + i
+ bspreceptacle
+
+# Balance nodes
+super + alt + i
+ bspc node @/ -B
+
+
+
+#---------------------------------------------------------------
+#
+# Move/resize
+#
+
+## Drag tiling window to floating
+alt + button1
+ bspdragtofloat
+alt + @button1
+ bspdragtofloat stop
+@button1
+ bspdragtofloat stop
+~button1
+ :
+
+# Move a floating window or swap with any other adjacent tiled/pseudo_tiled window
+ctrl + alt + {h,j,k,l}
+ { dir=west dx=-20 dy=0 \
+ , dir=south dx=0 dy=20 \
+ , dir=north dx=0 dy=-20 \
+ , dir=east dx=20 dy=0 \
+ }; \
+ bspc node --move "$dx" "$dy" || bspc node --swap $dir
+
+# Rotate windows to different nodes
+super + ctrl + r
+ bspc node @parent -R 90
+
+# Resize tiled/floating windows (script)
+shift + alt + {h,j,k,l}
+ bspwm_resize.sh {west,south,north,east}
+#shift + alt + {h,j,k,l}
+# {bspc node @parent/second -z left -20 0; \
+# bspc node @parent/first -z right -20 0, \
+# bspc node @parent/second -z top 0 +20; \
+# bspc node @parent/first -z bottom 0 +20, \
+# bspc node @parent/first -z bottom 0 -20; \
+# bspc node @parent/second -z top 0 -20, \
+# bspc node @parent/first -z right +20 0; \
+# bspc node @parent/second -z left +20 0}
+
+# Resize window into predefined pseudo_tiled window
+super + shift + s
+ bspc node -t pseudo_tiled; bspc node -t floating && wtp 396 185 570 394 "$(pfw)"
+
+# Spawn next window/program into predefined floating window
+super + ctrl + s
+ bspc rule -a '*' -o state=floating rectangle=720x480+320+200
+
+# Spawn next window/program into another desktop
+super + alt + {1-9,0}
+ bspc rule -a '*' -o desktop=^{1-9,10}
+
+# Focused desktop window gaps scroll
+shift + alt + {1,2}
+ bspc config -d focused window_gap $((`bspc config -d focused window_gap` {-,+} 5 ))
+
+# Global window gaps scroll
+ctrl + alt + {1,2}
+ bspc config window_gap $(( $(bspc config window_gap) {-,+} 5 ))
+
+#---------------------------------------------------------------
+#
+# Multimedia
+#
+# Multimedia control
+{XF86AudioStop,XF86AudioPlay,XF86AudioPrev,XF86AudioNext}
+ playerctl {stop,play-pause,previous,next}
+
+# Use arrow keys as multimedia keys
+alt + shift + {Left,Up,Right}
+ playerctl {previous,play-pause,next}
+
+# Brightness control
+XF86MonBrightness{Up,Down}
+ brightnessctl s 5%{+,-}
+
+# Use arrow keys as brightness keys
+alt + {Right,Left}
+ brightnessctl s 5%{+,-}
+
+# Volume control
+XF86Audio{Raise,Lower}Volume
+ pulsemixer --change-volume {+,-}5
+
+# Use arrow keys as volume keys
+alt + {Up,Down}
+ pulsemixer --change-volume {+,-}5
+
+
+#---------------------------------------------------------------
+#
+# Xdotool keys
+#
+
+# Move mouse cursor north, west, south, east
+alt + {w,a,s,d}
+ xdotool mousemove_relative --sync {-- 0 -24, -- -24 0, 0 24, 24 0}
+
+# Move mouse cursor diagonally north-west, north-east, south-west, south-east
+shift + alt + {q,e,a,d}
+ xdotool mousemove_relative --sync {-- -24 -24, -- 24 -24, -- -24 24,-- 24 24}
+
+# Emulate left mouse click
+alt + i
+ xdotool click --clearmodifiers 1
+
+# Emulate left mouse click select
+alt + shift + i
+ xdotool mousedown 1 sleep 0.5 mousemove_relative --sync {-- -8 0, 0 8, -- 0 -8, 8 0} sleep 0.5 mouseup 1
+
+# Emulate mouse right click
+alt + o
+ xdotool click --clearmodifiers 3
+
+# Emulate mouse scroll up
+alt + n
+ xdotool click --clearmodifiers 4
+
+# Emulate mouse scroll down
+alt + m
+ xdotool click --clearmodifiers 5
+
+# Emulate mouse scroll button
+alt + p
+ xdotool click --clearmodifiers 2
+
+# Emulate home key
+alt + ctrl + Left
+ xdotool keyup Left key --clearmodifiers Home
+
+# Emulate end key
+alt + ctrl + Right
+ xdotool keyup Right key --clearmodifiers End
+
+# Emulate delete key
+~alt + BackSpace
+ xte 'keyup Alt_L' 'key Delete' 'keydown Alt_L'
+
+
+#---------------------------------------------------------------
+#
+# Programs
+#
+
+# Scratchpd
+super + semicolon
+ ~/.scripts/scratchpad
+
+super + x
+ ~/.scripts/scratchpad
+
+# Heads-Up-Display scratchpad terminal
+super + e
+ ~/.scripts/heads-up-display
+
+## File manager nnn
+#super + shift + n
+# $TERMINAL -e nnn
+
+# File manager pcmanfm (GUI)
+super + shift + f
+ pcmanfm
+
+# Bitwarden-rofi
+super + shift + b
+ rofi-rbw
+
+# Thunderbird mail
+#super + shift + m
+# thunderbird
+
+# VirtualBox gui
+super + v
+ /usr/bin/VirtualBox -- :0 vt1
+
+# Discord
+super + shift + d
+ discord
+
+# Spotify
+super + shift + y
+ spotify
+
+# Book reader (zathura)
+super + shift + z
+ zathura
+
+# Dictionary
+super + ctrl + w
+ goldendict
+
+# Thesaurus
+super + shift + w
+ artha
+
+# Suspend
+alt + F4
+ systemctl suspend && betterlockscreen --lock dimblur
diff --git a/linux/home/.config/tmux/file_manager.sh b/linux/home/.config/tmux/file_manager.sh
new file mode 100755
index 0000000..b3a70a5
--- /dev/null
+++ b/linux/home/.config/tmux/file_manager.sh
@@ -0,0 +1,108 @@
+#!/usr/bin/env bash
+# tmux file opener with fallback file manager (no preview)
+
+# Mark this pane as the file manager immediately
+tmux select-pane -T "FILE_MANAGER"
+# Also set the option as backup
+tmux set-option -pq @file_manager 1
+
+orig_pane="$1"
+chooser_file="$HOME/.cache/tmux-fm-selected"
+rm -f "$chooser_file"
+
+# Function: pick available file manager
+pick_fm() {
+ if command -v lf >/dev/null 2>&1; then
+ echo "lf"
+ elif command -v nnn >/dev/null 2>&1; then
+ echo "nnn"
+ elif command -v ranger >/dev/null 2>&1; then
+ echo "ranger"
+ else
+ echo ""
+ fi
+}
+
+fm=$(pick_fm)
+if [[ -z "$fm" ]]; then
+ echo "No file manager found (lf, nnn, ranger)." >&2
+ cleanup
+ exit 1
+fi
+
+# Cleanup function to reset both title and option
+cleanup() {
+ tmux select-pane -T ""
+ tmux set-option -puq @file_manager
+ rm -f "$chooser_file"
+}
+
+# Set trap to cleanup on exit (including when user presses 'q')
+trap cleanup EXIT INT TERM
+
+# Launch the chosen file manager with no preview where possible
+case "$fm" in
+ nnn)
+ # Disable preview completely and use picker mode
+ # -A: disable dir auto-select, -e: open text files in editor
+ # -o: open files with opener, -x: show only selection
+ NNN_OPENER="tee \"$chooser_file\"" nnn -Axo
+ ;;
+ lf)
+ # Disable preview and set selection path
+ lf -command 'set preview false' -selection-path="$chooser_file"
+ ;;
+ ranger)
+ # Disable all previews
+ ranger --choosefile="$chooser_file" \
+ --cmd='set preview_files false' \
+ --cmd='set preview_directories false' \
+ --cmd='set preview_images false'
+ ;;
+esac
+
+# Exit if no file chosen (user pressed 'q' or cancelled)
+if [[ ! -s "$chooser_file" ]]; then
+ exit 0
+fi
+
+file="$(head -n 1 "$chooser_file")"
+rm -f "$chooser_file"
+
+# Restrict to current window panes and exclude the file manager pane
+current_window=$(tmux display-message -p '#I')
+mapfile -t panes < <(
+ tmux list-panes -t "$current_window" -F '#S:#I.#P' |
+ grep -v "^$(tmux display-message -p '#S:#I').$(tmux display-message -p '#P')$"
+)
+
+# Choose target pane
+if [[ ${#panes[@]} -eq 0 ]]; then
+ exit 1
+elif [[ ${#panes[@]} -eq 1 ]]; then
+ target="${panes[0]}"
+else
+ echo "Select target pane:"
+ for i in "${!panes[@]}"; do
+ letter=$(printf "\\$(printf '%03o' $((97 + i)))") # a, b, c...
+ echo "$letter) ${panes[$i]}"
+ done
+ read -n 1 -p "Choice: " choice
+ echo
+ idx=$(( $(printf "%d" "'$choice") - 97 ))
+ if [[ $idx -ge 0 && $idx -lt ${#panes[@]} ]]; then
+ target="${panes[$idx]}"
+ else
+ exit 1
+ fi
+fi
+
+# Decide if file is text or binary
+if file --mime-type "$file" 2>/dev/null | grep -q 'text/'; then
+ opener="${EDITOR:-$(command -v nvim || command -v vim || echo 'vi')}"
+else
+ opener="$(command -v xdg-open || command -v open || echo 'cat')"
+fi
+
+# Send open command to target pane
+tmux send-keys -t "$target" "$opener \"$file\"" C-m
diff --git a/linux/home/.config/tmux/fzf-menu.sh b/linux/home/.config/tmux/fzf-menu.sh
new file mode 100755
index 0000000..d7863d9
--- /dev/null
+++ b/linux/home/.config/tmux/fzf-menu.sh
@@ -0,0 +1,60 @@
+#!/bin/sh
+
+# fzf session name
+FZF_SESSION_NAME="fzf"
+
+# Print error messages
+error() {
+ echo "Error: $1" >&2
+}
+
+# Check if tmux is installed
+if ! command -v tmux >/dev/null 2>&1; then
+ error "tmux is not installed."
+ exit 1
+fi
+
+# Function to handle file or directory opening
+open_selected_item() {
+ # Use fzf to select a file from the specified directory
+ SELECTED_FILE=$(find ~ -type f | fzf --preview "bat --style=numbers --color=always --line-range=:500 {}" \
+ --preview-window=up:60% --height=90% --layout=reverse --border=sharp --ansi)
+
+ if [ "$SELECTED_FILE" != "" ]; then
+ # Ask whether to open the file or its directory
+ read -p "Open file (f) or directory (d)? " choice
+ case "$choice" in
+ f | F)
+ # Open the selected file in nvim
+ nvim "$SELECTED_FILE"
+ ;;
+ d | D)
+ # Change to the directory containing the selected file
+ cd "$(dirname "$SELECTED_FILE")"
+ ;;
+ *)
+ echo "Invalid choice. Please enter 'f' for file or 'd' for directory."
+ ;;
+ esac
+ else
+ echo "No file selected."
+ fi
+}
+
+# Check if the fzf session exists
+if tmux has-session -t "$FZF_SESSION_NAME" 2>/dev/null; then
+ # Get the current tmux session name
+ CURRENT_SESSION=$(tmux display-message -p '#S')
+
+ # If currently in the fzf session, detach; otherwise, attach to it
+ if [ "$CURRENT_SESSION" = "$FZF_SESSION_NAME" ]; then
+ tmux detach-client
+ else
+ tmux set -gF '@last_session_name' '#S' # Store the current session name
+ tmux display-popup -E -x200% -y0 -w50% -h99% "tmux attach-session -t $FZF_SESSION_NAME"
+ fi
+else
+ # If the fzf session doesn't exist, create it and run file selection logic in a popup
+ tmux set -gF '@last_session_name' '#S' # Store the current session name
+ tmux display-popup -E -w100% -h100% -y0 -x0 "tmux new-session -A -s fzf '$0 open_selected_item'"
+fi
diff --git a/linux/home/.config/tmux/left-status.sh b/linux/home/.config/tmux/left-status.sh
new file mode 100755
index 0000000..e4a8c49
--- /dev/null
+++ b/linux/home/.config/tmux/left-status.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+function ip-address() {
+ # Loop through the interfaces and check for the interface that is up.
+ for file in /sys/class/net/*; do
+ iface=$(basename $file);
+ read status < $file/operstate;
+ [ "$status" == "up" ] && ip addr show $iface | awk '/inet /{printf $2""}'
+ done
+}
+
+function vpn-connection() {
+ # Check for tun0 interface.
+ [ -d /sys/class/net/tun0 ] && printf "%s " 'VPN*'
+}
+
+function main() {
+ # Comment out any function you do not need.
+ ip-address
+ vpn-connection
+}
+
+# Calling the main function which will call the other functions.
+main
+
diff --git a/linux/home/.config/tmux/notes.sh b/linux/home/.config/tmux/notes.sh
new file mode 100755
index 0000000..71a8dc7
--- /dev/null
+++ b/linux/home/.config/tmux/notes.sh
@@ -0,0 +1,113 @@
+#!/usr/bin/env bash
+
+# Notes/TODO management & quick search engine via tmux
+
+NOTES_DIR="$HOME/documents/main"
+TODO_FILE="$NOTES_DIR/inbox/tasks/TODO.md"
+EDITOR="nvim"
+NOTE_SESSION_NAME="note"
+BROWSER_PREFERENCES=("firefox" "chromium" "google-chrome" "brave-browser" "chrome")
+SEARCH_URL="https://www.google.com/search?q="
+
+# simple error printing
+error() {
+ echo "Error: $1" >&2
+}
+
+# add a TODO entry with timestamp
+add_todo() {
+ local todo_text="$1"
+ [ -z "$todo_text" ] && return 1
+
+ [ ! -f "$TODO_FILE" ] && echo -e "# TODO List\n" > "$TODO_FILE"
+
+ echo "- [ ] $todo_text ($(date '+%Y-%m-%d %H:%M'))" >> "$TODO_FILE"
+ tmux display-message "Added TODO: $todo_text"
+}
+
+# open a web search
+search_web() {
+ local query="$1"
+ [ -z "$query" ] && return 1
+
+ local encoded_query=$(printf '%s' "$query" | sed 's/ /+/g' | sed 's/[^a-zA-Z0-9+._-]//g')
+ local search_url="${SEARCH_URL}${encoded_query}"
+
+ if command -v xdg-open >/dev/null 2>&1; then
+ xdg-open "$search_url" >/dev/null 2>&1 &
+ else
+ for browser in "${BROWSER_PREFERENCES[@]}"; do
+ command -v "$browser" >/dev/null 2>&1 && $browser "$search_url" >/dev/null 2>&1 & break
+ done
+ fi
+
+ tmux display-message "Opening search for: $query"
+}
+
+# display the notes menu (in-editor or popup)
+open_menu() {
+ tmux set -gF '@last_session_name' '#S'
+
+ if tmux has-session -t "$NOTE_SESSION_NAME" 2>/dev/null && tmux list-panes -t "$NOTE_SESSION_NAME" -F "#{pane_current_command}" | grep -q "^nvim$"; then
+ # menu for active nvim session
+ tmux display-menu -T "#[align=center] Notes (nvim-mode)" \
+ "New note" n "command-prompt -p 'Enter note title:' 'send-keys -t $NOTE_SESSION_NAME \":e $NOTES_DIR/%%.md\" Enter'" \
+ "Open note" o "send-keys -t $NOTE_SESSION_NAME \":cd $NOTES_DIR | FzfLua files\" Enter" \
+ "TODO List" t "send-keys -t $NOTE_SESSION_NAME \":e $TODO_FILE\" Enter" \
+ "Add Quick TODO" T "command-prompt -p 'Enter TODO:' 'run-shell \"$0 --add-todo %%\"'" \
+ "Grep/find patterns" g "send-keys -t $NOTE_SESSION_NAME \":cd $NOTES_DIR | FzfLua live_grep\" Enter" \
+ "Web Search" s "command-prompt -p 'Search query:' 'run-shell \"$0 --search %%\"'" \
+ "Quit (q)" q ""
+ else
+ # popup menu outside of nvim
+ tmux display-menu -T "#[align=center] Notes (popup-mode)" \
+ "New note" n "command-prompt -p 'Enter note title:' \"display-popup -w 100% -h 100% -E 'tmux new-session -A -s $NOTE_SESSION_NAME \\\"$EDITOR $NOTES_DIR/%%.md\\\"'\"" \
+ "Open note" o "display-popup -w 100% -h 100% -E \"tmux new-session -A -s $NOTE_SESSION_NAME 'fzf --preview \\\"bat --style=numbers --color=always --line-range=:500 {}\\\" --preview-window=up:60% --height=90% --layout=reverse --border=sharp --ansi < <(find $NOTES_DIR -type f -name \\\"*.md\\\") | xargs -r $EDITOR'\"" \
+ "TODO List" t "display-popup -w 100% -h 100% -E \"tmux new-session -A -s $NOTE_SESSION_NAME \\\"$EDITOR $TODO_FILE\\\"\"" \
+ "Add Quick TODO" T "command-prompt -p 'Enter TODO:' 'run-shell \"$0 --add-todo %%\"'" \
+ "Grep/find patterns" g "display-popup -w 100% -h 100% -E \"tmux new-session -A -s $NOTE_SESSION_NAME 'rg --color=always --line-number --no-heading --smart-case . $NOTES_DIR | fzf --delimiter=: --preview \\\"bat --style=numbers --color=always --line-range=:500 {1}\\\" --preview-window=up:60% --height=90% --layout=reverse --border=sharp --ansi | cut -d ':' -f 1 | xargs -r $EDITOR'\"" \
+ "Web Search" s "command-prompt -p 'Search query:' 'run-shell \"$0 --search %%\"'" \
+ "Quit (q)" q ""
+ fi
+}
+
+# make sure tmux is installed
+command -v tmux >/dev/null 2>&1 || { error "tmux is not installed."; exit 1; }
+
+# handle CLI arguments
+if [ "$1" = "--add-todo" ]; then
+ shift
+ add_todo "$*"
+ exit 0
+fi
+
+if [ "$1" = "--search" ]; then
+ shift
+ search_web "$*"
+ exit 0
+fi
+
+if [ "$1" = "--new" ]; then
+ if tmux has-session -t "$NOTE_SESSION_NAME" 2>/dev/null; then
+ # reuse existing session
+ tmux display-popup -w 100% -h 100% -E "
+ FILE=\$(find $NOTES_DIR -type f -name '*.md' \
+ | fzf --preview 'bat --style=numbers --color=always --line-range=:500 {}' \
+ --preview-window=up:60% --height=90% --layout=reverse --border=sharp --ansi)
+ [ -n \"\$FILE\" ] && tmux send-keys -t $NOTE_SESSION_NAME \":e \$FILE\" Enter
+ "
+ else
+ open_menu
+ fi
+ exit 0
+fi
+
+# default behavior: toggle or open menu
+if [ -z "$1" ]; then
+ if tmux has-session -t "$NOTE_SESSION_NAME" 2>/dev/null; then
+ CURRENT_SESSION=$(tmux display-message -p '#S')
+ [ "$CURRENT_SESSION" = "$NOTE_SESSION_NAME" ] && tmux detach-client || tmux display-popup -E -x200% -y0 -w50% -h99% "tmux attach-session -t $NOTE_SESSION_NAME"
+ else
+ open_menu
+ fi
+fi
diff --git a/linux/home/.config/tmux/right-status.sh b/linux/home/.config/tmux/right-status.sh
new file mode 100755
index 0000000..a14bbe3
--- /dev/null
+++ b/linux/home/.config/tmux/right-status.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+
+# Get CPU average
+getCPU=$[100-$(vmstat 1 2|tail -1|awk '{print $15}')]
+
+# Grab the second line of the ouput produced by the command: free -g (displays output in Gb)
+getMem=$(free -h | sed -n '2p')
+getMemPct=$(free -g | sed -n '2p')
+
+# Split the string in secondLine into an array
+read -ra ADDR <<< "$getMem"
+read -ra ADDRPct <<< "$getMemPct"
+
+# Get the total RAM from arrays
+totalRam="${ADDR[1]//[^0-9.0-9]/}"
+totalRamPct="${ADDRPct[1]}"
+
+# Get the used RAM from arrays
+usedRam="${ADDR[2]//[^0-9.0-9]/}"
+usedRamPct="${ADDRPct[2]}"
+
+# Calculate and display the percentages
+pct="$(($usedRamPct*100/$totalRamPct))"
+usage="$usedRam/$totalRam"
+#echo "cpu:$getCPU% | mem:$pct% ($usage""G)"
+echo "Cpu:$getCPU% | Mem:$pct% |"
+
diff --git a/linux/home/.config/tmux/tmux-popup-pane-manager.sh b/linux/home/.config/tmux/tmux-popup-pane-manager.sh
new file mode 100755
index 0000000..bb0cfef
--- /dev/null
+++ b/linux/home/.config/tmux/tmux-popup-pane-manager.sh
@@ -0,0 +1,152 @@
+#!/usr/bin/bash
+# tmux-popup-pane-manager.sh - menu driven tmux pane activities
+# github repo: https://github.com/pl643/tmux-scripts
+# resize, selection, syncronize, layout, splits, kill, break
+
+# sample tmux.conf binding:
+# bind-key -n M-p tmux-popup-pane-manager.sh
+
+[ "$TMUX" = "" ] && echo "NOTE: needs to be run inside a tmux sessions" && exit 1
+
+run_after_popup="/tmp/.run_after_popup"
+realpath="$(realpath "$0")"
+if [ "$1" != "--no-popup" ]; then
+ tmux popup -E -T "────────────── Pane Manager ─────" -w 46 -h 35 "$realpath --no-popup"
+
+ [ -f "$run_after_popup" ] && bash "$run_after_popup" && rm "$run_after_popup"
+ exit 0
+fi
+
+pane_border_status="off"
+display_menu() {
+ clear
+ tmux list-windows | grep active | awk '{print $2}' | tail -c2 | grep -q Z && zoom_status="on" || zoom_status="off"
+ tmux show-options -w | grep -q 'synchronize-panes.*on' && synchronize_panes="on" || synchronize_panes="off"
+ tmux show-options -w | grep -q 'pane-border-status.*top' && pane_border_status="top"
+ tmux show-options -w | grep -q 'pane-border-status.*bottom' && pane_border_status="bottom"
+ printf "
+ Resize
+
+ hjkl x 5 HJKL x 1
+ 1 - 9 | x 10%% ! - ) ─ x 10%%
+ = equally | + equally ─
+
+ Split
+
+ s - spilt - v | spilt |
+
+ Navigation
+
+ n p next/prev pane
+ N P next/prev layout
+ u d swap pane up/down
+
+ Toggles
+
+ b border [ %s ]
+ S syncronize [ %s ]
+ z zoom [ %s ]
+
+ Misc
+
+ B break (make pane into window)
+ o join this pane to window
+ D send C-d
+ e display panes / exit
+ t rename pane
+ X kill (no confirm!)
+ q quit" "$pane_border_status" "$synchronize_panes" "$zoom_status"
+}
+display_menu
+
+# https://www.reddit.com/r/tmux/comments/g9nr01/how_to_show_message_or_effect_when/
+# Uncomment this setting if want status of pane sync on the status bar
+tmux set -ag status-right '#{?pane_synchronized, #[fg=red]IN_SYNC#[default],}'
+
+# https://www.reddit.com/r/tmux/comments/dfj5ye/rename_pane_not_window_is_there_a_builtin/
+tmux set -g pane-border-format " [ ###P #T ] "
+
+# If C-c is press in the while [ true ] loop, a run runaway process occurs, limiting
+# it to 20 will cause the loop to exit after 20 loops. Modify MAXNUMLOOP if you
+# need more keys presses.
+MAXNUMLOOP=100
+COUNTER=0
+while [ "$COUNTER" -lt "$MAXNUMLOOP" ]; do
+
+ read -sn1 c || exit
+
+ # Resize x 1
+ [ "$c" = "H" ] && tmux resize-pane -L 1
+ [ "$c" = "L" ] && tmux resize-pane -R 1
+ [ "$c" = "J" ] && tmux resize-pane -D 1
+ [ "$c" = "K" ] && tmux resize-pane -U 1
+
+ # Resize x 5
+ [ "$c" = "h" ] && tmux resize-pane -L 5
+ [ "$c" = "l" ] && tmux resize-pane -R 5
+ [ "$c" = "j" ] && tmux resize-pane -D 5
+ [ "$c" = "k" ] && tmux resize-pane -U 5
+
+ # Resize X percent
+ [ "$c" = "1" ] && tmux resize-pane -x $(($(tmux display-message -p "#{window_width}") * 10 / 100))
+ [ "$c" = "2" ] && tmux resize-pane -x $(($(tmux display-message -p "#{window_width}") * 20 / 100))
+ [ "$c" = "3" ] && tmux resize-pane -x $(($(tmux display-message -p "#{window_width}") * 30 / 100))
+ [ "$c" = "4" ] && tmux resize-pane -x $(($(tmux display-message -p "#{window_width}") * 40 / 100))
+ [ "$c" = "5" ] && tmux resize-pane -x $(($(tmux display-message -p "#{window_width}") * 50 / 100))
+ [ "$c" = "6" ] && tmux resize-pane -x $(($(tmux display-message -p "#{window_width}") * 60 / 100))
+ [ "$c" = "7" ] && tmux resize-pane -x $(($(tmux display-message -p "#{window_width}") * 70 / 100))
+ [ "$c" = "8" ] && tmux resize-pane -x $(($(tmux display-message -p "#{window_width}") * 80 / 100))
+ [ "$c" = "9" ] && tmux resize-pane -x $(($(tmux display-message -p "#{window_width}") * 90 / 100))
+
+ # Resize Y percent
+ [ "$c" = "!" ] && tmux resize-pane -y $(($(tmux display-message -p "#{window_height}") * 10 / 100))
+ [ "$c" = "@" ] && tmux resize-pane -y $(($(tmux display-message -p "#{window_height}") * 20 / 100))
+ [ "$c" = "#" ] && tmux resize-pane -y $(($(tmux display-message -p "#{window_height}") * 30 / 100))
+ [ "$c" = "$" ] && tmux resize-pane -y $(($(tmux display-message -p "#{window_height}") * 40 / 100))
+ [ "$c" = "%" ] && tmux resize-pane -y $(($(tmux display-message -p "#{window_height}") * 50 / 100))
+ [ "$c" = "^" ] && tmux resize-pane -y $(($(tmux display-message -p "#{window_height}") * 60 / 100))
+ [ "$c" = "&" ] && tmux resize-pane -y $(($(tmux display-message -p "#{window_height}") * 70 / 100))
+ [ "$c" = "*" ] && tmux resize-pane -y $(($(tmux display-message -p "#{window_height}") * 80 / 100))
+ [ "$c" = "(" ] && tmux resize-pane -y $(($(tmux display-message -p "#{window_height}") * 90 / 100))
+
+ # Pane layout cycle
+ [ "$c" = "N" ] || [ "$c" = " " ] && tmux next-layout
+ [ "$c" = "P" ] && tmux previous-layout
+
+ # Pane selection cycle
+ [ "$c" = "n" ] && tmux select-pane -t :.+
+ [ "$c" = "p" ] && tmux select-pane -t :.-
+
+ # Pane layout selection even horizontal/vertical
+ [ "$c" = "=" ] && tmux select-layout even-horizontal
+ [ "$c" = "+" ] && tmux select-layout even-vertical
+
+ # Rotate pane
+ [ "$c" = "u" ] && tmux swap-pane -U
+ [ "$c" = "d" ] && tmux swap-pane -D
+
+ # Syncronize pane
+ [ "$c" = "S" ] && tmux setw synchronize-pane && display_menu
+
+ # border status ( 3 toggle off, top, bottom )
+ [ "$c" = "b" ] && [ "$pane_border_status" = "off" ] && tmux set pane-border-status && display_menu && continue
+ [ "$c" = "b" ] && [ "$pane_border_status" = "top" ] && tmux set pane-border-status bottom && display_menu && continue
+ [ "$c" = "b" ] && [ "$pane_border_status" = "bottom" ] && tmux set pane-border-status off &&
+ pane_border_status="off" && display_menu && continue
+
+ # Split panes
+ [ "$c" = "s" ] || [ "$c" = "-" ] && tmux split -v
+ [ "$c" = "v" ] || [ "$c" = "|" ] && tmux split -h
+
+ # Misc
+ [ "$c" = "B" ] && tmux break-pane
+ [ "$c" = "o" ] && printf "\n\n join window: " && read window && tmux join-pane -t "$window"
+ [ "$c" = "X" ] && tmux kill-pane
+ [ "$c" = "D" ] && tmux send-key C-d
+ display_menu
+ [ "$c" = "q" ] && exit
+ [ "$c" = "e" ] && echo tmux display-panes >"$run_after_popup" && exit
+ [ "$c" = "z" ] && tmux resize-pane -Z && display_menu
+ [ "$c" = "t" ] && printf "\n\n pane name: " && read pane_name && tmux select-pane -T "$pane_name" && display_menu
+ let COUNTER=COUNTER+1
+done
diff --git a/linux/home/.config/tmux/tmux-toggle-option.sh b/linux/home/.config/tmux/tmux-toggle-option.sh
new file mode 100755
index 0000000..52d5fdb
--- /dev/null
+++ b/linux/home/.config/tmux/tmux-toggle-option.sh
@@ -0,0 +1,27 @@
+#!/usr/bin/bash
+
+#USAGE="USAGE: $0 OPTION_NAME ON_STATE OFF_STATE"
+
+#OPTION_NAME=$1
+#ON_STATE=$2
+#OFF_STATE=$3
+#
+#if [[ "$#" != 3 ]]; then
+# echo $USAGE
+# exit 1
+#fi
+#
+#if [[ `tmux show-option -w | grep "$OPTION_NAME $ON_STATE"` ]]; then
+# OPTION_VALUE=$OFF_STATE
+#else
+# OPTION_VALUE=$ON_STATE
+#fi
+#
+#tmux display-message "monitor activity: $OPTION_NAME $OPTION_VALUE"
+#tmux set-option -w $OPTION_NAME $OPTION_VALUE > /dev/null
+
+if [ $(tmux show-option -A status-left) != 'status-left* "#[fg=#50fa7b,bg=default] #[bg=#50fa7b,fg=black]❐ #S #[fg=#50fa7b,bg=default]"' ]; then
+ tmux set -g status-left "#[fg=#50fa7b,bg=default] #[bg=#50fa7b,fg=black]❐ #S #[fg=#50fa7b,bg=default] ";
+else
+ tmux set -g status-left "#[fg=#50fa7b,bg=default]#[bg=#50fa7b,fg=black] ❐ #S #( ~/.config/tmux/left-status.sh ) #[fg=#50fa7b,bg=default]" && tmux set -g status-right "#[fg=#50fa7b,bg=default] #{?client_prefix,#[reverse] Prefix #[noreverse] ,}#[bg=default,fg=#50fa7b]#[bg=#50fa7b,fg=black] #( ~/.config/tmux/right-status.sh ) %d-%b-%y | %H:%M #[bg=default,fg=#50fa7b]";
+fi
diff --git a/linux/home/.config/tmux/tmux.conf b/linux/home/.config/tmux/tmux.conf
new file mode 100644
index 0000000..a62e3e3
--- /dev/null
+++ b/linux/home/.config/tmux/tmux.conf
@@ -0,0 +1,835 @@
+# ████████╗███╗ ███╗██╗ ██╗██╗ ██╗
+# ╚══██╔══╝████╗ ████║██║ ██║╚██╗██╔╝
+# ██║ ██╔████╔██║██║ ██║ ╚███╔╝
+# ██║ ██║╚██╔╝██║██║ ██║ ██╔██╗
+# ██║ ██║ ╚═╝ ██║╚██████╔╝██╔╝ ██╗
+# ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝
+#――――――――――――――――――――――――――――――――――――――――――
+
+### Options ###
+
+# Setting the Prefix from Ctrl+b to Ctrl+s
+unbind C-b
+set -g prefix C-s
+#set -g prefix M-Space
+
+# Ensure that we can send Ctrl+s to other apps
+bind C-s send-prefix
+
+# Check if in (n)vim
+is_vim="ps -o state= -o comm= -t '#{pane_tty}' | grep -iqE '^[^TXZ ]+ +(\\S+\\/)?g?(view|n?vim?x?)(diff)?$'"
+
+# Send the prefix to client inside window (nested tmux)
+#bind-key -n C-a send-prefix
+bind -n C-a if-shell "$is_vim" "send-keys C-a" "send-prefix"
+
+# Disable local tmux keys (nested tmux)
+bind -T root F12 \
+ set prefix None \;\
+ set key-table off \;\
+ if -F '#{pane_in_mode}' 'send-keys -X cancel' \;\
+ refresh-client -S \;\
+
+bind -T off F12 \
+ set -u prefix \;\
+ set -u key-table \;\
+ refresh-client -S
+
+# Toggle Sync Panes
+bind C-y set-window-option synchronize-panes\; display-message "Synchronize-Panes is now #{?pane_synchronized,on,off}"
+
+# Setting the delay between Prefix and Command
+set -sg escape-time 10
+#set-option -sg escape-time 10
+
+# Reload tmux with <Prefix>r
+bind r source-file ~/.config/tmux/tmux.conf \; display "Reloaded!"
+
+# Use <Prefix>L to clear terminal
+bind -r L send-keys "clear" Enter
+
+# Rename current window (Ctrl + A, A)
+bind R rename-window '' \; \
+ command-prompt -I "#W" "rename-window -- '%%'"
+
+# Auto rename pane
+set -wg automatic-rename on
+set -g automatic-rename-format "#{pane_current_command}"
+
+# Renumber all windows when one is killed
+set -g renumber-windows on
+
+# Mouse
+set -g mouse on
+
+# Right Click Menu
+bind -n MouseDown3Pane \
+ if-shell -F "#{||:#{match:#{pane_current_command},nvim},#{match:#{pane_current_command},vim}}" \
+ "send-keys -M" \
+ "display-menu -t= -xM -yM -T '#[align=centre]#{pane_index} (#{pane_id})' \
+ 'Vim' v 'send-keys \"vi\" Enter' \
+ 'File-manager' f 'send-keys \"yazi\" Enter' \
+ '' \
+ 'Horizontal Split' '\\\\' 'split-window -h -c \"#{pane_current_path}\"' \
+ 'Vertical Split' '-' 'split-window -v -c \"#{pane_current_path}\"' \
+ '' \
+ 'Copy Mode' 'C' 'copy-mode -e' \
+ 'Paste' 'p' {send-keys C-v} \
+ 'Open with xdg-open' 'o' 'send-keys \"xdg-open #{pane_current_path}\"' \
+ 'Enter' 'e' 'send-keys Enter' \
+ '' \
+ 'Go To Top' '↑' 'copy-mode -e; send-keys gg' \
+ 'Go To Bottom' '↓' 'copy-mode -e; send-keys G' \
+ '' \
+ 'Break Pane' 't' 'break-pane' \
+ 'Join Pane' 'j' 'choose-window \"join-pane -h -s %%\"' \
+ '#{?pane_marked,Unmark,Mark}' 'm' 'select-pane -m' \
+ '#{?#{>:#{window_panes},1},,-}Swap Up' 'u' 'swap-pane -U' \
+ '#{?#{>:#{window_panes},1},,-}Swap Down' 'd' 'swap-pane -D' \
+ '' \
+ 'New Window' 'n' 'new-window' \
+ 'Previous Window' '🡠' 'previous-window' \
+ 'Next Window' '🡢' 'next-window' \
+ 'Swap Window Left' '<' 'swap-window -t -1' \
+ 'Swap Window Right' '>' 'swap-window -t +1' \
+ '' \
+ 'Kill' 'X' 'kill-pane' \
+ 'Respawn' 'R' 'respawn-pane -k' \
+ 'Interrupt (Ctrl+C)' 'c' 'send-keys C-c' \
+ '#{?#{>:#{window_panes},1},,-}#{?window_zoomed_flag,Unzoom,Zoom}' 'z' 'resize-pane -Z'"
+
+
+# Disable copy on primary selection (prevents auto-copy on mouse drag release)
+unbind -T root MouseDrag1Pane
+unbind -T copy-mode MouseDragEnd1Pane
+unbind -T copy-mode-vi MouseDragEnd1Pane
+
+# Left-click to select a pane (no copy mode)
+bind -T root MouseDown1Pane {
+ select-pane -t= # Select pane under mouse
+ send-keys -M # Enable mouse interaction in pane
+}
+
+# Don't go into copy-mode on first click/drag
+bind -T root MouseDrag1Pane if-shell '[ "#{mouse_flags}" = "drag" ]' {
+ if-shell '[ "#{pane_id}" = "#{mouse_pane}" ]' {
+ copy-mode
+ send-keys -X begin-selection
+ } {
+ select-pane -t=
+ send-keys -M
+ }
+} {
+ select-pane -t=
+ send-keys -M
+}
+
+# Left-click to start selection in copy-mode
+bind -T root DoubleClick1Pane {
+ copy-mode
+ send-keys -X begin-selection
+}
+
+# Left-click to start selection in copy-mode
+bind -T copy-mode-vi MouseDown1Pane {
+ send-keys -X cancel-selection
+ send-keys -X begin-selection
+}
+
+# Left-click drag to start selection in copy-mode
+bind -T copy-mode-vi MouseDown1Pane {
+ send-keys -X begin-selection
+}
+
+# Right click to copy the selected text and exit copy mode
+bind -T copy-mode-vi MouseDown3Pane {
+ send-keys -X copy-pipe-no-clear
+ send-keys -X end-selection
+ send-keys Escape
+}
+
+# Middle click exit copy mode without copying (cancel the selection)
+bind -T copy-mode-vi MouseDown2Pane {
+ send-keys -X end-selection
+ send-keys Escape
+}
+
+# Middle click paste if not in copy-mode
+# Conditional behavior for MouseDown2Pane
+if-shell '[ "$CTRL_V_PASTE" = "true" ]' \
+ "bind -T root MouseDown2Pane send-keys C-v" \
+ "bind -T root MouseDown2Pane send-keys C-S-v"
+
+## Right-click drag to exit copy mode without selecting text
+#bind -T copy-mode-vi MouseDragEnd3Pane {
+# send-keys -X end-selection # End selection
+# send-keys Escape # Exit copy mode
+#}
+
+# Make mouse-drag work only with Ctrl
+#unbind -T root MouseDrag1Pane
+#unbind -T copy-mode-vi MouseDrag1Pane
+#bind -n C-MouseDrag1Pane if -Ft= \
+#'#{mouse_any_flag}' 'if -Ft= \"#{pane_in_mode}\" \"copy-mode -M\" send-keys -X \"send-keys -M\"' 'copy-mode -M'
+#bind -T copy-mode-vi C-MouseDrag1Pane send-keys -X begin-selection
+
+# Exiting copy mode without copying (Left-click to exit)
+#bind -T copy-mode-vi MouseDown1Pane {
+# select-pane -t= # Deselect pane
+# send-keys Escape # Exit copy mode
+#}
+
+
+# Scrollback
+set -g history-limit 10000
+
+# Sane scrolling
+set -g terminal-overrides 'xterm*:smcup@:rmcup@'
+
+# Sane scrolling
+bind -n WheelUpPane {
+ if -F '#{==:#{window_name},nvim}' {
+ #send-keys -M
+ send-keys Up
+ } {
+ copy-mode -e
+ }
+}
+bind -n WheelDownPane {
+ if -F '#{==:#{window_name},nvim}' {
+ #send-keys -M
+ send-keys Down
+ } {
+ copy-mode -e
+ }
+}
+
+# Focus events, allow supported requests from applications to passthrough/run in tmux
+set-option -g focus-events on
+
+# Update the TERM variable of terminal emulator when creating a new session or attaching a existing session
+set -g update-environment 'DISPLAY SSH_ASKPASS SSH_AGENT_PID SSH_CONNECTION WINDOWID XAUTHORITY TERM'
+
+# New session
+#bind -n M-N new-session
+
+
+# Lock
+#set-option -g lock-command "vlock -c"
+#set -g lock-after-time 910 # Seconds; 0 = never
+#bind -n M-Escape lock-session
+
+
+#――――――――――――――――――――――――――――――――――――――――――
+
+### Window Movement/Control ###
+
+# Aggressive resizing, useful when using "grouped sessions" and multi-monitor setup
+setw -g aggressive-resize on
+
+# Menu
+bind-key -n M-m display-menu -x W -y S \
+ "New Session" S "command-prompt -p \"New Session:\" \"new-session -A -s '%%'\"" \
+ "Kill Session" x "kill-session" \
+ "Kill Other Session(s)" X "kill-session -a" \
+ "" \
+ "New Window" ␍ new-window \
+ "Kill Window" k "killw" \
+ "Choose Window" w choose-window \
+ "Previous Window" 🡠 previous-window \
+ "Next Window" 🡢 next-window \
+ "Swap Window Right" ↑ "swap-window -t -1" \
+ "Swap Window Left" ↓ "swap-window -t +1" \
+ "Horizontal Split" v "split-window -h" \
+ "Vertical Split" s "split-window -v" \
+ "" \
+ "Layout Horizontal" h "select-layout even-horizontal" \
+ "Layout Vertical" k "select-layout even-horizontal" \
+ "" \
+ "Swap Pane Up" < "swap-pane -U" \
+ "Swap Pane Down" > "swap-pane -D" \
+ "Break Pane" t break-pane \
+ "Join Pane" j "choose-window 'join-pane -h -s \"%%\"'" \
+ "#{?window_zoomed_flag,Unzoom,Zoom}" z "resize-pane -Z"
+
+
+# Popup Pane Manager
+bind-key -n M-/ run-shell ~/.config/tmux/tmux-popup-pane-manager.sh
+
+# List sessions
+
+#bind M-q display-popup -E -w 75% -h 75% "\
+# tmux list-sessions -F '#{?session_attached,,#{session_name}}' |\
+# sed '/^$/d' |\
+# fzf --reverse --header jump-to-session --preview 'tmux capture-pane -pt {}' |\
+# xargs tmux switch-client -t"
+
+# Kill sessions
+bind M-q display-popup -E "\
+ tmux list-sessions -F '#{?session_attached,,#{session_name}}' |\
+ fzf --reverse -m --header=kill-session |\
+ xargs -I {} tmux kill-session -t {}"
+
+# List sessions
+bind -n M-Space display-popup -E "\
+ tmux list-sessions -F '#{?session_attached,,#{session_name}}' |\
+ sed '/^$/d' |\
+ fzf --reverse --header jump-to-session --preview 'tmux capture-pane -pt {}' |\
+ xargs tmux switch-client -t"
+
+# Session chooser
+#bind -n M-q choose-tree -Zs -O time
+
+# Quick window select
+bind -n M-? list-keys
+bind -n M-0 select-window -t :=0
+bind -n M-1 select-window -t :=1
+bind -n M-2 select-window -t :=2
+bind -n M-3 select-window -t :=3
+bind -n M-4 select-window -t :=4
+bind -n M-5 select-window -t :=5
+bind -n M-6 select-window -t :=6
+bind -n M-7 select-window -t :=7
+bind -n M-8 select-window -t :=8
+bind -n M-9 select-window -t :=9
+
+# move pane to existing window or create it
+bind-key -n M-! run -C '#{?#{m:*|1|*,|#{W:#I|}},joinp -ht :1,breakp -t :1}'
+bind-key -n M-@ run -C '#{?#{m:*|2|*,|#{W:#I|}},joinp -ht :2,breakp -t :2}'
+bind-key -n M-# run -C '#{?#{m:*|3|*,|#{W:#I|}},joinp -ht :3,breakp -t :3}'
+bind-key -n M-$ run -C '#{?#{m:*|4|*,|#{W:#I|}},joinp -ht :4,breakp -t :4}'
+bind-key -n M-% run -C '#{?#{m:*|5|*,|#{W:#I|}},joinp -ht :5,breakp -t :5}'
+bind-key -n M-^ run -C '#{?#{m:*|6|*,|#{W:#I|}},joinp -ht :6,breakp -t :6}'
+bind-key -n M-& run -C '#{?#{m:*|7|*,|#{W:#I|}},joinp -ht :7,breakp -t :7}'
+bind-key -n M-* run -C '#{?#{m:*|8|*,|#{W:#I|}},joinp -ht :8,breakp -t :8}'
+bind-key -n M-( run -C '#{?#{m:*|9|*,|#{W:#I|}},joinp -ht :9,breakp -t :9}'
+bind-key -n M-) run -C '#{?#{m:*|0|*,|#{W:#I|}},joinp -ht :0,breakp -t :0}'
+
+#――――――――――――――――――――――――――――――――――――――――――
+
+### Pane Movement/Control ###
+
+# Smart pane switching with awareness of Vim splits.
+# See: https://github.com/christoomey/vim-tmux-navigator
+# Navigate across tmux-vim
+
+# {{{ keybinds: select-pane
+#bind -Tnav h select-pane -L
+#bind h select-pane -L
+#bind -Tnav C-h select-pane -L
+#bind C-h select-pane -L
+#bind -Tnav j select-pane -D
+#bind j select-pane -D
+#bind -Tnav C-j select-pane -D
+#bind C-j select-pane -D
+#bind -Tnav k select-pane -U
+#bind k select-pane -U
+#bind -Tnav C-k select-pane -U
+#bind C-k select-pane -U
+#bind -Tnav l select-pane -R
+#bind l select-pane -R
+#bind -Tnav C-l select-pane -R
+#bind C-l select-pane -R
+
+# {{{ keybinds: select-pane
+# Smart pane switching with Vim awareness
+is_vim="ps -o state= -o comm= -t '#{pane_tty}' | grep -iqE '^[^TXZ ]+ +(n?vim|vimx?)'"
+
+bind -n C-h if-shell "$is_vim" "send-keys C-\\ C-n C-h" "select-pane -L"
+bind -n C-j if-shell "$is_vim" "send-keys C-\\ C-n C-j" "select-pane -D"
+bind -n C-k if-shell "$is_vim" "send-keys C-\\ C-n C-k" "select-pane -U"
+bind -n C-l if-shell "$is_vim" "send-keys C-\\ C-n C-l" "select-pane -R"
+# keybinds: select-pane }}}
+
+# {{{ keybinds: split-window
+bind -Tnav "\\" split-window -h -c "#{pane_current_path}" # vertical
+bind "\\" split-window -h -c "#{pane_current_path}" # vertical
+bind -Tnav "|" split-window -fh -c "#{pane_current_path}" # full vertical
+bind "|" split-window -fh -c "#{pane_current_path}" # full vertical
+bind -Tnav "-" split-window -v -c "#{pane_current_path}" # horizontal
+bind "-" split-window -v -c "#{pane_current_path}" # horizontal
+bind -Tnav "_" split-window -fv -c "#{pane_current_path}" # full horizontal
+bind "_" split-window -fv -c "#{pane_current_path}" # full horizontal
+# keybinds: split-window }}}
+
+# {{{ keybinds: resize-pane
+bind -r -Tnav M-h resize-pane -L 10
+bind -r M-h resize-pane -L 10
+bind -r -Tnav M-j resize-pane -D 5
+bind -r M-j resize-pane -D 5
+bind -r -Tnav M-k resize-pane -U 5
+bind -r M-k resize-pane -U 5
+bind -r -Tnav M-l resize-pane -R 10
+bind -r M-l resize-pane -R 10
+# keybinds: resize-pane }}}
+
+# Hide a pane and bring it back with <Prefix>! and <Prefix>@ respectively
+bind-key ! break-pane -d -n _hidden_pane
+bind-key @ join-pane -s $.0
+
+# Send Pane to another session/window with <Prefix>=
+bind-key = command-prompt -p "send pane to:" "join-pane -t '%%'"
+
+#――――――――――――――――――――――――――――――――――――――――――
+
+### Popup ###
+
+# Toggle popup "term" session
+set -g @term_session_name "term"
+bind -n M-t if-shell -F '#{==:#{session_name},term}' {
+ detach-client
+ } {
+ if-shell "tmux has-session -t term" {
+ set -gF '@last_session_name' '#S' # Store the current session name
+ display-popup -E -xC -yC -w60% -h60% "tmux attach-session -t term"
+ } {
+ set -gF '@last_session_name' '#S' # Store the current session name
+ display-popup -E -xC -yC -w60% -h60% "tmux new-session -d -c '#{pane_current_path}' -s term && tmux set-option -t term status off && tmux attach-session -t term"
+ }
+}
+
+
+## Toggle popup "note" session
+bind-key -n M-n if-shell -F "#{client_in_popup}" \
+ "detach-client -P" \
+ "run-shell ~/.config/tmux/notes.sh"
+
+bind-key -n M-N run-shell "~/.config/tmux/notes.sh --new"
+
+# Toggle popup "pack" session
+bind-key -n M-p if-shell -F '#{==:#{session_name},pack}' {
+ detach-client
+} {
+ set -gF '@last_session_name' '#S' # Store the current session name
+ #display-popup -w 80% -h 80% -E "tmux new-session -A -s pack"
+ display-popup -E -x200% -y0 -w50% -h99% "tmux new-session -A -s pack"
+}
+
+# M-T → Open TODO.md in popup
+bind-key -n M-o if-shell -F '#{==:#{session_name},todo}' {
+
+ detach-client
+} {
+ set -gF '@last_session_name' '#S'
+ display-popup -E -x200% -y0 -w50% -h99% "tmux new-session -A -s todo 'nvim ~/documents/main/inbox/tasks/TODO.md'"
+}
+
+# M-y → Toggle VM popup (starts/attaches session "virt" inside popup)
+bind-key -n M-y if-shell -F '#{==:#{session_name},virt}' {
+ detach-client -P
+} {
+ set -gF '@last_session_name' '#S'
+ display-popup -E -x200% -y0 -w40% -h60% "tmux new-session -A -s virt bash -lc 'echo \"### VM Manager ###\"; echo; echo \"Available VM scripts:\"; ls -1 ~/.scripts/env/virt/ 2>/dev/null || echo \"No scripts found in ~/.scripts/env/virt/\"; echo; echo \"Run your VM by typing its script name (e.g., ubuntu, fedora, win11).\"; exec \$SHELL'"
+}
+
+# M-H → Open history in popup
+bind-key -n M-H if-shell -F '#{==:#{session_name},hist}' {
+ detach-client
+} {
+ set -gF '@last_session_name' '#S'
+ display-popup -E -x200% -y0 -w50% -h99% "tmux new-session -A -s hist \"bash -c \'
+ shell=\\\$(basename \\\$SHELL)
+ if [[ \\\$shell == zsh ]]; then
+ if [[ -f \\\$HOME/.config/zsh/.zhistory ]]; then
+ nvim \\\$HOME/.config/zsh/.zhistory
+ elif [[ -f \\\$HOME/.zhistory ]]; then
+ nvim \\\$HOME/.zhistory
+ else
+ echo \\\"No Zsh history found\\\"; sleep 5
+ fi
+ elif [[ \\\$shell == bash ]]; then
+ if [[ -f \\\$HOME/.bash_history ]]; then
+ nvim \\\$HOME/.bash_history
+ else
+ echo \\\"No Bash history found\\\"; sleep 5
+ fi
+ else
+ echo \\\"Unknown shell: \\\$shell\\\"; sleep 5
+ fi
+ \'\""
+}
+
+# Move popup session (note, term, or pack) to scratchpad (tmux)/last session or back to respective popup
+bind -n M-x run-shell '
+ current_session=$(tmux display-message -p "#S");
+
+ # Check if the current session is one of the popups (note, term, pack)
+ if [ "$current_session" = "note" ] || [ "$current_session" = "term" ] || [ "$current_session" = "pack" ]; then
+ # Detach, break the pane out of the popup, and join it to the last session
+ tmux detach-client;
+ tmux break-pane;
+ last_session=$(tmux show -gvq @last_session_name || echo tmux);
+ tmux set -g @last_popup_session "$current_session"; # Save which popup it was
+ tmux join-pane -s "$current_session" -t "$last_session" -h;
+ else
+ # If not in one of the popups, move the last popup session back to its popup
+ last_popup=$(tmux show -gqv @last_popup_session);
+
+ if [ "$last_popup" = "note" ]; then
+ if ! tmux has-session -t note; then
+ # Spawn note session in detached mode without blocking
+ tmux new-session -d -s note;
+ fi
+ tmux join-pane -s $(tmux display-message -p "#P") -t note;
+ tmux kill-pane -a -t note:0;
+ elif [ "$last_popup" = "term" ]; then
+ if ! tmux has-session -t term; then
+ tmux new-session -d -s term;
+ fi
+ tmux join-pane -s $(tmux display-message -p "#P") -t term;
+ tmux kill-pane -a -t term:0;
+ elif [ "$last_popup" = "pack" ]; then
+ if ! tmux has-session -t pack; then
+ tmux new-session -d -s pack;
+ fi
+ tmux join-pane -s $(tmux display-message -p "#P") -t pack;
+ tmux kill-pane -a -t pack:0;
+ fi
+ fi'
+# TODO: add keybinding that will allow going across the dipslay popup and main tmux session
+# TODO: add keybinding for toggling the display popup size to/from fullscreen and it's original size
+
+# Toggle popup "fzf" session
+#bind-key -n M-o run-shell ~/.config/tmux/fzf-menu.sh
+
+# Toggle popup "htop" session
+bind -n M-i if-shell -F '#{==:#{session_name},htop}' {
+ detach-client
+ } {
+ if-shell "tmux has-session -t HUD" {
+ display-popup -E -x200% -y0 -w40% -h40% "tmux new-session -A -s HUD"
+ } {
+ display-popup -E -x200% -y0 -w40% -h40% "tmux new-session -A -s htop 'htop'"
+ }
+}
+
+# Toggle popup "lazygit" session
+bind-key -n M-g if-shell -F '#{==:#{session_name},lazygit}' {
+ detach-client
+} {
+ display-popup -w 90% -h 90% -E "tmux new-session -A -s lazygit 'lazygit'"
+}
+
+## Toggle popup "yazi" session
+#bind-key -n M-f if-shell -F '#{==:#{session_name},yazi}' {
+# detach-client
+#} {
+# display-popup -w 90% -h 90% -E "tmux new-session -A -s yazi 'yazi'"
+#}
+#tmux split-window -h -p 30 \
+# "NNN_OPENER=~/.config/tmux/file_manager.sh nnn"
+
+#bind-key -n M-f run-shell 'tmux split-window -hb -p 30 "~/.config/tmux/file_manager.sh $(tmux display-message -p "#I.#P")"'
+
+# Robust file manager toggle - checks both title and option
+bind-key -n M-f run-shell "\
+fm_pane_title=\$(tmux list-panes -F '#{pane_id} #{pane_title}' | awk '\$2==\"FILE_MANAGER\" {print \$1}'); \
+fm_pane_option=\$(tmux list-panes -F '#{pane_id} #{@file_manager}' | awk '\$2==\"1\" {print \$1}'); \
+fm_pane=\${fm_pane_title:-\$fm_pane_option}; \
+if [ -n \"\$fm_pane\" ]; then \
+ tmux kill-pane -t \"\$fm_pane\"; \
+else \
+ current_pane=\$(tmux display-message -p '#{session_name}:#{window_index}.#{pane_index}'); \
+ tmux split-window -hb -p 30 \"~/.config/tmux/file_manager.sh \$current_pane\"; \
+fi"
+
+## Toggle popup "speedtest" session
+#bind-key -n M-s if-shell -F '#{==:#{session_name},speedtest}' {
+# detach-client
+#} {
+# display-popup -E "tmux new-session -A -s speedtest 'speedtest'"
+#}
+
+
+# Toggle popup "nvim" session
+bind-key -n M-v if-shell -F '#{==:#{session_name},edit}' {
+ detach-client
+} {
+ display-popup -w 100% -h 100% -E "tmux new-session -A -s edit 'cd && nvim'"
+}
+
+
+# Toggle popup "ssh" session
+bind-key -n M-s if-shell -F '#{==:#{session_name},ssh}' {
+ detach-client
+} {
+ display-popup -w 40% -h 50% -E "tmux new-session -A -s ssh"
+}
+
+# Toggle popup session to fullscreen
+bind-key -n M-F run-shell '
+ popup_session=$(tmux display-message -p "#{session_name}");
+ if [[ "$popup_session" == "term" || "$popup_session" == "note" || "$popup_session" == "lazygit" || "$popup_session" == "yazi" || "$popup_session" == "speedtest" || "$popup_session" == "nvim" || "$popup_session" == "ssh" || "$popup_session" == "pack" || "$popup_session" == "htop" ]]; then
+ tmux detach-client
+ sleep 0.1
+ tmux display-popup -E -w 100% -h 100% "tmux new-session -A -s $popup_session"
+ fi
+'
+
+bind -n M-L lock-session
+
+#bind-key -n M-o display-popup -E nvim -c ":ObsidianNew"
+#bind-key -n M-/ display-popup -w "90%" -h "85%" -E nvim -c ":ObsidianSearch"
+
+
+#proc
+#netw
+#ssh
+
+
+#――――――――――――――――――――――――――――――――――――――――――
+
+### Copy Mode (Copy/Paste) ###
+
+# Set Vi copy mode, use <prefix>[ to enter copy mode
+setw -g mode-keys vi # `<prefix>:list-keys -T copy-mode-vi` to confirm
+
+# super fast way to reach copy-mode and search upwards
+bind-key / copy-mode \; send-key ?
+
+# Alt + Space copy-mode without prefix
+bind-key -n M-c copy-mode
+
+# Shift up/down copy-mode without prefix
+bind-key -n S-Up copy-mode \; send-key Up
+bind-key -n S-Down copy-mode \; send-key Down
+bind-key -n Pageup copy-mode \; send-key Pageup
+bind-key -n Pagedown copy-mode \; send-key Pagedown
+#bind -n Pageup if-shell "$is_vim" "send-keys Pageup" "copy-mode -u"
+#bind -n S-Pageup if-shell "$is_vim" "send-keys Pageup" "copy-mode -u"
+bind -n Pageup if-shell "$is_vim" "send-keys Pageup"
+bind -n S-Pageup if-shell "$is_vim" "send-keys Pageup"
+bind -n S-Pagedown send-keys Pagedown
+
+# Change selection <space> and enter to vi keybinding
+bind-key -T copy-mode-vi 'v' send -X begin-selection
+bind-key -T copy-mode-vi 'y' send -X copy-selection-and-cancel
+bind -T copy-mode-vi Escape send -X cancel
+bind -T copy-mode-vi C-c send -X clear-selection
+bind -T copy-mode-vi C-v send -X begin-selection \; send-keys -X rectangle-toggle
+
+# Unbind any previous 'y' in copy-mode-vi to prevent conflicts
+unbind -T copy-mode-vi 'y'
+
+# Clipboard copy for X11/XWayland
+if-shell -b '[ "$DISPLAY" ] && command -v xclip >/dev/null' "\
+ bind-key -T copy-mode-vi 'y' send-keys -X copy-pipe-and-cancel 'xclip -in -selection clipboard > /dev/null 2>&1'"
+
+# Clipboard copy for Wayland
+if-shell -b '[ \"$WAYLAND_DISPLAY\" ] && command -v wl-copy >/dev/null' "\
+ bind-key -T copy-mode-vi 'y' send-keys -X copy-pipe-and-cancel 'wl-copy --foreground --type text/plain > /dev/null 2>&1'"
+
+# macOS clipboard copy
+if-shell -b '[ \"$(uname -s)\" = \"Darwin\" ] && command -v pbcopy >/dev/null' "\
+ bind-key -T copy-mode-vi 'y' send-keys -X copy-pipe-and-cancel 'pbcopy'"
+
+# Windows (WSL) clipboard copy
+if-shell -b 'uname -r | grep -qi microsoft && command -v clip.exe >/dev/null' "\
+ bind-key -T copy-mode-vi 'y' send-keys -X copy-pipe-and-cancel 'clip.exe > /dev/null 2>&1'"
+
+# For OSC-Yank in vim to work over ssh
+# https://github.com/ojroques/vim-oscyank
+#set -s set-clipboard on
+#set -g set-clipboard on
+#set -g terminal-overrides 'xterm*:paste:Ctrl+Shift+V'
+
+set -g allow-passthrough on
+set extended-keys on
+
+# notify when a window has activity
+set-window-option -g monitor-activity on
+
+# Copy mode search with a simple shortcut (@see https://superuser.com/a/1253137)
+bind-key / copy-mode \; send-key ?
+
+#――――――――――――――――――――――――――――――――――――――――――
+
+### Colors ###
+
+## Assume external terminal supports the 256 colors palette (when TERM=xterm-256color)
+#set -sa terminal-features ",xterm-256color:256"
+## Assume external terminal supports RGB colors (when TERM=xterm-256color)
+#set -sa terminal-features ",xterm-256color:RGB"
+#
+## Set TERM for proper colors
+#set -g default-terminal "tmux-256color"
+#set -g default-terminal "xterm-256color"
+
+# Set 256 color terminal
+#set-option -sa terminal-overrides ",tmux-256color:Tc"
+#set-option -sa terminal-overrides ",xterm*:Tc,alacritty*:Tc"
+
+# Colors for pane borders(default)
+setw -g pane-border-style fg=white
+setw -g pane-active-border-style fg=green
+
+# Active pane normal, other shaded out
+setw -g window-style fg=colour240
+setw -g window-active-style fg=white
+
+# Popup border
+set -g popup-border-lines rounded
+
+#
+
+#――――――――――――――――――――――――――――――――――――――――――
+
+### Status ###
+
+set -g status-position bottom # [top, bottom]
+set -g status on
+#set -g status-interval 1
+set -g status-interval 500
+#set -g status-style fg=#50fa7b,bg=default
+
+# Toggle status
+bind T set status
+
+# Inactive windows
+set -g status-style bg=default
+set -g window-status-activity-style noreverse
+set -g window-status-format "#[fg=#ffffff,bg=default]#I: #W"
+
+## Left
+set -g status-left ""
+set -g status-left-length 60
+
+## Center
+set -g status-justify absolute-centre
+
+## Right
+set -g status-right-length 60
+set-option -g status-right ""
+
+
+### Status Style
+set -g status-style "bg=default,fg=#CDD6F4"
+#set -g status-left-length 100
+#set -g status-right-length 150
+
+### Left
+set -g status-left '#( \
+ if fc-list | grep -qi nerd; then \
+ cat ~/.vi-mode | awk '\''/-- NORMAL --/ {print "#[fg=#39BAE6]#[bg=default]#[bg=#39BAE6,fg=#000000] ❐ #S #[fg=#39BAE6,bg=default]"} \
+ /-- INSERT --/ {print "#[fg=#50fa7b]#[bg=default]#[bg=#50fa7b,fg=#000000] ❐ #S #[fg=#50fa7b,bg=default]"}'\''; \
+ else \
+ cat ~/.vi-mode | awk '\''/-- NORMAL --/ {print "#[bg=default,fg=#39BAE6] #S "} \
+ /-- INSERT --/ {print "#[bg=default,fg=#50fa7b] #S "}'\''; \
+ fi \
+)'
+
+### Center
+set -g window-status-current-format '#( \
+ if fc-list | grep -qi nerd; then \
+ cat ~/.vi-mode | awk '\''/-- NORMAL --/ {print "#[fg=#39BAE6]#[bg=default]#[fg=#000000,bg=#39BAE6] #I:#W #[bg=default,fg=#39BAE6]"} \
+ /-- INSERT --/ {print "#[fg=#50fa7b]#[bg=default]#[fg=#000000,bg=#50fa7b] #I:#W #[bg=default,fg=#50fa7b]"}'\''; \
+ else \
+ cat ~/.vi-mode | awk '\''/-- NORMAL --/ {print "#[fg=#39BAE6,bg=default] #I:#W "} \
+ /-- INSERT --/ {print "#[fg=#50fa7b,bg=default] #I:#W "}'\''; \
+ fi \
+)'
+
+### Right
+set -g status-right '#( \
+ key_off="#[fg=#50fa7b,bg=default]#([ $(tmux show-option -qv key-table) = off ] && echo KEYS\ OFF)#[default]"; \
+ vi_segment=$(cat ~/.vi-mode | awk '\''/-- NORMAL --/ {print "#[fg=#50fa7b,bg=default] #[fg=#39BAE6]#[bg=#39BAE6,fg=#000000] %H:%M #[bg=default,fg=#39BAE6]"} \
+ /-- INSERT --/ {print "#[fg=#39BAE6,bg=default] #[fg=#50fa7b]#[bg=#50fa7b,fg=#000000] %H:%M #[bg=default,fg=#50fa7b]"}'\'')
+
+ if fc-list | grep -qi nerd; then \
+ echo "$key_off $vi_segment"; \
+ else \
+ cat ~/.vi-mode | awk '\''/-- NORMAL --/ {print "#[fg=#50fa7b,bg=default] #[fg=#39BAE6] %H:%M "} \
+ /-- INSERT --/ {print "#[fg=#39BAE6,bg=default] #[fg=#50fa7b] %H:%M "}'\''; \
+ fi \
+)'
+
+# Force refresh after session creation
+#set-hook -g after-new-session 'source-file ~/.tmux.conf'
+#set-hook -g client-attached 'source-file ~/.tmux.conf'
+
+
+# Set environment variable from script output
+#run-shell 'tmux set-environment -g NERD_FONT_DETECTED "$(~/.config/tmux/detect_nerd_font)"'
+
+## Reload Status with IP addr, Cpu, Mem and Date
+bind a run-shell ~/.config/tmux/tmux-toggle-option.sh
+
+## Numbers
+bind < command-prompt -p index "run-shell '~/.config/tmux/tmux_number.sh %%'"
+
+## Reload Config/Status silently
+bind b source-file ~/.config/tmux/tmux.conf
+
+set-option -g default-shell "/usr/bin/zsh"
+
+
+#――――――――――――――――――――――――――――――――――――――――――
+
+### Plugin Install ###
+
+set -g @plugin "tmux-plugins/tpm"
+set -g @plugin "tmux-plugins/tmux-sensible"
+set -g @plugin "tmux-plugins/tmux-resurrect"
+set -g @plugin "tmux-plugins/tmux-continuum"
+#set -g @plugin "loichyan/tmux-toggle-popup"
+set -g @plugin "christoomey/vim-tmux-navigator"
+#set -g @plugin "tmux-plugins/tmux-yank"
+#set -g @plugin 'srdusr/tmux-vi-mode'
+#set -g @plugin 'vi-mode ~/.config/tmux/plugins/vi-mode.sh'
+
+
+set -g @plugin 'catppuccin/tmux'
+set -g @plugin 'tmux-plugins/tmux-online-status'
+set -g @plugin 'tmux-plugins/tmux-battery'
+
+#――――――――――――――――――――――――――――――――――――――――――
+
+### Plugins Settings ###
+
+# Plugin to save and restore tmux sessions after restart
+# * Save with: <Prefix> + Ctrl-s
+# * Restore with: <Prefix> + Ctlr-r
+# Change default save and restore keybindings
+ set -g @resurrect-save "W" # <Prefix> + W
+ set -g @resurrect-restore "E" # <Prefix> + E
+
+# Restore vim and nvim sessions as well
+# For vim:
+ set -g @resurrect-strategy-vim "session"
+# For neovim:
+ set -g @resurrect-strategy-nvim "session"
+
+# Automatic restore
+ #set -g @continuum-restore "on"
+ #set -g @continuum-boot "on"
+
+# Restore Panes
+# set -g @resurrect-capture-pane-contents "on"
+
+# This is a hook for tmux-resurrect which tells it to kill session 0 before restoring the panels
+ set -g @resurrect-hook-pre-restore-pane-processes "tmux switch-client -n && tmux kill-session -t=0"
+
+# Tmux navigation
+ #set -g @plugin 'christoomey/vim-tmux-navigator'
+# Plugin uninstall
+ # Delete or comment out the plugin in .tmux.conf.
+ # Press <prefix> + alt + u to remove the plugin.
+
+# >>>>> CATPPUCCIN CONFIGS <<<<<
+
+# Configure Catppuccin
+set -g @catppuccin_flavor "mocha"
+set -g @catppuccin_status_background "none"
+set -g @catppuccin_window_status_style "none"
+set -g @catppuccin_pane_status_enabled "off"
+set -g @catppuccin_pane_border_status "off"
+
+# Configure Online status
+set -g @online_icon "ok"
+set -g @offline_icon "nok"
+
+# TMUX plugin manager (keep at the bottom of tmux.conf)
+ #run "~/.config/tmux/plugins/tpm/tpm"
+ if "test ! -d ~/.config/tmux/plugins/tpm" \
+ "run 'git clone https://github.com/tmux-plugins/tpm ~/.config/tmux/plugins/tpm && ~/.config/tmux/plugins/tpm/bin/install_plugins'"
+run -b "~/.config/tmux/plugins/tpm/tpm"
diff --git a/linux/home/.config/tmux/tmux_number.sh b/linux/home/.config/tmux/tmux_number.sh
new file mode 100755
index 0000000..5d239d0
--- /dev/null
+++ b/linux/home/.config/tmux/tmux_number.sh
@@ -0,0 +1,11 @@
+#!/bin/sh
+set -x
+dest="$1"
+[ "x""$dest" != "x" ]
+tmux list-windows -F "#{window_index}" | grep "^${dest}$" 2>&1 >/dev/null
+ret="$?"
+if [ "x""$ret" = "x0" ]; then
+ tmux swap-window -t ":${dest}"
+else
+ tmux move-window -t ":${dest}"
+fi
diff --git a/linux/home/.config/tridactyl/tridactylrc b/linux/home/.config/tridactyl/tridactylrc
new file mode 100644
index 0000000..a9547cc
--- /dev/null
+++ b/linux/home/.config/tridactyl/tridactylrc
@@ -0,0 +1,170 @@
+" -*- vimrc-generic -*-
+"
+" Base on
+" https://raw.githubusercontent.com/tridactyl/tridactyl/master/.tridactylrc
+" See that for more advanced examples.
+
+" Installing Tridactyl:
+"
+" * Put this config in ~/.tridactylrc (or $XDG_CONFIG_DIR/tridactyl/tridactylrc).
+"
+" * Install the native messenger by running :installnative in Tridactyl
+" and then running the shell command it copies to clipboard.
+"
+" * Run :source in the browser or just restart.
+
+" NB: If you want "vim-like" behaviour where removing a line from
+" here makes the setting disappear, uncomment the line below.
+"
+"sanitise tridactyllocal tridactylsync
+
+" Use this to see current config in the browser
+" :viewconfig --user
+
+" WARNING: THERE IS A BUG WHEREBY SOMETIMES SOME LINES IN THE CONFIG GET IGNORED :/
+" https://github.com/tridactyl/tridactyl/issues/1409
+
+""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+"
+" Search Urls
+"
+" These work in the 'o' -> ':open ' dialog, but not in the regular
+" address bar.
+"
+" In addition to using %s for a single query param, you can use %1,
+" %2, etc, for multiple query params.
+
+"set searchurls.hackage http://hackage.haskell.org/package/%s
+"set searchurls.hayoo http://hayoo.fh-wedel.de/?query=%s
+"set searchurls.h4 https://www.haskell.org/hoogle/?hoogle=%s
+"set searchurls.h5 https://hoogle.haskell.org/?hoogle=%s&scope=set%3Astackage
+
+""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+"
+" Quickmarks
+"
+" Use go<key>/gn<key>/gw<key> to open quickmark for <key> in
+" current/new tab/new window
+
+quickmark g https://mail.google.com/mail/u/0/#inbox
+quickmark G https://mail.google.com/mail/u/1/#inbox
+
+""""""""""""""""
+"
+" Disable on some sites
+"
+
+"blacklistadd youtube.com
+blacklistadd calendar.google.com
+blacklistadd docs.google.com
+blacklistadd drive.google.com
+blacklistadd keep.google.com
+blacklistadd mail.google.com
+blacklistadd monkeytype.com
+blacklistadd typeracer.com
+blacklistadd codepen.io
+blacklistadd codesandbox.io
+"blacklistadd github.dev
+
+""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+"
+" Binds
+"
+" Some defaults:
+" - :help
+" - :tutor
+" - b : buffers/tabs navigation
+" - yy : yank url
+" - gg : top of page
+" - G : bottom of page
+" - insert, shift+insert, ctrl+alt+`, shift+escape : toggle pass thru
+" - d/D : close current tab and move focus right/left
+" - p/P : open url in clipboard or search for clipboard content in current/new tab
+" - zi/zo/zz : zoom in/out/reset
+" - \[\[ / \]\] : guess previous/next page (seems smart, e.g. works when
+" url does not end in a number, but "next" occurs in link text)
+" - ;; : focus an element. Useful if you want to scroll something non-default with up/down or j/k
+" - ;p : copy element (e.g. link, paragraph) to clipboard
+" - ;k : kill element (e.g. a big "please disable your ad blocker" banner)
+" - :viewconfig nmaps : see all key bindings (but how to delete?)
+" - f/;t/F: follow hint in current tab/new foreground tab (switch focus)/new background tab (don't switch focus)
+" - C-o : run a single command (in normal mode) when in ignore mode, then switch back to ignore mode
+
+" Make 'd' switch to previous active tab after close
+bind d composite tabclose | buffer #
+
+" Don't bind paste to ignore mode. Can use 'S-Esc' or 'C-A-Esc' instead.
+unbind <S-Insert>
+
+" Don't bind 'f' in youtube
+unbindurl youtube\.com/watch\?v=.* f
+
+" Bind <Insert> to toggle normal/ignore mode. I use <Insert> to toggle
+" Ctrl-lock in Emacs, so hopefully this will be memorable. We leave
+" insert mode, but don't enter it. The insert mode bind doesn't work,
+" but <C-,> still allows escape.
+bind --mode=normal <Insert> mode ignore
+bind --mode=ignore <Insert> mode normal
+"bind --mode=insert <Insert> mode normal
+
+" Bind <C-o> to toggle normal/ignore mode for one command. Already
+" bound to C-o in normal mode, make it work everywhere. I'm rebinding
+" the existing normal mode bind for completeness/clairity. We leave
+" insert mode, but don't enter. The insert mode bind doesn't work, but
+" <C-,> still allows escape.
+bind --mode=normal <C-o> nmode normal 1 mode ignore
+bind --mode=ignore <C-o> nmode ignore 1 mode normal
+"bind --mode=insert <C-o> nmode insert 1 mode normal
+
+bind / fillcmdline find
+bind n findnext 1
+bind N findnext -1
+"bind <Space>/ nohlsearch
+set findcase smart
+
+" Workaround bug on web.whatsapp.com that prevents focus from leaving
+" message entry
+"" box. https://github.com/tridactyl/tridactyl/issues/3070
+"bindurl web.whatsapp.com --mode=normal <Escape> composite hint -f m2 ; fillcmdline ; ex.hide_and_clear
+"bindurl web.whatsapp.com --mode=insert <Escape> composite hint -f m2 ; fillcmdline ; ex.hide_and_clear
+"bindurl web.whatsapp.com --mode=input <Escape> composite hint -f m2 ; fillcmdline ; ex.hide_and_clear
+
+""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+"
+" Misc settings
+"
+
+" but make sure i can always get into the console"
+bind : fillcmdline_notrail
+
+" newtab
+set newtab about:blank
+
+" I’m a smooth operator
+set smoothscroll true
+
+" Vimperator-style hinting, using numbers to select and letters to
+" narrow, instead of just letters to select.
+set hintfiltermode vimperator-reflow
+set hintnames numeric
+" " I use Programmer Dvorak
+" set hintchars dhtnaoeuifgcrl',.pybm;qjkx
+
+" Defaults to 300ms
+set hintdelay 100
+
+" Don't autofocus!
+autocmd TabEnter .* unfocus
+autocmd DocLoad .* unfocus
+
+
+" Include numbers in tab names, to make 'b' and '<A-<number>>'
+" switching easier.
+guiset tabs numbers
+
+" Make Tridactyl work on more sites at the expense of some security
+" set csp clobber
+" fixamo_quiet
+
+" This will have to do until someone writes us a nice syntax file :)
+" vim: set filetype=vim:
diff --git a/linux/home/.config/user-dirs.dirs b/linux/home/.config/user-dirs.dirs
new file mode 100644
index 0000000..0db0cae
--- /dev/null
+++ b/linux/home/.config/user-dirs.dirs
@@ -0,0 +1,15 @@
+# This file is written by xdg-user-dirs-update
+# If you want to change or add directories, just edit the line you're
+# interested in. All local changes will be retained on the next run.
+# Format is XDG_xxx_DIR="$HOME/yyy", where yyy is a shell-escaped
+# homedir-relative path, or XDG_xxx_DIR="/yyy", where /yyy is an
+# absolute path. No other format is supported.
+#
+XDG_DESKTOP_DIR="$HOME/"
+XDG_DOWNLOAD_DIR="$HOME/downloads"
+XDG_TEMPLATES_DIR="$HOME/"
+XDG_PUBLICSHARE_DIR="$HOME/"
+XDG_DOCUMENTS_DIR="$HOME/documents"
+XDG_MUSIC_DIR="$HOME/music"
+XDG_PICTURES_DIR="$HOME/pictures"
+XDG_VIDEOS_DIR="$HOME/videos"
diff --git a/linux/home/.config/user-dirs.locale b/linux/home/.config/user-dirs.locale
new file mode 100644
index 0000000..3e0b419
--- /dev/null
+++ b/linux/home/.config/user-dirs.locale
@@ -0,0 +1 @@
+en_US \ No newline at end of file
diff --git a/linux/home/.config/waybar/config.jsonc b/linux/home/.config/waybar/config.jsonc
new file mode 100644
index 0000000..dbf6556
--- /dev/null
+++ b/linux/home/.config/waybar/config.jsonc
@@ -0,0 +1,292 @@
+{
+ "layer": "top", // Waybar at top layer
+ "position": "top", // Waybar position (top|bottom|left|right)
+ //"gtk-layer-shell": "false",
+ "height": 45, // Waybar height (to be removed for auto height)
+ // "width": 2560, // Waybar width
+ "spacing": 0, // Gaps between modules (0px) Adjusted in the css
+ "margin-top": 0,
+ //"margin-bottom":-10,
+ "margin-left": 10,
+ "margin-right": 10,
+ // Choose the order of the modules
+ "modules-left": [
+ "custom/launcher",
+ "wlr/workspaces",
+ "cpu",
+ "memory",
+ "temperature",
+ "disk",
+ "custom/updates"
+ ],
+ "modules-center": [
+ "custom/music"
+ ],
+ "modules-right": [
+ "network",
+ "pulseaudio",
+ "backlight",
+ "battery",
+ "custom/notifications",
+ "tray",
+ "clock",
+ "custom/weather",
+ "custom/cycle_wall",
+ "custom/clipboard",
+ "custom/power",
+ "custom/custom"
+ ],
+ "custom/launcher": {
+ "format": "{}",
+ "tooltip": true,
+ "exec": "echo '{\"text\":\"💧\",\"tooltip\":\"Drun | Run\"}'",
+ "return-type": "json",
+ "on-click": "pkill wofi || wofi --show drun -n",
+ "on-click-right": "pkill wofi || wofi --show run -n",
+ },
+ "wlr/workspaces": {
+ // "format": "{icon} {name}",
+ "format": "",
+ "format-icons": {
+ "active": "",
+ "default": ""
+ },
+ "on-scroll-up": "hyprctl dispatch workspace e-1",
+ "on-scroll-down": "hyprctl dispatch workspace e+1",
+ "on-click": "activate"
+ },
+ "cpu": {
+ "format": " {usage}%",
+ "tooltip": true,
+ "on-click": "kitty --start-as=fullscreen --title all_is_kitty sh -c 'btop'",
+ "interval": 2
+ },
+ "memory": {
+ "format": " {}%",
+ "tooltip": true,
+ "on-click": "kitty --start-as=fullscreen --title all_is_kitty sh -c 'btop'",
+ "interval": 2
+ },
+ "temperature": {
+ // "thermal-zone": 2,
+ // "hwmon-path": "/sys/class/hwmon/hwmon2/temp1_input",
+ "critical-threshold": 40,
+ "format-critical": "{icon} {temperatureC}°C",
+ "format": "{icon} {temperatureC}°C",
+ "format-icons": [
+ "",
+ "",
+ ""
+ ],
+ "tooltip": true,
+ "on-click": "kitty --start-as=fullscreen --title all_is_kitty sh -c 'btop'",
+ "interval": 2
+ },
+ "disk": {
+ "format": " {percentage_used}% ({free})",
+ "tooltip": true,
+ "on-click": "kitty --start-as=fullscreen --title all_is_kitty sh -c 'btop'",
+ "interval": 2
+ },
+ "custom/updates": {
+ "format": "{}",
+ "exec": "~/.scripts/updates",
+ "on-click": "~/.scripts/updates update",
+ "interval": 300,
+ "tooltip": true,
+ // "tooltip-format": "{}",
+ // "exec-tooltip": "~/.scripts/updates tooltip"
+ },
+ "custom/music": {
+ "format": "{icon}{}",
+ "format-icons": {
+ // "Playing": " ", // Uncomment if not using the dynamic script
+ "Paused": " ",
+ "Stopped": "&#x202d;ﭥ " // This stop symbol is RTL. So &#x202d; is left-to-right override.
+ },
+ "escape": true,
+ "tooltip": true,
+ "exec": "~/.scripts/caway -b 10",
+ "return-type": "json",
+ "on-click": "playerctl play-pause",
+ "on-scroll-up": "playerctl previous",
+ "on-scroll-down": "playerctl next",
+ "on-click-right": "g4music",
+ "max-length": 35
+ },
+ "hyprland/window": {
+ "format": "{}",
+ "separate-outputs": true,
+ "max-length": 35
+ },
+ "network": {
+ // "interface": "wlp2*", // (Optional) To force the use of this interface
+ "format": "↕{bandwidthTotalBytes}",
+ "format-disconnected": "{icon} No Internet",
+ "format-linked": " {ifname} (No IP)",
+ "format-alt": "↕{bandwidthUpBytes} | ↕{bandwidthDownBytes}",
+ "tooltip-format": "{ifname}: {ipaddr}/{cidr}  {gwaddr}",
+ "tooltip-format-wifi": "{icon} {essid} ({signalStrength}%)",
+ "tooltip-format-ethernet": "{icon} {ipaddr}/{cidr}",
+ "tooltip-format-disconnected": "{icon} Disconnected",
+ "on-click-right": "nm-connection-editor",
+ "format-icons": {
+ "ethernet": "",
+ "disconnected": "⚠",
+ "wifi": [
+ "睊",
+ "直"
+ ]
+ },
+ "interval": 2
+ },
+ "pulseaudio": {
+ // "scroll-step": 1, // %, can be a float
+ "format": "{icon} {volume}%", // {format_source}
+ "format-bluetooth": "{icon} {volume}%", // {format_source}
+ "format-bluetooth-muted": "", // {format_source}
+ "format-muted": "", // {format_source}
+ "format-source": "{volume}% ",
+ "format-source-muted": "",
+ "format-icons": {
+ "headphone": "",
+ "headset": "",
+ "phone": "",
+ "portable": "",
+ "car": " ",
+ "default": [
+ "",
+ "",
+ ""
+ ]
+ },
+ "on-click": "pavucontrol"
+ },
+ "backlight": {
+ // "device": "acpi_video1",
+ "format": "{icon} {percent}%",
+ "format-icons": [
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ ""
+ ],
+ "on-scroll-up": "light -A 1",
+ "on-scroll-down": "light -U 1",
+ "interval": 2
+ },
+ "battery": {
+ "states": {
+ "good": 100,
+ "warning": 30,
+ "critical": 10
+ },
+ "format": "{icon} {capacity}%",
+ "format-charging": " {capacity}%",
+ "format-plugged": " {capacity}%",
+ "format-alt": "{icon} {time}",
+ // "format-good": "", // An empty format will hide the module
+ "format-full": " {capacity}%",
+ "format-icons": [
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ ""
+ ],
+ "interval": 2
+ },
+ "custom/notifications": {
+ "tooltip": false,
+ "format": "{icon}",
+ "format-icons": {
+ "notification": "<span foreground='red'><sup></sup></span>",
+ "none": "",
+ "dnd-notification": "<span foreground='red'><sup></sup></span>",
+ "dnd-none": ""
+ },
+ "return-type": "json",
+ "exec-if": "which swaync-client",
+ "exec": "swaync-client -swb",
+ "on-click": "swaync-client -t -sw",
+ "on-click-right": "swaync-client -d -sw",
+ "escape": true
+ },
+ "tray": {
+ "icon-size": 15,
+ "spacing": 15
+ },
+ "clock": {
+ "timezone": "Asia/Calcutta",
+ "format": " {:%d <small>%a</small> %H:%M}",
+ //"format": " {:%a %b %d %Y | %H:%M}",
+ "format-alt": " {:%A %B %d %Y (%V) | %r}",
+ "tooltip-format": "<big>{:%Y %B}</big>\n<tt><small>{calendar}</small></tt>",
+ "calendar-weeks-pos": "right",
+ "today-format": "<span color='#f38ba8'><b><u>{}</u></b></span>",
+ "format-calendar": "<span color='#f2cdcd'><b>{}</b></span>",
+ "format-calendar-weeks": "<span color='#94e2d5'><b>W{:%U}</b></span>",
+ "format-calendar-weekdays": "<span color='#f9e2af'><b>{}</b></span>",
+ "interval": 60
+ },
+ "idle_inhibitor": {
+ "format": "{icon}",
+ "format-icons": {
+ "activated": "",
+ "deactivated": ""
+ }
+ },
+ "custom/weather": {
+ "tooltip": true,
+ "format": "{}",
+ "exec": "~/.scripts/tools/expand weather",
+ "return-type": "json"
+ },
+ "custom/ss": {
+ "format": "{}",
+ "exec": "~/./scripts/tools/expand ss-icon",
+ "return-type": "json",
+ "on-click": "~/.scripts/screenshot_full"
+ },
+ "custom/cycle_wall": {
+ "format": "{}",
+ "exec": "~/.scripts/tools/expand wall",
+ "return-type": "json",
+ // "interval": 1,
+ "on-click": "~/.scripts/tools/expand cycle",
+ "on-click-right": "~/.scripts/tools/expand cycler"
+ },
+ "custom/clipboard": {
+ "format": "{}",
+ "exec": "~/.scripts/tools/expand clipboard",
+ "return-type": "json",
+ // Here "-l top_right -x -15 -y 10" doesn't matter as '-n' mode is used
+ // Window position is managed in Hyperland config's windowrulev2
+ "on-click": "pkill wofi || cliphist list | wofi --dmenu -p clippick -l top_right -x -15 -y 10 -n | cliphist decode | wl-copy",
+ "on-click-middle": "rm -f ~/.cache/cliphist/db",
+ "on-click-right": "pkill wofi || cliphist list | wofi --dmenu -p clippick -l top_right -x -15 -y 10 -n | cliphist delete",
+ "escape": true
+ },
+ "custom/power": {
+ "format": "{}",
+ "exec": "~/.scripts/tools/expand power",
+ "return-type": "json",
+ "on-click": "~/.config/wlogout/launch.sh"
+ },
+ "custom/custom": {
+ "format": "{}",
+ "exec": "~/.scripts/tools/expand arrow-icon",
+ "on-click": "~/.scripts/tools/expand_toolbar",
+ "return-type": "json"
+ }
+}
diff --git a/linux/home/.config/waybar/style.css b/linux/home/.config/waybar/style.css
new file mode 100644
index 0000000..348bb04
--- /dev/null
+++ b/linux/home/.config/waybar/style.css
@@ -0,0 +1,434 @@
+/*
+@import "catppuccin/mocha.css";
+*/
+
+/*
+* Catppuccin Mocha palette
+*/
+
+@define-color base #1e1e2e;
+@define-color mantle #181825;
+@define-color crust #11111b;
+
+@define-color text #cdd6f4;
+@define-color subtext0 #a6adc8;
+@define-color subtext1 #bac2de;
+
+@define-color surface0 rgba(22, 25, 37, 0.9);
+@define-color surface1 #45475a;
+@define-color surface2 #585b70;
+@define-color surface3 #394161;
+
+@define-color overlay0 #6c7086;
+@define-color overlay1 #7f849c;
+@define-color overlay2 #9ba3c3;
+
+@define-color blue #89b4fa;
+@define-color lavender #b4befe;
+@define-color sapphire #74c7ec;
+@define-color sky #89dceb;
+@define-color teal #94e2d5;
+@define-color green #a6e3a1;
+@define-color yellow #f9e2af;
+@define-color peach #fab387;
+@define-color maroon #eba0ac;
+@define-color red #f38ba8;
+@define-color mauve #cba6f7;
+@define-color pink #f5c2e7;
+@define-color flamingo #f2cdcd;
+@define-color rosewater #f5e0dc;
+
+/* =============================== */
+/* Universal Styling */
+* {
+ border: none;
+ border-radius: 0;
+ font-family: 'CaskaydiaCove Nerd Font', monospace;
+ font-size: 13px;
+ min-height: 0;
+}
+
+/* =============================== */
+
+
+/* =============================== */
+/* Bar Styling */
+#waybar {
+ background: transparent;
+ color: @text;
+}
+
+/* =============================== */
+
+
+/* =============================== */
+/* Main Modules */
+#custom-launcher,
+#workspaces,
+#window,
+#tray,
+#backlight,
+#clock,
+#battery,
+#pulseaudio,
+#network,
+#mpd,
+#cpu,
+#memory,
+#disk,
+#temperature,
+#custom-music,
+#custom-updates,
+#custom-nordvpn,
+#custom-notifications,
+#custom-power,
+#custom-custom,
+#custom-cycle_wall,
+#custom-clipboard,
+#custom-ss,
+#custom-weather {
+ background-color: @surface0;
+ color: @text;
+ border-radius: 16px;
+ padding: 0.5rem 1rem;
+ box-shadow: rgba(0, 0, 0, 0.116) 2px 2px 5px 2px;
+ margin-top: 10px;
+ /*
+ margin-bottom: 10px;
+*/
+ margin-right: 10px;
+}
+
+/* =============================== */
+/* Launcher Module */
+#custom-launcher {
+ color: @green;
+ padding-top: 0px;
+ padding-bottom: 0px;
+ padding-right: 10px;
+}
+
+/* =============================== */
+/* Workspaces */
+#workspaces {
+ padding-left: 8px;
+ padding-right: 8px;
+}
+
+#workspaces * {
+ font-size: 0px;
+}
+
+#workspaces button {
+ background-color: @surface3;
+ color: @mauve;
+ border-radius: 100%;
+ min-height: 14px;
+ min-width: 14px;
+ margin: 5px 8px;
+ padding: 0px;
+ /*transition: all 0.5s cubic-bezier(0.33, 1.0, 0.68, 1.0); easeInOutCubic */
+ transition: all 0.5s cubic-bezier(.55, -0.68, .48, 1.68);
+ box-shadow: rgba(0, 0, 0, 0.288) 2px 2px 5px 2px;
+}
+
+#workspaces button.active {
+ /*color: @surface0;
+ border-radius: 1rem;
+ padding: 0rem 10px;*/
+ background: radial-gradient(circle, rgba(203, 166, 247, 1) 0%, rgba(193, 168, 247, 1) 12%, rgba(249, 226, 175, 1) 19%, rgba(189, 169, 247, 1) 20%, rgba(182, 171, 247, 1) 24%, rgba(198, 255, 194, 1) 36%, rgba(177, 172, 247, 1) 37%, rgba(170, 173, 248, 1) 48%, rgba(255, 255, 255, 1) 52%, rgba(166, 174, 248, 1) 52%, rgba(160, 175, 248, 1) 59%, rgba(148, 226, 213, 1) 66%, rgba(155, 176, 248, 1) 67%, rgba(152, 177, 248, 1) 68%, rgba(205, 214, 244, 1) 77%, rgba(148, 178, 249, 1) 78%, rgba(144, 179, 250, 1) 82%, rgba(180, 190, 254, 1) 83%, rgba(141, 179, 250, 1) 90%, rgba(137, 180, 250, 1) 100%);
+ background-size: 400% 400%;
+ animation: gradient_f 20s ease-in-out infinite;
+ transition: all 0.3s cubic-bezier(.55, -0.68, .48, 1.682);
+}
+
+#workspaces button:hover {
+ background-color: @mauve;
+}
+
+@keyframes gradient {
+ 0% {
+ background-position: 0% 50%;
+ }
+
+ 50% {
+ background-position: 100% 30%;
+ }
+
+ 100% {
+ background-position: 0% 50%;
+ }
+}
+
+@keyframes gradient_f {
+ 0% {
+ background-position: 0% 200%;
+ }
+
+ 50% {
+ background-position: 200% 0%;
+ }
+
+ 100% {
+ background-position: 400% 200%;
+ }
+}
+
+@keyframes gradient_f_nh {
+ 0% {
+ background-position: 0% 200%;
+ }
+
+ 100% {
+ background-position: 200% 200%;
+ }
+}
+
+/* =============================== */
+
+
+/* =============================== */
+/* System Monitoring Modules */
+#cpu,
+#memory,
+#temperature {
+ color: @blue;
+}
+
+#cpu {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+ margin-right: 0px;
+ padding-right: 5px;
+}
+
+#memory {
+ border-radius: 0px;
+ margin-right: 0px;
+ padding-left: 5px;
+ padding-right: 5px;
+}
+
+#temperature {
+ border-radius: 0px;
+ margin-right: 0px;
+ padding-left: 5px;
+ padding-right: 5px;
+}
+
+#disk {
+ color: @peach;
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+ padding-left: 5px;
+ padding-right: 1rem;
+}
+
+/* Updates Module */
+#custom-updates {
+ color: @sky;
+}
+
+/* =============================== */
+
+
+/* =============================== */
+/* Clock Module */
+#clock {
+ color: @flamingo;
+}
+
+/* =============================== */
+
+
+#custom-music.low {
+ background: rgb(148, 226, 213);
+ background: linear-gradient(52deg, rgba(148, 226, 213, 1) 0%, rgba(137, 220, 235, 1) 19%, rgba(116, 199, 236, 1) 43%, rgba(137, 180, 250, 1) 56%, rgba(180, 190, 254, 1) 80%, rgba(186, 187, 241, 1) 100%);
+ background-size: 300% 300%;
+ text-shadow: 0px 0px 5px rgba(0, 0, 0, 0.377);
+ animation: gradient 15s ease infinite;
+ font-weight: bold;
+ color: #fff;
+}
+
+#custom-music.random {
+ background: rgb(148, 226, 213);
+ background: radial-gradient(circle, rgba(148, 226, 213, 1) 0%, rgba(156, 227, 191, 1) 21%, rgba(249, 226, 175, 1) 34%, rgba(158, 227, 186, 1) 35%, rgba(163, 227, 169, 1) 59%, rgba(148, 226, 213, 1) 74%, rgba(164, 227, 167, 1) 74%, rgba(166, 227, 161, 1) 100%);
+ background-size: 400% 400%;
+ animation: gradient_f 4s ease infinite;
+ text-shadow: 0px 0px 5px rgba(0, 0, 0, 0.377);
+ font-weight: bold;
+ color: #fff;
+}
+
+#custom-music.critical {
+ background: rgb(235, 160, 172);
+ background: linear-gradient(52deg, rgba(235, 160, 172, 1) 0%, rgba(243, 139, 168, 1) 30%, rgba(231, 130, 132, 1) 48%, rgba(250, 179, 135, 1) 77%, rgba(249, 226, 175, 1) 100%);
+ background-size: 300% 300%;
+ animation: gradient 15s cubic-bezier(.55, -0.68, .48, 1.68) infinite;
+ text-shadow: 0px 0px 5px rgba(0, 0, 0, 0.377);
+ font-weight: bold;
+ color: #fff;
+}
+
+#custom-music.Playing {
+ background: rgb(137, 180, 250);
+ background: radial-gradient(circle, rgba(137, 180, 250, 120) 0%, rgba(142, 179, 250, 120) 6%, rgba(148, 226, 213, 1) 14%, rgba(147, 178, 250, 1) 14%, rgba(155, 176, 249, 1) 18%, rgba(245, 194, 231, 1) 28%, rgba(158, 175, 249, 1) 28%, rgba(181, 170, 248, 1) 58%, rgba(205, 214, 244, 1) 69%, rgba(186, 169, 248, 1) 69%, rgba(195, 167, 247, 1) 72%, rgba(137, 220, 235, 1) 73%, rgba(198, 167, 247, 1) 78%, rgba(203, 166, 247, 1) 100%);
+ background-size: 400% 400%;
+ animation: gradient_f 9s cubic-bezier(.72, .39, .21, 1) infinite;
+ text-shadow: 0px 0px 5px rgba(0, 0, 0, 0.377);
+ font-weight: bold;
+ color: #fff;
+}
+
+#custom-music.Paused,
+#custom-music.Stopped {
+ background: @surface0;
+}
+
+
+/* =============================== */
+/* Music/PlayerCTL Module */
+#custom-music {
+ color: @mauve;
+}
+
+/* =============================== */
+
+
+/* =============================== */
+/* Network Module */
+#network {
+ color: @blue;
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+ margin-right: 0px;
+ padding-right: 5px;
+}
+
+/* =============================== */
+
+
+/* =============================== */
+/* PulseAudio Module */
+#pulseaudio {
+ color: @mauve;
+ border-radius: 0;
+ margin-right: 0px;
+ padding-left: 5px;
+ padding-right: 5px;
+}
+
+/* =============================== */
+
+
+/* =============================== */
+/* Backlight Module */
+#backlight {
+ color: @teal;
+ border-radius: 0;
+ margin-right: 0px;
+ padding-left: 5px;
+ padding-right: 5px;
+}
+
+/* =============================== */
+
+
+/* =============================== */
+/* Battery Module */
+#battery {
+ color: @green;
+ border-radius: 0;
+ margin-right: 0px;
+ padding-left: 5px;
+ padding-right: 5px;
+}
+
+#battery.charging {
+ color: @green;
+}
+
+#battery.warning:not(.charging) {
+ color: @maroon;
+}
+
+#battery.critical:not(.charging) {
+ color: @red;
+ animation-name: blink;
+ animation-duration: 1s;
+ animation-timing-function: linear;
+ animation-iteration-count: infinite;
+ animation-direction: alternate;
+}
+
+@keyframes blink {
+ to {
+ background: @red;
+ color: @surface1;
+ }
+}
+
+/* =============================== */
+
+/* Notifications Module */
+#custom-notifications {
+ color: @mauve;
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+ padding-left: 5px;
+ padding-right: 1.25rem;
+}
+
+
+/* =============================== */
+/* Tray Module */
+#tray {
+ color: @mauve;
+ padding-right: 1.25rem;
+}
+
+/* =============================== */
+
+
+/* =============================== */
+/* | Custom Modules | */
+/* =============================== */
+#custom-custom {
+ color: @peach;
+ padding-right: 1.25rem;
+ margin-right: 0px;
+}
+
+/* Screenshot */
+#custom-ss {
+ color: @mauve;
+ padding-right: 1.5rem;
+}
+
+/* Wallpaper */
+#custom-cycle_wall {
+ background: linear-gradient(45deg, rgba(245, 194, 231, 1) 0%, rgba(203, 166, 247, 1) 0%, rgba(243, 139, 168, 1) 13%, rgba(235, 160, 172, 1) 26%, rgba(250, 179, 135, 1) 34%, rgba(249, 226, 175, 1) 49%, rgba(166, 227, 161, 1) 65%, rgba(148, 226, 213, 1) 77%, rgba(137, 220, 235, 1) 82%, rgba(116, 199, 236, 1) 88%, rgba(137, 180, 250, 1) 95%);
+ background-size: 500% 500%;
+ animation: gradient 7s linear infinite;
+}
+
+/* Notifications Module */
+#custom-clipboard {
+ color: @mauve;
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+ margin-right: 0px;
+ padding-right: 8px;
+}
+
+/* Powermenu Module */
+#custom-power {
+ color: @mauve;
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+ padding-left: 8px;
+ padding-right: 1.20rem;
+}
+
+/* =============================== */
diff --git a/linux/home/.config/wofi/config b/linux/home/.config/wofi/config
new file mode 100644
index 0000000..d1ea4c8
--- /dev/null
+++ b/linux/home/.config/wofi/config
@@ -0,0 +1,17 @@
+width=420
+height=550
+location=center
+show=drun
+matching=fuzzy
+prompt=Search...
+filter_rate=100
+allow_markup=true
+no_actions=true
+halign=fill
+orientation=vertical
+content_halign=fill
+insensitive=true
+allow_images=true
+image_size=28
+gtk_dark=false
+term=kitty
diff --git a/linux/home/.config/wofi/style.css b/linux/home/.config/wofi/style.css
new file mode 100644
index 0000000..6fb5107
--- /dev/null
+++ b/linux/home/.config/wofi/style.css
@@ -0,0 +1,99 @@
+* {
+ transition: 0.2s;
+}
+
+window {
+ font-family: "FiraCode Nerd Font Mono";
+ font-size: 13px;
+}
+
+window {
+ margin: 0px;
+ border: 2px solid #cba6f7;
+/*
+ background-color: #161925;
+*/
+ background-color: transparent;
+ border-radius: 16px;
+}
+
+#input {
+ padding: 4px;
+ margin: 20px;
+ padding-left: 20px;
+ border: none;
+ color: #fff;
+ font-weight: bold;
+ background: linear-gradient(90deg, #cba6f7 0%, #94e2d5 100%);
+ outline: none;
+ border-radius: 16px;
+}
+
+#input image {
+ color: #fff;
+}
+
+#input:focus {
+ border: none;
+ outline: none;
+}
+
+#inner-box {
+ margin: 20px;
+ margin-top: 0px;
+ border: none;
+ color: #cba6f7;
+ border-radius: 16px;
+}
+
+#inner-box * {
+ transition: none;
+}
+
+#outer-box {
+ margin: 0px;
+ border: none;
+ padding: 0px;
+ border-radius: 16px;
+}
+
+#scroll {
+ margin-top: 5px;
+ border: none;
+ border-radius: 16px;
+ margin-bottom: 5px;
+}
+
+#text:selected {
+ color: #fff;
+ font-weight: bold;
+}
+
+#img {
+ margin-right: 20px;
+ background: transparent;
+}
+
+#text {
+ margin: 0px;
+ border: none;
+ padding: 0px;
+ background: transparent;
+}
+
+#entry {
+ margin: 0px;
+ border: none;
+ border-radius: 16px;
+ background-color: transparent;
+ min-height:32px;
+ font-weight: bold;
+}
+
+#entry:selected {
+ outline: none;
+ margin: 0px;
+ border: none;
+ border-radius: 16px;
+ background: linear-gradient(90deg, #cba6f7 0%, #94e2d5 100%);
+}
diff --git a/linux/home/.config/xkb/symbols/custom-us b/linux/home/.config/xkb/symbols/custom-us
new file mode 100644
index 0000000..9faa2e9
--- /dev/null
+++ b/linux/home/.config/xkb/symbols/custom-us
@@ -0,0 +1,27 @@
+// Clear existing modifiers
+//default partial modifier_keys
+partial alphanumeric_keys
+xkb_symbols "basic" {
+ include "us(basic)"
+
+ // Define a custom modifier (Mode_switch)
+ modifier_map Mod5 { <MDSW> };
+
+ // Remap Caps Lock to AltGr (ISO_Level3_Shift) and clear other modifiers
+ key <CAPS> {
+ type[Group1] = "ONE_LEVEL",
+ symbols[Group1] = [ ISO_Level3_Shift ],
+ actions[Group1] = [ SetMods(modifiers=Mod5) ]
+ };
+
+ // Remap h, j, k, l to arrow keys when Mode_switch is active and clear other modifiers
+ key <AC06> { [ h, H, Left ] };
+ key <AC07> { [ j, J, Down ] };
+ key <AC08> { [ k, K, Up ] };
+ key <AC09> { [ l, L, Right ] };
+
+ key <AD02> { [ w, W, Up ] };
+ key <AC01> { [ a, A, Left ] };
+ key <AC02> { [ s, S, Down ] };
+ key <AC03> { [ d, D, Right ] };
+};
diff --git a/linux/home/.config/xob/launch.sh b/linux/home/.config/xob/launch.sh
new file mode 100755
index 0000000..46d4861
--- /dev/null
+++ b/linux/home/.config/xob/launch.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+killall -q xob
+pkill -f manage-volume
+pkill -f manage-brightness
+pkill -f manage-microphone
+
+"$HOME"/.config/xob/manage-volume | xob -s default &
+"$HOME"/.config/xob/manage-brightness | xob -s default &
+"$HOME"/.config/xob/manage-microphone | xob -s default &
diff --git a/linux/home/.config/xob/manage-brightness b/linux/home/.config/xob/manage-brightness
new file mode 100755
index 0000000..496900f
--- /dev/null
+++ b/linux/home/.config/xob/manage-brightness
@@ -0,0 +1,16 @@
+#!/bin/node
+
+const fs = require('fs')
+const { exec } = require("child_process");
+
+let device = "intel_backlight";
+
+brightnessFile = `/sys/class/backlight/${device}/brightness`;
+maxBrightnessFile = `/sys/class/backlight/${device}/max_brightness`;
+
+maxValue = parseInt(fs.readFileSync(maxBrightnessFile, 'utf8'));
+
+fs.watch(brightnessFile, () => {
+ value = parseInt(fs.readFileSync(brightnessFile, 'utf8'));
+ console.log(Math.round(value / maxValue * 100));
+})
diff --git a/linux/home/.config/xob/manage-microphone b/linux/home/.config/xob/manage-microphone
new file mode 100755
index 0000000..3bf5972
--- /dev/null
+++ b/linux/home/.config/xob/manage-microphone
@@ -0,0 +1,56 @@
+#!/usr/bin/env python3
+
+import sys
+
+from pulsectl import Pulse, PulseLoopStop
+
+
+def callback(ev):
+ if ev.index == source_index:
+ raise PulseLoopStop
+
+
+def current_status(source):
+ return round(source.volume.value_flat * 100), source.mute == 1
+
+
+def get_default_source_idx():
+ default_source_name = pulse.server_info().default_source_name
+ try:
+ source_index = next(index for index, source in sources.items()
+ if source.name == default_source_name)
+ return source_index
+ except StopIteration:
+ raise StopIteration("No default source was found.")
+
+
+try:
+ with Pulse() as pulse:
+ sources = {s.index: s for s in pulse.source_list()}
+
+ if len(sys.argv) > 1:
+ # Source index from command line argument if provided
+ source_index = int(sys.argv[1])
+ if source_index not in sources:
+ raise KeyError(
+ f"Source index {source_index} not found in list of sources."
+ )
+ else:
+ # Automatic determination of default source otherwise
+ source_index = get_default_source_idx()
+
+ pulse.event_mask_set('source')
+ pulse.event_callback_set(callback)
+ last_value, last_mute = current_status(sources[source_index])
+
+ while True:
+ pulse.event_listen()
+ sources = {s.index: s for s in pulse.source_list()}
+ value, mute = current_status(sources[source_index])
+ if value != last_value or mute != last_mute:
+ print(str(value) + ('!' if mute else ''))
+ last_value, last_mute = value, mute
+ sys.stdout.flush()
+
+except Exception as e:
+ print(f"ERROR: {e}", file=sys.stderr)
diff --git a/linux/home/.config/xob/manage-volume b/linux/home/.config/xob/manage-volume
new file mode 100755
index 0000000..d05a92f
--- /dev/null
+++ b/linux/home/.config/xob/manage-volume
@@ -0,0 +1,46 @@
+#!/usr/bin/env python3
+
+from pulsectl import Pulse, PulseLoopStop
+import sys
+
+with Pulse() as pulse:
+ while True:
+ def callback(ev):
+ if ev.index == sink_index: raise PulseLoopStop
+
+ def current_status(sink):
+ return round(sink.volume.value_flat * 100), sink.mute == 1
+
+ def get_default_sink_idx():
+ default_sink_name = pulse.server_info().default_sink_name
+ try:
+ sink_index = next(index for index,sink in sinks.items() if sink.name == default_sink_name)
+ return sink_index
+ except StopIteration: raise StopIteration("No default sink was found.")
+
+ try:
+ sinks = {s.index:s for s in pulse.sink_list()}
+ if len(sys.argv) > 1:
+ # Sink index from command line argument if provided
+ sink_index = int(sys.argv[1])
+ if not sink_index in sinks:
+ raise KeyError(f"Sink index {sink_index} not found in list of sinks.")
+ else:
+ # Automatic determination of default sink otherwise
+ sink_index = get_default_sink_idx()
+
+ pulse.event_mask_set('sink')
+ pulse.event_callback_set(callback)
+ last_value, last_mute = current_status(sinks[sink_index])
+
+ while True:
+ pulse.event_listen()
+ sinks = {s.index:s for s in pulse.sink_list()}
+ value, mute = current_status(sinks[sink_index])
+ if value != last_value or mute != last_mute:
+ print(str(value) + ('!' if mute else ''))
+ last_value, last_mute = value, mute
+ sys.stdout.flush()
+
+ except Exception as e:
+ print(f"ERROR: {e}", file=sys.stderr)
diff --git a/linux/home/.config/xob/styles.cfg b/linux/home/.config/xob/styles.cfg
new file mode 100644
index 0000000..d236ff8
--- /dev/null
+++ b/linux/home/.config/xob/styles.cfg
@@ -0,0 +1,100 @@
+#vol = {
+# x = {relative = 1 ;offset = 88}
+# y = {relative = 0 ;offset = 32}
+# length = {relative = 0; offset = 0;};
+# padding = 0;
+# border = 0;
+# outline = 0;
+# thickness = 20;
+# orientation = "vertical"
+# color = {
+# normal = {
+# fg = "#ffffff";
+# bg = "#000000";
+# border = "#ffffff";
+# };
+# alt = {
+# fg = "#dc322f";
+# bg = "#000000";
+# border = "#dc322f";
+# };
+# overflow = {
+# fg = "#555555";
+# bg = "#000000";
+# border = "#555555";
+# };
+# altoverflow = {
+# fg = "#550000";
+# bg = "#000000";
+# border = "#550000";
+# };
+# };
+#};
+#
+#brightness = {
+# x = {relative = 1 ;offset = 88}
+# y = {relative = 0 ;offset = 88}
+# length = {relative = 0; offset = 0;};
+# padding = 0;
+# border = 0;
+# outline = 0;
+# thickness = 20;
+# orientation = "vertical"
+# color = {
+# normal = {
+# fg = "#ffffff";
+# bg = "#000000";
+# border = "#ffffff";
+# };
+# alt = {
+# fg = "#dc322f";
+# bg = "#000000";
+# border = "#dc322f";
+# };
+# overflow = {
+# fg = "#555555";
+# bg = "#000000";
+# border = "#555555";
+# };
+# altoverflow = {
+# fg = "#550000";
+# bg = "#000000";
+# border = "#550000";
+# };
+# };
+#};
+default = {
+ x = {relative = 1; offset = -48;};
+ y = {relative = 0.5; offset = 0;};
+ length = {relative = 0.3; offset = 0;};
+ thickness = 24;
+ outline = 3;
+ border = 4;
+ padding = 3;
+ orientation = "vertical";
+
+ overflow = "proportional";
+
+ color = {
+ normal = {
+ fg = "#ffffff";
+ bg = "#00000090";
+ border = "#ffffff";
+ };
+ alt = {
+ fg = "#555555";
+ bg = "#00000090";
+ border = "#555555";
+ };
+ overflow = {
+ fg = "#ff0000";
+ bg = "#00000090";
+ border = "#ff0000";
+ };
+ altoverflow = {
+ fg = "#550000";
+ bg = "#00000090";
+ border = "#550000";
+ };
+ };
+};
diff --git a/linux/home/.config/zathura/zathurarc b/linux/home/.config/zathura/zathurarc
new file mode 100644
index 0000000..78d66a1
--- /dev/null
+++ b/linux/home/.config/zathura/zathurarc
@@ -0,0 +1,27 @@
+set window-title-basename "true"
+
+
+
+
+# Basic Settings
+
+set highlight-transparency .1
+set zoom-center "true"
+set selection-clipboard "clipboard"
+set render-loading "false"
+set pages-per-row 1
+set scroll-page-aware "true"
+set scroll-full-overlap 0.01
+set scroll-step 50
+set zoom-min 10
+set guioptions ""
+set render-loading "false"
+
+
+# Startup options
+set adjust-open "best-fit"
+set recolor true
+
+# Side by side view (view 2 pages like an open book)
+map D set "first-page-column 1:1"
+map <C-d> set "first-page-column 1:2"
diff --git a/linux/home/.local/bin/control-center b/linux/home/.local/bin/control-center
new file mode 100755
index 0000000..d0b3320
--- /dev/null
+++ b/linux/home/.local/bin/control-center
@@ -0,0 +1,31 @@
+#!/bin/bash
+
+LOCK_FILE="$HOME/.cache/eww-control-center.lock"
+EWW_BIN="$HOME/.local/bin/eww"
+ACTIVE_PLAYERS=$(playerctl -l | head -n 1)
+
+run() {
+ ${EWW_BIN} open control-center
+ sleep 0.2
+ xdo raise -N eww-bar
+ ${EWW_BIN} update ccenter=true
+
+ sleep 1 && [[ ! -z "$ACTIVE_PLAYERS" ]] && ${EWW_BIN} update mp=true
+}
+
+# Run eww daemon if not running
+if [[ ! `pidof eww` ]]; then
+ ${EWW_BIN} daemon
+ sleep 1
+else
+ if [[ ! -f "$LOCK_FILE" ]]; then
+ touch "$LOCK_FILE"
+ run
+ else
+ [[ ! -z "$ACTIVE_PLAYERS" ]] && ${EWW_BIN} update mp=false && sleep 0.4
+ ${EWW_BIN} update ccenter=false
+ sleep 0.6
+ ${EWW_BIN} close control-center
+ rm "$LOCK_FILE"
+ fi
+fi
diff --git a/linux/home/.local/bin/eww b/linux/home/.local/bin/eww
new file mode 100755
index 0000000..585d6d2
--- /dev/null
+++ b/linux/home/.local/bin/eww
Binary files differ
diff --git a/linux/home/.local/bin/ffmpeg b/linux/home/.local/bin/ffmpeg
new file mode 100755
index 0000000..2258fbd
--- /dev/null
+++ b/linux/home/.local/bin/ffmpeg
@@ -0,0 +1,24 @@
+#!/bin/bash
+
+# audio
+A="$(pactl list sources | grep 'analog.*monitor' | awk '{print $2}')"
+# screen size
+S="$(xdpyinfo | grep dimensions | awk '{print $2}')"
+# file name
+N="$(date +"%m-%d-%Y_%I:%M%p").mp4"
+
+# Desktop audio + screen recording
+ffmpeg \
+-s "$S" -r 25 -f x11grab -i :0.0+0,0 \
+-ac 2 ~/"$N"
+
+# ffmpeg can output high quality GIF. Before you start it is always recommended to use a recent version: download or compile.
+
+# ffmpeg -ss 30 -t 3 -i input.mp4 -vf "fps=10,scale=320:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" -loop 0 output.gif
+
+# This example will skip the first 30 seconds (-ss 30) of the input and create a 3 second output (-t 3).
+# fps filter sets the frame rate. A rate of 10 frames per second is used in the example.
+# scale filter will resize the output to 320 pixels wide and automatically determine the height while preserving the aspect ratio. The lanczos scaling algorithm is used in this example.
+# palettegen and paletteuse filters will generate and use a custom palette generated from your input. These filters have many options, so refer to the links for a list of all available options and values. Also see the Advanced options section below.
+# split filter will allow everything to be done in one command and avoids having to create a temporary PNG file of the palette.
+# Control looping with -loop output option but the values are confusing. A value of 0 is infinite looping, -1 is no looping, and 1 will loop once meaning it will play twice. So a value of 10 will cause the GIF to play 11 times.
diff --git a/linux/home/.local/bin/nitrogen b/linux/home/.local/bin/nitrogen
new file mode 100755
index 0000000..b8afe27
--- /dev/null
+++ b/linux/home/.local/bin/nitrogen
@@ -0,0 +1,63 @@
+#!/usr/bin/env bash
+
+# Wrapper for nitrogen setting the freedesktop.org AccountsService/BackgroundFile so LightDM wallpaper is synced.
+# Inspired by <https://rafaelc.org/posts/sync-wm-wallpaper-with-lightdm-on-linux-mint/>
+
+key_value_retriever() {
+ KEY="${1}"
+ if [[ -z "$KEY" ]]; then
+ printf "ERROR: KEY should not be empty\n"
+ return
+ fi
+ FILE="${2}"
+ if [[ ! -f "$FILE" ]]; then
+ printf "ERROR: Cannot find FILE: %s\n" "$FILE"
+ return
+ fi
+ VALUE_VARNAME="${3}"
+ if [[ -z "$VALUE_VARNAME" ]]; then
+ printf "ERROR: VALUE_VARNAME should not be empty\n"
+ return
+ fi
+ MATCH=$(grep -m1 "^[[:space:]]*${KEY}=" "$FILE")
+ INDEX_OF_FIRST_EQUAL=$(expr index "$MATCH" =)
+ VALUE="${MATCH:${INDEX_OF_FIRST_EQUAL}}"
+ export "$VALUE_VARNAME"="$VALUE"
+}
+
+/usr/bin/nitrogen "$@"
+if [[ "${1:-}" == "--restore" ]]; then
+ exit $?
+fi
+
+NITROGEN_BG_SAVED_CFG_FILE="${HOME}/.config/nitrogen/bg-saved.cfg"
+if [[ ! -f "$NITROGEN_BG_SAVED_CFG_FILE" ]]; then
+ printf "!ERROR! Cannot find NITROGEN_BG_SAVED_CFG_FILE[%s]\n" "$NITROGEN_BG_SAVED_CFG_FILE" 1>&2
+ exit 1
+fi
+
+key_value_retriever "file" "$NITROGEN_BG_SAVED_CFG_FILE" "NITROGEN_BG_SAVED_CFG_FIRST_BACKGROUND_FILE"
+
+if [[ -z "$NITROGEN_BG_SAVED_CFG_FIRST_BACKGROUND_FILE" ]]; then
+ printf "!ERROR! Cannot retrieve NITROGEN_BG_SAVED_CFG_FIRST_BACKGROUND_FILE from NITROGEN_BG_SAVED_CFG_FILE[%s]\n" "NITROGEN_BG_SAVED_CFG_FIRST_BACKGROUND_FILE" 1>&2
+ exit 1
+fi
+
+if [[ ! -f "$NITROGEN_BG_SAVED_CFG_FIRST_BACKGROUND_FILE" ]]; then
+ printf "!ERROR! Cannot find NITROGEN_BG_SAVED_CFG_FIRST_BACKGROUND_FILE[%s]\n" "$NITROGEN_BG_SAVED_CFG_FIRST_BACKGROUND_FILE" 1>&2
+ exit 1
+fi
+
+printf "Setting nitrogen background into freedesktop.org AccountsService...\n"
+printf "NITROGEN_BG_SAVED_CFG_FIRST_BACKGROUND_FILE[%s]\n" "$NITROGEN_BG_SAVED_CFG_FIRST_BACKGROUND_FILE"
+
+"$HOME"/.scripts/lockscreen-wallpaper &
+dbus-send \
+ --print-reply \
+ --system \
+ --dest=org.freedesktop.Accounts \
+ /org/freedesktop/Accounts/User"$(id -u)" \
+ org.freedesktop.DBus.Properties.Set \
+ string:org.freedesktop.DisplayManager.AccountsService \
+ string:BackgroundFile \
+ variant:string:"$NITROGEN_BG_SAVED_CFG_FIRST_BACKGROUND_FILE"
diff --git a/linux/home/.local/bin/notification-center b/linux/home/.local/bin/notification-center
new file mode 100755
index 0000000..d785628
--- /dev/null
+++ b/linux/home/.local/bin/notification-center
@@ -0,0 +1,26 @@
+#!/bin/bash
+
+LOCK_FILE="$HOME/.cache/eww-notification-center.lock"
+EWW_BIN="$HOME/.local/bin/eww"
+
+run() {
+ "$EWW_BIN" open notification-center
+ sleep 0.2
+ "$EWW_BIN" update noticenter=true
+}
+
+# Run eww daemon if not running
+if [[ ! $(pidof eww) ]]; then
+ "$EWW_BIN" daemon
+ sleep 1
+else
+ if [[ ! -f "$LOCK_FILE" ]]; then
+ touch "$LOCK_FILE"
+ run
+ else
+ "$EWW_BIN" update noticenter=false
+ sleep 0.8
+ "$EWW_BIN" close notification-center
+ rm "$LOCK_FILE"
+ fi
+fi
diff --git a/linux/home/.local/bin/xcolor-pick b/linux/home/.local/bin/xcolor-pick
new file mode 100755
index 0000000..459246f
--- /dev/null
+++ b/linux/home/.local/bin/xcolor-pick
@@ -0,0 +1,31 @@
+#!/usr/bin/env bash
+
+# ██╗ ██╗ ██████╗ ██████╗ ██╗ ██████╗ ██████╗
+# ╚██╗██╔╝██╔════╝██╔═══██╗██║ ██╔═══██╗██╔══██╗
+# ╚███╔╝ ██║ ██║ ██║██║ ██║ ██║██████╔╝
+# ██╔██╗ ██║ ██║ ██║██║ ██║ ██║██╔══██╗
+# ██╔╝ ██╗╚██████╗╚██████╔╝███████╗╚██████╔╝██║ ██║
+# ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝
+# color picker for X.
+# Simple Script To Pick Color Quickly Using Gpick.
+# Created By: rxyhn.
+
+TEMP_DIR=/tmp/xcolor
+MSG=${XDG_CACHE_HOME:-$HOME/.cache}/xcolor.msg
+
+EXPIRE_TIME=5000
+
+main() {
+
+ HEX_COLOR=$(gpick -pso --no-newline)
+ mkdir -p $TEMP_DIR
+ HEX="${HEX_COLOR#\#}"
+ FNAME="$TEMP_DIR/$HEX.png"
+ convert -size 100x100 xc:"$HEX_COLOR" "$FNAME"
+ COLOR_CODE="$HEX_COLOR"
+
+ printf %s "$COLOR_CODE" | xclip -sel c
+ notify-send -a XColor "$COLOR_CODE" --icon="$FNAME" --expire-time="$EXPIRE_TIME"
+}
+
+main
diff --git a/linux/home/.local/share/applications/com.obsproject.Studio.desktop b/linux/home/.local/share/applications/com.obsproject.Studio.desktop
new file mode 100644
index 0000000..a0a09fd
--- /dev/null
+++ b/linux/home/.local/share/applications/com.obsproject.Studio.desktop
@@ -0,0 +1,99 @@
+[Desktop Entry]
+Version=1.0
+Name=OBS Studio
+GenericName=Streaming/Recording Software
+Comment=Free and Open Source Streaming/Recording Software
+Exec=obs
+#Icon=$HOME/.icons/WhiteSur-dark/apps/scalable/obs.svg
+#Icon=$HOME/.local/share/icons/WhiteSur-dark/apps/scalable/obs.svg
+Icon=/home/srdusr/.local/share/icons/WhiteSur-dark/apps/scalable/obs.svg
+Terminal=false
+Type=Application
+Categories=AudioVideo;Recorder;
+StartupNotify=true
+StartupWMClass=obs
+
+GenericName[an_ES]=Programa de retransmisión/gravación
+Comment[an_ES]=Program de retransmisión/gravación libre y de codigo ubierto
+GenericName[ar_SA]=برامج البث / التسجيل
+Comment[ar_SA]=برنامج بث / تسجيل مجاني ومفتوح المصدر
+GenericName[bn_BD]=স্ট্রিমিং/রেকর্ডিং সফটওয়্যার
+Comment[bn_BD]=ফ্রি এবং মুক্ত সোর্স স্ট্রিমিং/রেকর্ডিং সফ্টওয়্যার
+GenericName[ca_ES]=Programa de retransmissió/enregistrament
+Comment[ca_ES]=Programa de retransmissió/enregistrament de codi lliure i gratuït
+GenericName[cs_CZ]=Software pro vysílání a nahrávání
+Comment[cs_CZ]=Svobodný software pro vysílání a nahrávání
+GenericName[da_DK]=Streaming-/optagelsessoftware
+Comment[da_DK]=Gratis og open-source streaming-/optagelsessoftware
+GenericName[de_DE]=Streaming-/Aufnahme-Software
+Comment[de_DE]=Freie und Open-Source-Streaming-/Aufnahme-Software
+GenericName[el_GR]=Λογισμικό Ροής/Καταγραφής
+Comment[el_GR]=Δωρεαν Λογισμικό Streaming/Kαταγραφή ανοιχτου κωδικα
+GenericName[en_GB]=Streaming/Recording Software
+Comment[en_GB]=Free and Open Source Streaming/Recording Software
+GenericName[es_ES]=Disfusion digital/ Software de grabacion
+Comment[es_ES]=Difusion Digital/Software de grabacion Gratis y con Fuentes Abiertas
+GenericName[et_EE]=Video voogesituse ja salvestuse tarkvara
+Comment[et_EE]=Tasuta ja avatud lähtekoodiga video voogesituse ja salvestuse tarkvara
+GenericName[fa_IR]=نرم افزار جریان/ضبط
+Comment[fa_IR]=نرم افزار منبع باز و رایگان جریان/ضبط
+GenericName[fi_FI]=Striimaus-/tallennusohjelmisto
+Comment[fi_FI]=Ilmainen ja avoimen lähdekoodin striimaus-/tallennusohjelmisto
+GenericName[fil_PH]=Software para sa Streaming/Recording
+Comment[fil_PH]=Libre at Open Source na Streaming/Recording Software
+GenericName[fr_FR]=Logiciel de diffusion/enregistrement
+Comment[fr_FR]=Logiciel de diffusion/enregistrement gratuit et Open Source
+GenericName[gd_GB]=Bathar-bog sruthaidh/clàraidh
+Comment[gd_GB]=Bathar-bog sruthaidh/clàraidh saor le bun-tùs fosgailte
+GenericName[he_IL]=תוכנה לשידורים חיים והקלטה
+Comment[he_IL]=תכנה חינמית בקוד פתוח לשידורים חיים ולהקלטה
+GenericName[hi_IN]=स्ट्रीमिंग/रिकॉर्डिंग सॉफ्टवेयर
+Comment[hi_IN]=स्वतंत्र एवं खुले स्रोत वाला स्ट्रीमिंग/रिकॉर्डिंग सॉफ्टवेयर
+GenericName[hr_HR]=Softver za emitiranje/snimanje
+Comment[hr_HR]=Slobodan softver otvorenog koda za emitiranje/snimanje
+GenericName[hu_HU]=Közvetítő/rögzítő szoftver
+Comment[hu_HU]=Szabad és nyílt forráskódú közvetítő/rögzítő szoftver
+GenericName[id_ID]=Perangkat Lunak Streaming/Perekaman
+Comment[id_ID]=Perangkat Lunak Streaming/Perekaman Gratis dan Sumber Terbuka
+GenericName[it_IT]=Software per dirette e registrazione schermo
+Comment[it_IT]=Software Libero e Open Source Streaming/Registrazione
+GenericName[ja_JP]=配信/録画ソフトウェア
+Comment[ja_JP]=無料のオープンソース配信/録画ソフトウェア
+GenericName[ka_GE]=ვიდეოს ეთერში გამშვები/ჩამწერი პროგრამა
+Comment[ka_GE]=თავისუფალი და ღია წყაროს მქონე, ვიდეოს ეთერში გამშვები/ჩამწერი პროგრამა
+GenericName[kmr_TR]=Nermalava weşandin/tomarkirin-ê
+Comment[kmr_TR]=Nermalava weşandin/tomarkirin-ê belaş û çavkaniya azad
+GenericName[ko_KR]=방송 및 녹화 프로그램
+Comment[ko_KR]=무료 오픈소스 방송 및 녹화 프로그램
+GenericName[ms_MY]=Perisian Penstriman/Rakaman
+Comment[ms_MY]=Perisian Penstriman/Rakaman Bersumber Terbuka dan Bebas
+GenericName[nb_NO]=Strømming- og Opptaksprogramvare
+Comment[nb_NO]=Gratis Strømming- og Opptaksprogramvare med Åpen Kildekode
+GenericName[nl_NL]=Streaming/Opname Software
+Comment[nl_NL]=Vrij en Open Source Streaming/Opname Sofware
+GenericName[pl_PL]=Oprogramowanie do transmisji strumieniowej/nagrywania
+Comment[pl_PL]=Darmowe i otwarte oprogramowanie do transmisji strumieniowej/nagrywania
+GenericName[pt_BR]=Software de Streaming/Gravação
+Comment[pt_BR]=Software de Streaming/Gravação de Código Aberto e Livre
+GenericName[pt_PT]=Programa de transmissão/gravação
+Comment[pt_PT]=Programa de transmissão/gravação livre e de código aberto
+GenericName[ro_RO]=Program de Streaming/Înregistrare
+Comment[ro_RO]=Program de streaming / înregistrare gratuit și open source
+GenericName[ru_RU]=Программа для видеостриминга и видеозаписи
+Comment[ru_RU]=Свободное и открытое ПО для видеостриминга и видеозаписи
+GenericName[sk_SK]=Streamovací/Nahrávací Software
+Comment[sk_SK]=Bezplatný a otvorený streamovací/nahrávací software
+GenericName[sl_SI]=Pretočna/snemalna programska oprema
+Comment[sl_SI]=Brezplačni in odprtokodna programska oprema za pretakanje/snemanje
+GenericName[sv_SE]=Programvara för strömning/inspelning
+Comment[sv_SE]=Fri programvara för strömning/inspelning med öppen källkod
+GenericName[tr_TR]=Yayın/Kayıt Yazılımı
+Comment[tr_TR]=Ücretsiz ve Açık Kaynaklı Yayın/Kayıt Yazılımı
+GenericName[uk_UA]=Програма для трансляцій/запису
+Comment[uk_UA]=Вільне та відкрите програмне забезпечення для трансляцій/запису
+GenericName[vi_VN]=Phần mềm ghi hình/phát luồng
+Comment[vi_VN]=Phần mềm ghi hình / phát luồng mở và miễn phí
+GenericName[zh_CN]=直播/录像软件
+Comment[zh_CN]=自由且开源的用于直播串流以及视频录制的软件
+GenericName[zh_TW]=串流與錄影軟體
+Comment[zh_TW]=免費,開源的串流與錄影軟體
diff --git a/linux/home/.local/share/applications/phototonic.desktop b/linux/home/.local/share/applications/phototonic.desktop
new file mode 100644
index 0000000..b99875e
--- /dev/null
+++ b/linux/home/.local/share/applications/phototonic.desktop
@@ -0,0 +1,17 @@
+[Desktop Entry]
+Name=Phototonic
+Name[de]=Phototonic
+Name[fr]=Phototonic
+Comment=View photos on your computer
+Comment[de]=Photos betrachten und verwalten
+Comment[fr]=Visionner et gérer des photos sur votre ordinateur
+GenericName=Image Viewer
+GenericName[de]=Bildbetrachter
+GenericName[fr]=Visionneuse d'images
+Exec=phototonic %F
+Icon=/home/srdusr/.local/share/icons/WhiteSur-dark/apps/scalable/gwenview.svg
+Terminal=false
+Type=Application
+Categories=Graphics;Viewer;
+StartupNotify=true
+MimeType=image/png;image/gif;image/jpeg;image/bmp;image/svg+xml;
diff --git a/linux/home/.scripts/Heads-Up-Display b/linux/home/.scripts/Heads-Up-Display
new file mode 100755
index 0000000..8680123
--- /dev/null
+++ b/linux/home/.scripts/Heads-Up-Display
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+# Created By: srdusr
+# Created On: Wed 05 Feb 2023 01:24:37 AM CAT
+# Project: bspwm scratchpad (Heads-Up-Display) with tmux session
+
+if id="$(xdo id -N Heads-Up-Display)"
+ then bspc node "$id" -g hidden -f
+ else kitty --class "Heads-Up-Display" -e tmux new-session -A -s HUD -e bash > /dev/null 2>&1 &
+fi
+
+#- - - - - - - - - -
+
+
+### Alternative method
+
+#id=$(xdotool search --class Heads-Up-Display);
+#if [ -z "$id" ]; then
+# #kitty --class "Heads-Up-Display" -e tmux new-session -A -s HUD -e bash > /dev/null 2>&1 &
+# alacritty --class "Heads-Up-Display" -e tmux new-session -A -s HUD -e bash > /dev/null 2>&1 &
+#else
+# if [ ! -f /tmp/hide_hud ]; then
+# touch /tmp/hide_hud && xdo hide "$id"
+# elif [ -f /tmp/hide_hud ]; then
+# rm /tmp/hide_hud && xdo show "$id"
+# fi
+#fi
+
diff --git a/linux/home/.scripts/README.md b/linux/home/.scripts/README.md
new file mode 100644
index 0000000..458b3cc
--- /dev/null
+++ b/linux/home/.scripts/README.md
@@ -0,0 +1 @@
+# scripts
diff --git a/linux/home/.scripts/bspwm-toggle-visibility.sh b/linux/home/.scripts/bspwm-toggle-visibility.sh
new file mode 100755
index 0000000..45a4c53
--- /dev/null
+++ b/linux/home/.scripts/bspwm-toggle-visibility.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+
+# Created By: srdusr
+# Created On: Mon 18 Sep 2023 18:37:21 CAT
+# Project: Bspwm script to toggle visibility of initial window and bring focus back to it
+
+# Get the ID of the currently focused desktop
+current_desktop_id=$(bspc query -D -d focused --names)
+
+# Get the ID of the first hidden window in the current desktop
+hidden_window_id=$(bspc query -N -d "$current_desktop_id" -n .hidden | head -n 1)
+
+# Check if there's a hidden window in the current desktop
+if [[ -n "$hidden_window_id" ]]; then
+ # There's a hidden window, so unhide it
+ bspc node "$hidden_window_id" -g hidden=off
+ # Bring focus back to the previously hidden window
+ bspc node -f "$hidden_window_id"
+else
+ # There's no hidden window in the current desktop, hide the first available window
+ first_window_id=$(bspc query -N -n focused.window)
+ bspc node "$first_window_id" -g hidden=on
+fi
diff --git a/linux/home/.scripts/bspwm_resize.sh b/linux/home/.scripts/bspwm_resize.sh
new file mode 100755
index 0000000..29ab5cf
--- /dev/null
+++ b/linux/home/.scripts/bspwm_resize.sh
@@ -0,0 +1,67 @@
+#!/bin/bash
+
+size=${2:-'10'}
+dir=$1
+
+# Find current window mode
+is_tiled() {
+bspc query -T -n | grep -q '"state":"tiled"'
+}
+# If the window is floating, move it
+if ! is_tiled; then
+#only parse input if window is floating,tiled windows accept input as is
+ case "$dir" in
+ west) switch="-w"
+ sign="-"
+ ;;
+ east) switch="-w"
+ sign="+"
+ ;;
+ north) switch="-h"
+ sign="-"
+ ;;
+ south) switch="-h"
+ sign="+"
+ ;;
+ esac
+ xdo resize ${switch} ${sign}${size}
+
+# Otherwise, window is tiled: switch with window in given direction
+else
+ case "$dir" in
+ west) bspc node @west -r -$size || bspc node @east -r -${size}
+ ;;
+ east) bspc node @west -r +$size || bspc node @east -r +${size}
+ ;;
+ north) bspc node @south -r -$size || bspc node @north -r -${size}
+ ;;
+ south) bspc node @south -r +$size || bspc node @north -r +${size}
+ ;;
+ esac
+fi
+
+##!/bin/bash
+#
+#[ "$#" -eq 3 ] || { echo "Needs exactly three arguments."; exit 1; }
+#
+#motion="$1"
+#direction="$2"
+#size="$3"
+#
+#if [ "$motion" = 'expand' ]; then
+# # These expand the window's given side
+# case "$direction" in
+# north) bspc node -z top 0 -"$size" ;;
+# east) bspc node -z right "$size" 0 ;;
+# south) bspc node -z bottom 0 "$size" ;;
+# west) bspc node -z left -"$size" 0 ;;
+# esac
+#else
+# # These contract the window's given side
+# case "$direction" in
+# north) bspc node -z top 0 "$size" ;;
+# east) bspc node -z right -"$size" 0 ;;
+# south) bspc node -z bottom 0 -"$size" ;;
+# west) bspc node -z left "$size" 0 ;;
+# esac
+#fi
diff --git a/linux/home/.scripts/check-updates.sh b/linux/home/.scripts/check-updates.sh
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/linux/home/.scripts/check-updates.sh
diff --git a/linux/home/.scripts/colors.sh b/linux/home/.scripts/colors.sh
new file mode 100755
index 0000000..fc1c10c
--- /dev/null
+++ b/linux/home/.scripts/colors.sh
@@ -0,0 +1,78 @@
+#!/usr/bin/env bash
+colors=$@
+for (( n=0; n < $colors; n++ )) do
+ printf " [%d] $(tput setaf $n)%s$(tput sgr0)" $n "Hello World!
+"
+done
+PADDING='Padding'
+
+main() {
+ local xterm_start=0 \
+ xterm_width=8 \
+ xterm_height=2
+
+ local cube_start=$((xterm_start + xterm_width * xterm_height)) \
+ cube_width=6 \
+ cube_height=$((6 * 6))
+
+ local greys_start=$((cube_start + cube_width * cube_height)) \
+ greys_width=8 \
+ greys_height=3
+
+ color_block $xterm_start $xterm_width $xterm_height
+ color_block $cube_start $cube_width $cube_height use_padding
+ color_block $greys_start $greys_width $greys_height
+ echo
+}
+
+color_block() {
+ local start=$1 width=$2 height=$3 use_padding=$4
+ local max s color_nums colors
+
+ max=$((start + width * height - 1))
+
+ echo
+ for s in $(seq $start $width $max); do
+ color_nums=$(seq $s $((s + width - 1)))
+ colors="${use_padding:+$PADDING }${color_nums}${use_padding:+ $PADDING}"
+
+ printf '%s%s %s%s\n' \
+ "$(fg_bars $colors)" $ansi_reset \
+ "$(bg_bars $colors)" $ansi_reset
+ done
+}
+
+fg_bars() {
+ for color in $@; do
+ color_bar ansi_fg $color ''
+ done
+}
+
+bg_bars() {
+ for color in $@; do
+ color_bar ansi_bg $color ' '
+ done
+}
+
+color_bar() {
+ local ansi=$1 color=$2 trail=$3
+
+ if [ "$color" == $PADDING ]; then
+ printf '%s %s' $ansi_reset "$trail"
+ else
+ local color_seq=$($ansi $color)
+ printf '%s %03d%s' $color_seq $color "$trail"
+ fi
+}
+
+ansi_reset=$'\033[0m'
+
+ansi_fg() {
+ printf '\033[38;5;%dm' $1
+}
+
+ansi_bg() {
+ printf '\033[48;5;%dm' $1
+}
+
+main
diff --git a/linux/home/.scripts/cryptocheck b/linux/home/.scripts/cryptocheck
new file mode 100755
index 0000000..02ba42d
--- /dev/null
+++ b/linux/home/.scripts/cryptocheck
@@ -0,0 +1,31 @@
+#!/usr/bin/env bash
+if [ ! -d ~/.cache/crypto ]; then
+ mkdir ~/.cache/crypto
+fi
+ticker=(BTC ETH ADA DOT SOL XMR)
+
+for currency in "${ticker[@]}"; do
+ echo "$currency"
+done | while read coin
+ do
+ price=$(curl rate.sx/1$coin)
+ if [ $coin = "BTC" ]; then
+ icon=󰠓
+ elif [ $coin = "ETH" ]; then
+ icon=󰡪
+ elif [ $coin = "ADA" ]; then
+ icon=󰝨
+ elif [ $coin = "DOT" ]; then
+ icon=󰐇
+ elif [ $coin = "SOL" ]; then
+ icon=󰘙
+ elif [ $coin = "XMR" ]; then
+ icon=󰝴
+ fi
+
+ echo "$icon $coin: $price" > ~/.cache/crypto/$coin
+
+ done
+
+date > ~/.cache/crypto/time
+
diff --git a/linux/home/.scripts/cryptonotify b/linux/home/.scripts/cryptonotify
new file mode 100755
index 0000000..47883c3
--- /dev/null
+++ b/linux/home/.scripts/cryptonotify
@@ -0,0 +1,19 @@
+#!/bin/sh
+source cryptocheck
+Output=
+for file in ~/.cache/crypto/*
+do
+ if [ ! -z "$Output" ]; then
+ if [ ! $(basename $file) = "time" ]; then
+ Output="$Output\n$(cat $file)"
+ fi
+ else
+ if [ ! $(basename $file) = "time" ]; then
+ Output="$Output$(cat $file)"
+ fi
+ fi
+
+done
+
+Output="$Output\n$(cat ~/.cache/crypto/time)"
+notify-send "Crypto Prices" "$Output"
diff --git a/linux/home/.scripts/dotfiles.sh b/linux/home/.scripts/dotfiles.sh
new file mode 100755
index 0000000..b231367
--- /dev/null
+++ b/linux/home/.scripts/dotfiles.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+
+# Set the bare dotfiles repo directory
+dotfiles_dir="$HOME/.cfg"
+
+# Set the home directory
+home_dir="$HOME"
+
+# Exclude the .cfg directory and any other files/directories you want to ignore
+exclude_list=(".cfg")
+
+# Change to the home directory
+cd "$home_dir"
+
+# Get a list of all dotfiles in the repository
+files=$(find "$dotfiles_dir" -maxdepth 1 -type f -not -name ".*" -not -name "${exclude_list[*]}" -printf "%f\n")
+
+# Link each file to its corresponding location in $HOME
+for file in $files; do
+ ln -sf "$dotfiles_dir/$file" "$home_dir/.$file"
+done
+
+# Get a list of all dot directories in the repository
+dirs=$(find "$dotfiles_dir" -maxdepth 1 -type d -not -path "$dotfiles_dir" -not -name ".*" -not -name "${exclude_list[*]}" -printf "%f\n")
+
+# Link each directory to its corresponding location in $HOME
+for dir in $dirs; do
+ ln -sf "$dotfiles_dir/$dir" "$home_dir/.$dir"
+done
+
+# Remove any symlinks that are no longer present in the repo
+while IFS= read -r -d '' link; do
+ if [[ ! -e "$link" ]]; then
+ rm "$link"
+ fi
+done < <(find "$home_dir" -maxdepth 1 -type l -name ".*" -not -name ".cfg" -print0)
+
diff --git a/linux/home/.scripts/ffmpeg b/linux/home/.scripts/ffmpeg
new file mode 100755
index 0000000..2258fbd
--- /dev/null
+++ b/linux/home/.scripts/ffmpeg
@@ -0,0 +1,24 @@
+#!/bin/bash
+
+# audio
+A="$(pactl list sources | grep 'analog.*monitor' | awk '{print $2}')"
+# screen size
+S="$(xdpyinfo | grep dimensions | awk '{print $2}')"
+# file name
+N="$(date +"%m-%d-%Y_%I:%M%p").mp4"
+
+# Desktop audio + screen recording
+ffmpeg \
+-s "$S" -r 25 -f x11grab -i :0.0+0,0 \
+-ac 2 ~/"$N"
+
+# ffmpeg can output high quality GIF. Before you start it is always recommended to use a recent version: download or compile.
+
+# ffmpeg -ss 30 -t 3 -i input.mp4 -vf "fps=10,scale=320:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" -loop 0 output.gif
+
+# This example will skip the first 30 seconds (-ss 30) of the input and create a 3 second output (-t 3).
+# fps filter sets the frame rate. A rate of 10 frames per second is used in the example.
+# scale filter will resize the output to 320 pixels wide and automatically determine the height while preserving the aspect ratio. The lanczos scaling algorithm is used in this example.
+# palettegen and paletteuse filters will generate and use a custom palette generated from your input. These filters have many options, so refer to the links for a list of all available options and values. Also see the Advanced options section below.
+# split filter will allow everything to be done in one command and avoids having to create a temporary PNG file of the palette.
+# Control looping with -loop output option but the values are confusing. A value of 0 is infinite looping, -1 is no looping, and 1 will loop once meaning it will play twice. So a value of 10 will cause the GIF to play 11 times.
diff --git a/linux/home/.scripts/get_zle_keymap_select.sh b/linux/home/.scripts/get_zle_keymap_select.sh
new file mode 100755
index 0000000..1e2eaf4
--- /dev/null
+++ b/linux/home/.scripts/get_zle_keymap_select.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+# Get the value of the zle-keymap-select variable
+value=$(print -v zle-keymap-select)
+
+# Specify the file path to save the value
+file_path="~/file.txt"
+
+# Write the value to the file
+echo "$value" > "$file_path"
+
+# Optionally, you can also print the value to the console
+echo "The value of zle-keymap-select is: $value"
diff --git a/linux/home/.scripts/gsettings.sh b/linux/home/.scripts/gsettings.sh
new file mode 100755
index 0000000..0cd28c2
--- /dev/null
+++ b/linux/home/.scripts/gsettings.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+
+# Disable screen lock
+gsettings set org.gnome.desktop.screensaver lock-enabled false
+gsettings set org.gnome.desktop.session idle-delay 0
+
+# Mutter Overlay Key
+gsettings set org.gnome.mutter overlay-key ''
+
+# Disable update notification
+#gsettings set org.gnome.software download-updates false
+#gsettings set com.ubuntu.update-notifier no-show-notifications true
+#sudo rm /etc/xdg/autostart/upg-notifier-autostart.desktop
+
+#sudo mv /etc/xdg/autostart/update-notifier.desktop /etc/xdg/autostart/update-notifier.desktop.old
+#sudo mv /etc/xdg/autostart/gnome-software-service.desktop /etc/xdg/autostart/gnome-software-service.desktop.old
+
+# Custom Keybinding Names
+gsettings set org.gnome.settings-daemon.plugins.media-keys custom-keybindings "['/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom0/']"
+
+# Custom Keybinding 0
+gsettings set org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom0/ binding "<Alt>T"
+gsettings set org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom0/ command "scratchpad"
+gsettings set org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom0/ name "scratchpad"
+
+# Disable keyboard plugin
+gsettings set org.gnome.settings-daemon.plugins.keyboard active false
diff --git a/linux/home/.scripts/killandnotify b/linux/home/.scripts/killandnotify
new file mode 100755
index 0000000..2e7222e
--- /dev/null
+++ b/linux/home/.scripts/killandnotify
@@ -0,0 +1,4 @@
+#!/bin/sh
+# Kills an application and sends a notification that it's been killed
+
+killall "$1" && notify-send "$1" "$2"
diff --git a/linux/home/.scripts/layer.sh b/linux/home/.scripts/layer.sh
new file mode 100755
index 0000000..4b17ed1
--- /dev/null
+++ b/linux/home/.scripts/layer.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+
+current_layer="$(bspc query -T -n | jq -r '.client.layer')"
+case $1 in
+ +|-)
+ declare -A _layers=( [below]=0 [normal]=1 [above]=2 )
+ layers=( below normal above )
+ maxl=$(( ${#layers[@]} - 1 ))
+ current_layer="$(bspc query -T -n | jq -r '.client.layer')"
+ i=$(( ${_layers[$current_layer]} $1 1 ))
+ if [[ $i -lt 0 ]]; then
+ i=0
+ elif [[ $i -gt $maxl ]]; then
+ i=$maxl
+ fi
+ #cycle? nah
+ #i=$(( (${_layers[$current_layer]} + ${#layers[@]} ${1} 1) % ${#layers[@]} ))
+ new_layer="${layers[$i]}"
+ ;;
+ *)
+ new_layer="$(bspc query -T -n | jq -r '.client.lastLayer')"
+ ;;
+esac
+[[ "$current_layer" != "$new_layer" ]] && bspc node -l "$new_layer"
diff --git a/linux/home/.scripts/neovim.sh b/linux/home/.scripts/neovim.sh
new file mode 100755
index 0000000..39554cb
--- /dev/null
+++ b/linux/home/.scripts/neovim.sh
@@ -0,0 +1,421 @@
+#!/bin/bash
+
+# Created By: srdusr
+# Created On: Sat 12 Aug 2023 13:11:39 CAT
+# Project: Install/update/uninstall/change version Neovim script, primarily for Linux but may work in other platforms
+
+# Dependencies: wget/curl, fuse
+
+# Color definitions
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+NC='\033[0m' # No Color
+
+# Function to handle errors
+handle_error() {
+ local message="$1"
+ printf "${RED}Error: $message${NC}\n"
+}
+
+# Check if necessary dependencies are installed
+check_dependencies() {
+ if [ -x "$(command -v wget)" ]; then
+ DOWNLOAD_COMMAND="wget"
+ elif [ -x "$(command -v curl)" ]; then
+ DOWNLOAD_COMMAND="curl"
+ else
+ printf "${RED}Error: Neither wget nor curl found. Please install one of them to continue!${NC}\n"
+ exit 1
+ fi
+}
+
+# Check for privilege escalation tools
+check_privilege_tools() {
+ if [ -x "$(command -v sudo)" ]; then
+ PRIVILEGE_TOOL="sudo"
+ elif [ -x "$(command -v doas)" ]; then
+ PRIVILEGE_TOOL="doas"
+ elif [ -x "$(command -v pkexec)" ]; then
+ PRIVILEGE_TOOL="pkexec"
+ elif [ -x "$(command -v dzdo)" ]; then
+ PRIVILEGE_TOOL="dzdo"
+ elif [ "$(id -u)" -eq 0 ]; then
+ PRIVILEGE_TOOL="" # root
+ else
+ PRIVILEGE_TOOL="" # No privilege escalation mechanism found
+ printf "\n${RED}Error: No privilege escalation tool (sudo, doas, pkexec, dzdo, or root privileges) found. You may not have sufficient permissions to run this script.${NC}\n"
+ printf "\nAttempt to continue Installation (might fail without a privilege escalation tool)? [yes/no] "
+ read continue_choice
+ case $continue_choice in
+ [Yy] | [Yy][Ee][Ss]) ;;
+ [Nn] | [Nn][Oo]) exit ;;
+ *) handle_error "Invalid choice. Exiting..." && exit ;;
+ esac
+ fi
+}
+
+# Check if Neovim is already installed
+check_neovim_installed() {
+ if [ -x "$(command -v nvim)" ]; then
+ return 0 # Neovim is installed
+ else
+ return 1 # Neovim is not installed
+ fi
+}
+
+# Nightly version
+nightly_version() {
+ local url="https://github.com/neovim/neovim/releases/download/nightly/nvim.appimage"
+ install_neovim "$url"
+ local version_output=$(nvim --version)
+ version_id="Nightly $(echo "$version_output" | grep -oP 'NVIM \d+\.\d+')"
+}
+
+# Stable version
+stable_version() {
+ local url="https://github.com/neovim/neovim/releases/download/stable/nvim.appimage"
+ install_neovim "$url"
+ local version_output=$(nvim --version)
+ version_id="Stable $(echo "$version_output" | grep -oP 'NVIM \d+\.\d+')"
+}
+
+# Specific version
+specific_version() {
+ local version="$1"
+
+ # Add 'v' prefix if not present
+ if [[ $version != v* ]]; then
+ version="v$version"
+ fi
+
+ local url="https://github.com/neovim/neovim/releases/download/$version/nvim.appimage"
+ install_neovim "$url"
+ local version_output=$(nvim --version)
+ version_id="Version $version $(echo "$version_output" | grep -oP 'NVIM \d+\.\d+')"
+}
+
+# Function to download a file using wget or curl
+download_file() {
+ local url="$1"
+ local output="$2"
+
+ if [ "$DOWNLOAD_COMMAND" = "wget" ]; then
+ if ! "$DOWNLOAD_COMMAND" -q --show-progress -O "$output" "$url"; then
+ handle_error "Download failed. Exiting..."
+ exit 1
+ fi
+ elif [ "$DOWNLOAD_COMMAND" = "curl" ]; then
+ if ! "$DOWNLOAD_COMMAND" --progress-bar -# -o "$output" "$url"; then
+ handle_error "Download failed. Exiting..."
+ exit 1
+ fi
+ else
+ echo "Unsupported download command: $DOWNLOAD_COMMAND"
+ exit 1
+ fi
+}
+
+# Check if a specific version of Neovim exists
+version_exists() {
+ local version="$1"
+
+ # Add 'v' prefix if not present
+ if [[ $version != v* ]]; then
+ version="v$version"
+ fi
+
+ # Fetch all the release tags from GitHub
+ ALL_TAGS=$(curl -s "https://api.github.com/repos/neovim/neovim/tags" | grep '"name":' | cut -d '"' -f 4)
+
+ # Check if the desired version is in the list of release tags
+ if echo "$ALL_TAGS" | grep -q "$version"; then
+ return 0 # Version exists
+ else
+ return 1 # Version does not exist
+ fi
+}
+
+# Update Neovim to the latest version (nightly/stable)
+update_version() {
+ valid_choice=false
+ while [ "$valid_choice" = false ]; do
+ # Determine which version to update to (nightly/stable)
+ printf "Select version to install/update to:\n"
+ printf " 1. Nightly\n"
+ printf " 2. Stable\n"
+ printf " 3. Choose specific version by tag\n"
+ printf "Enter the number corresponding to your choice (1/2/3): "
+ read update_choice
+
+ case $update_choice in
+ 1)
+ version="Nightly"
+ nightly_version
+ valid_choice=true
+ ;;
+ 2)
+ version="Stable"
+ stable_version
+ valid_choice=true
+ ;;
+ 3)
+ # Ask user for specific version
+ read -p "Enter the specific version (e.g., v0.1.0): " version
+ # Normalize version
+ if [[ $version != v* ]]; then
+ version="v$version"
+ fi
+ # Check if the specific version exists on GitHub releases
+ if version_exists "$version"; then
+ # Install specific version
+ specific_version "$version" # Pass the normalized version to the function
+ valid_choice=true
+ else
+ printf "${RED}The specified version $version does not exist.${NC}\n"
+ fi
+ ;;
+
+ *)
+ handle_error "Invalid choice. Please enter a valid option (1, 2 or 3)."
+ ;;
+ esac
+ done
+
+}
+
+# Install Neovim
+install_neovim() {
+ local url="$1"
+ local install_action="$3"
+
+ if [ "$install_action" = "installed" ]; then
+ printf "Downloading and installing Neovim $version...\n"
+ else
+ printf "${GREEN}Updating Neovim to the latest version ($version)...${NC}\n"
+ fi
+
+ # Determine the platform-specific installation steps
+ case "$(uname -s)" in
+ Linux)
+ printf "Detected Linux OS.\n"
+ if [ -x "$(command -v fusermount)" ]; then
+ printf "FUSE is available. Downloading and running the AppImage...\n"
+ download_file "$url" "nvim.appimage"
+ chmod u+x nvim.appimage
+ "$PRIVILEGE_TOOL" cp nvim.appimage /usr/local/bin/nvim
+ "$PRIVILEGE_TOOL" mv nvim.appimage /usr/bin/nvim
+ else
+ printf "FUSE is not available. Downloading and extracting the AppImage...\n"
+ download_file "$url" "nvim.appimage"
+ chmod u+x nvim.appimage
+ ./nvim.appimage --appimage-extract
+ "$PRIVILEGE_TOOL" cp squashfs-root/usr/bin/nvim /usr/local/bin
+ "$PRIVILEGE_TOOL" mv squashfs-root/usr/bin/nvim /usr/bin
+ fi
+ ;;
+
+ Darwin)
+ printf "Detected macOS.\n"
+ download_file "$url" "nvim-macos.tar.gz"
+ xattr -c ./nvim-macos.tar.gz
+ tar xzvf nvim-macos.tar.gz
+ "$PRIVILEGE_TOOL" cp nvim-macos/bin/nvim /usr/local/bin
+ "$PRIVILEGE_TOOL" mv nvim-macos/bin/nvim /usr/bin/nvim
+ ;;
+
+ MINGW*)
+ printf "Detected Windows.\n"
+ download_file "$url" "nvim.appimage"
+ chmod +x nvim.appimage
+ if [ "$PRIVILEGE_TOOL" = "sudo" ]; then
+ "$PRIVILEGE_TOOL" cp nvim.appimage /usr/local/bin/nvim
+ "$PRIVILEGE_TOOL" mv /usr/local/bin/nvim /usr/bin
+ elif [ "$PRIVILEGE_TOOL" = "" ]; then
+ cp nvim.appimage /usr/local/bin/nvim
+ mv /usr/local/bin/nvim /usr/bin
+ else
+ printf "No privilege escalation tool found. Cannot install Neovim on Windows.\n"
+ fi
+ ;;
+
+ *)
+ printf "Unsupported operating system.\n"
+ exit 1
+ ;;
+ esac
+ # Check if the installation was successful
+ if [ $? -eq 0 ]; then
+ if [ "$install_action" = "installed" ]; then
+ printf "${GREEN}Neovim $version has been installed successfully!${NC}\n"
+ else
+ printf "${GREEN}Neovim has been updated successfully to $version!${NC}\n"
+ fi
+ else
+ printf "${RED}Error: Neovim installation/update failed.${NC}\n"
+ exit 1
+ fi
+}
+
+# Uninstall Neovim
+uninstall_neovim() {
+ printf "${RED}Uninstalling Neovim...${NC}\n"
+
+ # Detect the operating system to determine the appropriate uninstallation method
+ case "$(uname -s)" in
+ Linux)
+ printf "Detected Linux OS.\n"
+ "$PRIVILEGE_TOOL" rm /usr/local/bin/nvim
+ "$PRIVILEGE_TOOL" rm /usr/bin/nvim
+ ;;
+
+ Darwin)
+ printf "Detected macOS.\n"
+ "$PRIVILEGE_TOOL" rm /usr/local/bin/nvim
+ "$PRIVILEGE_TOOL" rm /usr/bin/nvim
+ ;;
+
+ MINGW*)
+ printf "Detected Windows.\n"
+ if [ "$PRIVILEGE_TOOL" = "sudo" ]; then
+ "$PRIVILEGE_TOOL" rm /usr/local/bin/nvim
+ "$PRIVILEGE_TOOL" rm /usr/bin/nvim
+ else
+ [ "$PRIVILEGE_TOOL" = "" ]
+ rm /usr/local/bin/nvim
+ rm /usr/bin/nvim
+ fi
+ ;;
+ *)
+ printf "Unsupported operating system.\n"
+ ;;
+ esac
+
+ printf "${GREEN}Neovim has been uninstalled successfully!${NC}\n"
+}
+
+# Check if Neovim is running
+check_neovim_running() {
+ if pgrep nvim >/dev/null; then
+ printf "${RED}Error: Neovim is currently running. Please close Neovim before proceeding.${NC}\n"
+ read -p "Do you want to forcefully terminate Neovim and continue? [yes/no] " terminate_choice
+
+ case $terminate_choice in
+ [Yy] | [Yy][Ee][Ss])
+ pkill nvim # Forcefully terminate Neovim
+ ;;
+ [Nn] | [Nn][Oo])
+ echo "Exiting..."
+ exit 1
+ ;;
+ *)
+ handle_error "Invalid choice."
+ ;;
+ esac
+ fi
+}
+
+check_neovim_running
+
+# Define the variable to control the prompt
+SHOW_PROMPT=1
+
+# Check if necessary dependencies are installed
+check_dependencies
+
+# Check for privilege escalation tools
+check_privilege_tools
+
+# Check if Neovim is already installed and ask the user if want to install it
+if check_neovim_installed; then
+ printf "${GREEN}Neovim is already installed!${NC}\n"
+else
+ printf "${RED}Neovim is not installed.${NC}\n"
+ read -p "Install Neovim? (y/n): " install_choice
+
+ case $install_choice in
+ [Yy])
+ update_version
+ ;;
+ [Nn])
+ echo "Exiting..."
+ exit
+ ;;
+ *)
+ handle_error "Invalid choice. Please enter 'y' for yes or 'n' for no."
+ ;;
+ esac
+fi
+
+# Function to check for updates and display breaking changes
+check_version_updates() {
+ local latest_version_url="https://api.github.com/repos/neovim/neovim/releases/latest"
+ local latest_version=""
+
+ if [ -x "$(command -v curl)" ]; then
+ latest_version=$(curl -sSL "$latest_version_url" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
+ elif [ -x "$(command -v wget)" ]; then
+ latest_version=$(wget -qO - "$latest_version_url" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
+ else
+ printf "${RED}Error: Neither curl nor wget found. Please install one of them to continue!${NC}\n"
+ exit 1
+ fi
+
+ if version_exists "$latest_version"; then
+ printf "${GREEN}An update is available!${NC}\n"
+ display_breaking_changes "$latest_version"
+ else
+ printf "You have the latest version of Neovim.\n"
+ fi
+}
+
+# Function to display breaking changes for a specific version
+display_breaking_changes() {
+ local version="$1"
+ local changelog_url="https://github.com/neovim/neovim/releases/tag/$version"
+ local changelog=""
+
+ if [ -x "$(command -v curl)" ]; then
+ changelog=$(curl -sSL "$changelog_url" | grep -oE '<h1>Breaking Changes.*?</ul>' | sed 's/<[^>]*>//g')
+ elif [ -x "$(command -v wget)" ]; then
+ changelog=$(wget -qO - "$changelog_url" | grep -oE '<h1>Breaking Changes.*?</ul>' | sed 's/<[^>]*>//g')
+ else
+ printf "${RED}Error: Neither curl nor wget found. Please install one of them to continue!${NC}\n"
+ exit 1
+ fi
+
+ printf "\nBreaking Changes in Neovim $version:\n"
+ printf "$changelog\n"
+}
+
+# Main loop
+while [ "$SHOW_PROMPT" -gt 0 ]; do
+ printf "Select an option:\n"
+ printf " 1. Install/update Neovim\n"
+ printf " 2. Check for updates\n"
+ printf " 3. Uninstall Neovim\n"
+ printf " 4. Run Neovim\n"
+ printf " 5. Quit\n"
+ read -p "Enter a number or press 'q' to quit: " choice
+
+ case $choice in
+ 1)
+ update_version
+ ;;
+ 2)
+ check_version_updates
+ ;;
+ 3)
+ uninstall_neovim
+ ;;
+ 4)
+ nvim
+ ;;
+ 5 | [Qq])
+ echo "Exiting..."
+ exit
+ ;;
+ *)
+ handle_error "Invalid choice. Please choose a valid option by entering the corresponding number or press 'q' to 'quit'."
+ ;;
+ esac
+done
diff --git a/linux/home/.scripts/opacity-change.sh b/linux/home/.scripts/opacity-change.sh
new file mode 100755
index 0000000..b509936
--- /dev/null
+++ b/linux/home/.scripts/opacity-change.sh
@@ -0,0 +1,34 @@
+#!/usr/bin/env bash
+set -eu
+[[ -n ${DEBUG:-} ]] && set -x
+
+#### Example alacritty.yml usage
+#key_bindings:
+# - { key: N, mods: Control|Shift, action: SpawnNewInstance }
+# - { key: O, mods: Control|Shift, command: { program: "opacity-change.sh", args: ["-"] } }
+# - { key: P, mods: Control|Shift, command: { program: "opacity-change.sh", args: ["+"] } }
+
+
+operation="${1:-}${2:-}" # Arg #1 & #2 (in case the user misinterpreted a space in the usage), Default ''
+step="${operation:1}" # Substring from char index 1
+step="${step:-1}" # Default '1'
+operation="${operation:0:1}" # Substring from char index 0 length of 1
+config_file="$HOME/.config/alacritty/alacritty.yml"
+config_field="opacity"
+tmp_file="/tmp/$(basename $config_file).$(date +%s)"
+current_value=$(sed 's/#.*//g; /\b'"$config_field"':/!d; s/.*: \?//' < "$config_file")
+
+case $operation in
+"-")
+ verb="Decreasing" ;;
+"+")
+ verb="Increasing" ;;
+*)
+ echo "Usage: ${BASH_SOURCE[0]} (-|+)[int]"; exit 255 ;;
+esac
+
+new_value="$(awk '{n=$1+$2/10; print (n<0 ? 0 : n>1 ? 1 : n)}' <<<"$current_value $operation$step")"
+echo "$verb $config_field from $current_value to $new_value" >&2
+cp "$config_file" "$tmp_file"
+sed "s/\b$config_field:.*/$config_field: $new_value/" "$tmp_file" > "$config_file"
+
diff --git a/linux/home/.scripts/powermenu b/linux/home/.scripts/powermenu
new file mode 100755
index 0000000..7bd913e
--- /dev/null
+++ b/linux/home/.scripts/powermenu
@@ -0,0 +1,38 @@
+#!/bin/bash
+
+# display a power menu to: shutdown, reboot,
+# lock, logout, and suspend. This script can be
+# executed by clicking on the polybar powermenu module
+# or with a keyboard shortcut
+
+# options to be displayed
+shutdown=" Shutdown"
+reboot=" Reboot"
+lock=" Lock"
+logout=" Logout"
+suspend=" Suspend"
+
+uptime=$(uptime -p | sed -e 's/up //g')
+
+# options passed into variable
+options="$shutdown\n$reboot\n$lock\n$logout\n$suspend"
+
+chosen="$(echo -e "$options" | rofi -theme ~/.config/rofi/styles/powermenu.rasi -lines 5 -dmenu -p "$uptime")"
+
+case $chosen in
+$shutdown)
+ systemctl poweroff
+ ;;
+$reboot)
+ systemctl reboot
+ ;;
+$lock)
+ betterlockscreen --lock dimblur
+ ;;
+$logout)
+ bspc quit
+ ;;
+$suspend)
+ systemctl suspend
+ ;;
+esac
diff --git a/linux/home/.scripts/qemu-helper.sh b/linux/home/.scripts/qemu-helper.sh
new file mode 100755
index 0000000..0d38aba
--- /dev/null
+++ b/linux/home/.scripts/qemu-helper.sh
@@ -0,0 +1,172 @@
+#!/bin/bash
+
+# Created By: srdusr
+# Created On: Wed 02 Aug 2023 16:16:21 PM CAT
+# Project: QEMU setup/opener helper wrapper script
+
+# Set global variables for VM parameters
+ram_size="4G"
+
+# Function to prompt user for VM parameters
+function get_vm_parameters() {
+ read -p "Enter VM name (default: vm): " vm_name
+ vm_name=${vm_name:-vm}
+
+ # Set the default ISO file path to ~/machines/images
+ default_iso_path="$HOME/machines/images"
+
+ # Generate completions for ISO and IMG files in the images directory
+ COMPREPLY=()
+ local files=$(compgen -G "$default_iso_path/*.{iso,img}" -o plusdirs)
+ for file in $files; do
+ COMPREPLY+=("$file")
+ done
+
+ # Use read with -i and -e options for tab-completion
+ read -ep "Enter ISO file path (default: $default_iso_path): " -i "$default_iso_path" iso_path
+
+ # Manually expand the ~ to the user's home directory
+ iso_path=$(eval echo "$iso_path")
+
+ # Validate the user input
+ while [ ! -f "$iso_path" ]; do
+ read -ep "Invalid file path. Enter a valid ISO file path: " iso_path
+ done
+
+ # Check if the selected file is an IMG file
+ if [[ "$iso_path" == *.img ]]; then
+ guest_os="windows"
+ else
+ guest_os="linux"
+ fi
+
+ # Show available disk space before asking for disk image size
+ echo "Available disk space:"
+ df -h "$vm_images_path"
+
+ read -p "Enter disk image size in GB (default: 10G): " disk_size
+ disk_size=${disk_size:-10G}
+
+ read -p "Enter RAM size in GB (default: 4G): " ram_size
+ ram_size=${ram_size:-4G}
+
+ # Check if the RAM size is in the correct format (e.g., "4G")
+ while ! [[ $ram_size =~ ^[0-9]+[kKmMgGtTpPeE]$ ]]; do
+ read -p "Invalid RAM size format. Enter RAM size in GB (e.g., 4G): " ram_size
+ done
+
+ read -p "Enter number of CPU cores (default: 2): " cpu_cores
+ cpu_cores=${cpu_cores:-2}
+}
+
+
+# Function to list available VMs
+function list_vms() {
+ echo "Available VMs:"
+ for vm_file in "$vm_images_path"/*.qcow2; do
+ vm=$(basename "$vm_file" .qcow2)
+ echo " - $vm"
+ done
+}
+
+# Function to list available ISO and IMG files in the images directory
+function list_iso_img_files() {
+ echo "Available ISO and IMG files in $iso_images_path:"
+ iso_img_files=()
+ while IFS= read -r -d $'\0' file; do
+ iso_img_files+=("$file")
+ done < <(find "$iso_images_path" -type f \( -iname \*.iso -o -iname \*.img \) -print0)
+
+ for ((i = 0; i < ${#iso_img_files[@]}; i++)); do
+ echo " $((i + 1)). ${iso_img_files[i]##*/}"
+ done
+}
+
+# Function to check if VM is already running
+function is_vm_running() {
+ vm_name=$1
+ if ps aux | grep -v grep | grep -q "[q]emu-system-x86_64.*$vm_name"; then
+ return 0
+ else
+ return 1
+ fi
+}
+
+# Function to start VM
+function start_vm() {
+ vm_name=$1
+ is_vm_running "$vm_name"
+ if [ $? -eq 0 ]; then
+ echo "VM '$vm_name' is already running."
+ return
+ fi
+
+ # VM parameters
+ qemu_cmd="qemu-system-x86_64 -enable-kvm -machine type=q35 -m $ram_size -cpu host -smp 2 -vga virtio"
+ qemu_cmd+=" -device qemu-xhci -device usb-tablet -device usb-kbd -device virtio-net,netdev=user0 -netdev user,id=user0,hostfwd=tcp::5555-:22"
+ qemu_cmd+=" -cdrom \"$iso_path\" -drive file=\"$vm_images_path/$vm_name.qcow2\",index=0,media=disk,if=virtio"
+
+ if [[ $guest_os == "windows" ]]; then
+ qemu_cmd+=" -drive file=\"$iso_images_path/virtio-win.iso\",index=3,media=cdrom"
+ fi
+
+ qemu_cmd+=" -boot menu=on"
+ qemu_cmd+=" -net nic -net user,hostname=$vm_name -name \"$vm_name\""
+
+ echo "Starting VM: $vm_name"
+ eval "$qemu_cmd"
+}
+
+# Main script starts here
+vm_images_path="$HOME/machines/vm"
+iso_images_path="$HOME/machines/images"
+
+# Check if directories exist
+mkdir -p "$vm_images_path"
+mkdir -p "$iso_images_path"
+
+# List available VMs
+list_vms
+
+# List available ISO and IMG files in the images directory
+list_iso_img_files
+
+# Ask the user if they want to use an existing VM or create a new one
+read -p "Do you want to use an existing VM? (y/n): " use_existing_vm
+if [[ $use_existing_vm =~ ^[Yy]$ ]]; then
+ read -p "Enter the name of the existing VM: " existing_vm_name
+ while [ ! -f "$vm_images_path/$existing_vm_name.qcow2" ]; do
+ echo "VM '$existing_vm_name' does not exist."
+ read -p "Enter a valid existing VM name: " existing_vm_name
+ done
+ vm_name=$existing_vm_name
+else
+ # Prompt user for VM parameters
+ get_vm_parameters
+
+ # Check if VM already exists
+ if [ -f "$vm_images_path/$vm_name.qcow2" ]; then
+ read -p "VM '$vm_name' already exists. Do you want to start it? (y/n): " start_vm_choice
+ if [[ $start_vm_choice =~ ^[Yy]$ ]]; then
+ start_vm "$vm_name"
+ exit 0
+ fi
+ else
+ # Create new VM
+ echo "Creating new VM: $vm_name"
+ qemu-img create -f qcow2 "$vm_images_path/$vm_name.qcow2" "$disk_size"
+ start_vm "$vm_name"
+ exit 0
+ fi
+fi
+
+# If an existing VM is selected, ask if the user wants to modify its parameters
+read -p "Do you want to modify the VM parameters? (y/n): " modify_vm_params
+if [[ $modify_vm_params =~ ^[Yy]$ ]]; then
+ get_vm_parameters
+fi
+
+# Start the VM
+start_vm "$vm_name"
+
+echo "Script execution completed."
diff --git a/linux/home/.scripts/random_data.py b/linux/home/.scripts/random_data.py
new file mode 100755
index 0000000..071ab7c
--- /dev/null
+++ b/linux/home/.scripts/random_data.py
@@ -0,0 +1,153 @@
+#!/usr/bin/env python3
+
+import os
+import random
+import string
+import json
+import datetime
+import csv
+
+
+def generate_random_string(length, charset=string.ascii_letters):
+ """Generate a random string of given length and character set."""
+ return ''.join(random.choice(charset) for _ in range(length))
+
+
+def generate_random_number(min_value, max_value):
+ """Generate a random number within the specified range."""
+ return random.randint(min_value, max_value)
+
+
+def generate_random_date(start_date, end_date):
+ """Generate a random date within the specified range."""
+ time_between_dates = end_date - start_date
+ days_between_dates = time_between_dates.days
+ random_number_of_days = random.randrange(days_between_dates)
+ random_date = start_date + datetime.timedelta(days=random_number_of_days)
+ return random_date.strftime("%Y-%m-%d")
+
+
+def generate_sql_insert(table_name, columns, num_records):
+ """Generate SQL INSERT statements for populating a table."""
+ sql_statements = []
+ for _ in range(num_records):
+ values = [f"'{generate_random_string(int(input(f'Enter length for {column}: ')))}'" for column in columns]
+ sql = f"INSERT INTO {table_name} ({', '.join(columns)}) VALUES ({', '.join(values)});"
+ sql_statements.append(sql)
+ return sql_statements
+
+
+def generate_placeholder_data(num_records, data_format):
+ """Generate placeholder data based on user-defined format."""
+ placeholder_data = []
+ for _ in range(num_records):
+ record = {}
+ for field, field_data in data_format.items():
+ data_type = field_data['type']
+ if data_type == 'string':
+ length = field_data['length']
+ charset = field_data['charset']
+ record[field] = generate_random_string(length, charset)
+ elif data_type == 'number':
+ min_value = field_data['min']
+ max_value = field_data['max']
+ record[field] = generate_random_number(min_value, max_value)
+ elif data_type == 'date':
+ start_date = datetime.datetime.strptime(field_data['start_date'], "%Y-%m-%d")
+ end_date = datetime.datetime.strptime(field_data['end_date'], "%Y-%m-%d")
+ record[field] = generate_random_date(start_date, end_date)
+ elif data_type == 'boolean':
+ record[field] = random.choice([True, False])
+ placeholder_data.append(record)
+ return placeholder_data
+
+
+def get_data_format_from_user():
+ """Prompt the user for data format preferences."""
+ data_format = {}
+ while True:
+ field = input("Enter field name (or 'done' to finish): ").strip()
+ if field.lower() == 'done':
+ break
+
+ data_type = input(f"Enter data type for '{field}' (string/number/date/boolean): ").strip()
+ if data_type not in ['string', 'number', 'date', 'boolean']:
+ print("Invalid data type. Please enter 'string', 'number', 'date', or 'boolean'.")
+ continue
+
+ if data_type == 'string':
+ length = int(input(f"Enter length for '{field}' (integer): "))
+ charset = input(f"Enter character set for '{field}' (optional, press Enter for default): ").strip()
+ if not charset:
+ charset = string.ascii_letters
+ data_format[field] = {'type': 'string', 'length': length, 'charset': charset}
+ elif data_type == 'number':
+ min_value = int(input(f"Enter minimum value for '{field}' (integer): "))
+ max_value = int(input(f"Enter maximum value for '{field}' (integer): "))
+ data_format[field] = {'type': 'number', 'min': min_value, 'max': max_value}
+ elif data_type == 'date':
+ start_date = input(f"Enter start date for '{field}' (YYYY-MM-DD): ")
+ end_date = input(f"Enter end date for '{field}' (YYYY-MM-DD): ")
+ data_format[field] = {'type': 'date', 'start_date': start_date, 'end_date': end_date}
+ elif data_type == 'boolean':
+ data_format[field] = {'type': 'boolean'}
+
+ return data_format
+
+
+def get_file_type_from_user():
+ """Prompt the user for the desired file type (e.g., JSON, CSV, SQL, TXT, MD, HTML)."""
+ while True:
+ file_type = input("Enter the desired file type for saving the data (json/csv/sql/txt/md/html): ").strip().lower()
+ if file_type in ['json', 'csv', 'sql', 'txt', 'md', 'html']:
+ return file_type
+ else:
+ print("Invalid file type. Please enter 'json', 'csv', 'sql', 'txt', 'md', or 'html'.")
+
+
+def save_data_to_file(data, file_type):
+ """Save the generated data to a file of the specified type."""
+ if file_type == 'json':
+ with open('placeholder_data.json', 'w') as json_file:
+ json.dump(data, json_file, indent=4)
+ elif file_type == 'csv':
+ with open('placeholder_data.csv', 'w', newline='') as csv_file:
+ fieldnames = data[0].keys()
+ writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
+ writer.writeheader()
+ for record in data:
+ writer.writerow(record)
+ elif file_type == 'sql':
+ table_name = input("Enter the SQL table name: ")
+ columns = input("Enter column names separated by commas: ").split(',')
+ sql_statements = generate_sql_insert(table_name, columns, len(data))
+ with open('generated_data.sql', 'w') as sql_file:
+ sql_file.write('\n'.join(sql_statements))
+ elif file_type == 'txt':
+ with open('placeholder_data.txt', 'w') as txt_file:
+ for record in data:
+ txt_file.write(str(record) + '\n')
+ elif file_type == 'md':
+ with open('placeholder_data.md', 'w') as md_file:
+ for record in data:
+ md_file.write('- ' + ', '.join([f"{key}: {value}" for key, value in record.items()]) + '\n')
+ elif file_type == 'html':
+ with open('placeholder_data.html', 'w') as html_file:
+ html_file.write('<html>\n<head>\n<title>Placeholder Data</title>\n</head>\n<body>\n')
+ for record in data:
+ html_file.write('<ul>\n')
+ for key, value in record.items():
+ html_file.write(f'<li>{key}: {value}</li>\n')
+ html_file.write('</ul>\n')
+
+
+if __name__ == "__main__":
+ num_records = int(input("Enter the number of records to generate: "))
+ data_format = get_data_format_from_user()
+ file_type = get_file_type_from_user()
+
+ placeholder_data = generate_placeholder_data(num_records, data_format)
+
+ save_data_to_file(placeholder_data, file_type)
+
+ print(f"Data will be saved to: {os.path.abspath('generated_data.sql')}")
diff --git a/linux/home/.scripts/scratchpad b/linux/home/.scripts/scratchpad
new file mode 100755
index 0000000..8a1aea0
--- /dev/null
+++ b/linux/home/.scripts/scratchpad
@@ -0,0 +1,64 @@
+#!/bin/bash
+
+# Created By: srdusr
+# Created On: Tue 07 Mar 2023 15:06:47 PM CAT
+# Project: Agnostic scratchpad/dropdown terminal that works on most window managers
+
+# Dependencies: wmctrl, xprop, xdo, xdotool
+# NOTE: Ensure script is included in system's path and can therefore be invoked with the command 'scratchpad'.
+# Furthermore make sure the terminal is using x11 as a backend in wayland to allow this to work.
+# Example: wezterm.lua: enable_wayland = false,
+# kitty.conf: linux_display_server x11
+
+# Set the environment variables to x11 to allow working in Wayland
+export GDK_BACKEND=x11
+export QT_QPA_PLATFORM=xcb
+export WAYLAND_DISPLAY=""
+export WINIT_UNIX_BACKEND=x11
+
+# Supported terminals and dropdown class
+supported_terminals=("wezterm" "kitty" "alacritty")
+
+# Check if any supported terminal with scratchpad class is running
+for term in "${supported_terminals[@]}"; do
+ if pgrep -f "$term.*--class scratchpad" >/dev/null; then
+ my_term="$term"
+ break
+ fi
+done
+
+# If no supported terminal is running, start the first available one
+if [ "$my_term" = "" ]; then
+ for term in "${supported_terminals[@]}"; do
+ if command -v "$term" >/dev/null 2>&1; then
+ my_term="$term"
+ break
+ fi
+ done
+ if [ "$my_term" = "" ]; then
+ echo "No supported terminal found." && exit 1
+ fi
+
+ # Start terminal with scratchpad class
+ case "$my_term" in
+ "wezterm") wezterm start --class scratchpad -e tmux new-session -A -s tmux -e bash & ;;
+ "kitty") kitty --class scratchpad tmux new-session -A -s tmux -e bash & ;;
+ "alacritty") alacritty --class scratchpad -e tmux new-session -A -s tmux -e bash & ;;
+ esac
+fi
+
+# Get the window ID of the scratchpad terminal
+id="$(xdo id -N scratchpad)"
+
+# Toggle scratchpad terminal visibility
+if [ "$id" != "" ]; then
+ if xwininfo -id "$id" | grep "Map State: IsViewable" >/dev/null; then
+ # Scratchpad is visible, hide it
+ dimensions="$(xwininfo -id "$id" | awk '/Width:|Height:/ { printf("%s=%s;", tolower($1), $2) }')"
+ xdo hide "$id" 2>/dev/null
+ else
+ # Scratchpad is hidden, show it and restore dimensions
+ xdo show "$id"
+ xdotool windowsize "$id" "$(echo "$dimensions" | tr ';' ' ')" 2>/dev/null
+ fi
+fi
diff --git a/linux/home/.scripts/spec b/linux/home/.scripts/spec
new file mode 100755
index 0000000..19810fc
--- /dev/null
+++ b/linux/home/.scripts/spec
@@ -0,0 +1,73 @@
+#!/usr/bin/env bash
+
+# Created By: srdusr
+# Created On: Wed 18 Oct 2023 20:19:03 CAT
+# Project: Create Spectrograms of audio files
+
+# Dependencies: sox
+
+# Define the timestamp function
+timestamp() {
+ date +%Y%m%d%H%M%S
+}
+
+spec() {
+
+ if [[ $# -eq 0 ]]; then
+ echo "No audio files provided."
+ return
+ fi
+
+ local outdir
+
+ if [[ -d "$HOME/pictures" ]]; then
+ outdir="$HOME/pictures/spectrograms"
+ elif [[ -d "$HOME/Pictures" ]]; then
+ outdir="$HOME/Pictures/Spectrograms"
+ elif [[ -d "$HOME/Images" ]]; then
+ outdir="$HOME/Images/Spectrograms"
+ else
+ outdir="./spectrograms" # Save to the current directory if none of the expected directories exist
+ fi
+
+ for file in "$@"; do
+ if [[ -f "$file" ]]; then
+ local filename
+ filename=$(basename "$file")
+ local filename_no_extension="${filename%.*}"
+ local target_dir="$outdir"
+ local outname="$target_dir/sox-spec-$(timestamp)-${filename_no_extension}.png"
+
+ if [[ ! -d "$target_dir" ]]; then
+ mkdir -p "$target_dir" # Create the directory if it doesn't exist
+ fi
+
+ sox "$file" -S -n spectrogram -o "$outname"
+
+ # Add the generated spectrogram file name to the array
+ spectrogram_files+=("$outname")
+ else
+ echo "File not found: $file"
+ fi
+ done
+
+ if [[ ${#} -gt 0 ]]; then
+ read -p "Do you want to open the spectrogram(s)? (y/n): " open_choice
+ case "$open_choice" in
+ [Yy])
+ for file in "${spectrogram_files[@]}"; do
+ xdg-open "$file" # Open the spectrogram images generated from the provided audio files
+ done
+ ;;
+ [Nn])
+ echo "Not opening the spectrogram(s)."
+ ;;
+ *)
+ echo "Invalid choice. Not opening the spectrogram(s)."
+ ;;
+ esac
+ fi
+}
+
+# Call the spec function with provided arguments
+spec "$@"
diff --git a/linux/home/.scripts/track-books.sh b/linux/home/.scripts/track-books.sh
new file mode 100755
index 0000000..f13add8
--- /dev/null
+++ b/linux/home/.scripts/track-books.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+# Created By: srdusr
+# Created On: Wed 25 Oct 2023 13:45:52 CAT
+# Project: Simple script to track most recent books opened, mainly for neovim usage.
+
+# Dependencies: inotify-tools
+
+books_directory="$HOME/documents/books"
+recent_books_file="$HOME/.config/nvim/tmp/recent_books.txt"
+
+inotifywait -m -e CREATE -e OPEN -r "$books_directory" |
+ while read -r path action file; do
+ if [[ $file == *.pdf || $file == *.epub ]]; then
+ echo "$path/$file" >>"$recent_books_file"
+ # Remove duplicates and overwrite the recent_books_file
+ sort -u -o "$recent_books_file" "$recent_books_file"
+ fi
+ done
diff --git a/linux/home/.scripts/win-nvim.bat b/linux/home/.scripts/win-nvim.bat
new file mode 100644
index 0000000..c99374d
--- /dev/null
+++ b/linux/home/.scripts/win-nvim.bat
@@ -0,0 +1,37 @@
+@echo off
+
+REM Install NeoVim with winget, if not already present on the system
+where nvim >nul 2>nul
+if %errorlevel% neq 0 (
+ winget install Neovim.Neovim -q
+)
+
+REM Clone my dotfiles repo
+set dotFilesRoot=%USERPROFILE%\dotfiles
+if not exist "%dotFilesRoot%\." (
+ git clone git@github.com:srdusr/dotfiles.git "%dotFilesRoot%"
+)
+
+REM Link NeoVim configuration
+set localConfiguration=%LOCALAPPDATA%\nvim
+set dotfilesConfiguration=%dotFilesRoot%\.config\nvim
+
+if not exist "%localConfiguration%\." (
+ mklink /D "%localConfiguration%" "%dotfilesConfiguration%"
+)
+
+REM Clone Packer.nvim, if not already present on the system
+set localPacker=%LOCALAPPDATA%\nvim-data\site\pack\packer\start\packer.nvim
+
+if not exist "%localPacker%\." (
+ git clone https://github.com/wbthomason/packer.nvim "%localPacker%"
+)
+
+REM Run the script by using this command in the same existing directory: win-nvim.bat
+
+@powershell -NoProfile -ExecutionPolicy Bypass -Command "iex ((new-object net.webclient).DownloadString('https://aka.ms/install-winget'))"
+iex ((new-object net.webclient).DownloadString('https://aka.ms/install-winget'))
+curl -o winget-cli.appxbundle https://aka.ms/winget-cli-appxbundle
+
+powershell Add-AppxPackage -Path "winget-cli.appxbundle"
+
diff --git a/linux/home/.scripts/win-nvim.ps1 b/linux/home/.scripts/win-nvim.ps1
new file mode 100644
index 0000000..ca67755
--- /dev/null
+++ b/linux/home/.scripts/win-nvim.ps1
@@ -0,0 +1,39 @@
+# Install NeoVim with winget, if not already present on the system
+if (!(Get-Command nvim -ErrorAction SilentlyContinue)) {
+ winget install Neovim.Neovim
+}
+
+# Clone my dotfiles repo
+$dotFilesRoot = Join-Path $HOME "dotfiles"
+
+if (!(Test-Path $dotFilesRoot -PathType Container)) {
+ git clone https://github.com/srdusr/dotfiles.git $dotFilesRoot
+}
+
+# Link NeoVim configuration
+$localConfiguration = Join-Path $env:LOCALAPPDATA "nvim"
+$dotfilesConfiguration = Join-Path $dotFilesRoot ".config" "nvim"
+
+if (!(Test-Path $localConfiguration -PathType Container)) {
+ Start-Process -FilePath "cmd.exe" -ArgumentList "/c mklink /D $localConfiguration $dotfilesConfiguration" -Verb runas
+}
+
+# Clone Packer.nvim, if not already present on the system
+$localPacker = Join-Path $env:LOCALAPPDATA "nvim-data" "site" "pack" "packer" "start" "packer.nvim"
+
+if (!(Test-Path $localPacker -PathType Container)) {
+ git clone https://github.com/wbthomason/packer.nvim $localPacker
+}
+
+# To allow script execution, run the following command in PowerShell as an administrator:
+# Set-ExecutionPolicy RemoteSigned
+# Then run the script by using this command in the same existing directory:
+# ./win-nvim.ps1
+#curl -o winget-cli.appxbundle https://aka.ms/winget-cli-appxbundle
+#powershell Add-AppxPackage -Path "winget-cli.appxbundle"
+#Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
+#use `-y` or consider: choco feature enable -n allowGlobalConfirmation
+#choco install git
+#- Refresh the environment
+#Import-Module $env:ChocolateyInstall\helpers\chocolateyProfile.psm1
+#refreshenv
diff --git a/linux/home/.vim/autoload/autoload/statusline.vim b/linux/home/.vim/autoload/autoload/statusline.vim
new file mode 100644
index 0000000..bf5f972
--- /dev/null
+++ b/linux/home/.vim/autoload/autoload/statusline.vim
@@ -0,0 +1,267 @@
+" statusline.vim
+
+if exists('g:loaded_statusline') | finish | endif
+let g:loaded_statusline = 1
+
+" --- Detect Nerd Fonts ---
+function! s:HasNerdFonts()
+ if exists('g:statusline_nerd_fonts')
+ return g:statusline_nerd_fonts
+ endif
+
+ if executable('fc-list')
+ let l:output = system('fc-list | grep -i nerd')
+ if len(split(l:output, '\n')) > 0
+ return 1
+ endif
+ endif
+
+ return 0
+endfunction
+
+let g:statusline_has_nerd_fonts = s:HasNerdFonts()
+
+" --- Color Palette ---
+let g:StslineColorGreen = '#2BBB4F'
+let g:StslineColorBlue = '#4799EB'
+let g:StslineColorViolet = '#986FEC'
+let g:StslineColorYellow = '#D7A542'
+let g:StslineColorOrange = '#EB754D'
+let g:StslineColorLight = '#C0C0C0'
+let g:StslineColorDark = '#080808'
+let g:StslineColorDark1 = '#181818'
+let g:StslineColorDark2 = 'NONE'
+let g:StslineColorDark3 = '#303030'
+
+let g:StslineBackColor = g:StslineColorDark2
+let g:StslineOnBackColor = g:StslineColorLight
+let g:StslinePriColor = g:StslineColorGreen
+let g:StslineOnPriColor = g:StslineColorDark
+let g:StslineSecColor = g:StslineColorDark3
+let g:StslineOnSecColor = g:StslineColorLight
+
+" --- Highlight Groups ---
+" Initial setup of highlight groups (will be updated by UpdateStslineColors)
+execute 'highlight StslinePriColorBG guifg=' . g:StslineOnPriColor . ' guibg=' . g:StslinePriColor
+execute 'highlight StslineSecColorFG guifg=' . g:StslineSecColor . ' guibg=' . g:StslineBackColor
+execute 'highlight StslineSecColorBG guifg=' . g:StslineColorLight . ' guibg=' . g:StslineSecColor
+execute 'highlight StslineBackColorBG guifg=' . g:StslineColorLight . ' guibg=' . g:StslineBackColor
+execute 'highlight StslineBackColorFGSecColorBG guifg=' . g:StslineBackColor . ' guibg=' . g:StslineSecColor
+execute 'highlight StslineSecColorFGBackColorBG guifg=' . g:StslineSecColor . ' guibg=' . g:StslineBackColor
+execute 'highlight StslineModColorFG guifg=' . g:StslineColorYellow . ' guibg=' . g:StslineBackColor
+execute 'highlight StslinePriColorBG_SecColorBG guifg=' . g:StslinePriColor . ' guibg=' . g:StslineSecColor
+execute 'highlight StslineModeSep guifg=' . g:StslinePriColor . ' guibg=' . g:StslineSecColor
+execute 'highlight StslineGitSep guifg=' . g:StslineSecColor . ' guibg=' . g:StslineColorDark2
+
+" --- Statusline Settings ---
+if has('nvim')
+ set laststatus=3
+else
+ set laststatus=2
+endif
+
+"set noshowmode
+"set termguicolors
+
+let space = ''
+
+" Get Statusline mode & also set primary color for that mode
+function! autoload#statusline#StslineMode() abort
+ let l:CurrentMode = mode()
+
+ if l:CurrentMode ==# 'n'
+ let g:StslinePriColor = g:StslineColorGreen
+ let b:CurrentMode = 'NORMAL '
+ elseif l:CurrentMode ==# 'i'
+ let g:StslinePriColor = g:StslineColorViolet
+ let b:CurrentMode = 'INSERT '
+ elseif l:CurrentMode ==# 'c'
+ let g:StslinePriColor = g:StslineColorYellow
+ let b:CurrentMode = 'COMMAND'
+ elseif l:CurrentMode ==# 'v'
+ let g:StslinePriColor = g:StslineColorBlue
+ let b:CurrentMode = 'VISUAL '
+ elseif l:CurrentMode ==# '\<C-v>'
+ let g:StslinePriColor = g:StslineColorBlue
+ let b:CurrentMode = 'V-BLOCK'
+ elseif l:CurrentMode ==# 'V'
+ let g:StslinePriColor = g:StslineColorBlue
+ let b:CurrentMode = 'V-LINE '
+ elseif l:CurrentMode ==# 'R'
+ let g:StslinePriColor = g:StslineColorViolet
+ let b:CurrentMode = 'REPLACE'
+ elseif l:CurrentMode ==# 's'
+ let g:StslinePriColor = g:StslineColorBlue
+ let b:CurrentMode = 'SELECT '
+ elseif l:CurrentMode ==# 't'
+ let g:StslinePriColor = g:StslineColorYellow
+ let b:CurrentMode = 'TERM '
+ elseif l:CurrentMode ==# '!'
+ let g:StslinePriColor = g:StslineColorYellow
+ let b:CurrentMode = 'SHELL '
+ else
+ let g:StslinePriColor = g:StslineColorGreen
+ endif
+
+ call autoload#statusline#UpdateStslineColors()
+
+ return b:CurrentMode
+endfunction
+
+function! autoload#statusline#UpdateStslineColors() abort
+ execute 'highlight StslinePriColorBG guifg=' . g:StslineOnPriColor . ' guibg=' . g:StslinePriColor
+ execute 'highlight StslinePriColorBGBold guifg=' . g:StslineOnPriColor . ' guibg=' . g:StslinePriColor . ' gui=bold'
+ execute 'highlight StslinePriColorFG guifg=' . g:StslinePriColor . ' guibg=' . g:StslineBackColor
+ execute 'highlight StslinePriColorFGSecColorBG guifg=' . g:StslinePriColor . ' guibg=' . g:StslineSecColor
+ execute 'highlight StslineModeSep guifg=' . g:StslinePriColor . ' guibg=' . g:StslineSecColor
+ execute 'highlight StslineGitSep guifg=' . g:StslineSecColor . ' guibg=' . g:StslineColorDark2
+ execute 'highlight StslineSecColorBG guifg=' . g:StslineColorLight . ' guibg=' . g:StslineSecColor
+ execute 'highlight StslineBackColorBG guifg=' . g:StslineColorLight . ' guibg=' . g:StslineBackColor
+ execute 'highlight StslineBackColorFGSecColorBG guifg=' . g:StslineBackColor . ' guibg=' . g:StslineSecColor
+ execute 'highlight StslineSecColorFGBackColorBG guifg=' . g:StslineSecColor . ' guibg=' . g:StslineBackColor
+ execute 'highlight StslineModColorFG guifg=' . g:StslineColorYellow . ' guibg=' . g:StslineBackColor
+ execute 'highlight StslinePriColorBG_SecColorBG guifg=' . g:StslinePriColor . ' guibg=' . g:StslineSecColor
+ execute 'highlight StslineSecColorFG guifg=' . g:StslineSecColor . ' guibg=' . g:StslineBackColor
+endfunction
+
+function! autoload#statusline#GetGitBranch() abort
+ let b:GitBranch = ''
+ try
+ let l:dir = expand('%:p:h')
+ let l:gitrevparse = system("git -C ".l:dir." rev-parse --abbrev-ref HEAD")
+ if !v:shell_error
+ let icon = g:statusline_has_nerd_fonts ? '  ' : ' [git] '
+ let b:GitBranch = icon . substitute(l:gitrevparse, '\n', '', 'g') . ' '
+ endif
+ catch
+ endtry
+endfunction
+
+function! autoload#statusline#GetFileType() abort
+ if !g:statusline_has_nerd_fonts
+ let b:FiletypeIcon = ''
+ return
+ endif
+ if &filetype ==# 'typescript' | let b:FiletypeIcon = ' '
+ elseif &filetype ==# 'html' | let b:FiletypeIcon = ' '
+ elseif &filetype ==# 'scss' | let b:FiletypeIcon = ' '
+ elseif &filetype ==# 'css' | let b:FiletypeIcon = ' '
+ elseif &filetype ==# 'javascript' | let b:FiletypeIcon = ' '
+ elseif &filetype ==# 'javascriptreact' | let b:FiletypeIcon = ' '
+ elseif &filetype ==# 'markdown' | let b:FiletypeIcon = ' '
+ elseif &filetype ==# 'sh' || &filetype ==# 'zsh' | let b:FiletypeIcon = ' '
+ elseif &filetype ==# 'vim' | let b:FiletypeIcon = ' '
+ elseif &filetype ==# 'rust' | let b:FiletypeIcon = ' '
+ elseif &filetype ==# 'ruby' | let b:FiletypeIcon = ' '
+ elseif &filetype ==# 'cpp' | let b:FiletypeIcon = ' '
+ elseif &filetype ==# 'c' | let b:FiletypeIcon = ' '
+ elseif &filetype ==# 'go' | let b:FiletypeIcon = ' '
+ elseif &filetype ==# 'lua' | let b:FiletypeIcon = ' '
+ elseif &filetype ==# 'haskell' | let b:FiletypeIcon = ' '
+ else | let b:FiletypeIcon = ' '
+ endif
+endfunction
+
+function! autoload#statusline#ActivateStatusline() abort
+ call autoload#statusline#GetFileType()
+ call autoload#statusline#GetGitBranch() " Ensure git branch is updated
+
+ let current_mode_str = autoload#statusline#StslineMode()
+ call autoload#statusline#UpdateStslineColors()
+
+ let readonly_icon = g:statusline_has_nerd_fonts ? ' ' : '[RO] '
+ let modified_icon = g:statusline_has_nerd_fonts ? ' ' : '[+] '
+ let git_sep = g:statusline_has_nerd_fonts ? '' : ' '
+ let file_sep1 = g:statusline_has_nerd_fonts ? ' ' : ' '
+ let file_sep2 = g:statusline_has_nerd_fonts ? '' : ''
+
+ " Get dynamic parts as simple strings
+ let git_status_str = get(b:, "coc_git_status", get(b:, "GitBranch", ""))
+ let git_blame_str = get(b:, "coc_git_blame", "")
+ let filetype_icon_str = get(b:, "FiletypeIcon", "")
+ let file_encoding_str = ''
+ if &fenc != "utf-8"
+ let file_encoding_str = &fenc . ' '
+ endif
+
+ " Build the statusline as a static string
+ let l:statusline = ''
+
+ let l:statusline .= '%#StslinePriColorBG# ' . current_mode_str . ''
+ let l:statusline .= '%#StslineModeSep#' . git_sep
+ let l:statusline .= '%#StslineSecColorBG#' . git_status_str . git_blame_str
+ let l:statusline .= '%#StslineGitSep#' . git_sep
+
+ " File info (Readonly, Modified, Filename)
+ let l:statusline .= '%#StslinePriColorFG#'
+ if &readonly
+ let l:statusline .= readonly_icon
+ endif
+ let l:statusline .= ' %F '
+ if &modified
+ let l:statusline .= modified_icon
+ endif
+
+ " Right align everything after this
+ let l:statusline .= '%='
+
+ " Right side (Filetype, Encoding, Position)
+ let l:statusline .= '%#StslinePriColorFG# ' . filetype_icon_str . '%y'
+ let l:statusline .= '%#StslineSecColorFG#' . file_sep1
+ "let l:statusline .= '%#StslineSecColorBG# ' . file_encoding_str
+ let l:statusline .= '%#StslinePriColorFGSecColorBG#' . file_sep2
+ let l:statusline .= '%#StslinePriColorBG# %p%% %#StslinePriColorBGBold#%l%#StslinePriColorBG#/%L :%c '
+ let l:statusline .= '%#StslineBackColorBG#'
+
+ " Set the statusline for the current buffer
+ let &l:statusline = l:statusline
+endfunction
+
+function! autoload#statusline#DeactivateStatusline() abort
+ let git_sep = g:statusline_has_nerd_fonts ? '' : ''
+ let readonly_icon = g:statusline_has_nerd_fonts ? ' ' : '[RO] '
+ let modified_icon = g:statusline_has_nerd_fonts ? ' ' : '[+] '
+
+ " NOTE: This DeactivateStatusline function still uses %{} for dynamic parts.
+ " If you encounter general E518 or other issues related to %{} expressions,
+ " you will need to refactor this function to build a static string
+ " similar to how ActivateStatusline now does it.
+ if !exists("b:GitBranch") || b:GitBranch == ''
+ let statusline =
+ \ '%#StslineSecColorBG# INACTIVE ' .
+ \ '%{get(b:,"coc_git_statusline",b:GitBranch)}%{get(b:,"coc_git_blame","")}' .
+ \ '%#StslineBackColorFGSecColorBG#' . git_sep .
+ \ '%#StslineBackColorBG# %{&readonly?"' . readonly_icon . '":""}%F ' .
+ \ '%#StslineModColorFG#%{&modified?"' . modified_icon . '":""}' .
+ \ '%=%#StslineBackColorBG# %{b:FiletypeIcon}%{&filetype}' .
+ \ '%#StslineSecColorFGBackColorBG# | %p%% %l/%L :%c'
+ else
+ let statusline =
+ \ '%#StslineSecColorBG# %{get(b:,"coc_git_statusline",b:GitBranch)}%{get(b:,"coc_git_blame","")}' .
+ \ '%#StslineBackColorFGSecColorBG#' . git_sep .
+ \ '%#StslineBackColorBG# %{&readonly?"' . readonly_icon . '":""}%F ' .
+ \ '%#StslineModColorFG#%{&modified?"' . modified_icon . '":""}' .
+ \ '%=%#StslineBackColorBG# %{b:FiletypeIcon}%{&filetype}' .
+ \ '%#StslineSecColorFGBackColorBG# | %p%% %l/%L :%c'
+ endif
+
+ execute 'setlocal statusline=' . substitute(statusline, '"', '\\"', 'g')
+endfunction
+
+augroup StatuslineGit
+ autocmd!
+ autocmd BufEnter * call autoload#statusline#GetGitBranch()
+augroup END
+
+augroup SetStsline
+ autocmd!
+ autocmd BufEnter,WinEnter * call autoload#statusline#ActivateStatusline()
+ autocmd ModeChanged * call autoload#statusline#ActivateStatusline()
+augroup END
+
+augroup StatuslineAutoReload
+ autocmd!
+ autocmd BufWritePost statusline.vim source <afile> | call autoload#statusline#ActivateStatusline()
+augroup END
+
+"call autoload#statusline#ActivateStatusline()
diff --git a/linux/home/.vim/autoload/statusline.vim b/linux/home/.vim/autoload/statusline.vim
new file mode 100644
index 0000000..bf5f972
--- /dev/null
+++ b/linux/home/.vim/autoload/statusline.vim
@@ -0,0 +1,267 @@
+" statusline.vim
+
+if exists('g:loaded_statusline') | finish | endif
+let g:loaded_statusline = 1
+
+" --- Detect Nerd Fonts ---
+function! s:HasNerdFonts()
+ if exists('g:statusline_nerd_fonts')
+ return g:statusline_nerd_fonts
+ endif
+
+ if executable('fc-list')
+ let l:output = system('fc-list | grep -i nerd')
+ if len(split(l:output, '\n')) > 0
+ return 1
+ endif
+ endif
+
+ return 0
+endfunction
+
+let g:statusline_has_nerd_fonts = s:HasNerdFonts()
+
+" --- Color Palette ---
+let g:StslineColorGreen = '#2BBB4F'
+let g:StslineColorBlue = '#4799EB'
+let g:StslineColorViolet = '#986FEC'
+let g:StslineColorYellow = '#D7A542'
+let g:StslineColorOrange = '#EB754D'
+let g:StslineColorLight = '#C0C0C0'
+let g:StslineColorDark = '#080808'
+let g:StslineColorDark1 = '#181818'
+let g:StslineColorDark2 = 'NONE'
+let g:StslineColorDark3 = '#303030'
+
+let g:StslineBackColor = g:StslineColorDark2
+let g:StslineOnBackColor = g:StslineColorLight
+let g:StslinePriColor = g:StslineColorGreen
+let g:StslineOnPriColor = g:StslineColorDark
+let g:StslineSecColor = g:StslineColorDark3
+let g:StslineOnSecColor = g:StslineColorLight
+
+" --- Highlight Groups ---
+" Initial setup of highlight groups (will be updated by UpdateStslineColors)
+execute 'highlight StslinePriColorBG guifg=' . g:StslineOnPriColor . ' guibg=' . g:StslinePriColor
+execute 'highlight StslineSecColorFG guifg=' . g:StslineSecColor . ' guibg=' . g:StslineBackColor
+execute 'highlight StslineSecColorBG guifg=' . g:StslineColorLight . ' guibg=' . g:StslineSecColor
+execute 'highlight StslineBackColorBG guifg=' . g:StslineColorLight . ' guibg=' . g:StslineBackColor
+execute 'highlight StslineBackColorFGSecColorBG guifg=' . g:StslineBackColor . ' guibg=' . g:StslineSecColor
+execute 'highlight StslineSecColorFGBackColorBG guifg=' . g:StslineSecColor . ' guibg=' . g:StslineBackColor
+execute 'highlight StslineModColorFG guifg=' . g:StslineColorYellow . ' guibg=' . g:StslineBackColor
+execute 'highlight StslinePriColorBG_SecColorBG guifg=' . g:StslinePriColor . ' guibg=' . g:StslineSecColor
+execute 'highlight StslineModeSep guifg=' . g:StslinePriColor . ' guibg=' . g:StslineSecColor
+execute 'highlight StslineGitSep guifg=' . g:StslineSecColor . ' guibg=' . g:StslineColorDark2
+
+" --- Statusline Settings ---
+if has('nvim')
+ set laststatus=3
+else
+ set laststatus=2
+endif
+
+"set noshowmode
+"set termguicolors
+
+let space = ''
+
+" Get Statusline mode & also set primary color for that mode
+function! autoload#statusline#StslineMode() abort
+ let l:CurrentMode = mode()
+
+ if l:CurrentMode ==# 'n'
+ let g:StslinePriColor = g:StslineColorGreen
+ let b:CurrentMode = 'NORMAL '
+ elseif l:CurrentMode ==# 'i'
+ let g:StslinePriColor = g:StslineColorViolet
+ let b:CurrentMode = 'INSERT '
+ elseif l:CurrentMode ==# 'c'
+ let g:StslinePriColor = g:StslineColorYellow
+ let b:CurrentMode = 'COMMAND'
+ elseif l:CurrentMode ==# 'v'
+ let g:StslinePriColor = g:StslineColorBlue
+ let b:CurrentMode = 'VISUAL '
+ elseif l:CurrentMode ==# '\<C-v>'
+ let g:StslinePriColor = g:StslineColorBlue
+ let b:CurrentMode = 'V-BLOCK'
+ elseif l:CurrentMode ==# 'V'
+ let g:StslinePriColor = g:StslineColorBlue
+ let b:CurrentMode = 'V-LINE '
+ elseif l:CurrentMode ==# 'R'
+ let g:StslinePriColor = g:StslineColorViolet
+ let b:CurrentMode = 'REPLACE'
+ elseif l:CurrentMode ==# 's'
+ let g:StslinePriColor = g:StslineColorBlue
+ let b:CurrentMode = 'SELECT '
+ elseif l:CurrentMode ==# 't'
+ let g:StslinePriColor = g:StslineColorYellow
+ let b:CurrentMode = 'TERM '
+ elseif l:CurrentMode ==# '!'
+ let g:StslinePriColor = g:StslineColorYellow
+ let b:CurrentMode = 'SHELL '
+ else
+ let g:StslinePriColor = g:StslineColorGreen
+ endif
+
+ call autoload#statusline#UpdateStslineColors()
+
+ return b:CurrentMode
+endfunction
+
+function! autoload#statusline#UpdateStslineColors() abort
+ execute 'highlight StslinePriColorBG guifg=' . g:StslineOnPriColor . ' guibg=' . g:StslinePriColor
+ execute 'highlight StslinePriColorBGBold guifg=' . g:StslineOnPriColor . ' guibg=' . g:StslinePriColor . ' gui=bold'
+ execute 'highlight StslinePriColorFG guifg=' . g:StslinePriColor . ' guibg=' . g:StslineBackColor
+ execute 'highlight StslinePriColorFGSecColorBG guifg=' . g:StslinePriColor . ' guibg=' . g:StslineSecColor
+ execute 'highlight StslineModeSep guifg=' . g:StslinePriColor . ' guibg=' . g:StslineSecColor
+ execute 'highlight StslineGitSep guifg=' . g:StslineSecColor . ' guibg=' . g:StslineColorDark2
+ execute 'highlight StslineSecColorBG guifg=' . g:StslineColorLight . ' guibg=' . g:StslineSecColor
+ execute 'highlight StslineBackColorBG guifg=' . g:StslineColorLight . ' guibg=' . g:StslineBackColor
+ execute 'highlight StslineBackColorFGSecColorBG guifg=' . g:StslineBackColor . ' guibg=' . g:StslineSecColor
+ execute 'highlight StslineSecColorFGBackColorBG guifg=' . g:StslineSecColor . ' guibg=' . g:StslineBackColor
+ execute 'highlight StslineModColorFG guifg=' . g:StslineColorYellow . ' guibg=' . g:StslineBackColor
+ execute 'highlight StslinePriColorBG_SecColorBG guifg=' . g:StslinePriColor . ' guibg=' . g:StslineSecColor
+ execute 'highlight StslineSecColorFG guifg=' . g:StslineSecColor . ' guibg=' . g:StslineBackColor
+endfunction
+
+function! autoload#statusline#GetGitBranch() abort
+ let b:GitBranch = ''
+ try
+ let l:dir = expand('%:p:h')
+ let l:gitrevparse = system("git -C ".l:dir." rev-parse --abbrev-ref HEAD")
+ if !v:shell_error
+ let icon = g:statusline_has_nerd_fonts ? '  ' : ' [git] '
+ let b:GitBranch = icon . substitute(l:gitrevparse, '\n', '', 'g') . ' '
+ endif
+ catch
+ endtry
+endfunction
+
+function! autoload#statusline#GetFileType() abort
+ if !g:statusline_has_nerd_fonts
+ let b:FiletypeIcon = ''
+ return
+ endif
+ if &filetype ==# 'typescript' | let b:FiletypeIcon = ' '
+ elseif &filetype ==# 'html' | let b:FiletypeIcon = ' '
+ elseif &filetype ==# 'scss' | let b:FiletypeIcon = ' '
+ elseif &filetype ==# 'css' | let b:FiletypeIcon = ' '
+ elseif &filetype ==# 'javascript' | let b:FiletypeIcon = ' '
+ elseif &filetype ==# 'javascriptreact' | let b:FiletypeIcon = ' '
+ elseif &filetype ==# 'markdown' | let b:FiletypeIcon = ' '
+ elseif &filetype ==# 'sh' || &filetype ==# 'zsh' | let b:FiletypeIcon = ' '
+ elseif &filetype ==# 'vim' | let b:FiletypeIcon = ' '
+ elseif &filetype ==# 'rust' | let b:FiletypeIcon = ' '
+ elseif &filetype ==# 'ruby' | let b:FiletypeIcon = ' '
+ elseif &filetype ==# 'cpp' | let b:FiletypeIcon = ' '
+ elseif &filetype ==# 'c' | let b:FiletypeIcon = ' '
+ elseif &filetype ==# 'go' | let b:FiletypeIcon = ' '
+ elseif &filetype ==# 'lua' | let b:FiletypeIcon = ' '
+ elseif &filetype ==# 'haskell' | let b:FiletypeIcon = ' '
+ else | let b:FiletypeIcon = ' '
+ endif
+endfunction
+
+function! autoload#statusline#ActivateStatusline() abort
+ call autoload#statusline#GetFileType()
+ call autoload#statusline#GetGitBranch() " Ensure git branch is updated
+
+ let current_mode_str = autoload#statusline#StslineMode()
+ call autoload#statusline#UpdateStslineColors()
+
+ let readonly_icon = g:statusline_has_nerd_fonts ? ' ' : '[RO] '
+ let modified_icon = g:statusline_has_nerd_fonts ? ' ' : '[+] '
+ let git_sep = g:statusline_has_nerd_fonts ? '' : ' '
+ let file_sep1 = g:statusline_has_nerd_fonts ? ' ' : ' '
+ let file_sep2 = g:statusline_has_nerd_fonts ? '' : ''
+
+ " Get dynamic parts as simple strings
+ let git_status_str = get(b:, "coc_git_status", get(b:, "GitBranch", ""))
+ let git_blame_str = get(b:, "coc_git_blame", "")
+ let filetype_icon_str = get(b:, "FiletypeIcon", "")
+ let file_encoding_str = ''
+ if &fenc != "utf-8"
+ let file_encoding_str = &fenc . ' '
+ endif
+
+ " Build the statusline as a static string
+ let l:statusline = ''
+
+ let l:statusline .= '%#StslinePriColorBG# ' . current_mode_str . ''
+ let l:statusline .= '%#StslineModeSep#' . git_sep
+ let l:statusline .= '%#StslineSecColorBG#' . git_status_str . git_blame_str
+ let l:statusline .= '%#StslineGitSep#' . git_sep
+
+ " File info (Readonly, Modified, Filename)
+ let l:statusline .= '%#StslinePriColorFG#'
+ if &readonly
+ let l:statusline .= readonly_icon
+ endif
+ let l:statusline .= ' %F '
+ if &modified
+ let l:statusline .= modified_icon
+ endif
+
+ " Right align everything after this
+ let l:statusline .= '%='
+
+ " Right side (Filetype, Encoding, Position)
+ let l:statusline .= '%#StslinePriColorFG# ' . filetype_icon_str . '%y'
+ let l:statusline .= '%#StslineSecColorFG#' . file_sep1
+ "let l:statusline .= '%#StslineSecColorBG# ' . file_encoding_str
+ let l:statusline .= '%#StslinePriColorFGSecColorBG#' . file_sep2
+ let l:statusline .= '%#StslinePriColorBG# %p%% %#StslinePriColorBGBold#%l%#StslinePriColorBG#/%L :%c '
+ let l:statusline .= '%#StslineBackColorBG#'
+
+ " Set the statusline for the current buffer
+ let &l:statusline = l:statusline
+endfunction
+
+function! autoload#statusline#DeactivateStatusline() abort
+ let git_sep = g:statusline_has_nerd_fonts ? '' : ''
+ let readonly_icon = g:statusline_has_nerd_fonts ? ' ' : '[RO] '
+ let modified_icon = g:statusline_has_nerd_fonts ? ' ' : '[+] '
+
+ " NOTE: This DeactivateStatusline function still uses %{} for dynamic parts.
+ " If you encounter general E518 or other issues related to %{} expressions,
+ " you will need to refactor this function to build a static string
+ " similar to how ActivateStatusline now does it.
+ if !exists("b:GitBranch") || b:GitBranch == ''
+ let statusline =
+ \ '%#StslineSecColorBG# INACTIVE ' .
+ \ '%{get(b:,"coc_git_statusline",b:GitBranch)}%{get(b:,"coc_git_blame","")}' .
+ \ '%#StslineBackColorFGSecColorBG#' . git_sep .
+ \ '%#StslineBackColorBG# %{&readonly?"' . readonly_icon . '":""}%F ' .
+ \ '%#StslineModColorFG#%{&modified?"' . modified_icon . '":""}' .
+ \ '%=%#StslineBackColorBG# %{b:FiletypeIcon}%{&filetype}' .
+ \ '%#StslineSecColorFGBackColorBG# | %p%% %l/%L :%c'
+ else
+ let statusline =
+ \ '%#StslineSecColorBG# %{get(b:,"coc_git_statusline",b:GitBranch)}%{get(b:,"coc_git_blame","")}' .
+ \ '%#StslineBackColorFGSecColorBG#' . git_sep .
+ \ '%#StslineBackColorBG# %{&readonly?"' . readonly_icon . '":""}%F ' .
+ \ '%#StslineModColorFG#%{&modified?"' . modified_icon . '":""}' .
+ \ '%=%#StslineBackColorBG# %{b:FiletypeIcon}%{&filetype}' .
+ \ '%#StslineSecColorFGBackColorBG# | %p%% %l/%L :%c'
+ endif
+
+ execute 'setlocal statusline=' . substitute(statusline, '"', '\\"', 'g')
+endfunction
+
+augroup StatuslineGit
+ autocmd!
+ autocmd BufEnter * call autoload#statusline#GetGitBranch()
+augroup END
+
+augroup SetStsline
+ autocmd!
+ autocmd BufEnter,WinEnter * call autoload#statusline#ActivateStatusline()
+ autocmd ModeChanged * call autoload#statusline#ActivateStatusline()
+augroup END
+
+augroup StatuslineAutoReload
+ autocmd!
+ autocmd BufWritePost statusline.vim source <afile> | call autoload#statusline#ActivateStatusline()
+augroup END
+
+"call autoload#statusline#ActivateStatusline()
diff --git a/linux/home/.vim/colors/colors/colorscheme.vim b/linux/home/.vim/colors/colors/colorscheme.vim
new file mode 100644
index 0000000..ce0526e
--- /dev/null
+++ b/linux/home/.vim/colors/colors/colorscheme.vim
@@ -0,0 +1,247 @@
+" Vim Colorscheme
+" Name: cherryblossom.vim
+" Author: Luo Boming
+" Version: 0.3
+" License: The MIT Licence
+
+"{{{ Pre-setting
+let g:colors_name = expand('<sfile>:t:r')
+
+"hi clear
+"if exists("syntax_on")
+" syntax reset
+"endif
+
+if ! exists("g:terminal_italics")
+ let g:terminal_italics = 0
+endif
+
+"if ! exists("g:switch_statusline_bg_in_insert")
+" let g:switch_statusline_bg_in_insert = 0
+"endif
+
+if ! exists("g:spell_undercurl")
+ let g:spell_undercurl = 0
+endif
+
+"}}}
+"{{{ Color Palette
+" Color Entity
+let s:black = { "gui": "#171717", "cterm": "16" }
+let s:white = { "gui": "#EAE8E7", "cterm": "231" }
+
+let s:gray = { "gui": "#3a3f52", "cterm": "247" }
+
+let s:green = { "gui": "#30B536", "cterm": "34" }
+let s:pink = { "gui": "#D36DD3", "cterm": "170" }
+let s:orange = { "gui": "#FC923F", "cterm": "208" }
+let s:purple = { "gui": "#B586E7", "cterm": "141" }
+let s:light_cyan = { "gui": "#D7FFFF", "cterm": "195" }
+let s:dark_cyan = { "gui": "#00AF87", "cterm": "36" }
+let s:ultramarine = { "gui": "#229EC0", "cterm": "38" }
+let s:skyblue = { "gui": "#9BE7F8", "cterm": "195" }
+
+let s:white_pink = { "gui": "#FEF7FE", "cterm": "231" }
+let s:white_pink_deep = { "gui": "#FEF0FE", "cterm": "255" }
+let s:black_green = { "gui": "#053703", "cterm": "235" }
+let s:black_green_bright = { "gui": "#074005", "cterm": "239" }
+let s:middle_gray = { "gui": "#8a8a8a", "cterm": "245" }
+
+let s:light_gray = { "gui": "#E1DCDA", "cterm": "253" }
+let s:light_green = { "gui": "#B7EFA5", "cterm": "157" }
+let s:light_pink = { "gui": "#FEDCFE", "cterm": "225" }
+let s:light_yellow = { "gui": "#EDE682", "cterm": "228" }
+let s:light_red = { "gui": "#EB5A7C", "cterm": "204" }
+
+let s:dark_gray = { "gui": "#4D4A48", "cterm": "241" }
+let s:dark_green = { "gui": "#09570A", "cterm": "22" }
+let s:dark_yellow = { "gui": "#BC922B", "cterm": "3" }
+let s:dark_pink = { "gui": "#B365A2", "cterm": "133" }
+let s:dark_red = { "gui": "#D9372D", "cterm": "160" }
+let s:NONE = { "gui": "NONE", "cterm": "NONE" }
+
+" Color Alias
+if &background == "light"
+ let s:norm = s:black
+ let s:bg = s:NONE
+ let s:bg_subtle = s:white_pink_deep
+ let s:gray_fg = s:middle_gray
+ let s:green_fg = s:green
+ let s:yellow_fg = s:dark_yellow
+ let s:pink_fg = s:dark_pink
+ let s:cyan_fg = s:dark_cyan
+ let s:blue_fg = s:ultramarine
+ let s:red_fg = s:dark_red
+ let s:gray_bg = s:light_gray
+ let s:green_bg = s:light_green
+ let s:yellow_bg = s:light_yellow
+ let s:pink_bg = s:light_pink
+ let s:cyan_bg = s:light_cyan
+ let s:blue_bg = s:skyblue
+ let s:red_bg = s:light_red
+endif
+
+if &background == "dark"
+ let s:norm = s:white
+ let s:bg = s:NONE
+ let s:bg_subtle = s:gray
+ let s:gray_fg = s:middle_gray
+ let s:green_fg = s:light_green
+ let s:yellow_fg = s:light_yellow
+ let s:pink_fg = s:light_pink
+ let s:cyan_fg = s:light_cyan
+ let s:blue_fg = s:skyblue
+ let s:red_fg = s:light_red
+ let s:gray_bg = s:dark_gray
+ let s:green_bg = s:green
+ let s:yellow_bg = s:dark_yellow
+ let s:pink_bg = s:pink
+ let s:cyan_bg = s:dark_cyan
+ let s:blue_bg = s:ultramarine
+ let s:red_bg = s:dark_red
+endif
+"}}}
+"{{{ Highlight Function
+" shamelessly stolen from pencil: https://github.com/reedes/vim-colors-pencil
+function! s:hi(group, style)
+ if g:terminal_italics == 0
+ if has_key(a:style, "cterm") && a:style["cterm"] == "italic"
+ unlet a:style.cterm
+ endif
+ if has_key(a:style, "term") && a:style["term"] == "italic"
+ unlet a:style.term
+ endif
+ endif
+ execute "highlight" a:group
+ \ "guifg=" (has_key(a:style, "fg") ? a:style.fg.gui : "NONE")
+ \ "guibg=" (has_key(a:style, "bg") ? a:style.bg.gui : "NONE")
+ \ "guisp=" (has_key(a:style, "sp") ? a:style.sp.gui : "NONE")
+ \ "gui=" (has_key(a:style, "gui") ? a:style.gui : "NONE")
+ \ "ctermfg=" (has_key(a:style, "fg") ? a:style.fg.cterm : "NONE")
+ \ "ctermbg=" (has_key(a:style, "bg") ? a:style.bg.cterm : "NONE")
+ \ "cterm=" (has_key(a:style, "cterm") ? a:style.cterm : "NONE")
+ \ "term=" (has_key(a:style, "term") ? a:style.term : "NONE")
+endfunction
+
+if g:spell_undercurl == 1
+ let s:attr_un = 'undercurl'
+else
+ let s:attr_un = 'underline'
+endif
+
+"}}}
+"{{{ Common Highlighting
+call s:hi("Normal", {"fg": s:norm, "bg": s:bg})
+call s:hi("Cursor", {})
+call s:hi("Comment", {"fg": s:gray_fg, "gui": "italic", "cterm": "italic", "term": "italic"})
+
+call s:hi("Constant", {"fg": s:pink_fg})
+hi! link String Constant
+hi! link Character Constant
+hi! link Number Constant
+hi! link Boolean Constant
+hi! link Float Constant
+
+call s:hi("Identifier", {"fg": s:red_fg})
+hi! link Function Identifier
+
+call s:hi("Statement", {"fg": s:green_fg})
+hi! link Conditonal Statement
+hi! link Repeat Statement
+hi! link Label Statement
+hi! link Operator Statement
+hi! link Keyword Statement
+hi! link Exception Statement
+
+call s:hi("PreProc", {"fg": s:blue_fg})
+hi! link Include PreProc
+hi! link Define PreProc
+hi! link Macro PreProc
+hi! link PreCondit PreProc
+
+call s:hi("Type", {"fg": s:yellow_fg})
+hi! link StorageClass Type
+hi! link Structure Type
+hi! link Typedef Type
+
+call s:hi("Special", {"fg": s:orange})
+hi! link SpecialChar Special
+hi! link Tag Special
+hi! link Delimiter Special
+hi! link SpecialComment Special
+hi! link Debug Special
+
+call s:hi("Underlined", {"gui": "underline", "cterm": "underline"})
+call s:hi("Ignore", {"fg": s:bg_subtle})
+call s:hi("Error", {"fg": s:white, "bg": s:red_fg , "gui": "bold", "cterm": "bold"})
+call s:hi("Todo", {"bg": s:yellow_bg, "gui": "bold", "cterm": "bold"})
+
+"}}}
+"{{{ Semi-Common Highlighting
+call s:hi("SpecialKey", {"fg": s:purple, "gui": "bold", "cterm": "bold", "term": "bold"})
+call s:hi("NonText", {"fg": s:cyan_bg, "gui": "bold", "cterm": "bold", "term": "bold"})
+call s:hi("Directory", {"fg": s:blue_fg, "gui": "bold", "cterm": "bold", "term": "bold"})
+call s:hi("ErrorMsg", {"fg": s:red_fg, "gui": "bold", "cterm": "bold", "term": "bold"})
+call s:hi("IncSearch", {"gui": "reverse", "cterm": "reverse", "term": "reverse"})
+call s:hi("Search", {"fg": s:norm, "bg": s:pink_bg})
+call s:hi("MoreMsg", {"fg": s:pink_fg, "gui": "bold", "cterm": "bold", "term": "bold"})
+call s:hi("ModeMsg", {"fg": s:pink_fg, "gui": "bold", "cterm": "bold", "term": "bold"})
+call s:hi("LineNr", {"fg": s:gray})
+call s:hi("CursorLineNr", {"fg": s:pink_fg, "gui": "bold", "cterm": "bold", "term": "bold"})
+call s:hi("Question", {"fg": s:purple, "gui": "bold", "cterm": "bold", "term": "bold"})
+"call s:hi("StatusLine", {"fg": s:norm, "bg": s:green_bg, "gui": "bold", "cterm": "bold", "term": "bold"})
+"call s:hi("StatusLineNC", {"fg": s:norm, "bg": s:gray_bg})
+call s:hi("Conceal", {"fg": s:yellow_fg})
+call s:hi("VertSplit", {"gui": "reverse", "cterm": "reverse", "term": "reverse"})
+call s:hi("Title", {"fg": s:pink_fg, "gui": "bold", "cterm": "bold", "term": "bold"})
+call s:hi("Visual", {"gui": "reverse", "cterm": "reverse", "term": "reverse"})
+call s:hi("VisualNOS", {"gui": "bold,underline", "cterm": "bold,underline", "term": "bold,underline"})
+call s:hi("WarningMsg", {"fg": s:orange, "gui": "bold", "cterm": "bold", "term": "bold"})
+call s:hi("WildMenu", {"fg": s:norm, "bg": s:blue_bg})
+call s:hi("Folded", {"fg": s:green_fg, "bg": s:gray_bg})
+call s:hi("FoldColumn", {"fg": s:green_fg, "bg": s:gray_bg})
+call s:hi("DiffAdd", {"bg": s:green_bg})
+call s:hi("DiffChange", {"bg": s:yellow_bg})
+call s:hi("DiffDelete", {"bg": s:red_bg})
+call s:hi("DiffText", {"bg": s:blue_bg, "gui": "bold", "cterm": "bold", "term": "bold"})
+call s:hi("SignColumn", {"fg": s:green_fg, "bg": s:gray})
+if has("gui_running")
+ call s:hi("SpellBad", {"gui": s:attr_un, "sp": s:red_bg})
+ call s:hi("SpellCap", {"gui": s:attr_un, "sp": s:yellow_bg})
+ call s:hi("SpellRare", {"gui": s:attr_un, "sp": s:blue_bg})
+ call s:hi("SpellLocal", {"gui": s:attr_un, "sp": s:green_bg})
+else
+ call s:hi("SpellBad", {"cterm": s:attr_un, "fg": s:red_fg})
+ call s:hi("SpellCap", {"cterm": s:attr_un, "fg": s:yellow_fg})
+ call s:hi("SpellRare", {"cterm": s:attr_un, "fg": s:blue_fg})
+ call s:hi("SpellLocal", {"cterm": s:attr_un, "fg": s:green_fg})
+endif
+call s:hi("Pmenu", {"bg": s:gray_bg})
+call s:hi("PmenuSel", {"bg": s:pink_bg})
+call s:hi("PmenuSbar", {"bg": s:gray_bg})
+call s:hi("PmenuThumb", {"bg": s:gray_bg})
+call s:hi("TabLine", {"bg": s:bg_subtle})
+call s:hi("TabLineSel", {"bg": s:pink_bg})
+call s:hi("TabLineFill", {"bg": s:bg_subtle})
+call s:hi("CursorColumn", {"bg": s:yellow_fg})
+call s:hi("CursorLine", {"bg": s:bg_subtle})
+call s:hi("ColorColumn", {"bg": s:bg_subtle})
+call s:hi("MatchParen", {"fg": s:pink_fg, "gui": "underline", "cterm": "underline"})
+call s:hi("qfLineNr", {"fg": s:gray})
+
+"}}}
+""{{{ Switching StatusLine bg
+"function! s:changebg(group, color)
+" execute "highlight" a:group "guibg=" a:color.gui "ctermbg=" a:color.cterm
+"endfunction
+"
+"if g:switch_statusline_bg_in_insert == 1
+" "" Change Color when entering Insert Mode
+" autocmd InsertEnter * call s:changebg("StatusLine", s:pink_bg)
+" "" Revert Color to default when leaving Insert Mode
+" autocmd InsertLeave * call s:changebg("StatusLine", s:green_bg)
+"endif
+
+"}}}
+" vim: set foldmethod=marker:
+
diff --git a/linux/home/.vim/colors/colors/default.vim b/linux/home/.vim/colors/colors/default.vim
new file mode 100644
index 0000000..ebc66e8
--- /dev/null
+++ b/linux/home/.vim/colors/colors/default.vim
@@ -0,0 +1,175 @@
+"{{{ Pre-setting
+let g:colors_name = expand('<sfile>:t:r')
+
+if ! exists("g:terminal_italics")
+ let g:terminal_italics = 0
+endif
+
+if ! exists("g:spell_undercurl")
+ let g:spell_undercurl = 0
+endif
+"}}}
+
+"{{{ Color Palette (updated to match official Vim default colors)
+" Note: Hex colors chosen to reflect official Vim default colorscheme
+
+let s:black = { "gui": "#171717", "cterm": "16" }
+let s:white = { "gui": "#EAE8E7", "cterm": "231" }
+let s:gray = { "gui": "#808080", "cterm": "244" }
+
+" Reds
+let s:red_fg = { "gui": "#FFFFFF", "cterm": "231" } " White fg on red bg for errors
+let s:red_bg = { "gui": "#A40000", "cterm": "52" } " DarkRed bg (ErrorMsg bg)
+
+" Blues and Cyan
+let s:blue_fg = { "gui": "#6A5ACD", "cterm": "60" } " SlateBlue
+let s:dark_cyan = { "gui": "#008B8B", "cterm": "36" } " DarkCyan
+let s:cyan_bg = { "gui": "#00CED1", "cterm": "38" } " DarkTurquoise
+
+" Greens
+let s:green_fg = { "gui": "#008000", "cterm": "22" } " DarkGreen
+let s:green_bg = { "gui": "#90EE90", "cterm": "120" } " LightGreen
+
+" Yellows and Oranges
+let s:yellow_fg = { "gui": "#A52A2A", "cterm": "94" } " Brown (used in Vim default)
+let s:yellow_bg = { "gui": "#FFFF00", "cterm": "226" } " Yellow bg
+
+let s:orange = { "gui": "#FFA500", "cterm": "214" } " Orange
+
+" Purples
+let s:purple = { "gui": "#6A0DAD", "cterm": "90" } " DarkMagenta
+
+" Grays
+let s:light_gray = { "gui": "#D3D3D3", "cterm": "252" }
+let s:dark_gray = { "gui": "#4D4D4D", "cterm": "240" }
+
+" No color
+let s:NONE = { "gui": "NONE", "cterm": "NONE" }
+
+" Alias for Normal fg and background depending on background setting
+if &background == "light"
+ let s:norm = s:black
+ let s:bg = s:NONE
+ let s:bg_subtle = s:light_gray
+ let s:gray_fg = s:gray
+ let s:green_fg = s:green_fg
+ let s:yellow_fg = s:yellow_fg
+ let s:pink_fg = s:purple
+ let s:cyan_fg = s:dark_cyan
+ let s:blue_fg = s:blue_fg
+ let s:red_fg = s:red_bg
+ let s:gray_bg = s:light_gray
+ let s:green_bg = s:green_bg
+ let s:yellow_bg = s:yellow_bg
+ let s:pink_bg = s:orange
+ let s:cyan_bg = s:cyan_bg
+ let s:blue_bg = s:blue_fg
+ let s:red_bg = s:red_bg
+else
+ let s:norm = s:white
+ let s:bg = s:NONE
+ let s:bg_subtle = s:dark_gray
+ let s:gray_fg = s:gray
+ let s:green_fg = s:green_bg
+ let s:yellow_fg = s:yellow_bg
+ let s:pink_fg = s:orange
+ let s:cyan_fg = s:cyan_bg
+ let s:blue_fg = s:blue_bg
+ let s:red_fg = s:red_bg
+ let s:gray_bg = s:dark_gray
+ let s:green_bg = s:green_fg
+ let s:yellow_bg = s:yellow_fg
+ let s:pink_bg = s:purple
+ let s:cyan_bg = s:dark_cyan
+ let s:blue_bg = s:blue_fg
+ let s:red_bg = s:red_bg
+endif
+"}}}
+
+"{{{ Highlight Function (keep your existing function)
+function! s:hi(group, style)
+ if g:terminal_italics == 0
+ if has_key(a:style, "cterm") && a:style["cterm"] == "italic"
+ unlet a:style.cterm
+ endif
+ if has_key(a:style, "term") && a:style["term"] == "italic"
+ unlet a:style.term
+ endif
+ endif
+ execute "highlight" a:group
+ \ "guifg=" (has_key(a:style, "fg") ? a:style.fg.gui : "NONE")
+ \ "guibg=" (has_key(a:style, "bg") ? a:style.bg.gui : "NONE")
+ \ "guisp=" (has_key(a:style, "sp") ? a:style.sp.gui : "NONE")
+ \ "gui=" (has_key(a:style, "gui") ? a:style.gui : "NONE")
+ \ "ctermfg=" (has_key(a:style, "fg") ? a:style.fg.cterm : "NONE")
+ \ "ctermbg=" (has_key(a:style, "bg") ? a:style.bg.cterm : "NONE")
+ \ "cterm=" (has_key(a:style, "cterm") ? a:style.cterm : "NONE")
+ \ "term=" (has_key(a:style, "term") ? a:style.term : "NONE")
+endfunction
+
+if g:spell_undercurl == 1
+ let s:attr_un = 'undercurl'
+else
+ let s:attr_un = 'underline'
+endif
+"}}}
+
+"{{{ Common Highlighting updated to match official Vim default colorscheme
+
+call s:hi("Normal", {"fg": s:norm, "bg": s:bg})
+call s:hi("Cursor", {})
+call s:hi("Conceal", {"fg": s:yellow_fg})
+call s:hi("ErrorMsg", {"fg": s:red_fg, "bg": s:red_bg, "gui": "bold", "cterm": "bold"})
+call s:hi("IncSearch", {"gui": "reverse", "cterm": "reverse"})
+call s:hi("ModeMsg", {"gui": "bold", "cterm": "bold"})
+call s:hi("NonText", {"fg": s:blue_fg, "gui": "bold", "cterm": "bold"})
+call s:hi("PmenuSbar", {"bg": s:gray_bg})
+call s:hi("StatusLine", {"gui": "reverse,bold", "cterm": "reverse,bold"})
+call s:hi("StatusLineNC", {"gui": "reverse", "cterm": "reverse"})
+call s:hi("TabLineFill", {"gui": "reverse", "cterm": "reverse"})
+call s:hi("TabLineSel", {"gui": "bold", "cterm": "bold"})
+call s:hi("TermCursor", {"gui": "reverse", "cterm": "reverse"})
+call s:hi("WinBar", {"gui": "bold", "cterm": "bold"})
+call s:hi("WildMenu", {"fg": s:black, "bg": s:yellow_bg})
+
+call s:hi("VertSplit", {"link": "Normal"})
+call s:hi("WinSeparator", {"link": "VertSplit"})
+call s:hi("WinBarNC", {"link": "WinBar"})
+call s:hi("DiffTextAdd", {"link": "DiffText"})
+call s:hi("EndOfBuffer", {"link": "NonText"})
+call s:hi("LineNrAbove", {"link": "LineNr"})
+call s:hi("LineNrBelow", {"link": "LineNr"})
+call s:hi("QuickFixLine", {"link": "Search"})
+call s:hi("CursorLineSign", {"link": "SignColumn"})
+call s:hi("CursorLineFold", {"link": "FoldColumn"})
+call s:hi("CurSearch", {"link": "Search"})
+call s:hi("PmenuKind", {"link": "Pmenu"})
+call s:hi("PmenuKindSel", {"link": "PmenuSel"})
+call s:hi("PmenuMatch", {"link": "Pmenu"})
+call s:hi("PmenuMatchSel", {"link": "PmenuSel"})
+call s:hi("PmenuExtra", {"link": "Pmenu"})
+call s:hi("PmenuExtraSel", {"link": "PmenuSel"})
+call s:hi("ComplMatchIns", {})
+call s:hi("Substitute", {"link": "Search"})
+call s:hi("Whitespace", {"link": "NonText"})
+call s:hi("MsgSeparator", {"link": "StatusLine"})
+call s:hi("NormalFloat", {"link": "Pmenu"})
+call s:hi("FloatBorder", {"link": "WinSeparator"})
+call s:hi("FloatTitle", {"link": "Title"})
+call s:hi("FloatFooter", {"link": "Title"})
+
+call s:hi("Error", {"fg": s:red_fg, "bg": s:red_bg, "gui": "bold", "cterm": "bold"})
+call s:hi("Todo", {"fg": s:black, "bg": s:yellow_bg, "gui": "bold", "cterm": "bold"})
+
+call s:hi("String", {"link": "Constant"})
+call s:hi("Character", {"link": "Constant"})
+call s:hi("Number", {"link": "Constant"})
+call s:hi("Boolean", {"link": "Constant"})
+call s:hi("Float", {"link": "Number"})
+call s:hi("Function", {"link": "Identifier"})
+call s:hi("Conditional", {"link": "Statement"})
+call s:hi("Repeat", {"link": "Statement"})
+call s:hi("Label", {"link": "Statement"})
+call s:hi("Operator", {"link": "Statement"})
+call s:hi("Keyword", {"link": "Statement"})
+call s:hi("Exception",
diff --git a/linux/home/.vim/colors/colorscheme.vim b/linux/home/.vim/colors/colorscheme.vim
new file mode 100644
index 0000000..ce0526e
--- /dev/null
+++ b/linux/home/.vim/colors/colorscheme.vim
@@ -0,0 +1,247 @@
+" Vim Colorscheme
+" Name: cherryblossom.vim
+" Author: Luo Boming
+" Version: 0.3
+" License: The MIT Licence
+
+"{{{ Pre-setting
+let g:colors_name = expand('<sfile>:t:r')
+
+"hi clear
+"if exists("syntax_on")
+" syntax reset
+"endif
+
+if ! exists("g:terminal_italics")
+ let g:terminal_italics = 0
+endif
+
+"if ! exists("g:switch_statusline_bg_in_insert")
+" let g:switch_statusline_bg_in_insert = 0
+"endif
+
+if ! exists("g:spell_undercurl")
+ let g:spell_undercurl = 0
+endif
+
+"}}}
+"{{{ Color Palette
+" Color Entity
+let s:black = { "gui": "#171717", "cterm": "16" }
+let s:white = { "gui": "#EAE8E7", "cterm": "231" }
+
+let s:gray = { "gui": "#3a3f52", "cterm": "247" }
+
+let s:green = { "gui": "#30B536", "cterm": "34" }
+let s:pink = { "gui": "#D36DD3", "cterm": "170" }
+let s:orange = { "gui": "#FC923F", "cterm": "208" }
+let s:purple = { "gui": "#B586E7", "cterm": "141" }
+let s:light_cyan = { "gui": "#D7FFFF", "cterm": "195" }
+let s:dark_cyan = { "gui": "#00AF87", "cterm": "36" }
+let s:ultramarine = { "gui": "#229EC0", "cterm": "38" }
+let s:skyblue = { "gui": "#9BE7F8", "cterm": "195" }
+
+let s:white_pink = { "gui": "#FEF7FE", "cterm": "231" }
+let s:white_pink_deep = { "gui": "#FEF0FE", "cterm": "255" }
+let s:black_green = { "gui": "#053703", "cterm": "235" }
+let s:black_green_bright = { "gui": "#074005", "cterm": "239" }
+let s:middle_gray = { "gui": "#8a8a8a", "cterm": "245" }
+
+let s:light_gray = { "gui": "#E1DCDA", "cterm": "253" }
+let s:light_green = { "gui": "#B7EFA5", "cterm": "157" }
+let s:light_pink = { "gui": "#FEDCFE", "cterm": "225" }
+let s:light_yellow = { "gui": "#EDE682", "cterm": "228" }
+let s:light_red = { "gui": "#EB5A7C", "cterm": "204" }
+
+let s:dark_gray = { "gui": "#4D4A48", "cterm": "241" }
+let s:dark_green = { "gui": "#09570A", "cterm": "22" }
+let s:dark_yellow = { "gui": "#BC922B", "cterm": "3" }
+let s:dark_pink = { "gui": "#B365A2", "cterm": "133" }
+let s:dark_red = { "gui": "#D9372D", "cterm": "160" }
+let s:NONE = { "gui": "NONE", "cterm": "NONE" }
+
+" Color Alias
+if &background == "light"
+ let s:norm = s:black
+ let s:bg = s:NONE
+ let s:bg_subtle = s:white_pink_deep
+ let s:gray_fg = s:middle_gray
+ let s:green_fg = s:green
+ let s:yellow_fg = s:dark_yellow
+ let s:pink_fg = s:dark_pink
+ let s:cyan_fg = s:dark_cyan
+ let s:blue_fg = s:ultramarine
+ let s:red_fg = s:dark_red
+ let s:gray_bg = s:light_gray
+ let s:green_bg = s:light_green
+ let s:yellow_bg = s:light_yellow
+ let s:pink_bg = s:light_pink
+ let s:cyan_bg = s:light_cyan
+ let s:blue_bg = s:skyblue
+ let s:red_bg = s:light_red
+endif
+
+if &background == "dark"
+ let s:norm = s:white
+ let s:bg = s:NONE
+ let s:bg_subtle = s:gray
+ let s:gray_fg = s:middle_gray
+ let s:green_fg = s:light_green
+ let s:yellow_fg = s:light_yellow
+ let s:pink_fg = s:light_pink
+ let s:cyan_fg = s:light_cyan
+ let s:blue_fg = s:skyblue
+ let s:red_fg = s:light_red
+ let s:gray_bg = s:dark_gray
+ let s:green_bg = s:green
+ let s:yellow_bg = s:dark_yellow
+ let s:pink_bg = s:pink
+ let s:cyan_bg = s:dark_cyan
+ let s:blue_bg = s:ultramarine
+ let s:red_bg = s:dark_red
+endif
+"}}}
+"{{{ Highlight Function
+" shamelessly stolen from pencil: https://github.com/reedes/vim-colors-pencil
+function! s:hi(group, style)
+ if g:terminal_italics == 0
+ if has_key(a:style, "cterm") && a:style["cterm"] == "italic"
+ unlet a:style.cterm
+ endif
+ if has_key(a:style, "term") && a:style["term"] == "italic"
+ unlet a:style.term
+ endif
+ endif
+ execute "highlight" a:group
+ \ "guifg=" (has_key(a:style, "fg") ? a:style.fg.gui : "NONE")
+ \ "guibg=" (has_key(a:style, "bg") ? a:style.bg.gui : "NONE")
+ \ "guisp=" (has_key(a:style, "sp") ? a:style.sp.gui : "NONE")
+ \ "gui=" (has_key(a:style, "gui") ? a:style.gui : "NONE")
+ \ "ctermfg=" (has_key(a:style, "fg") ? a:style.fg.cterm : "NONE")
+ \ "ctermbg=" (has_key(a:style, "bg") ? a:style.bg.cterm : "NONE")
+ \ "cterm=" (has_key(a:style, "cterm") ? a:style.cterm : "NONE")
+ \ "term=" (has_key(a:style, "term") ? a:style.term : "NONE")
+endfunction
+
+if g:spell_undercurl == 1
+ let s:attr_un = 'undercurl'
+else
+ let s:attr_un = 'underline'
+endif
+
+"}}}
+"{{{ Common Highlighting
+call s:hi("Normal", {"fg": s:norm, "bg": s:bg})
+call s:hi("Cursor", {})
+call s:hi("Comment", {"fg": s:gray_fg, "gui": "italic", "cterm": "italic", "term": "italic"})
+
+call s:hi("Constant", {"fg": s:pink_fg})
+hi! link String Constant
+hi! link Character Constant
+hi! link Number Constant
+hi! link Boolean Constant
+hi! link Float Constant
+
+call s:hi("Identifier", {"fg": s:red_fg})
+hi! link Function Identifier
+
+call s:hi("Statement", {"fg": s:green_fg})
+hi! link Conditonal Statement
+hi! link Repeat Statement
+hi! link Label Statement
+hi! link Operator Statement
+hi! link Keyword Statement
+hi! link Exception Statement
+
+call s:hi("PreProc", {"fg": s:blue_fg})
+hi! link Include PreProc
+hi! link Define PreProc
+hi! link Macro PreProc
+hi! link PreCondit PreProc
+
+call s:hi("Type", {"fg": s:yellow_fg})
+hi! link StorageClass Type
+hi! link Structure Type
+hi! link Typedef Type
+
+call s:hi("Special", {"fg": s:orange})
+hi! link SpecialChar Special
+hi! link Tag Special
+hi! link Delimiter Special
+hi! link SpecialComment Special
+hi! link Debug Special
+
+call s:hi("Underlined", {"gui": "underline", "cterm": "underline"})
+call s:hi("Ignore", {"fg": s:bg_subtle})
+call s:hi("Error", {"fg": s:white, "bg": s:red_fg , "gui": "bold", "cterm": "bold"})
+call s:hi("Todo", {"bg": s:yellow_bg, "gui": "bold", "cterm": "bold"})
+
+"}}}
+"{{{ Semi-Common Highlighting
+call s:hi("SpecialKey", {"fg": s:purple, "gui": "bold", "cterm": "bold", "term": "bold"})
+call s:hi("NonText", {"fg": s:cyan_bg, "gui": "bold", "cterm": "bold", "term": "bold"})
+call s:hi("Directory", {"fg": s:blue_fg, "gui": "bold", "cterm": "bold", "term": "bold"})
+call s:hi("ErrorMsg", {"fg": s:red_fg, "gui": "bold", "cterm": "bold", "term": "bold"})
+call s:hi("IncSearch", {"gui": "reverse", "cterm": "reverse", "term": "reverse"})
+call s:hi("Search", {"fg": s:norm, "bg": s:pink_bg})
+call s:hi("MoreMsg", {"fg": s:pink_fg, "gui": "bold", "cterm": "bold", "term": "bold"})
+call s:hi("ModeMsg", {"fg": s:pink_fg, "gui": "bold", "cterm": "bold", "term": "bold"})
+call s:hi("LineNr", {"fg": s:gray})
+call s:hi("CursorLineNr", {"fg": s:pink_fg, "gui": "bold", "cterm": "bold", "term": "bold"})
+call s:hi("Question", {"fg": s:purple, "gui": "bold", "cterm": "bold", "term": "bold"})
+"call s:hi("StatusLine", {"fg": s:norm, "bg": s:green_bg, "gui": "bold", "cterm": "bold", "term": "bold"})
+"call s:hi("StatusLineNC", {"fg": s:norm, "bg": s:gray_bg})
+call s:hi("Conceal", {"fg": s:yellow_fg})
+call s:hi("VertSplit", {"gui": "reverse", "cterm": "reverse", "term": "reverse"})
+call s:hi("Title", {"fg": s:pink_fg, "gui": "bold", "cterm": "bold", "term": "bold"})
+call s:hi("Visual", {"gui": "reverse", "cterm": "reverse", "term": "reverse"})
+call s:hi("VisualNOS", {"gui": "bold,underline", "cterm": "bold,underline", "term": "bold,underline"})
+call s:hi("WarningMsg", {"fg": s:orange, "gui": "bold", "cterm": "bold", "term": "bold"})
+call s:hi("WildMenu", {"fg": s:norm, "bg": s:blue_bg})
+call s:hi("Folded", {"fg": s:green_fg, "bg": s:gray_bg})
+call s:hi("FoldColumn", {"fg": s:green_fg, "bg": s:gray_bg})
+call s:hi("DiffAdd", {"bg": s:green_bg})
+call s:hi("DiffChange", {"bg": s:yellow_bg})
+call s:hi("DiffDelete", {"bg": s:red_bg})
+call s:hi("DiffText", {"bg": s:blue_bg, "gui": "bold", "cterm": "bold", "term": "bold"})
+call s:hi("SignColumn", {"fg": s:green_fg, "bg": s:gray})
+if has("gui_running")
+ call s:hi("SpellBad", {"gui": s:attr_un, "sp": s:red_bg})
+ call s:hi("SpellCap", {"gui": s:attr_un, "sp": s:yellow_bg})
+ call s:hi("SpellRare", {"gui": s:attr_un, "sp": s:blue_bg})
+ call s:hi("SpellLocal", {"gui": s:attr_un, "sp": s:green_bg})
+else
+ call s:hi("SpellBad", {"cterm": s:attr_un, "fg": s:red_fg})
+ call s:hi("SpellCap", {"cterm": s:attr_un, "fg": s:yellow_fg})
+ call s:hi("SpellRare", {"cterm": s:attr_un, "fg": s:blue_fg})
+ call s:hi("SpellLocal", {"cterm": s:attr_un, "fg": s:green_fg})
+endif
+call s:hi("Pmenu", {"bg": s:gray_bg})
+call s:hi("PmenuSel", {"bg": s:pink_bg})
+call s:hi("PmenuSbar", {"bg": s:gray_bg})
+call s:hi("PmenuThumb", {"bg": s:gray_bg})
+call s:hi("TabLine", {"bg": s:bg_subtle})
+call s:hi("TabLineSel", {"bg": s:pink_bg})
+call s:hi("TabLineFill", {"bg": s:bg_subtle})
+call s:hi("CursorColumn", {"bg": s:yellow_fg})
+call s:hi("CursorLine", {"bg": s:bg_subtle})
+call s:hi("ColorColumn", {"bg": s:bg_subtle})
+call s:hi("MatchParen", {"fg": s:pink_fg, "gui": "underline", "cterm": "underline"})
+call s:hi("qfLineNr", {"fg": s:gray})
+
+"}}}
+""{{{ Switching StatusLine bg
+"function! s:changebg(group, color)
+" execute "highlight" a:group "guibg=" a:color.gui "ctermbg=" a:color.cterm
+"endfunction
+"
+"if g:switch_statusline_bg_in_insert == 1
+" "" Change Color when entering Insert Mode
+" autocmd InsertEnter * call s:changebg("StatusLine", s:pink_bg)
+" "" Revert Color to default when leaving Insert Mode
+" autocmd InsertLeave * call s:changebg("StatusLine", s:green_bg)
+"endif
+
+"}}}
+" vim: set foldmethod=marker:
+
diff --git a/linux/home/.vim/colors/default.vim b/linux/home/.vim/colors/default.vim
new file mode 100644
index 0000000..ebc66e8
--- /dev/null
+++ b/linux/home/.vim/colors/default.vim
@@ -0,0 +1,175 @@
+"{{{ Pre-setting
+let g:colors_name = expand('<sfile>:t:r')
+
+if ! exists("g:terminal_italics")
+ let g:terminal_italics = 0
+endif
+
+if ! exists("g:spell_undercurl")
+ let g:spell_undercurl = 0
+endif
+"}}}
+
+"{{{ Color Palette (updated to match official Vim default colors)
+" Note: Hex colors chosen to reflect official Vim default colorscheme
+
+let s:black = { "gui": "#171717", "cterm": "16" }
+let s:white = { "gui": "#EAE8E7", "cterm": "231" }
+let s:gray = { "gui": "#808080", "cterm": "244" }
+
+" Reds
+let s:red_fg = { "gui": "#FFFFFF", "cterm": "231" } " White fg on red bg for errors
+let s:red_bg = { "gui": "#A40000", "cterm": "52" } " DarkRed bg (ErrorMsg bg)
+
+" Blues and Cyan
+let s:blue_fg = { "gui": "#6A5ACD", "cterm": "60" } " SlateBlue
+let s:dark_cyan = { "gui": "#008B8B", "cterm": "36" } " DarkCyan
+let s:cyan_bg = { "gui": "#00CED1", "cterm": "38" } " DarkTurquoise
+
+" Greens
+let s:green_fg = { "gui": "#008000", "cterm": "22" } " DarkGreen
+let s:green_bg = { "gui": "#90EE90", "cterm": "120" } " LightGreen
+
+" Yellows and Oranges
+let s:yellow_fg = { "gui": "#A52A2A", "cterm": "94" } " Brown (used in Vim default)
+let s:yellow_bg = { "gui": "#FFFF00", "cterm": "226" } " Yellow bg
+
+let s:orange = { "gui": "#FFA500", "cterm": "214" } " Orange
+
+" Purples
+let s:purple = { "gui": "#6A0DAD", "cterm": "90" } " DarkMagenta
+
+" Grays
+let s:light_gray = { "gui": "#D3D3D3", "cterm": "252" }
+let s:dark_gray = { "gui": "#4D4D4D", "cterm": "240" }
+
+" No color
+let s:NONE = { "gui": "NONE", "cterm": "NONE" }
+
+" Alias for Normal fg and background depending on background setting
+if &background == "light"
+ let s:norm = s:black
+ let s:bg = s:NONE
+ let s:bg_subtle = s:light_gray
+ let s:gray_fg = s:gray
+ let s:green_fg = s:green_fg
+ let s:yellow_fg = s:yellow_fg
+ let s:pink_fg = s:purple
+ let s:cyan_fg = s:dark_cyan
+ let s:blue_fg = s:blue_fg
+ let s:red_fg = s:red_bg
+ let s:gray_bg = s:light_gray
+ let s:green_bg = s:green_bg
+ let s:yellow_bg = s:yellow_bg
+ let s:pink_bg = s:orange
+ let s:cyan_bg = s:cyan_bg
+ let s:blue_bg = s:blue_fg
+ let s:red_bg = s:red_bg
+else
+ let s:norm = s:white
+ let s:bg = s:NONE
+ let s:bg_subtle = s:dark_gray
+ let s:gray_fg = s:gray
+ let s:green_fg = s:green_bg
+ let s:yellow_fg = s:yellow_bg
+ let s:pink_fg = s:orange
+ let s:cyan_fg = s:cyan_bg
+ let s:blue_fg = s:blue_bg
+ let s:red_fg = s:red_bg
+ let s:gray_bg = s:dark_gray
+ let s:green_bg = s:green_fg
+ let s:yellow_bg = s:yellow_fg
+ let s:pink_bg = s:purple
+ let s:cyan_bg = s:dark_cyan
+ let s:blue_bg = s:blue_fg
+ let s:red_bg = s:red_bg
+endif
+"}}}
+
+"{{{ Highlight Function (keep your existing function)
+function! s:hi(group, style)
+ if g:terminal_italics == 0
+ if has_key(a:style, "cterm") && a:style["cterm"] == "italic"
+ unlet a:style.cterm
+ endif
+ if has_key(a:style, "term") && a:style["term"] == "italic"
+ unlet a:style.term
+ endif
+ endif
+ execute "highlight" a:group
+ \ "guifg=" (has_key(a:style, "fg") ? a:style.fg.gui : "NONE")
+ \ "guibg=" (has_key(a:style, "bg") ? a:style.bg.gui : "NONE")
+ \ "guisp=" (has_key(a:style, "sp") ? a:style.sp.gui : "NONE")
+ \ "gui=" (has_key(a:style, "gui") ? a:style.gui : "NONE")
+ \ "ctermfg=" (has_key(a:style, "fg") ? a:style.fg.cterm : "NONE")
+ \ "ctermbg=" (has_key(a:style, "bg") ? a:style.bg.cterm : "NONE")
+ \ "cterm=" (has_key(a:style, "cterm") ? a:style.cterm : "NONE")
+ \ "term=" (has_key(a:style, "term") ? a:style.term : "NONE")
+endfunction
+
+if g:spell_undercurl == 1
+ let s:attr_un = 'undercurl'
+else
+ let s:attr_un = 'underline'
+endif
+"}}}
+
+"{{{ Common Highlighting updated to match official Vim default colorscheme
+
+call s:hi("Normal", {"fg": s:norm, "bg": s:bg})
+call s:hi("Cursor", {})
+call s:hi("Conceal", {"fg": s:yellow_fg})
+call s:hi("ErrorMsg", {"fg": s:red_fg, "bg": s:red_bg, "gui": "bold", "cterm": "bold"})
+call s:hi("IncSearch", {"gui": "reverse", "cterm": "reverse"})
+call s:hi("ModeMsg", {"gui": "bold", "cterm": "bold"})
+call s:hi("NonText", {"fg": s:blue_fg, "gui": "bold", "cterm": "bold"})
+call s:hi("PmenuSbar", {"bg": s:gray_bg})
+call s:hi("StatusLine", {"gui": "reverse,bold", "cterm": "reverse,bold"})
+call s:hi("StatusLineNC", {"gui": "reverse", "cterm": "reverse"})
+call s:hi("TabLineFill", {"gui": "reverse", "cterm": "reverse"})
+call s:hi("TabLineSel", {"gui": "bold", "cterm": "bold"})
+call s:hi("TermCursor", {"gui": "reverse", "cterm": "reverse"})
+call s:hi("WinBar", {"gui": "bold", "cterm": "bold"})
+call s:hi("WildMenu", {"fg": s:black, "bg": s:yellow_bg})
+
+call s:hi("VertSplit", {"link": "Normal"})
+call s:hi("WinSeparator", {"link": "VertSplit"})
+call s:hi("WinBarNC", {"link": "WinBar"})
+call s:hi("DiffTextAdd", {"link": "DiffText"})
+call s:hi("EndOfBuffer", {"link": "NonText"})
+call s:hi("LineNrAbove", {"link": "LineNr"})
+call s:hi("LineNrBelow", {"link": "LineNr"})
+call s:hi("QuickFixLine", {"link": "Search"})
+call s:hi("CursorLineSign", {"link": "SignColumn"})
+call s:hi("CursorLineFold", {"link": "FoldColumn"})
+call s:hi("CurSearch", {"link": "Search"})
+call s:hi("PmenuKind", {"link": "Pmenu"})
+call s:hi("PmenuKindSel", {"link": "PmenuSel"})
+call s:hi("PmenuMatch", {"link": "Pmenu"})
+call s:hi("PmenuMatchSel", {"link": "PmenuSel"})
+call s:hi("PmenuExtra", {"link": "Pmenu"})
+call s:hi("PmenuExtraSel", {"link": "PmenuSel"})
+call s:hi("ComplMatchIns", {})
+call s:hi("Substitute", {"link": "Search"})
+call s:hi("Whitespace", {"link": "NonText"})
+call s:hi("MsgSeparator", {"link": "StatusLine"})
+call s:hi("NormalFloat", {"link": "Pmenu"})
+call s:hi("FloatBorder", {"link": "WinSeparator"})
+call s:hi("FloatTitle", {"link": "Title"})
+call s:hi("FloatFooter", {"link": "Title"})
+
+call s:hi("Error", {"fg": s:red_fg, "bg": s:red_bg, "gui": "bold", "cterm": "bold"})
+call s:hi("Todo", {"fg": s:black, "bg": s:yellow_bg, "gui": "bold", "cterm": "bold"})
+
+call s:hi("String", {"link": "Constant"})
+call s:hi("Character", {"link": "Constant"})
+call s:hi("Number", {"link": "Constant"})
+call s:hi("Boolean", {"link": "Constant"})
+call s:hi("Float", {"link": "Number"})
+call s:hi("Function", {"link": "Identifier"})
+call s:hi("Conditional", {"link": "Statement"})
+call s:hi("Repeat", {"link": "Statement"})
+call s:hi("Label", {"link": "Statement"})
+call s:hi("Operator", {"link": "Statement"})
+call s:hi("Keyword", {"link": "Statement"})
+call s:hi("Exception",
diff --git a/linux/home/.vim/ftplugin/after/vim.vim b/linux/home/.vim/ftplugin/after/vim.vim
new file mode 100644
index 0000000..3548816
--- /dev/null
+++ b/linux/home/.vim/ftplugin/after/vim.vim
@@ -0,0 +1,12 @@
+" Enable Vim-specific options
+setlocal tabstop=4
+setlocal shiftwidth=4
+setlocal softtabstop=4
+setlocal expandtab
+setlocal foldmethod=marker
+setlocal keywordprg=:help
+setlocal iskeyword+=:
+
+" Spell-check off by default
+setlocal nospell
+
diff --git a/linux/home/.vim/ftplugin/ftplugin/after/vim.vim b/linux/home/.vim/ftplugin/ftplugin/after/vim.vim
new file mode 100644
index 0000000..3548816
--- /dev/null
+++ b/linux/home/.vim/ftplugin/ftplugin/after/vim.vim
@@ -0,0 +1,12 @@
+" Enable Vim-specific options
+setlocal tabstop=4
+setlocal shiftwidth=4
+setlocal softtabstop=4
+setlocal expandtab
+setlocal foldmethod=marker
+setlocal keywordprg=:help
+setlocal iskeyword+=:
+
+" Spell-check off by default
+setlocal nospell
+
diff --git a/linux/home/.vim/pack/plugins/start/vim-tmux-navigator b/linux/home/.vim/pack/plugins/start/vim-tmux-navigator
new file mode 160000
+Subproject 38b1d0402c4600543281dc85b3f51884205674b
diff --git a/linux/home/.vim/vimrc b/linux/home/.vim/vimrc
new file mode 100644
index 0000000..5b0f444
--- /dev/null
+++ b/linux/home/.vim/vimrc
@@ -0,0 +1,700 @@
+" ============================================================================
+" Vim Configuration
+" Maintainer: srdusr
+" ============================================================================
+
+" ============================================================================
+" Core Settings
+" ============================================================================
+set nocompatible
+set encoding=utf-8
+set fileencoding=utf-8
+scriptencoding utf-8
+set termguicolors
+set mouse=a
+set clipboard=unnamedplus
+set hidden
+set updatetime=300
+set timeoutlen=500
+set ttimeoutlen=10
+
+" Performance
+set ttyfast
+set lazyredraw
+set ttyscroll=3
+
+" ============================================================================
+" Display and UI Settings
+" ============================================================================
+set number
+set relativenumber
+set cursorline
+set signcolumn=no
+set showcmd
+set showmode
+set showmatch
+set matchtime=2
+set laststatus=2
+set cmdheight=1
+set scrolloff=8
+set sidescrolloff=8
+set sidescroll=1
+set display=lastline
+set nowrap
+set linebreak
+set showbreak=↪\
+
+" Window behavior
+set splitright
+set splitbelow
+set winminwidth=1
+set winwidth=5
+
+" ============================================================================
+" Formatting and Indentation Settings
+" ============================================================================
+set autoindent
+set smartindent
+set expandtab
+set tabstop=2
+set shiftwidth=2
+set softtabstop=2
+set shiftround
+set textwidth=80
+set formatoptions+=j
+" set colorcolumn=+1
+
+" File-specific formatting
+setlocal tabstop=4
+setlocal shiftwidth=4
+setlocal softtabstop=4
+setlocal foldmethod=marker
+
+" ============================================================================
+" Search and Matching Settings
+" ============================================================================
+set hlsearch
+set incsearch
+set ignorecase
+set smartcase
+set showmatch
+" set inccommand=split
+
+" ============================================================================
+" File Handling and Backup Settings
+" ============================================================================
+set autoread
+set autowrite
+set autochdir
+set confirm
+set fileformats=unix,dos,mac
+
+" Backup and undo configuration
+set backup
+set writebackup
+set backupcopy=yes
+set undofile
+set noswapfile
+set backupdir=~/.cache/vim/backup//
+set directory=~/.cache/vim/swap//
+set undodir=~/.cache/vim/undo//
+set undolevels=10000
+set undoreload=10000
+
+" Create necessary directories
+let s:config_dir = expand('~/.vim')
+let s:cache_dir = expand('~/.cache/vim')
+let s:dirs = [
+ \ s:cache_dir . '/backup',
+ \ s:cache_dir . '/swap',
+ \ s:cache_dir . '/undo',
+ \ s:cache_dir . '/shada',
+ \ s:config_dir . '/sessions',
+ \ ]
+
+for s:dir in s:dirs
+ if !isdirectory(s:dir)
+ call mkdir(s:dir, 'p', 0700)
+ endif
+endfor
+
+" ============================================================================
+" Completion and Command Line Settings
+" ============================================================================
+set omnifunc=syntaxcomplete#Complete
+set complete=.,w,b,u,t,i,kspell
+set completeopt=menu,menuone,noselect
+
+" Wildmenu configuration
+set wildmenu
+set wildmode=longest:full,full
+set wildignorecase
+set wildignore+=*.o,*.obj,.git,*.rbc,*.pyc,__pycache__
+set wildignore+=.git,.hg,.svn
+set wildignore+=*.swp,*.swo
+set wildignore+=*.DS_Store
+set wildignore+=*.class
+
+" ============================================================================
+" Leader Keys
+" ============================================================================
+let mapleader = ";"
+let maplocalleader = "\\"
+
+" ============================================================================
+" Navigation and Movement Mappings
+" ============================================================================
+
+" Tmux/Vim Navigation Function
+function! SmartMove(direction, tmux_flag)
+ let curwin = win_getid()
+ execute 'wincmd ' . a:direction
+ if win_getid() == curwin
+ call system('tmux select-pane ' . a:tmux_flag)
+ endif
+endfunction
+
+" Window navigation
+nnoremap <silent> <C-h> :call SmartMove('h', '-L')<CR>
+nnoremap <silent> <C-j> :call SmartMove('j', '-D')<CR>
+nnoremap <silent> <C-k> :call SmartMove('k', '-U')<CR>
+nnoremap <silent> <C-l> :call SmartMove('l', '-R')<CR>
+
+" Split window horizontally
+nnoremap <leader>- :split<CR>
+
+" Split window vertically
+nnoremap <leader>\ :vsplit<CR>
+
+" Close the current window
+nnoremap <leader>c <C-w>c
+
+" Buffer navigation
+nnoremap <silent> <leader>bn :bnext<CR>
+nnoremap <silent> <leader>bp :bprevious<CR>
+nnoremap <silent> <leader>bd :bdelete<CR>
+nnoremap <silent> <leader>ba :%bdelete<CR>
+nnoremap <silent> <leader>bl :ls<CR>:b<Space>
+
+" Quickfix and location list
+nnoremap ]q :cnext<CR>zz
+nnoremap [q :cprev<CR>zz
+nnoremap ]l :lnext<CR>zz
+nnoremap [l :lprev<CR>zz
+
+" ============================================================================
+" File and Buffer Management Mappings
+" ============================================================================
+nnoremap <silent> <leader>w :w<CR>
+nnoremap <silent> <leader>q :q<CR>
+nnoremap <silent> <leader>wq :wq<CR>
+nnoremap <silent> <leader>Q :qa!<CR>
+
+" File operations
+nnoremap <leader>f :Lex<CR>
+noremap <leader>o :Explore<CR>
+nnoremap <leader>rf :browse old<cr>
+
+" Utility mappings
+nnoremap <silent> <leader>r :registers<CR>
+nnoremap <silent> <leader>m :redir @+<CR>:silent messages<CR>:redir END<CR>
+nnoremap <silent> <leader>hx :call HexState()<CR>
+nnoremap <buffer> <leader>h :help <C-R><C-W><CR>
+
+" Format disable
+cnoremap F! :noautocmd w<CR>
+
+" ============================================================================
+" Window and Terminal Mappings
+" ============================================================================
+
+" Window resizing
+nnoremap <M-Up> :resize -2<CR>
+noremap <M-Down> :resize +2<CR>
+noremap <M-Left> :vertical resize -2<CR>
+noremap <M-Right> :vertical resize +2<CR>
+
+" Terminal mode
+nnoremap <silent> <C-t> :terminal<CR>
+tnoremap <C-t> <C-\><C-n>:q!<CR>
+tnoremap <Esc> <C-\><C-n>
+tnoremap <C-h> <C-\><C-n><C-w>h
+tnoremap <C-j> <C-\><C-n><C-w>j
+tnoremap <C-k> <C-\><C-n><C-w>k
+tnoremap <C-l> <C-\><C-n><C-w>l
+
+" ============================================================================
+" Text Editing and Visual Mode Mappings
+" ============================================================================
+
+" Insert mode shortcuts
+inoremap jk <ESC>
+
+" Visual mode operations
+vnoremap J :m '>+1<CR>gv=gv
+vnoremap K :m '>-2<CR>gv=gv
+vnoremap < <gv
+vnoremap > >gv
+
+" Text formatting
+nnoremap Q gqap
+
+" Auto-closing pairs
+inoremap [ []<left>
+inoremap ( ()<left>
+inoremap { {}<left>
+inoremap /* /**/<left><left>
+
+" ============================================================================
+" Tab and Completion Functions
+" ============================================================================
+
+" Smart Tab Function
+function! SmartIndentTab()
+ let line = getline('.')
+ let col = col('.') - 1
+
+ if col == 0 || line[col - 1] =~ '^\s*$'
+ return "\<C-T>"
+ else
+ return "\<tab>"
+ endif
+endfunction
+
+" Tab mappings
+silent! iunmap <tab>
+inoremap <silent><expr> <tab> SmartIndentTab()
+inoremap <expr> <CR> pumvisible() ? "\<C-Y>" : "\<CR>"
+
+" Auto-completion function
+function! AutoComplete()
+ let skip_filetypes = ['netrw', 'help', 'startify', 'qf', 'man', 'gitcommit']
+
+ if index(skip_filetypes, &filetype) >= 0
+ return
+ endif
+
+ let col = col('.')
+ if col < 4
+ return
+ endif
+
+ let line = getline('.')
+ let prev3 = line[col - 4]
+ let prev2 = line[col - 3]
+ let prev1 = line[col - 2]
+ let curr = v:char
+
+ if curr =~ '\k' && prev3 !~ '\k' && prev2 =~ '\k' && prev1 =~ '\k'
+ call feedkeys("\<C-n>", 'n')
+ endif
+endfunction
+
+" ============================================================================
+" Spell Checking
+" ============================================================================
+noremap <silent> <C-z> :setlocal spell!<CR>
+setlocal nospell
+
+function! SpellCheckStatus()
+ return &spell ? ' [SPELL]' : ''
+endfunction
+
+" ============================================================================
+" File Explorer Configuration
+" ============================================================================
+let g:netrw_banner=0
+let g:netrw_browse_split=4
+let g:netrw_altv=1
+let g:netrw_liststyle=3
+let g:netrw_fastbrowse = 0
+let g:netrw_winsize=20
+
+" ============================================================================
+" Search Tools Configuration
+" ============================================================================
+if executable("rg")
+ set grepprg=rg\ --vimgrep\ --no-heading
+ set grepformat=%f:%l:%c:%m,%f:%l:%m
+endif
+
+" ============================================================================
+" Clipboard Configuration
+" ============================================================================
+
+" OS Detection
+let s:uname = substitute(system('uname'), '\n', '', '')
+let g:is_mac = has('mac')
+let g:is_linux = s:uname ==# 'Linux'
+let g:is_windows = has('win32') || has('win64') || s:uname =~? 'Windows'
+let g:is_wsl = has('wsl') || (g:is_linux && !empty($WSL_DISTRO_NAME))
+let g:is_termux = has('termux') || (!empty($PREFIX) && $PREFIX =~# 'com.termux')
+let g:is_wayland = !empty($WAYLAND_DISPLAY)
+let g:is_x11 = !empty($DISPLAY) && empty($WAYLAND_DISPLAY)
+
+" Clipboard configuration
+if has('unnamedplus')
+ set clipboard=unnamed,unnamedplus
+else
+ set clipboard=unnamed
+endif
+
+if has('clipboard')
+ " Linux: Wayland/X11 providers
+ if g:is_wayland && executable('wl-copy') && executable('wl-paste')
+ let g:clipboard = {
+ \ 'name': 'wl-clipboard',
+ \ 'copy': {
+ \ '+': ['wl-copy', '--trim-newline'],
+ \ '*': ['wl-copy', '--trim-newline', '--primary'],
+ \ },
+ \ 'paste': {
+ \ '+': ['wl-paste', '--no-newline'],
+ \ '*': ['wl-paste', '--no-newline', '--primary'],
+ \ },
+ \ 'cache_enabled': 1,
+ \ }
+ elseif g:is_x11 && executable('xclip')
+ let g:clipboard = {
+ \ 'name': 'xclip',
+ \ 'copy': {
+ \ '+': ['xclip', '-selection', 'clipboard'],
+ \ '*': ['xclip', '-selection', 'primary'],
+ \ },
+ \ 'paste': {
+ \ '+': ['xclip', '-selection', 'clipboard', '-o'],
+ \ '*': ['xclip', '-selection', 'primary', '-o'],
+ \ },
+ \ 'cache_enabled': 1,
+ \ }
+ elseif g:is_x11 && executable('xsel')
+ let g:clipboard = {
+ \ 'name': 'xsel',
+ \ 'copy': {
+ \ '+': ['xsel', '--clipboard', '--input'],
+ \ '*': ['xsel', '--primary', '--input'],
+ \ },
+ \ 'paste': {
+ \ '+': ['xsel', '--clipboard', '--output'],
+ \ '*': ['xsel', '--primary', '--output'],
+ \ },
+ \ 'cache_enabled': 1,
+ \ }
+ " macOS: pbcopy/pbpaste
+ elseif g:is_mac && executable('pbcopy') && executable('pbpaste')
+ let g:clipboard = {
+ \ 'name': 'macOS-clipboard',
+ \ 'copy': { '+': ['pbcopy'], '*': ['pbcopy'] },
+ \ 'paste': { '+': ['pbpaste'], '*': ['pbpaste'] },
+ \ 'cache_enabled': 1,
+ \ }
+ " WSL: prefer win32yank, fallback to clip.exe + PowerShell
+ elseif g:is_wsl && executable('win32yank.exe')
+ let g:clipboard = {
+ \ 'name': 'win32yank-wsl',
+ \ 'copy': { '+': ['win32yank.exe', '-i', '--crlf'],
+ \ '*': ['win32yank.exe', '-i', '--crlf'] },
+ \ 'paste': { '+': ['win32yank.exe', '-o', '--lf'],
+ \ '*': ['win32yank.exe', '-o', '--lf'] },
+ \ 'cache_enabled': 1,
+ \ }
+ elseif g:is_wsl && executable('clip.exe') && executable('powershell.exe')
+ let g:clipboard = {
+ \ 'name': 'wsl-clip',
+ \ 'copy': {
+ \ '+': ['clip.exe'],
+ \ '*': ['clip.exe'],
+ \ },
+ \ 'paste': {
+ \ '+': ['powershell.exe', '-NoProfile', '-Command',
+ \ '[Console]::Out.Write((Get-Clipboard -Raw) -replace "`r","")'],
+ \ '*': ['powershell.exe', '-NoProfile', '-Command',
+ \ '[Console]::Out.Write((Get-Clipboard -Raw) -replace "`r","")'],
+ \ },
+ \ 'cache_enabled': 0,
+ \ }
+ " Native Windows
+ elseif g:is_windows && !g:is_wsl && executable('win32yank.exe')
+ let g:clipboard = {
+ \ 'name': 'win32yank',
+ \ 'copy': { '+': ['win32yank.exe', '-i', '--crlf'],
+ \ '*': ['win32yank.exe', '-i', '--crlf'] },
+ \ 'paste': { '+': ['win32yank.exe', '-o', '--lf'],
+ \ '*': ['win32yank.exe', '-o', '--lf'] },
+ \ 'cache_enabled': 1,
+ \ }
+ elseif g:is_windows && !g:is_wsl && executable('powershell.exe')
+ let g:clipboard = {
+ \ 'name': 'powershell-clipboard',
+ \ 'copy': {
+ \ '+': ['powershell.exe', '-NoProfile', '-Command',
+ \ 'Set-Clipboard ([Console]::In.ReadToEnd())'],
+ \ '*': ['powershell.exe', '-NoProfile', '-Command',
+ \ 'Set-Clipboard ([Console]::In.ReadToEnd())'],
+ \ },
+ \ 'paste': {
+ \ '+': ['powershell.exe', '-NoProfile', '-Command',
+ \ '[Console]::Out.Write((Get-Clipboard -Raw) -replace "`r","")'],
+ \ '*': ['powershell.exe', '-NoProfile', '-Command',
+ \ '[Console]::Out.Write((Get-Clipboard -Raw) -replace "`r","")'],
+ \ },
+ \ 'cache_enabled': 0,
+ \ }
+ endif
+endif
+
+" ============================================================================
+" Colorscheme and Appearance
+" ============================================================================
+syntax enable
+set termguicolors
+set guicursor=
+let &t_SI = "\e[6 q"
+let &t_EI = "\e[2 q"
+set background=dark
+
+" Window title
+let progname = substitute($VIM, '.*[/\\]', '', '')
+set title titlestring=%{progname}\ %f\ +%l\ #%{tabpagenr()}.%{winnr()}
+if &term =~ '^screen' && !has('vim')
+ exe "set t_ts=\e]2; t_fs=\7"
+endif
+
+colorscheme colorscheme
+
+" ============================================================================
+" Custom Functions
+" ============================================================================
+
+" Clear Terminal Buffer
+function! ClearTerminal()
+ if &buftype == 'terminal'
+ let s:scroll_value = &scrollback
+ set scrollback=1
+ call feedkeys("\<C-\\>\<C-n>i")
+ call feedkeys("clear\<CR>")
+ call timer_start(100, {-> execute('let &scrollback=' . s:scroll_value)})
+ endif
+endfunction
+
+" Rename Current File
+function! RenameFile()
+ let old_name = expand('%')
+ let new_name = input('New file name: ', old_name, 'file')
+ if new_name != '' && new_name != old_name
+ try
+ execute 'saveas ' . fnameescape(new_name)
+ if filereadable(old_name) && !filereadable(new_name)
+ call delete(old_name)
+ endif
+ redraw!
+ catch /^Vim\%(\w\+\).*/
+ echohl ErrorMsg | echo 'Error renaming file: ' . v:exception | echohl None
+ endtry
+ endif
+endfunction
+
+" Format with cursor preservation
+function! PreserveCursorFormat()
+ let l:pos = getpos(".")
+ silent! normal! gg=G
+ call setpos('.', l:pos)
+endfunction
+
+" File/URL opener
+function! OpenFileOrUrl(path) abort
+ if !exists('g:os_name')
+ echohl WarningMsg | echom 'OS detection not available' | echohl None
+ return
+ endif
+
+ let cmd = ''
+ if g:os_name ==# 'mac'
+ let cmd = 'open ' . shellescape(a:path)
+ elseif g:os_name ==# 'linux'
+ let cmd = 'xdg-open ' . shellescape(a:path) . ' >/dev/null 2>&1 &'
+ elseif g:os_name ==# 'wsl'
+ let cmd = 'wslview ' . shellescape(a:path) . ' >/dev/null 2>&1 &'
+ elseif g:os_name ==# 'windows'
+ let cmd = 'start "" ' . shellescape(a:path)
+ elseif g:os_name ==# 'termux'
+ let cmd = 'am start -a android.intent.action.VIEW -d ' . shellescape(a:path)
+ else
+ echohl WarningMsg | echom 'No file opener for OS: ' . g:os_name | echohl None
+ return
+ endif
+
+ call system(cmd)
+ if v:shell_error
+ echohl ErrorMsg | echom 'Failed to open: ' . a:path | echohl None
+ endif
+endfunction
+
+" ============================================================================
+" Hex Editing Functions
+" ============================================================================
+
+" Convert to hex
+function! DoHex() abort
+ let current_file = expand('%:p')
+ if empty(current_file)
+ echohl ErrorMsg | echo 'No file name' | echohl None
+ return
+ endif
+
+ let new_file = current_file . '.hex'
+ try
+ execute 'w !xxd > ' . fnameescape(new_file)
+ echohl Directory | echo 'Hex file created: ' . new_file | echohl None
+ catch /^Vim\%(\w\+\):/
+ echohl ErrorMsg | echo 'Error creating hex file: ' . v:exception | echohl None
+ endtry
+endfunction
+
+" Convert from hex
+function! UndoHex() abort
+ let current_file = expand('%:p')
+ if empty(current_file)
+ echohl ErrorMsg | echo 'No file name' | echohl None
+ return
+ endif
+
+ let new_file = substitute(current_file, '\.hex$', '', '')
+ if new_file ==# current_file
+ echohl WarningMsg | echo 'Not a .hex file' | echohl None
+ return
+ endif
+
+ try
+ execute '%!xxd -r'
+ execute 'w ' . fnameescape(new_file)
+ echohl Directory | echo 'Binary file restored: ' . new_file | echohl None
+ catch /^Vim\%(\w\+\):/
+ echohl ErrorMsg | echo 'Error converting hex file: ' . v:exception | echohl None
+ endtry
+endfunction
+
+" Hex state switcher
+function! HexState() abort
+ let choices = [
+ \ '1. Convert to hex',
+ \ '2. Convert from hex',
+ \ '3. Cancel'
+ \ ]
+
+ let choice = inputlist(choices)
+ if choice == 1
+ call DoHex()
+ elseif choice == 2
+ call UndoHex()
+ else
+ echo 'Operation cancelled.'
+ endif
+endfunction
+
+" ============================================================================
+" Commands
+" ============================================================================
+command! -nargs=0 OpenFile call OpenFileOrUrl(expand('%:p'))
+
+" ============================================================================
+" Auto Commands
+" ============================================================================
+
+" Return to last position
+augroup line_return
+ au!
+ au BufReadPost *
+ \ if line("'\"") > 0 && line("'\"") <= line("$") |
+ \ execute 'normal! g`"zvzz' |
+ \ endif
+augroup END
+
+" Auto-completion
+augroup AutoCompleteWordStart
+ autocmd!
+ autocmd InsertCharPre * call AutoComplete()
+augroup END
+
+" Relative numbers in insert mode
+autocmd InsertEnter * set norelativenumber
+autocmd InsertLeave * set relativenumber
+
+" Netrw cleanup
+autocmd FileType netrw setl bufhidden=wipe
+
+" Reload config
+augroup ReloadVimrc
+ autocmd!
+ autocmd BufWritePost $MYVIMRC nested source $MYVIMRC |
+ \ doautocmd ColorScheme |
+ \ call autoload#statusline#ActivateStatusline() |
+ \ echon "Reloaded .vimrc" |
+ \ redraw!
+augroup END
+
+" Auto format
+augroup FormatOnSave
+ autocmd!
+ autocmd BufWritePre * call PreserveCursorFormat()
+augroup END
+
+" ============================================================================
+" Statusline Configuration
+" ============================================================================
+let s:statusline_file = expand('~/.vim/autoload/statusline.vim')
+if filereadable(s:statusline_file)
+ execute 'source ' . fnameescape(s:statusline_file)
+
+ augroup StatuslineConfig
+ autocmd!
+ autocmd VimEnter * call autoload#statusline#ActivateStatusline()
+ autocmd ColorScheme * call autoload#statusline#UpdateStslineColors()
+ autocmd VimEnter * redrawstatus!
+ augroup END
+
+ set laststatus=2
+ set statusline=%!StatusLine()
+
+ augroup StatuslineOverride
+ autocmd!
+ autocmd FileType help,gitcommit,netrw setlocal statusline=%f\ %h%m%r%=%-14.(%l,%c%V%)\ %P
+ augroup END
+endif
+
+" ============================================================================
+" GUI Configuration
+" ============================================================================
+if has('gui_running')
+ color slate
+
+ if has('mac')
+ set guifont=Menlo\ Regular:h14
+ elseif has('win32')
+ set guifont=Consolas:h12
+ else
+ set guifont=Monospace\ 11
+ endif
+
+ set guioptions-=T
+ set guioptions-=r
+ set guioptions-=L
+ set guioptions-=m
+
+ autocmd VimEnter * :Lexplore | wincmd p
+endif
+
+" ============================================================================
+" Final Setup
+" ============================================================================
+syntax on
+filetype plugin indent on
+setlocal keywordprg=:help
+setlocal iskeyword+=:
+
+" Load matchit for better % matching
+if !exists('g:loaded_matchit') && findfile('plugin/matchit.vim', &rtp) ==# ''
+ runtime! macros/matchit.vim
+endif
+
+" ============================================================================
diff --git a/linux/home/.vnc/config b/linux/home/.vnc/config
new file mode 100644
index 0000000..5c1462c
--- /dev/null
+++ b/linux/home/.vnc/config
@@ -0,0 +1,4 @@
+session=bspwm
+geometry=1920x1080
+localhost
+alwaysshared
diff --git a/linux/home/.vnc/xstartup b/linux/home/.vnc/xstartup
new file mode 100755
index 0000000..bbbc0db
--- /dev/null
+++ b/linux/home/.vnc/xstartup
@@ -0,0 +1,16 @@
+#!/data/data/com.termux/files/usr/bin/sh
+
+## file is executed during VNC server
+## startup.
+
+# Launch terminal emulator Aterm.
+# Requires package 'aterm'.
+
+unset SESSION_MANAGER
+unset DBUS_SESSION_BUS_ADDRESS
+
+export PULSE_SERVER=127.0.0.1 && pulseaudio --start --disable-shm=1 --exit-idle-time=-1
+
+# Launch Window Manager/Desktop Environment
+bspwm &
+
diff --git a/macos/Library/Preferences/com.example.plist b/macos/Library/Preferences/com.example.plist
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/macos/Library/Preferences/com.example.plist
diff --git a/macos/home/.zshrc b/macos/home/.zshrc
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/macos/home/.zshrc
diff --git a/profile/dev/README.md b/profile/dev/README.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/profile/dev/README.md
diff --git a/profile/minimal/README.md b/profile/minimal/README.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/profile/minimal/README.md
diff --git a/profile/server/README.md b/profile/server/README.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/profile/server/README.md
diff --git a/windows/AppData/windows-terminal/settings.json b/windows/AppData/windows-terminal/settings.json
new file mode 100644
index 0000000..71bf5bd
--- /dev/null
+++ b/windows/AppData/windows-terminal/settings.json
@@ -0,0 +1,590 @@
+{
+ "$help": "https://aka.ms/terminal-documentation",
+ "$schema": "https://aka.ms/terminal-profiles-schema",
+ "actions":
+ [
+ { "command": "scrollDownPage", "keys": "ctrl+shift+pgdn" },
+ { "command": "unbound", "keys": "ctrl+v" },
+ { "command": "unbound", "keys": "ctrl+c" },
+ { "command": { "action": "copy", "singleLine": false } },
+ { "command": { "action": "scrollUp" }, "keys": "ctrl+shift+up" },
+ { "command": "find", "keys": "ctrl+shift+f" },
+ { "command": "paste" },
+
+ { "command": { "action": "splitPane", "split": "vertical", "splitMode": "duplicate" }, "keys": "alt+shift+plus" },
+ { "command": { "action": "splitPane", "split": "horizontal", "splitMode": "duplicate" }, "keys": "alt+shift+-" },
+
+ { "command": { "action": "nextTab", "tabSwitcherMode": "mru" }, "keys": "ctrl+tab" },
+ { "command": "openTabRenamer", "keys": "ctrl+shift+r" },
+
+ { "command": { "action": "splitPane", "split": "auto", "splitMode": "duplicate" }, "keys": "alt+shift+d" },
+ { "command": { "action": "scrollDown" }, "keys": "ctrl+shift+down" },
+
+ { "command": "scrollUpPage", "keys": "ctrl+shift+pgup" },
+
+ { "command": { "action": "moveFocus", "direction": "left" }, "keys": "alt+h" },
+ { "command": { "action": "moveFocus", "direction": "down" }, "keys": "alt+j" },
+ { "command": { "action": "moveFocus", "direction": "up" }, "keys": "alt+k" },
+ { "command": { "action": "moveFocus", "direction": "right" }, "keys": "alt+l" },
+
+ { "command": { "action": "resizePane", "direction": "left" }, "keys": "alt+shift+y" },
+ { "command": { "action": "resizePane", "direction": "down" }, "keys": "alt+shift+u" },
+ { "command": { "action": "resizePane", "direction": "up" }, "keys": "alt+shift+i" },
+ { "command": { "action": "resizePane", "direction": "right" }, "keys": "alt+shift+o" },
+
+ { "command": { "action": "swapPane", "direction": "left" }, "keys": "alt+shift+h" },
+ { "command": { "action": "swapPane", "direction": "down" }, "keys": "alt+shift+j" },
+ { "command": { "action": "swapPane", "direction": "up" }, "keys": "alt+shift+k" },
+ { "command": { "action": "swapPane", "direction": "right" }, "keys": "alt+shift+l" },
+
+ { "command": "togglePaneZoom", "keys": "alt+f" }
+ ],
+ "copyFormatting": "none",
+ "copyOnSelect": false,
+ "defaultProfile": "{f19ccc1d-fe9a-4e79-ad18-49276786d144}",
+ "launchMode": "default",
+ "profiles":
+ {
+ "defaults":
+ {
+ "backgroundImage": null,
+ "colorScheme": "Gruvbox Dark",
+ "font":
+ {
+ "face": "CaskaydiaCove NF"
+ },
+ "historySize": 9001,
+ "opacity": 100,
+ "padding": "0",
+ "snapOnInput": false,
+ "useAcrylic": false,
+ "closeOnExit":"graceful",
+ "intenseTextStyle": "all"
+ },
+ "list":
+ [
+ {
+ "backgroundImage": "C:\\Users\\srdusr\\wallpapers\\download3.jpg",
+ "backgroundImageOpacity": 0.089999999999999997,
+ "colorScheme": "Tokyo Night",
+ "commandline": "C:\\Program Files\\PowerShell\\7\\pwsh.exe",
+ "experimental.retroTerminalEffect": false,
+ "font":
+ {
+ "face": "CaskaydiaCove NF"
+ },
+ "guid": "{f19ccc1d-fe9a-4e79-ad18-49276786d144}",
+ "historySize": 99999,
+ "icon": "ms-appx:///ProfileIcons/pwsh.png",
+ "name": "PowerShell 7 - BG",
+ "opacity": 100,
+ "padding": "0",
+ "scrollbarState": "visible",
+ "startingDirectory": "%USERPROFILE%",
+ "suppressApplicationTitle": false,
+ "useAcrylic": false,
+ "closeOnExit":"graceful",
+ "intenseTextStyle": "all"
+ },
+ {
+ "backgroundImage": "C:\\Users\\srdusr\\wallpapers\\photo-1533484306792-cf313c2b8ab0 (2).jpg",
+ "backgroundImageOpacity": 0.19,
+ "bellStyle": "taskbar",
+ "colorScheme": "Tokyo Night",
+ "font":
+ {
+ "face": "CaskaydiaCove NF"
+ },
+ "guid": "{07b52e3e-de2c-5db4-bd2d-ba144ed6c273}",
+ "hidden": false,
+ "historySize": 99999,
+ "name": "Ubuntu-20.04",
+ "source": "Windows.Terminal.Wsl",
+ "closeOnExit":"graceful",
+ "intenseTextStyle": "all"
+ },
+ {
+ "guid": "{f9ceaf27-504c-58d7-927c-d1d6a7ac7d3c}",
+ "hidden": false,
+ "backgroundImage": "C:\\Users\\srdusr\\wallpapers\\photo-1533484306792-cf313c2b8ab0 (2).jpg",
+ "backgroundImageOpacity": 0.19,
+ "name": "Ubuntu 22.04.1 LTS",
+ "source": "CanonicalGroupLimited.Ubuntu22.04LTS_79rhkp1fndgsc",
+ "colorScheme": "Tokyo Night",
+ "font":
+ {
+ "face": "CaskaydiaCove NF"
+ },
+ "closeOnExit":"graceful",
+ "intenseTextStyle": "all"
+ },
+ {
+ "font":
+ {
+ "face": "CaskaydiaCove NF"
+ },
+ "guid": "{574e775e-4f2a-5b96-ac1e-a2962a402336}",
+ "hidden": false,
+ "historySize": 99999,
+ "name": "PowerShell 7 - NO BG",
+ "padding": "0",
+ "source": "Windows.Terminal.PowershellCore",
+ "suppressApplicationTitle": false,
+ "useAcrylic": false,
+ "closeOnExit":"graceful",
+ "intenseTextStyle": "all"
+ },
+ {
+ "font":
+ {
+ "face": "CaskaydiaCove NF"
+ },
+ "guid": "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}",
+ "hidden": false,
+ "historySize": 99999,
+ "name": "Command Prompt",
+ "closeOnExit":"graceful",
+ "intenseTextStyle": "all"
+ },
+ {
+ "font":
+ {
+ "face": "CaskaydiaCove NF"
+ },
+ "guid": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
+ "hidden": false,
+ "historySize": 99999,
+ "name": "PowerShell 5",
+ "closeOnExit":"graceful",
+ "intenseTextStyle": "all"
+ },
+ {
+ "font":
+ {
+ "face": "CaskaydiaCove NF"
+ },
+ "guid": "{46ca431a-3a87-5fb3-83cd-11ececc031d2}",
+ "hidden": false,
+ "name": "Kali Linux",
+ "source": "Windows.Terminal.Wsl",
+ "closeOnExit":"graceful",
+ "intenseTextStyle": "all"
+ },
+ {
+ "font":
+ {
+ "face": "CaskaydiaCove NF"
+ },
+ "guid": "{b453ae62-4e3d-5e58-b989-0a998ec441b8}",
+ "hidden": false,
+ "historySize": 99999,
+ "name": "Azure Cloud Shell",
+ "source": "Windows.Terminal.Azure",
+ "closeOnExit":"graceful",
+ "intenseTextStyle": "all"
+ },
+ {
+ "backgroundImage": null,
+ "backgroundImageOpacity": 0.29999999999999999,
+ "commandline": "C:\\GBox\\Applications\\Tools\\Applications\\Neovim\\nvim-win64\\0.6.0\\bin\\nvim.exe",
+ "font":
+ {
+ "face": "CaskaydiaCove NF"
+ },
+ "guid": "{0caa0dad-35be-5f56-a8ff-afceeeaa6102}",
+ "hidden": false,
+ "historySize": 99999,
+ "name": "Neovim",
+ "padding": "0",
+ "useAcrylic": false,
+ "closeOnExit":"graceful",
+ "intenseTextStyle": "all"
+ },
+ {
+ "guid": "{d701ea23-d39a-51a9-9966-855f1c8051f1}",
+ "hidden": false,
+ "name": "Developer Command Prompt for VS 2022 [Preview]",
+ "source": "Windows.Terminal.VisualStudio",
+ "closeOnExit":"graceful",
+ "intenseTextStyle": "all"
+ },
+ {
+ "guid": "{960e8c7a-d53d-5f77-ad6f-daf9dfeb597d}",
+ "hidden": false,
+ "name": "Developer PowerShell for VS 2022 [Preview]",
+ "source": "Windows.Terminal.VisualStudio",
+ "closeOnExit":"graceful",
+ "intenseTextStyle": "all"
+ },
+ {
+ "guid": "{fddd4872-371a-5869-85aa-657716af07fa}",
+ "hidden": true,
+ "name": "Developer Command Prompt for VS 2022",
+ "source": "Windows.Terminal.VisualStudio",
+ "closeOnExit":"graceful",
+ "intenseTextStyle": "all"
+ },
+ {
+ "guid": "{e1c4148c-0ee2-5cd8-802c-8a170e188519}",
+ "hidden": true,
+ "name": "Developer PowerShell for VS 2022",
+ "source": "Windows.Terminal.VisualStudio",
+ "closeOnExit":"graceful",
+ "intenseTextStyle": "all"
+ }
+ ]
+ },
+ "schemes":
+ [
+ {
+ "background": "#0C0C0C",
+ "black": "#0C0C0C",
+ "blue": "#0037DA",
+ "brightBlack": "#767676",
+ "brightBlue": "#3B78FF",
+ "brightCyan": "#61D6D6",
+ "brightGreen": "#16C60C",
+ "brightPurple": "#B4009E",
+ "brightRed": "#E74856",
+ "brightWhite": "#F2F2F2",
+ "brightYellow": "#F9F1A5",
+ "cursorColor": "#FFFFFF",
+ "cyan": "#3A96DD",
+ "foreground": "#CCCCCC",
+ "green": "#13A10E",
+ "name": "Campbell",
+ "purple": "#881798",
+ "red": "#C50F1F",
+ "selectionBackground": "#FFFFFF",
+ "white": "#CCCCCC",
+ "yellow": "#C19C00"
+ },
+ {
+ "background": "#012456",
+ "black": "#0C0C0C",
+ "blue": "#0037DA",
+ "brightBlack": "#767676",
+ "brightBlue": "#3B78FF",
+ "brightCyan": "#61D6D6",
+ "brightGreen": "#16C60C",
+ "brightPurple": "#B4009E",
+ "brightRed": "#E74856",
+ "brightWhite": "#F2F2F2",
+ "brightYellow": "#F9F1A5",
+ "cursorColor": "#FFFFFF",
+ "cyan": "#3A96DD",
+ "foreground": "#CCCCCC",
+ "green": "#13A10E",
+ "name": "Campbell Powershell",
+ "purple": "#881798",
+ "red": "#C50F1F",
+ "selectionBackground": "#FFFFFF",
+ "white": "#CCCCCC",
+ "yellow": "#C19C00"
+ },
+ {
+ "background": "#282828",
+ "black": "#282828",
+ "blue": "#458588",
+ "brightBlack": "#928374",
+ "brightBlue": "#83A598",
+ "brightCyan": "#8EC07C",
+ "brightGreen": "#B8BB26",
+ "brightPurple": "#D3869B",
+ "brightRed": "#FB4934",
+ "brightWhite": "#EBDBB2",
+ "brightYellow": "#FABD2F",
+ "cursorColor": "#FFFFFF",
+ "cyan": "#689D6A",
+ "foreground": "#EBDBB2",
+ "green": "#98971A",
+ "name": "Gruvbox Dark",
+ "purple": "#B16286",
+ "red": "#CC241D",
+ "selectionBackground": "#FFFFFF",
+ "white": "#A89984",
+ "yellow": "#D79921"
+ },
+ {
+ "background": "#282C34",
+ "black": "#282C34",
+ "blue": "#61AFEF",
+ "brightBlack": "#5A6374",
+ "brightBlue": "#61AFEF",
+ "brightCyan": "#56B6C2",
+ "brightGreen": "#98C379",
+ "brightPurple": "#C678DD",
+ "brightRed": "#E06C75",
+ "brightWhite": "#DCDFE4",
+ "brightYellow": "#E5C07B",
+ "cursorColor": "#FFFFFF",
+ "cyan": "#56B6C2",
+ "foreground": "#DCDFE4",
+ "green": "#98C379",
+ "name": "One Half Dark",
+ "purple": "#C678DD",
+ "red": "#E06C75",
+ "selectionBackground": "#FFFFFF",
+ "white": "#DCDFE4",
+ "yellow": "#E5C07B"
+ },
+ {
+ "background": "#282C34",
+ "black": "#282C34",
+ "blue": "#61AFEF",
+ "brightBlack": "#282C34",
+ "brightBlue": "#61AFEF",
+ "brightCyan": "#56B6C2",
+ "brightGreen": "#98C379",
+ "brightPurple": "#C678DD",
+ "brightRed": "#E06C75",
+ "brightWhite": "#DCDFE4",
+ "brightYellow": "#E5C07B",
+ "cursorColor": "#FFFFFF",
+ "cyan": "#56B6C2",
+ "foreground": "#DCDFE4",
+ "green": "#98C379",
+ "name": "One Half Dark2",
+ "purple": "#C678DD",
+ "red": "#E06C75",
+ "selectionBackground": "#FFFFFF",
+ "white": "#DCDFE4",
+ "yellow": "#E5C07B"
+ },
+ {
+ "background": "#FAFAFA",
+ "black": "#383A42",
+ "blue": "#0184BC",
+ "brightBlack": "#4F525D",
+ "brightBlue": "#61AFEF",
+ "brightCyan": "#56B5C1",
+ "brightGreen": "#98C379",
+ "brightPurple": "#C577DD",
+ "brightRed": "#DF6C75",
+ "brightWhite": "#FFFFFF",
+ "brightYellow": "#E4C07A",
+ "cursorColor": "#4F525D",
+ "cyan": "#0997B3",
+ "foreground": "#383A42",
+ "green": "#50A14F",
+ "name": "One Half Light",
+ "purple": "#A626A4",
+ "red": "#E45649",
+ "selectionBackground": "#FFFFFF",
+ "white": "#FAFAFA",
+ "yellow": "#C18301"
+ },
+ {
+ "background": "#FAFAFA",
+ "black": "#383A42",
+ "blue": "#0184BC",
+ "brightBlack": "#383A42",
+ "brightBlue": "#0184BC",
+ "brightCyan": "#0997B3",
+ "brightGreen": "#50A14F",
+ "brightPurple": "#A626A4",
+ "brightRed": "#E45649",
+ "brightWhite": "#FAFAFA",
+ "brightYellow": "#C18401",
+ "cursorColor": "#FFFFFF",
+ "cyan": "#0997B3",
+ "foreground": "#383A42",
+ "green": "#50A14F",
+ "name": "One Half Light2",
+ "purple": "#A626A4",
+ "red": "#E45649",
+ "selectionBackground": "#FFFFFF",
+ "white": "#FAFAFA",
+ "yellow": "#C18401"
+ },
+ {
+ "background": "#002B36",
+ "black": "#002B36",
+ "blue": "#268BD2",
+ "brightBlack": "#073642",
+ "brightBlue": "#839496",
+ "brightCyan": "#93A1A1",
+ "brightGreen": "#586E75",
+ "brightPurple": "#6C71C4",
+ "brightRed": "#CB4B16",
+ "brightWhite": "#FDF6E3",
+ "brightYellow": "#657B83",
+ "cursorColor": "#FFFFFF",
+ "cyan": "#2AA198",
+ "foreground": "#839496",
+ "green": "#859900",
+ "name": "Solarized Dark",
+ "purple": "#D33682",
+ "red": "#DC322F",
+ "selectionBackground": "#FFFFFF",
+ "white": "#EEE8D5",
+ "yellow": "#B58900"
+ },
+ {
+ "background": "#001E27",
+ "black": "#002831",
+ "blue": "#2176C7",
+ "brightBlack": "#475B62",
+ "brightBlue": "#708284",
+ "brightCyan": "#819090",
+ "brightGreen": "#475B62",
+ "brightPurple": "#5956BA",
+ "brightRed": "#BD3613",
+ "brightWhite": "#FCF4DC",
+ "brightYellow": "#536870",
+ "cursorColor": "#FFFFFF",
+ "cyan": "#259286",
+ "foreground": "#708284",
+ "green": "#738A05",
+ "name": "Solarized Dark - Patched",
+ "purple": "#C61C6F",
+ "red": "#D11C24",
+ "selectionBackground": "#FFFFFF",
+ "white": "#EAE3CB",
+ "yellow": "#A57706"
+ },
+ {
+ "background": "#FDF6E3",
+ "black": "#002B36",
+ "blue": "#268BD2",
+ "brightBlack": "#073642",
+ "brightBlue": "#839496",
+ "brightCyan": "#93A1A1",
+ "brightGreen": "#586E75",
+ "brightPurple": "#6C71C4",
+ "brightRed": "#CB4B16",
+ "brightWhite": "#FDF6E3",
+ "brightYellow": "#657B83",
+ "cursorColor": "#002B36",
+ "cyan": "#2AA198",
+ "foreground": "#657B83",
+ "green": "#859900",
+ "name": "Solarized Light",
+ "purple": "#D33682",
+ "red": "#DC322F",
+ "selectionBackground": "#FFFFFF",
+ "white": "#EEE8D5",
+ "yellow": "#B58900"
+ },
+ {
+ "background": "#000000",
+ "black": "#000000",
+ "blue": "#3465A4",
+ "brightBlack": "#555753",
+ "brightBlue": "#729FCF",
+ "brightCyan": "#34E2E2",
+ "brightGreen": "#8AE234",
+ "brightPurple": "#AD7FA8",
+ "brightRed": "#EF2929",
+ "brightWhite": "#EEEEEC",
+ "brightYellow": "#FCE94F",
+ "cursorColor": "#FFFFFF",
+ "cyan": "#06989A",
+ "foreground": "#D3D7CF",
+ "green": "#4E9A06",
+ "name": "Tango Dark",
+ "purple": "#75507B",
+ "red": "#CC0000",
+ "selectionBackground": "#FFFFFF",
+ "white": "#D3D7CF",
+ "yellow": "#C4A000"
+ },
+ {
+ "background": "#FFFFFF",
+ "black": "#000000",
+ "blue": "#3465A4",
+ "brightBlack": "#555753",
+ "brightBlue": "#729FCF",
+ "brightCyan": "#34E2E2",
+ "brightGreen": "#8AE234",
+ "brightPurple": "#AD7FA8",
+ "brightRed": "#EF2929",
+ "brightWhite": "#EEEEEC",
+ "brightYellow": "#FCE94F",
+ "cursorColor": "#000000",
+ "cyan": "#06989A",
+ "foreground": "#555753",
+ "green": "#4E9A06",
+ "name": "Tango Light",
+ "purple": "#75507B",
+ "red": "#CC0000",
+ "selectionBackground": "#FFFFFF",
+ "white": "#D3D7CF",
+ "yellow": "#C4A000"
+ },
+ {
+ "background": "#1A1B2C",
+ "black": "#414868",
+ "blue": "#7AA2F7",
+ "brightBlack": "#414868",
+ "brightBlue": "#7AA2F7",
+ "brightCyan": "#7DCFFF",
+ "brightGreen": "#73DACA",
+ "brightPurple": "#BB9AF7",
+ "brightRed": "#F7768E",
+ "brightWhite": "#C0CAF5",
+ "brightYellow": "#E0AF68",
+ "cursorColor": "#C0CAF5",
+ "cyan": "#7DCFFF",
+ "foreground": "#A9B1DC",
+ "green": "#73DACA",
+ "name": "Tokyo Night",
+ "purple": "#BB9AF7",
+ "red": "#F7768E",
+ "selectionBackground": "#28344A",
+ "white": "#C0CAF5",
+ "yellow": "#E0AF68"
+ },
+ {
+ "background": "#24283B",
+ "black": "#414868",
+ "blue": "#7AA2F7",
+ "brightBlack": "#414868",
+ "brightBlue": "#7AA2F7",
+ "brightCyan": "#7DCFFF",
+ "brightGreen": "#73DACA",
+ "brightPurple": "#BB9AF7",
+ "brightRed": "#F7768E",
+ "brightWhite": "#C0CAF5",
+ "brightYellow": "#E0AF68",
+ "cursorColor": "#C0CAF5",
+ "cyan": "#7DCFFF",
+ "foreground": "#A9B1DC",
+ "green": "#73DACA",
+ "name": "Tokyo Night Storm",
+ "purple": "#BB9AF7",
+ "red": "#F7768E",
+ "selectionBackground": "#28344A",
+ "white": "#C0CAF5",
+ "yellow": "#E0AF68"
+ },
+ {
+ "background": "#000000",
+ "black": "#000000",
+ "blue": "#000080",
+ "brightBlack": "#808080",
+ "brightBlue": "#0000FF",
+ "brightCyan": "#00FFFF",
+ "brightGreen": "#00FF00",
+ "brightPurple": "#FF00FF",
+ "brightRed": "#FF0000",
+ "brightWhite": "#FFFFFF",
+ "brightYellow": "#FFFF00",
+ "cursorColor": "#FFFFFF",
+ "cyan": "#008080",
+ "foreground": "#C0C0C0",
+ "green": "#008000",
+ "name": "Vintage",
+ "purple": "#800080",
+ "red": "#800000",
+ "selectionBackground": "#FFFFFF",
+ "white": "#C0C0C0",
+ "yellow": "#808000"
+ }
+ ],
+ "showTabsInTitlebar": true,
+ "showTerminalTitleInTitlebar": true,
+ "tabWidthMode": "titleLength",
+ "trimBlockSelection": true,
+ "useAcrylicInTabRow": false
+}
diff --git a/windows/Documents/PowerShell/Microsoft.PowerShell_profile.ps1 b/windows/Documents/PowerShell/Microsoft.PowerShell_profile.ps1
new file mode 100644
index 0000000..40b5879
--- /dev/null
+++ b/windows/Documents/PowerShell/Microsoft.PowerShell_profile.ps1
@@ -0,0 +1,284 @@
+# Dotfiles Management System
+if (Test-Path "$HOME\.cfg" -and Test-Path "$HOME\.cfg\refs") {
+
+ # Core git wrapper with repository as work-tree
+ function _config {
+ param(
+ [Parameter(Mandatory=$true, ValueFromRemainingArguments=$true)]
+ [String[]]$Args
+ )
+ git --git-dir="$HOME\.cfg" --work-tree="$HOME\.cfg" @Args
+ }
+
+ # Detect OS (cross-platform, PowerShell-native)
+ $osPlatform = [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform
+ if ($osPlatform([System.Runtime.InteropServices.OSPlatform]::Windows)) {
+ $global:CFG_OS = "windows"
+ } elseif ($osPlatform([System.Runtime.InteropServices.OSPlatform]::Linux)) {
+ $global:CFG_OS = "linux"
+ } elseif ($osPlatform([System.Runtime.InteropServices.OSPlatform]::OSX)) {
+ $global:CFG_OS = "macos"
+ } else {
+ $global:CFG_OS = "other"
+ }
+
+ # Map system path to repository path
+ function _repo_path {
+ param([string]$FilePath)
+
+ # If it's an absolute path that's not in HOME, handle it specially
+ if (($FilePath.StartsWith("\\") -or $FilePath.Contains(":")) -and -not $FilePath.StartsWith($HOME)) {
+ return "$CFG_OS/" + ($FilePath -replace '^[A-Z]:\\', '' -replace '\\', '/')
+ }
+
+ # Check for paths that should go to the repository root
+ if ($FilePath -match '^(common|linux|macos|windows|profile)/.*|^README\.md$') {
+ return $FilePath -replace '\\', '/'
+ }
+
+ # Remove HOME prefix if present
+ if ($FilePath.StartsWith($HOME)) {
+ $FilePath = $FilePath.Substring($HOME.Length + 1)
+ }
+
+ # Default: put under OS-specific home
+ return "$CFG_OS/home/" + ($FilePath -replace '\\', '/')
+ }
+
+ # Map repository path back to system path
+ function _sys_path {
+ param([string]$RepoPath)
+
+ $osPathPattern = "$CFG_OS/"
+
+ # Handle OS-specific files that are not in the home subdirectory
+ if ($RepoPath.StartsWith($osPathPattern) -and $RepoPath -notmatch '/home/') {
+ return ($RepoPath.Substring($osPathPattern.Length) -replace '/', '\\')
+ }
+
+ switch -Wildcard ($RepoPath) {
+ "common/config/*" {
+ $file = $RepoPath.Substring("common/config/".Length)
+ switch ($CFG_OS) {
+ "linux" { return Join-Path ($env:XDG_CONFIG_HOME ?? "$HOME\.config") $file }
+ "macos" { return Join-Path "$HOME\Library\Application Support" $file }
+ "windows" { return Join-Path $env:LOCALAPPDATA $file }
+ default { return Join-Path "$HOME\.config" $file }
+ }
+ }
+ "common/assets/*" { return Join-Path "$HOME\.cfg" $RepoPath }
+ "common/*" { return Join-Path $HOME ($RepoPath.Substring("common/".Length)) }
+ "*/home/*" { return Join-Path $HOME ($RepoPath.Substring($RepoPath.IndexOf("home/") + 5)) }
+ "profile/*" { return Join-Path "$HOME\.cfg" $RepoPath }
+ "README.md" { return Join-Path "$HOME\.cfg" $RepoPath }
+ default { return Join-Path "$HOME\.cfg" $RepoPath }
+ }
+ }
+
+ # Prompts for administrator permissions if needed and runs the command
+ function _admin_prompt {
+ param(
+ [Parameter(Mandatory=$true, ValueFromRemainingArguments=$true)]
+ [String[]]$Command
+ )
+ if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
+ Write-Host "Warning: This action requires administrator privileges." -ForegroundColor Yellow
+ Start-Process powershell.exe -ArgumentList "-NoProfile", "-Command", "Set-Location '$PWD'; & $Command" -Verb RunAs
+ } else {
+ & $Command
+ }
+ }
+
+ # Main config command
+ function config {
+ param(
+ [string]$Command,
+ [string]$TargetDir = "",
+ [Parameter(ValueFromRemainingArguments=$true)]
+ [string[]]$Args
+ )
+
+ # Parse --target flag for add command
+ if ($Command -eq "add" -and $Args.Count -gt 0) {
+ $i = 0
+ while ($i -lt $Args.Count) {
+ if ($Args[$i] -eq "--target" -or $Args[$i] -eq "-t") {
+ if ($i + 1 -lt $Args.Count) {
+ $TargetDir = $Args[$i + 1]
+ $Args = $Args[0..($i-1)] + $Args[($i+2)..($Args.Count-1)]
+ break
+ } else {
+ Write-Host "Error: --target requires a directory argument" -ForegroundColor Red
+ return
+ }
+ }
+ $i++
+ }
+ }
+
+ switch ($Command) {
+ "add" {
+ foreach ($file in $Args) {
+ if (-not $TargetDir) {
+ $repoPath = _repo_path $file
+ } else {
+ $fileName = if ($file.Contains("\\") -or $file.Contains(":")) { Split-Path $file -Leaf } else { $file }
+ $repoPath = "$TargetDir/$fileName" -replace '\\', '/'
+ }
+
+ $fullRepoPath = Join-Path "$HOME\.cfg" $repoPath
+ $dir = Split-Path $fullRepoPath
+ if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null }
+ Copy-Item -Path $file -Destination $fullRepoPath -Recurse -Force
+ _config add $repoPath
+ Write-Host "Added: $file -> $repoPath" -ForegroundColor Green
+ }
+ }
+ "rm" {
+ $rmOpts = @()
+ $fileList = @()
+
+ foreach ($arg in $Args) {
+ if ($arg.StartsWith("-")) {
+ $rmOpts += $arg
+ } else {
+ $fileList += $arg
+ }
+ }
+
+ foreach ($file in $fileList) {
+ $repoPath = _repo_path $file
+ if ($rmOpts -contains "-r") {
+ _config rm --cached -r $repoPath
+ } else {
+ _config rm --cached $repoPath
+ }
+ Remove-Item -Path $file -Recurse:($rmOpts -contains "-r") -Force
+ Write-Host "Removed: $file" -ForegroundColor Yellow
+ }
+ }
+ "sync" {
+ $direction = if ($Args.Count -gt 0) { $Args[0] } else { "to-repo" }
+ _config ls-files | ForEach-Object {
+ $repoFile = $_
+ $sysFile = _sys_path $repoFile
+ $fullRepoPath = Join-Path "$HOME\.cfg" $repoFile
+
+ if ($direction -eq "to-repo") {
+ if ((Test-Path $sysFile) -and (Test-Path $fullRepoPath)) {
+ $diff = Compare-Object (Get-Content $fullRepoPath -ErrorAction SilentlyContinue) (Get-Content $sysFile -ErrorAction SilentlyContinue)
+ if ($diff) {
+ Copy-Item $sysFile $fullRepoPath -Force
+ Write-Host "Synced to repo: $sysFile" -ForegroundColor Cyan
+ }
+ }
+ } elseif ($direction -eq "from-repo") {
+ if ((Test-Path $fullRepoPath)) {
+ $diff = if (Test-Path $sysFile) { Compare-Object (Get-Content $fullRepoPath -ErrorAction SilentlyContinue) (Get-Content $sysFile -ErrorAction SilentlyContinue) } else { $true }
+ if ($diff) {
+ $destDir = Split-Path $sysFile
+ if (($sysFile.StartsWith('\\') -or $sysFile.Contains(':')) -and -not $sysFile.StartsWith($HOME)) {
+ _admin_prompt "Copy-Item '$fullRepoPath' '$sysFile' -Recurse -Force"
+ } else {
+ if (-not (Test-Path $destDir)) { New-Item -ItemType Directory -Path $destDir -Force | Out-Null }
+ Copy-Item $fullRepoPath $sysFile -Recurse -Force
+ }
+ Write-Host "Synced from repo: $sysFile" -ForegroundColor Cyan
+ }
+ }
+ }
+ }
+ }
+ "status" {
+ $autoSynced = @()
+ _config ls-files | ForEach-Object {
+ $repoFile = $_
+ $sysFile = _sys_path $repoFile
+ $fullRepoPath = Join-Path "$HOME\.cfg" $repoFile
+ if ((Test-Path $sysFile) -and (Test-Path $fullRepoPath)) {
+ $diff = Compare-Object (Get-Content $fullRepoPath -ErrorAction SilentlyContinue) (Get-Content $sysFile -ErrorAction SilentlyContinue)
+ if ($diff) {
+ Copy-Item $sysFile $fullRepoPath -Force
+ $autoSynced += $repoFile
+ }
+ }
+ }
+ if ($autoSynced.Count -gt 0) {
+ Write-Host "=== Auto-synced Files ===" -ForegroundColor Magenta
+ foreach ($repoFile in $autoSynced) {
+ Write-Host "synced: $(_sys_path $repoFile) -> $repoFile" -ForegroundColor Cyan
+ }
+ Write-Host
+ }
+ _config status
+ Write-Host
+ }
+ "deploy" {
+ _config ls-files | ForEach-Object {
+ $repoFile = $_
+ $sysFile = _sys_path $repoFile
+ $fullRepoPath = Join-Path "$HOME\.cfg" $repoFile
+
+ if ((Test-Path $fullRepoPath) -and $sysFile) {
+ $destDir = Split-Path $sysFile
+ if (($sysFile.StartsWith('\\') -or $sysFile.Contains(':')) -and -not $sysFile.StartsWith($HOME)) {
+ _admin_prompt "New-Item -ItemType Directory -Path '$destDir' -Force; Copy-Item '$fullRepoPath' '$sysFile' -Recurse -Force"
+ } else {
+ if (-not (Test-Path $destDir)) { New-Item -ItemType Directory -Path $destDir -Force | Out-Null }
+ Copy-Item $fullRepoPath $sysFile -Recurse -Force
+ }
+ Write-Host "Deployed: $repoFile -> $sysFile" -ForegroundColor Green
+ }
+ }
+ }
+ "checkout" {
+ Write-Host "Checking out dotfiles from .cfg..." -ForegroundColor Blue
+ _config ls-files | ForEach-Object {
+ $repoFile = $_
+ $sysFile = _sys_path $repoFile
+ $fullRepoPath = Join-Path "$HOME\.cfg" $repoFile
+
+ if ((Test-Path $fullRepoPath) -and $sysFile) {
+ $destDir = Split-Path $sysFile
+ if (($sysFile.StartsWith('\\') -or $sysFile.Contains(':')) -and -not $sysFile.StartsWith($HOME)) {
+ _admin_prompt "New-Item -ItemType Directory -Path '$destDir' -Force; Copy-Item '$fullRepoPath' '$sysFile' -Recurse -Force"
+ } else {
+ if (-not (Test-Path $destDir)) { New-Item -ItemType Directory -Path $destDir -Force | Out-Null }
+ Copy-Item $fullRepoPath $sysFile -Recurse -Force
+ }
+ Write-Host "Checked out: $repoFile -> $sysFile" -ForegroundColor Green
+ }
+ }
+ }
+ "backup" {
+ $timestamp = Get-Date -Format "yyyyMMddHHmmss"
+ $backupDir = Join-Path $HOME ".dotfiles_backup\$timestamp"
+ Write-Host "Backing up existing dotfiles to $backupDir..." -ForegroundColor Blue
+
+ _config ls-files | ForEach-Object {
+ $repoFile = $_
+ $sysFile = _sys_path $repoFile
+ if (Test-Path $sysFile) {
+ $destDirFull = Join-Path $backupDir (Split-Path $repoFile)
+ if (-not (Test-Path $destDirFull)) { New-Item -ItemType Directory -Path $destDirFull -Force | Out-Null }
+ Copy-Item $sysFile (Join-Path $backupDir $repoFile) -Recurse -Force
+ }
+ }
+ Write-Host "Backup complete. To restore, copy files from $backupDir to their original locations." -ForegroundColor Green
+ }
+ default {
+ _config $Command @Args
+ }
+ }
+ }
+}
+
+# Shows navigable menu of all options when hitting Tab
+Set-PSReadlineKeyHandler -Key Tab -Function MenuComplete
+
+# Autocompletion for arrow keys
+Set-PSReadlineKeyHandler -Key UpArrow -Function HistorySearchBackward
+Set-PSReadlineKeyHandler -Key DownArrow -Function HistorySearchForward
+
+New-Alias vi nvim.exe
+
diff --git a/windows/Documents/PowerShell/bloatware.ps1 b/windows/Documents/PowerShell/bloatware.ps1
new file mode 100644
index 0000000..2ee078a
--- /dev/null
+++ b/windows/Documents/PowerShell/bloatware.ps1
@@ -0,0 +1,332 @@
+# bloatware.ps1
+
+# Check if the powershell-yaml module is installed, if not, install it
+if (-not (Get-Module powershell-yaml -ListAvailable)) {
+ $policy = Get-PSRepository -Name 'PSGallery' | Select-Object -ExpandProperty InstallationPolicy
+ Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted
+ Install-Module powershell-yaml
+ Set-PSRepository -Name 'PSGallery' -InstallationPolicy $policy
+}
+
+Import-Module powershell-yaml
+
+# Define the path to the YAML file
+$yamlFilePath = "$HOME\packages.yml"
+
+# Parse the YAML file
+$packages = ConvertFrom-Yaml -Path $yamlFilePath
+$bloatware = $packages.bloatware
+$defaultApps = $packages.defaultApps
+
+# Check if Registry key exists
+function Check-RegistryKeyExists {
+ param(
+ [Parameter(Mandatory = $true)]
+ [string]$KeyPath
+ )
+
+ if (Test-Path $KeyPath) {
+ Write-Host "Registry key exists: $KeyPath"
+ return $true
+ } else {
+ Write-Host "Registry key does not exist: $KeyPath"
+ return $false
+ }
+}
+
+# Helper functions ------------------------
+function force-mkdir($path) {
+ if (!(Test-Path $path)) {
+ Write-Host "-- Creating full path to: " $path -ForegroundColor White -BackgroundColor DarkGreen
+ New-Item -ItemType Directory -Force -Path $path
+ }
+}
+
+function Takeown-Registry($key) {
+ # TODO does not work for all root keys yet
+ switch ($key.split('\')[0]) {
+ "HKEY_CLASSES_ROOT" {
+ $reg = [Microsoft.Win32.Registry]::ClassesRoot
+ $key = $key.substring(18)
+ }
+ "HKEY_CURRENT_USER" {
+ $reg = [Microsoft.Win32.Registry]::CurrentUser
+ $key = $key.substring(18)
+ }
+ "HKEY_LOCAL_MACHINE" {
+ $reg = [Microsoft.Win32.Registry]::LocalMachine
+ $key = $key.substring(19)
+ }
+ }
+
+ # get administrator group
+ $admins = New-Object System.Security.Principal.SecurityIdentifier("S-1-5-32-544")
+ $admins = $admins.Translate([System.Security.Principal.NTAccount])
+
+ # set owner
+ $key = $reg.OpenSubKey($key, "ReadWriteSubTree", "TakeOwnership")
+ $acl = $key.GetAccessControl()
+ $acl.SetOwner($admins)
+ $key.SetAccessControl($acl)
+
+ # set FullControl
+ $acl = $key.GetAccessControl()
+ $rule = New-Object System.Security.AccessControl.RegistryAccessRule($admins, "FullControl", "Allow")
+ $acl.SetAccessRule($rule)
+ $key.SetAccessControl($acl)
+}
+
+# Function to take ownership of registry keys
+function Takeown-Registry($keyPath) {
+ $regKey = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey($keyPath, [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree, [System.Security.AccessControl.RegistryRights]::TakeOwnership)
+ $acl = $regKey.GetAccessControl()
+ $acl.SetOwner([System.Security.Principal.NTAccount]"Administrators")
+ $regKey.SetAccessControl($acl)
+ $regKey.Close()
+}
+
+# Remove Features
+function Takeown-File($path) {
+ takeown.exe /A /F $path
+ $acl = Get-Acl $path
+
+ # get administrator group
+ $admins = New-Object System.Security.Principal.SecurityIdentifier("S-1-5-32-544")
+ $admins = $admins.Translate([System.Security.Principal.NTAccount])
+
+ # add NT Authority\SYSTEM
+ $rule = New-Object System.Security.AccessControl.FileSystemAccessRule($admins, "FullControl", "None", "None", "Allow")
+ $acl.AddAccessRule($rule)
+
+ Set-Acl -Path $path -AclObject $acl
+}
+
+function Takeown-Folder($path) {
+ Takeown-File $path
+ foreach ($item in Get-ChildItem $path) {
+ if (Test-Path $item -PathType Container) {
+ Takeown-Folder $item.FullName
+ }
+ else {
+ Takeown-File $item.FullName
+ }
+ }
+}
+
+function Elevate-Privileges {
+ param($Privilege)
+ $Definition = @"
+ using System;
+ using System.Runtime.InteropServices;
+
+ public class AdjPriv {
+ [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
+ internal static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall, ref TokPriv1Luid newst, int len, IntPtr prev, IntPtr rele);
+
+ [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
+ internal static extern bool OpenProcessToken(IntPtr h, int acc, ref IntPtr phtok);
+
+ [DllImport("advapi32.dll", SetLastError = true)]
+ internal static extern bool LookupPrivilegeValue(string host, string name, ref long pluid);
+
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ internal struct TokPriv1Luid {
+ public int Count;
+ public long Luid;
+ public int Attr;
+ }
+
+ internal const int SE_PRIVILEGE_ENABLED = 0x00000002;
+ internal const int TOKEN_QUERY = 0x00000008;
+ internal const int TOKEN_ADJUST_PRIVILEGES = 0x00000020;
+
+ public static bool EnablePrivilege(long processHandle, string privilege) {
+ bool retVal;
+ TokPriv1Luid tp;
+ IntPtr hproc = new IntPtr(processHandle);
+ IntPtr htok = IntPtr.Zero;
+ retVal = OpenProcessToken(hproc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref htok);
+ tp.Count = 1;
+ tp.Luid = 0;
+ tp.Attr = SE_PRIVILEGE_ENABLED;
+ retVal = LookupPrivilegeValue(null, privilege, ref tp.Luid);
+ retVal = AdjustTokenPrivileges(htok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero);
+ return retVal;
+ }
+ }
+"@
+ $ProcessHandle = (Get-Process -id $pid).Handle
+ $type = Add-Type $definition -PassThru
+ $type[0]::EnablePrivilege($processHandle, $Privilege)
+}
+
+# Remove Features ------------------------
+foreach ($bloat in $bloatware) {
+ Write-Output "Removing packages containing $bloat"
+ $pkgs = Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\Packages" | Where-Object Name -Like "*$bloat*"
+
+ foreach ($pkg in $pkgs) {
+ $pkgname = $pkg.Name.Split('\')[-1]
+ Takeown-Registry $pkg.Name
+ Takeown-Registry ($pkg.Name + "\Owners")
+ Set-ItemProperty -Path ("HKLM:" + $pkg.Name.Substring(18)) -Name Visibility -Value 1
+ New-ItemProperty -Path ("HKLM:" + $pkg.Name.Substring(18)) -Name DefVis -PropertyType DWord -Value 2
+ Remove-Item -Path ("HKLM:" + $pkg.Name.Substring(18) + "\Owners")
+ dism.exe /Online /Remove-Package /PackageName:$pkgname /NoRestart
+ }
+}
+
+# Remove default apps and bloat
+Write-Output "Uninstalling default apps"
+foreach ($app in $defaultApps) {
+ Write-Output "Trying to remove $app"
+ Get-AppxPackage -Name $app -AllUsers | Remove-AppxPackage -AllUsers
+ Get-AppXProvisionedPackage -Online | Where-Object DisplayName -EQ $app | Remove-AppxProvisionedPackage -Online
+}
+
+# Disable Microsoft Edge sidebar
+$RegistryPath = 'HKLM:\SOFTWARE\Policies\Microsoft\Edge'
+$Name = 'HubsSidebarEnabled'
+$Value = '00000000'
+# Create the key if it does not exist
+If (-NOT (Test-Path $RegistryPath)) {
+ New-Item -Path $RegistryPath -Force | Out-Null
+}
+New-ItemProperty -Path $RegistryPath -Name $Name -Value $Value -PropertyType DWORD -Force
+
+# Disable Microsoft Edge first-run Welcome screen
+$RegistryPath = 'HKLM:\SOFTWARE\Policies\Microsoft\Edge'
+$Name = 'HideFirstRunExperience'
+$Value = '00000001'
+# Create the key if it does not exist
+If (-NOT (Test-Path $RegistryPath)) {
+ New-Item -Path $RegistryPath -Force | Out-Null
+}
+New-ItemProperty -Path $RegistryPath -Name $Name -Value $Value -PropertyType DWORD -Force
+
+# Remove Microsoft Edge ------------------------
+$ErrorActionPreference = "Stop"
+$regView = [Microsoft.Win32.RegistryView]::Registry32
+$microsoft = [Microsoft.Win32.RegistryKey]::OpenBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, $regView).
+OpenSubKey('SOFTWARE\Microsoft', $true)
+$edgeUWP = "$env:SystemRoot\SystemApps\Microsoft.MicrosoftEdge_8wekyb3d8bbwe"
+$uninstallRegKey = $microsoft.OpenSubKey('Windows\CurrentVersion\Uninstall\Microsoft Edge')
+$uninstallString = $uninstallRegKey.GetValue('UninstallString') + ' --force-uninstall'
+Write-Host "Removed Microsoft Edge"
+
+$edgeClient = $microsoft.OpenSubKey('EdgeUpdate\ClientState\{56EB18F8-B008-4CBD-B6D2-8C97FE7E9062}', $true)
+if ($null -ne $edgeClient.GetValue('experiment_control_labels')) {
+ $edgeClient.DeleteValue('experiment_control_labels')
+}
+$microsoft.CreateSubKey('EdgeUpdateDev').SetValue('AllowUninstall', '')
+[void](New-Item $edgeUWP -ItemType Directory -ErrorVariable fail -ErrorAction SilentlyContinue)
+[void](New-Item "$edgeUWP\MicrosoftEdge.exe" -ErrorAction Continue)
+Start-Process cmd.exe "/c $uninstallString" -WindowStyle Hidden -Wait
+[void](Remove-Item "$edgeUWP\MicrosoftEdge.exe" -ErrorAction Continue)
+
+if (-not $fail) {
+ [void](Remove-Item "$edgeUWP")
+}
+
+Write-Output "Edge should now be uninstalled!"
+
+# Kill OneDrive with fire ------------------------
+Write-Output "Kill OneDrive process"
+taskkill.exe /F /IM "OneDrive.exe"
+taskkill.exe /F /IM "explorer.exe"
+Write-Output "Remove OneDrive"
+if (Test-Path "$env:systemroot\System32\OneDriveSetup.exe") {
+ & "$env:systemroot\System32\OneDriveSetup.exe" /uninstall
+}
+if (Test-Path "$env:systemroot\SysWOW64\OneDriveSetup.exe") {
+ & "$env:systemroot\SysWOW64\OneDriveSetup.exe" /uninstall
+}
+
+Write-Output "Removing OneDrive leftovers"
+Remove-Item -Recurse -Force -ErrorAction SilentlyContinue "$env:localappdata\Microsoft\OneDrive"
+Remove-Item -Recurse -Force -ErrorAction SilentlyContinue "$env:programdata\Microsoft OneDrive"
+Remove-Item -Recurse -Force -ErrorAction SilentlyContinue "$env:systemdrive\OneDriveTemp"
+# check if directory is empty before removing:
+If ((Get-ChildItem "$env:userprofile\OneDrive" -Recurse | Measure-Object).Count -eq 0) {
+ Remove-Item -Recurse -Force -ErrorAction SilentlyContinue "$env:userprofile\OneDrive"
+}
+
+Write-Output "Disable OneDrive via Group Policies"
+force-mkdir "HKLM:\SOFTWARE\Wow6432Node\Policies\Microsoft\Windows\OneDrive"
+Set-ItemProperty "HKLM:\SOFTWARE\Wow6432Node\Policies\Microsoft\Windows\OneDrive" "DisableFileSyncNGSC" 1
+
+Write-Output "Remove Onedrive from explorer sidebar"
+New-PSDrive -PSProvider "Registry" -Root "HKEY_CLASSES_ROOT" -Name "HKCR"
+force-mkdir "HKCR:\CLSID\{018D5C66-4533-4307-9B53-224DE2ED1FE6}"
+Set-ItemProperty "HKCR:\CLSID\{018D5C66-4533-4307-9B53-224DE2ED1FE6}" "System.IsPinnedToNameSpaceTree" 0
+force-mkdir "HKCR:\Wow6432Node\CLSID\{018D5C66-4533-4307-9B53-224DE2ED1FE6}"
+Set-ItemProperty "HKCR:\Wow6432Node\CLSID\{018D5C66-4533-4307-9B53-224DE2ED1FE6}" "System.IsPinnedToNameSpaceTree" 0
+Remove-PSDrive "HKCR"
+
+# Thank you Matthew Israelsson
+Write-Output "Removing run hook for new users"
+reg load "hku\Default" "C:\Users\Default\NTUSER.DAT"
+reg delete "HKEY_USERS\Default\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" /v "OneDriveSetup" /f
+reg unload "hku\Default"
+
+Write-Output "Removing startmenu entry"
+Remove-Item -Force -ErrorAction SilentlyContinue "$env:userprofile\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\OneDrive.lnk"
+
+Write-Output "Removing scheduled task"
+$scheduledTasks = Get-ScheduledTask -TaskPath '\' -TaskName 'OneDrive*' -ErrorAction SilentlyContinue
+if ($scheduledTasks) {
+ try {
+ $scheduledTasks | Unregister-ScheduledTask -Confirm:$false
+ Write-Output "OneDrive scheduled tasks removed."
+ } catch {
+ Write-Warning "Failed to unregister scheduled tasks: $_"
+ }
+} else {
+ Write-Output "No OneDrive scheduled tasks found."
+}
+
+Write-Output "Restarting explorer"
+Start-Process "explorer.exe"
+
+Write-Output "Waiting for explorer to complete loading"
+Start-Sleep 10
+
+Write-Output "Removing additional OneDrive leftovers"
+foreach ($item in (Get-ChildItem "$env:WinDir\WinSxS\*onedrive*")) {
+ Takeown-Folder $item.FullName
+ try {
+ Remove-Item -Recurse -Force -ErrorAction Continue -ErrorVariable RemoveError $item.FullName
+ if ($RemoveError) {
+ Write-Warning "Failed to remove $($item.FullName): $($RemoveError.Exception.Message)"
+ } else {
+ Write-Output "Successfully removed: $($item.FullName)"
+ }
+ } catch {
+ Write-Warning "Failed to remove $($item.FullName): $_"
+ }
+}
+
+# Remove OneDrive directory if it exists
+Write-Host "Removing OneDrive directory"
+
+# Change directory to user's home directory
+Set-Location $HOME
+
+# Check if OneDrive directory exists
+$OneDrivePath = Join-Path $HOME "OneDrive"
+if (Test-Path -Path $OneDrivePath -PathType Container) {
+ # Remove OneDrive directory recursively and forcefully
+ Remove-Item -Path $OneDrivePath -Recurse -Force -ErrorAction Continue
+ if ($?) {
+ Write-Output "OneDrive directory removed successfully."
+ } else {
+ Write-Warning "Failed to remove OneDrive directory."
+ }
+} else {
+ Write-Output "OneDrive directory not found."
+}
+
+# Prevents "Suggested Applications" returning
+if (Check-RegistryKeyExists -KeyPath "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Cloud Content") {
+ Set-ItemProperty "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Cloud Content" "DisableWindowsConsumerFeatures" 1
+}
diff --git a/windows/Documents/PowerShell/bootstrap.ps1 b/windows/Documents/PowerShell/bootstrap.ps1
new file mode 100644
index 0000000..d2f4369
--- /dev/null
+++ b/windows/Documents/PowerShell/bootstrap.ps1
@@ -0,0 +1,669 @@
+#!/usr/bin/env pwsh
+
+# Created By: srdusr
+# Created On: Windows PowerShell Bootstrap Script
+# Project: Dotfiles installation script for Windows
+
+# Dependencies: git, powershell
+
+param(
+ [string]$Profile = "essentials",
+ [switch]$Force = $false,
+ [switch]$Ask = $false,
+ [switch]$Help = $false
+)
+
+# Color definitions for pretty UI
+$Script:Colors = @{
+ Reset = "`e[0m"
+ Red = "`e[0;31m"
+ Green = "`e[0;32m"
+ Yellow = "`e[0;33m"
+ Blue = "`e[0;34m"
+ Cyan = "`e[0;36m"
+ White = "`e[0;37m"
+ Bold = "`e[1m"
+}
+
+# Prompt helper: Yes/No with default; in non-Ask mode, returns default immediately
+function Prompt-YesNo {
+ param(
+ [Parameter(Mandatory=$true)][string]$Question,
+ [ValidateSet('Y','N')][string]$Default = 'Y'
+ )
+ if (-not $Script:AskPreference) {
+ return $Default -eq 'Y'
+ }
+ $suffix = if ($Default -eq 'Y') { '[Y/n]' } else { '[y/N]' }
+ while ($true) {
+ Write-Host -NoNewline "$Question $suffix: " -ForegroundColor Yellow
+ $resp = Read-Host
+ if ([string]::IsNullOrWhiteSpace($resp)) { $resp = $Default }
+ switch ($resp.ToUpper()) {
+ 'Y' { return $true }
+ 'YES' { return $true }
+ 'N' { return $false }
+ 'NO' { return $false }
+ default { Write-Warning "Please answer Y/yes or N/no" }
+ }
+ }
+}
+
+# Configuration
+$Script:Config = @{
+ DotfilesUrl = 'https://github.com/srdusr/dotfiles.git'
+ DotfilesDir = "$HOME\.cfg"
+ LogFile = "$HOME\AppData\Local\dotfiles_install.log"
+ StateFile = "$HOME\AppData\Local\dotfiles_install_state"
+ BackupDir = "$HOME\.dotfiles-backup-$(Get-Date -Format 'yyyyMMdd-HHmmss')"
+ PackagesFile = "packages.yml"
+ OS = "windows"
+}
+
+# Logging functions
+function Write-Log {
+ param([string]$Message, [string]$Level = "INFO")
+ $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
+ $logEntry = "[$timestamp] [$Level] $Message"
+ Add-Content -Path $Script:Config.LogFile -Value $logEntry -Force
+}
+
+function Write-ColorOutput {
+ param([string]$Message, [string]$Color = "White")
+ Write-Host $Message -ForegroundColor $Color
+ Write-Log $Message
+}
+
+function Write-Success { param([string]$Message) Write-ColorOutput "✓ $Message" "Green" }
+function Write-Info { param([string]$Message) Write-ColorOutput "ℹ $Message" "Cyan" }
+function Write-Warning { param([string]$Message) Write-ColorOutput "⚠ $Message" "Yellow" }
+function Write-Error { param([string]$Message) Write-ColorOutput "✗ $Message" "Red" }
+
+function Write-Header {
+ param([string]$Title)
+ Write-Host ""
+ Write-Host "=" * 60 -ForegroundColor Blue
+ Write-Host " $Title" -ForegroundColor Bold
+ Write-Host "=" * 60 -ForegroundColor Blue
+ Write-Host ""
+}
+
+# Utility functions
+function Test-CommandExists {
+ param([string]$Command)
+ return [bool](Get-Command $Command -ErrorAction SilentlyContinue)
+}
+
+function Test-IsAdmin {
+ $currentUser = [Security.Principal.WindowsIdentity]::GetCurrent()
+ $principal = New-Object Security.Principal.WindowsPrincipal($currentUser)
+ return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
+}
+
+function Invoke-AdminCommand {
+ param([string]$Command)
+ if (-not (Test-IsAdmin)) {
+ Write-Warning "Elevating privileges for: $Command"
+ Start-Process powershell.exe -ArgumentList "-NoProfile", "-Command", $Command -Verb RunAs -Wait
+ } else {
+ Invoke-Expression $Command
+ }
+}
+
+# Package management functions
+function Get-PackageManager {
+ if (Test-CommandExists "choco") { return "chocolatey" }
+ if (Test-CommandExists "winget") { return "winget" }
+ if (Test-CommandExists "scoop") { return "scoop" }
+ return $null
+}
+
+# Return $true if a package appears to be installed for the given manager
+function Test-PackageInstalled {
+ param(
+ [Parameter(Mandatory=$true)][string]$Manager,
+ [Parameter(Mandatory=$true)][string]$Name
+ )
+ switch ($Manager) {
+ "chocolatey" {
+ $out = choco list --local-only --exact $Name 2>$null
+ return ($out | Select-String -Pattern "^\s*$([regex]::Escape($Name))\s").Length -gt 0
+ }
+ "winget" {
+ # Winget list may return multiple rows; use --exact name match when possible
+ $out = winget list --name $Name 2>$null
+ return ($out | Select-String -SimpleMatch $Name).Length -gt 0
+ }
+ "scoop" {
+ # scoop list <name> returns 0 when installed
+ scoop list $Name *> $null
+ return $LASTEXITCODE -eq 0
+ }
+ default { return $false }
+ }
+}
+
+function Install-PackageManager {
+ Write-Header "Installing Package Manager"
+
+ if (-not (Test-CommandExists "choco")) {
+ Write-Info "Installing Chocolatey..."
+ Set-ExecutionPolicy Bypass -Scope Process -Force
+ [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072
+ Invoke-Expression ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
+
+ if (Test-CommandExists "choco") {
+ Write-Success "Chocolatey installed successfully"
+ } else {
+ Write-Error "Failed to install Chocolatey"
+ return $false
+ }
+ } else {
+ Write-Info "Chocolatey already installed"
+ }
+ return $true
+}
+
+function Install-Packages {
+ param([string]$PackagesFile, [string]$Profile)
+
+ if (-not (Test-Path $PackagesFile)) {
+ Write-Warning "Packages file not found: $PackagesFile"
+ return
+ }
+
+ Write-Header "Installing Packages"
+
+ # Install powershell-yaml if not available
+ if (-not (Get-Module powershell-yaml -ListAvailable)) {
+ Write-Info "Installing powershell-yaml module..."
+ $policy = Get-PSRepository -Name 'PSGallery' | Select-Object -ExpandProperty InstallationPolicy
+ Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted
+ Install-Module powershell-yaml -Force
+ Set-PSRepository -Name 'PSGallery' -InstallationPolicy $policy
+ }
+
+ Import-Module powershell-yaml
+
+ # Helper: run custom_installs.<name>.windows if condition passes
+ function Invoke-CustomInstallsWindows {
+ param([Parameter(Mandatory=$true)]$Yaml)
+ if (-not $Yaml.custom_installs) { return }
+ foreach ($name in $Yaml.custom_installs.PSObject.Properties.Name) {
+ $entry = $Yaml.custom_installs.$name
+ if (-not $entry) { continue }
+ $winCmd = $entry.windows
+ if (-not $winCmd) { continue }
+ $shouldRun = $true
+ if ($entry.condition) {
+ $cond = [string]$entry.condition
+ if ($cond -match "!\s*command\s+-v\s+([A-Za-z0-9._-]+)") {
+ $shouldRun = -not (Test-CommandExists $Matches[1])
+ } elseif ($cond -match "command\s+-v\s+([A-Za-z0-9._-]+)") {
+ $shouldRun = (Test-CommandExists $Matches[1])
+ }
+ }
+ if (-not $shouldRun) { Write-Info "Skipping custom install: $name"; continue }
+ Write-Info "Running custom install: $name"
+ try { Invoke-Expression $winCmd; Write-Success "Custom install completed: $name" }
+ catch { Write-Warning "Custom install failed for '$name': $_" }
+ }
+ }
+
+ try {
+ $packages = Get-Content $PackagesFile | ConvertFrom-Yaml
+ $packageManager = Get-PackageManager
+
+ if (-not $packageManager) {
+ Write-Error "No package manager available"
+ return
+ }
+
+ # Get packages for current profile and OS
+ $profilePackages = @()
+ if ($packages.profiles.$Profile.windows) {
+ $profilePackages += $packages.profiles.$Profile.windows
+ }
+ if ($packages.profiles.$Profile.common) {
+ $profilePackages += $packages.profiles.$Profile.common
+ }
+
+ foreach ($package in $profilePackages) {
+ $packageName = if ($packages.packages.$package.windows) {
+ $packages.packages.$package.windows
+ } else {
+ $package
+ }
+
+ if (Test-PackageInstalled -Manager $packageManager -Name $packageName) {
+ Write-Info "Already installed: $packageName"
+ continue
+ }
+
+ Write-Info "Installing package: $packageName"
+
+ switch ($packageManager) {
+ "chocolatey" {
+ if (-not (choco list --local-only | Select-String -Pattern "^$packageName\s")) {
+ choco install $packageName -y
+ if ($LASTEXITCODE -eq 0) {
+ Write-Success "Installed: $packageName"
+ } else {
+ Write-Error "Failed to install: $packageName"
+ }
+ } else {
+ Write-Info "Already installed: $packageName"
+ }
+ }
+ "winget" {
+ winget install $packageName --accept-package-agreements --accept-source-agreements
+ }
+ "scoop" {
+ scoop install $packageName
+ }
+ }
+ }
+
+ # Also install top-level Windows packages list if present
+ if ($packages.windows) {
+ foreach ($pkg in $packages.windows) {
+ if ([string]::IsNullOrWhiteSpace($pkg)) { continue }
+ if (Test-PackageInstalled -Manager $packageManager -Name $pkg) { Write-Info "Already installed: $pkg"; continue }
+ Write-Info "Installing package: $pkg"
+ switch ($packageManager) {
+ "chocolatey" {
+ if (-not (choco list --local-only | Select-String -Pattern "^$([regex]::Escape($pkg))\s")) { choco install $pkg -y }
+ }
+ "winget" { winget install --id $pkg --silent --accept-package-agreements --accept-source-agreements }
+ "scoop" { scoop install $pkg }
+ }
+ }
+ }
+
+ # Execute Windows custom installs from packages.yml
+ Invoke-CustomInstallsWindows -Yaml $packages
+ } catch {
+ Write-Error "Error processing packages: $_"
+ }
+}
+
+# Dotfiles management functions
+function Install-Dotfiles {
+ Write-Header "Installing Dotfiles"
+
+ if (Test-Path $Script:Config.DotfilesDir) {
+ Write-Info "Updating existing dotfiles repository..."
+ & git --git-dir="$($Script:Config.DotfilesDir)" --work-tree="$($Script:Config.DotfilesDir)" pull origin main
+ } else {
+ Write-Info "Cloning dotfiles repository..."
+ git clone --bare $Script:Config.DotfilesUrl $Script:Config.DotfilesDir
+
+ if (-not (Test-Path $Script:Config.DotfilesDir)) {
+ Write-Error "Failed to clone dotfiles repository"
+ return $false
+ }
+ }
+
+ # Set up config alias for this session
+ function script:config {
+ git --git-dir="$($Script:Config.DotfilesDir)" --work-tree="$($Script:Config.DotfilesDir)" @args
+ }
+
+ # Configure repository
+ config config --local status.showUntrackedFiles no
+
+ # Checkout files to restore directory structure
+ Write-Info "Checking out dotfiles..."
+ try {
+ config checkout 2>$null
+ if ($LASTEXITCODE -ne 0) {
+ Write-Info "Forcing checkout to overwrite existing files..."
+ config checkout -f
+ }
+ Write-Success "Dotfiles checked out successfully"
+ } catch {
+ Write-Error "Failed to checkout dotfiles: $_"
+ return $false
+ }
+
+ return $true
+}
+
+function Deploy-Dotfiles {
+ Write-Header "Deploying Dotfiles"
+
+ if (-not (Test-Path $Script:Config.DotfilesDir)) {
+ Write-Error "Dotfiles directory not found. Run Install-Dotfiles first."
+ return $false
+ }
+
+ # Source the config command from profile if available
+ $profilePath = "$HOME\Documents\PowerShell\Microsoft.PowerShell_profile.ps1"
+ if (Test-Path $profilePath) {
+ Write-Info "Loading config command from profile..."
+ . $profilePath
+ }
+
+ # Deploy using config command if available, otherwise manual deployment
+ if (Get-Command config -ErrorAction SilentlyContinue) {
+ Write-Info "Deploying dotfiles using config command..."
+ config deploy
+ } else {
+ Write-Warning "Config command not available, using manual deployment..."
+ # Manual deployment fallback would go here
+ }
+
+ Write-Success "Dotfiles deployment completed"
+ return $true
+}
+
+# Locate profile-specific packages.yml similar to Linux installer
+function Get-ProfilePackagesFile {
+ param([string]$Profile)
+ $candidates = @(
+ Join-Path $HOME ".cfg/profile/$Profile/packages.yml",
+ Join-Path $HOME "profile/$Profile/packages.yml",
+ Join-Path $HOME "dot_setup/profile/$Profile/packages.yml",
+ Join-Path $Script:Config.DotfilesDir "common/$($Script:Config.PackagesFile)"
+ )
+ foreach ($pf in $candidates) {
+ if (Test-Path $pf) { return $pf }
+ }
+ return $null
+}
+
+# System configuration functions
+function Set-WindowsConfiguration {
+ param(
+ [string]$PackagesFile
+ )
+
+ Write-Header "Configuring Windows Settings"
+
+ if (-not $PackagesFile -or -not (Test-Path $PackagesFile)) {
+ Write-Warning "Packages file not found, skipping Windows configuration"
+ return
+ }
+
+ try {
+ # Load YAML content
+ $yamlContent = Get-Content $PackagesFile -Raw | ConvertFrom-Yaml
+ $registrySettings = $yamlContent.system_tweaks.windows.registry
+
+ if (-not $registrySettings) {
+ Write-Warning "No Windows registry settings found in packages.yml"
+ return
+ }
+
+ Write-Info "Applying registry settings from packages.yml..."
+
+ foreach ($setting in $registrySettings) {
+ try {
+ $path = $setting.path
+ $name = $setting.name
+ $value = $setting.value
+ $type = $setting.type
+ $description = $setting.description
+
+ Write-Info "Setting: $description"
+
+ # Ensure the registry path exists
+ $pathParts = $path -split '\\'
+ $currentPath = $pathParts[0]
+ for ($i = 1; $i -lt $pathParts.Length; $i++) {
+ $currentPath = "$currentPath\$($pathParts[$i])"
+ if (-not (Test-Path $currentPath)) {
+ New-Item -Path $currentPath -Force | Out-Null
+ }
+ }
+
+ # Set the registry value
+ Set-ItemProperty -Path $path -Name $name -Value $value -Type $type -Force
+ Write-Success "Applied: $description"
+
+ } catch {
+ Write-Warning "Failed to apply setting '$($setting.description)': $_"
+ }
+ }
+
+ Write-Success "Windows configuration applied"
+
+ # Restart explorer to apply changes
+ Write-Info "Restarting Windows Explorer..."
+ Stop-Process -Name explorer -Force
+ Start-Process explorer.exe
+
+ } catch {
+ Write-Warning "Failed to process Windows configuration: $_"
+ }
+}
+
+function Enable-WindowsFeatures {
+ param(
+ [string]$PackagesFile
+ )
+
+ Write-Header "Enabling Windows Features"
+
+ if (-not $PackagesFile -or -not (Test-Path $PackagesFile)) {
+ Write-Warning "Packages file not found, skipping Windows features"
+ return
+ }
+
+ try {
+ # Load YAML content
+ $yamlContent = Get-Content $PackagesFile -Raw | ConvertFrom-Yaml
+ $features = $yamlContent.system_tweaks.windows.features
+
+ if (-not $features) {
+ Write-Warning "No Windows features found in packages.yml"
+ return
+ }
+
+ foreach ($feature in $features) {
+ $featureName = $feature.name
+ $description = $feature.description
+ $requiresAdmin = $feature.requires_admin
+
+ if ($requiresAdmin -and -not (Test-IsAdmin)) {
+ Write-Warning "Skipping '$description' - requires administrator privileges"
+ continue
+ }
+
+ try {
+ Write-Info "Enabling: $description"
+ dism.exe /online /enable-feature /featurename:$featureName /all /norestart
+ Write-Success "Enabled: $description"
+ } catch {
+ Write-Warning "Failed to enable '$description': $_"
+ }
+ }
+
+ Write-Success "Windows features processing complete (restart may be required)"
+
+ } catch {
+ Write-Warning "Failed to process Windows features: $_"
+ }
+}
+
+function Install-PowerShellProfile {
+ Write-Header "Setting up PowerShell Profile"
+
+ $documentsPath = [System.Environment]::GetFolderPath('MyDocuments')
+ $powerShellProfileDir = "$documentsPath\PowerShell"
+ $profilePath = "$powerShellProfileDir\Microsoft.PowerShell_profile.ps1"
+
+ Write-Info "PowerShell profile directory: $powerShellProfileDir"
+
+ if (-not (Test-Path $powerShellProfileDir)) {
+ New-Item -ItemType Directory -Path $powerShellProfileDir -Force | Out-Null
+ Write-Success "Created PowerShell profile directory"
+ }
+
+ # Copy profile from dotfiles if it exists
+ $dotfilesProfile = "$($Script:Config.DotfilesDir)\windows\Documents\PowerShell\Microsoft.PowerShell_profile.ps1"
+ if (Test-Path $dotfilesProfile) {
+ Copy-Item $dotfilesProfile $profilePath -Force
+ Write-Success "PowerShell profile installed from dotfiles"
+ } else {
+ Write-Warning "PowerShell profile not found in dotfiles"
+ }
+}
+
+# Main execution function
+function Start-Bootstrap {
+ param([string]$Profile, [switch]$Force, [switch]$Ask)
+
+ Write-Header "Windows Dotfiles Bootstrap"
+ Write-Info "Profile: $Profile"
+ Write-Info "Force mode: $Force"
+ Write-Info "Interactive mode: $Ask"
+
+ # Initialize logging
+ $logDir = Split-Path $Script:Config.LogFile
+ if (-not (Test-Path $logDir)) {
+ New-Item -ItemType Directory -Path $logDir -Force | Out-Null
+ }
+
+ Write-Log "Bootstrap started with profile: $Profile"
+
+ # Set Ask preference for all prompts
+ $Script:AskPreference = [bool]$Ask
+
+ # Check dependencies
+ Write-Header "Checking Dependencies"
+ $requiredCommands = @("git", "powershell")
+ $missingCommands = @()
+
+ foreach ($cmd in $requiredCommands) {
+ if (-not (Test-CommandExists $cmd)) {
+ $missingCommands += $cmd
+ Write-Error "Required command not found: $cmd"
+ } else {
+ Write-Success "Found: $cmd"
+ }
+ }
+
+ if ($missingCommands.Count -gt 0) {
+ Write-Error "Missing required dependencies. Please install: $($missingCommands -join ', ')"
+ return $false
+ }
+
+ # Install package manager (skippable)
+ if (Prompt-YesNo -Question "Install/check package manager?" -Default 'Y') {
+ if (-not (Install-PackageManager)) {
+ Write-Error "Failed to install package manager"
+ return $false
+ }
+ } else {
+ Write-Warning "Skipped package manager step by user choice"
+ }
+
+ # Install dotfiles
+ if (Prompt-YesNo -Question "Install or update dotfiles?" -Default 'Y') {
+ if (-not (Install-Dotfiles)) {
+ Write-Error "Failed to install dotfiles"
+ return $false
+ }
+ } else {
+ Write-Warning "Skipped dotfiles installation by user choice"
+ }
+
+ # Get packages file (profile-aware)
+ $packagesFile = Get-ProfilePackagesFile -Profile $Profile
+ if (-not $packagesFile) {
+ Write-Error "Failed to get packages file for profile '$Profile'"
+ return $false
+ }
+
+ # Install packages
+ if (Prompt-YesNo -Question "Install profile packages?" -Default 'Y') {
+ Install-Packages -PackagesFile $packagesFile -Profile $Profile
+ } else {
+ Write-Warning "Skipped package installation by user choice"
+ }
+
+ # Set up PowerShell profile
+ if (Prompt-YesNo -Question "Install PowerShell profile?" -Default 'Y') {
+ Install-PowerShellProfile
+ } else {
+ Write-Warning "Skipped PowerShell profile setup by user choice"
+ }
+
+ # Deploy dotfiles
+ if (Prompt-YesNo -Question "Deploy dotfiles to system locations?" -Default 'Y') {
+ if (-not (Deploy-Dotfiles)) {
+ Write-Error "Failed to deploy dotfiles"
+ return $false
+ }
+ } else {
+ Write-Warning "Skipped dotfiles deployment by user choice"
+ }
+
+ # Configure Windows
+ if (Prompt-YesNo -Question "Apply Windows configuration from packages.yml?" -Default 'N') {
+ Set-WindowsConfiguration -PackagesFile $packagesPath
+ } else {
+ Write-Warning "Skipped Windows configuration by user choice"
+ }
+
+ # Enable Windows features (if admin)
+ if (Prompt-YesNo -Question "Enable Windows optional features?" -Default 'N') {
+ Enable-WindowsFeatures -PackagesFile $packagesPath
+ } else {
+ Write-Warning "Skipped enabling Windows features by user choice"
+ }
+
+ Write-Header "Bootstrap Complete"
+ Write-Success "Windows dotfiles bootstrap completed successfully!"
+ Write-Info "Please restart your computer to apply all changes."
+ Write-Log "Bootstrap completed successfully"
+
+ return $true
+}
+
+# Help function
+function Show-Help {
+ Write-Host @"
+Windows Dotfiles Bootstrap Script
+
+USAGE:
+ .\bootstrap.ps1 [-Profile <profile>] [-Force] [-Ask] [-Help]
+
+PARAMETERS:
+ -Profile <string> Installation profile (default: essentials)
+ Available: essentials, minimal, dev, server, full, or a custom profile.
+ Custom profile files are resolved from:
+ - %USERPROFILE%\.cfg\profile\<name>\packages.yml
+ - %USERPROFILE%\profile\<name>\packages.yml
+ - %USERPROFILE%\dot_setup\profile\<name>\packages.yml
+ -Force Force installation without prompts
+ -Ask Interactive mode with step-by-step prompts
+ -Help Show this help message
+
+EXAMPLES:
+ .\bootstrap.ps1 # Install with essentials profile
+ .\bootstrap.ps1 -Profile dev # Install development profile
+ .\bootstrap.ps1 -Profile full -Force # Force install full profile
+ .\bootstrap.ps1 -Ask # Interactive installation
+
+"@ -ForegroundColor Cyan
+}
+
+# Script entry point
+if ($Help) {
+ Show-Help
+ exit 0
+}
+
+# Run bootstrap
+try {
+ $result = Start-Bootstrap -Profile $Profile -Force:$Force -Ask:$Ask
+ if (-not $result) {
+ exit 1
+ }
+} catch {
+ Write-Error "Bootstrap failed: $_"
+ Write-Log "Bootstrap failed: $_" "ERROR"
+ exit 1
+}
diff --git a/windows/Documents/PowerShell/initialize.ps1 b/windows/Documents/PowerShell/initialize.ps1
new file mode 100644
index 0000000..72f0ea4
--- /dev/null
+++ b/windows/Documents/PowerShell/initialize.ps1
@@ -0,0 +1,227 @@
+<#
+ .SYNOPSIS
+ Bootstrap Windows command prompts (cmd, PS, PSCore) with my dotfiles and apps.
+
+ .DESCRIPTION
+ To bootstrap directly from GitHub, run these 2 cmdlets in a PowerShell prompt:
+ > Set-ExecutionPolicy RemoteSigned -Scope CurrentUser -Force
+ > irm 'https://raw.githubusercontent.com/srdusr/dotfiles/main/windows/Documents/PowerShell/bootstrap.ps1' | iex
+#>
+[CmdletBinding()]
+param (
+ [ValidateSet('clone', 'setup', 'apps', 'env', IgnoreCase = $true)]
+ [Parameter(Position = 0)] [string]
+ $verb = 'clone',
+ [Parameter()] [string]
+ $userName = $null,
+ [Parameter()] [string]
+ $email = $null,
+ [Parameter()] [switch]
+ $runAsAdmin = $false
+)
+
+$ErrorActionPreference = 'Stop'
+
+$originGitHub = 'https://github.com/srdusr/dotfiles.git'
+$dotPath = (Join-Path $env:USERPROFILE '.cfg')
+
+# Ensure Tls12
+[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
+
+function ensureLocalGit {
+ if (Get-Command 'git' -ErrorAction SilentlyContinue) {
+ return
+ }
+
+ $localGitFolder = (Join-Path $env:USERPROFILE (Join-Path "Downloads" "localGit"))
+ Write-Host "Installing ad-hoc git into $localGitFolder..."
+
+ $gitUrl = Invoke-RestMethod 'https://api.github.com/repos/git-for-windows/git/releases/latest' |
+ Select-Object -ExpandProperty 'assets' |
+ Where-Object { $_.name -Match 'MinGit' -and $_.name -Match '64-bit' -and $_.name -notmatch 'busybox' } |
+ Select-Object -ExpandProperty 'browser_download_url'
+ $localGitZip = (Join-Path $localGitFolder "MinGit.zip")
+ New-Item -ItemType Directory -Path $localGitFolder -Force | Out-Null
+ (New-Object Net.WebClient).DownloadFile($gitUrl, $localGitZip)
+ Expand-Archive -Path $localGitZip -DestinationPath $localGitFolder -Force
+
+ $gitPath = (Join-Path $localGitFolder 'cmd')
+ $env:Path += ";$gitPath"
+}
+
+function cloneDotfiles {
+ Write-Host "Cloning $originGitHub -> $dotPath"
+ Write-Host -NoNewline "OK to proceed with setup? [Y/n] "
+ $answer = (Read-Host).ToUpper()
+ if ($answer -ne 'Y' -and $answer -ne '') {
+ Write-Warning "Aborting."
+ return 4
+ }
+
+ ensureLocalGit
+
+ if (-not $userName -or $userName -eq '') {
+ $userName = (& git config --global --get user.name)
+ }
+ if (-not $userName -or $userName -eq '') {
+ $userName = "$env:USERNAME@$env:COMPUTERNAME"
+ }
+
+ if (-not $email -or $email -eq '') {
+ $email = (& git config --global --get user.email)
+ }
+ if (-not $email -or $email -eq '') {
+ $email = Read-Host "Enter your email address for git commits"
+ if ($email -eq '') {
+ Write-Warning "Need email address, aborting."
+ return 3
+ }
+ }
+
+ & git.exe config --global user.name $userName
+ & git.exe config --global user.email $email
+
+
+ function global:config {
+ git --git-dir="$dotPath" --work-tree="$env:USERPROFILE" $args
+ }
+
+ Add-Content -Path "$env:USERPROFILE\.gitignore" -Value ".cfg"
+
+ if (Test-Path -Path $dotfiles_dir) {
+ config pull | Out-Null
+ $update = $true
+ } else {
+ git clone --bare $originGitHub $dotPath | Out-Null
+ $update = $false
+ }
+
+ $std_err_output = config checkout 1>&1
+
+ if ($std_err_output -match "following untracked working tree files would be overwritten") {
+ if (-not $update) {
+ config checkout | Out-Null
+ }
+ }
+ config config --local status.showUntrackedFiles no
+
+ if ($update -or (Read-Host "Do you want to overwrite existing files and continue with the dotfiles setup? [Y/n]" -eq "Y")) {
+ config fetch origin main:main | Out-Null
+ config reset --hard main | Out-Null
+ config checkout -f
+ if ($?) {
+ Write-Host "Successfully imported $dotPath."
+ } else {
+ handle_error "Mission failed."
+ }
+ } else {
+ handle_error "Aborted by user. Exiting..."
+ }
+
+ return 0
+}
+
+function setup {
+ ensureLocalGit
+}
+
+function installApps {
+ ensureLocalGit
+}
+
+function writeGitConfig {
+ param (
+ [Parameter(Mandatory = $true)] [string] $configIniFile
+ )
+
+ if ((Test-Path (Join-Path $env:USERPROFILE '.gitconfig')) -and -not (Test-Path (Join-Path $env:USERPROFILE '.gitconfig.bak'))) {
+ $userName = (& git config --global --get user.name)
+ $email = (& git config --global --get user.email)
+
+ Move-Item -Path (Join-Path $env:USERPROFILE '.gitconfig') -Destination (Join-Path $env:USERPROFILE '.gitconfig.bak')
+
+ if ($userName -and $userName -ne '') {
+ & git.exe config --global user.name $userName
+ }
+ if ($email -and $email -ne '') {
+ & git.exe config --global user.email $email
+ }
+ }
+
+ Get-Content $configIniFile | ForEach-Object {
+ if ($_.TrimStart().StartsWith('#')) { return }
+ $key, $value = $_.Split('=', 2)
+ Write-Verbose "git config --global $key $value"
+ & git.exe config --global $key "$value"
+ }
+}
+
+function setupShellEnvs {
+ Write-Host "Setting cmd console properties:"
+ $consolePath = 'HKCU\Console'
+ & reg add $consolePath /v QuickEdit /d 0x1 /t REG_DWORD /f | Out-Null
+ & reg add $consolePath /v WindowSize /d 0x00320078 /t REG_DWORD /f | Out-Null
+ & reg add $consolePath /v ScreenBufferSize /d 0x23280078 /t REG_DWORD /f | Out-Null
+ & reg add $consolePath /v FontFamily /d 0x36 /t REG_DWORD /f | Out-Null
+ & reg add $consolePath /v HistoryBufferSize /d 0x64 /t REG_DWORD /f | Out-Null
+ & reg add $consolePath /v FaceName /d "Hack Nerd Font Mono" /t REG_SZ /f | Out-Null
+ & reg add $consolePath /v FontSize /d 0x00100000 /t REG_DWORD /f | Out-Null
+
+ $win32rc = (Join-Path $PSScriptRoot (Join-Path 'win' 'win32-rc.cmd'))
+ Write-Host "Setting up cmd autorun: $win32rc"
+ & reg add "HKCU\Software\Microsoft\Command Processor" /v AutoRun /t REG_SZ /d $win32rc /f | Out-Null
+
+ Write-Host "Configuring user home dir..."
+ $configDir = (Join-Path $env:USERPROFILE '.config')
+ New-Item -ItemType Directory -Path $configDir -ErrorAction SilentlyContinue | Out-Null
+
+ $sshDir = (Join-Path $env:USERPROFILE '.ssh')
+ Remove-Item (Join-Path $sshDir 'config') -ErrorAction SilentlyContinue -Force | Out-Null
+ $openSsh = ((Join-Path $env:windir 'System32\OpenSSH\ssh.exe').Replace("\", "/"))
+ & git config --global core.sshCommand $openSsh
+}
+
+function main {
+ param (
+ [Parameter(Mandatory = $true)] [string] $verbAction
+ )
+
+ Write-Verbose "PS: $($PSVersionTable.PSVersion)-$($PSVersionTable.PSEdition)"
+ switch ($verbAction) {
+ 'clone' {
+ Write-Host
+ if (Test-Path (Join-Path $dotPath '.git')) {
+ Write-Host "Local git repo already exists, skipping."
+ main setup
+ return
+ }
+
+ $rc = cloneDotfiles
+ if ($rc -ne 0) {
+ Write-Error "Cloning dotfiles failed, aborting."
+ return
+ }
+
+ $script = (Join-Path $dotPath 'Documents\PowerShell\bootstrap.ps1')
+ Write-Host "Continue $script in child process"
+ Start-Process -PassThru -NoNewWindow -FilePath "powershell.exe" -ArgumentList "-NoProfile -File $script setup" | Wait-Process
+ }
+
+ 'setup' {
+ Write-Host "Setting up..."
+ setup
+ installApps
+ setupShellEnvs
+ Write-Host "Done (setup)."
+ exit
+ }
+
+ 'apps' { installApps }
+
+ 'env' { setupShellEnvs }
+ }
+
+ Write-Host "Done."
+}
+
+main $verb
diff --git a/windows/install.bat b/windows/install.bat
new file mode 100644
index 0000000..89b3ac6
--- /dev/null
+++ b/windows/install.bat
@@ -0,0 +1,11 @@
+@echo off
+REM === Installing Dotfiles ===
+
+REM Set execution policy for current user
+powershell -NoProfile -Command "Set-ExecutionPolicy RemoteSigned -Scope CurrentUser -Force"
+
+REM Run PowerShell profile (dotfiles)
+powershell -NoProfile -ExecutionPolicy Bypass -File "%USERPROFILE%\Documents\PowerShell\Microsoft.PowerShell_profile.ps1"
+
+REM Run bootstrap script
+powershell -NoProfile -ExecutionPolicy Bypass -File "%USERPROFILE%\Documents\PowerShell\bootstrap.ps1"