Compare commits

..

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

591 changed files with 8180 additions and 32849 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:'

5
.gitattributes vendored
View file

@ -1,5 +0,0 @@
flake.lock -diff
flake.nix -diff
**/non-declarative-conf -diff
**/package-lock.json -diff
**/HomeAssistantGenerated -diff

21
.gitignore vendored
View file

@ -1,23 +1,2 @@
# Python
*.egg-info
# NPM
*node_modules
*build/
# Direnv
*.direnv/
# Generated by nix
result*
!results/
.nixd.json
## AGS
**/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.

124
README.md
View file

@ -1,120 +1,18 @@
# My NixOS configs
TODO: add directory structure info and enforce it
- every root folder in the repo represents a flake output except inputs
- every root folder only has a `default.nix` and subfolders for each
of its attrs
- in a subfolder, there should always be a `default.nix`
- if there is non nix code, it will be in a `config` folder
- redo docs
- every module should not do anything if imported
what is currently not working:
## AGS
- plymouth theme has no login prompt
- sddm theme flashes white
- autosign in to keyring
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/modules/ags)
to see my configuration.
what i want to do:
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.
- learn flakes
- add auto-rotate widget in ags control center
- when multiple widgets open, clicking on a background one puts it forward
## About
## Docs
### General
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 |
| `scopedPackages` | Some custom [package scopes](https://git.nelim.org/matt1432/nixos-configs/src/branch/master/scopedPackages) 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/modules) 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,2 +0,0 @@
use flake $FLAKE#node
npm ci

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',
],
},
});

View file

@ -1,3 +0,0 @@
import eslintConf from './eslint.config';
export default eslintConf;

Binary file not shown.

View file

@ -1,16 +0,0 @@
{
"name": "eslint-conf",
"version": "0.0.0",
"type": "module",
"exports": "./index.ts",
"devDependencies": {
"@eslint/js": "9.17.0",
"@stylistic/eslint-plugin": "2.12.1",
"eslint": "9.17.0",
"eslint-plugin-jsdoc": "50.6.1",
"jiti": "2.4.2",
"pkg-types": "1.2.1",
"typescript": "5.7.2",
"typescript-eslint": "8.18.1"
}
}

View file

@ -1,25 +0,0 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
// Env
"target": "ESNext",
"lib": [
"ESNext"
],
// Module
"module": "nodenext",
"moduleResolution": "bundler",
"baseUrl": ".",
// Emit
"noEmit": true,
"newLine": "LF",
// Interop
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
// Type Checking
"strict": true,
"noImplicitAny": false
}
}

View file

@ -1,10 +0,0 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "./tsconfig.base.json",
"includes": [
"*.ts",
"**/*.ts",
"*.js",
"**/*.js"
]
}

View file

