Compare commits

..

No commits in common. "master" and "no-flakes" have entirely different histories.

548 changed files with 8123 additions and 44416 deletions

View file

@ -1,19 +0,0 @@
name: Discord
on:
- workflow_dispatch
- push
jobs:
discord_commits:
runs-on: ubuntu-latest
name: discord commits
if: contains(github.event.head_commit.message, '(servers)')
steps:
- name: Discommit
uses: https://github.com/matt1432/discommit@v0.0.2
with:
discord_webhook: ${{ secrets.DISCORD_WEBHOOK }}
api_url: 'https://git.nelim.org/api/v1/repos/$OWNER/$REPO/git/commits/$REF'
title: 'New commit containing changes to server configs:'

20
.gitignore vendored
View file

@ -1,22 +1,2 @@
# Python
*.egg-info
# NPM
*node_modules
*build/
# Direnv
*.direnv/
# Generated by nix
result*
.nixd.json
## AGS
nixosModules/ags/config/ts/lockscreen/vars.ts
**/config.js
*icons
**/types
# Other
*.temp

View file

@ -1,16 +0,0 @@
MIT No Attribution
Copyright 2024 Mathis H.
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.
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.

115
README.md
View file

@ -1,111 +1,18 @@
# My NixOS configs
## Ags
what is currently not working:
You might find it weird that most of my config is written in TypeScript.
That's because all my desktops run
[AGS](https://github.com/Aylur/ags)
for UI. Click on
[this](https://git.nelim.org/matt1432/nixos-configs/src/branch/master/nixosModules/ags)
to see my configuration.
- plymouth theme has no login prompt
- sddm theme flashes white
- autosign in to keyring
I'm also a victim of Stockholm syndrome at this point and make my scripts
in TypeScript because it's the scripting language I am most comfortable with.
what i want to do:
## About
- learn flakes
- add auto-rotate widget in ags control center
- when multiple widgets open, clicking on a background one puts it forward
### General
## Docs
This repo is the complete configuration of machines I own,
running NixOS or Nix. Its structure is based on a flake's
[outputs](https://wiki.nixos.org/wiki/Flakes#Output_schema).
### Flake Location
This git repo will always be located at `$FLAKE` (`config.environment.variables.FLAKE`)
and symlinked to `/etc/nixos` to have everything where NixOS tools
expect things to be.
ie.
```bash
sudo rm -r /etc/nixos
echo "$FLAKE" # /home/matt/.nix
sudo ln -sf /home/matt/.nix /etc/nixos
```
### Flake Outputs
| Output | Description |
| ---------------------------------- | ----------- |
| `nixosConfigurations` | [devices](https://git.nelim.org/matt1432/nixos-configs/src/branch/master/devices)' + ISO's configurations |
| `nixOnDroidConfigurations.default` | [Nix-On-Droid](https://git.nelim.org/matt1432/nixos-configs/src/branch/master/devices/android)'s configuration |
| `packages` | Some custom [packages](https://git.nelim.org/matt1432/nixos-configs/src/branch/master/packages) not available in nixpkgs or modified from it |
| `legacyPackages` | Some custom [package scopes](https://git.nelim.org/matt1432/nixos-configs/src/branch/master/legacyPackages) not available in nixpkgs or modified from it |
| `apps` | Scripts ran from the flake defined [here](https://git.nelim.org/matt1432/nixos-configs/src/branch/master/apps) |
| `homeManagerModules` | [Modules](https://git.nelim.org/matt1432/nixos-configs/src/branch/master/homeManagerModules) made for home-manager |
| `homeManagerModules` | [Modules](https://git.nelim.org/matt1432/nixos-configs/src/branch/master/nixosModules) made for NixOS systems |
| `formatter` | I format nix code with [alejandra](https://github.com/kamadorueda/alejandra) |
| `devShells.default` | A dev shell to build an ISO from the live-image nixosConfiguration |
| `devShells.ags` | A dev shell to have a NodeJS env when I enter my AGS's config directory |
### Flake Inputs
To allow use of the full nix language for my inputs, I use [genflake](https://github.com/jorsn/flakegen).
Therefore, the flake I edit is located at `./outputs.nix`.
I also prefer using a more descriptive format for my inputs like so:
```nix
nixpkgs = {
type = "github";
owner = "NixOS";
repo = "nixpkgs";
# Branch name
ref = "nixos-unstable";
# Pin this input to a specific commit
rev = "842d9d80cfd4560648c785f8a4e6f3b096790e19";
};
```
to make it more clear what is what in the flake URI
### Secrets
All my secrets are in a private git repo that makes use of
[sops-nix](https://github.com/Mic92/sops-nix).
I generate `.sops.yaml` from `.sops.nix`:
```nix
let
wim = "somekey";
binto = "somekey2";
in {
creation_rules = [
{
path_regex = "secrets/[^/]+\\.(yaml|json|env|ini)$";
key_groups = [
{
age = [wim binto];
}
];
}
];
}
```
which is then converted to `.sops.yaml` using
[remarshal](https://github.com/remarshal-project/remarshal)
and this shell command:
```bash
nix eval --json --file ./.sops.nix | remarshal --if json --of yaml > .sops.yaml
```
TLDR: I
**[hate](https://ruudvanasseldonk.com/2023/01/11/the-yaml-document-from-hell)**
YAML
Since I use my laptop with one user, I symlinked the configs to my home
directory following the tutorial [here](https://nixos.wiki/wiki/NixOS_configuration_editors)

View file

@ -1,14 +0,0 @@
{
inputs,
pkgs,
...
}: let
inherit (pkgs.lib) getExe;
mkApp = file: {
program = getExe (pkgs.callPackage file ({} // inputs));
type = "app";
};
in {
updateFlake = mkApp ./update;
}

View file

@ -1 +0,0 @@
use flake $FLAKE#node

View file

@ -1,33 +0,0 @@
{
lib,
buildNpmPackage,
callPackage,
makeWrapper,
nodejs_latest,
...
}: let
inherit (lib) concatMapStringsSep getBin;
inherit (builtins) readFile fromJSON;
packageJSON = fromJSON (readFile ./package.json);
in
buildNpmPackage rec {
pname = packageJSON.name;
inherit (packageJSON) version;
src = ./.;
npmDepsHash = "sha256-nYdr7jbe5wW9Rg0G4l5jbZg8G0o8DioeSGpx+8e0VZI=";
runtimeInputs = [
(callPackage ../../nixosModules/docker/updateImage.nix {})
];
nativeBuildInputs = [makeWrapper];
postInstall = ''
wrapProgram $out/bin/${pname} \
--prefix PATH : ${concatMapStringsSep ":" (p: getBin p) runtimeInputs}
'';
nodejs = nodejs_latest;
meta.mainProgram = pname;
}

View file

@ -1,451 +0,0 @@
import eslint from '@eslint/js';
import jsdoc from 'eslint-plugin-jsdoc';
import stylistic from '@stylistic/eslint-plugin';
import tseslint from 'typescript-eslint';
export default tseslint.config({
files: ['**/*.js', '**/*.ts'],
ignores: ['node_modules/**', 'types/**'],
extends: [
eslint.configs.recommended,
jsdoc.configs['flat/recommended-typescript'],
stylistic.configs['recommended-flat'],
...tseslint.configs.recommended,
...tseslint.configs.stylistic,
],
rules: {
// JSDoc settings
'jsdoc/tag-lines': ['warn', 'any', { startLines: 1 }],
'jsdoc/check-line-alignment': ['warn', 'always', {
tags: ['param', 'arg', 'argument', 'property', 'prop'],
}],
'jsdoc/no-types': 'off',
// Newer settings
'@typescript-eslint/no-extraneous-class': ['off'],
'@typescript-eslint/no-implied-eval': ['off'],
'class-methods-use-this': 'off',
'@stylistic/no-multiple-empty-lines': 'off',
// Pre-flat config
'@typescript-eslint/no-unused-vars': [
'error',
{
args: 'all',
argsIgnorePattern: '^_',
caughtErrors: 'all',
caughtErrorsIgnorePattern: '^_',
destructuredArrayIgnorePattern: '^_',
varsIgnorePattern: '^_',
ignoreRestSiblings: true,
},
],
'array-callback-return': [
'error',
{
allowImplicit: true,
checkForEach: true,
},
],
'no-constructor-return': [
'error',
],
'no-unreachable-loop': [
'error',
{
ignore: [
'ForInStatement',
'ForOfStatement',
],
},
],
'no-use-before-define': [
'error',
{
functions: false,
},
],
'block-scoped-var': [
'error',
],
'curly': [
'warn',
],
'default-case-last': [
'warn',
],
'default-param-last': [
'error',
],
'eqeqeq': [
'error',
'smart',
],
'func-names': [
'warn',
'never',
],
'func-style': [
'warn',
'expression',
],
'logical-assignment-operators': [
'warn',
'always',
],
'no-array-constructor': [
'error',
],
'no-empty-function': [
'warn',
],
'no-empty-static-block': [
'warn',
],
'no-extend-native': [
'error',
],
'no-extra-bind': [
'warn',
],
'no-implicit-coercion': [
'warn',
],
'no-iterator': [
'error',
],
'no-labels': [
'error',
],
'no-lone-blocks': [
'error',
],
'no-lonely-if': [
'error',
],
'no-loop-func': [
'error',
],
'no-magic-numbers': [
'error',
{
ignore: [
-1,
0.1,
0,
1,
2,
3,
4,
5,
10,
12,
33,
66,
100,
255,
360,
450,
500,
1000,
],
ignoreDefaultValues: true,
ignoreClassFieldInitialValues: true,
},
],
'no-multi-assign': [
'error',
],
'no-new-wrappers': [
'error',
],
'no-object-constructor': [
'error',
],
'no-proto': [
'error',
],
'no-return-assign': [
'error',
],
'no-sequences': [
'error',
],
'no-shadow': [
'error',
{
builtinGlobals: true,
allow: [
'Window',
],
},
],
'no-undef-init': [
'warn',
],
'no-undefined': [
'error',
],
'no-useless-constructor': [
'warn',
],
'no-useless-escape': [
'off',
],
'no-useless-return': [
'error',
],
'no-var': [
'error',
],
'no-void': [
'off',
],
'no-with': [
'error',
],
'object-shorthand': [
'error',
'always',
],
'one-var': [
'error',
'never',
],
'operator-assignment': [
'warn',
'always',
],
'prefer-arrow-callback': [
'error',
],
'prefer-const': [
'error',
],
'prefer-object-has-own': [
'error',
],
'prefer-regex-literals': [
'error',
],
'prefer-template': [
'warn',
],
'no-prototype-builtins': 'off',
'@typescript-eslint/no-var-requires': [
'off',
],
'@stylistic/array-bracket-newline': [
'warn',
'consistent',
],
'@stylistic/array-bracket-spacing': [
'warn',
'never',
],
'@stylistic/arrow-parens': [
'warn',
'always',
],
'@stylistic/brace-style': [
'warn',
'stroustrup',
],
'@stylistic/comma-dangle': [
'warn',
'always-multiline',
],
'@stylistic/comma-spacing': [
'warn',
{
before: false,
after: true,
},
],
'@stylistic/comma-style': [
'error',
'last',
],
'@stylistic/dot-location': [
'error',
'property',
],
'@stylistic/function-call-argument-newline': [
'warn',
'consistent',
],
'@stylistic/function-paren-newline': [
'warn',
'consistent',
],
'@stylistic/indent': [
'warn',
4,
{
SwitchCase: 1,
ignoreComments: true,
ignoredNodes: ['TemplateLiteral > *'],
},
],
'@stylistic/key-spacing': [
'warn',
{
beforeColon: false,
afterColon: true,
},
],
'@stylistic/keyword-spacing': [
'warn',
{
before: true,
},
],
'@stylistic/linebreak-style': [
'error',
'unix',
],
'@stylistic/lines-between-class-members': [
'warn',
'always',
{
exceptAfterSingleLine: true,
},
],
'@stylistic/max-len': [
'warn',
{
code: 105,
ignoreComments: true,
ignoreTrailingComments: true,
ignoreUrls: true,
},
],
'@stylistic/multiline-ternary': [
'warn',
'always-multiline',
],
'@stylistic/new-parens': [
'error',
],
'@stylistic/no-mixed-operators': [
'warn',
],
'@stylistic/no-mixed-spaces-and-tabs': [
'error',
],
'@stylistic/no-multi-spaces': [
'error',
],
'@stylistic/no-tabs': [
'error',
],
'@stylistic/no-trailing-spaces': [
'error',
],
'@stylistic/no-whitespace-before-property': [
'warn',
],
'@stylistic/nonblock-statement-body-position': [
'error',
'below',
],
'@stylistic/object-curly-newline': [
'warn',
{
consistent: true,
},
],
'@stylistic/object-curly-spacing': [
'warn',
'always',
],
'@stylistic/operator-linebreak': [
'warn',
'after',
],
'@stylistic/padded-blocks': [
'error',
'never',
],
'@stylistic/padding-line-between-statements': [
'warn',
{
blankLine: 'always',
prev: '*',
next: 'return',
},
{
blankLine: 'always',
prev: [
'const',
'let',
'var',
],
next: '*',
},
{
blankLine: 'any',
prev: [
'const',
'let',
'var',
],
next: [
'const',
'let',
'var',
],
},
{
blankLine: 'always',
prev: [
'case',
'default',
],
next: '*',
},
],
'@stylistic/quote-props': [
'error',
'consistent-as-needed',
],
'@stylistic/quotes': [
'error',
'single',
{
avoidEscape: true,
},
],
'@stylistic/semi': [
'error',
'always',
],
'@stylistic/semi-spacing': [
'warn',
],
'@stylistic/space-before-blocks': [
'warn',
],
'@stylistic/space-before-function-paren': [
'warn',
'never',
],
'@stylistic/space-infix-ops': [
'warn',
],
'@stylistic/spaced-comment': [
'warn',
'always',
],
'@stylistic/switch-colon-spacing': [
'warn',
],
'@stylistic/wrap-regex': [
'warn',
],
},
});

File diff suppressed because it is too large Load diff

View file

@ -1,21 +0,0 @@
{
"name": "update-flake",
"version": "0.0.0",
"bin": "out/bin/app.cjs",
"type": "module",
"scripts": {
"build": "node_ver=$(node -v); esbuild src/app.ts --bundle --platform=node --target=\"node${node_ver:1:2}\" --outfile=out/bin/app.cjs"
},
"dependencies": {
"@eslint/js": "9.11.1",
"@stylistic/eslint-plugin": "2.8.0",
"@types/eslint__js": "8.42.3",
"@types/node": "22.6.0",
"esbuild": "0.24.0",
"eslint": "9.11.1",
"eslint-plugin-jsdoc": "50.2.4",
"jiti": "1.21.6",
"typescript": "5.6.2",
"typescript-eslint": "8.7.0"
}
}

View file

@ -1,78 +0,0 @@
import { spawnSync } from 'node:child_process';
import { writeFileSync } from 'node:fs';
import { parseArgs } from './lib.ts';
import { updateFirefoxAddons } from '././firefox.ts';
import { updateDocker, updateFlakeInputs, updateVuetorrent } from './misc.ts';
/* Constants */
const FLAKE = process.env.FLAKE;
if (!FLAKE) {
console.error('Env var FLAKE not found');
process.exit(1);
}
const args = parseArgs();
if (args['d'] || args['docker']) {
console.log(updateDocker());
}
if (args['i'] || args['inputs']) {
console.log(updateFlakeInputs());
}
if (args['f'] || args['firefox']) {
console.log(updateFirefoxAddons());
}
if (args['v'] || args['vuetorrent']) {
console.log(updateVuetorrent());
}
if (args['a'] || args['all']) {
// Update this first because of nix run cmd
const firefoxOutput = updateFirefoxAddons();
console.log(firefoxOutput);
const flakeOutput = updateFlakeInputs();
console.log(flakeOutput);
const dockerOutput = updateDocker();
console.log(dockerOutput);
const vuetorrentOutput = updateVuetorrent();
console.log(vuetorrentOutput);
spawnSync('nix-fast-build', ['-f', `${FLAKE}#nixFastChecks`], {
shell: true,
stdio: [process.stdin, process.stdout, process.stderr],
});
const output = [
'chore: update flake.lock',
`Flake Inputs:\n${flakeOutput}`,
`Docker Images:\n${dockerOutput}`,
`Firefox Addons:\n${firefoxOutput}`,
`Misc Sources:\n${vuetorrentOutput}`,
].join('\n\n');
if (args['f']) {
writeFileSync(args['f'] as string, output);
}
else {
console.log(output);
}
}
spawnSync('alejandra', ['-q', FLAKE], { shell: true });

View file

@ -1,76 +0,0 @@
import { spawnSync } from 'node:child_process';
import { readFileSync } from 'node:fs';
import { parseFetchurl } from './lib.ts';
/* Constants */
const FLAKE = process.env.FLAKE;
const updateFFZ = () => {
const FILE = `${FLAKE}/legacyPackages/firefox-addons/default.nix`;
const URL = 'https://cdn.frankerfacez.com/script/frankerfacez-4.0-an+fx.xpi';
const HASH = parseFetchurl(URL);
spawnSync('sed', ['-i', `'s,url = .*,url = \"${URL}\";,'`, FILE], { shell: true });
spawnSync('sed', ['-i', `'s,sha256 = .*,sha256 = \"${HASH}\";,'`, FILE], { shell: true });
};
export const updateFirefoxAddons = () => {
console.log('Updating FFZ addon');
updateFFZ();
console.log('Updating firefox addons using mozilla-addons-to-nix');
const DIR = `${FLAKE}/legacyPackages/firefox-addons`;
const GENERATED_FILE = `${DIR}/generated-firefox-addons.nix`;
const SLUGS = `${DIR}/addons.json`;
const nameMap = Object.fromEntries([...JSON.parse(readFileSync(SLUGS, 'utf-8'))]
.map((addon) => [addon.slug, addon.pname || addon.slug]));
const nixExpr = `'
x: let
inherit (builtins) attrValues filter hasAttr isAttrs map;
in
map (d: d.name) (filter (y:
isAttrs y &&
hasAttr "type" y &&
y.type == "derivation") (attrValues x))
'`;
const OLD_VERS = Object.fromEntries([...JSON.parse(spawnSync('nix', [
'eval',
'.#legacyPackages.x86_64-linux.firefoxAddons',
'--apply',
nixExpr,
'--json',
], { shell: true }).stdout.toString())]
.map((p) => {
const pname = p.replace(/-[0-9].*$/, '');
return [pname, p.replace(`${pname}-`, '')];
})
.filter((pinfo) => pinfo[0] !== 'frankerfacez'));
const NEW_VERS = Object.fromEntries(spawnSync(
'nix',
['run', 'sourcehut:~rycee/mozilla-addons-to-nix',
SLUGS, GENERATED_FILE],
{ shell: true },
).stdout
.toString()
.split('\n')
.map((p) => {
const pinfo = p.replace('Fetched ', '').split(' ');
return [nameMap[pinfo[0]], pinfo[2]];
}));
return Object.keys(OLD_VERS)
.sort()
.filter((pname) => OLD_VERS[pname] !== NEW_VERS[pname])
.map((pname) => `${pname}: ${OLD_VERS[pname]} -> ${NEW_VERS[pname]}`)
.join('\n');
};

View file

@ -1,30 +0,0 @@
import { spawnSync } from 'node:child_process';
export const parseArgs = () => {
const args = {} as Record<string, unknown>;
let lastFlag: string | null = null;
for (let i = 2; i < process.argv.length; ++i) {
const arg = process.argv[i];
if (arg.toString().startsWith('-')) {
lastFlag = arg.toString().replace(/^-{1,2}/, '');
args[lastFlag] = true;
}
else if (lastFlag) {
args[lastFlag] = arg;
lastFlag = null;
}
else {
console.error(`Could not parse args: ${arg.toString()}`);
}
}
return args;
};
export const parseFetchurl = (url: string) => JSON.parse(spawnSync(
'nix', ['store', 'prefetch-file', '--refresh', '--json',
'--hash-type', 'sha256', url, '--name', '"escaped"'], { shell: true },
).stdout.toString()).hash;

View file

@ -1,75 +0,0 @@
import { readdirSync, writeFileSync } from 'node:fs';
import { spawnSync } from 'node:child_process';
import { parseFetchurl } from './lib.ts';
/* Constants */
const FLAKE = process.env.FLAKE;
export const updateFlakeInputs = () => {
const output = spawnSync(
`git restore flake.lock &> /dev/null; nix flake update --flake ${FLAKE}` +
' |& grep -v "warning: updating lock file"',
[],
{ shell: true },
).stdout
.toString()
// Add an extra blank line between inputs
.split('\n•')
.join('\n\n•')
// Shorten git revs to help readability
.split('\n')
.map((l) => l
.replace(/.{33}\?narHash=sha256[^']*/, '')
.replace(/&rev=(.{7})[^'&]*/, (_, backref) => `&rev=${backref}`))
.join('\n');
return output;
};
export const updateDocker = () => {
let updates = '';
const FILE = `${FLAKE}/devices/nos/modules/docker`;
readdirSync(FILE, { withFileTypes: true, recursive: true }).forEach((path) => {
if (path.name === 'compose.nix') {
console.log(`Updating ${path.parentPath.split('/').at(-1)} images`);
updates += spawnSync('updateImages', [path.parentPath], { shell: true })
.stdout.toString();
}
});
return updates;
};
const genVueText = (version: string, hash: string, url: string) =>
`# This file was autogenerated. DO NOT EDIT!
{
version = "${version}";
url = "${url}";
hash = "${hash}";
}
`;
export const updateVuetorrent = () => {
const FILE = `${FLAKE}/devices/nos/modules/qbittorrent/vuetorrent.nix`;
const OLD_VERSION = JSON.parse(spawnSync('nix',
['eval', '-f', FILE, '--json'],
{ shell: true }).stdout.toString()).version;
const VERSION = JSON.parse(spawnSync('curl',
['-s', 'https://api.github.com/repos/VueTorrent/VueTorrent/releases/latest'],
{ shell: true }).stdout.toString()).tag_name.replace('v', '');
const URL = `https://github.com/VueTorrent/VueTorrent/releases/download/v${VERSION}/vuetorrent.zip`;
const HASH = parseFetchurl(URL);
const fileText = genVueText(VERSION, HASH, URL);
writeFileSync(FILE, fileText);
return OLD_VERSION !== VERSION ? `Vuetorrent: ${OLD_VERSION} -> ${VERSION}` : '';
};

View file

@ -1,31 +0,0 @@
{
"compilerOptions": {
// Env
"target": "ESNext",
"lib": ["ESNext"],
// Module
"module": "nodenext",
"moduleResolution": "nodenext",
"allowImportingTsExtensions": true,
"baseUrl": ".",
// Emit
"noEmit": true,
"newLine": "LF",
// Interop
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
// Type Checking
"strict": true,
"noImplicitAny": false,
"allowJs": true,
"checkJs": true
},
"includes": [
"*.ts",
"**/*.ts",
"*.js",
"**/*.js"
]
}

View file

@ -1,7 +0,0 @@
{
pkgs,
self,
}: let
nixosMachines = import ./machines.nix {inherit pkgs self;};
in
nixosMachines

View file

@ -1,10 +0,0 @@
# CI: https://github.com/Mic92/dotfiles/blob/c2f538934d67417941f83d8bb65b8263c43d32ca/flake.nix#L168
{
pkgs,
self,
}: let
inherit (pkgs.lib) filterAttrs mapAttrs' nameValuePair;
in
mapAttrs'
(name: config: nameValuePair "nixos-${name}" config.config.system.build.toplevel)
((filterAttrs (_: config: config.pkgs.system == pkgs.system)) self.nixosConfigurations)

View file

@ -1,131 +0,0 @@
{
config,
home-manager,
lib,
nh,
pkgs,
self,
...
}: {
imports = [
./vars
./modules
./packages.nix
self.nixosModules.borgbackup
home-manager.nixosModules.home-manager
];
boot.tmp.useTmpfs = true;
systemd.services.nix-daemon = {
environment.TMPDIR = "/home/nix-cache";
preStart = ''
mkdir -p ${config.systemd.services.nix-daemon.environment.TMPDIR}
'';
};
nix = {
package = pkgs.nixVersions.nix_2_24;
# Edit nix.conf
settings = {
# Store
keep-outputs = true;
keep-derivations = true;
auto-optimise-store = true;
# Commands
experimental-features = ["nix-command" "flakes"];
http-connections = 0; # unlimited for local cache
warn-dirty = false;
show-trace = true;
allow-import-from-derivation = true;
# remote building
trusted-users = ["matt" "nixremote"];
};
};
programs.nh = {
enable = true;
package = nh.packages.${pkgs.system}.default;
# weekly cleanup
clean = {
enable = true;
extraArgs = "--keep-since 30d";
};
};
services = {
fwupd.enable = true;
xserver.xkb = {
layout = "ca";
variant = "multix";
};
};
boot.supportedFilesystems = ["ext4" "xfs" "btrfs" "vfat" "ntfs"];
system.fsPackages = builtins.attrValues {
inherit
(pkgs)
btrfs-progs
nfs-utils
ntfs3g
xfsprogs
;
};
environment.variables.NPM_CONFIG_GLOBALCONFIG = "/etc/npmrc";
environment.etc.npmrc.text = ''
fund = false
update-notifier = false
'';
environment.systemPackages = builtins.attrValues {
# Peripherals
inherit
(pkgs)
hdparm
pciutils
usbutils
rar
;
};
home-manager.users = let
inherit (lib) mkIf mkOption types;
inherit (config.vars) mainUser;
default = {
imports = [
# Make the vars be the same on Nix and HM
{
options.vars = mkOption {
type = types.attrs;
readOnly = true;
default = config.vars;
};
}
{
programs.bash.sessionVariables = {
FLAKE = config.environment.variables.FLAKE;
};
}
./home
./home/trash-d
];
home.stateVersion = config.system.stateVersion;
};
in {
root = default;
greeter = mkIf (config.services.greetd.enable) default;
${mainUser} = default;
};
}

View file

@ -1,27 +0,0 @@
# Check git status of nix configs
fetchNix() {(
cd "$FLAKE" || exit 1
git fetch --all --quiet
GIT=$(git -c color.status=always status |
grep -v -e "On branch" \
-e "up to date" \
-e 'use "git' \
-e "nothing to commit")
CHECK=$(echo "$GIT" | sed '/^$/d')
if [ "$CHECK" != "" ]; then
echo "$GIT"
echo
fi
)}
# Check for internet
if wget -q --spider https://git.nelim.org; then
fetchNix
else
echo "Offline"
fi
# Pokemon Sprite
pokemon-colorscripts -r 1-5

View file

@ -1,31 +0,0 @@
function colorgrid() {
iter=16
while [ $iter -lt 52 ]
do
second=$[$iter+36]
third=$[$second+36]
four=$[$third+36]
five=$[$four+36]
six=$[$five+36]
seven=$[$six+36]
if [ $seven -gt 250 ];then seven=$[$seven-251]; fi
echo -en "\033[38;5;$(echo $iter)m█ "
printf "%03d" $iter
echo -en " \033[38;5;$(echo $second)m█ "
printf "%03d" $second
echo -en " \033[38;5;$(echo $third)m█ "
printf "%03d" $third
echo -en " \033[38;5;$(echo $four)m█ "
printf "%03d" $four
echo -en " \033[38;5;$(echo $five)m█ "
printf "%03d" $five
echo -en " \033[38;5;$(echo $six)m█ "
printf "%03d" $six
echo -en " \033[38;5;$(echo $seven)m█ "
printf "%03d" $seven
iter=$[$iter+1]
printf '\r\n'
done
}

View file

@ -1,3 +0,0 @@
# Modified from https://github.com/dracula/fzf
export FZF_DEFAULT_OPTS='--color=fg:#f8f8f2,hl:#bd93f9 --color=fg+:#f8f8f2,hl+:#bd93f9 --color=info:#ffb86c,prompt:#50fa7b,pointer:#ff79c6 --color=marker:#ff79c6,spinner:#ffb86c,header:#6272a4'

View file

@ -1,10 +0,0 @@
# Modified from https://github.com/dracula/man-pages
#man-page colors
export LESS_TERMCAP_mb=$'\e[1;31m' # begin bold
export LESS_TERMCAP_md=$'\e[1;34m' # begin blink
export LESS_TERMCAP_so=$'\e[01;45;37m' # begin reverse video
export LESS_TERMCAP_us=$'\e[01;36m' # begin underline
export LESS_TERMCAP_me=$'\e[0m' # reset bold/blink
export LESS_TERMCAP_se=$'\e[0m' # reset reverse video
export LESS_TERMCAP_ue=$'\e[0m' # reset underline

View file

@ -1,143 +0,0 @@
{
config,
lib,
...
}: let
inherit (lib) concatStrings fileContents;
inherit (config.vars) promptColors;
in {
imports = [./programs.nix];
programs = {
starship = {
enable = true;
enableBashIntegration = true;
settings = {
format = concatStrings [
""
"[](fg:${promptColors.firstColor})"
"[ ](bg:${promptColors.firstColor} fg:#090c0c)"
"[](bg:${promptColors.secondColor} fg:${promptColors.firstColor})"
"$username$hostname"
"[](fg:${promptColors.secondColor} bg:${promptColors.thirdColor})"
"$directory"
"[](fg:${promptColors.thirdColor} bg:${promptColors.fourthColor})"
"$git_branch"
"[](fg:${promptColors.fourthColor})$shlvl$nix_shell"
"\n$character"
];
username = {
show_always = true;
style_user = "fg:${promptColors.textColor} bg:${promptColors.secondColor}";
style_root = "fg:red bg:${promptColors.secondColor} blink";
format = "[ $user]($style)";
};
hostname = {
ssh_only = false;
style = "fg:${promptColors.textColor} bg:${promptColors.secondColor}";
format = "[@$hostname ]($style)";
};
directory = {
style = "fg:${promptColors.firstColor} bg:${promptColors.thirdColor}";
format = "[ $path ]($style)";
truncate_to_repo = false;
truncation_length = 0;
substitutions = {
"Documents" = "󰈙 ";
"Downloads" = " ";
"Music" = " ";
"Pictures" = " ";
};
};
git_branch = {
style = "fg:${promptColors.secondColor} bg:${promptColors.fourthColor}";
symbol = "";
format = "[ $symbol $branch ]($style)";
};
shlvl = {
disabled = false;
repeat = true;
symbol = "󰔳 ";
format = "[ $symbol]($style)";
threshold = 1;
};
nix_shell = {
symbol = " ";
format = "[ $symbol]($style)";
};
character = {
success_symbol = "[\\$](bold green)";
error_symbol = "[\\$](bold red)";
};
};
};
bash = {
enable = true;
enableCompletion = true;
historyFile = "\$HOME/.cache/.bash_history";
historyFileSize = 100000; # default
historySize = 10000; # default
historyControl = [
"erasedups"
"ignorespace"
];
historyIgnore = [
"ls"
"exit"
"logout"
];
shellOptions = [
"histappend"
"checkwinsize"
"extglob"
"globstar"
"checkjobs"
"autocd"
"cdspell"
"dirspell"
"dotglob"
];
shellAliases = {
# Add whitespace after, to allow
# sudo to inherit all other aliases
sudo = "sudo ";
ls = "ls -lah --color=auto";
tree = "tree -a -I node_modules";
cp = "cp -r";
};
#profileExtra = ''
#'';
bashrcExtra =
# bash
''
# Check if shell is interactive
[[ $- == *i* ]] || return 0
${fileContents ./config/dracula/less.sh}
${fileContents ./config/dracula/fzf.sh}
${fileContents ./config/colorgrid.sh}
${fileContents ./config/bashrc}
'';
#initExtra = ''
#'';
#logoutExtra = ''
#'';
};
};
}

View file

@ -1,54 +0,0 @@
{
pkgs,
config,
self,
...
}: {
programs = {
fzf = {
enable = true;
enableBashIntegration = true;
};
bash = {
sessionVariables = {
inherit (config.home.sessionVariables) RIPGREP_CONFIG_PATH;
};
shellAliases = {
rg = "rga";
cat = "bat ";
man = "BAT_THEME='default' batman ";
};
};
ripgrep = {
enable = true;
package = pkgs.ripgrep-all;
arguments = [
"--max-columns=150"
"--max-columns-preview"
"--hidden"
"--glob=!.git/*"
"--smart-case"
"--sort"
"path"
];
};
jq.enable = true;
htop.enable = true;
bat = {
enable = true;
config.theme = "dracula-bat";
themes.dracula-bat.src = self.legacyPackages.${pkgs.system}.dracula.bat;
extraPackages = builtins.attrValues {
inherit (pkgs.bat-extras) batman;
};
};
};
}

View file

@ -1,10 +0,0 @@
{...}: {
imports = [
./bash
./direnv
./git
./neovim
./nix-index
./tmux
];
}

View file

@ -1,11 +0,0 @@
{pkgs, ...}: {
programs.direnv = {
enable = true;
enableBashIntegration = true;
nix-direnv = {
enable = true;
package = pkgs.nix-direnv;
};
};
}

View file

@ -1,78 +0,0 @@
{
pkgs,
self,
...
}: let
mkRemoteConf = remote: email: name: {
condition = "hasconfig:remote.*.url:${remote}:*/**";
contents.user = {inherit email name;};
};
mkDefaultRemote = remote: mkRemoteConf remote "matt@nelim.org" "matt1432";
in {
programs = {
git = {
enable = true;
package = pkgs.gitFull;
lfs.enable = true;
includes = [
{path = toString self.legacyPackages.${pkgs.system}.dracula.git;}
(mkDefaultRemote "https://github.com")
(mkDefaultRemote "git@github.com")
(mkDefaultRemote "git@git.nelim.org")
(mkRemoteConf "git@gitlab.info.uqam.ca" "gj591944@ens.uqam.ca" "Mathis Hurtubise")
];
delta = {
enable = true;
options = {
side-by-side = true;
line-numbers-zero-style = "#E6EDF3";
};
};
extraConfig = {
diff.sopsdiffer.textconv = "sops --config /dev/null -d";
# https://github.com/dandavison/delta/issues/630#issuecomment-860046929
pager = let
cmd = "LESS='LRc --mouse' ${pkgs.delta}/bin/delta";
in {
diff = cmd;
show = cmd;
stash = cmd;
log = cmd;
reflog = cmd;
};
sendemail = {
smtpserver = "127.0.0.1";
smtpuser = "matt@nelim.org";
smtpencryption = "tls";
smtpserverport = 1025;
smtpsslcertpath = "";
};
};
};
};
home.packages = [
(pkgs.writeShellApplication {
name = "chore";
runtimeInputs = [pkgs.git];
text = ''
DIR=''${1:-"$FLAKE"}
cd "$DIR" || exit 1
git add flake.lock
git commit -m 'chore: update flake.lock'
git push
'';
})
];
}

View file

@ -1,107 +0,0 @@
# see https://github.com/CppCXY/EmmyLuaCodeStyle
[*.lua]
# [basic]
indent_style = space
indent_size = 4
quote_style = single
max_line_length = 120
end_of_line = lf
table_separator_style = comma
trailing_table_separator = smart
call_arg_parentheses = keep
# [space]
space_around_table_field_list = true
space_before_attribute = true
space_before_function_open_parenthesis = false
space_before_function_call_open_parenthesis = false
space_before_closure_open_parenthesis = false
space_before_function_call_single_arg = false
space_before_open_square_bracket = false
space_inside_function_call_parentheses = false
space_inside_function_param_list_parentheses = false
space_inside_square_brackets = false
# like t[#t+1] = 1
space_around_table_append_operator = false
ignore_spaces_inside_function_call = false
# detail number or 'keep'
space_before_inline_comment = 1
# convert '---' to '--- ' or '--' to '-- '
space_after_comment_dash = false
# [operator space]
space_around_math_operator = true
space_around_math_operator.exponent = false
space_around_concat_operator = true
space_around_logical_operator = true
space_around_assign_operator = true
space_after_comma = true
space_after_comma_in_for_statement = true
# [align]
align_call_args = false
align_function_params = true
align_continuous_assign_statement = true
align_continuous_rect_table_field = true
align_continuous_line_space = 2
align_if_branch = false
# option none / always / contain_curly/
align_array_table = true
align_continuous_similar_call_args = false
align_continuous_inline_comment = true
# option none / always / only_call_stmt
align_chain_expr = none
# [indent]
never_indent_before_if_condition = false
never_indent_comment_on_if_branch = false
keep_indents_on_empty_lines = false
allow_non_indented_comments = false
# [line space]
# The following configuration supports four expressions
# keep
# fixed(n)
# min(n)
# max(n)
# for eg. min(2)
line_space_after_if_statement = keep
line_space_after_do_statement = keep
line_space_after_while_statement = keep
line_space_after_repeat_statement = keep
line_space_after_for_statement = keep
line_space_after_local_or_assign_statement = keep
line_space_after_function_statement = fixed(2)
line_space_after_expression_statement = keep
line_space_after_comment = keep
line_space_around_block = fixed(1)
# [line break]
break_all_list_when_line_exceed = false
auto_collapse_lines = false
break_before_braces = false
# [preference]
ignore_space_after_colon = false
end_statement_with_semicolon = always

View file

@ -1,101 +0,0 @@
{
config,
pkgs,
...
}: {
imports = [
./git.nix
./langs
./theme.nix
./treesitter.nix
];
programs = {
neovim = {
enable = true;
extraLuaConfig =
# lua
''
-- by default, the indent is 2 spaces.
vim.opt.smartindent = true;
vim.opt.expandtab = true;
vim.opt.shiftwidth = 2;
vim.opt.softtabstop = 2;
vim.opt.tabstop = 2;
vim.opt.number = true;
vim.opt.relativenumber = true;
vim.opt.undofile = true;
vim.opt.undodir = '${config.xdg.cacheHome}/nvim/';
-- Always show the signcolumn, otherwise it would shift
-- the text each time diagnostics appear/become resolved
vim.opt.signcolumn = 'yes';
-- remove highlight on words
vim.keymap.set('n', '<esc>', ':noh<cr><esc>', {
noremap = true,
silent = true,
});
-- Get rid of deprecated messages
vim.tbl_add_reverse_lookup = function(tbl)
for k, v in pairs(tbl) do
tbl[v] = k;
end
end;
vim.tbl_islist = function(tbl)
return vim.islist(tbl);
end;
vim.diagnostic.is_disabled = function()
return not vim.diagnostic.is_enabled();
end;
vim.lsp.buf_get_clients = function()
return vim.lsp.get_clients();
end;
vim.lsp.get_active_clients = function()
return vim.lsp.get_clients();
end;
'';
plugins = [
pkgs.vimPlugins.fzfWrapper
pkgs.vimPlugins.fzf-vim
{
plugin = pkgs.vimPlugins.todo-comments-nvim;
type = "lua";
config =
# lua
''
require('todo-comments').setup();
'';
}
{
plugin = pkgs.vimPlugins.mini-nvim;
type = "lua";
config =
# lua
''
-- TODO: see how this works
local ts_input = require('mini.surround').gen_spec.input.treesitter;
require('mini.surround').setup({
custom_surroundings = {
-- Use tree-sitter to search for function call
f = {
input = ts_input({
outer = '@call.outer',
inner = '@call.inner',
});
},
},
});
'';
}
];
};
};
}

View file

@ -1,31 +0,0 @@
{pkgs, ...}: {
programs = {
neovim = {
plugins = [
pkgs.vimPlugins.fugitive
{
plugin = pkgs.vimPlugins.gitsigns-nvim;
type = "lua";
config =
# lua
''
local gitsigns = require("gitsigns");
local function visual_stage()
local first_line = vim.fn.line('v');
local last_line = vim.fn.getpos('.')[2];
gitsigns.stage_hunk({ first_line, last_line });
end
vim.keymap.set("v", "gs", function()
visual_stage()
end);
gitsigns.setup();
'';
}
];
};
};
}

View file

@ -1,48 +0,0 @@
{
config,
pkgs,
lib,
...
}: let
inherit (lib) getExe mkIf;
inherit (config.vars) neovimIde;
in {
programs = {
# I love doing typos
bash.shellAliases = {
nivm = "nvim";
nivim = "nvim";
};
neovim = {
defaultEditor = true;
viAlias = true;
vimAlias = true;
extraPackages = mkIf neovimIde [
pkgs.nodePackages.bash-language-server
pkgs.shellcheck
];
extraLuaConfig =
mkIf neovimIde
# lua
''
vim.api.nvim_create_autocmd('FileType', {
pattern = 'sh',
command = 'setlocal ts=4 sw=4 sts=0 expandtab',
});
require('lspconfig').bashls.setup({
capabilities = require('cmp_nvim_lsp').default_capabilities(),
settings = {
bashIde = {
shellcheckPath = '${getExe pkgs.shellcheck}',
},
},
});
'';
};
};
}

View file

@ -1,52 +0,0 @@
{
config,
pkgs,
lib,
...
}: let
inherit (lib) mkIf;
inherit (config.vars) neovimIde;
in
mkIf neovimIde {
programs = {
neovim = {
extraPackages = builtins.attrValues {
inherit
(pkgs)
gcc
clang-tools
cmake-language-server
;
};
extraLuaConfig =
# lua
''
vim.api.nvim_create_autocmd('FileType', {
pattern = { 'cpp' , 'c'},
command = 'setlocal ts=4 sw=4 sts=0 expandtab',
});
local lsp = require('lspconfig');
lsp.cmake.setup({
capabilities = require('cmp_nvim_lsp').default_capabilities(),
});
lsp.clangd.setup({
capabilities = require('cmp_nvim_lsp').default_capabilities(),
handlers = require('lsp-status').extensions.clangd.setup(),
on_attach = function(_, bufnr)
require("clangd_extensions.inlay_hints").setup_autocmd()
require("clangd_extensions.inlay_hints").set_inlay_hints()
end,
});
'';
plugins = builtins.attrValues {
inherit (pkgs.vimPlugins) clangd_extensions-nvim;
};
};
};
}

View file

@ -1,103 +0,0 @@
{
config,
lib,
pkgs,
...
}: let
inherit (lib) fileContents mkBefore mkIf;
inherit (config.vars) neovimIde;
in {
imports = [
./bash.nix
./clang.nix
./hyprlang.nix
./java.nix
./json.nix
./lua.nix
./markdown.nix
./nix.nix
./python.nix
./rust.nix
./web.nix
];
programs = mkIf neovimIde {
neovim = {
extraLuaConfig =
mkBefore
# lua
''
-- Start completion / snippet stuff
vim.g.coq_settings = {
auto_start = 'shut-up',
keymap = {
recommended = false,
},
-- https://github.com/NixOS/nixpkgs/issues/168928#issuecomment-1109581739
xdg = true,
};
-- Add formatting cmd
vim.api.nvim_create_user_command(
'Format',
function()
vim.lsp.buf.format({ async = true });
end,
{}
);
-- LSP-Status setup
local lsp_status = require('lsp-status');
lsp_status.register_progress();
-- Remove LSP highlighting to use Treesitter
vim.api.nvim_create_autocmd('LspAttach', {
callback = function(args)
local client = vim.lsp.get_client_by_id(args.data.client_id);
client.server_capabilities.semanticTokensProvider = nil;
lsp_status.on_attach(client);
end,
});
-- Disable virtual_text since it's redundant due to lsp_lines.
vim.diagnostic.config({
virtual_text = false,
});
require('lsp_lines').setup();
'';
plugins =
(builtins.attrValues {
inherit
(pkgs.vimPlugins)
nvim-lspconfig
lsp-status-nvim
lsp_lines-nvim
cmp-buffer
cmp-nvim-lsp
cmp-path
cmp-spell
vim-vsnip
;
})
++ [
{
plugin = pkgs.vimPlugins.nvim-cmp;
type = "lua";
config = fileContents ../plugins/cmp.lua;
}
{
plugin = pkgs.vimPlugins.nvim-autopairs;
type = "lua";
config =
# lua
''
require('nvim-autopairs').setup({});
'';
}
];
};
};
}

View file

@ -1,26 +0,0 @@
{
config,
lib,
...
}: let
inherit (lib) mkIf;
inherit (config.vars) neovimIde;
in
mkIf neovimIde {
programs = {
neovim = {
extraLuaConfig =
# lua
''
vim.filetype.add({
pattern = { ['.*/hypr/.*%.conf'] = 'hyprlang' },
});
vim.api.nvim_create_autocmd('FileType', {
pattern = 'hyprlang',
command = 'setlocal ts=4 sw=4 sts=0 expandtab',
});
'';
};
};
}

View file

@ -1,83 +0,0 @@
{
config,
lib,
pkgs,
...
}: let
inherit (lib) getExe mkIf;
inherit (config.vars) neovimIde;
javaSdk = pkgs.temurin-bin-17;
javaPkgs = builtins.attrValues {inherit (pkgs) gradle maven;};
in
mkIf neovimIde {
home.packages = javaPkgs;
xdg.dataFile.".gradle/gradle.properties".text = ''
org.gradle.java.home = ${javaSdk}
'';
programs = {
java = {
enable = true;
package = javaSdk;
};
neovim = {
extraPackages = javaPkgs;
extraLuaConfig =
# lua
''
vim.api.nvim_create_autocmd('FileType', {
pattern = 'java',
command = 'setlocal ts=4 sw=4 sts=0 expandtab',
});
'';
plugins = [
{
# TOOD: setup debugger https://github.com/mfussenegger/nvim-jdtls#debugger-via-nvim-dap
plugin = pkgs.vimPlugins.nvim-jdtls;
type = "lua";
config =
# lua
''
--
local startJdtls = function()
local config = {
capabilities = require('cmp_nvim_lsp').default_capabilities(),
cmd = { '${getExe pkgs.jdt-language-server}' },
root_dir = vim.fs.dirname(vim.fs.find(
{ 'gradlew', '.git', 'mvnw', 'pom.xml' },
{ upward = true }
)[1]),
settings = {
java = {
configuration = {
runtimes = {
{
name = 'JavaSE-17',
path = '${javaSdk}',
},
},
},
},
},
};
require('jdtls').start_or_attach(config);
end
vim.api.nvim_create_autocmd('FileType', {
pattern = 'java',
callback = startJdtls,
});
'';
}
];
};
};
}

View file

@ -1,55 +0,0 @@
{
config,
lib,
pkgs,
...
}: let
inherit (lib) mkIf;
inherit (config.vars) neovimIde;
in
mkIf neovimIde {
programs = {
neovim = {
extraPackages = builtins.attrValues {
inherit
(pkgs)
vscode-langservers-extracted
yaml-language-server
;
};
extraLuaConfig =
# lua
''
vim.api.nvim_create_autocmd('FileType', {
pattern = 'yaml',
command = 'setlocal ts=4 sw=4 sts=0 expandtab',
});
vim.api.nvim_create_autocmd('FileType', {
pattern = 'json',
command = 'setlocal ts=4 sw=4 sts=0 expandtab',
});
local lsp = require('lspconfig');
lsp.jsonls.setup({
capabilities = require('cmp_nvim_lsp').default_capabilities(),
});
lsp.yamlls.setup({
capabilities = require('cmp_nvim_lsp').default_capabilities(),
settings = {
yaml = {
schemas = {
[
"https://json.schemastore.org/github-workflow.json"
] = "/.github/workflows/*",
},
},
},
});
'';
};
};
}

View file

@ -1,49 +0,0 @@
{
config,
pkgs,
lib,
...
}: let
inherit (lib) mkIf;
inherit (config.vars) neovimIde;
flakeEnv = config.programs.bash.sessionVariables.FLAKE;
in
mkIf neovimIde {
programs = {
neovim = {
extraPackages = builtins.attrValues {
inherit (pkgs) lua-language-server;
};
plugins = [
{
plugin = pkgs.vimPlugins.neodev-nvim;
type = "lua";
config =
# lua
''
vim.api.nvim_create_autocmd('FileType', {
pattern = 'lua',
command = 'setlocal ts=4 sw=4 sts=0 expandtab',
});
-- IMPORTANT: make sure to setup neodev BEFORE lspconfig
require("neodev").setup({
override = function(root_dir, library)
if root_dir:find('${flakeEnv}', 1, true) == 1 then
library.enabled = true
library.plugins = true
end
end,
});
require('lspconfig').lua_ls.setup({
capabilities = require('cmp_nvim_lsp').default_capabilities(),
});
'';
}
];
};
};
}

View file

@ -1,128 +0,0 @@
{
config,
lib,
pkgs,
self,
vimplugin-easytables-src,
...
}: let
inherit (lib) mkIf;
inherit (config.vars) neovimIde;
inherit (import "${self}/lib" {inherit pkgs;}) buildPlugin;
in
mkIf neovimIde {
programs = {
neovim = {
extraPackages = builtins.attrValues {
inherit
(pkgs)
pandoc
texlab
texliveFull
rubber
;
};
extraLuaConfig =
# lua
''
local lsp = require('lspconfig');
lsp.texlab.setup({
capabilities = require('cmp_nvim_lsp').default_capabilities(),
settings = {
texlab = {
formatterLineLength = 100,
latexFormatter = 'latexindent',
latexindent = {
modifyLineBreaks = false,
["local"] = '.indentconfig.yaml';
},
},
},
});
'';
plugins = [
{
plugin = buildPlugin "easytables-nvim" vimplugin-easytables-src;
type = "lua";
config =
# lua
''
require('easytables').setup();
'';
}
{
plugin = pkgs.vimPlugins.knap;
type = "lua";
config =
# lua
''
--
vim.api.nvim_create_autocmd('FileType', {
pattern = 'tex',
command = 'setlocal ts=4 sw=4 sts=0 expandtab',
});
vim.g.knap_settings = {
-- HTML
htmloutputext = 'html',
htmltohtml = 'none',
htmltohtmlviewerlaunch = "",
htmltohtmlviewerrefresh = 'none',
-- Markdown
mdoutputext = 'html',
markdownoutputext = 'html',
-- Markdown to PDF
mdtopdf = 'pandoc %docroot% -o /tmp/%outputfile%',
markdowntopdf = 'pandoc %docroot% -o /tmp/%outputfile%',
mdtopdfviewerlaunch = 'sioyek /tmp/%outputfile%',
markdowntopdfviewerlaunch = 'sioyek /tmp/%outputfile%',
mdtopdfviewerrefresh = 'none',
markdowntopdfviewerrefresh = "none",
-- Markdown to HTML
mdtohtml = 'pandoc --standalone %docroot% -o /tmp/%outputfile%',
markdowntohtml = 'pandoc --standalone %docroot% -o /tmp/%outputfile%',
mdtohtmlviewerlaunch = 'firefox -new-window /tmp/%outputfile%',
markdowntohtmlviewerlaunch = 'firefox -new-window /tmp/%outputfile%',
mdtohtmlviewerrefresh = 'none',
markdowntohtmlviewerrefresh = 'none',
-- LaTeX
-- TODO: stop from polluting workspace
};
-- F4 processes the document once, and refreshes the view
vim.keymap.set({ 'n', 'v', 'i' }, '<F4>', function()
require('knap').process_once();
end);
-- F5 closes the viewer application, and
-- allows settings to be reset
vim.keymap.set({ 'n', 'v', 'i' }, '<F5>', function()
require('knap').close_viewer();
end);
-- F6 toggles the auto-processing on and off
vim.keymap.set({ 'n', 'v', 'i' }, '<F6>', function()
require('knap').toggle_autopreviewing();
end);
-- F7 invokes a SyncTeX forward search, or similar,
-- where appropriate
vim.keymap.set({ 'n', 'v', 'i' }, '<F7>', function()
require('knap').forward_jump();
end);
'';
}
];
};
};
}

View file

@ -1,75 +0,0 @@
{
config,
pkgs,
lib,
nixd,
self,
...
}: let
inherit (lib) getExe hasPrefix mkIf removePrefix;
inherit (config.vars) hostName mainUser neovimIde;
defaultFormatter = self.formatter.${pkgs.system};
nixdPkg = nixd.packages.${pkgs.system}.default;
flakeEnv = config.programs.bash.sessionVariables.FLAKE;
flakeDir = "${removePrefix "/home/${mainUser}/" flakeEnv}";
in
mkIf neovimIde {
assertions = [
{
assertion =
neovimIde
&& hasPrefix "/home/${mainUser}/" flakeEnv
|| !neovimIde;
message = ''
Your $FLAKE environment variable needs to point to a directory in
the main users' home to use the neovim module.
'';
}
];
home.packages = [
defaultFormatter
nixdPkg
];
# nixd by default kinda spams LspLog
home.sessionVariables.NIXD_FLAGS = "-log=error";
xdg.dataFile."${flakeDir}/.nixd.json".text = builtins.toJSON {
nixpkgs = {
expr = "import (builtins.getFlake \"${flakeDir}\").inputs.nixpkgs {}";
};
options.nixos = {
expr = "(builtins.getFlake \"${flakeDir}\").nixosConfigurations.${hostName}.options";
};
};
programs = {
neovim = {
extraPackages = [
nixdPkg
];
extraLuaConfig =
# lua
''
require('lspconfig').nixd.setup({
capabilities = require('cmp_nvim_lsp').default_capabilities(),
filetypes = { 'nix', 'in.nix' },
settings = {
nixd = {
formatting = {
-- TODO: Try to find <flake>.formatter
command = { '${getExe defaultFormatter}' },
},
},
},
});
'';
};
};
}

View file

@ -1,28 +0,0 @@
{
config,
lib,
pkgs,
...
}: let
inherit (lib) mkIf;
inherit (config.vars) neovimIde;
in
mkIf neovimIde {
programs = {
neovim = {
withPython3 = true;
extraPackages = [
pkgs.basedpyright
];
extraLuaConfig =
# lua
''
require('lspconfig').basedpyright.setup({
capabilities = require('cmp_nvim_lsp').default_capabilities(),
});
'';
};
};
}

View file

@ -1,37 +0,0 @@
{
config,
pkgs,
lib,
...
}: let
inherit (lib) mkIf;
inherit (config.vars) neovimIde;
in
mkIf neovimIde {
programs = {
neovim = {
extraPackages = builtins.attrValues {
inherit
(pkgs)
cargo
rustc
rust-analyzer
rustfmt
;
};
extraLuaConfig =
# lua
''
vim.api.nvim_create_autocmd('FileType', {
pattern = { 'rust' },
command = 'setlocal ts=4 sw=4 sts=0 expandtab',
});
require('lspconfig').rust_analyzer.setup({
capabilities = require('cmp_nvim_lsp').default_capabilities(),
});
'';
};
};
}

View file

@ -1,160 +0,0 @@
{
config,
lib,
pkgs,
self,
vimplugin-ts-error-translator-src,
...
}: let
inherit (lib) mkIf;
inherit (config.vars) neovimIde;
inherit (import "${self}/lib" {inherit pkgs;}) buildPlugin;
in
mkIf neovimIde {
programs = {
neovim = {
withNodeJs = true;
extraPackages = builtins.attrValues {
inherit
(pkgs)
nodejs_latest
vscode-langservers-extracted
;
inherit
(pkgs.nodePackages)
npm
neovim
;
};
extraLuaConfig =
# lua
''
vim.api.nvim_create_autocmd('FileType', {
pattern = { 'javascript', 'javascriptreact', 'javascript.jsx', 'typescript', 'typescriptreact', 'typescript.tsx', 'css', 'scss' },
command = 'setlocal ts=4 sw=4 sts=0 expandtab',
});
vim.api.nvim_create_autocmd('FileType', {
pattern = 'html',
command = 'setlocal ts=2 sw=2 expandtab',
});
vim.api.nvim_create_autocmd('FileType', {
pattern = 'scss',
command = 'setlocal iskeyword+=@-@',
});
local lsp = require('lspconfig');
local tsserver = require('typescript-tools');
tsserver.setup({
capabilities = require('cmp_nvim_lsp').default_capabilities(),
handlers = {
-- format error code with better error message
['textDocument/publishDiagnostics'] = function(err, result, ctx, config)
require('ts-error-translator').translate_diagnostics(err, result, ctx, config)
vim.lsp.diagnostic.on_publish_diagnostics(err, result, ctx, config)
end,
},
});
lsp.eslint.setup({
capabilities = require('cmp_nvim_lsp').default_capabilities(),
-- auto-save
on_attach = function(client, bufnr)
vim.api.nvim_create_autocmd('BufWritePre', {
buffer = bufnr,
command = 'EslintFixAll',
});
end,
settings = {
validate = 'on',
packageManager = 'npm',
useESLintClass = true,
useFlatConfig = true,
experimental = {
useFlatConfig = true,
},
codeAction = {
disableRuleComment = {
enable = true,
location = 'separateLine'
},
showDocumentation = {
enable = true,
},
},
codeActionOnSave = {
mode = 'all',
rules = {},
},
format = true,
quiet = false,
onIgnoredFiles = 'off',
rulesCustomizations = {},
run = 'onType',
problems = {
shortenToSingleLine = false,
},
nodePath = "",
workingDirectory = {
mode = 'location',
},
options = {
flags = {'unstable_ts_config'},
},
},
});
lsp.cssls.setup({
capabilities = require('cmp_nvim_lsp').default_capabilities(),
settings = {
css = {
validate = false,
},
less = {
validate = false,
},
scss = {
validate = false,
},
},
});
'';
plugins = [
pkgs.vimPlugins.typescript-tools-nvim
(buildPlugin "ts-error-translator" vimplugin-ts-error-translator-src)
{
plugin = pkgs.vimPlugins.package-info-nvim;
type = "lua";
config =
# lua
''
local packageInfo = require('package-info');
packageInfo.setup({
hide_up_to_date = true,
package_manager = 'npm',
});
vim.api.nvim_create_autocmd({ 'BufRead', 'BufNewFile' }, {
pattern = { 'package.json' },
callback = function()
packageInfo.show({ force = true });
end,
});
'';
}
];
};
};
}

View file

@ -1,50 +0,0 @@
local cmp = require('cmp');
local cmp_autopairs = require('nvim-autopairs.completion.cmp');
cmp.event:on(
'confirm_done',
cmp_autopairs.on_confirm_done()
);
cmp.setup({
sources = {
{ name = 'nvim_lsp' },
{ name = 'buffer' },
{ name = 'path' },
},
snippet = {
expand = function(args)
vim.fn['vsnip#anonymous'](args.body);
end,
},
mapping = {
-- Confirm selection
['<Right>'] = cmp.mapping.confirm({ select = true }),
-- Next selection
['<Down>'] = cmp.mapping(function(fallback)
if cmp.visible() then
cmp.select_next_item();
else
fallback();
end;
end, {
'i',
's',
}),
-- Previous selection
['<Up>'] = cmp.mapping(function(fallback)
if cmp.visible() then
cmp.select_prev_item();
else
fallback();
end;
end, {
'i',
's',
}),
},
});

View file

@ -1,443 +0,0 @@
-- Modified from https://github.com/lauranaujokat/nvim/blob/4102c789d05667f636107e3dae4ac589053ee88d/lua/setups/heirline.lua#L4
local conditions = require('heirline.conditions');
local utils = require('heirline.utils');
---@class Palette
---@field [string] any
local dracula = require('dracula').colors();
local colors = {
bright_bg = dracula.selection,
dark_bg = dracula.menu,
bright_fg = dracula.fg,
red = dracula.red,
dark_red = utils.get_highlight('DiffDelete').bg,
green = dracula.green,
blue = dracula.blue,
gray = utils.get_highlight('NonText').fg,
orange = utils.get_highlight('Constant').fg,
purple = utils.get_highlight('Statement').fg,
cyan = dracula.cyan,
diag_warn = utils.get_highlight('DiagnosticWarn').fg,
diag_error = utils.get_highlight('DiagnosticError').fg,
diag_hint = utils.get_highlight('DiagnosticHint').fg,
diag_info = utils.get_highlight('DiagnosticInfo').fg,
git_del = utils.get_highlight('GitSignsDelete').fg,
git_add = utils.get_highlight('GitSignsAdd').fg,
git_change = utils.get_highlight('GitSignsChange').fg,
};
require('heirline').load_colors(colors);
local ViMode = {
-- get vim current mode, this information will be required by the provider
-- and the highlight functions, so we compute it only once per component
-- evaluation and store it as a component attribute
init = function(self)
self.mode = vim.fn.mode(1);
-- execute this only once, this is required if you want the ViMode
-- component to be updated on operator pending mode
if not self.once then
vim.api.nvim_create_autocmd('ModeChanged', {
pattern = '*:*o',
command = 'redrawstatus',
});
self.once = true;
end;
end,
static = {
mode_names = {
n = 'N',
no = 'N?',
nov = 'N?',
noV = 'N?',
['no\22'] = 'N?',
niI = 'Ni',
niR = 'Nr',
niV = 'Nv',
nt = 'Nt',
v = 'V',
vs = 'Vs',
V = 'V_',
Vs = 'Vs',
['\22'] = '^V',
['\22s'] = '^V',
s = 'S',
S = 'S_',
['\19'] = '^S',
i = 'I',
ic = 'Ic',
ix = 'Ix',
R = 'R',
Rc = 'Rc',
Rx = 'Rx',
Rv = 'Rv',
Rvc = 'Rv',
Rvx = 'Rv',
c = 'C',
cv = 'Ex',
r = '...',
rm = 'M',
['r?'] = '?',
['!'] = '!',
t = 'T',
},
mode_colors = {
n = 'red',
i = 'green',
v = 'cyan',
V = 'cyan',
['\22'] = 'cyan',
c = 'orange',
s = 'purple',
S = 'purple',
['\19'] = 'purple',
R = 'orange',
r = 'orange',
['!'] = 'red',
t = 'red',
},
},
-- To be extra meticulous, we can also add some vim statusline syntax to
-- control the padding and make sure our string is always at least 2
-- characters long. Plus a nice Icon.
provider = function(self)
return '' .. self.mode_names[self.mode] .. '%)';
end,
-- Same goes for the highlight. Now the foreground will change according to the current mode.
hl = function(self)
local mode = self.mode:sub(1, 1); -- get only the first mode character
return { fg = self.mode_colors[mode], bold = true };
end,
-- Re-evaluate the component only on ModeChanged event
update = {
'ModeChanged',
},
};
local FileNameBlock = {
init = function(self)
self.filename = vim.api.nvim_buf_get_name(0);
end,
};
-- FileNameBlock children
local FileIcon = {
init = function(self)
local filename = self.filename;
local extension = vim.fn.fnamemodify(filename, ':e');
self.icon, self.icon_color =
require('nvim-web-devicons').get_icon_color(filename, extension, { default = true });
end,
provider = function(self)
return self.icon and (self.icon .. ' ');
end,
hl = function(self)
return { fg = self.icon_color };
end,
};
local FileName = {
provider = function(self)
-- first, trim the pattern relative to the current directory. For other
-- options, see :h filename-modifers
local filename = vim.fn.fnamemodify(self.filename, ':.');
if filename == '' then
return '[No Name]';
end;
-- now, if the filename would occupy more than 1/4th of the available
-- space, we trim the file path to its initials
-- See Flexible Components section below for dynamic truncation
if not conditions.width_percent_below(#filename, 0.25) then
filename = vim.fn.pathshorten(filename);
end;
return filename;
end,
hl = { fg = utils.get_highlight('Directory').fg },
};
local FileFlags = {
{
condition = function()
return vim.bo.modified;
end,
provider = '[+]',
hl = { fg = 'green' },
},
{
condition = function()
return not vim.bo.modifiable or vim.bo.readonly;
end,
provider = '',
hl = { fg = 'orange' },
},
};
local FileNameModifer = {
hl = function()
if vim.bo.modified then
-- use `force` because we need to override the child's hl foreground
return { fg = 'cyan', bold = true, force = true };
end;
end,
};
-- let's add the children to our FileNameBlock component
FileNameBlock = utils.insert(
FileNameBlock,
FileIcon,
utils.insert(FileNameModifer, FileName), -- a new table where FileName is a child of FileNameModifier
unpack(FileFlags), -- A small optimisation, since their parent does nothing
{ provider = '%<' } -- this means that the statusline is cut here when there's not enough space
);
local Ruler = {
provider = ' line: %l col: %c',
hl = { fg = 'green', bold = false },
};
local ScrollRuler = {
-- %l = current line number
-- %L = number of lines in the buffer
-- %c = column number
-- %P = percentage through file of displayed window
provider = '%P',
};
local ScrollBar = {
static = {
sbar = { '', '', '', '', '', '', '', '' },
-- sbar = { '🭶', '🭷', '🭸', '🭹', '🭺', '🭻' }
},
provider = function(self)
local curr_line = vim.api.nvim_win_get_cursor(0)[1];
local lines = vim.api.nvim_buf_line_count(0);
local i = math.floor((curr_line - 1) / lines * #self.sbar) + 1;
return string.rep(self.sbar[i], 2);
end,
hl = { fg = 'cyan', bg = 'bright_bg' },
};
local LSPActive = {
condition = conditions.lsp_attached,
update = { 'LspAttach', 'LspDetach' },
provider = function()
local names = {};
for _, server in pairs(vim.lsp.get_clients()) do
table.insert(names, server.name);
end;
return ' [' .. table.concat(names, ' ') .. '] ';
end,
hl = { fg = 'green', bold = false },
};
local spinner_frames = { '', '', '', '', '', '', '', '', '', '' };
-- From https://github.com/mhartington/dotfiles/blob/5961460e3a492f7815259a692fca5ca2a1df924a/config/nvim/lua/mh/statusline/lsp_status.lua#L4
local function get_lsp_progress()
local messages = require('lsp-status/messaging').messages;
local buf_messages = messages();
local msgs = {};
for _, msg in ipairs(buf_messages) do
local contents;
if msg.progress then
contents = msg.title;
if msg.spinner then
contents = spinner_frames[(msg.spinner % #spinner_frames) + 1] .. ' ' .. contents;
end;
elseif msg.status then
contents = msg.content;
if msg.uri then
local space = math.min(60, math.floor(0.6 * vim.fn.winwidth(0)));
local filename = vim.uri_to_fname(msg.uri);
filename = vim.fn.fnamemodify(filename, ':~:.');
if #filename > space then
filename = vim.fn.pathshorten(filename);
end;
contents = '(' .. filename .. ') ' .. contents;
end;
else
contents = msg.content;
end;
table.insert(msgs, contents);
end;
return table.concat(msgs, ' ');
end;
local LSPMessages = {
provider = function()
local progress = get_lsp_progress();
if progress == '' then
return '';
else
return ' ' .. progress;
end;
end,
hl = { fg = 'purple' },
};
local Diagnostics = {
condition = conditions.has_diagnostics,
static = {
error_icon = '',
warn_icon = '',
info_icon = '',
hint_icon = '',
},
init = function(self)
self.errors = #vim.diagnostic.get(0, { severity = vim.diagnostic.severity.ERROR });
self.warnings = #vim.diagnostic.get(0, { severity = vim.diagnostic.severity.WARN });
self.hints = #vim.diagnostic.get(0, { severity = vim.diagnostic.severity.HINT });
self.info = #vim.diagnostic.get(0, { severity = vim.diagnostic.severity.INFO });
end,
update = { 'DiagnosticChanged', 'BufEnter' },
{
provider = function(self)
-- 0 is just another output, we can decide to print it or not!
return self.errors > 0 and (self.error_icon .. self.errors);
end,
hl = { fg = 'diag_error' },
},
{
provider = function(self)
return self.warnings > 0 and (self.warn_icon .. self.warnings);
end,
hl = { fg = 'diag_warn' },
},
{
provider = function(self)
return self.info > 0 and (self.info_icon .. self.info);
end,
hl = { fg = 'diag_info' },
},
{
provider = function(self)
return self.hints > 0 and (self.hint_icon .. self.hints);
end,
hl = { fg = 'diag_hint' },
},
};
local Git = {
condition = conditions.is_git_repo,
init = function(self)
self.status_dict = vim.b.gitsigns_status_dict;
self.has_changes = self.status_dict.added ~= 0 or
self.status_dict.removed ~= 0 or
self.status_dict.changed ~= 0;
end,
hl = { fg = 'orange' },
{ -- git branch name
provider = function(self)
return '' .. self.status_dict.head;
end,
hl = { bold = true },
},
-- You could handle delimiters, icons and counts similar to Diagnostics
{
condition = function(self)
return self.has_changes;
end,
provider = '(',
},
{
provider = function(self)
local count = self.status_dict.added or 0;
return count > 0 and ('+' .. count);
end,
hl = { fg = 'git_add' },
},
{
provider = function(self)
local count = self.status_dict.removed or 0;
return count > 0 and ('-' .. count);
end,
hl = { fg = 'git_del' },
},
{
provider = function(self)
local count = self.status_dict.changed or 0;
return count > 0 and ('~' .. count);
end,
hl = { fg = 'git_change' },
},
{
condition = function(self)
return self.has_changes;
end,
provider = ')',
},
};
local Align = { provider = '%=' };
local Space = { provider = ' ' };
Left = utils.surround({ '', '' }, 'bright_bg', { ViMode, Diagnostics, LSPMessages });
Middle = utils.surround({ '', '' }, 'bright_bg', { LSPActive, FileNameBlock, Ruler });
Right = utils.surround({ '', '' }, 'bright_bg', { Git, Space, ScrollRuler, Space, ScrollBar });
local DefaultStatusline = {
hl = { bg = 'dark_bg' },
condition = function()
return true;
end,
Left,
Align,
Middle,
Align,
Right,
};
local StatusLines = {
hl = function()
if conditions.is_active() then
return 'StatusLine';
else
return 'StatusLineNC';
end;
end,
-- the first statusline with no condition, or which condition returns true is used.
-- think of it as a switch case with breaks to stop fallthrough.
fallthrough = false,
DefaultStatusline,
};
-- Make it global
vim.opt.laststatus = 3;
require('heirline').setup({
statusline = StatusLines,
});

View file

@ -1,63 +0,0 @@
-- Override netrw
vim.g.loaded_netrw = 0;
vim.g.loaded_netrwPlugin = 0;
require('neo-tree').setup({
close_if_last_window = true,
enable_refresh_on_write = true,
window = {
width = 22,
},
filesystem = {
use_libuv_file_watcher = true,
group_empty_dirs = true,
filtered_items = {
visible = false,
hide_dotfiles = false,
hide_gitignored = false,
hide_by_name = {},
hide_by_pattern = {},
always_show = {},
never_show = {},
never_show_by_pattern = {},
},
},
source_selector = {
winbar = true,
statusline = false,
},
follow_current_file = {
enabled = true,
leave_dirs_open = true,
},
});
local function is_neotree_open()
local manager = require('neo-tree.sources.manager');
local renderer = require('neo-tree.ui.renderer');
local state = manager.get_state('filesystem');
return renderer.window_exists(state);
end;
-- Auto open Neo-Tree on big enough window
vim.api.nvim_create_autocmd({ 'VimEnter', 'VimResized' }, {
pattern = '*',
callback = function()
if vim.api.nvim_eval([[&columns]]) > 100 then
if is_neotree_open() == false then
vim.cmd[[Neotree show]];
vim.cmd[[Neotree close]];
vim.cmd[[Neotree show]];
end;
else
if is_neotree_open() then
vim.cmd[[Neotree close]];
end;
end;
end,
});

View file

@ -1,179 +0,0 @@
{
config,
pkgs,
lib,
nvim-theme-src,
...
}: let
inherit (lib) fileContents optionals;
inherit (config.vars) neovimIde;
in {
programs = {
neovim = {
extraPackages = builtins.attrValues {
inherit (pkgs) bat;
};
plugins =
[
{
plugin = pkgs.vimPlugins.dracula-nvim.overrideAttrs {
src = nvim-theme-src;
};
type = "lua";
config =
# lua
''
-- set dot icon in place of trailing whitespaces
vim.opt.listchars = {
tab = ' ',
trail = '',
extends = '',
precedes = '',
nbsp = '',
};
vim.opt.list = true;
-- Add visual indicator for trailing whitespaces
vim.opt.fillchars = { eob = " " };
vim.fn.matchadd('errorMsg', [[\s\+$]]);
vim.cmd.colorscheme('dracula');
'';
}
{
plugin = pkgs.vimPlugins.indent-blankline-nvim;
type = "lua";
config =
# lua
''
--
local highlight = {
"RainbowRed",
"RainbowYellow",
"RainbowBlue",
"RainbowOrange",
"RainbowGreen",
"RainbowViolet",
"RainbowCyan",
};
local hooks = require('ibl.hooks');
hooks.register(hooks.type.HIGHLIGHT_SETUP, function()
vim.api.nvim_set_hl(0, "RainbowRed", { fg = "#E06C75" });
vim.api.nvim_set_hl(0, "RainbowYellow", { fg = "#E5C07B" });
vim.api.nvim_set_hl(0, "RainbowBlue", { fg = "#61AFEF" });
vim.api.nvim_set_hl(0, "RainbowOrange", { fg = "#D19A66" });
vim.api.nvim_set_hl(0, "RainbowGreen", { fg = "#98C379" });
vim.api.nvim_set_hl(0, "RainbowViolet", { fg = "#C678DD" });
vim.api.nvim_set_hl(0, "RainbowCyan", { fg = "#56B6C2" });
end);
require('ibl').setup({
indent = {
highlight = highlight,
char = "",
},
});
'';
}
{
plugin = pkgs.vimPlugins.nvim-highlight-colors;
type = "lua";
config =
# lua
''
-- Ensure termguicolors is enabled if not already
vim.opt.termguicolors = true;
require('nvim-highlight-colors').setup({});
'';
}
# Deps of heirline config
pkgs.vimPlugins.nvim-web-devicons
{
plugin = pkgs.vimPlugins.heirline-nvim;
type = "lua";
config = fileContents ./plugins/heirline.lua;
}
]
++ optionals neovimIde [
{
plugin = pkgs.vimPlugins.neo-tree-nvim;
type = "lua";
config = fileContents ./plugins/neotree.lua;
}
{
plugin = pkgs.vimPlugins.codewindow-nvim;
type = "lua";
config =
# lua
''
--
local codewindow = require('codewindow');
codewindow.setup({
auto_enable = false,
minimap_width = 8,
relative = 'editor',
window_border = 'none',
exclude_filetypes = { 'help' },
});
vim.api.nvim_create_autocmd({ 'VimEnter', 'VimResized' }, {
pattern = '*',
callback = function()
if vim.api.nvim_win_get_width(0) < 88 then
codewindow.close_minimap();
else
codewindow.open_minimap();
end
end,
});
'';
}
{
plugin = pkgs.vimPlugins.transparent-nvim;
type = "lua";
config =
# lua
''
require('transparent').setup({
groups = {
'Normal',
'NormalNC',
'Comment',
'Constant',
'Special',
'Identifier',
'Statement',
'PreProc',
'Type',
'Underlined',
'Todo',
'String',
'Function',
'Conditional',
'Repeat',
'Operator',
'Structure',
'LineNr',
'NonText',
'SignColumn',
'CursorLine',
'CursorLineNr',
'StatusLine',
'StatusLineNC',
'EndOfBuffer',
},
extra_groups = {},
exclude_groups = {},
});
'';
}
];
};
};
}

View file

@ -1,34 +0,0 @@
{pkgs, ...}: {
programs.neovim.plugins = [
{
plugin = pkgs.vimPlugins.nvim-treesitter-context;
type = "lua";
config =
# lua
''
require('treesitter-context').setup({
enable = true,
max_lines = 3,
min_window_height = 20,
});
vim.cmd.hi('TreesitterContextBottom', 'gui=underline guisp=Grey');
'';
}
pkgs.vimPlugins.nvim-treesitter-textobjects
{
plugin = pkgs.vimPlugins.nvim-treesitter.withAllGrammars;
type = "lua";
config =
# lua
''
require('nvim-treesitter.configs').setup({
highlight = { enable = true },
indent = { enable = true },
});
'';
}
];
}

View file

@ -1,11 +0,0 @@
{nix-index-db, ...}: {
imports = [nix-index-db.hmModules.nix-index];
programs = {
nix-index-database.comma.enable = true;
nix-index = {
enable = true;
enableBashIntegration = true;
};
};
}

View file

@ -1,31 +0,0 @@
{pkgs, ...}: {
programs = {
# Make sure we have color support
bash.shellAliases.tmux = "tmux -2";
tmux = {
enable = true;
mouse = true;
keyMode = "vi";
terminal = "tmux-256color";
newSession = true;
historyLimit = 30000;
plugins = builtins.attrValues {
inherit (pkgs.tmuxPlugins) dracula;
};
extraConfig =
# bash
''
bind-key -n Home send Escape "OH"
bind-key -n End send Escape "OF"
bind -T root WheelUpPane if-shell -F -t = "#{alternate_on}" "send-keys -M" "select-pane -t =; copy-mode -e; send-keys -M"
bind -T root WheelDownPane if-shell -F -t = "#{alternate_on}" "send-keys -M" "select-pane -t =; send-keys -M"
set -ga terminal-overrides ',xterm*:smcup@:rmcup@'
set -ga terminal-overrides ",*256col*:Tc"
'';
};
};
}

View file

@ -1,9 +0,0 @@
{
pkgs,
self,
...
}: {
home.packages = [self.packages.${pkgs.system}.trash-d];
programs.bash.shellAliases.rm = "trash";
}

View file

@ -1,46 +0,0 @@
{
config,
lib,
pkgs,
...
}: let
inherit (lib) foldl isList mergeAttrsWithFunc optionals unique;
mergeAttrsList = list:
foldl (mergeAttrsWithFunc (a: b:
if isList a && isList b
then unique (a ++ b)
else b)) {}
list;
in {
environment.systemPackages = [
(pkgs.writeShellApplication {
name = "rebuild-no-cache";
runtimeInputs = [config.programs.nh.package];
text = ''
nh os switch -- --option binary-caches "https://cache.nixos.org" "$@"
'';
})
];
nix = {
settings = let
mkSubstituterConf = url: key: {
substituters = [url];
trusted-public-keys = optionals (key != null) [key];
};
in
mergeAttrsList ([
(mkSubstituterConf "https://cache.nixos.org" "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=")
(mkSubstituterConf "https://hyprland.cachix.org" "hyprland.cachix.org-1:a7pgxzMz7+chwVL3/pzj6jIBMioiJM7ypFP8PwtkuGc=")
(mkSubstituterConf "https://nix-gaming.cachix.org" "nix-gaming.cachix.org-1:nbjlureqMbRAxR1gJ/f3hxemL9svXaZF/Ees8vCUUs4=")
(mkSubstituterConf "https://nixpkgs-wayland.cachix.org" "nixpkgs-wayland.cachix.org-1:3lwxaILxMRkVhehr5StQprHdEo4IrE8sRho9R9HOLYA=")
(mkSubstituterConf "https://nix-community.cachix.org" "nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs=")
(mkSubstituterConf "https://viperml.cachix.org" "viperml.cachix.org-1:qZhKBMTfmcLL+OG6fj/hzsMEedgKvZVFRRAhq7j8Vh8=")
(mkSubstituterConf "https://cuda-maintainers.cachix.org" "cuda-maintainers.cachix.org-1:0dq3bujKpuEPMCX6U4WylrUDZ9JyUG0VpVZa7CNfq5E=")
]
++ optionals (!config.services.nix-serve.enable) [
(mkSubstituterConf "https://cache.nelim.org" "cache.nelim.org:JmFqkUdH11EA9EZOFAGVHuRYp7EbsdJDHvTQzG2pPyY=")
]);
};
}

View file

@ -1,8 +0,0 @@
{...}: {
imports = [
./cachix.nix
./locale.nix
./locate.nix
./global.nix
];
}

View file

@ -1,23 +0,0 @@
{
config,
lib,
nixpkgs,
...
}: let
inherit (config.sops.secrets) access-token;
inherit (lib) hasAttr optionalString;
in {
# Minimize dowloads of indirect nixpkgs flakes
nix = {
registry.nixpkgs.flake = nixpkgs;
nixPath = ["nixpkgs=${nixpkgs}"];
extraOptions =
optionalString (hasAttr "sops" config)
"!include ${access-token.path}";
};
# Global hm settings
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
}

View file

@ -1,45 +0,0 @@
{pkgs, ...}: {
fonts = {
fontconfig = {
enable = true;
defaultFonts = {
emoji = ["Noto Color Emoji"];
monospace = ["JetBrainsMono Nerd Font"];
sansSerif = ["Noto Nerd Font"];
serif = ["Noto Nerd Font"];
};
};
packages =
[
(pkgs.nerdfonts.override {
fonts = [
"JetBrainsMono"
"Go-Mono"
"Iosevka"
"NerdFontsSymbolsOnly"
"SpaceMono"
"Ubuntu"
"Noto"
];
})
]
++ (builtins.attrValues {
inherit
(pkgs)
noto-fonts
noto-fonts-cjk
noto-fonts-emoji
liberation_ttf
font-awesome
meslo-lgs-nf
jetbrains-mono
ubuntu_font_family
;
});
};
# Select internationalisation properties.
i18n.defaultLocale = "en_CA.UTF-8";
console.useXkbConfig = true;
}

View file

@ -1,79 +0,0 @@
{
config,
pkgs,
lib,
...
}: let
inherit (config.vars) mainUser;
cfg = config.services.locate;
locateGroup = lib.getName cfg.package.name;
locate = "${cfg.package}/bin/locate";
updatedb = "${cfg.package}/bin/updatedb";
database = "/var/lib/locate/locatedb";
pruneFS = builtins.concatStringsSep " " cfg.pruneFS;
pruneNames = builtins.concatStringsSep " " cfg.pruneNames;
prunePaths = builtins.concatStringsSep " " cfg.prunePaths;
updatedbBin = ''
${updatedb} -o ${database} --prunefs "${pruneFS}" \
--prunepaths "${prunePaths}" --prunenames "${pruneNames}"
'';
in {
users.users.${mainUser}.extraGroups = [
locateGroup
];
# TODO: add timer
systemd.services.locate = {
wantedBy = ["default.target"];
serviceConfig = {
User = mainUser;
Group = locateGroup;
StateDirectory = "locate";
StateDirectoryMode = "0770";
ExecStart = updatedbBin;
};
};
home-manager.users.${mainUser}.programs.bash.shellAliases = {
locate = "${pkgs.writeShellScriptBin "lct" ''
exec ${locate} -d ${database} "$@" 2> >(grep -v "/var/cache/locatedb")
''}/bin/lct";
updatedb = updatedbBin;
};
services.locate = {
enable = true;
package = pkgs.mlocate;
localuser = null;
interval = "never";
prunePaths = [
"/var/lib/flatpak"
# Defaults
"/tmp"
"/var/tmp"
"/var/cache"
"/var/lock"
"/var/run"
"/var/spool"
"/nix/var/log/nix"
];
pruneNames = [
"node_modules"
# Defaults
".bzr"
".cache"
".git"
".hg"
".svn"
];
};
}

View file

@ -1,77 +0,0 @@
{
config,
lib,
...
}: {
imports = [
./vars
./modules/global.nix
./packages.nix
];
nix = {
# Edit nix.conf
extraOptions = ''
experimental-features = nix-command flakes
keep-outputs = true
keep-derivations = true
warn-dirty = false
'';
substituters = [
# Nix-community
"https://nix-community.cachix.org"
];
trustedPublicKeys = [
# Nix-community
"nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
];
};
# Global hm settings
home-manager.config = {
imports = [
# Make the vars be the same on Nix and HM
{
options.vars = lib.mkOption {
type = lib.types.attrs;
readOnly = true;
default = config.vars;
};
}
./home
{
programs.bash.sessionVariables = {
FLAKE = config.environment.variables.FLAKE;
};
programs.bash.shellAliases = {
# Make ping work on nix-on-droid
# https://github.com/nix-community/nix-on-droid/issues/185#issuecomment-1659294700
ping = "/android/system/bin/linker64 /android/system/bin/ping";
# SSH
# Desktop
pc = "ssh -t matt@100.64.0.6 'tmux -2u new -At phone'";
# NAS
nos = "ssh -t matt@100.64.0.4 'tmux -2u new -At phone'";
# Experimenting server
servivi = "ssh -t matt@100.64.0.7 'tmux -2u new -At phone'";
# Home-assistant
homie = "ssh -t matt@100.64.0.10 'tmux -2u new -At phone'";
# Cluster nodes
thingone = "ssh -t matt@100.64.0.8 'tmux -2u new -At phone'";
thingtwo = "ssh -t matt@100.64.0.9 'tmux -2u new -At phone'";
};
}
];
home.stateVersion = "23.05";
};
}

View file

@ -1,113 +0,0 @@
inputs @ {
pkgs,
self,
...
}: {
nixpkgs.overlays =
(map (i: inputs.${i}.overlays.default) [
"discord-overlay"
"grim-hyprland"
"nixpkgs-wayland"
])
++ (builtins.attrValues {
inherit
(self.overlays)
xdg-desktop-portal-kde
;
});
environment.systemPackages =
(builtins.attrValues {
inherit
(self.packages.${pkgs.system})
pokemon-colorscripts
repl
;
inherit
(pkgs.nodePackages)
undollar
;
inherit (pkgs) alejandra;
# Archiving
inherit
(pkgs)
zip
unzip
p7zip
bzip2
gzip
gnutar
xz
;
# File management
inherit
(pkgs)
findutils
diffutils
utillinux
which
imagemagick
;
# Networking
inherit (pkgs.dig) dnsutils;
inherit
(pkgs)
openssh
rsync
wget
gnupg
;
# Misc CLI stuff
inherit
(pkgs)
killall
nix-output-monitor
progress
tree
gnugrep
gnused
;
# Expected Stuff
inherit
(pkgs)
hostname
man
perl
tzdata
;
})
++ [
# This could help as well: nix derivation show -r /run/current-system
(pkgs.writeShellApplication {
name = "listDerivs";
text = ''
nix-store --query --requisites /run/current-system | cut -d- -f2- | sort -u
'';
})
(pkgs.writeShellApplication {
name = "from";
runtimeInputs = builtins.attrValues {
inherit
(pkgs)
coreutils
which
;
};
text = ''
for var do
realpath "$(which "$var")"
done
'';
})
];
}

View file

@ -1,68 +0,0 @@
{
config,
lib,
...
}: let
inherit (lib) mkDefault mkOption types;
flakeDir = config.environment.variables.FLAKE;
cfg = config.vars;
in {
options.vars = {
mainUser = mkOption {
type = types.str;
description = ''
Username that was defined at the initial setup process
'';
};
hostName = mkOption {
type = types.str;
description = ''
Hostname that was defined at the initial setup process
'';
};
promptMainColor = mkOption {
type = types.enum (import ./prompt-schemes.nix {});
default = "purple";
};
promptColors = mkOption {
description = ''
Colors used in starship prompt
'';
default = import ./prompt-schemes.nix {color = cfg.promptMainColor;};
readOnly = true;
type = types.submodule {
options = let
inherit (types) str;
in {
textColor = mkOption {type = str;};
firstColor = mkOption {type = str;};
secondColor = mkOption {type = str;};
thirdColor = mkOption {type = str;};
fourthColor = mkOption {type = str;};
};
};
};
configDir = mkOption {
type = types.str;
default = "${flakeDir}/devices/${cfg.hostName}/config";
description = ''
The path to where most of the devices' configs are in the .nix folder
'';
};
neovimIde = mkOption {
type = types.bool;
default = true;
};
};
config = {
environment.variables.FLAKE = mkDefault "/home/${cfg.mainUser}/.nix";
};
}

View file

@ -1,81 +0,0 @@
{color ? null}: let
inherit (builtins) attrNames removeAttrs;
schemes = {
"purple" = {
textColor = "#090c0c";
firstColor = "#bd93f9";
secondColor = "#715895";
thirdColor = "#382c4a";
fourthColor = "#120e18";
};
"green" = {
textColor = "#090c0c";
firstColor = "#78ae66";
secondColor = "#567c49";
thirdColor = "#334a2c";
fourthColor = "#11180e";
};
"red" = {
textColor = "#090c0c";
firstColor = "#e04242";
secondColor = "#9c2e2e";
thirdColor = "#591a1a";
fourthColor = "#160606";
};
"blue" = {
textColor = "#090c0c";
firstColor = "#6684ee";
secondColor = "#475ca6";
thirdColor = "#28345f";
fourthColor = "#010617";
};
"orange" = {
textColor = "#090c0c";
firstColor = "#ff9c42";
secondColor = "#c66b00";
thirdColor = "#874500";
fourthColor = "#3a1c00";
};
"yellow" = {
textColor = "#090c0c";
firstColor = "#ffea42";
secondColor = "#d4c300";
thirdColor = "#8f8b00";
fourthColor = "#3e3c00";
};
"cyan" = {
textColor = "#090c0c";
firstColor = "#42eaff";
secondColor = "#00a2b8";
thirdColor = "#005768";
fourthColor = "#001f26";
};
"pink" = {
textColor = "#090c0c";
firstColor = "#ff42cb";
secondColor = "#b80073";
thirdColor = "#6b003f";
fourthColor = "#2d0017";
};
# Template
"color" = {
textColor = "#090c0c";
firstColor = "";
secondColor = "";
thirdColor = "";
fourthColor = "";
};
};
in
if ! isNull color
then schemes.${color}
else attrNames (removeAttrs schemes ["color"])

13
config/ags/bin/heart.sh Executable file
View file

@ -0,0 +1,13 @@
#!/usr/bin/env bash
FILE="$HOME/.config/.heart"
toggle() {
if grep -q 󰣐 "$FILE"; then
echo  > "$FILE"
else
echo 󰣐 >> "$FILE"
fi
}
[[ "$1" == "toggle" ]] && toggle

15
config/ags/bin/launch-app.sh Executable file
View file

@ -0,0 +1,15 @@
#!/usr/bin/env bash
APP="$1"
EXEC="$2"
if [[ "$APP" == "thunderbird" ]]; then
hyprctl dispatch togglespecialworkspace thunder
elif [[ "$APP" == "Spotify" ]]; then
hyprctl dispatch togglespecialworkspace spot
elif [[ $(hyprctl clients | grep "$APP") != "" ]]; then
hyprctl dispatch focuswindow "^($APP)$"
else
hyprctl dispatch workspace empty
hyprctl dispatch exec "$EXEC"
fi

30
config/ags/bin/osk-toggle.sh Executable file
View file

@ -0,0 +1,30 @@
#!/usr/bin/env bash
state () {
if [[ $(busctl get-property --user sm.puri.OSK0 /sm/puri/OSK0 sm.puri.OSK0 Visible) == "b true" ]]; then
echo "Running"
else
echo "Stopped"
fi
}
toggle () {
if [[ $(busctl get-property --user sm.puri.OSK0 /sm/puri/OSK0 sm.puri.OSK0 Visible) == "b true" ]]; then
echo "Running"
busctl call --user sm.puri.OSK0 /sm/puri/OSK0 sm.puri.OSK0 SetVisible b false
else
echo "Stopped"
busctl call --user sm.puri.OSK0 /sm/puri/OSK0 sm.puri.OSK0 SetVisible b true
fi
}
if [[ $1 == "getState" ]]; then
while true; do
sleep 0.2
state
done
fi
if [[ $1 == "toggle" ]];then
toggle
fi

33
config/ags/bin/qs-toggles.sh Executable file
View file

@ -0,0 +1,33 @@
#!/usr/bin/env bash
radio_status () {
radio_status=$(nmcli radio wifi)
if [[ $radio_status == "enabled" ]]; then
echo "on"
else
echo "off"
fi
}
if [[ $1 == "toggle-radio" ]]; then
stat=$(radio_status)
if [[ $stat == "on" ]]; then
nmcli radio wifi off
else
nmcli radio wifi on
fi
fi
FILE='/home/matt/.config/.bluetooth'
get_state() {
if [[ "$(rfkill list | grep -A 1 hci0 | grep -o no)" == "no" ]]; then
echo " 󰂯 " > "$FILE"
else
echo " 󰂲 " > "$FILE"
fi
}
if [[ "$1" == "blue-toggle" ]]; then
rfkill toggle bluetooth
get_state
fi

10
config/ags/bin/startup.sh Executable file
View file

@ -0,0 +1,10 @@
#!/usr/bin/env bash
## Make bluetooth status persistent between reboots
if [[ ! -f "$HOME/.config/.bluetooth" ]]; then
echo 󰂲 > "$FILE"
fi
if grep -q 󰂲 "$HOME/.config/.bluetooth"; then
rfkill block bluetooth
fi

38
config/ags/bin/tablet-toggle.sh Executable file
View file

@ -0,0 +1,38 @@
#!/usr/bin/env bash
tablet() {
gsettings set org.gnome.desktop.a11y.applications screen-keyboard-enabled true
brightnessctl -d tpacpi::kbd_backlight s 0
"$HYPR_PATH"/autorotate.sh &
evtest --grab "/dev/input/by-path/platform-i8042-serio-0-event-kbd" &
evtest --grab "/dev/input/by-path/platform-i8042-serio-1-event-mouse" &
evtest --grab "/dev/input/by-path/platform-AMDI0010:02-event-mouse" &
evtest --grab "/dev/input/by-path/platform-thinkpad_acpi-event" &
evtest --grab "/dev/video-bus" &
}
laptop() {
gsettings set org.gnome.desktop.a11y.applications screen-keyboard-enabled false
brightnessctl -d tpacpi::kbd_backlight s 2
killall -r autorotate.sh
killall -r evtest
}
toggle () {
if [[ "$(gsettings get org.gnome.desktop.a11y.applications screen-keyboard-enabled)" == "false" ]]; then
echo "Tablet"
tablet > /dev/null
else
echo "Laptop"
laptop > /dev/null
fi
}
[[ $1 == "toggle" ]] && toggle
[[ $1 == "laptop" ]] && laptop
[[ $1 == "tablet" ]] && tablet

44
config/ags/config.js Normal file
View file

@ -0,0 +1,44 @@
import { exec, execAsync } from 'resource:///com/github/Aylur/ags/utils.js';
import { Powermenu } from './js/powermenu.js';
import { Bar } from './js/bar/main.js';
import { NotificationCenter } from './js/notifications/center.js';
import { NotificationsPopupList } from './js/notifications/popup.js'
import { Calendar } from './js/date.js';
import { QuickSettings } from './js/quick-settings/main.js';
import Overview from './js/overview/main.js';
import AppLauncher from './js/applauncher/main.js';
import { Closer, closeAll } from './js/misc/closer.js';
ags.App.closeAll = () => closeAll();
const scss = ags.App.configDir + '/scss/main.scss';
const css = ags.App.configDir + '/style.css';
exec(`sassc ${scss} ${css}`);
execAsync(['bash', '-c', '$AGS_PATH/startup.sh']).catch(print);
export default {
style: css,
notificationPopupTimeout: 5000,
cacheNotificationActions: true,
closeWindowDelay: {
'quick-settings': 500,
'notification-center': 500,
'calendar': 500,
'powermenu': 500,
'overview': 500,
'applauncher': 500,
},
windows: [
Powermenu,
Bar,
Closer,
NotificationCenter,
NotificationsPopupList,
Calendar,
QuickSettings,
Overview,
AppLauncher,
],
};

View file

@ -0,0 +1,134 @@
const { App } = ags;
const { Applications } = ags.Service;
const { Label, Box, Icon, Button, Scrollable, Entry, Window, EventBox } = ags.Widget;
import { Separator } from '../misc/separator.js';
import { PopUp } from '../misc/popup.js';
const icons = {
apps: {
apps: 'view-app-grid-symbolic',
search: 'preferences-system-search-symbolic',
}
};
const AppItem = (app, window) => {
if (app.app.get_string('Icon') == 'Nextcloud')
return;
return Button({
className: 'app',
connections: [['clicked', () => {
App.closeWindow(window);
app.launch();
}]],
child: Box({
children: [
Icon({
icon: app.app.get_string('Icon'),
size: 42,
}),
Box({
vertical: true,
children: [
Label({
className: 'title',
label: app.name,
xalign: 0,
valign: 'center',
ellipsize: 3,
}),
Label({
className: 'description',
label: app.description || '',
wrap: true,
xalign: 0,
justification: 'left',
valign: 'center',
}),
],
}),
],
}),
});
};
const Applauncher = ({ windowName = 'applauncher' } = {}) => {
const list = Box({ vertical: true });
const placeholder = Label({
label: " Couldn't find a match",
className: 'placeholder',
});
const entry = Entry({
hexpand: true,
placeholderText: 'Search',
onAccept: ({ text }) => {
const list = Applications.query(text);
if (list[0]) {
App.toggleWindow(windowName);
list[0].launch();
}
},
onChange: ({ text }) => {
list.children = Applications.query(text).map(app => [
Separator(4),
AppItem(app, windowName),
]).flat();
list.add(Separator(4));
list.show_all();
placeholder.visible = list.children.length === 1;
},
});
return Box({
className: 'applauncher',
properties: [['list', list]],
vertical: true,
children: [
Box({
className: 'header',
children: [
Icon(icons.apps.search),
entry,
],
}),
Scrollable({
hscroll: 'never',
child: Box({
vertical: true,
children: [list, placeholder],
}),
}),
],
connections: [[App, (_b, name, visible) => {
if (name !== windowName)
return;
entry.set_text('-'); // force onChange
entry.set_text('');
if (visible)
entry.grab_focus();
}]],
});
};
// FIXME: make it so I don't have to click to trigger onHoverLost
export default Window({
name: 'applauncher',
popup: true,
focusable: true,
layer: 'overlay',
child: EventBox({
onHover: box => {
box.get_parent().focusable = true
},
onHoverLost: box => {
box.get_parent().focusable = false
},
child: PopUp({
name: 'applauncher',
child: Applauncher(),
}),
}),
});

View file

@ -0,0 +1,55 @@
const { Audio } = ags.Service;
const { Label, Box, Icon, Stack, Button, Slider } = ags.Widget;
import { Separator } from '../misc/separator.js';
import { EventBox } from '../misc/cursorbox.js';
const items = {
101: 'audio-volume-overamplified-symbolic',
67: 'audio-volume-high-symbolic',
34: 'audio-volume-medium-symbolic',
1: 'audio-volume-low-symbolic',
0: 'audio-volume-muted-symbolic',
};
const SpeakerIndicator = params => Icon({
...params,
icon: '',
connections: [[Audio, icon => {
if (Audio.speaker) {
if (Audio.speaker.isMuted) {
icon.icon = items[0];
}
else {
const vol = Audio.speaker.volume * 100;
for (const threshold of [-1, 0, 33, 66, 100]) {
if (vol > threshold + 1) {
icon.icon = items[threshold + 1];
}
}
}
}
}, 'speaker-changed']],
});
const SpeakerPercentLabel = params => Label({
...params,
connections: [[Audio, label => {
if (Audio.speaker) {
label.label = Math.round(Audio.speaker.volume * 100) + '%';
}
}, 'speaker-changed']],
});
export const AudioIndicator = EventBox({
onPrimaryClickRelease: 'pavucontrol',
className: 'toggle-off',
child: Box({
className: 'audio',
children: [
SpeakerIndicator(),
Separator(5),
SpeakerPercentLabel(),
],
}),
});

View file

@ -0,0 +1,58 @@
const { Battery } = ags.Service;
const { Label, Icon, Stack, Box } = ags.Widget;
import { Separator } from '../misc/separator.js';
const icons = charging => ([
...Array.from({ length: 10 }, (_, i) => i * 10).map(i => ([
`${i}`, Icon({
className: `${i} ${charging ? 'charging' : 'discharging'}`,
icon: `battery-level-${i}${charging ? '-charging' : ''}-symbolic`,
}),
])),
['100', Icon({
className: `100 ${charging ? 'charging' : 'discharging'}`,
icon: `battery-level-100${charging ? '-charged' : ''}-symbolic`,
})],
]);
const Indicators = charging => Stack({
items: icons(charging),
connections: [[Battery, stack => {
stack.shown = `${Math.floor(Battery.percent / 10) * 10}`;
}]],
});
const Indicator = ({
charging = Indicators(true),
discharging = Indicators(false),
...params
} = {}) => Stack({
...params,
className: 'battery-indicator',
items: [
['true', charging],
['false', discharging],
],
connections: [[Battery, stack => {
stack.shown = `${Battery.charging || Battery.charged}`;
stack.toggleClassName('charging', Battery.charging);
stack.toggleClassName('charged', Battery.charged);
stack.toggleClassName('low', Battery.percent < 20);
}]],
});
const LevelLabel = params => Label({
...params,
className: 'label',
connections: [[Battery, label => label.label = `${Battery.percent}%`]],
});
export const BatteryIndicator = Box({
className: 'toggle-off battery',
children: [
Indicator(),
Separator(5),
LevelLabel(),
],
});

View file

@ -0,0 +1,35 @@
const { ProgressBar, Overlay, Box } = ags.Widget;
const { execAsync } = ags.Utils;
import { Separator } from '../misc/separator.js';
import { Heart } from './heart.js';
export const Brightness = Overlay({
setup: widget => {
widget.set_tooltip_text('Brightness');
},
child: ProgressBar({
className: 'toggle-off brightness',
connections: [
[200, progress => {
execAsync('brightnessctl get').then(out => {
let br = out / 255;
if (br > 0.33) {
progress.value = br;
}
else {
progress.value = 0.33;
}
}).catch(print);
}],
],
}),
overlays: [
Box({
style: 'color: #CBA6F7;',
children: [
Separator(25),
Heart,
],
}),
],
});

View file

@ -0,0 +1,34 @@
const { Box, Label } = ags.Widget;
const { toggleWindow } = ags.App;
const { DateTime } = imports.gi.GLib;
import { EventBox } from '../misc/cursorbox.js';
const ClockModule = ({
interval = 1000,
...params
}) => Label({
...params,
className: 'clock',
connections: [
[interval, label => {
var time = DateTime.new_now_local();
label.label = time.format('%a. ') + time.get_day_of_month() + time.format(' %b. %H:%M');
}],
],
});
export const Clock = EventBox({
className: 'toggle-off',
onPrimaryClickRelease: () => toggleWindow('calendar'),
connections: [
[ags.App, (box, windowName, visible) => {
if (windowName == 'calendar') {
box.toggleClassName('toggle-on', visible);
}
}],
],
child: Box({
child: ClockModule({}),
}),
});

View file

@ -0,0 +1,13 @@
const { Hyprland } = ags.Service;
const { Label } = ags.Widget;
const { Gtk } = imports.gi;
export const CurrentWindow = Label({
style: 'color: #CBA6F7; font-size: 18px',
truncate: 'end',
connections: [
[Hyprland, label => {
label.label = Hyprland.active.client.title;
}, 'changed'],
],
});

View file

@ -0,0 +1,32 @@
const { Window, CenterBox, EventBox, Button } = ags.Widget;
const { openWindow } = ags.App;
const { Gtk, Gdk } = imports.gi;
const display = Gdk.Display.get_default();
export const Gesture = ({
child,
...params
}) => {
let w = EventBox({
...params,
});
let gesture = Gtk.GestureSwipe.new(w);
w.child = CenterBox({
children: [
child,
],
connections: [
[gesture, box => {
const velocity = gesture.get_velocity()[1];
if (velocity < -100)
openWindow('quick-settings');
}, 'update'],
],
});
return w;
};

View file

@ -0,0 +1,26 @@
const { Box, Label } = ags.Widget;
const { subprocess, execAsync } = ags.Utils;
const deflisten = subprocess;
import { EventBox } from '../misc/cursorbox.js';
deflisten(
['bash', '-c', 'tail -f /home/matt/.config/.heart'],
(output) => {
Heart.child.children[0].label = ' ' + output;
},
);
export const Heart = EventBox({
halign: 'center',
onPrimaryClickRelease: () => {
execAsync(['bash', '-c', '$AGS_PATH/heart.sh toggle']).catch(print);
},
child: Box({
className: 'heart-toggle',
vertical: false,
child: Label({
label: '',
}),
}),
});

79
config/ags/js/bar/main.js Normal file
View file

@ -0,0 +1,79 @@
const { Window, CenterBox, Box } = ags.Widget;
import { Separator } from '../misc/separator.js';
import { CurrentWindow } from './current-window.js';
import { Workspaces } from './workspaces.js';
import { OskToggle } from './osk-toggle.js';
import { TabletToggle } from './tablet-toggle.js';
import { QsToggle } from './quick-settings.js';
import { NotifButton } from './notif-button.js';
import { Clock } from './clock.js';
import { SysTray } from './systray.js';
import { BatteryIndicator } from './battery.js';
import { Brightness } from './brightness.js';
import { AudioIndicator } from './audio.js';
import { Gesture } from './gesture.js';
export const Bar = Window({
name: 'bar',
layer: 'overlay',
anchor: 'top left right',
exclusive: true,
child: Gesture({
child: CenterBox({
className: 'transparent',
halign: 'fill',
style: 'margin: 5px',
vertical: false,
startWidget: Box({
halign: 'start',
children: [
OskToggle,
Separator(12),
TabletToggle,
Separator(12),
SysTray,
AudioIndicator,
Separator(12),
Brightness,
Separator(12),
Workspaces,
],
}),
centerWidget: CurrentWindow,
endWidget: Box({
halign: 'end',
children: [
BatteryIndicator,
Separator(12),
Clock,
Separator(12),
NotifButton,
Separator(12),
QsToggle,
],
}),
}),
}),
});

View file

@ -0,0 +1,54 @@
const { Box, Label, Icon } = ags.Widget;
const { toggleWindow } = ags.App;
const { Notifications } = ags.Service;
import { Separator } from '../misc/separator.js';
import { EventBox } from '../misc/cursorbox.js';
export const NotifButton = EventBox({
className: 'toggle-off',
onPrimaryClickRelease: () => toggleWindow('notification-center'),
connections: [
[ags.App, (box, windowName, visible) => {
if (windowName == 'notification-center') {
box.toggleClassName('toggle-on', visible);
}
}],
],
child: Box({
className: 'notif-panel',
vertical: false,
children: [
Separator(28),
Icon({
connections: [
[Notifications, icon => {
if (Notifications.dnd) {
icon.icon = 'notification-disabled-symbolic'
}
else {
if (Notifications.notifications.length > 0) {
icon.icon = 'notification-new-symbolic'
}
else {
icon.icon = 'notification-symbolic'
}
}
}],
],
}),
Separator(8),
Label({
connections: [
[Notifications, label => {
label.label = String(Notifications.notifications.length);
}],
],
}),
],
}),
});

View file

@ -0,0 +1,41 @@
const { Box, Label } = ags.Widget;
const { subprocess } = ags.Utils;
const deflisten = subprocess;
import { EventBox } from '../misc/cursorbox.js';
deflisten(
['bash', '-c', '$AGS_PATH/osk-toggle.sh getState'],
(output) => {
if (output == 'Running') {
OskToggle.toggleClassName('toggle-on', true);
} else {
OskToggle.toggleClassName('toggle-on', false);
}
},
);
export const OskToggle = EventBox({
className: 'toggle-off',
onPrimaryClickRelease: function() {
subprocess(
['bash', '-c', '$AGS_PATH/osk-toggle.sh toggle'],
(output) => {
if (output == 'Running') {
OskToggle.toggleClassName('toggle-on', false);
} else {
OskToggle.toggleClassName('toggle-on', true);
}
},
);
},
child: Box({
className: 'osk-toggle',
vertical: false,
children: [
Label({
label: " 󰌌 ",
}),
],
}),
});

View file

@ -0,0 +1,23 @@
const { Box, Label } = ags.Widget;
const { toggleWindow } = ags.App;
import { EventBox } from '../misc/cursorbox.js';
export const QsToggle = EventBox({
className: 'toggle-off',
onPrimaryClickRelease: () => toggleWindow('quick-settings'),
connections: [
[ags.App, (box, windowName, visible) => {
if (windowName == 'quick-settings') {
box.toggleClassName('toggle-on', visible);
}
}],
],
child: Box({
className: 'quick-settings-toggle',
vertical: false,
child: Label({
label: "  ",
}),
}),
});

View file

@ -0,0 +1,57 @@
const { SystemTray } = ags.Service;
const { Box, Revealer, Icon, MenuItem } = ags.Widget;
const { Gtk } = imports.gi;
import { Separator } from '../misc/separator.js';
const SysTrayItem = item => MenuItem({
className: 'tray-item',
child: Icon({
size: 24,
}),
submenu: item.menu,
connections: [[item, btn => {
btn.child.icon = item.icon;
btn.tooltipMarkup = item.tooltipMarkup;
}]]
});
export const SysTray = Revealer({
transition: 'slide_right',
connections: [[SystemTray, rev => {
rev.revealChild = rev.child.children[0].get_children().length > 0;
}]],
child: Box({
children: [
ags.Widget({
type: Gtk.MenuBar,
className: 'sys-tray',
properties: [
['items', new Map()],
['onAdded', (box, id) => {
const item = SystemTray.getItem(id);
if (box._items.has(id) || !item)
return;
const widget = SysTrayItem(item);
box._items.set(id, widget);
box.add(widget);
box.show_all();
}],
['onRemoved', (box, id) => {
if (!box._items.has(id))
return;
box._items.get(id).destroy();
box._items.delete(id);
}],
],
connections: [
[SystemTray, (box, id) => box._onAdded(box, id), 'added'],
[SystemTray, (box, id) => box._onRemoved(box, id), 'removed'],
],
}),
Separator(12),
],
}),
});

View file

@ -0,0 +1,28 @@
const { Box, Label } = ags.Widget;
const { subprocess } = ags.Utils;
import { EventBox } from '../misc/cursorbox.js';
export const TabletToggle = EventBox({
className: 'toggle-off',
onPrimaryClickRelease: function() {
subprocess(
['bash', '-c', '$AGS_PATH/tablet-toggle.sh toggle'],
(output) => {
print(output)
if (output == 'Tablet') {
TabletToggle.toggleClassName('toggle-on', true);
} else {
TabletToggle.toggleClassName('toggle-on', false);
}
},
);
},
child: Box({
className: 'tablet-toggle',
vertical: false,
child: Label({
label: " 󰦧 ",
}),
}),
});

View file

@ -0,0 +1,70 @@
const { Hyprland, Applications } = ags.Service;
const { execAsync } = ags.Utils;
const { Box, Button, Label, Revealer } = ags.Widget;
import { EventBox } from '../misc/cursorbox.js';
const Workspace = ({ i } = {}) =>
Revealer({
transition: "slide_right",
properties: [
['id', i],
],
child: EventBox({
tooltipText: `${i}`,
onPrimaryClickRelease: () => execAsync(`hyprctl dispatch workspace ${i}`).catch(print),
child: Box({
className: 'button',
child: Label(`${i}`),
connections: [
[Hyprland, btn => {
const occupied = Hyprland.getWorkspace(i)?.windows > 0;
btn.toggleClassName('active', Hyprland.active.workspace.id === i);
btn.toggleClassName('occupied', occupied);
btn.toggleClassName('empty', !occupied);
}]
],
}),
}),
});
export const Workspaces = Box({
className: 'workspaces',
children: [EventBox({
child: Box({
properties: [
['workspaces'],
['refresh', box => {
box.children.forEach(rev => rev.reveal_child = false);
box._workspaces.forEach(ws => {
ws.revealChild = true;
});
}],
['updateWs', box => {
Hyprland.workspaces.forEach(ws => {
let currentWs = box.children.find(ch => ch._id == ws.id);
if (!currentWs && ws.id > 0) {
box.add(Workspace({ i: ws.id}));
}
});
box.show_all();
// Make sure the order is correct
box._workspaces.forEach((workspace, i) => {
workspace.get_parent().reorder_child(workspace, i);
});
}],
],
connections: [[Hyprland, box => {
box._workspaces = box.children.filter(ch => {
return Hyprland.workspaces.find(ws => ws.id == ch._id)
}).sort((a, b) => a._id - b._id);
box._updateWs(box);
box._refresh(box);
}]],
}),
})],
});

88
config/ags/js/date.js Normal file
View file

@ -0,0 +1,88 @@
const { Box, Label, Window } = ags.Widget;
const { Gtk } = imports.gi;
const { DateTime } = imports.gi.GLib;
import { PopUp } from './misc/popup.js';
const Divider = () => Box({
className: 'divider',
vertical: true,
});
const Time = () => Box({
className: 'timebox',
vertical: true,
children: [
Box({
className: 'time-container',
halign: 'center',
valign: 'center',
children: [
Label({
className: 'content',
label: 'hour',
connections: [[1000, label => {
label.label = DateTime.new_now_local().format('%H');
}]],
}),
Divider(),
Label({
className: 'content',
label: 'minute',
connections: [[1000, label => {
label.label = DateTime.new_now_local().format('%M');
}]],
}),
],
}),
Box({
className: 'date-container',
halign: 'center',
child: Label({
style: 'font-size: 20px',
label: 'complete date',
connections: [[1000, label => {
var time = DateTime.new_now_local();
label.label = time.format("%A, %B ") + time.get_day_of_month() + time.format(", %Y");
}]],
}),
}),
],
});
const CalendarWidget = () => Box({
className: 'cal-box',
child: ags.Widget({
type: Gtk.Calendar,
showDayNames: true,
showHeading: true,
className: 'cal',
connections: [/* */]
}),
});
export const Calendar = Window({
name: 'calendar',
layer: 'overlay',
popup: true,
anchor: 'top right',
margin: [ 8, 182, 0, 0],
child: PopUp({
name: 'calendar',
child: Box({
className: 'date',
vertical: true,
children: [
Time(),
CalendarWidget(),
],
}),
}),
});

View file

@ -0,0 +1,82 @@
const { Box, Overlay, EventBox } = ags.Widget;
const { Gtk } = imports.gi;
const MAX_OFFSET = 200;
const OFFSCREEN = 500;
const TRANSITION = 'transition: margin 0.5s ease, opacity 3s ease;';
export default ({ properties, connections, params } = {}) => {
let widget = EventBox();
let gesture = Gtk.GestureDrag.new(widget)
widget.child = Overlay({
...params,
properties: [
...properties,
['dragging', false],
],
child: Box({className: 'player'}),
connections: [
...connections,
[gesture, overlay => {
if (overlay.overlays.length <= 1)
return;
overlay._dragging = true;
const offset = gesture.get_offset()[1];
let playerBox = overlay.get_children().at(-1);
if (offset >= 0) {
playerBox.setStyle(`margin-left: ${offset}px;
margin-right: -${offset}px;
${playerBox._bgStyle}`);
}
else {
let newOffset = Math.abs(offset);
playerBox.setStyle(`margin-left: -${newOffset}px;
margin-right: ${newOffset}px;
${playerBox._bgStyle}`);
}
overlay._selected = playerBox;
}, 'drag-update'],
[gesture, overlay => {
if (overlay.overlays.length <= 1)
return;
overlay._dragging = false;
const offset = gesture.get_offset()[1];
let playerBox = overlay.get_children().at(-1);
if (Math.abs(offset) > MAX_OFFSET) {
if (offset >= 0) {
playerBox.setStyle(`${TRANSITION}
margin-left: ${OFFSCREEN}px;
margin-right: -${OFFSCREEN}px;
opacity: 0;
${playerBox._bgStyle}`);
}
else {
playerBox.setStyle(`${TRANSITION}
margin-left: -${OFFSCREEN}px;
margin-right: ${OFFSCREEN}px;
opacity: 0;
${playerBox._bgStyle}`);
}
setTimeout(() => {
overlay.reorder_overlay(playerBox, 0);
playerBox.setStyle(playerBox._bgStyle);
overlay._selected = overlay.get_children().at(-1);
}, 500);
}
else
playerBox.setStyle(`${TRANSITION} ${playerBox._bgStyle}`);
}, 'drag-end'],
],
});
return widget;
};

View file

@ -0,0 +1,345 @@
const { execAsync, lookUpIcon } = ags.Utils;
const { Mpris } = ags.Service;
const { Button, Icon, Label, Stack, Slider, CenterBox, Box } = ags.Widget;
const { Gdk } = imports.gi;
const display = Gdk.Display.get_default();
import { EventBox } from '../misc/cursorbox.js';
import { Separator } from '../misc/separator.js';
const icons = {
mpris: {
fallback: 'audio-x-generic-symbolic',
shuffle: {
enabled: '󰒝',
disabled: '󰒞',
},
loop: {
none: '󰑗',
track: '󰑘',
playlist: '󰑖',
},
playing: ' ',
paused: ' ',
stopped: ' ',
prev: '󰒮',
next: '󰒭',
},
}
export const CoverArt = (player, params) => CenterBox({
...params,
vertical: true,
properties: [['bgStyle', '']],
connections: [
[player, box => {
execAsync(['bash', '-c', `[[ -f "${player.coverPath}" ]] && coloryou "${player.coverPath}"`])
.then(out => {
if (!Mpris.players.find(p => player === p))
return;
player.colors.value = JSON.parse(out);
box._bgStyle = `background: radial-gradient(circle,
rgba(0, 0, 0, 0.4) 30%,
${player.colors.value.imageAccent}),
url("${player.coverPath}");
background-size: cover;
background-position: center;`;
if (!box.get_parent()._dragging)
box.setStyle(box._bgStyle);
}).catch(err => { if (err !== "") print(err) });
}],
],
});
export const TitleLabel = (player, params) => Label({
...params,
xalign: 0,
maxWidthChars: 40,
truncate: 'end',
justification: 'left',
className: 'title',
binds: [['label', player, 'trackTitle']],
});
export const ArtistLabel = (player, params) => Label({
...params,
xalign: 0,
maxWidthChars: 40,
truncate: 'end',
justification: 'left',
className: 'artist',
connections: [[player, label => {
label.label = player.trackArtists.join(', ') || '';
}]],
});
export const PlayerIcon = (player, { symbolic = true, ...params } = {}) => {
let MainIcon = Icon({
...params,
className: 'player-icon',
size: 32,
tooltipText: player.identity || '',
connections: [
[player, icon => {
const name = `${player.entry}${symbolic ? '-symbolic' : ''}`;
lookUpIcon(name) ? icon.icon = name
: icon.icon = icons.mpris.fallback;
}],
],
});
return Box({
connections: [
[Mpris, box => {
let overlays = box.get_parent().get_parent().get_parent().overlays;
let player = overlays.find(overlay => overlay === box.get_parent().get_parent());
let index = overlays.indexOf(player)
let children = [];
for (let i = 0; i < overlays.length; ++i) {
if (i === index) {
children.push(MainIcon);
children.push(Separator(2));
}
else {
children.push(Box({ className: 'position-indicator' }));
children.push(Separator(2));
}
}
box.children = children;
}],
],
});
}
export const PositionSlider = (player, params) => EventBox({
child: Slider({
...params,
className: 'position-slider',
hexpand: true,
drawValue: false,
onChange: ({ value }) => {
player.position = player.length * value;
},
properties: [
['update', slider => {
if (slider.dragging) {
slider.get_parent().window.set_cursor(Gdk.Cursor.new_from_name(display, 'grabbing'));
}
else {
if (slider.get_parent() && slider.get_parent().window) {
slider.get_parent().window.set_cursor(Gdk.Cursor.new_from_name(display, 'pointer'));
}
slider.sensitive = player.length > 0;
if (player.length > 0) {
slider.value = player.position / player.length;
}
}
}],
],
connections: [
[player, s => s._update(s), 'position'],
[1000, s => s._update(s)],
[player.colors, s => {
if (player.colors.value)
s.setCss(`highlight { background-color: ${player.colors.value.buttonAccent}; }
slider { background-color: ${player.colors.value.buttonAccent}; }
slider:hover { background-color: ${player.colors.value.hoverAccent}; }
trough { background-color: ${player.colors.value.buttonText}; }`);
}],
],
}),
});
function lengthStr(length) {
const min = Math.floor(length / 60);
const sec0 = Math.floor(length % 60) < 10 ? '0' : '';
const sec = Math.floor(length % 60);
return `${min}:${sec0}${sec}`;
}
export const PositionLabel = player => Label({
properties: [['update', label => {
player.length > 0 ? label.label = lengthStr(player.position)
: label.visible = !!player;
}]],
connections: [
[player, l => l._update(l), 'position'],
[1000, l => l._update(l)],
],
});
export const LengthLabel = player => Label({
connections: [[player, label => {
player.length > 0 ? label.label = lengthStr(player.length)
: label.visible = !!player;
}]],
});
export const Slash = player => Label({
label: '/',
connections: [[player, label => {
label.visible = player.length > 0;
}]],
});
// TODO: use label instead of stack to fix UI issues
const PlayerButton = ({ player, items, onClick, prop }) => Button({
child: Stack({ items }),
onPrimaryClickRelease: () => player[onClick](),
properties: [['hovered', false]],
onHover: box => {
box._hovered = true;
if (! box.child.sensitive || ! box.sensitive) {
box.window.set_cursor(Gdk.Cursor.new_from_name(display, 'not-allowed'));
}
else {
box.window.set_cursor(Gdk.Cursor.new_from_name(display, 'pointer'));
}
if (prop == 'playBackStatus') {
items.forEach(item => {
item[1].setStyle(`background-color: ${player.colors.value.hoverAccent};
color: ${player.colors.value.buttonText};
min-height: 40px; min-width: 36px;
margin-bottom: 1px; margin-right: 1px;`);
});
}
},
onHoverLost: box => {
box._hovered = false;
box.window.set_cursor(null);
if (prop == 'playBackStatus') {
items.forEach(item => {
item[1].setStyle(`background-color: ${player.colors.value.buttonAccent};
color: ${player.colors.value.buttonText};
min-height: 42px; min-width: 38px;`);
});
}
},
connections: [
[player, button => {
button.child.shown = `${player[prop]}`;
}],
[player.colors, button => {
if (!Mpris.players.find(p => player === p))
return;
if (player.colors.value) {
if (prop == 'playBackStatus') {
if (button._hovered) {
items.forEach(item => {
item[1].setStyle(`background-color: ${player.colors.value.hoverAccent};
color: ${player.colors.value.buttonText};
min-height: 40px; min-width: 36px;
margin-bottom: 1px; margin-right: 1px;`);
});
}
else {
items.forEach(item => {
item[1].setStyle(`background-color: ${player.colors.value.buttonAccent};
color: ${player.colors.value.buttonText};
min-height: 42px; min-width: 38px;`);
});
}
}
else {
button.setCss(`* { color: ${player.colors.value.buttonAccent}; }
*:hover { color: ${player.colors.value.hoverAccent}; }`);
}
}
}],
],
});
export const ShuffleButton = player => PlayerButton({
player,
items: [
['true', Label({
className: 'shuffle enabled',
label: icons.mpris.shuffle.enabled,
})],
['false', Label({
className: 'shuffle disabled',
label: icons.mpris.shuffle.disabled,
})],
],
onClick: 'shuffle',
prop: 'shuffleStatus',
});
export const LoopButton = player => PlayerButton({
player,
items: [
['None', Label({
className: 'loop none',
label: icons.mpris.loop.none,
})],
['Track', Label({
className: 'loop track',
label: icons.mpris.loop.track,
})],
['Playlist', Label({
className: 'loop playlist',
label: icons.mpris.loop.playlist,
})],
],
onClick: 'loop',
prop: 'loopStatus',
});
export const PlayPauseButton = player => PlayerButton({
player,
items: [
['Playing', Label({
className: 'pausebutton playing',
label: icons.mpris.playing,
})],
['Paused', Label({
className: 'pausebutton paused',
label: icons.mpris.paused,
})],
['Stopped', Label({
className: 'pausebutton stopped paused',
label: icons.mpris.stopped,
})],
],
onClick: 'playPause',
prop: 'playBackStatus',
});
export const PreviousButton = player => PlayerButton({
player,
items: [
['true', Label({
className: 'previous',
label: icons.mpris.prev,
})],
['false', Label({
className: 'previous',
label: icons.mpris.prev,
})],
],
onClick: 'previous',
prop: 'canGoPrev',
});
export const NextButton = player => PlayerButton({
player,
items: [
['true', Label({
className: 'next',
label: icons.mpris.next,
})],
['false', Label({
className: 'next',
label: icons.mpris.next,
})],
],
onClick: 'next',
prop: 'canGoNext',
});

View file

@ -0,0 +1,139 @@
const { Mpris } = ags.Service;
const { Box, CenterBox, Label } = ags.Widget;
import * as mpris from './mpris.js';
import PlayerGesture from './gesture.js';
import { Separator } from '../misc/separator.js';
const Top = player => Box({
className: 'top',
halign: 'start',
valign: 'start',
children: [
mpris.PlayerIcon(player, {
symbolic: false,
}),
],
});
const Center = player => Box({
className: 'center',
children: [
CenterBox({
vertical: true,
children: [
Box({
className: 'metadata',
vertical: true,
halign: 'start',
valign: 'center',
hexpand: true,
children: [
mpris.TitleLabel(player),
mpris.ArtistLabel(player),
],
}),
Label(),
Label(),
],
}),
CenterBox({
vertical: true,
children: [
Label(),
mpris.PlayPauseButton(player),
Label(),
],
}),
],
});
const Bottom = player => Box({
className: 'bottom',
children: [
mpris.PreviousButton(player, {
valign: 'end',
halign: 'start',
}),
Separator(8),
mpris.PositionSlider(player),
Separator(8),
mpris.NextButton(player),
Separator(8),
mpris.ShuffleButton(player),
Separator(8),
mpris.LoopButton(player),
],
});
const PlayerBox = player => mpris.CoverArt(player, {
className: `player ${player.name}`,
hexpand: true,
children: [
Top(player),
Center(player),
Bottom(player),
],
});
export default () => Box({
className: 'media',
child: PlayerGesture({
properties: [
['players', new Map()],
['setup', false],
['selected'],
],
connections: [
[Mpris, (overlay, busName) => {
if (!busName || overlay._players.has(busName))
return;
const player = Mpris.getPlayer(busName);
player.colors = ags.Variable();
overlay._players.set(busName, PlayerBox(player));
let result = [];
overlay._players.forEach(widget => {
result.push(widget);
});
overlay.overlays = result;
// Favor spotify
if (!overlay._setup) {
if (overlay._players.has('org.mpris.MediaPlayer2.spotify')) {
overlay._selected = overlay._players.get('org.mpris.MediaPlayer2.spotify');
}
overlay._setup = true;
}
if (overlay._selected)
overlay.reorder_overlay(overlay._selected, -1);
}, 'player-added'],
[Mpris, (overlay, busName) => {
if (!busName || !overlay._players.has(busName))
return;
overlay._players.delete(busName);
let result = [];
overlay._players.forEach(widget => {
result.push(widget);
});
overlay.overlays = result;
if (overlay._selected)
overlay.reorder_overlay(overlay._selected, -1);
}, 'player-closed'],
],
}),
});

View file

@ -0,0 +1,34 @@
const { Window, EventBox } = ags.Widget;
const { closeWindow } = ags.App;
const ALWAYS_OPEN = [
'closer',
'bar',
'notifications',
];
// TODO: close on scroll event too?
export const closeAll = () => {
ags.App.windows.forEach(w => {
if (!ALWAYS_OPEN.some(window => window === w.name))
ags.App.closeWindow(w.name)
});
closeWindow('closer');
};
export const Closer = Window({
name: 'closer',
popup: true,
layer: 'top',
anchor: 'top bottom left right',
child: EventBox({
onPrimaryClickRelease: () => closeAll(),
connections: [[ags.App, (_b, _w, _v) => {
if (!Array.from(ags.App.windows).some(w => w[1].visible &&
!ALWAYS_OPEN.some(window => window === w[0]))) {
closeWindow('closer');
}
}]],
}),
});

View file

@ -0,0 +1,34 @@
import Gdk from 'gi://Gdk';
const display = Gdk.Display.get_default();
export const EventBox = ({ reset = true, ...params }) => ags.Widget.EventBox({
...params,
onHover: box => {
if (! box.child.sensitive || ! box.sensitive) {
box.window.set_cursor(Gdk.Cursor.new_from_name(display, 'not-allowed'));
}
else {
box.window.set_cursor(Gdk.Cursor.new_from_name(display, 'pointer'));
}
},
onHoverLost: box => {
if (reset)
box.window.set_cursor(null);
},
});
export const Button = ({ reset = true, ...params }) => ags.Widget.Button({
...params,
onHover: box => {
if (! box.child.sensitive || ! box.sensitive) {
box.window.set_cursor(Gdk.Cursor.new_from_name(display, 'not-allowed'));
}
else {
box.window.set_cursor(Gdk.Cursor.new_from_name(display, 'pointer'));
}
},
onHoverLost: box => {
if (reset)
box.window.set_cursor(null);
},
});

138
config/ags/js/misc/drag.js Normal file
View file

@ -0,0 +1,138 @@
const { Box, EventBox } = ags.Widget;
const { Gtk, Gdk } = imports.gi;
const display = Gdk.Display.get_default();
export const Draggable = ({
maxOffset = 150,
startMargin = 0,
endMargin = 300,
command = () => {},
onHover = w => {},
onHoverLost = w => {},
child = '',
children = [],
properties = [[]],
...params
}) => {
let w = EventBox({
...params,
properties: [
['dragging', false],
...properties,
],
onHover: box => {
box.window.set_cursor(Gdk.Cursor.new_from_name(display, 'grab'));
onHover(box);
},
onHoverLost: box => {
box.window.set_cursor(null);
onHoverLost(box);
},
});
let gesture = Gtk.GestureDrag.new(w);
let leftAnim1 = `transition: margin 0.5s ease, opacity 0.5s ease;
margin-left: -${Number(maxOffset + endMargin)}px;
margin-right: ${Number(maxOffset + endMargin)}px;
opacity: 0;`;
let leftAnim2 = `transition: margin 0.5s ease, opacity 0.5s ease;
margin-left: -${Number(maxOffset + endMargin)}px;
margin-right: ${Number(maxOffset + endMargin)}px;
margin-bottom: -70px; margin-top: -70px; opacity: 0;`;
let rightAnim1 = `transition: margin 0.5s ease, opacity 0.5s ease;
margin-left: ${Number(maxOffset + endMargin)}px;
margin-right: -${Number(maxOffset + endMargin)}px;
opacity: 0;`;
let rightAnim2 = `transition: margin 0.5s ease, opacity 0.5s ease;
margin-left: ${Number(maxOffset + endMargin)}px;
margin-right: -${Number(maxOffset + endMargin)}px;
margin-bottom: -70px; margin-top: -70px; opacity: 0;`;
w.child = Box({
properties: [
['leftAnim1', leftAnim1],
['leftAnim2', leftAnim2],
['rightAnim1', rightAnim1],
['rightAnim2', rightAnim2],
['ready', false]
],
children: [
...children,
child,
],
style: leftAnim2,
connections: [
[gesture, box => {
var offset = gesture.get_offset()[1];
if (offset >= 0) {
box.setStyle('margin-left: ' + Number(offset + startMargin) + 'px; ' +
'margin-right: -' + Number(offset + startMargin) + 'px;');
}
else {
offset = Math.abs(offset);
box.setStyle('margin-right: ' + Number(offset + startMargin) + 'px; ' +
'margin-left: -' + Number(offset + startMargin) + 'px;');
}
box.get_parent()._dragging = Math.abs(offset) > 10;
if (w.window)
w.window.set_cursor(Gdk.Cursor.new_from_name(display, 'grabbing'));
}, 'drag-update'],
[gesture, box => {
if (!box._ready) {
box.setStyle(`transition: margin 0.5s ease, opacity 0.5s ease;
margin-left: -${Number(maxOffset + endMargin)}px;
margin-right: ${Number(maxOffset + endMargin)}px;
margin-bottom: 0px; margin-top: 0px; opacity: 0;`);
setTimeout(() => {
box.setStyle('transition: margin 0.5s ease, opacity 0.5s ease; ' +
'margin-left: ' + startMargin + 'px; ' +
'margin-right: ' + startMargin + 'px; ' +
'margin-bottom: unset; margin-top: unset; opacity: 1;');
}, 500);
setTimeout(() => box._ready = true, 1000);
return;
}
const offset = gesture.get_offset()[1];
if (Math.abs(offset) > maxOffset) {
if (offset > 0) {
box.setStyle(rightAnim1);
setTimeout(() => box.setStyle(rightAnim2), 500);
}
else {
box.setStyle(leftAnim1);
setTimeout(() => box.setStyle(leftAnim2), 500);
}
setTimeout(() => {
command();
box.destroy();
}, 1000);
}
else {
box.setStyle('transition: margin 0.5s ease, opacity 0.5s ease; ' +
'margin-left: ' + startMargin + 'px; ' +
'margin-right: ' + startMargin + 'px; ' +
'margin-bottom: unset; margin-top: unset; opacity: 1;');
if (w.window)
w.window.set_cursor(Gdk.Cursor.new_from_name(display, 'grab'));
box.get_parent()._dragging = false;
}
}, 'drag-end'],
],
});
return w;
};

View file

@ -0,0 +1,20 @@
const { Revealer, Box } = ags.Widget;
const { openWindow } = ags.App;
export const PopUp = ({name, child, transition = 'slide_down', ...params}) => Box({
style: 'min-height:1px; min-width:1px',
child: Revealer({
...params,
transition,
transitionDuration: 500,
connections: [[ags.App, (revealer, currentName, visible) => {
if (currentName === name) {
revealer.reveal_child = visible;
if (visible && name !== 'overview')
openWindow('closer');
}
}]],
child: child,
}),
});

View file

@ -0,0 +1,3 @@
export const Separator = width => ags.Widget.Box({
style: `min-width: ${width}px;`,
});

View file

@ -0,0 +1,184 @@
const { GLib } = imports.gi;
const { Notifications, Applications } = ags.Service;
const { lookUpIcon, exec, execAsync } = ags.Utils;
const { Box, Icon, Label, Button } = ags.Widget;
import { Draggable } from '../misc/drag.js';
import { EventBox } from '../misc/cursorbox.js'
import { closeAll } from '../misc/closer.js';
const NotificationIcon = ({ appEntry, appIcon, image }) => {
let iconCmd = () => {};
if (Applications.query(appEntry).length > 0) {
let app = Applications.query(appEntry)[0];
if (app.app.get_string('StartupWMClass') != null) {
iconCmd = box => {
if (!box.get_parent().get_parent().get_parent().get_parent().get_parent()._dragging) {
execAsync(['bash', '-c', `$AGS_PATH/launch-app.sh ${app.app.get_string('StartupWMClass')} ${app.app.get_string('Exec')}`]).catch(print);
closeAll();
}
}
}
else if (app.app.get_filename().includes('discord')) {
iconCmd = box => {
if (!box.get_parent().get_parent().get_parent().get_parent().get_parent()._dragging) {
execAsync(['bash', '-c', `$AGS_PATH/launch-app.sh discord ${app.app.get_string('Exec')}`])
.catch(print);
closeAll();
}
}
}
}
if (image) {
return EventBox({
onPrimaryClickRelease: iconCmd,
child: Box({
valign: 'start',
hexpand: false,
className: 'icon img',
style: `
background-image: url("${image}");
background-size: contain;
background-repeat: no-repeat;
background-position: center;
min-width: 78px;
min-height: 78px;
`,
}),
});
}
let icon = 'dialog-information-symbolic';
if (lookUpIcon(appIcon)) {
icon = appIcon;
}
if (lookUpIcon(appEntry)) {
icon = appEntry;
}
return EventBox({
onPrimaryClickRelease: iconCmd,
child: Box({
valign: 'start',
hexpand: false,
className: 'icon',
style: `
min-width: 78px;
min-height: 78px;
`,
children: [Icon({
icon, size: 58,
halign: 'center',
hexpand: true,
valign: 'center',
vexpand: true,
})],
}),
});
};
export default ({ id, summary, body, actions, urgency, time, command = i => {}, ...icon }) => {
const BlockedApps = [
'Spotify',
];
if (BlockedApps.find(app => app == Notifications.getNotification(id).appName)) {
Notifications.close(id);
return;
}
return Draggable({
maxOffset: 200,
command: () => command(id),
properties: [
['hovered', false],
['id', id],
],
onHover: w => {
if (!w._hovered) {
w._hovered = true;
}
},
onHoverLost: w => {
if (w._hovered) {
w._hovered = false;
}
},
child: Box({
className: `notification ${urgency}`,
vexpand: false,
// Notification
child: Box({
vertical: true,
children: [
// Content
Box({
children: [
NotificationIcon(icon),
Box({
hexpand: true,
vertical: true,
children: [
// Top of Content
Box({
children: [
Label({
className: 'title',
xalign: 0,
justification: 'left',
hexpand: true,
maxWidthChars: 24,
truncate: 'end',
wrap: true,
label: summary,
useMarkup: summary.startsWith('<'),
}),
Label({
className: 'time',
valign: 'start',
label: GLib.DateTime.new_from_unix_local(time).format('%H:%M'),
}),
EventBox({
reset: false,
child: Button({
className: 'close-button',
valign: 'start',
onClicked: () => Notifications.close(id),
child: Icon('window-close-symbolic'),
}),
}),
],
}),
Label({
className: 'description',
hexpand: true,
useMarkup: true,
xalign: 0,
justification: 'left',
label: body,
wrap: true,
}),
],
}),
],
}),
// Actions
Box({
className: 'actions',
children: actions.map(action => Button({
className: 'action-button',
onClicked: () => Notifications.invoke(id, action.id),
hexpand: true,
child: Label(action.label),
})),
}),
],
}),
}),
});
};

View file

@ -0,0 +1,145 @@
const { Notifications } = ags.Service;
const { Button, Label, Box, Icon, Scrollable, Window, Revealer } = ags.Widget;
const { timeout } = ags.Utils;
const { getWindow } = ags.App;
import Notification from './base.js';
import { EventBox } from '../misc/cursorbox.js';
import { PopUp } from '../misc/popup.js';
const ClearButton = () => EventBox({
child: Button({
onPrimaryClickRelease: button => {
button._popups.children.forEach(ch => ch.child.setStyle(ch.child._leftAnim1));
button._notifList.children.forEach(ch => {
ch.child.setStyle(ch.child._rightAnim1);
timeout(500, () => {
button._notifList.remove(ch);
Notifications.close(ch._id);
});
});
},
properties: [['notifList'], ['popups']],
connections: [[Notifications, button => {
if (!button._notifList)
button._notifList = NotificationList;
if (!button._popups)
button._popups = getWindow('notifications').child.children[0].child;
button.sensitive = Notifications.notifications.length > 0;
}]],
child: Box({
children: [
Label('Clear '),
Icon({
connections: [[Notifications, icon => {
icon.icon = Notifications.notifications.length > 0
? 'user-trash-full-symbolic' : 'user-trash-symbolic';
}]],
}),
],
}),
}),
});
const Header = () => Box({
className: 'header',
children: [
Label({ label: 'Notifications', hexpand: true, xalign: 0 }),
ClearButton(),
],
});
const NotificationList = Box({
vertical: true,
vexpand: true,
connections: [
[Notifications, (box, id) => {
if (box.children.length == 0) {
box.children = Notifications.notifications
.reverse()
.map(n => Notification({ ...n, command: i => Notifications.close(i), }));
}
else if (id) {
const NewNotif = Notification({
...Notifications.getNotification(id),
command: i => Notifications.close(i),
});
if (NewNotif) {
box.add(NewNotif);
box.show_all();
}
}
}, 'notified'],
[Notifications, (box, id) => {
for (const ch of box.children) {
if (ch._id == id) {
ch.child.setStyle(ch.child._rightAnim1);
timeout(500, () => box.remove(ch));
return;
}
}
}, 'closed'],
[Notifications, box => box.visible = Notifications.notifications.length > 0],
],
});
const Placeholder = () => Revealer({
transition: 'crossfade',
connections: [[Notifications, box => {
box.revealChild = Notifications.notifications.length === 0;
}]],
child: Box({
className: 'placeholder',
vertical: true,
valign: 'center',
halign: 'center',
vexpand: true,
hexpand: true,
children: [
Icon('notification-disabled-symbolic'),
Label('Your inbox is empty'),
],
}),
});
const NotificationCenterWidget = Box({
className: 'notification-center',
vertical: true,
children: [
Header(),
Box({
className: 'notification-wallpaper-box',
children: [Scrollable({
className: 'notification-list-box',
hscroll: 'never',
vscroll: 'automatic',
child: Box({
className: 'notification-list',
vertical: true,
children: [
NotificationList,
Placeholder(),
],
}),
})],
}),
],
});
export const NotificationCenter = Window({
name: 'notification-center',
layer: 'overlay',
anchor: 'top right',
popup: true,
margin: [ 8, 60, 0, 0 ],
child: PopUp({
name: 'notification-center',
child: NotificationCenterWidget,
}),
});

View file

@ -0,0 +1,82 @@
import Notification from './base.js';
const { Notifications } = ags.Service;
const { Box, Revealer, Window } = ags.Widget;
const { timeout, interval } = ags.Utils;
const { source_remove } = imports.gi.GLib;
const Popups = () => Box({
vertical: true,
properties: [
['map', new Map()],
['dismiss', (box, id, force = false) => {
if (!id || !box._map.has(id))
return;
if (box._map.get(id)._hovered && !force)
return;
if (box._map.size - 1 === 0)
box.get_parent().reveal_child = false;
timeout(200, () => {
if (box._map.get(id)?.interval) {
source_remove(box._map.get(id).interval);
box._map.get(id).interval = undefined;
}
box._map.get(id)?.destroy();
box._map.delete(id);
});
}],
['notify', (box, id) => {
if (!id || Notifications.dnd)
return;
if (! Notifications.getNotification(id))
return;
box._map.delete(id);
box._map.set(id, Notification({
...Notifications.getNotification(id),
command: i => Notifications.dismiss(i),
}));
box.children = Array.from(box._map.values()).reverse();
timeout(10, () => {
box.get_parent().revealChild = true;
});
box._map.get(id).interval = interval(4500, () => {
if (!box._map.get(id)._hovered) {
box._map.get(id).child.setStyle(box._map.get(id).child._leftAnim1);
if (box._map.get(id).interval) {
source_remove(box._map.get(id).interval);
box._map.get(id).interval = undefined;
}
}
});
}],
],
connections: [
[Notifications, (box, id) => box._notify(box, id), 'notified'],
[Notifications, (box, id) => box._dismiss(box, id), 'dismissed'],
[Notifications, (box, id) => box._dismiss(box, id, true), 'closed'],
],
});
const PopupList = ({ transition = 'none' } = {}) => Box({
className: 'notifications-popup-list',
style: 'padding: 1px',
children: [
Revealer({
transition,
child: Popups(),
}),
],
});
export const NotificationsPopupList = Window({
name: `notifications`,
anchor: 'top left',
child: PopupList(),
});

View file

@ -0,0 +1,124 @@
const { Icon, Revealer } = ags.Widget;
const { closeWindow } = ags.App;
const { execAsync } = ags.Utils;
const { Hyprland } = ags.Service;
import { WindowButton } from './dragndrop.js';
import * as VARS from './variables.js';
Array.prototype.remove = function (el) { this.splice(this.indexOf(el), 1) };
const IconStyle = app => `min-width: ${app.size[0] * VARS.SCALE - VARS.MARGIN}px;
min-height: ${app.size[1] * VARS.SCALE - VARS.MARGIN}px;
font-size: ${Math.min(app.size[0] * VARS.SCALE - VARS.MARGIN,
app.size[1] * VARS.SCALE - VARS.MARGIN) * VARS.ICON_SCALE}px;`;
const Client = (client, active, clients) => Revealer({
transition: 'crossfade',
setup: rev => {
rev.revealChild = true;
},
properties: [
['address', client.address],
['toDestroy', false]
],
child: WindowButton({
address: client.address,
onSecondaryClickRelease: () => {
execAsync(`hyprctl dispatch closewindow address:${client.address}`)
.catch(print)
},
onPrimaryClickRelease: () => {
if (client.workspace.id < 0) {
if (client.workspace.name === 'special') {
execAsync(`hyprctl dispatch movetoworkspacesilent special:${client.workspace.id},address:${client.address}`)
.then(execAsync(`hyprctl dispatch togglespecialworkspace ${client.workspace.id}`)
.then(() => closeWindow('overview'))
.catch(print))
.catch(print);
}
else {
execAsync(`hyprctl dispatch togglespecialworkspace ${String(client.workspace.name)
.replace('special:', '')}`)
.then(() => closeWindow('overview'))
.catch(print);
}
}
else {
// close special workspace if one is opened
let activeAddress = Hyprland.active.client.address;
let currentActive = clients.find(c => c.address === activeAddress)
if (currentActive && currentActive.workspace.id < 0)
execAsync(`hyprctl dispatch togglespecialworkspace ${String(currentActive.workspace.name)
.replace('special:', '')}`).catch(print);
execAsync(`hyprctl dispatch focuswindow address:${client.address}`)
.then(() => closeWindow('overview'))
.catch(print);
}
},
child: Icon({
className: `window ${active}`,
style: IconStyle(client) + 'font-size: 10px;',
icon: client.class,
}),
}),
});
export function updateClients(box) {
ags.Utils.execAsync('hyprctl clients -j')
.then(result => {
let clients = JSON.parse(result).filter(client => client.class)
box._workspaces.forEach(workspace => {
let fixed = workspace.child.child.overlays[0].children[0];
let toRemove = fixed.get_children();
clients.filter(client => client.workspace.id == workspace._id).forEach(client => {
let active = '';
if (client.address == Hyprland.active.client.address) {
active = 'active';
}
// Special workspaces that haven't been opened yet
// return a size of 0. We need to set them to default
// values to show the workspace properly
if (client.size[0] === 0) {
client.size[0] = VARS.DEFAULT_SPECIAL.SIZE_X;
client.size[1] = VARS.DEFAULT_SPECIAL.SIZE_Y;
client.at[0] = VARS.DEFAULT_SPECIAL.POS_X;
client.at[1] = VARS.DEFAULT_SPECIAL.POS_Y;
}
let existingClient = fixed.get_children().find(ch => ch._address == client.address);
toRemove.remove(existingClient);
if (existingClient) {
fixed.move(
existingClient,
client.at[0] * VARS.SCALE,
client.at[1] * VARS.SCALE,
);
existingClient.child.child.className = `window ${active}`;
existingClient.child.child.style = IconStyle(client);
}
else {
fixed.put(
Client(client, active, clients),
client.at[0] * VARS.SCALE,
client.at[1] * VARS.SCALE,
);
}
});
fixed.show_all();
toRemove.forEach(ch => {
if (ch._toDestroy) {
ch.destroy();
}
else {
ch.revealChild = false;
ch._toDestroy = true;
}
});
});
}).catch(print);
};

View file

@ -0,0 +1,69 @@
const { Gtk, Gdk } = imports.gi;
const { EventBox } = ags.Widget;
const { execAsync } = ags.Utils;
const { getWindow } = ags.App;
import Cairo from 'cairo';
import { Button } from '../misc/cursorbox.js';
const TARGET = [Gtk.TargetEntry.new('text/plain', Gtk.TargetFlags.SAME_APP, 0)];
function createSurfaceFromWidget(widget) {
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;
};
let hidden = 0;
export const WorkspaceDrop = params => EventBox({
...params,
//tooltipText: `Workspace: ${id}`,
connections: [['drag-data-received', (eventbox, _c, _x, _y, data) => {
let id = eventbox.get_parent()._id;
if (id < -1) {
id = eventbox.get_parent()._name;
}
else if (id === -1) {
id = `special:${++hidden}`;
}
else if (id === 1000) {
id = "empty";
}
execAsync(`hyprctl dispatch movetoworkspacesilent ${id},address:${data.get_text()}`)
.catch(print);
}]],
setup: eventbox => {
eventbox.drag_dest_set(Gtk.DestDefaults.ALL, TARGET, Gdk.DragAction.COPY);
},
});
export const WindowButton = ({address, ...params} = {}) => Button({
...params,
setup: button => {
button.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, TARGET, Gdk.DragAction.COPY);
button.connect('drag-data-get', (_w, _c, data) => {
data.set_text(address, address.length);
});
button.connect('drag-begin', (_, context) => {
Gtk.drag_set_icon_surface(context, createSurfaceFromWidget(button));
button.get_parent().revealChild = false;
});
button.connect('drag-end', () => {
button.get_parent().destroy();
let mainBox = getWindow('overview').child.children[0].child;
mainBox._updateClients(mainBox);
});
},
});

View file

@ -0,0 +1,51 @@
const { Window, Box } = ags.Widget;
const { Hyprland } = ags.Service;
import { PopUp } from '../misc/popup.js';
import { WorkspaceRow, getWorkspaces, updateWorkspaces } from './workspaces.js';
import { updateClients } from './clients.js';
export default Window({
name: 'overview',
layer: 'overlay',
popup: true,
child: PopUp({
name: 'overview',
transition: 'crossfade',
child: Box({
className: 'overview',
vertical: true,
children: [
Box({
vertical: true,
children: [
WorkspaceRow('normal', 0),
],
}),
Box({
vertical: true,
children: [
WorkspaceRow('special', 0),
],
}),
],
connections: [
[Hyprland, box => {
box._getWorkspaces(box);
box._updateWorkspaces(box);
box._updateClients(box);
}],
],
properties: [
['workspaces'],
['getWorkspaces', getWorkspaces],
['updateWorkspaces', updateWorkspaces],
['updateClients', updateClients],
],
}),
}),
});

View file

@ -0,0 +1,14 @@
export const SCALE = 0.11;
export const ICON_SCALE = 0.8;
export const MARGIN = 8;
export const DEFAULT_SPECIAL = {
SIZE_X: 1524,
SIZE_Y: 908,
POS_X: 197,
POS_Y: 170,
};
export const WORKSPACE_PER_ROW = 6;
export const SCREEN = {
X: 1920,
Y: 1200,
};

View file

@ -0,0 +1,187 @@
const { Revealer, CenterBox, Box, EventBox, Label, Overlay } = ags.Widget;
const { Hyprland } = ags.Service;
const { Gtk } = imports.gi;
import { WorkspaceDrop } from './dragndrop.js';
import * as VARS from './variables.js';
const DEFAULT_STYLE = `min-width: ${VARS.SCREEN.X * VARS.SCALE}px;
min-height: ${VARS.SCREEN.Y * VARS.SCALE}px;`;
export function getWorkspaces(box) {
let children = [];
box.children.forEach(type => {
type.children.forEach(row => {
row.child.centerWidget.child.children.forEach(ch => {
children.push(ch);
});
});
});
box._workspaces = children.sort((a, b) => a._id - b._id);
};
export const WorkspaceRow = (className, i) => Revealer({
transition: 'slide_down',
connections: [[Hyprland, rev => {
let minId = i * VARS.WORKSPACE_PER_ROW;
let activeId = Hyprland.active.workspace.id;
rev.revealChild = Hyprland.workspaces.some(ws => ws.id > minId &&
(ws.windows > 0 ||
ws.id === activeId));
}]],
child: CenterBox({
children: [null, EventBox({
properties: [['box']],
setup: eventbox => eventbox._box = eventbox.child.children[0],
connections: [[Hyprland, eventbox => {
let maxId = i * VARS.WORKSPACE_PER_ROW + VARS.WORKSPACE_PER_ROW;
let activeId = Hyprland.active.workspace.id;
eventbox._box.revealChild = className === 'special' ||
!Hyprland.workspaces.some(ws => ws.id > maxId &&
(ws.windows > 0 ||
ws.id === activeId));
}]],
child: Box({
className: className,
children: [
Revealer({
transition: 'slide_right',
properties: [
['id', className === 'special' ? -1 : 1000],
['name', className === 'special' ? 'special' : ''],
],
child: WorkspaceDrop({
child: Overlay({
child: Box({
className: 'workspace',
style: DEFAULT_STYLE,
}),
overlays: [Box({
style: DEFAULT_STYLE,
children: [
ags.Widget({
type: Gtk.Fixed,
}),
Label({
label: ' +',
style: 'font-size: 40px;',
}),
],
})],
}),
}),
}),
],
}),
}), null],
}),
});
const Workspace = (id, name) => Revealer({
transition: 'slide_right',
transitionDuration: 500,
properties: [
['id', id],
['name', name],
['timeouts', []],
['wasActive', false],
],
connections: [[Hyprland, box => {
box._timeouts.forEach(clearTimeout);
let activeId = Hyprland.active.workspace.id;
let active = activeId === box._id;
let rev = box.child.child.child;
let n = activeId > box._id;
if (Hyprland.getWorkspace(box._id)?.windows > 0 || active) {
rev.setStyle(DEFAULT_STYLE);
box._timeouts.push(setTimeout(() => {
box.revealChild = true;
}, 100));
}
else if (!Hyprland.getWorkspace(box._id)?.windows > 0) {
rev.setStyle(DEFAULT_STYLE);
box._timeouts.push(setTimeout(() => {
box.revealChild = false;
}, 100));
return;
}
if (active) {
rev.setStyle(`${DEFAULT_STYLE}
transition: margin 0.5s ease-in-out;
opacity: 1;`);
box._wasActive = true;
}
else if (box._wasActive) {
box._wasActive = false;
box._timeouts.push(setTimeout(() => {
rev.setStyle(`${DEFAULT_STYLE}
transition: margin 0.5s ease-in-out;
opacity: 1; margin-left: ${n ? '' : '-'}300px;
margin-right: ${n ? '-' : ''}300px;`);
}, 120));
box._timeouts.push(setTimeout(() => {
rev.setStyle(`${DEFAULT_STYLE} opacity: 0;
margin-left: ${n ? '' : '-'}300px;
margin-right: ${n ? '-' : ''}300px;`);
}, 500));
}
else {
rev.setStyle(`${DEFAULT_STYLE} opacity: 0;
margin-left: ${n ? '' : '-'}300px;
margin-right: ${n ? '-' : ''}300px;`);
}
}]],
child: WorkspaceDrop({
child: Overlay({
child: Box({
className: 'workspace active',
style: `${DEFAULT_STYLE} opacity: 0;`,
}),
overlays: [Box({
className: 'workspace',
style: DEFAULT_STYLE,
child: ags.Widget({
type: Gtk.Fixed,
}),
})],
}),
}),
});
export function updateWorkspaces(box) {
Hyprland.workspaces.forEach(ws => {
let currentWs = box._workspaces.find(ch => ch._id == ws.id);
if (!currentWs) {
var type = 0;
var rowNo = 0;
if (ws.id < 0) {
// This means it's a special workspace
type = 1;
}
else {
rowNo = Math.floor((ws.id - 1) / VARS.WORKSPACE_PER_ROW);
if (rowNo >= box.children[type].children.length) {
for (let i = box.children[type].children.length; i <= rowNo; ++i) {
box.children[type].add(WorkspaceRow(type ? 'special' : 'normal', i));
}
}
}
var row = box.children[type].children[rowNo].child.centerWidget.child;
row.add(Workspace(ws.id, type ? ws.name : ''));
}
});
box.show_all();
// Make sure the order is correct
box._workspaces.forEach((workspace, i) => {
workspace.get_parent().reorder_child(workspace, i)
});
}

View file

@ -0,0 +1,47 @@
const { Window, CenterBox, Label } = ags.Widget;
import { PopUp } from './misc/popup.js';
import { Button } from './misc/cursorbox.js'
const PowermenuWidget = CenterBox({
className: 'powermenu',
vertical: false,
startWidget: Button({
className: 'shutdown',
onPrimaryClickRelease: 'systemctl poweroff',
child: Label({
label: '襤',
}),
}),
centerWidget: Button({
className: 'reboot',
onPrimaryClickRelease: 'systemctl reboot',
child: Label({
label: '勒',
}),
}),
endWidget: Button({
className: 'logout',
onPrimaryClickRelease: 'hyprctl dispatch exit',
child: Label({
label: '',
}),
}),
});
export const Powermenu = Window({
name: 'powermenu',
popup: true,
layer: 'overlay',
child: PopUp({
name: 'powermenu',
transition: 'crossfade',
child: PowermenuWidget,
}),
});

View file

@ -0,0 +1,212 @@
const { Box, CenterBox, Label, Icon } = ags.Widget;
const { Network, Bluetooth, Audio } = ags.Service;
const { execAsync } = ags.Utils;
const { openWindow } = ags.App;
import { EventBox } from '../misc/cursorbox.js';
const GridButton = ({ command = () => {}, secondaryCommand = () => {}, icon } = {}) => Box({
className: 'grid-button',
children: [
EventBox({
className: 'left-part',
onPrimaryClickRelease: () => command(),
child: icon,
}),
EventBox({
className: 'right-part',
onPrimaryClickRelease: () => secondaryCommand(),
child: Label({
label: " ",
className: 'grid-chev',
}),
}),
],
});
const FirstRow = Box({
className: 'button-row',
halign: 'center',
style: 'margin-top: 15px; margin-bottom: 7px;',
children: [
GridButton({
command: () => Network.toggleWifi(),
secondaryCommand: () => execAsync(['bash', '-c', 'nm-connection-editor']).catch(print),
icon: Icon({
className: 'grid-label',
connections: [[Network, icon => {
if (Network.wifi.enabled) {
icon.icon = 'network-wireless-connected-symbolic';
}
else {
icon.icon = 'network-wireless-offline-symbolic';
}
}, 'changed']],
}),
}),
GridButton({
command: () => execAsync(['bash', '-c', '$AGS_PATH/qs-toggles.sh blue-toggle']).catch(print),
secondaryCommand: () => execAsync(['bash', '-c', 'blueberry']).catch(print),
icon: Icon({
className: 'grid-label',
connections: [[Bluetooth, icon => {
if (Bluetooth.enabled) {
icon.icon = 'bluetooth-active-symbolic';
execAsync(['bash', '-c', 'echo 󰂯 > $HOME/.config/.bluetooth']).catch(print);
}
else {
icon.icon = 'bluetooth-disabled-symbolic';
execAsync(['bash', '-c', 'echo 󰂲 > $HOME/.config/.bluetooth']).catch(print);
}
}, 'changed']],
})
}),
GridButton({
command: () => execAsync(['bash', '-c', '$AGS_PATH/qs-toggles.sh toggle-radio']).catch(print),
secondaryCommand: () => execAsync(['notify-send', 'set this up moron']).catch(print),
icon: Icon({
className: 'grid-label',
connections: [[Network, icon => {
if (Network.wifi.enabled) {
icon.icon = 'airplane-mode-disabled-symbolic';
}
else {
icon.icon = 'airplane-mode-symbolic';
}
}, 'changed']],
}),
}),
],
});
const SubRow = CenterBox({
halign: 'start',
children: [
Label({
className: 'sub-label',
truncate: 'end',
maxWidthChars: 12,
connections: [[Network, label => {
label.label = Network.wifi.ssid;
}, 'changed']],
}),
Label({
className: 'sub-label',
truncate: 'end',
maxWidthChars: 12,
connections: [[Bluetooth, label => {
label.label = Bluetooth.connectedDevices[0] ? String(Bluetooth.connectedDevices[0]) :
'Disconnected';
}, 'changed']],
}),
Label({
className: '',
truncate: 'end',
maxWidthChars: 12,
/*connections: [[Network, label => {
label.label = Network.wifi.ssid;
}, 'changed']],*/
}),
],
});
const items = {
101: 'audio-volume-overamplified-symbolic',
67: 'audio-volume-high-symbolic',
34: 'audio-volume-medium-symbolic',
1: 'audio-volume-low-symbolic',
0: 'audio-volume-muted-symbolic',
};
const itemsMic = {
2: 'audio-input-microphone-high-symbolic',
1: 'audio-input-microphone-muted-symbolic',
0: 'audio-input-microphone-muted-symbolic',
};
const SecondRow = Box({
className: 'button-row',
halign: 'center',
style: 'margin-top: 7px; margin-bottom: 15px;',
children: [
GridButton({
command: () => execAsync(['swayosd-client', '--output-volume', 'mute-toggle']).catch(print),
secondaryCommand: () => execAsync(['bash', '-c', 'pavucontrol']).catch(print),
icon: Icon({
className: 'grid-label',
connections: [[Audio, icon => {
if (Audio.speaker) {
if (Audio.speaker.isMuted) {
icon.icon = items[0];
}
else {
const vol = Audio.speaker.volume * 100;
for (const threshold of [-1, 0, 33, 66, 100]) {
if (vol > threshold + 1) {
icon.icon = items[threshold + 1];
}
}
}
}
}, 'speaker-changed']],
}),
}),
GridButton({
command: () => execAsync(['swayosd-client', '--input-volume', 'mute-toggle']).catch(print),
secondaryCommand: () => execAsync(['bash', '-c', 'pavucontrol']).catch(print),
icon: Icon({
className: 'grid-label',
connections: [[Audio, icon => {
if (Audio.microphone) {
if (Audio.microphone.isMuted) {
icon.icon = itemsMic[0];
}
else {
const vol = Audio.microphone.volume * 100;
for (const threshold of [-1, 0, 1]) {
if (vol > threshold + 1) {
icon.icon = itemsMic[threshold + 1];
}
}
}
}
}, 'microphone-changed']],
})
}),
GridButton({
command: () => execAsync(['bash', '-c', '$LOCK_PATH/lock.sh']).catch(print),
secondaryCommand: () => openWindow('powermenu'),
icon: Label({
className: 'grid-label',
label: " 󰌾 ",
}),
}),
],
});
export const ButtonGrid = Box({
className: 'button-grid',
vertical: true,
halign: 'center',
children: [
FirstRow,
SubRow,
SecondRow,
],
});

Some files were not shown because too many files have changed in this diff Show more