@ -1,20 +0,0 @@
{
inputs,
pkgs,
...
}: let
inherit (pkgs.lib) getExe listToAttrs nameValuePair;
buildApp = attrs: (pkgs.callPackage ./nix/buildApp.nix ({} // inputs // attrs));
mkApp = file: {
program = getExe (pkgs.callPackage file ({inherit buildApp;} // inputs));
type = "app";
};
mkApps = apps: listToAttrs (map (x: nameValuePair x (mkApp ./${x})) apps);
in
mkApps [
"extract-subs"
"update-sources"
]

View file

@ -1,2 +0,0 @@
use flake $FLAKE#subtitles-dev
npm ci

View file

@ -1,13 +0,0 @@
{
buildApp,
ffmpeg-full,
...
}:
buildApp {
src = ./.;
npmDepsHash = "sha256-ytaO1mqoORNB5rP/8X/WpIoHkxmi1+Pc6Nep02+fTZ8=";
runtimeInputs = [
ffmpeg-full
];
}

View file

@ -1,3 +0,0 @@
import eslintConf from 'eslint-conf';
export default eslintConf;

Binary file not shown.

View file

@ -1,21 +0,0 @@
{
"name": "extract-subs",
"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": {
"@types/fluent-ffmpeg": "2.1.27",
"fluent-ffmpeg": "2.1.3"
},
"devDependencies": {
"eslint-conf": "file:../config",
"@types/node": "22.10.2",
"esbuild": "0.24.2",
"eslint": "9.17.0",
"jiti": "2.4.2",
"typescript": "5.7.2"
}
}

View file

@ -1,157 +0,0 @@
import { spawnSync as spawn } from 'child_process';
import ffprobe from './ffprobe';
import { ISO6393To1 } from './lang-codes';
/* Types */
import { FfprobeStream } from 'fluent-ffmpeg';
const SPAWN_OPTS = {
stdio: [process.stdin, process.stdout, process.stderr],
};
/**
* These are the cli arguments
*
* @param videoPath the directory in which we want to sync the subtitles
* @param languages a comma-separated list of languages (3 letters) to sync the subtitles
*/
const video = process.argv[2];
const languages = process.argv[3]?.split(',');
// Global Vars
const subIndexes: number[] = [];
let videoPath: string;
let baseName: string;
/**
* Gets the relative path to the subtitle file of a ffmpeg stream.
*
* @param sub the stream of the subtitles to extract
* @returns the path of the subtitle file
*/
const getSubPath = (sub: FfprobeStream): string => {
const language = ISO6393To1.get(sub.tags.language);
const forced = sub.disposition?.forced === 0 ?
'' :
'.forced';
const hearingImpaired = sub.disposition?.hearing_impaired === 0 ?
'' :
'.sdh';
return `${baseName}${forced}.${language}${hearingImpaired}.srt`;
};
/**
* Removes all subtitles streams from the video file.
*/
const removeContainerSubs = (): void => {
spawn('mv', [
videoPath,
`${videoPath}.bak`,
], SPAWN_OPTS);
spawn('ffmpeg', [
'-i', `${videoPath}.bak`,
'-map', '0',
...subIndexes.map((i) => ['-map', `-0:${i}`]).flat(),
'-c', 'copy', videoPath,
], SPAWN_OPTS);
spawn('rm', [
`${videoPath}.bak`,
], SPAWN_OPTS);
};
/**
* Extracts a sub of a video file to a subtitle file.
*
* @param sub the stream of the subtitles to extract
*/
const extractSub = (sub: FfprobeStream): void => {
const subFile = getSubPath(sub);
spawn('ffmpeg', [
'-i', videoPath,
'-map', `0:${sub.index}`, subFile,
], SPAWN_OPTS);
subIndexes.push(sub.index);
};
/**
* Sorts the list of streams to only keep subtitles
* that can be extracted.
*
* @param lang the language of the subtitles
* @param streams the streams
* @returns the streams that represent subtitles
*/
const findSubs = (
lang: string,
streams: FfprobeStream[],
): FfprobeStream[] => {
const subs = streams.filter((s) => s.tags?.language &&
s.tags.language === lang &&
s.codec_type === 'subtitle');
const pgs = subs.filter((s) => s.codec_name === 'hdmv_pgs_subtitle');
// If we only have PGS subs, warn user
if (pgs.length === subs.length) {
console.warn(`No SRT subtitle tracks were found for ${lang}`);
}
// Remove PGS streams from subs
return subs.filter((s) => s.codec_name !== 'hdmv_pgs_subtitle');
};
/**
* Where the magic happens.
*/
const main = async(): Promise<void> => {
// Get rid of video extension
baseName = videoPath.split('/').at(-1)!.replace(/\.[^.]*$/, '');
// ffprobe the video file to see available sub tracks
const data = await ffprobe(videoPath);
if (!data?.streams) {
console.error('Couldn\'t find streams in video file');
return;
}
// Check for languages wanted
languages.forEach((lang) => {
const subs = findSubs(lang, data.streams);
if (subs.length === 0) {
console.warn(`No subtitle tracks were found for ${lang}`);
return;
}
// Extract all subs
subs.forEach((sub) => { extractSub(sub); });
});
removeContainerSubs();
};
// Check if there are 2 params
if (video && languages) {
videoPath = video;
main();
}
else {
console.error('Error: no argument passed');
process.exit(1);
}

View file

@ -1,8 +0,0 @@
import Ffmpeg from 'fluent-ffmpeg';
export default (videoPath: string) => new Promise<Ffmpeg.FfprobeData>((resolve) => {
Ffmpeg.ffprobe(videoPath, (_e, data) => {
resolve(data);
});
});

View file

@ -1,373 +0,0 @@
export const ISO6391To3 = new Map([
['aa', 'aar'],
['ab', 'abk'],
['af', 'afr'],
['ak', 'aka'],
['am', 'amh'],
['ar', 'ara'],
['an', 'arg'],
['as', 'asm'],
['av', 'ava'],
['ae', 'ave'],
['ay', 'aym'],
['az', 'aze'],
['ba', 'bak'],
['bm', 'bam'],
['be', 'bel'],
['bn', 'ben'],
['bi', 'bis'],
['bo', 'bod'],
['bs', 'bos'],
['br', 'bre'],
['bg', 'bul'],
['ca', 'cat'],
['cs', 'ces'],
['ch', 'cha'],
['ce', 'che'],
['cu', 'chu'],
['cv', 'chv'],
['kw', 'cor'],
['co', 'cos'],
['cr', 'cre'],
['cy', 'cym'],
['da', 'dan'],
['de', 'deu'],
['dv', 'div'],
['dz', 'dzo'],
['el', 'ell'],
['en', 'eng'],
['eo', 'epo'],
['et', 'est'],
['eu', 'eus'],
['ee', 'ewe'],
['fo', 'fao'],
['fa', 'fas'],
['fj', 'fij'],
['fi', 'fin'],
['fr', 'fre'],
['fy', 'fry'],
['ff', 'ful'],
['gd', 'gla'],
['ga', 'gle'],
['gl', 'glg'],
['gv', 'glv'],
['gn', 'grn'],
['gu', 'guj'],
['ht', 'hat'],
['ha', 'hau'],
['sh', 'hbs'],
['he', 'heb'],
['hz', 'her'],
['hi', 'hin'],
['ho', 'hmo'],
['hr', 'hrv'],
['hu', 'hun'],
['hy', 'hye'],
['ig', 'ibo'],
['io', 'ido'],
['ii', 'iii'],
['iu', 'iku'],
['ie', 'ile'],
['ia', 'ina'],
['id', 'ind'],
['ik', 'ipk'],
['is', 'isl'],
['it', 'ita'],
['jv', 'jav'],
['ja', 'jpn'],
['kl', 'kal'],
['kn', 'kan'],
['ks', 'kas'],
['ka', 'kat'],
['kr', 'kau'],
['kk', 'kaz'],
['km', 'khm'],
['ki', 'kik'],
['rw', 'kin'],
['ky', 'kir'],
['kv', 'kom'],
['kg', 'kon'],
['ko', 'kor'],
['kj', 'kua'],
['ku', 'kur'],
['lo', 'lao'],
['la', 'lat'],
['lv', 'lav'],
['li', 'lim'],
['ln', 'lin'],
['lt', 'lit'],
['lb', 'ltz'],
['lu', 'lub'],
['lg', 'lug'],
['mh', 'mah'],
['ml', 'mal'],
['mr', 'mar'],
['mk', 'mkd'],
['mg', 'mlg'],
['mt', 'mlt'],
['mn', 'mon'],
['mi', 'mri'],
['ms', 'msa'],
['my', 'mya'],
['na', 'nau'],
['nv', 'nav'],
['nr', 'nbl'],
['nd', 'nde'],
['ng', 'ndo'],
['ne', 'nep'],
['nl', 'nld'],
['nn', 'nno'],
['nb', 'nob'],
['no', 'nor'],
['ny', 'nya'],
['oc', 'oci'],
['oj', 'oji'],
['or', 'ori'],
['om', 'orm'],
['os', 'oss'],
['pa', 'pan'],
['pi', 'pli'],
['pl', 'pol'],
['pt', 'por'],
['ps', 'pus'],
['qu', 'que'],
['rm', 'roh'],
['ro', 'ron'],
['rn', 'run'],
['ru', 'rus'],
['sg', 'sag'],
['sa', 'san'],
['si', 'sin'],
['sk', 'slk'],
['sl', 'slv'],
['se', 'sme'],
['sm', 'smo'],
['sn', 'sna'],
['sd', 'snd'],
['so', 'som'],
['st', 'sot'],
['es', 'spa'],
['sq', 'sqi'],
['sc', 'srd'],
['sr', 'srp'],
['ss', 'ssw'],
['su', 'sun'],
['sw', 'swa'],
['sv', 'swe'],
['ty', 'tah'],
['ta', 'tam'],
['tt', 'tat'],
['te', 'tel'],
['tg', 'tgk'],
['tl', 'tgl'],
['th', 'tha'],
['ti', 'tir'],
['to', 'ton'],
['tn', 'tsn'],
['ts', 'tso'],
['tk', 'tuk'],
['tr', 'tur'],
['tw', 'twi'],
['ug', 'uig'],
['uk', 'ukr'],
['ur', 'urd'],
['uz', 'uzb'],
['ve', 'ven'],
['vi', 'vie'],
['vo', 'vol'],
['wa', 'wln'],
['wo', 'wol'],
['xh', 'xho'],
['yi', 'yid'],
['yo', 'yor'],
['za', 'zha'],
['zh', 'zho'],
['zu', 'zul'],
]);
export const ISO6393To1 = new Map([
['aar', 'aa'],
['abk', 'ab'],
['afr', 'af'],
['aka', 'ak'],
['amh', 'am'],
['ara', 'ar'],
['arg', 'an'],
['asm', 'as'],
['ava', 'av'],
['ave', 'ae'],
['aym', 'ay'],
['aze', 'az'],
['bak', 'ba'],
['bam', 'bm'],
['bel', 'be'],
['ben', 'bn'],
['bis', 'bi'],
['bod', 'bo'],
['bos', 'bs'],
['bre', 'br'],
['bul', 'bg'],
['cat', 'ca'],
['ces', 'cs'],
['cha', 'ch'],
['che', 'ce'],
['chu', 'cu'],
['chv', 'cv'],
['cor', 'kw'],
['cos', 'co'],
['cre', 'cr'],
['cym', 'cy'],
['dan', 'da'],
['deu', 'de'],
['div', 'dv'],
['dzo', 'dz'],
['ell', 'el'],
['eng', 'en'],
['epo', 'eo'],
['est', 'et'],
['eus', 'eu'],
['ewe', 'ee'],
['fao', 'fo'],
['fas', 'fa'],
['fij', 'fj'],
['fin', 'fi'],
['fre', 'fr'],
['fry', 'fy'],
['ful', 'ff'],
['gla', 'gd'],
['gle', 'ga'],
['glg', 'gl'],
['glv', 'gv'],
['grn', 'gn'],
['guj', 'gu'],
['hat', 'ht'],
['hau', 'ha'],
['hbs', 'sh'],
['heb', 'he'],
['her', 'hz'],
['hin', 'hi'],
['hmo', 'ho'],
['hrv', 'hr'],
['hun', 'hu'],
['hye', 'hy'],
['ibo', 'ig'],
['ido', 'io'],
['iii', 'ii'],
['iku', 'iu'],
['ile', 'ie'],
['ina', 'ia'],
['ind', 'id'],
['ipk', 'ik'],
['isl', 'is'],
['ita', 'it'],
['jav', 'jv'],
['jpn', 'ja'],
['kal', 'kl'],
['kan', 'kn'],
['kas', 'ks'],
['kat', 'ka'],
['kau', 'kr'],
['kaz', 'kk'],
['khm', 'km'],
['kik', 'ki'],
['kin', 'rw'],
['kir', 'ky'],
['kom', 'kv'],
['kon', 'kg'],
['kor', 'ko'],
['kua', 'kj'],
['kur', 'ku'],
['lao', 'lo'],
['lat', 'la'],
['lav', 'lv'],
['lim', 'li'],
['lin', 'ln'],
['lit', 'lt'],
['ltz', 'lb'],
['lub', 'lu'],
['lug', 'lg'],
['mah', 'mh'],
['mal', 'ml'],
['mar', 'mr'],
['mkd', 'mk'],
['mlg', 'mg'],
['mlt', 'mt'],
['mon', 'mn'],
['mri', 'mi'],
['msa', 'ms'],
['mya', 'my'],
['nau', 'na'],
['nav', 'nv'],
['nbl', 'nr'],
['nde', 'nd'],
['ndo', 'ng'],
['nep', 'ne'],
['nld', 'nl'],
['nno', 'nn'],
['nob', 'nb'],
['nor', 'no'],
['nya', 'ny'],
['oci', 'oc'],
['oji', 'oj'],
['ori', 'or'],
['orm', 'om'],
['oss', 'os'],
['pan', 'pa'],
['pli', 'pi'],
['pol', 'pl'],
['por', 'pt'],
['pus', 'ps'],
['que', 'qu'],
['roh', 'rm'],
['ron', 'ro'],
['run', 'rn'],
['rus', 'ru'],
['sag', 'sg'],
['san', 'sa'],
['sin', 'si'],
['slk', 'sk'],
['slv', 'sl'],
['sme', 'se'],
['smo', 'sm'],
['sna', 'sn'],
['snd', 'sd'],
['som', 'so'],
['sot', 'st'],
['spa', 'es'],
['sqi', 'sq'],
['srd', 'sc'],
['srp', 'sr'],
['ssw', 'ss'],
['sun', 'su'],
['swa', 'sw'],
['swe', 'sv'],
['tah', 'ty'],
['tam', 'ta'],
['tat', 'tt'],
['tel', 'te'],
['tgk', 'tg'],
['tgl', 'tl'],
['tha', 'th'],
['tir', 'ti'],
['ton', 'to'],
['tsn', 'tn'],
['tso', 'ts'],
['tuk', 'tk'],
['tur', 'tr'],
['twi', 'tw'],
['uig', 'ug'],
['ukr', 'uk'],
['urd', 'ur'],
['uzb', 'uz'],
['ven', 've'],
['vie', 'vi'],
['vol', 'vo'],
['wln', 'wa'],
['wol', 'wo'],
['xho', 'xh'],
['yid', 'yi'],
['yor', 'yo'],
['zha', 'za'],
['zho', 'zh'],
['zul', 'zu'],
]);

View file

@ -1,10 +0,0 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "../config/tsconfig.base.json",
"includes": [
"*.ts",
"**/*.ts",
"*.js",
"**/*.js"
]
}

View file

@ -1,39 +0,0 @@
{
runtimeInputs,
npmDepsHash,
src,
lib,
buildNpmPackage,
makeWrapper,
nodejs_latest,
jq,
...
}: let
inherit (lib) concatMapStringsSep;
inherit (builtins) fromJSON readFile;
packageJSON = fromJSON (readFile "${src}/package.json");
in
buildNpmPackage rec {
pname = packageJSON.name;
inherit (packageJSON) version;
inherit src runtimeInputs npmDepsHash;
prePatch = ''
mv ./tsconfig.json ./project.json
sed 's/^ *\/\/.*//' ${../config/tsconfig.json} > ./base.json
${jq}/bin/jq -sr '.[0] * .[1] | del(.extends)' ./project.json ./base.json > ./tsconfig.json
rm base.json project.json
'';
nativeBuildInputs = [makeWrapper];
postInstall = ''
wrapProgram $out/bin/${pname} \
--prefix PATH : ${concatMapStringsSep ":" (p: p + "/bin") runtimeInputs}
'';
nodejs = nodejs_latest;
meta.mainProgram = pname;
}

View file

@ -1,14 +0,0 @@
{
pkgs,
self,
...
}: let
inherit (pkgs.lib) mapAttrs removeSuffix;
in
mapAttrs (
name: app: (pkgs.symlinkJoin {
name = "app-${name}";
paths = [(removeSuffix "/bin/${name}" (toString app.program))];
})
)
(removeAttrs self.apps.${pkgs.system} ["genflake"])

View file

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

View file

@ -1,19 +0,0 @@
{
buildApp,
callPackage,
nix-update,
nodejs_latest,
prefetch-npm-deps,
...
}:
buildApp {
src = ./.;
npmDepsHash = "sha256-vIhR/+Pgj35sv/ZX1iL+xWheu6w7vQeJy4OEfa8tXFw=";
runtimeInputs = [
nix-update
nodejs_latest
prefetch-npm-deps
(callPackage ../../modules/docker/updateImage.nix {})
];
}

View file

@ -1,3 +0,0 @@
import eslintConf from 'eslint-conf';
export default eslintConf;

Binary file not shown.

View file

@ -1,18 +0,0 @@
{
"name": "update-sources",
"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"
},
"devDependencies": {
"eslint-conf": "file:../config",
"@types/node": "22.10.2",
"esbuild": "0.24.2",
"eslint": "9.17.0",
"jiti": "2.4.2",
"pkg-types": "1.2.1",
"typescript": "5.7.2"
}
}

View file

@ -1,153 +0,0 @@
import { spawnSync } from 'node:child_process';
import { writeFileSync } from 'node:fs';
import { parseArgs } from './lib';
import { updateDocker } from './docker';
import { updateFirefoxAddons } from '././firefox';
import { updateFlakeInputs } from './flake';
import updateNodeModules from './node-modules';
import {
runNixUpdate,
updateCustomPackage,
updateVuetorrent,
} from './misc';
/* Constants */
const FLAKE = process.env.FLAKE;
if (!FLAKE) {
console.error('Env var FLAKE not found');
process.exit(1);
}
const args = parseArgs();
const main = async() => {
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['c'] || args['custom-sidebar']) {
console.log(updateCustomPackage(
'scopedPackages.x86_64-linux.lovelace-components.custom-sidebar',
));
}
if (args['s'] || args['some-sass-language-server']) {
console.log(updateCustomPackage('some-sass-language-server'));
}
if (args['n'] || args['node_modules']) {
console.log(await updateNodeModules());
}
if (args['h'] || args['homepage']) {
console.log(runNixUpdate('homepage'));
}
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 nodeModulesOutput = await updateNodeModules();
console.log(nodeModulesOutput);
const vuetorrentOutput = updateVuetorrent();
console.log(vuetorrentOutput);
// This doesn't need to be added to commit msgs
console.log(updateCustomPackage(
'scopedPackages.x86_64-linux.lovelace-components.custom-sidebar',
));
console.log(updateCustomPackage('some-sass-language-server'));
// nix-update executions
let nixUpdateOutputs = '';
const updatePackage = (pkg: string): void => {
const execution = runNixUpdate(pkg);
nixUpdateOutputs += execution.stdout;
console.log(execution.stderr);
console.log(execution.stdout);
};
updatePackage('homepage');
spawnSync('nixFastBuild', [], {
shell: true,
stdio: [process.stdin, process.stdout, process.stderr],
});
const indentOutput = (output: string): string => {
return ` ${output.split('\n').join('\n ')}`;
};
const output = [
'chore: update sources\n\n',
];
if (flakeOutput.length > 5) {
output.push(`Flake Inputs:\n${indentOutput(flakeOutput)}\n\n`);
}
if (dockerOutput.length > 5) {
output.push(`Docker Images:\n${indentOutput(dockerOutput)}\n`);
}
if (firefoxOutput.length > 5) {
output.push(`Firefox Addons:\n${indentOutput(firefoxOutput)}\n\n`);
}
if (nodeModulesOutput.length > 5) {
output.push(`Node modules:\n${indentOutput(nodeModulesOutput)}\n`);
}
if (vuetorrentOutput.length > 5) {
output.push(`Misc Sources:\n${indentOutput(vuetorrentOutput)}\n\n`);
}
if (nixUpdateOutputs.length > 5) {
output.push(`nix-update executions:\n${indentOutput(nixUpdateOutputs)}\n`);
}
if (args['f']) {
writeFileSync(args['f'] as string, output.join(''));
}
else {
console.log(output.join(''));
}
}
spawnSync('alejandra', ['-q', FLAKE], { shell: true });
};
main();

View file

@ -1,33 +0,0 @@
import { readdirSync } from 'node:fs';
import { spawnSync } from 'node:child_process';
/* Constants */
const FLAKE = process.env.FLAKE;
const updateImages = (imagePath: string): string | undefined => {
console.log(`Updating ${imagePath.split('/').at(-1)} images`);
const out = spawnSync('updateImages', [imagePath], { shell: true }).stdout.toString();
if (!out.startsWith('# Locked')) {
return out;
}
};
export const updateDocker = () => {
let updates = '';
updates += updateImages(`${FLAKE}/configurations/nos/modules/jellyfin`) ?? '';
updates += updateImages(`${FLAKE}/configurations/homie/modules/home-assistant/netdaemon`) ?? '';
const DIR = `${FLAKE}/configurations/nos/modules/docker`;
readdirSync(DIR, { withFileTypes: true, recursive: true }).forEach((path) => {
if (path.name === 'compose.nix') {
updates += updateImages(path.parentPath) ?? '';
}
});
return updates;
};

View file

@ -1,61 +0,0 @@
import { spawnSync } from 'node:child_process';
import { readFileSync } from 'node:fs';
/* Constants */
const FLAKE = process.env.FLAKE;
export const updateFirefoxAddons = () => {
console.log('Updating firefox addons using mozilla-addons-to-nix');
const DIR = `${FLAKE}/scopedPackages/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',
'.#scopedPackages.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,44 +0,0 @@
import { spawnSync } from 'node:child_process';
/* 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" |& grep -v "unpacking"',
[],
{ shell: true },
).stdout
.toString()
// Add an extra blank line between inputs
.split('\n•')
// Filter out some inputs
.filter((input) => ![
'systems',
'flake-utils',
'flake-parts',
'treefmt-nix',
'lib-aggregate',
'lib-aggregate/nixpkgs-lib',
'sops-nix/nixpkgs-stable',
'discord-overlay/Vencord-src',
'nix-gaming/umu',
].some((inputName) => input.startsWith(` Updated input '${inputName}'`)))
.join('\n\n•')
// help readability of git revs
.split('\n')
.map((l) => l
.replace(
/\/(.{40})\?narHash=sha256[^']*(.*)/,
(_, backref1, backref2) => `${backref2} rev: ${backref1}`,
)
.replace(
/\?ref.*&rev=(.{40})[^'&]*(.*)/,
(_, backref1, backref2) => `${backref2} rev: ${backref1}`,
))
.join('\n');
return output;
};

View file

@ -1,43 +0,0 @@
import { spawnSync } from 'node:child_process';
import { readFileSync, writeFileSync } from 'node:fs';
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;
export const replaceInFile = (replace: RegExp, replacement: string, file: string) => {
const fileContents = readFileSync(file);
const replaced = fileContents.toString().replace(replace, replacement);
writeFileSync(file, replaced);
};
export const npmRun = (args: string[], workspaceDir: string) => spawnSync(
'npm', args, { cwd: workspaceDir },
).stdout.toString();

View file

@ -1,73 +0,0 @@
import { writeFileSync } from 'node:fs';
import { spawnSync } from 'node:child_process';
import { parseFetchurl } from './lib';
/* Constants */
const FLAKE = process.env.FLAKE;
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}/configurations/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}` : '';
};
export const updateCustomPackage = (pkg: string) => spawnSync(
`nix run ${FLAKE}#${pkg}.update`,
[],
{ shell: true },
).stderr.toString();
const getAttrVersion = (attr: string): string => spawnSync('nix',
['eval', '--raw', `${FLAKE}#${attr}.version`],
{ shell: true }).stdout.toString();
export const runNixUpdate = (
attr: string,
options: string[] = [],
): { stdout: string, stderr: string } => {
const OLD_VERSION = getAttrVersion(attr);
const execution = spawnSync(
`nix-update --flake ${attr} --write-commit-message >(head -n 1 -) > /dev/null`,
options,
{ shell: true, cwd: FLAKE },
);
const NEW_VERSION = getAttrVersion(attr);
return {
stdout: OLD_VERSION !== NEW_VERSION ? execution.stdout.toString() : '',
stderr: execution.stderr.toString(),
};
};

View file

@ -1,82 +0,0 @@
import { readPackageJSON, writePackageJSON } from 'pkg-types';
import { readdirSync } from 'node:fs';
import { spawnSync } from 'node:child_process';
import { replaceInFile, npmRun } from './lib';
/* Constants */
const FLAKE = process.env.FLAKE as string;
const updatePackageJson = async(workspaceDir: string, updates: object) => {
const currentPackageJson = await readPackageJSON(`${workspaceDir}/package.json`);
const outdated = JSON.parse(npmRun(['outdated', '--json'], workspaceDir));
const updateDeps = (deps: string) => {
Object.keys(currentPackageJson[deps]).forEach((dep) => {
const versions = outdated[dep];
const current = versions?.wanted || versions?.current;
if (!current) {
return;
}
if (current !== versions.latest) {
updates[dep] = `${current} -> ${versions.latest}`;
}
currentPackageJson[deps][dep] = versions.latest;
});
};
if (currentPackageJson.dependencies) {
updateDeps('dependencies');
}
if (currentPackageJson.devDependencies) {
updateDeps('devDependencies');
}
await writePackageJSON(`${workspaceDir}/package.json`, currentPackageJson);
};
const prefetchNpmDeps = (workspaceDir: string): string => {
npmRun(['install', '--package-lock-only'], workspaceDir);
return spawnSync(
'prefetch-npm-deps',
[`${workspaceDir}/package-lock.json`],
).stdout.toString().replace('\n', '');
};
export default async() => {
const updates = {};
const packages = readdirSync(FLAKE, { withFileTypes: true, recursive: true });
for (const path of packages) {
if (
path.name === 'package.json' &&
!path.parentPath.includes('node_modules') &&
!path.parentPath.includes('apps/config')
) {
await updatePackageJson(path.parentPath, updates);
const hash = prefetchNpmDeps(path.parentPath);
replaceInFile(
/npmDepsHash = ".*";/,
`npmDepsHash = "${hash}";`,
`${path.parentPath}/default.nix`,
);
}
}
return Object.entries(updates)
.map(([key, dep]) => `${key}: ${dep}`)
.join('\n');
};

View file

@ -1,10 +0,0 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "../config/tsconfig.base.json",
"includes": [
"*.ts",
"**/*.ts",
"*.js",
"**/*.js"
]
}

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,
],
});

View file

@ -0,0 +1,80 @@
const { Window, Box, Label, Revealer, Icon } = ags.Widget;
const { Mpris } = ags.Service;
const { ToggleButton } = imports.gi.Gtk;
import { ButtonGrid } from './button-grid.js';
import { SliderBox } from './slider-box.js';
import Player from '../media-player/player.js';
import { EventBox } from '../misc/cursorbox.js';
import { PopUp } from '../misc/popup.js';
const QuickSettingsWidget = Box({
className: 'qs-container',
vertical: true,
children: [
Box({
className: 'quick-settings',
vertical: true,
children: [
Label({
label: 'Control Center',
className: 'title',
halign: 'start',
style: 'margin-left: 20px'
}),
ButtonGrid,
SliderBox,
EventBox({
child: ags.Widget({
type: ToggleButton,
setup: btn => {
const id = Mpris.instance.connect('changed', () => {
btn.set_active(Mpris.players.length > 0);
Mpris.instance.disconnect(id);
});
},
connections: [['toggled', button => {
if (button.get_active()) {
button.child.setStyle("-gtk-icon-transform: rotate(0deg);");
button.get_parent().get_parent().get_parent().children[1].revealChild = true;
}
else {
button.child.setStyle('-gtk-icon-transform: rotate(180deg);');
button.get_parent().get_parent().get_parent().children[1].revealChild = false;
}
}]],
child: Icon({
icon: 'folder-download-symbolic',
className: 'arrow',
style: `-gtk-icon-transform: rotate(180deg);`,
}),
}),
}),
],
}),
Revealer({
transition: 'slide_down',
child: Player(),
})
],
});
export const QuickSettings = Window({
name: 'quick-settings',
layer: 'overlay',
anchor: 'top right',
popup: true,
margin: [ 8, 5, 0, ],
child: PopUp({
name: 'quick-settings',
child: QuickSettingsWidget,
}),
});

View file

@ -0,0 +1,91 @@
const { Box, Slider, Icon, EventBox } = ags.Widget;
const { Audio } = ags.Service;
const { execAsync } = ags.Utils;
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',
};
export const SliderBox = Box({
className: 'slider-box',
vertical: true,
halign: 'center',
children: [
Box({
className: 'slider',
valign: 'start',
halign: 'center',
children: [
Icon({
size: 26,
className: 'slider-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']],
}),
Slider({
connections: [[Audio, slider => {
if (Audio.speaker) {
slider.value = Audio.speaker.volume;
}
}, 'speaker-changed']],
onChange: ({ value }) => Audio.speaker.volume = value,
max: 0.999,
draw_value: false,
}),
],
}),
Box({
className: 'slider',
valign: 'start',
halign: 'center',
children: [
Icon({
className: 'slider-label',
icon: 'display-brightness-symbolic',
}),
EventBox({
onHover: box => box.child._canChange = false,
onHoverLost: box => box.child._canChange = true,
child: Slider({
properties: [
['canChange', true],
],
onChange: ({ value }) => {
execAsync(`brightnessctl set ${value}`).catch(print);
},
connections: [[1000, slider => {
if (slider._canChange) {
execAsync('brightnessctl get').then(out => slider.value = out).catch(print);
}
}]],
min: 0,
max: 255,
draw_value: false,
}),
}),
],
}),
],
});

View file

@ -0,0 +1,32 @@
$darkbg: #0b0d16;
$bg: rgba(40, 42, 54, 0.8); //rgba(69, 71, 90, 0.3); #0d0f18;
$bgfull: rgb(40, 42, 54);
$contrastbg: rgba(189, 147, 249, 0.8);
$bgSecondary: rgba(#382c4a, 0.8);
$bgSecondaryAlt: #a5b6cf;
$fg: #a5b6cf;
$fgDim: #a5b6cf;
$watermelon: #dd6777;
// Aliases
$background: $bg;
$backgroundSecondary: $bgSecondary;
$backgroundSecondaryAlt: $bgSecondaryAlt;
$foreground: $fg;
$foregroundDim: $fgDim;
$black: #151720;
$dimblack: #1a1c25;
$lightblack: #262831;
$red: #dd6777;
$blue: #86aaec;
$cyan: #93cee9;
$blue-desaturated: #93cee9;
$magenta: #c296eb;
$purple: #c296eb;
$green: #90ceaa;
$aquamarine: #90ceaa;
$yellow: #ecd3a0;
$accent: $blue;
$javacafeMagenta: #c296eb;
$javacafeBlue: #86aaec;

16
config/ags/scss/main.scss Normal file
View file

@ -0,0 +1,16 @@
* {
all: unset;
}
@import "./common.scss";
@import "./widgets/powermenu.scss";
@import "./widgets/traybuttons.scss";
@import "./widgets/workspaces.scss";
@import "./widgets/systray.scss";
@import "./widgets/notification-center.scss";
@import "./widgets/notification.scss";
@import "./widgets/date.scss";
@import "./widgets/quick-settings.scss";
@import "./widgets/player.scss";
@import "./widgets/overview.scss";
@import "./widgets/applauncher.scss";

View file

@ -0,0 +1,113 @@
.applauncher {
all: unset;
box-shadow: 0 0 4.5px 0 rgba(0, 0, 0, 0.6);
margin: 9px;
border: 2px solid $contrastbg;
border-radius: 25px;
background-color: $bg;
color: #f8f8f2;
padding: 16.2px;
* {
font-size: 16px;
}
.header {
margin: 16.2px;
margin-bottom: 0;
image, entry {
all: unset;
border-radius: 9px;
color: #f8f8f2;
background-color: rgba(#44475a, 0.6);
border: 1px solid #44475a;
padding: 4.5px;
}
image {
margin-right: 9px;
-gtk-icon-transform: scale(0.8);
font-size: 25.6px;
}
}
scrolledwindow {
padding: 16.2px;
min-width: 700px;
min-height: 450px;
scrollbar, scrollbar * {
all: unset;
}
scrollbar.vertical {
transition: 200ms;
background-color: rgba(23, 23, 23, 0.3);
&:hover {
background-color: rgba(23, 23, 23, 0.7);
slider {
background-color: rgba(238, 238, 238, 0.7);
min-width: .6em;
}
}
slider {
background-color: rgba(238, 238, 238, 0.5);
border-radius: 9px;
min-width: .4em;
min-height: 2em;
transition: 200ms;
}
}
}
.placeholder {
margin-top: 9px;
color: #f8f8f2;
font-size: 1.2em;
}
.app {
all: unset;
transition: 200ms;
padding: 9px;
&:active {
background-color: rgba($contrastbg, 0.5);
border-radius: 9px;
box-shadow: inset 0 0 0 3px rgba(238, 238, 238, 0.03);
.title {
color: #f8f8f2;
}
}
&:hover, &:focus {
.title {
color: $contrastbg;
}
image {
-gtk-icon-shadow: 2px 2px $contrastbg;
}
}
label {
transition: 200ms;
&.title {
color: #f8f8f2;
}
&.description {
color: rgba(238, 238, 238, 0.7);
}
}
image {
transition: 200ms;
margin-right: 9px;
}
}
}

View file

@ -0,0 +1,76 @@
.date {
background-color: $bg;
color: $fg;
border-radius: 30px;
border-top-right-radius: 0px;
border: 2px solid $contrastbg;
}
.timebox {
margin: 30px 0px;
.time-container {
.content {
font-family: Product Sans;
font-weight: bolder;
font-size: 60px;
}
.divider {
margin: 8px 15px;
padding: 0px 1px;
background: linear-gradient($red, $magenta, $blue, $cyan);
}
}
.date-container {
font-family: Product Sans;
margin-top: 2px;
}
}
.cal-box {
font-family: Product Sans;
border-radius: 30px;
padding: 0 1rem .2rem;
color: $fg;
background-color: $bgfull;
border-bottom: 2px solid $contrastbg;
border-top: 2px solid $contrastbg;
margin: 0px 12px 18px 12px;
.cal {
font-size: 20px;
background-color: inherit;
padding: .5rem .10rem 0rem;
margin-left: 10px;
border-radius: 30px;
& > * {
border: solid 0px transparent;
}
&.highlight {
padding: 10rem;
}
}
}
calendar:selected {
color: $cyan;
}
calendar.header {
color: $cyan;
font-weight: bold;
}
calendar.button {
color: $cyan;
}
calendar.highlight {
color: $green;
font-weight: bold;
}
calendar:indeterminate {
color: $lightblack;
}

View file

@ -0,0 +1,100 @@
.notification-center {
min-height: 700px;
min-width: 524px;
background: $bg;
border-radius: 30px;
border-top-right-radius: 0px;
border: 2px solid $contrastbg;
padding: 0px;
box-shadow: 0 0 4.5px 0 rgba(0, 0, 0, 0.6);
* {
font-size: 16px;
}
.header {
padding: 10px;
margin-top: 22px;
margin-bottom: 9px;
label {
font-size: 22px;
}
button {
all: unset;
transition: 200ms;
border-radius: 9px;
color: #eee;
background-color: #664C90;
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
padding: 4.5px 9px;
&:hover {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
background-color: rgba(238, 238, 238, 0.154);
color: #f1f1f1;
}
&:disabled {
box-shadow: none;
background-color: rgba(#664C90, 0.3);
color: rgba(238, 238, 238, 0.3);
}
label {
font-size: 1.2em;
}
}
}
.notification-list-box {
background: $bgfull;
padding: 0 12px 0 12px;
border-radius: 30px;
border-top: 2px solid $contrastbg;
box-shadow: 0 0 6px 0 rgba(0, 0, 0, 0.5);
scrollbar {
all: unset;
border-radius: 8px;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
* {
all: unset;
}
&:hover {
border-radius: 8px;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
}
scrollbar.vertical {
transition: 200ms;
background-color: rgba(23, 23, 23, 0.3);
&:hover {
background-color: rgba(23, 23, 23, 0.7);
slider {
background-color: rgba(238, 238, 238, 0.7);
min-width: .6em;
}
}
slider {
background-color: rgba(238, 238, 238, 0.5);
border-radius: 9px;
min-width: .4em;
min-height: 2em;
transition: 200ms;
}
}
}
.placeholder {
color: white;
image {
font-size: 7em;
text-shadow: 2px 2px 2px rgba(0, 0, 0, 0.6);
-gtk-icon-shadow: 2px 2px 2px rgba(0, 0, 0, 0.6);
}
label {
font-size: 1.2em;
text-shadow: 2px 2px 2px rgba(0, 0, 0, 0.6);
-gtk-icon-shadow: 2px 2px 2px rgba(0, 0, 0, 0.6);
}
}
}

View file

@ -0,0 +1,179 @@
$background-color_1: rgba(238, 238, 238, 0.154);
$background-color_2: rgba(230, 112, 144, 0.5);
$background-color_3: rgba(238, 238, 238, 0.06);
$background-color_4: #51a4e7;
$background-color_5: transparent;
$background-color_6: #171717;
$background-color_7: rgba(23, 23, 23, 0.3);
$background-color_8: rgba(23, 23, 23, 0.7);
$background-color_9: rgba(238, 238, 238, 0.7);
$background-color_10: rgba(238, 238, 238, 0.5);
.notification.critical {
>box {
box-shadow: inset 0 0 0.5em 0 #e67090;
}
}
.notification {
>box {
all: unset;
box-shadow: 0 0 4.5px 0 rgba(0, 0, 0, 0.4);
margin: 9px 9px 0 9px;
border: 2px solid $contrastbg;
border-radius: 15px;
background-color: $bg;
padding: 16.2px;
* {
font-size: 16px;
}
}
&:hover {
.close-button {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
background-color: $background-color_1;
background-color: $background-color_2;
}
}
.title {
margin-right: 9px;
font-size: 1.1em;
}
.description {
font-size: .9em;
min-width: 350px;
}
.icon {
border-radius: 7.2px;
margin-right: 9px;
}
.icon.img {
border: 1px solid rgba(238, 238, 238, 0.03);
}
.actions {
button {
all: unset;
transition: all 500ms;
border-radius: 9px;
background-color: $background-color_3;
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
border-radius: 7.2px;
font-size: 1.2em;
padding: 4.5px 9px;
margin: 9px 4.5px 0;
* {
font-size: 16px;
}
&:focus {
box-shadow: inset 0 0 0 1px #51a4e7;
background-color: $background-color_1;
}
&:hover {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
background-color: $background-color_1;
}
&:active {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
background-image: linear-gradient(to right, #51a4e7, #6cb2eb);
background-color: $background-color_4;
&:hover {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03), inset 0 0 0 99px rgba(238, 238, 238, 0.154);
}
}
&:checked {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
background-image: linear-gradient(to right, #51a4e7, #6cb2eb);
background-color: $background-color_4;
&:hover {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03), inset 0 0 0 99px rgba(238, 238, 238, 0.154);
}
}
&:disabled {
box-shadow: none;
background-color: $background-color_5;
}
&:first-child {
margin-left: 0;
}
&:last-child {
margin-right: 0;
}
}
button.on {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
background-image: linear-gradient(to right, #51a4e7, #6cb2eb);
background-color: $background-color_4;
&:hover {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03), inset 0 0 0 99px rgba(238, 238, 238, 0.154);
}
}
button.active {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
background-image: linear-gradient(to right, #51a4e7, #6cb2eb);
background-color: $background-color_4;
&:hover {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03), inset 0 0 0 99px rgba(238, 238, 238, 0.154);
}
}
}
button.close-button {
all: unset;
transition: all 500ms;
border-radius: 9px;
background-color: $background-color_5;
background-image: none;
box-shadow: none;
margin-left: 9px;
border-radius: 7.2px;
min-width: 1.2em;
min-height: 1.2em;
* {
font-size: 16px;
}
&:focus {
box-shadow: inset 0 0 0 1px #51a4e7;
background-color: $background-color_1;
}
&:hover {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
background-color: $background-color_1;
background-color: $background-color_2;
}
&:active {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
background-image: linear-gradient(to right, #51a4e7, #6cb2eb);
background-color: $background-color_4;
background-image: linear-gradient(#e67090, #e67090);
&:hover {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03), inset 0 0 0 99px rgba(238, 238, 238, 0.154);
}
}
&:checked {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
background-image: linear-gradient(to right, #51a4e7, #6cb2eb);
background-color: $background-color_4;
&:hover {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03), inset 0 0 0 99px rgba(238, 238, 238, 0.154);
}
}
&:disabled {
box-shadow: none;
background-color: $background-color_5;
}
}
button.close-button.on {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
background-image: linear-gradient(to right, #51a4e7, #6cb2eb);
background-color: $background-color_4;
&:hover {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03), inset 0 0 0 99px rgba(238, 238, 238, 0.154);
}
}
button.close-button.active {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
background-image: linear-gradient(to right, #51a4e7, #6cb2eb);
background-color: $background-color_4;
&:hover {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03), inset 0 0 0 99px rgba(238, 238, 238, 0.154);
}
}
}

View file

@ -0,0 +1,52 @@
.overview {
.workspace {
padding: 4px 15px 4px 0px;
border: 2px solid transparent;
border-radius: 10px;
&.active {
background-color: rgba(lighten($color: black, $amount: 15), 0.8);
border: 2px solid black;
}
}
.workspace .window {
background-color: $bgfull;
border-radius: 10px;
margin: 0 10px;
transition: min-width 0.2s ease-in-out,
min-height 0.2s ease-in-out,
border-color 0.2s ease-in-out,
font-size 0.2s ease-in-out;
}
.normal {
margin-bottom: 5px;
.workspace {
.window {
border: 2px solid #411C6C;
&.active {
border: 2px solid purple;
}
}
}
}
.special {
.workspace {
.window {
border: 2px solid lighten($color: black, $amount: 20);
&.active {
border: 2px solid purple;
}
}
}
}
}

View file

@ -0,0 +1,111 @@
.arrow {
transition: -gtk-icon-transform 0.3s ease-in-out;
margin-bottom: 12px;
}
.media {
margin-top: 9px;
}
.player {
all: unset;
padding: 10px;
min-width: 400px;
min-height: 200px;
border-radius: 30px;
border-top: 2px solid $contrastbg;
border-bottom: 2px solid $contrastbg;
transition: background 250ms;
.top {
font-size: 23px;
}
.metadata {
.title{
font-weight: 500;
transition: text 250ms;
}
.artist{
font-weight: 400;
font-size: 15px;
transition: text 250ms;
}
}
.bottom {
font-size: 30px;
}
.pausebutton {
transition: background-color ease .2s,
color ease .2s;
font-size: 15px;
padding: 4px 4px 4px 7px;
}
.playing {
transition: background-color ease .2s,
color ease .2s;
border-radius: 15px;
}
.stopped,
.paused {
transition: background-color ease .2s,
color ease .2s;
border-radius: 26px;
padding: 4px 4px 4px 10px;
}
button label {
min-width: 35px;
}
}
.position-indicator {
min-width: 18px;
margin: 7px;
background-color: rgba(255, 255, 255, 0.7);
box-shadow: 0 0 5px 0px rgba(255, 255, 255, 0.3);
border-radius: 100%;
}
.previous,
.next,
.shuffle,
.loop {
border-radius: 100%;
transition: color 200ms;
&:hover {
border-radius: 100%;
background-color: rgba(127, 132, 156, 0.4);
transition: color 200ms;
}
}
.loop {
label {
padding-right: 8px;
}
}
.position-slider {
highlight {
margin: 0px;
border-radius: 2em;
}
trough {
margin: 0 8px;
border-radius: 2em;
}
slider {
margin: -8px;
min-height: 20px;
border-radius: 10px;
box-shadow: rgba(0, 0, 0, 0.25) 0px 54px 55px, rgba(0, 0, 0, 0.12) 0px -12px 30px, rgba(0, 0, 0, 0.12) 0px 4px 6px, rgba(0, 0, 0, 0.17) 0px 12px 13px, rgba(0, 0, 0, 0.09) 0px -3px 5px;
transition: background-color 0.5s ease-in-out;
}
slider:hover {
transition: background-color 0.5s ease-in-out;
}
}

View file

@ -0,0 +1,36 @@
.powermenu {
background-color: $bg;
color: $fg;
padding: 10px;
font-family: MesloLGS NF;
/*font-family: Iosevka Nerd Font;*/
font-size: 70px;
border-radius: 30px;
border: 2px solid $contrastbg;
label {
min-width: 140px;
min-height: 130px;
}
button {
margin-right: 10px;
margin-left: 10px;
margin-top: 5px;
margin-bottom: 5px;
border-radius: 12px;
min-width: 80px;
transition: all ease .2s;
&:hover { background-color: $bgSecondary; }
&:active { background-color: $bgSecondary; }
.content {
border-radius: 4px;
padding: 0px 15px 0px 15px;
}
}
.shutdown { color: $red; }
.reboot { color: $magenta; }
.logout { color: $yellow; }
}
.powermenu-clickhandler {
background-color: black;
}

View file

@ -0,0 +1,162 @@
.quick-settings {
font-size: 30px;
min-width: 500px;
padding: 0px 0px 0px 0px;
background-color: $bg;
border-radius: 30px 0 30px 30px;
border: 2px solid $contrastbg;
}
.title {
font-size: 22px;
margin-top: 30px;
}
.grid-label {
font-size: 30px;
margin-left: 15px;
margin-right: 10px;
min-width: 50px;
}
.sub-label {
font-size: 14px;
margin-top: -10px;
margin-left: 31px;
&:nth-child(1) {
margin-left: 25px;
}
margin-bottom: 10px;
padding: 3px;
border: 2px solid $contrastbg;
border-top-right-radius: 20px;
border-top-left-radius: 10px;
border-bottom-left-radius: 10px;
border-bottom-right-radius: 20px;
min-width: 106px;
background: #1b1b1b;
}
.grid-chev {
margin-left: 0px;
font-size: 40px;
margin-right: 5px;
}
.button-grid {
font-size: 10px;
min-height: 160px;
min-width: 470px;
background-color: $bgfull;
border-top: 2px solid $contrastbg;
border-bottom: 2px solid $contrastbg;
border-radius: 15px;
margin-top: 30px;
}
.button-row {
min-height: 70px;
min-width: 450px;
margin-left: 20px;
}
.grid-button {
min-height: 65px;
min-width: 70px;
margin: 5px;
margin-left: 22px;
&:nth-child(1) {
margin-left: 5px;
}
}
.left-part {
background: #1b1b1b;
border-top-left-radius: 15px;
border-bottom-left-radius: 15px;
border-left: 2px solid $contrastbg;
border-top: 2px solid $contrastbg;
border-bottom: 2px solid $contrastbg;
transition: all 0.5s ease-in-out;
}
.right-part {
background: #1b1b1b;
border-top-right-radius: 30px;
border-bottom-right-radius: 30px;
border-right: 2px solid $contrastbg;
border-top: 2px solid $contrastbg;
border-bottom: 2px solid $contrastbg;
transition: all 0.5s ease-in-out;
}
.right-part:hover, .right-part:active {
color: $contrastbg;
border: 2px solid $contrastbg;
border-top-left-radius: 7px;
border-bottom-left-radius: 7px;
transition: all 0.5s ease-in-out;
}
.left-part:hover, .left-part:active {
color: $contrastbg;
border: 2px solid $contrastbg;
border-top-right-radius: 7px;
border-bottom-right-radius: 7px;
transition: all 0.5s ease-in-out;
}
.player {
margin-top: 6px;
min-height: 220px;
opacity: 0;
}
.slider-box {
min-height: 100px;
min-width: 470px;
background-color: $bgfull;
border-top: 2px solid $contrastbg;
border-bottom: 2px solid $contrastbg;
border-radius: 15px;
margin-top: 30px;
margin-bottom: 20px;
.slider-label {
font-size: 30px;
min-width: 30px;
}
scale {
min-height: 55px;
min-width: 400px;
margin-left: 18px;
margin-right: 20px;
highlight {
margin: 0px;
background-color: #79659f;
border-radius: 2em;
}
trough {
background-color: #363847;
border-radius: 2em;
}
slider {
margin: -4px;
min-width: 20px;
min-height: 20px;
background: #3e4153;
border-radius: 100%;
box-shadow: rgba(0, 0, 0, 0.25) 0px 54px 55px, rgba(0, 0, 0, 0.12) 0px -12px 30px, rgba(0, 0, 0, 0.12) 0px 4px 6px, rgba(0, 0, 0, 0.17) 0px 12px 13px, rgba(0, 0, 0, 0.09) 0px -3px 5px;
transition: background-color 0.5s ease-in-out;
}
slider:hover {
background-color: #303240;
transition: background-color 0.5s ease-in-out;
}
}
}

View file

@ -0,0 +1,54 @@
.sys-tray {
padding: 5px;
background-color: $bg;
color: #CBA6F7;
border-radius: 80px;
border: 2px solid $bgSecondary;
transition: background-color 0.5s ease-in-out,
border 0.5s ease-in-out;
.tray-item {
all: unset;
padding: 0px 2px 0px 2px;
font-size: 25px;
border-radius: 100%;
transition: background-color 0.5s ease-in-out,
border 0.5s ease-in-out;
&:hover {
background: rgba(127, 132, 156, 0.4);
border-radius: 100%;
transition: background-color 0.5s ease-in-out,
border 0.5s ease-in-out;
}
}
menu {
background: $bgfull;
padding-top: 5px;
padding-bottom: 5px;
border: 2px solid #1b1b1b;
border-radius: 10px;
check {
color: white;
margin-left: 2px;
margin-right: 5px;
min-height: 20px;
min-width: 20px;
-gtk-icon-source: -gtk-icontheme('checkbox-symbolic');
&:checked {
-gtk-icon-source: -gtk-icontheme('checkbox-checked-symbolic');
}
}
}
menuitem:not(.tray-item) {
padding: 5px;
transition: all 0.2s ease-in-out;
&:hover {
background: #1b1b1b;
}
}
}

View file

@ -0,0 +1,107 @@
.osk-toggle,
.tablet-toggle,
.heart-toggle {
font-size: 28px;
min-height: 40px;
min-width: 53px;
}
.notif-panel {
font-size: 20px;
border-radius: 80px;
min-height: 37px;
min-width: 105px;
padding: 1px 0px 1px 5px;
.toggle-on {
border-top-left-radius: 22px;
border-top-right-radius: 22px;
border-bottom-left-radius: 0px;
border-bottom-right-radius: 0px;
border-bottom: 0px solid $bg;
}
}
.quick-settings-toggle {
font-size: 24px;
min-height: 40px;
min-width: 40px;
padding-right: 4px;
margin-left: -3px;
}
.toggle-off {
background-color: $bg;
color: #CBA6F7;
border-radius: 80px;
border: 2px solid $bgSecondary;
transition: background-color 0.5s ease-in-out,
border 0.5s ease-in-out;
}
.toggle-on {
background-color: $bg;
color: #CBA6F7;
border-radius: 80px;
border: 2px solid $contrastbg;
transition: background-color 0.5s ease-in-out,
border 0.5s ease-in-out;
}
.toggle-on:hover, .toggle-off:hover {
background-color: rgba(127, 132, 156, 0.4);
transition: background-color 0.5s ease-in-out,
border 0.5s ease-in-out;
}
.clock {
font-size: 20px;
padding-left: 10px;
padding-right: 10px;
min-width: 230px;
}
.audio {
padding: 0 10px 0 10px;
font-size: 20px;
}
.battery {
padding: 0 10px 0 10px;
font-size: 20px;
.battery-indicator {
&.charging {
color: green;
}
&.charged {}
&.low {
color: red;
}
}
icon {
.charging {}
.discharging {}
}
.label {
font-size: 20px;
}
}
.brightness {
trough {
margin-right: -50px;
progress {
margin-right: 50px;
margin-top: -30px;
border-radius: 80px;
min-height: 36px;
background: rgba(#5e497c, 0.8);
}
}
}
tooltip {
background: rgba(0,0,0, 0.6);
border-radius: 5px;
}

View file

@ -0,0 +1,30 @@
.workspaces {
background-color: $bg;
border-radius: 80px;
border: 2px solid $bgSecondary;
padding-top: 3px;
padding-bottom: 3px;
padding-left: 12px;
padding-right: 12px;
.button {
margin: 2px;
min-width: 20px;
border-radius: 100%;
* {color: transparent;}
}
.empty {
border: none;
transition: border-color 0.25s linear;
}
.occupied {
border: 2px solid $bg;
background: $contrastbg;
transition: border-color 0.25s linear;
}
.active {
border: 2px solid #50fa7b;
transition: border-color 0.25s linear;
}
}

View file

@ -0,0 +1,162 @@
/*
import Libinput from './libinput.js';
Libinput.instance.connect('device-init', () => {
let pointers = [];
Libinput.devices.forEach(dev => {
if (dev.Capabilities.includes('pointer')) {
pointers.push(dev);
}
})
Libinput.addDebugInstance('pointers', pointers)
.connect('new-line', e => print(e.lastLine))
});
*/
const { Service } = ags;
const { execAsync } = ags.Utils;
import GLib from 'gi://GLib';
import Gio from 'gi://Gio';
import GObject from 'gi://GObject';
class DebugInstance extends GObject.Object {
static {
GObject.registerClass({
Signals: {
'changed': {},
'closed': {},
'new-line': {},
},
}, this);
}
devices = [];
name = '';
lastLine = '';
readOutput(stdout, stdin) {
stdout.read_line_async(GLib.PRIORITY_LOW, null, (stream, result) => {
try {
const [line] = stream.read_line_finish_utf8(result);
if (line !== null) {
this.lastLine = line;
this.emit('new-line');
this.readOutput(stdout, stdin);
}
} catch (e) {
logError(e);
}
});
}
getOutput(devs) {
try {
let args = [];
devs.forEach(dev => {
if (dev.Kernel) {
args.push('--device');
args.push(dev.Kernel);
}
});
const proc = Gio.Subprocess.new(['libinput', 'debug-events', ...args],
Gio.SubprocessFlags.STDIN_PIPE | Gio.SubprocessFlags.STDOUT_PIPE);
// Get the `stdin`and `stdout` pipes, wrapping `stdout` to make it easier to
// read lines of text
const stdinStream = proc.get_stdin_pipe();
const stdoutStream = new Gio.DataInputStream({
base_stream: proc.get_stdout_pipe(),
close_base_stream: true,
});
// Start the loop
this.readOutput(stdoutStream, stdinStream);
} catch (e) {
logError(e);
}
}
constructor(name, devs) {
super();
this.devices = devs;
this.name = name;
this.getOutput(devs);
}
}
class LibinputService extends Service {
static {
Service.register(this, {
'device-init': ['boolean'],
'instance-closed': ['string'],
'instance-added': ['string'],
});
}
debugInstances = new Map();
devices = new Map();
get devices() { return this._devices; }
parseOutput(output) {
let lines = output.split('\n');
let device = null;
lines.forEach(line => {
let parts = line.split(':');
if (parts[0] === 'Device') {
device = {};
this.devices.set(parts[1].trim(), device);
}
else if (device && parts[1]) {
let key = parts[0].trim();
let value = parts[1].trim();
device[key] = value;
}
});
this.emit('device-init', true);
}
constructor() {
super();
this.debugInstances = new Map();
execAsync(['libinput', 'list-devices'])
.then(out => this.parseOutput(out))
.catch(console.error);
}
addDebugInstance(name, devs) {
if (this.debugInstances.get(name))
return;
devs = Array(devs);
if (devs.some(dev => dev.Capabilities && dev.Capabilities.includes('pointer'))) {
}
const debugInst = new DebugInstance(name, devs);
debugInst.connect('closed', () => {
this.debugInstances.delete(name);
this.emit('instance-closed', name);
this.emit('changed');
});
this.debugInstances.set(name, debugInst);
this.emit('instance-added', name);
return debugInst;
}
}
export default class Libinput {
static { Service.Libinput = this; }
static instance = new LibinputService();
static get devices() { return Libinput.instance.devices; }
static get debugInstances() { return Libinput.instance.debugInstances; }
static addDebugInstance(name, dev) {
return Libinput.instance.addDebugInstance(name, dev);
}
}

770
config/ags/style.css Normal file
View file

@ -0,0 +1,770 @@
* {
all: unset; }
.powermenu {
background-color: rgba(40, 42, 54, 0.8);
color: #a5b6cf;
padding: 10px;
font-family: MesloLGS NF;
/*font-family: Iosevka Nerd Font;*/
font-size: 70px;
border-radius: 30px;
border: 2px solid rgba(189, 147, 249, 0.8); }
.powermenu label {
min-width: 140px;
min-height: 130px; }
.powermenu button {
margin-right: 10px;
margin-left: 10px;
margin-top: 5px;
margin-bottom: 5px;
border-radius: 12px;
min-width: 80px;
transition: all ease .2s; }
.powermenu button:hover {
background-color: rgba(56, 44, 74, 0.8); }
.powermenu button:active {
background-color: rgba(56, 44, 74, 0.8); }
.powermenu button .content {
border-radius: 4px;
padding: 0px 15px 0px 15px; }
.powermenu .shutdown {
color: #dd6777; }
.powermenu .reboot {
color: #c296eb; }
.powermenu .logout {
color: #ecd3a0; }
.powermenu-clickhandler {
background-color: black; }
.osk-toggle,
.tablet-toggle,
.heart-toggle {
font-size: 28px;
min-height: 40px;
min-width: 53px; }
.notif-panel {
font-size: 20px;
border-radius: 80px;
min-height: 37px;
min-width: 105px;
padding: 1px 0px 1px 5px; }
.notif-panel .toggle-on {
border-top-left-radius: 22px;
border-top-right-radius: 22px;
border-bottom-left-radius: 0px;
border-bottom-right-radius: 0px;
border-bottom: 0px solid rgba(40, 42, 54, 0.8); }
.quick-settings-toggle {
font-size: 24px;
min-height: 40px;
min-width: 40px;
padding-right: 4px;
margin-left: -3px; }
.toggle-off {
background-color: rgba(40, 42, 54, 0.8);
color: #CBA6F7;
border-radius: 80px;
border: 2px solid rgba(56, 44, 74, 0.8);
transition: background-color 0.5s ease-in-out, border 0.5s ease-in-out; }
.toggle-on {
background-color: rgba(40, 42, 54, 0.8);
color: #CBA6F7;
border-radius: 80px;
border: 2px solid rgba(189, 147, 249, 0.8);
transition: background-color 0.5s ease-in-out, border 0.5s ease-in-out; }
.toggle-on:hover, .toggle-off:hover {
background-color: rgba(127, 132, 156, 0.4);
transition: background-color 0.5s ease-in-out, border 0.5s ease-in-out; }
.clock {
font-size: 20px;
padding-left: 10px;
padding-right: 10px;
min-width: 230px; }
.audio {
padding: 0 10px 0 10px;
font-size: 20px; }
.battery {
padding: 0 10px 0 10px;
font-size: 20px; }
.battery .battery-indicator.charging {
color: green; }
.battery .battery-indicator.low {
color: red; }
.battery .label {
font-size: 20px; }
.brightness trough {
margin-right: -50px; }
.brightness trough progress {
margin-right: 50px;
margin-top: -30px;
border-radius: 80px;
min-height: 36px;
background: rgba(94, 73, 124, 0.8); }
tooltip {
background: rgba(0, 0, 0, 0.6);
border-radius: 5px; }
.workspaces {
background-color: rgba(40, 42, 54, 0.8);
border-radius: 80px;
border: 2px solid rgba(56, 44, 74, 0.8);
padding-top: 3px;
padding-bottom: 3px;
padding-left: 12px;
padding-right: 12px; }
.workspaces .button {
margin: 2px;
min-width: 20px;
border-radius: 100%; }
.workspaces .button * {
color: transparent; }
.workspaces .empty {
border: none;
transition: border-color 0.25s linear; }
.workspaces .occupied {
border: 2px solid rgba(40, 42, 54, 0.8);
background: rgba(189, 147, 249, 0.8);
transition: border-color 0.25s linear; }
.workspaces .active {
border: 2px solid #50fa7b;
transition: border-color 0.25s linear; }
.sys-tray {
padding: 5px;
background-color: rgba(40, 42, 54, 0.8);
color: #CBA6F7;
border-radius: 80px;
border: 2px solid rgba(56, 44, 74, 0.8);
transition: background-color 0.5s ease-in-out, border 0.5s ease-in-out; }
.sys-tray .tray-item {
all: unset;
padding: 0px 2px 0px 2px;
font-size: 25px;
border-radius: 100%;
transition: background-color 0.5s ease-in-out, border 0.5s ease-in-out; }
.sys-tray .tray-item:hover {
background: rgba(127, 132, 156, 0.4);
border-radius: 100%;
transition: background-color 0.5s ease-in-out, border 0.5s ease-in-out; }
.sys-tray menu {
background: #282a36;
padding-top: 5px;
padding-bottom: 5px;
border: 2px solid #1b1b1b;
border-radius: 10px; }
.sys-tray menu check {
color: white;
margin-left: 2px;
margin-right: 5px;
min-height: 20px;
min-width: 20px;
-gtk-icon-source: -gtk-icontheme("checkbox-symbolic"); }
.sys-tray menu check:checked {
-gtk-icon-source: -gtk-icontheme("checkbox-checked-symbolic"); }
.sys-tray menuitem:not(.tray-item) {
padding: 5px;
transition: all 0.2s ease-in-out; }
.sys-tray menuitem:not(.tray-item):hover {
background: #1b1b1b; }
.notification-center {
min-height: 700px;
min-width: 524px;
background: rgba(40, 42, 54, 0.8);
border-radius: 30px;
border-top-right-radius: 0px;
border: 2px solid rgba(189, 147, 249, 0.8);
padding: 0px;
box-shadow: 0 0 4.5px 0 rgba(0, 0, 0, 0.6); }
.notification-center * {
font-size: 16px; }
.notification-center .header {
padding: 10px;
margin-top: 22px;
margin-bottom: 9px; }
.notification-center .header label {
font-size: 22px; }
.notification-center .header button {
all: unset;
transition: 200ms;
border-radius: 9px;
color: #eee;
background-color: #664C90;
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
padding: 4.5px 9px; }
.notification-center .header button:hover {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
background-color: rgba(238, 238, 238, 0.154);
color: #f1f1f1; }
.notification-center .header button:disabled {
box-shadow: none;
background-color: rgba(102, 76, 144, 0.3);
color: rgba(238, 238, 238, 0.3); }
.notification-center .header button label {
font-size: 1.2em; }
.notification-center .notification-list-box {
background: #282a36;
padding: 0 12px 0 12px;
border-radius: 30px;
border-top: 2px solid rgba(189, 147, 249, 0.8);
box-shadow: 0 0 6px 0 rgba(0, 0, 0, 0.5); }
.notification-center .notification-list-box scrollbar {
all: unset;
border-radius: 8px;
border-top-left-radius: 0;
border-bottom-left-radius: 0; }
.notification-center .notification-list-box scrollbar * {
all: unset; }
.notification-center .notification-list-box scrollbar:hover {
border-radius: 8px;
border-top-left-radius: 0;
border-bottom-left-radius: 0; }
.notification-center .notification-list-box scrollbar.vertical {
transition: 200ms;
background-color: rgba(23, 23, 23, 0.3); }
.notification-center .notification-list-box scrollbar.vertical:hover {
background-color: rgba(23, 23, 23, 0.7); }
.notification-center .notification-list-box scrollbar.vertical:hover slider {
background-color: rgba(238, 238, 238, 0.7);
min-width: .6em; }
.notification-center .notification-list-box scrollbar.vertical slider {
background-color: rgba(238, 238, 238, 0.5);
border-radius: 9px;
min-width: .4em;
min-height: 2em;
transition: 200ms; }
.notification-center .placeholder {
color: white; }
.notification-center .placeholder image {
font-size: 7em;
text-shadow: 2px 2px 2px rgba(0, 0, 0, 0.6);
-gtk-icon-shadow: 2px 2px 2px rgba(0, 0, 0, 0.6); }
.notification-center .placeholder label {
font-size: 1.2em;
text-shadow: 2px 2px 2px rgba(0, 0, 0, 0.6);
-gtk-icon-shadow: 2px 2px 2px rgba(0, 0, 0, 0.6); }
.notification.critical > box {
box-shadow: inset 0 0 0.5em 0 #e67090; }
.notification > box {
all: unset;
box-shadow: 0 0 4.5px 0 rgba(0, 0, 0, 0.4);
margin: 9px 9px 0 9px;
border: 2px solid rgba(189, 147, 249, 0.8);
border-radius: 15px;
background-color: rgba(40, 42, 54, 0.8);
padding: 16.2px; }
.notification > box * {
font-size: 16px; }
.notification:hover .close-button {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
background-color: rgba(238, 238, 238, 0.154);
background-color: rgba(230, 112, 144, 0.5); }
.notification .title {
margin-right: 9px;
font-size: 1.1em; }
.notification .description {
font-size: .9em;
min-width: 350px; }
.notification .icon {
border-radius: 7.2px;
margin-right: 9px; }
.notification .icon.img {
border: 1px solid rgba(238, 238, 238, 0.03); }
.notification .actions button {
all: unset;
transition: all 500ms;
border-radius: 9px;
background-color: rgba(238, 238, 238, 0.06);
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
border-radius: 7.2px;
font-size: 1.2em;
padding: 4.5px 9px;
margin: 9px 4.5px 0; }
.notification .actions button * {
font-size: 16px; }
.notification .actions button:focus {
box-shadow: inset 0 0 0 1px #51a4e7;
background-color: rgba(238, 238, 238, 0.154); }
.notification .actions button:hover {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
background-color: rgba(238, 238, 238, 0.154); }
.notification .actions button:active {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
background-image: linear-gradient(to right, #51a4e7, #6cb2eb);
background-color: #51a4e7; }
.notification .actions button:active:hover {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03), inset 0 0 0 99px rgba(238, 238, 238, 0.154); }
.notification .actions button:checked {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
background-image: linear-gradient(to right, #51a4e7, #6cb2eb);
background-color: #51a4e7; }
.notification .actions button:checked:hover {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03), inset 0 0 0 99px rgba(238, 238, 238, 0.154); }
.notification .actions button:disabled {
box-shadow: none;
background-color: transparent; }
.notification .actions button:first-child {
margin-left: 0; }
.notification .actions button:last-child {
margin-right: 0; }
.notification .actions button.on {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
background-image: linear-gradient(to right, #51a4e7, #6cb2eb);
background-color: #51a4e7; }
.notification .actions button.on:hover {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03), inset 0 0 0 99px rgba(238, 238, 238, 0.154); }
.notification .actions button.active {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
background-image: linear-gradient(to right, #51a4e7, #6cb2eb);
background-color: #51a4e7; }
.notification .actions button.active:hover {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03), inset 0 0 0 99px rgba(238, 238, 238, 0.154); }
.notification button.close-button {
all: unset;
transition: all 500ms;
border-radius: 9px;
background-color: transparent;
background-image: none;
box-shadow: none;
margin-left: 9px;
border-radius: 7.2px;
min-width: 1.2em;
min-height: 1.2em; }
.notification button.close-button * {
font-size: 16px; }
.notification button.close-button:focus {
box-shadow: inset 0 0 0 1px #51a4e7;
background-color: rgba(238, 238, 238, 0.154); }
.notification button.close-button:hover {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
background-color: rgba(238, 238, 238, 0.154);
background-color: rgba(230, 112, 144, 0.5); }
.notification button.close-button:active {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
background-image: linear-gradient(to right, #51a4e7, #6cb2eb);
background-color: #51a4e7;
background-image: linear-gradient(#e67090, #e67090); }
.notification button.close-button:active:hover {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03), inset 0 0 0 99px rgba(238, 238, 238, 0.154); }
.notification button.close-button:checked {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
background-image: linear-gradient(to right, #51a4e7, #6cb2eb);
background-color: #51a4e7; }
.notification button.close-button:checked:hover {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03), inset 0 0 0 99px rgba(238, 238, 238, 0.154); }
.notification button.close-button:disabled {
box-shadow: none;
background-color: transparent; }
.notification button.close-button.on {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
background-image: linear-gradient(to right, #51a4e7, #6cb2eb);
background-color: #51a4e7; }
.notification button.close-button.on:hover {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03), inset 0 0 0 99px rgba(238, 238, 238, 0.154); }
.notification button.close-button.active {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
background-image: linear-gradient(to right, #51a4e7, #6cb2eb);
background-color: #51a4e7; }
.notification button.close-button.active:hover {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03), inset 0 0 0 99px rgba(238, 238, 238, 0.154); }
.date {
background-color: rgba(40, 42, 54, 0.8);
color: #a5b6cf;
border-radius: 30px;
border-top-right-radius: 0px;
border: 2px solid rgba(189, 147, 249, 0.8); }
.timebox {
margin: 30px 0px; }
.timebox .time-container .content {
font-family: Product Sans;
font-weight: bolder;
font-size: 60px; }
.timebox .time-container .divider {
margin: 8px 15px;
padding: 0px 1px;
background: linear-gradient(#dd6777, #c296eb, #86aaec, #93cee9); }
.timebox .date-container {
font-family: Product Sans;
margin-top: 2px; }
.cal-box {
font-family: Product Sans;
border-radius: 30px;
padding: 0 1rem .2rem;
color: #a5b6cf;
background-color: #282a36;
border-bottom: 2px solid rgba(189, 147, 249, 0.8);
border-top: 2px solid rgba(189, 147, 249, 0.8);
margin: 0px 12px 18px 12px; }
.cal-box .cal {
font-size: 20px;
background-color: inherit;
padding: .5rem .10rem 0rem;
margin-left: 10px;
border-radius: 30px; }
.cal-box .cal > * {
border: solid 0px transparent; }
.cal-box .cal.highlight {
padding: 10rem; }
calendar:selected {
color: #93cee9; }
calendar.header {
color: #93cee9;
font-weight: bold; }
calendar.button {
color: #93cee9; }
calendar.highlight {
color: #90ceaa;
font-weight: bold; }
calendar:indeterminate {
color: #262831; }
.quick-settings {
font-size: 30px;
min-width: 500px;
padding: 0px 0px 0px 0px;
background-color: rgba(40, 42, 54, 0.8);
border-radius: 30px 0 30px 30px;
border: 2px solid rgba(189, 147, 249, 0.8); }
.title {
font-size: 22px;
margin-top: 30px; }
.grid-label {
font-size: 30px;
margin-left: 15px;
margin-right: 10px;
min-width: 50px; }
.sub-label {
font-size: 14px;
margin-top: -10px;
margin-left: 31px;
margin-bottom: 10px;
padding: 3px;
border: 2px solid rgba(189, 147, 249, 0.8);
border-top-right-radius: 20px;
border-top-left-radius: 10px;
border-bottom-left-radius: 10px;
border-bottom-right-radius: 20px;
min-width: 106px;
background: #1b1b1b; }
.sub-label:nth-child(1) {
margin-left: 25px; }
.grid-chev {
margin-left: 0px;
font-size: 40px;
margin-right: 5px; }
.button-grid {
font-size: 10px;
min-height: 160px;
min-width: 470px;
background-color: #282a36;
border-top: 2px solid rgba(189, 147, 249, 0.8);
border-bottom: 2px solid rgba(189, 147, 249, 0.8);
border-radius: 15px;
margin-top: 30px; }
.button-row {
min-height: 70px;
min-width: 450px;
margin-left: 20px; }
.grid-button {
min-height: 65px;
min-width: 70px;
margin: 5px;
margin-left: 22px; }
.grid-button:nth-child(1) {
margin-left: 5px; }
.left-part {
background: #1b1b1b;
border-top-left-radius: 15px;
border-bottom-left-radius: 15px;
border-left: 2px solid rgba(189, 147, 249, 0.8);
border-top: 2px solid rgba(189, 147, 249, 0.8);
border-bottom: 2px solid rgba(189, 147, 249, 0.8);
transition: all 0.5s ease-in-out; }
.right-part {
background: #1b1b1b;
border-top-right-radius: 30px;
border-bottom-right-radius: 30px;
border-right: 2px solid rgba(189, 147, 249, 0.8);
border-top: 2px solid rgba(189, 147, 249, 0.8);
border-bottom: 2px solid rgba(189, 147, 249, 0.8);
transition: all 0.5s ease-in-out; }
.right-part:hover, .right-part:active {
color: rgba(189, 147, 249, 0.8);
border: 2px solid rgba(189, 147, 249, 0.8);
border-top-left-radius: 7px;
border-bottom-left-radius: 7px;
transition: all 0.5s ease-in-out; }
.left-part:hover, .left-part:active {
color: rgba(189, 147, 249, 0.8);
border: 2px solid rgba(189, 147, 249, 0.8);
border-top-right-radius: 7px;
border-bottom-right-radius: 7px;
transition: all 0.5s ease-in-out; }
.player {
margin-top: 6px;
min-height: 220px;
opacity: 0; }
.slider-box {
min-height: 100px;
min-width: 470px;
background-color: #282a36;
border-top: 2px solid rgba(189, 147, 249, 0.8);
border-bottom: 2px solid rgba(189, 147, 249, 0.8);
border-radius: 15px;
margin-top: 30px;
margin-bottom: 20px; }
.slider-box .slider-label {
font-size: 30px;
min-width: 30px; }
.slider-box scale {
min-height: 55px;
min-width: 400px;
margin-left: 18px;
margin-right: 20px; }
.slider-box scale highlight {
margin: 0px;
background-color: #79659f;
border-radius: 2em; }
.slider-box scale trough {
background-color: #363847;
border-radius: 2em; }
.slider-box scale slider {
margin: -4px;
min-width: 20px;
min-height: 20px;
background: #3e4153;
border-radius: 100%;
box-shadow: rgba(0, 0, 0, 0.25) 0px 54px 55px, rgba(0, 0, 0, 0.12) 0px -12px 30px, rgba(0, 0, 0, 0.12) 0px 4px 6px, rgba(0, 0, 0, 0.17) 0px 12px 13px, rgba(0, 0, 0, 0.09) 0px -3px 5px;
transition: background-color 0.5s ease-in-out; }
.slider-box scale slider:hover {
background-color: #303240;
transition: background-color 0.5s ease-in-out; }
.arrow {
transition: -gtk-icon-transform 0.3s ease-in-out;
margin-bottom: 12px; }
.media {
margin-top: 9px; }
.player {
all: unset;
padding: 10px;
min-width: 400px;
min-height: 200px;
border-radius: 30px;
border-top: 2px solid rgba(189, 147, 249, 0.8);
border-bottom: 2px solid rgba(189, 147, 249, 0.8);
transition: background 250ms; }
.player .top {
font-size: 23px; }
.player .metadata .title {
font-weight: 500;
transition: text 250ms; }
.player .metadata .artist {
font-weight: 400;
font-size: 15px;
transition: text 250ms; }
.player .bottom {
font-size: 30px; }
.player .pausebutton {
transition: background-color ease .2s, color ease .2s;
font-size: 15px;
padding: 4px 4px 4px 7px; }
.player .playing {
transition: background-color ease .2s, color ease .2s;
border-radius: 15px; }
.player .stopped,
.player .paused {
transition: background-color ease .2s, color ease .2s;
border-radius: 26px;
padding: 4px 4px 4px 10px; }
.player button label {
min-width: 35px; }
.position-indicator {
min-width: 18px;
margin: 7px;
background-color: rgba(255, 255, 255, 0.7);
box-shadow: 0 0 5px 0px rgba(255, 255, 255, 0.3);
border-radius: 100%; }
.previous,
.next,
.shuffle,
.loop {
border-radius: 100%;
transition: color 200ms; }
.previous:hover,
.next:hover,
.shuffle:hover,
.loop:hover {
border-radius: 100%;
background-color: rgba(127, 132, 156, 0.4);
transition: color 200ms; }
.loop label {
padding-right: 8px; }
.position-slider highlight {
margin: 0px;
border-radius: 2em; }
.position-slider trough {
margin: 0 8px;
border-radius: 2em; }
.position-slider slider {
margin: -8px;
min-height: 20px;
border-radius: 10px;
box-shadow: rgba(0, 0, 0, 0.25) 0px 54px 55px, rgba(0, 0, 0, 0.12) 0px -12px 30px, rgba(0, 0, 0, 0.12) 0px 4px 6px, rgba(0, 0, 0, 0.17) 0px 12px 13px, rgba(0, 0, 0, 0.09) 0px -3px 5px;
transition: background-color 0.5s ease-in-out; }
.position-slider slider:hover {
transition: background-color 0.5s ease-in-out; }
.overview .workspace {
padding: 4px 15px 4px 0px;
border: 2px solid transparent;
border-radius: 10px; }
.overview .workspace.active {
background-color: rgba(38, 38, 38, 0.8);
border: 2px solid black; }
.overview .workspace .window {
background-color: #282a36;
border-radius: 10px;
margin: 0 10px;
transition: min-width 0.2s ease-in-out, min-height 0.2s ease-in-out, border-color 0.2s ease-in-out, font-size 0.2s ease-in-out; }
.overview .normal {
margin-bottom: 5px; }
.overview .normal .workspace .window {
border: 2px solid #411C6C; }
.overview .normal .workspace .window.active {
border: 2px solid purple; }
.overview .special .workspace .window {
border: 2px solid #333333; }
.overview .special .workspace .window.active {
border: 2px solid purple; }
.applauncher {
all: unset;
box-shadow: 0 0 4.5px 0 rgba(0, 0, 0, 0.6);
margin: 9px;
border: 2px solid rgba(189, 147, 249, 0.8);
border-radius: 25px;
background-color: rgba(40, 42, 54, 0.8);
color: #f8f8f2;
padding: 16.2px; }
.applauncher * {
font-size: 16px; }
.applauncher .header {
margin: 16.2px;
margin-bottom: 0; }
.applauncher .header image, .applauncher .header entry {
all: unset;
border-radius: 9px;
color: #f8f8f2;
background-color: rgba(68, 71, 90, 0.6);
border: 1px solid #44475a;
padding: 4.5px; }
.applauncher .header image {
margin-right: 9px;
-gtk-icon-transform: scale(0.8);
font-size: 25.6px; }
.applauncher scrolledwindow {
padding: 16.2px;
min-width: 700px;
min-height: 450px; }
.applauncher scrolledwindow scrollbar, .applauncher scrolledwindow scrollbar * {
all: unset; }
.applauncher scrolledwindow scrollbar.vertical {
transition: 200ms;
background-color: rgba(23, 23, 23, 0.3); }
.applauncher scrolledwindow scrollbar.vertical:hover {
background-color: rgba(23, 23, 23, 0.7); }
.applauncher scrolledwindow scrollbar.vertical:hover slider {
background-color: rgba(238, 238, 238, 0.7);
min-width: .6em; }
.applauncher scrolledwindow scrollbar.vertical slider {
background-color: rgba(238, 238, 238, 0.5);
border-radius: 9px;
min-width: .4em;
min-height: 2em;
transition: 200ms; }
.applauncher .placeholder {
margin-top: 9px;
color: #f8f8f2;
font-size: 1.2em; }
.applauncher .app {
all: unset;
transition: 200ms;
padding: 9px; }
.applauncher .app:active {
background-color: rgba(189, 147, 249, 0.5);
border-radius: 9px;
box-shadow: inset 0 0 0 3px rgba(238, 238, 238, 0.03); }
.applauncher .app:active .title {
color: #f8f8f2; }
.applauncher .app:hover .title, .applauncher .app:focus .title {
color: rgba(189, 147, 249, 0.8); }
.applauncher .app:hover image, .applauncher .app:focus image {
-gtk-icon-shadow: 2px 2px rgba(189, 147, 249, 0.8); }
.applauncher .app label {
transition: 200ms; }
.applauncher .app label.title {
color: #f8f8f2; }
.applauncher .app label.description {
color: rgba(238, 238, 238, 0.7); }
.applauncher .app image {
transition: 200ms;
margin-right: 9px; }

View file

@ -0,0 +1,11 @@
{
"SKIP_HOST_UPDATE": true,
"IS_MAXIMIZED": false,
"IS_MINIMIZED": false,
"WINDOW_BOUNDS": {
"x": 7,
"y": 57,
"width": 1906,
"height": 1136
}
}

View file

@ -19,7 +19,6 @@ Places Icons Static Size=22
1920x1200 screen: Height=567
1920x1200 screen: Width=604
1920x1200 screen: Window-Maximized=true
3 screens: Window-Maximized=true
[MainWindow]
MenuBar=Disabled

View file

@ -0,0 +1,4 @@
[main]
modules=/nix/var/nix/profiles/system/sw/lib/gtklock/powerbar-module.so;/nix/var/nix/profiles/system/sw/lib/gtklock/playerctl-module.so
#;/nix/var/nix/profiles/system/sw/lib/gtklock/userinfo-module.so

5
config/gtklock/scripts/blur.sh Executable file
View file

@ -0,0 +1,5 @@
#!/usr/bin/env bash
grim -t jpeg /tmp/image.jpeg
convert /tmp/image.jpeg -blur 0x8 /tmp/image.jpeg

4
config/gtklock/scripts/lock.sh Executable file
View file

@ -0,0 +1,4 @@
#!/usr/bin/env bash
$AGS_PATH/tablet-toggle.sh laptop &
$LOCK_PATH/blur.sh
gtklock

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