refactor(ags): remove unneeded agsV1 stuff

This commit is contained in:
matt1432 2024-11-12 16:55:57 -05:00
parent c20ec51142
commit ab8bb48feb
137 changed files with 184 additions and 10725 deletions

View file

@ -1,40 +0,0 @@
# [AGS](https://github.com/Aylur/ags)
## Nix
This directory is the Nix entrypoint to my AGS configration.
On system activation, if this module is imported, it will
generate the `config.js` of the host with the host's name
as the parameter in `transpileTypeScript` to support a different
setup per device.
## Non-Nix
To use this setup without Nix:
```js
/* ~/.config/ags/config.js */
import { transpileTypeScript } from './js/utils.js';
export default (await transpileTypeScript('wim')).default;
```
If you want to try my main config, this is what you need to have
as your `config.js` after copying the contents of `./config` to
`~/.config/ags`
## Dependencies
The main dependencies to try it are as follows:
- **bun** to transpile TS to JS
- **dart-sass** to compile SCSS to CSS
- **[coloryou](https://git.nelim.org/matt1432/nixos-configs/src/branch/master/common/pkgs/coloryou)** for media player colors
- **playerctl** for media player functionality
If you're interested in my 2-1 laptop setup, you'll need:
- **ydotool** for my custom on-screen keyboard
- **lisgd** to have touch screen gestures

View file

@ -1,19 +0,0 @@
{
cliphist,
gawk,
imagemagick,
ripgrep,
writeShellApplication,
...
}:
writeShellApplication {
name = "clipboard-manager";
runtimeInputs = [
cliphist
gawk
imagemagick
ripgrep
];
text = builtins.readFile ./script.sh;
}

View file

@ -1,45 +0,0 @@
set +o errexit
# Modified from https://github.com/sentriz/cliphist/blob/master/contrib/cliphist-wofi-img
# set up thumbnail directory
thumb_dir="/tmp/cliphist/thumbs"
mkdir -p "$thumb_dir"
cliphist_list="$(cliphist list)"
# delete thumbnails in cache but not in cliphist
for thumb in "$thumb_dir"/*; do
clip_id="${thumb##*/}"
clip_id="${clip_id%.*}"
check=$(rg <<< "$cliphist_list" "^$clip_id\s")
if [ -z "$check" ]; then
>&2 rm -v "$thumb"
fi
done
# create thumbnail if image not processed already
read -r -d '' prog <<EOF
/^[0-9]+\s<meta http-equiv=/ { next }
match(\$0, /^([0-9]+)\s(\[\[\s)?binary.*(jpg|jpeg|png|bmp)/, grp) {
image = grp[1]"."grp[3]
system("[ -f $thumb_dir/"image" ] || echo " grp[1] "\\\\\t | cliphist decode | convert - -resize '256x256>' $thumb_dir/"image )
print "img:$thumb_dir/"image
next
}
1
EOF
output=$(gawk <<< "$cliphist_list" "$prog")
# Use a while loop with read to iterate over each line
echo "$output" | while IFS= read -r line; do
if [[ ! $line =~ ^img:/tmp/cliphist/thumbs ]]; then
[[ $line =~ ([0-9]+) ]]
line=${BASH_REMATCH[1]}
fi
echo "$line"
done

View file

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

View file

@ -1,40 +0,0 @@
import Brightness from './services/brightness.ts';
import GSR from './services/gpu-screen-recorder.ts';
import Pointers from './services/pointers.ts';
import AppLauncher from './ts/applauncher/main.ts';
import Bar from './ts/bar/binto.ts';
import Clipboard from './ts/clipboard/main.ts';
import Calendar from './ts/date/binto.ts';
import { NotifPopups, NotifCenter } from './ts/notifications/binto.ts';
import OSD from './ts/osd/main.ts';
import Powermenu from './ts/powermenu.ts';
import Screenshot from './ts/screenshot/main.ts';
// TODO: add workspace indicator
App.config({
icons: './icons',
onConfigParsed: () => {
Brightness.capsName = 'input30::capslock';
globalThis.Brightness = Brightness;
globalThis.Pointers = Pointers;
setTimeout(() => {
globalThis.GSR = new GSR();
}, 1000);
},
windows: () => [
AppLauncher(),
Calendar(),
Clipboard(),
NotifCenter(),
Powermenu(),
Screenshot(),
Bar(),
NotifPopups(),
OSD(),
],
});

View file

@ -1,452 +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({
// @ts-expect-error this should work
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,177 +0,0 @@
import { Widget } from 'types/@girs/gtk-3.0/gtk-3.0.cjs';
import GObject from 'types/@girs/gobject-2.0/gobject-2.0';
import { Variable as Var } from 'types/variable';
import { Widget as agsWidget } from 'types/widgets/widget';
export type AgsWidget = agsWidget<unknown> & Widget;
// Generic widgets
import AgsBox from 'types/widgets/box.ts';
export type BoxGeneric = AgsBox<unknown & Widget, unknown>;
import AgsCenterBox, { CenterBoxProps } from 'types/widgets/centerbox';
export type CenterBoxGeneric = AgsCenterBox<
unknown & Widget, unknown & Widget, unknown & Widget, unknown
>;
export type CenterBoxPropsGeneric = CenterBoxProps<
unknown & Widget, unknown & Widget, unknown & Widget, unknown
>;
import AgsEventBox from 'types/widgets/eventbox';
export type EventBoxGeneric = AgsEventBox<unknown & Widget, unknown>;
import AgsIcon, { IconProps } from 'types/widgets/icon';
export type IconGeneric = AgsIcon<unknown>;
export type IconPropsGeneric = IconProps<unknown>;
import AgsLabel from 'types/widgets/label';
export type LabelGeneric = AgsLabel<unknown>;
import AgsOverlay, { OverlayProps } from 'types/widgets/overlay';
export type OverlayGeneric = AgsOverlay<unknown & Widget, unknown>;
import AgsProgressBar from 'types/widgets/progressbar';
export type ProgressBarGeneric = AgsProgressBar<unknown & Widget, unknown>;
import AgsRevealer, { RevealerProps } from 'types/widgets/revealer';
export type RevealerGeneric = AgsRevealer<unknown & Widget, unknown>;
import AgsStack, { StackProps } from 'types/widgets/stack';
export type StackGeneric = AgsStack<Record<string, Widget>, unknown>;
import AgsScrollable from 'types/widgets/scrollable';
export type ScrollableGeneric = AgsScrollable<unkown & Widget, unknown>;
import AgsWindow from 'types/widgets/window';
export type WindowGeneric = AgsWindow<unknown & Widget, unknown>;
// For ./ts/applauncher/main.ts
import { Application } from 'types/service/applications.ts';
import { CursorBoxProps } from 'ts/misc/cursorbox';
export type AgsAppItem = AgsEventBox<Widget, { app: Application }
& CursorBoxProps<Widget, unknown>>;
// For ./ts/bar/hovers/keyboard.ts
export interface Keyboard {
address: string
name: string
rules: string
model: string
layout: string
variant: string
options: string
active_keymap: string
main: boolean
}
// For ./ts/bar/items/workspaces.ts
// TODO: improve type
export type Workspace = AgsRevealer<unknown & Widget, unknown & { id: number }>;
// For ./ts/bar/fullscreen.ts
export type DefaultProps = RevealerProps<CenterBoxGeneric>;
// For ./ts/media-player/gesture.ts
export interface Gesture {
attribute?: object
setup?(self: OverlayGeneric): void
props?: OverlayProps<unknown & Widget, unknown>
}
// For ./ts/media-player/mpris.ts
type PlayerDragProps = unknown & { dragging: boolean };
export type PlayerDrag = AgsCenterBox<
unknown & Widget, unknown & Widget, unknown & Widget, unknown & PlayerDragProps
>;
interface Colors {
imageAccent: string
buttonAccent: string
buttonText: string
hoverAccent: string
}
// For ./ts/media-player
export interface PlayerBoxProps {
bgStyle: string
player: MprisPlayer
}
export type PlayerBox = AgsCenterBox<
unknown & Widget, unknown & Widget, unknown & Widget, PlayerBoxProps
>;
export type PlayerOverlay = AgsOverlay<AgsWidget, {
players: Map
setup: boolean
dragging: boolean
includesWidget(playerW: PlayerBox): PlayerBox
showTopOnly(): void
moveToTop(player: PlayerBox): void
}>;
export interface PlayerButtonType {
player: MprisPlayer
colors: Var<Colors>
children: StackProps['children']
onClick: string
prop: string
}
// For ./ts/notifications/gesture.js
interface NotifGestureProps {
dragging: boolean
hovered: boolean
ready: boolean
id: number
slideAway(side: 'Left' | 'Right'): void
}
export type NotifGesture = AgsEventBox<BoxGeneric, NotifGestureProps>;
// For ./ts/osd/ctor.ts
export type OSDStack = AgsStack<unknown & Widget, {
popup(osd: string): void
}>;
export type ConnectFunc = (self?: ProgressBarGeneric) => void;
export interface OSD {
name: string
icon: IconPropsGeneric['icon']
info: {
mod: GObject.Object
signal?: string | string[]
logic?(self: ProgressBarGeneric): void
widget?: AgsWidget
}
}
// For ./ts/on-screen-keyboard
export type OskWindow = Window<BoxGeneric, {
startY: null | number
setVisible: (state: boolean) => void
killGestureSigs: () => void
setSlideUp: () => void
setSlideDown: () => void
}>;
// For CursorBox
import { CursorBox, CursorBoxProps } from 'ts/misc/cursorbox';
export type CursorBox = CursorBox;
export type CursorBoxProps = CursorBoxProps;
// For PopupWindow
export type HyprTransition = 'slide' | 'slide top' | 'slide bottom' | 'slide left' |
'slide right' | 'popin' | 'fade';
export type CloseType = 'none' | 'stay' | 'released' | 'clicked';
import { PopupWindow } from 'ts/misc/popup';
export type PopupWindow = PopupWindow;
// For ./ts/quick-settings
import { BluetoothDevice as BTDev } from 'types/service/bluetooth.ts';
export interface APType {
bssid: string
address: string
lastSeen: number
ssid: string
active: boolean
strength: number
iconName: string
}
export type APBox = AgsBox<unknown & Widget, { ap: Var<APType> }>;
export type DeviceBox = AgsBox<unknown & Widget, { dev: BTDev }>;

View file

@ -1,52 +0,0 @@
const { execAsync, monitorFile } = Utils;
/**
* @param {string} host the name of the machine/user who's running ags
*/
const watchAndCompileSass = (host) => {
const reloadCss = () => {
const scss = `${App.configDir}/scss/${host}.scss`;
const css = `/tmp/ags-${host}/style.css`;
execAsync(`sass ${scss} ${css}`).then(() => {
App.resetCss();
App.applyCss(css);
}).catch(print);
};
monitorFile(
`${App.configDir}/scss`,
reloadCss,
);
reloadCss();
};
/**
* @param {string} host the name of the machine/user who's running ags
* @returns the config
*/
export const transpileTypeScript = async(host) => {
const outPath = `/tmp/ags-${host}/index.js`;
await execAsync([
'bash', '-c',
// Create the dir if it doesn't exist
`mkdir -p /tmp/ags-${host}; ` +
// Let bun see tsconfig.json
`cd ${App.configDir};` +
`bun build ${App.configDir}/${host === 'lockscreen' ? 'ts/lockscreen/main' : host}.ts ` +
'--external resource:///* ' +
'--external gi://* ' +
'--external cairo ' +
// Since bun wants to write in cwd, we just redirect stdin instead
`> ${outPath}`,
]).catch(print);
watchAndCompileSass(host);
return await import(`file://${outPath}`);
};

View file

@ -1,3 +0,0 @@
import { transpileTypeScript } from './js/utils.js';
export default (await transpileTypeScript('lockscreen')).default;

File diff suppressed because it is too large Load diff

View file

@ -1,18 +0,0 @@
{
"name": "ags-node-modules",
"version": "0.0.0",
"main": "config.js",
"type": "module",
"dependencies": {
"@eslint/js": "9.13.0",
"@stylistic/eslint-plugin": "2.9.0",
"@types/eslint__js": "8.42.3",
"@types/node": "22.7.7",
"eslint": "9.13.0",
"eslint-plugin-jsdoc": "50.4.3",
"fzf": "0.5.2",
"jiti": "2.3.3",
"typescript": "5.6.3",
"typescript-eslint": "8.10.0"
}
}

View file

@ -1,95 +0,0 @@
.applauncher {
all: unset;
border: 2px solid $contrast-bg;
background-color: $bg;
color: #f8f8f2;
padding: 2px;
* {
font-size: 16px;
}
list,
row {
all: unset;
}
.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: 10px;
padding-bottom: 0;
min-width: 900px;
min-height: 650px;
}
.placeholder {
margin-top: 9px;
color: #f8f8f2;
font-size: 1.2em;
}
.app {
all: unset;
transition: 200ms;
border-radius: 9px;
label {
transition: 200ms;
&.title {
margin-top: 20px;
color: #f8f8f2;
}
&.description {
color: rgba(238, 238, 238, 0.7);
}
}
image {
transition: 200ms;
margin: 0 8px;
}
&:active {
background-color: rgba($contrast-bg, 0.5);
box-shadow: inset 0 0 0 3px rgba(238, 238, 238, 0.03);
}
}
*:selected,
.app:hover,
.app:focus {
* {
font-weight: unset;
}
label.title {
color: $contrast-bg;
}
image {
-gtk-icon-shadow: 2px 2px $contrast-bg;
}
}
}

View file

@ -1,41 +0,0 @@
.bar {
.clock,
.notif-panel {
border: 2px solid $contrast-bg;
padding: 4.5px 7px;
background-color: $bgfull;
}
.sys-tray {
border: 2px solid $contrast-bg;
menubar {
background-color: $bgfull;
padding: 2.5px;
}
menuitem {
image {
color: #CBA6F7;
}
padding: 0 2px;
* {
font-size: 25px;
}
}
menuitem:first-child image {
padding-left: 4px;
}
}
.current-window {
border: 2px solid $contrast-bg;
font-size: 18px;
padding-right: 7px;
background-color: $bgfull;
}
}

View file

@ -1,62 +0,0 @@
.clipboard {
all: unset;
border: 2px solid $contrast-bg;
background-color: $bg;
color: #f8f8f2;
padding: 2px;
* {
font-size: 16px;
}
list,
row {
all: unset;
}
.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: 10px;
padding-bottom: 0;
min-width: 900px;
min-height: 750px;
}
.item {
all: unset;
transition: 200ms;
border-radius: 9px;
padding: 10px;
}
*:selected .item,
*:hover .item,
*:focus .item {
background-color: #363449;
}
.down-arrow {
transition: all 500ms;
opacity: 0.5;
}
}

View file

@ -1,74 +0,0 @@
.date {
background-color: $bg;
color: $fg;
border: 2px solid $contrast-bg;
}
.timebox {
margin: 30px 0;
.time-container {
.content {
font-weight: bolder;
font-size: 60px;
}
.divider {
margin: 8px 15px;
padding: 0 1px;
background: linear-gradient($red, $magenta, $blue, $cyan);
}
}
.date-container {
margin-top: 2px;
}
}
.cal-box {
border-radius: 30px;
padding: 0 1rem .2rem;
color: $fg;
background-color: $bgfull;
border-bottom: 2px solid $contrast-bg;
border-top: 2px solid $contrast-bg;
margin: 0 12px 18px;
.cal {
font-size: 20px;
background-color: inherit;
padding: .5rem .10rem 0;
margin-left: 10px;
border-radius: 30px;
&>* {
border: solid 0 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

@ -1,109 +0,0 @@
.notification-center {
border: 2px solid $contrast-bg;
min-height: 700px;
min-width: 524px;
background: $bg;
padding: 0;
* {
font-size: 16px;
}
.header {
padding: 10px;
margin-top: 22px;
margin-bottom: 9px;
label {
font-size: 22px;
}
.clear {
box {
all: unset;
transition: 200ms;
color: #eee;
background-color: #664C90;
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
padding: 4.5px 9px;
}
&:hover box {
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 {
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;
border-top: 2px solid $contrast-bg;
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

@ -1,207 +0,0 @@
$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;
background-color: $bg;
padding: 16.2px;
border: 2px solid $contrast-bg;
* {
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 {
margin-right: 9px;
}
.icon.img {
border: 1px solid rgba(238, 238, 238, 0.03);
}
.actions {
button {
all: unset;
transition: all 500ms;
background-color: $background-color-3;
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
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;
background-color: $background-color-5;
background-image: none;
box-shadow: none;
margin-left: 9px;
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-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

@ -1,40 +0,0 @@
.osd {
padding: 12px 20px;
background: rgba(40, 42, 54, 0.8);
border: 2px solid $contrast-bg;
label {
min-width: 170px;
}
progressbar:disabled {
opacity: 0.5;
}
progressbar {
min-height: 6px;
min-width: 170px;
border-radius: 999px;
background: transparent;
border: none;
trough {
background: #363847;
min-height: inherit;
border: none;
}
progress {
background: #79659f;
min-height: inherit;
border: none;
}
}
image {
font-size: 2rem;
color: white;
margin-left: -0.4rem;
margin-right: 0.8rem;
}
}

View file

@ -1,48 +0,0 @@
.powermenu {
background-color: $bg;
color: $fg;
padding: 10px;
font-family: "MesloLGS NF";
font-size: 70px;
border: 2px solid $contrast-bg;
label {
min-width: 140px;
min-height: 130px;
}
.button {
margin: 5px 10px;
min-width: 80px;
transition: all ease .2s;
&:hover {
background-color: $bg-secondary;
}
&:active {
background-color: $bg-secondary;
}
.content {
padding: 0 15px;
}
}
.shutdown {
color: $red;
}
.reboot {
color: $magenta;
}
.logout {
color: $yellow;
}
}
.powermenu-clickhandler {
background-color: black;
}

View file

@ -1,66 +0,0 @@
.screenshot {
background-color: $bg;
color: $fg;
border: 2px solid $contrast-bg;
label {
font-size: 20pt;
}
.header {
border-bottom: 2px solid $contrast-bg;
.header-btn {
transition: 200ms;
&:hover {
background-color: rgba(23, 23, 23, 0.7);
image {
-gtk-icon-shadow: 1px 1px $contrast-bg;
}
}
label {
margin: 10px;
font-weight: bold;
}
image {
font-size: 30px;
}
}
}
scrolledwindow {
min-height: 400px;
min-width: 800px;
padding: 0 10px;
box {
.item-btn {
transition: 200ms;
&:hover {
background-color: rgba(23, 23, 23, 0.7);
}
}
label {
padding: 5px;
}
image {
font-size: 30px;
}
&:first-child label {
padding-top: 10px;
}
&:last-child label {
padding-bottom: 10px;
}
}
}
}

View file

@ -1,20 +0,0 @@
window,
button,
eventbox,
box,
progressbar,
trough,
undershoot {
all: unset;
}
@import './common';
@import './binto-widgets/applauncher';
@import './binto-widgets/bar';
@import './binto-widgets/clipboard';
@import './binto-widgets/date';
@import './binto-widgets/notification';
@import './binto-widgets/notification-center';
@import './binto-widgets/osd';
@import './binto-widgets/powermenu';
@import './binto-widgets/screenshot';

View file

@ -1,65 +0,0 @@
$darkbg: #0b0d16;
$bg: rgba(40, 42, 54, 0.8); // rgba(69, 71, 90, 0.3); #0d0f18;
$bgfull: rgb(40, 42, 54);
$contrast-bg: rgba(189, 147, 249, 0.8);
$contrast-bg-full: rgba(189, 147, 249, 1);
$bg-secondary: rgba(#382c4a, 0.8);
$bg-secondary-alt: #a5b6cf;
$fg: #a5b6cf;
$fg-dim: #a5b6cf;
$watermelon: #dd6777;
// Aliases
$background: $bg;
$background-secondary: $bg-secondary;
$background-secondary-alt: $bg-secondary-alt;
$foreground: $fg;
$foreground-dim: $fg-dim;
$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;
$javacafe-magenta: #c296eb;
$javacafe-blue: #86aaec;
scrolledwindow {
scrollbar,
scrollbar * {
all: unset;
}
scrollbar.vertical {
transition: 200ms;
background-color: rgba(23, 23, 23, 0.3);
margin: 20px 0;
&: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;
}
}
}

View file

@ -1,10 +0,0 @@
@import './common';
window {
background-color: transparent;
}
.clock {
font-size: 80pt;
font-family: 'Ubuntu Mono';
}

View file

@ -1,95 +0,0 @@
.applauncher {
all: unset;
border: 2px solid $contrast-bg;
border-radius: 25px;
background-color: $bg;
color: #f8f8f2;
padding: 2px;
* {
font-size: 16px;
}
list,
row {
all: unset;
}
.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: 10px;
padding-bottom: 0;
min-width: 700px;
min-height: 450px;
}
.placeholder {
margin-top: 9px;
color: #f8f8f2;
font-size: 1.2em;
}
.app {
all: unset;
transition: 200ms;
border-radius: 9px;
label {
transition: 200ms;
&.title {
color: #f8f8f2;
}
&.description {
color: rgba(238, 238, 238, 0.7);
}
}
image {
transition: 200ms;
margin: 0 8px;
}
&:active {
background-color: rgba($contrast-bg, 0.5);
box-shadow: inset 0 0 0 3px rgba(238, 238, 238, 0.03);
}
}
*:selected,
.app:hover,
.app:focus {
* {
font-weight: unset;
}
label.title {
color: $contrast-bg;
}
image {
-gtk-icon-shadow: 2px 2px $contrast-bg;
}
}
}

View file

@ -1,63 +0,0 @@
.clipboard {
all: unset;
border: 2px solid $contrast-bg;
border-radius: 25px;
background-color: $bg;
color: #f8f8f2;
padding: 2px;
* {
font-size: 16px;
}
list,
row {
all: unset;
}
.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: 10px;
padding-bottom: 0;
min-width: 900px;
min-height: 750px;
}
.item {
all: unset;
transition: 200ms;
border-radius: 9px;
padding: 10px;
}
*:selected .item,
*:hover .item,
*:focus .item {
background-color: #363449;
}
.down-arrow {
transition: all 500ms;
opacity: 0.5;
}
}

View file

@ -1,75 +0,0 @@
.date {
background-color: $bg;
color: $fg;
border-radius: 30px;
border: 2px solid $contrast-bg;
}
.timebox {
margin: 30px 0;
.time-container {
.content {
font-weight: bolder;
font-size: 60px;
}
.divider {
margin: 8px 15px;
padding: 0 1px;
background: linear-gradient($red, $magenta, $blue, $cyan);
}
}
.date-container {
margin-top: 2px;
}
}
.cal-box {
border-radius: 30px;
padding: 0 1rem .2rem;
color: $fg;
background-color: $bgfull;
border-bottom: 2px solid $contrast-bg;
border-top: 2px solid $contrast-bg;
margin: 0 12px 18px;
.cal {
font-size: 20px;
background-color: inherit;
padding: .5rem .10rem 0;
margin-left: 10px;
border-radius: 30px;
&>* {
border: solid 0 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

@ -1,113 +0,0 @@
.notification-center {
min-height: 700px;
min-width: 524px;
background: $bg;
border-radius: 30px;
border-top-right-radius: 0;
border: 2px solid $contrast-bg;
padding: 0;
* {
font-size: 16px;
}
.header {
padding: 10px;
margin-top: 22px;
margin-bottom: 9px;
label {
font-size: 22px;
}
.clear {
box {
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 {
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 {
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;
border-radius: 30px;
border-top: 2px solid $contrast-bg;
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

@ -1,210 +0,0 @@
$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;
margin: 9px 9px 0;
border: 2px solid $contrast-bg;
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;
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;
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-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

@ -1,43 +0,0 @@
.osd {
padding: 12px 20px;
border-radius: 999px;
background: rgba(40, 42, 54, 0.8);
border: 2px solid $contrast-bg;
label {
min-width: 170px;
}
progressbar:disabled {
opacity: 0.5;
}
progressbar {
min-height: 6px;
min-width: 170px;
border-radius: 999px;
background: transparent;
border: none;
trough {
background: #363847;
min-height: inherit;
border-radius: inherit;
border: none;
}
progress {
background: #79659f;
min-height: inherit;
border-radius: inherit;
border: none;
}
}
image {
font-size: 2rem;
color: white;
margin-left: -0.4rem;
margin-right: 0.8rem;
}
}

View file

@ -1,99 +0,0 @@
.thingy {
border-radius: 2rem 2rem 0 0;
min-height: 2.7rem;
min-width: 20rem;
.settings {
padding: 0.5rem;
.button {
background-color: $bgfull;
border: 0.1rem solid $darkbg;
border-radius: 0.7rem;
padding: 0.3rem;
&.toggled {
background-color: $contrast-bg;
}
}
}
}
.osk {
padding-top: 4px;
border-radius: 10px 10px 0;
.side {
.key {
&:active label {
background-color: $contrast-bg;
}
label {
background-color: $bg;
border: 0.08rem solid $darkbg;
border-radius: 0.7rem;
min-height: 3rem;
transition: background-color 0.2s ease-in-out,
border-color 0.2s ease-in-out;
&.normal,
&.Super {
min-width: 3rem;
}
&.Tab,
&.Backspace {
min-width: 7rem;
}
&.Enter,
&.Caps {
min-width: 8rem;
}
&.Shift {
min-width: 9rem;
}
&.Space {
min-width: 20rem;
}
&.PrtSc,
&.AltGr {
min-width: 3.2rem;
}
&.active {
background-color: $darkbg;
}
&.altgr {
border: 0.08rem solid blue;
}
}
}
&.right-side {
.key .mod {
&.Ctrl {
min-width: 2.4rem;
}
}
}
&.left-side {
.key .mod {
&.Alt {
min-width: 3rem;
}
&.Ctrl {
min-width: 4rem;
}
}
}
}
}

View file

@ -1,116 +0,0 @@
.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 $contrast-bg;
border-bottom: 2px solid $contrast-bg;
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 0 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: 0;
border-radius: 2em;
}
trough {
margin: 0 8px;
border-radius: 2em;
}
slider {
margin: -8px;
min-height: 20px;
border-radius: 10px;
transition: background-color 0.5s ease-in-out;
}
slider:hover {
transition: background-color 0.5s ease-in-out;
}
}

View file

@ -1,52 +0,0 @@
.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 $contrast-bg;
label {
min-width: 140px;
min-height: 130px;
}
.button {
margin: 5px 10px;
border-radius: 12px;
min-width: 80px;
transition: all ease .2s;
&:hover {
background-color: $bg-secondary;
}
&:active {
background-color: $bg-secondary;
}
.content {
border-radius: 4px;
padding: 0 15px;
}
}
.shutdown {
color: $red;
}
.reboot {
color: $magenta;
}
.logout {
color: $yellow;
}
}
.powermenu-clickhandler {
background-color: black;
}

View file

@ -1,184 +0,0 @@
.quick-settings {
font-size: 30px;
min-width: 500px;
padding: 0;
background-color: $bg;
border-radius: 30px 0 30px 30px;
border: 2px solid $contrast-bg;
}
.title {
font-size: 22px;
margin-top: 30px;
}
.grid-label {
font-size: 30px;
margin-left: 15px;
margin-right: 10px;
min-width: 50px;
}
.scrolled-indicator {
margin: 5px 0;
}
.menu {
margin: 10px;
padding: 0;
border: 1.5px solid $contrast-bg;
border-radius: 10px;
font-size: 12px;
scrolledwindow {
padding: 3px;
}
row {
padding: 0;
margin: 0;
}
.menu-item {
margin: 5px;
label {
font-size: 16px;
margin-left: 5px;
}
image {
font-size: 20px;
}
}
}
.sub-label {
font-size: 14px;
padding: 3px;
border: 2px solid $contrast-bg;
border-radius: 10px 20px 20px 10px;
min-width: 106px;
background: #1b1b1b;
margin-top: 5px;
}
.grid-chev {
margin-left: 10px;
margin-right: 12px;
font-size: 25px;
transition: -gtk-icon-transform 0.3s ease-in-out;
}
.button-grid {
font-size: 10px;
min-width: 440px;
background-color: $bgfull;
border-top: 2px solid $contrast-bg;
border-bottom: 2px solid $contrast-bg;
border-radius: 15px;
padding: 10px 15px;
}
.grid-button {
min-height: 65px;
min-width: 70px;
}
.left-part {
background: #1b1b1b;
border-top-left-radius: 15px;
border-bottom-left-radius: 15px;
border-left: 2px solid $contrast-bg;
border-top: 2px solid $contrast-bg;
border-bottom: 2px solid $contrast-bg;
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 $contrast-bg;
border-top: 2px solid $contrast-bg;
border-bottom: 2px solid $contrast-bg;
transition: all 0.5s ease-in-out;
}
.right-part:hover,
.right-part:active {
color: $contrast-bg;
border: 2px solid $contrast-bg;
border-top-left-radius: 7px;
border-bottom-left-radius: 7px;
transition: all 0.5s ease-in-out;
}
.left-part:hover,
.left-part:active {
color: $contrast-bg;
border: 2px solid $contrast-bg;
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 $contrast-bg;
border-bottom: 2px solid $contrast-bg;
border-radius: 15px;
margin-top: 30px;
margin-bottom: 20px;
.slider-label {
font-size: 30px;
min-width: 40px;
margin-right: -20px;
}
.slider {
min-height: 55px;
margin-right: -15px;
scale {
min-width: 400px;
margin-left: 18px;
margin-right: 20px;
highlight {
margin: 0;
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%;
transition: background-color 0.5s ease-in-out;
}
slider:hover {
background-color: #303240;
transition: background-color 0.5s ease-in-out;
}
}
}
}

View file

@ -1,76 +0,0 @@
.screenshot {
background-color: $bg;
color: $fg;
border: 2px solid $contrast-bg;
border-radius: 25px;
label {
font-size: 20pt;
}
.header {
border-bottom: 2px solid $contrast-bg;
.header-btn {
transition: 200ms;
&:hover,
&.active {
background-color: rgba(23, 23, 23, 0.7);
image {
-gtk-icon-shadow: 1px 1px $contrast-bg;
}
}
label {
margin: 10px;
font-weight: bold;
}
image {
font-size: 30px;
}
&:first-child {
border-top-left-radius: 25px;
}
&:last-child {
border-top-right-radius: 25px;
}
}
}
scrolledwindow {
min-height: 400px;
min-width: 800px;
padding: 0 10px;
box {
.item-btn {
transition: 200ms;
&:hover {
background-color: rgba(23, 23, 23, 0.7);
}
}
label {
padding: 5px;
}
image {
font-size: 30px;
}
&:first-child label {
padding-top: 10px;
}
&:last-child label {
padding-bottom: 10px;
}
}
}
}

View file

@ -1,33 +0,0 @@
.sys-tray {
padding: 5px;
background-color: $bg;
border-radius: 80px;
border: 2px solid $bg-secondary;
transition: background-color 0.5s ease-in-out,
border 0.5s ease-in-out;
menuitem {
image {
color: #CBA6F7;
}
background-color: transparent;
padding: 0 2px;
border-radius: 100%;
transition: all 0.5s ease-in-out;
&:hover {
border-radius: 100%;
transition: all 0.5s ease-in-out;
}
* {
font-size: 25px;
border-radius: 10px;
}
}
menubar {
background-color: transparent;
}
}

View file

@ -1,123 +0,0 @@
.bar {
margin: 5px;
}
.current-window {
color: #CBA6F7;
font-size: 18px;
}
.osk-toggle,
.tablet-toggle,
.heart-toggle {
font-size: 28px;
min-height: 40px;
min-width: 53px;
}
.heart-toggle {
font-size: 28px;
min-height: 40px;
color: #CBA6F7;
}
.notif-panel {
font-size: 20px;
min-height: 37px;
min-width: 105px;
}
.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 $bg-secondary;
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 $contrast-bg;
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: 0 15px;
}
.audio,
.bluetooth,
.brightness,
.keyboard {
padding: 0 10px;
font-size: 20px;
margin-right: -10px;
}
.network {
padding: 0 10px;
font-size: 20px;
}
.bg-text {
color: $bg;
font-weight: bold;
}
.battery {
padding: 0 10px;
font-size: 20px;
.battery-indicator {
&.charging {
color: green;
}
&.charged {
// TODO: charged battery style
}
&.low {
color: red;
}
}
icon {
.charging {
// TODO: charging battery style
}
.discharging {
// TODO: discharging battery style
}
}
label {
font-size: 20px;
}
}
tooltip {
background: rgba(0, 0, 0, 0.6);
border-radius: 5px;
}

View file

@ -1,34 +0,0 @@
.bar {
.workspaces {
background-color: $bg;
border-radius: 80px;
border: 2px solid $bg-secondary;
padding: 3px 12px;
}
.button {
margin: 0 2.5px;
min-height: 22px;
min-width: 22px;
border-radius: 100%;
border: 2px solid transparent;
}
.occupied {
border: 2px solid $bg;
background: $contrast-bg;
transition: background-color 0.6s ease-in-out;
}
.urgent {
border: 2px solid $bg;
background: red;
transition: background-color 0.6s ease-in-out;
}
.active {
border: 2px solid #50fa7b;
transition: margin-left 0.5s cubic-bezier(0.165, 0.84, 0.44, 1);
}
}

View file

@ -1,25 +0,0 @@
window,
button,
eventbox,
box,
progressbar,
trough,
undershoot {
all: unset;
}
@import './common';
@import './wim-widgets/powermenu';
@import './wim-widgets/traybuttons';
@import './wim-widgets/workspaces';
@import './wim-widgets/systray';
@import './wim-widgets/notification-center';
@import './wim-widgets/notification';
@import './wim-widgets/date';
@import './wim-widgets/quick-settings';
@import './wim-widgets/player';
@import './wim-widgets/applauncher';
@import './wim-widgets/osd';
@import './wim-widgets/osk';
@import './wim-widgets/clipboard';
@import './wim-widgets/screenshot';

View file

@ -1,151 +0,0 @@
const { exec, execAsync } = Utils;
const KBD = 'tpacpi::kbd_backlight';
const INTERVAL = 500;
const SCREEN_ICONS = {
90: 'display-brightness-high-symbolic',
70: 'display-brightness-medium-symbolic',
20: 'display-brightness-low-symbolic',
5: 'display-brightness-off-symbolic',
};
class Brightness extends Service {
static {
Service.register(this, {
screen: ['float'],
kbd: ['float'],
caps: ['int'],
}, {
'screen-icon': ['string', 'rw'],
'caps-icon': ['string', 'rw'],
});
}
#kbd = 0;
#kbdMax = 0;
#screen = 0;
#screenIcon = 'display-brightness-symbolic';
#capsName = 'input0::capslock';
#caps = 0;
#capsIcon = 'caps-lock-symbolic';
get capsName() {
return this.#capsName;
}
set capsName(value: string) {
this.#capsName = value;
}
get kbd() {
return this.#kbd;
}
set kbd(value) {
if (value < 0 || value > this.#kbdMax) {
return;
}
execAsync(`brightnessctl -d ${KBD} s ${value} -q`)
.then(() => {
this.#kbd = value;
this.emit('kbd', this.#kbd);
})
.catch(console.error);
}
get screen() {
return this.#screen;
}
set screen(percent) {
if (percent < 0) {
percent = 0;
}
if (percent > 1) {
percent = 1;
}
execAsync(`brightnessctl s ${percent * 100}% -q`)
.then(() => {
this.#screen = percent;
this.#getScreenIcon();
this.emit('screen', this.#screen);
})
.catch(console.error);
}
get screenIcon() {
return this.#screenIcon;
}
get caps() {
return this.#caps;
}
get capsIcon() {
return this.#capsIcon;
}
constructor() {
super();
try {
this.#monitorKbdState();
this.#kbdMax = Number(exec(`brightnessctl -d ${KBD} m`));
this.#caps = Number(exec(`bash -c brightnessctl -d ${this.#capsName} g`));
this.#screen = Number(exec('brightnessctl g')) / Number(exec('brightnessctl m'));
}
catch (_e) {
console.error('missing dependancy: brightnessctl');
}
}
#getScreenIcon() {
const brightness = this.#screen * 100;
// eslint-disable-next-line
for (const threshold of [4, 19, 69, 89]) {
if (brightness > threshold + 1) {
this.#screenIcon = SCREEN_ICONS[threshold + 1];
this.notify('screen-icon');
}
}
}
fetchCapsState() {
execAsync(`brightnessctl -d ${this.#capsName} g`)
.then((out) => {
this.#caps = Number(out);
this.#capsIcon = this.#caps ?
'caps-lock-symbolic' :
'capslock-disabled-symbolic';
this.notify('caps-icon');
this.emit('caps', this.#caps);
})
.catch(logError);
}
#monitorKbdState() {
const interval = setInterval(() => {
execAsync(`brightnessctl -d ${KBD} g`).then(
(out) => {
if (parseInt(out) !== this.#kbd) {
this.#kbd = parseInt(out);
this.emit('kbd', this.#kbd);
return this.#kbd;
}
},
).catch(() => {
// @ts-expect-error this works in ags
interval.destroy();
});
}, INTERVAL);
}
}
const brightnessService = new Brightness();
export default brightnessService;

View file

@ -1,147 +0,0 @@
const { execAsync, subprocess } = Utils;
class Clipboard extends Service {
static {
Service.register(this, {
'clip-added': ['jsobject'],
'history-searched': [],
}, {
clips: ['jsobject'],
});
}
private static _MAX_CLIPS = 100;
// Class Attributes
private _clips_left = 0;
private _clips = new Map<number, { clip: string, isImage: boolean }>();
get clips() {
return this._clips;
}
constructor() {
super();
this._getHistory();
this._watchClipboard();
}
// Public Class Methods
public copyOldItem(key: string | number): void {
execAsync([
'bash', '-c', `cliphist list | grep '^${key}' | cliphist decode | wl-copy`,
]);
}
// Private Class Methods
private _decrementClipsLeft() {
if (--this._clips_left === 0) {
this.emit('history-searched');
// this is necessary when not putting a cap on clip amount
// exec(`prlimit --pid ${exec('pgrep ags')} --nofile=1024:`);
}
}
private async _decodeItem(index: string): Promise<string | null> {
try {
const decodedItem = await execAsync([
'bash', '-c', `cliphist list | grep -a ${index} | cliphist decode`,
]);
this._decrementClipsLeft();
return decodedItem;
}
catch (error) {
console.error(error);
this._decrementClipsLeft();
return null;
}
}
private _addClip(newClip: [number, { clip: string, isImage: boolean }]) {
if (
![...this.clips.values()]
.map((c) => c.clip)
.includes(newClip[1].clip)
) {
this._clips.set(...newClip);
this.emit('clip-added', newClip);
}
else {
const oldClip = [...this.clips.entries()]
.find(([_, { clip }]) => clip === newClip[1].clip);
if (oldClip && oldClip[0] < newClip[0]) {
this.clips.delete(oldClip[0]);
this._clips.set(...newClip);
this.emit('clip-added', newClip);
}
}
}
private _getHistory(n = Clipboard._MAX_CLIPS) {
// this is necessary when not putting a cap on clip amount
// exec(`prlimit --pid ${exec('pgrep ags')} --nofile=10024:`);
// This command comes from '../../clipboard/script.sh'
execAsync('clipboard-manager').then((out) => {
const rawClips = out.split('\n');
this._clips_left = Math.min(rawClips.length - 1, n);
rawClips.forEach(async(clip, i) => {
if (i > n) {
return;
}
if (clip.includes('img')) {
this._decrementClipsLeft();
const newClip: [number, { clip: string, isImage: boolean }] = [
parseInt((clip.match('[0-9]+') ?? [''])[0]),
{
clip,
isImage: true,
},
];
this._addClip(newClip);
}
else {
const decodedClip = await this._decodeItem(clip);
if (decodedClip) {
const newClip: [number, { clip: string, isImage: boolean }] = [
parseInt(clip),
{
clip: decodedClip,
isImage: false,
},
];
this._addClip(newClip);
}
}
});
}).catch((error) => console.error(error));
}
private _watchClipboard() {
subprocess(
['wl-paste', '--watch', 'echo'],
() => {
this._getHistory(1);
},
() => { /**/ },
);
}
}
const clipboard = new Clipboard();
export default clipboard;

View file

@ -1,80 +0,0 @@
const { execAsync, subprocess } = Utils;
const Notifications = await Service.import('notifications');
const APP_NAME = 'gpu-screen-recorder';
const START_APP_ID = 2345;
const ICON_NAME = 'nvidia';
class GSR extends Service {
static {
Service.register(this, {}, {});
}
#appID = START_APP_ID;
constructor() {
super();
subprocess(
['gsr-start'],
(path) => {
Notifications.getNotification(this.#appID)?.close();
const notifId = Notifications.Notify(
APP_NAME,
++this.#appID,
ICON_NAME,
'Replay Saved',
`Saved to ${path}`,
['folder', 'Open Folder', 'video', 'Open Video', 'kdenlive', 'Edit in kdenlive'],
{},
Notifications.popupTimeout,
);
Notifications.getNotification(notifId)?.connect(
'invoked',
(_, actionId: string) => {
if (actionId === 'folder') {
execAsync([
'xdg-open',
path.substring(0, path.lastIndexOf('/')),
]).catch(print);
}
else if (actionId === 'video') {
execAsync(['xdg-open', path]).catch(print);
}
else if (actionId === 'kdenlive') {
execAsync([
'bash',
'-c',
`kdenlive -i ${path}`,
]).catch(print);
}
},
);
},
() => { /**/ },
);
}
saveReplay() {
execAsync(['gpu-save-replay']).then(() => {
Notifications.Notify(
APP_NAME,
this.#appID,
ICON_NAME,
'Saving Replay',
'Last 20 minutes',
[],
{},
Notifications.popupTimeout,
);
}).catch(console.error);
}
}
export default GSR;

View file

@ -1,228 +0,0 @@
const Hyprland = await Service.import('hyprland');
const { subprocess } = Utils;
const ON_RELEASE_TRIGGERS = [
'released',
'TOUCH_UP',
'HOLD_END',
];
const ON_CLICK_TRIGGERS = [
'pressed',
'TOUCH_DOWN',
];
// Types
import { PopupWindow } from 'global-types';
import { Subprocess } from 'types/@girs/gio-2.0/gio-2.0.cjs';
interface Layer {
address: string
x: number
y: number
w: number
h: number
namespace: string
}
interface Levels {
0?: Layer[] | null
1?: Layer[] | null
2?: Layer[] | null
3?: Layer[] | null
}
interface Layers {
levels: Levels
}
interface CursorPos {
x: number
y: number
}
class Pointers extends Service {
static {
Service.register(this, {
'proc-started': ['boolean'],
'proc-destroyed': ['boolean'],
'device-fetched': ['boolean'],
'new-line': ['string'],
'released': ['string'],
'clicked': ['string'],
});
}
#process = null as Subprocess | null;
#lastLine = '';
#pointers = [] as string[];
get process() {
return this.#process;
}
get lastLine() {
return this.#lastLine;
}
get pointers() {
return this.#pointers;
}
constructor() {
super();
this.#initAppConnection();
}
startProc() {
if (this.#process) {
return;
}
this.#process = subprocess(
['libinput', 'debug-events'],
(output) => {
if (output.includes('cancelled')) {
return;
}
if (ON_RELEASE_TRIGGERS.some((p) => output.includes(p))) {
this.#lastLine = output;
Pointers.detectClickedOutside('released');
this.emit('released', output);
this.emit('new-line', output);
}
if (ON_CLICK_TRIGGERS.some((p) => output.includes(p))) {
this.#lastLine = output;
Pointers.detectClickedOutside('clicked');
this.emit('clicked', output);
this.emit('new-line', output);
}
},
);
this.emit('proc-started', true);
}
killProc() {
if (this.#process) {
this.#process.force_exit();
this.#process = null;
this.emit('proc-destroyed', true);
}
}
#initAppConnection() {
App.connect('window-toggled', () => {
const anyVisibleAndClosable =
(App.windows as PopupWindow[]).some((w) => {
const closable = w.close_on_unfocus &&
!(
w.close_on_unfocus === 'none' ||
w.close_on_unfocus === 'stay'
);
return w.visible && closable;
});
if (anyVisibleAndClosable) {
this.startProc();
}
else {
this.killProc();
}
});
}
static detectClickedOutside(clickStage: string) {
const toClose = ((App.windows as PopupWindow[])).some((w) => {
const closable = (
w.close_on_unfocus &&
w.close_on_unfocus === clickStage
);
return w.visible && closable;
});
if (!toClose) {
return;
}
Hyprland.messageAsync('j/layers').then((response) => {
const layers = JSON.parse(response) as { Layers: Layers };
Hyprland.messageAsync('j/cursorpos').then((res) => {
const pos = JSON.parse(res) as CursorPos;
Object.values(layers).forEach((key) => {
const overlayLayer = key.levels['3'];
if (overlayLayer) {
const noCloseWidgetsNames = [
'bar-',
'osk',
];
const getNoCloseWidgets = (names: string[]) => {
const arr = [] as Layer[];
names.forEach((name) => {
arr.push(
overlayLayer.find(
(n) => n.namespace.startsWith(name),
) ||
// Return an empty Layer if widget doesn't exist
{
address: '',
x: 0,
y: 0,
w: 0,
h: 0,
namespace: '',
},
);
});
return arr;
};
const clickIsOnWidget = (w: Layer) => {
return pos.x > w.x && pos.x < w.x + w.w &&
pos.y > w.y && pos.y < w.y + w.h;
};
const noCloseWidgets =
getNoCloseWidgets(noCloseWidgetsNames);
const widgets = overlayLayer.filter((n) => {
let window = null as null | PopupWindow;
if (App.windows.some((win) =>
win.name === n.namespace)) {
window = (App
.getWindow(n.namespace) as PopupWindow);
}
return window &&
window.close_on_unfocus &&
window.close_on_unfocus ===
clickStage;
});
if (noCloseWidgets.some(clickIsOnWidget)) {
// Don't handle clicks when on certain widgets
}
else {
widgets.forEach(
(w) => {
if (!(pos.x > w.x && pos.x < w.x + w.w &&
pos.y > w.y && pos.y < w.y + w.h)) {
App.closeWindow(w.namespace);
}
},
);
}
}
});
}).catch(print);
}).catch(print);
}
}
export default new Pointers();

View file

@ -1,172 +0,0 @@
const Hyprland = await Service.import('hyprland');
const { execAsync, subprocess } = Utils;
import TouchGestures from './touch-gestures.ts';
const ROTATION_MAP = {
'normal': 0,
'right-up': 3,
'bottom-up': 2,
'left-up': 1,
};
const SCREEN = 'desc:BOE 0x0964';
const DEVICES = [
'wacom-hid-52eb-finger',
'wacom-hid-52eb-pen',
];
// Types
import { Subprocess } from 'types/@girs/gio-2.0/gio-2.0.cjs';
class Tablet extends Service {
static {
Service.register(this, {
'device-fetched': ['boolean'],
'autorotate-started': ['boolean'],
'autorotate-destroyed': ['boolean'],
'autorotate-toggled': ['boolean'],
'inputs-blocked': ['boolean'],
'inputs-unblocked': ['boolean'],
'laptop-mode': ['boolean'],
'tablet-mode': ['boolean'],
'mode-toggled': ['boolean'],
'osk-toggled': ['boolean'],
});
}
#tabletMode = false;
#oskState = false;
#autorotate = null as Subprocess | null;
#blockedInputs = null as Subprocess | null;
get tabletMode() {
return this.#tabletMode;
}
get autorotateState() {
return this.#autorotate !== null;
}
get oskState() {
return this.#oskState;
}
set oskState(value: boolean) {
this.#oskState = value;
this.emit('osk-toggled', this.#oskState);
}
#blockInputs() {
if (this.#blockedInputs) {
return;
}
this.#blockedInputs = subprocess(['libinput', 'debug-events', '--grab',
'--device', '/dev/input/by-path/platform-i8042-serio-0-event-kbd',
'--device', '/dev/input/by-path/platform-i8042-serio-1-event-mouse',
'--device', '/dev/input/by-path/platform-AMDI0010:02-event-mouse',
'--device', '/dev/input/by-path/platform-thinkpad_acpi-event',
'--device', '/dev/video-bus'],
() => { /**/ });
this.emit('inputs-blocked', true);
}
#unblockInputs() {
if (this.#blockedInputs) {
this.#blockedInputs.force_exit();
this.#blockedInputs = null;
this.emit('inputs-unblocked', true);
}
}
setTabletMode() {
execAsync(['brightnessctl', '-d', 'tpacpi::kbd_backlight', 's', '0'])
.catch(print);
this.startAutorotate();
this.#blockInputs();
this.#tabletMode = true;
this.emit('tablet-mode', true);
this.emit('mode-toggled', true);
}
setLaptopMode() {
execAsync(['brightnessctl', '-d', 'tpacpi::kbd_backlight', 's', '2'])
.catch(print);
this.killAutorotate();
this.#unblockInputs();
this.#tabletMode = false;
this.emit('laptop-mode', true);
this.emit('mode-toggled', true);
}
toggleMode() {
if (this.#tabletMode) {
this.setLaptopMode();
}
else {
this.setTabletMode();
}
this.emit('mode-toggled', true);
}
startAutorotate() {
if (this.#autorotate) {
return;
}
this.#autorotate = subprocess(
['monitor-sensor'],
(output) => {
if (output.includes('orientation changed')) {
const index = output.split(' ').at(-1);
if (!index) {
return;
}
const orientation = ROTATION_MAP[index];
Hyprland.messageAsync(
`keyword monitor ${SCREEN},transform,${orientation}`,
).catch(print);
const batchRotate = DEVICES.map((dev) =>
`keyword device:${dev}:transform ${orientation}; `);
Hyprland.messageAsync(`[[BATCH]] ${batchRotate.flat()}`);
if (TouchGestures.gestureDaemon) {
TouchGestures.killDaemon();
TouchGestures.startDaemon();
}
}
},
);
this.emit('autorotate-started', true);
this.emit('autorotate-toggled', true);
}
killAutorotate() {
if (this.#autorotate) {
this.#autorotate.force_exit();
this.#autorotate = null;
this.emit('autorotate-destroyed', true);
this.emit('autorotate-toggled', false);
}
}
toggleOsk() {
this.#oskState = !this.#oskState;
this.emit('osk-toggled', this.#oskState);
}
}
const tabletService = new Tablet();
export default tabletService;

View file

@ -1,143 +0,0 @@
const { subprocess } = Utils;
const SCREEN = '/dev/input/by-path/platform-AMDI0010\:00-event';
const GESTURE_VERIF = [
'LR', // Left to Right
'RL', // Right to Left
'DU', // Down to Up
'UD', // Up to Down
'DLUR', // Down to Left to Up to Right (clockwise motion from Down)
'DRUL', // Down to Right to Up to Left (counter-clockwise from Down)
'URDL', // Up to Right to Down to Left (clockwise motion from Up)
'ULDR', // Up to Left to Down to Right (counter-clockwise from Up)
];
const EDGE_VERIF = [
'*', // Any
'N', // None
'L', // Left
'R', // Right
'T', // Top
'B', // Bottom
'TL', // Top left
'TR', // Top right
'BL', // Bottom left
'BR', // Bottom right
];
const DISTANCE_VERIF = [
'*', // Any
'S', // Short
'M', // Medium
'L', // Large
];
// Types
import { Subprocess } from 'types/@girs/gio-2.0/gio-2.0.cjs';
// TODO: add actmode param
// TODO: support multiple daemons for different thresholds
class TouchGestures extends Service {
static {
Service.register(this, {
'daemon-started': ['boolean'],
'daemon-destroyed': ['boolean'],
});
}
#gestures = new Map();
#gestureDaemon = null as Subprocess | null;
get gestures() {
return this.#gestures;
}
get gestureDaemon() {
return this.#gestureDaemon;
}
addGesture({
name,
nFingers = '1',
gesture,
edge = '*',
distance = '*',
command,
}) {
gesture = String(gesture).toUpperCase();
if (!GESTURE_VERIF.includes(gesture)) {
logError('Wrong gesture id');
return;
}
edge = String(edge).toUpperCase();
if (!EDGE_VERIF.includes(edge)) {
logError('Wrong edge id');
return;
}
distance = String(distance).toUpperCase();
if (!DISTANCE_VERIF.includes(distance)) {
logError('Wrong distance id');
return;
}
if (typeof command !== 'string') {
globalThis[name] = command;
command = `ags -r "${name}()"`;
}
this.#gestures.set(name, [
'-g',
`${nFingers},${gesture},${edge},${distance},${command}`,
]);
if (this.#gestureDaemon) {
this.killDaemon();
}
this.startDaemon();
}
startDaemon() {
if (this.#gestureDaemon) {
return;
}
let command = [
'lisgd', '-d', SCREEN,
// Orientation
'-o', '0',
// Threshold of gesture recognized
'-t', '125',
// Leniency of gesture angle
'-r', '25',
// Timeout time
'-m', '3200',
];
this.#gestures.forEach((gesture) => {
command = command.concat(gesture);
});
this.#gestureDaemon = subprocess(
command,
() => { /**/ },
);
this.emit('daemon-started', true);
}
killDaemon() {
if (this.#gestureDaemon) {
this.#gestureDaemon.force_exit();
this.#gestureDaemon = null;
this.emit('daemon-destroyed', true);
}
}
}
const touchGesturesService = new TouchGestures();
export default touchGesturesService;

View file

@ -1,63 +0,0 @@
const { Box, Icon, Label } = Widget;
const { lookUpIcon } = Utils;
import CursorBox from '../misc/cursorbox.ts';
/* Types */
import { Application } from 'types/service/applications.ts';
export default (app: Application) => {
const icon = Icon({ size: 42 });
const iconString = app.app.get_string('Icon');
if (app.icon_name) {
if (lookUpIcon(app.icon_name)) {
icon.icon = app.icon_name;
}
else if (iconString && iconString !== 'nix-snowflake') {
icon.icon = iconString;
}
else {
icon.icon = '';
}
}
const textBox = Box({
vertical: true,
vpack: 'start',
children: [
Label({
class_name: 'title',
label: app.name,
xalign: 0,
truncate: 'end',
}),
Label({
class_name: 'description',
label: app.description || '',
wrap: true,
xalign: 0,
justification: 'left',
}),
Label(),
],
});
return CursorBox({
hexpand: true,
class_name: 'app',
attribute: { app },
child: Box({
children: [
icon,
textBox,
],
}),
});
};

View file

@ -1,26 +0,0 @@
/* Types */
import { Application } from 'types/service/applications.ts';
const bash = async(strings: TemplateStringsArray | string, ...values: unknown[]) => {
const cmd = typeof strings === 'string' ?
strings :
strings.flatMap((str, i) => `${str}${values[i] ?? ''}`)
.join('');
return Utils.execAsync(['bash', '-c', cmd]).catch((err) => {
console.error(cmd, err);
return '';
});
};
export const launchApp = (app: Application) => {
const exe = app.executable
.split(/\s+/)
.filter((str) => !str.startsWith('%') && !str.startsWith('@'))
.join(' ');
bash(`${exe} &`);
app.frequency += 1;
};

View file

@ -1,76 +0,0 @@
const Applications = await Service.import('applications');
import { Fzf, FzfResultItem } from 'fzf';
import SortedList from '../misc/sorted-list.ts';
import AppItem from './app-item.ts';
import { launchApp } from './launch.ts';
/* Types */
import { Application } from 'types/service/applications.ts';
import { AgsAppItem } from 'global-types';
export default () => {
let fzfResults = [] as FzfResultItem<Application>[];
return SortedList({
name: 'applauncher',
class_name: 'applauncher',
transition: 'slide top',
on_select: (r) => {
App.closeWindow('win-applauncher');
launchApp((r.get_child() as AgsAppItem).attribute.app);
},
setup_list: (list, entry) => {
Applications.query('')
.flatMap((app) => AppItem(app))
.forEach((ch) => {
list.add(ch);
});
list.show_all();
list.set_sort_func((a, b) => {
const row1 = (a.get_children()[0] as AgsAppItem).attribute.app;
const row2 = (b.get_children()[0] as AgsAppItem).attribute.app;
if (entry.text === '' || entry.text === '-') {
a.set_visible(true);
b.set_visible(true);
return row2.frequency - row1.frequency;
}
else {
const s1 = fzfResults.find((r) => r.item.name === row1.name)?.score ?? 0;
const s2 = fzfResults.find((r) => r.item.name === row2.name)?.score ?? 0;
a.set_visible(s1 !== 0);
b.set_visible(s2 !== 0);
return s2 - s1;
}
});
},
on_text_change: (text, list, placeholder) => {
const fzf = new Fzf(Applications.list, {
selector: (app) => app.name + app.executable,
tiebreakers: [
(a, b) => b.item.frequency - a.item.frequency,
],
});
fzfResults = fzf.find(text);
list.invalidate_sort();
const visibleApps = list.get_children().filter((row) => row.visible).length;
placeholder.reveal_child = visibleApps <= 0;
},
});
};

View file

@ -1,57 +0,0 @@
const { Box, CenterBox } = Widget;
import Separator from '../misc/separator.ts';
import { get_gdkmonitor_from_desc } from '../lib.ts';
import BarRevealer from './fullscreen.ts';
import Clock from './items/cal-opener.ts';
import CurrentWindow from './items/current-window';
import NotifButton from './items/notif-button.ts';
import SysTray from './items/systray.ts';
const PADDING = 20;
export default () => BarRevealer({
gdkmonitor: get_gdkmonitor_from_desc('desc:Acer Technologies Acer K212HQL T3EAA0014201'),
exclusivity: 'exclusive',
anchor: ['bottom', 'left', 'right'],
bar: Box({
vertical: true,
children: [
CenterBox({
class_name: 'bar',
hexpand: true,
start_widget: Box({
hpack: 'start',
children: [
Separator(PADDING),
SysTray(),
],
}),
center_widget: Box({
children: [
CurrentWindow(),
Separator(PADDING / 2),
],
}),
end_widget: Box({
hpack: 'end',
children: [
NotifButton(),
Separator(PADDING / 2),
Clock(),
Separator(PADDING),
],
}),
}),
Separator(PADDING, { vertical: true }),
],
}),
});

View file

@ -1,156 +0,0 @@
const Hyprland = await Service.import('hyprland');
const { Box, EventBox, Revealer, Window } = Widget;
import Gdk from 'gi://Gdk?version=3.0';
import {
get_hyprland_monitor_desc,
get_monitor_desc_from_id,
} from '../lib.ts';
const FullscreenState = Variable({
monitors: [] as string[],
clientAddrs: new Map() as Map<string, string>,
});
Hyprland.connect('event', (hyprObj) => {
const arrayEquals = (a1: unknown[], a2: unknown[]) =>
a1.sort().toString() === a2.sort().toString();
const mapEquals = (m1: Map<string, string>, m2: Map<string, string>) =>
m1.size === m2.size &&
Array.from(m1.keys()).every((key) => m1.get(key) === m2.get(key));
const fs = FullscreenState.value;
const fsClients = hyprObj.clients.filter((c) => {
const mon = Hyprland.getMonitor(c.monitor);
return c.fullscreen &&
c.workspace.id === mon?.activeWorkspace.id;
});
const monitors = fsClients.map((c) =>
get_monitor_desc_from_id(c.monitor));
const clientAddrs = new Map(fsClients.map((c) => [
get_monitor_desc_from_id(c.monitor),
c.address,
]));
const hasChanged =
!arrayEquals(monitors, fs.monitors) ||
!mapEquals(clientAddrs, fs.clientAddrs);
if (hasChanged) {
FullscreenState.setValue({
monitors,
clientAddrs,
});
}
});
export default ({
anchor,
bar,
gdkmonitor = Gdk.Display.get_default()?.get_monitor(0) as Gdk.Monitor,
...rest
}) => {
const monitor = get_hyprland_monitor_desc(gdkmonitor);
const BarVisible = Variable(true);
FullscreenState.connect('changed', (v) => {
BarVisible.setValue(!v.value.monitors.includes(monitor));
});
const barCloser = Window({
name: `bar-${monitor}-closer`,
visible: false,
gdkmonitor,
anchor: ['top', 'bottom', 'left', 'right'],
layer: 'overlay',
child: EventBox({
on_hover: () => {
barCloser.set_visible(false);
BarVisible.setValue(false);
},
child: Box({
css: 'padding: 1px;',
}),
}),
});
// Hide bar instantly when out of focus
Hyprland.active.workspace.connect('changed', () => {
const addr = FullscreenState.value.clientAddrs.get(monitor);
if (addr) {
const client = Hyprland.getClient(addr);
if (client!.workspace.id !== Hyprland.active.workspace.id) {
BarVisible.setValue(false);
barCloser.visible = false;
}
}
});
const buffer = Box({
css: 'min-height: 10px',
visible: BarVisible.bind().as((v) => !v),
});
const vertical = anchor.includes('left') && anchor.includes('right');
const isBottomOrLeft = (
anchor.includes('left') && anchor.includes('right') && anchor.includes('bottom')
) || (
anchor.includes('top') && anchor.includes('bottom') && anchor.includes('left')
);
let transition: 'slide_up' | 'slide_down' | 'slide_left' | 'slide_right';
if (vertical) {
transition = isBottomOrLeft ? 'slide_up' : 'slide_down';
}
else {
transition = isBottomOrLeft ? 'slide_right' : 'slide_left';
}
const barWrap = Revealer({
reveal_child: BarVisible.bind(),
transition,
child: bar,
});
return Window({
name: `bar-${monitor}`,
layer: 'overlay',
gdkmonitor,
margins: [-1, -1, -1, -1],
anchor,
...rest,
attribute: {
barCloser,
},
child: EventBox({
child: Box({
css: 'min-height: 1px; padding: 1px;',
hexpand: true,
hpack: 'fill',
vertical,
children: isBottomOrLeft ?
[buffer, barWrap] :
[barWrap, buffer],
}),
}).on('enter-notify-event', () => {
if (!BarVisible.value) {
barCloser.visible = true;
BarVisible.setValue(true);
}
}),
});
};

View file

@ -1,21 +0,0 @@
const Audio = await Service.import('audio');
const { Label, Icon } = Widget;
import { SpeakerIcon } from '../../misc/audio-icons.ts';
import HoverRevealer from './hover-revealer.ts';
export default () => HoverRevealer({
class_name: 'audio',
icon: Icon({
icon: SpeakerIcon.bind(),
}),
label: Label().hook(Audio, (self) => {
if (Audio.speaker.volume) {
self.label =
`${Math.round(Audio.speaker.volume * 100)}%`;
}
}, 'speaker-changed'),
});

View file

@ -1,26 +0,0 @@
const Bluetooth = await Service.import('bluetooth');
const { Label, Icon } = Widget;
import HoverRevealer from './hover-revealer.ts';
export default () => HoverRevealer({
class_name: 'bluetooth',
icon: Icon().hook(Bluetooth, (self) => {
if (Bluetooth.enabled) {
self.icon = Bluetooth.connected_devices[0] ?
Bluetooth.connected_devices[0].icon_name :
'bluetooth-active-symbolic';
}
else {
self.icon = 'bluetooth-disabled-symbolic';
}
}),
label: Label().hook(Bluetooth, (self) => {
self.label = Bluetooth.connected_devices[0] ?
`${Bluetooth.connected_devices[0]}` :
'Disconnected';
}, 'notify::connected-devices'),
});

View file

@ -1,17 +0,0 @@
const { Icon, Label } = Widget;
import Brightness from '../../../services/brightness.ts';
import HoverRevealer from './hover-revealer.ts';
export default () => HoverRevealer({
class_name: 'brightness',
icon: Icon({
icon: Brightness.bind('screenIcon'),
}),
label: Label().hook(Brightness, (self) => {
self.label = `${Math.round(Brightness.screen * 100)}%`;
}, 'screen'),
});

View file

@ -1,48 +0,0 @@
const { Box, Revealer } = Widget;
import Gtk from 'gi://Gtk?version=3.0';
import Separator from '../../misc/separator.ts';
import CursorBox from '../../misc/cursorbox.ts';
export default ({
class_name,
icon,
label,
spacing = 5,
}) => {
const hoverRevLabel = Revealer({
transition: 'slide_right',
attribute: {
var: Variable(Box()),
},
child: Box({
children: [
Separator(spacing),
label,
],
}),
});
const widget = CursorBox({
on_hover: () => {
hoverRevLabel.reveal_child = true;
hoverRevLabel.attribute.var.value.set_state_flags(Gtk.StateFlags.PRELIGHT, false);
},
child: Box({
class_name,
children: [
icon,
hoverRevLabel,
],
}),
});
return widget;
};

View file

@ -1,54 +0,0 @@
const Hyprland = await Service.import('hyprland');
const { Icon, Label } = Widget;
import HoverRevealer from './hover-revealer.ts';
const DEFAULT_KB = 'at-translated-set-2-keyboard';
// Types
import { Keyboard, LabelGeneric } from 'global-types';
const getKbdLayout = (self: LabelGeneric, _: string, layout: string) => {
if (layout) {
if (layout === 'error') {
return;
}
const shortName = layout.match(/\(([A-Za-z]+)\)/);
self.label = shortName ? shortName[1] : layout;
}
else {
// At launch, kb layout is undefined
Hyprland.messageAsync('j/devices').then((obj) => {
const keyboards = Array.from(JSON.parse(obj)
.keyboards) as Keyboard[];
const kb = keyboards.find((v) => v.name === DEFAULT_KB);
if (kb) {
layout = kb.active_keymap;
const shortName = layout
.match(/\(([A-Za-z]+)\)/);
self.label = shortName ? shortName[1] : layout;
}
else {
self.label = 'None';
}
}).catch(print);
}
};
export default () => HoverRevealer({
class_name: 'keyboard',
spacing: 4,
icon: Icon({
icon: 'input-keyboard-symbolic',
size: 20,
}),
label: Label({ css: 'font-size: 20px;' })
.hook(Hyprland, getKbdLayout, 'keyboard-layout'),
});

View file

@ -1,37 +0,0 @@
const Network = await Service.import('network');
const { Label, Icon } = Widget;
import HoverRevealer from './hover-revealer.ts';
export default () => HoverRevealer({
class_name: 'network',
icon: Icon().hook(Network, (self) => {
if (Network.wifi.internet === 'connected' ||
Network.wifi.internet === 'connecting') {
self.icon = Network.wifi.icon_name;
}
else if (Network.wired.internet === 'connected' ||
Network.wired.internet === 'connecting') {
self.icon = Network.wired.icon_name;
}
else {
self.icon = Network.wifi.icon_name;
}
}),
label: Label().hook(Network, (self) => {
if (Network.wifi.internet === 'connected' ||
Network.wifi.internet === 'connecting') {
self.label = Network.wifi.ssid || 'Unknown';
}
else if (Network.wired.internet === 'connected' ||
Network.wired.internet === 'connecting') {
self.label = 'Connected';
}
else {
self.label = 'Disconnected';
}
}),
});

View file

@ -1,30 +0,0 @@
const Battery = await Service.import('battery');
const { Label, Icon, Box } = Widget;
import Separator from '../../misc/separator.ts';
const LOW_BATT = 20;
const SPACING = 5;
export default () => Box({
class_name: 'toggle-off battery',
children: [
Icon({
class_name: 'battery-indicator',
icon: Battery.bind('icon_name'),
}).hook(Battery, (self) => {
self.toggleClassName('charging', Battery.charging);
self.toggleClassName('charged', Battery.charged);
self.toggleClassName('low', Battery.percent < LOW_BATT);
}),
Separator(SPACING),
Label({
label: Battery.bind('percent')
.transform((v) => `${v}%`),
}),
],
});

View file

@ -1,19 +0,0 @@
import CursorBox from '../../misc/cursorbox.ts';
import Clock from './clock';
export default () => CursorBox({
class_name: 'toggle-off',
on_primary_click_release: () => App.toggleWindow('win-calendar'),
setup: (self) => {
self.hook(App, (_, windowName, visible) => {
if (windowName === 'win-calendar') {
self.toggleClassName('toggle-on', visible);
}
});
},
child: Clock(),
});

View file

@ -1,23 +0,0 @@
const { Label } = Widget;
const { new_now_local } = imports.gi.GLib.DateTime;
export default () => Label({ class_name: 'clock' })
.poll(1000, (self) => {
const time = new_now_local();
const dayName = time.format('%a. ');
const dayNum = time.get_day_of_month();
const date = time.format(' %b. ');
const hour = (new Date().toLocaleString([], {
hour: 'numeric',
minute: 'numeric',
hour12: true,
}) ?? '')
.replace('a.m.', 'AM')
.replace('p.m.', 'PM');
if (dayNum && dayName && date) {
self.label = dayName + dayNum + date + hour;
}
});

View file

@ -1,33 +0,0 @@
const Applications = await Service.import('applications');
const Hyprland = await Service.import('hyprland');
const { Box, Icon, Label } = Widget;
import Separator from '../../misc/separator.ts';
const SPACING = 8;
export default () => Box({
class_name: 'current-window',
children: [
Separator(SPACING / 2),
Icon({ size: 30 })
.hook(Hyprland.active.client, (self) => {
const app = Applications
.query(Hyprland.active.client.class)[0];
self.icon = app?.icon_name || '';
}),
Separator(SPACING),
Label({
truncate: 'end',
label: Hyprland.active.client.bind('title'),
}),
],
}).hook(Hyprland.active.client, (self) => {
self.visible = Hyprland.active.client.title !== '';
});

View file

@ -1,26 +0,0 @@
const { Label } = Widget;
import CursorBox from '../../misc/cursorbox.ts';
import Persist from '../../misc/persist.ts';
const HeartState = Variable('');
Persist({
name: 'heart',
gobject: HeartState,
prop: 'value',
condition: '',
whenFalse: '󰣐',
});
export default () => CursorBox({
on_primary_click_release: () => {
HeartState.setValue(HeartState.value === '' ? '󰣐' : '');
},
child: Label({
class_name: 'heart-toggle',
label: HeartState.bind(),
}),
});

View file

@ -1,60 +0,0 @@
const Notifications = await Service.import('notifications');
const { Box, CenterBox, Icon, Label } = Widget;
import CursorBox from '../../misc/cursorbox.ts';
import Separator from '../../misc/separator.ts';
const SPACING = 4;
// Types
import { PopupWindow } from 'global-types';
export default () => CursorBox({
class_name: 'toggle-off',
on_primary_click_release: (self) => {
(App.getWindow('win-notification-center') as PopupWindow)
.set_x_pos(
self.get_allocation(),
'right',
);
App.toggleWindow('win-notification-center');
},
setup: (self) => {
self.hook(App, (_, windowName, visible) => {
if (windowName === 'win-notification-center') {
self.toggleClassName('toggle-on', visible);
}
});
},
child: CenterBox({
class_name: 'notif-panel',
center_widget: Box({
children: [
Icon().hook(Notifications, (self) => {
if (Notifications.dnd) {
self.icon = 'notification-disabled-symbolic';
}
else if (Notifications.notifications.length > 0) {
self.icon = 'notification-new-symbolic';
}
else {
self.icon = 'notification-symbolic';
}
}),
Separator(SPACING),
Label({
label: Notifications.bind('notifications')
.transform((n) => String(n.length)),
}),
],
}),
}),
});

View file

@ -1,23 +0,0 @@
const { Label } = Widget;
import Tablet from '../../../services/tablet.ts';
import CursorBox from '../../misc/cursorbox.ts';
export default () => CursorBox({
class_name: 'toggle-off',
on_primary_click_release: () => Tablet.toggleOsk(),
setup: (self) => {
self.hook(Tablet, () => {
self.toggleClassName('toggle-on', Tablet.oskState);
}, 'osk-toggled');
},
child: Label({
class_name: 'osk-toggle',
xalign: 0.6,
label: '󰌌 ',
}),
});

View file

@ -1,85 +0,0 @@
const { Box, Icon } = Widget;
import Audio from '../hovers/audio.ts';
import Bluetooth from '../hovers/bluetooth.ts';
import Brightness from '../hovers/brightness.ts';
import KeyboardLayout from '../hovers/keyboard-layout.ts';
import Network from '../hovers/network.ts';
import CursorBox from '../../misc/cursorbox.ts';
import Separator from '../../misc/separator.ts';
const SPACING = 4;
// Types
import { PopupWindow } from 'global-types';
export default () => {
const hoverRevealers = [
KeyboardLayout(),
Brightness(),
Audio(),
Bluetooth(),
Network(),
];
const widget = CursorBox({
class_name: 'toggle-off',
on_primary_click_release: (self) => {
(App.getWindow('win-quick-settings') as PopupWindow)
.set_x_pos(
self.get_allocation(),
'right',
);
App.toggleWindow('win-quick-settings');
},
setup: (self) => {
self.hook(App, (_, windowName, visible) => {
if (windowName === 'win-quick-settings') {
self.toggleClassName('toggle-on', visible);
}
});
},
attribute: {
hoverRevealers: hoverRevealers.map((rev) => {
const box = rev.child;
return box.children[1];
}),
},
on_hover_lost: (self) => {
self.attribute.hoverRevealers.forEach((rev) => {
rev.reveal_child = false;
});
},
child: Box({
class_name: 'quick-settings-toggle',
vertical: false,
children: [
Separator(SPACING),
...hoverRevealers,
Icon('nixos-logo-symbolic'),
Separator(SPACING),
],
}),
});
widget.attribute.hoverRevealers.forEach((hv) => {
hv.attribute.var.setValue(widget);
});
return widget;
};

View file

@ -1,100 +0,0 @@
import AstalTray from 'gi://AstalTray?version=0.1';
const SystemTray = AstalTray.Tray.get_default();
const { timeout } = Utils;
const { Box, Icon, MenuItem, MenuBar, Revealer } = Widget;
import Separator from '../../misc/separator.ts';
const REVEAL_DURATION = 500;
const SPACING = 12;
const SysTrayItem = (item: AstalTray.TrayItem) => {
if (item.id === 'spotify-client') {
return;
}
return MenuItem({
submenu: item.create_menu(),
child: Revealer({
transition: 'slide_right',
transition_duration: REVEAL_DURATION,
child: Icon({
size: 24,
icon: Utils.watch(
item.iconPixbuf || item.iconName || 'image-missing',
item,
() => item.iconPixbuf || item.iconName || 'image-missing',
),
}),
}),
}).bind('tooltip_markup', item, 'tooltipMarkup');
};
const SysTray = () => MenuBar({
attribute: { items: new Map() },
setup: (self) => {
self
.hook(SystemTray, (_, id) => {
if (!id) {
return;
}
const item = SystemTray.get_item(id);
if (self.attribute.items.has(id) || !item) {
return;
}
const w = SysTrayItem(item);
// Early return if item is in blocklist
if (!w) {
return;
}
self.attribute.items.set(id, w);
self.add(w);
self.show_all();
w.child.reveal_child = true;
}, 'item_added')
.hook(SystemTray, (_, id) => {
if (!id || !self.attribute.items.has(id)) {
return;
}
self.attribute.items.get(id).child.reveal_child = false;
timeout(REVEAL_DURATION, () => {
self.attribute.items.get(id).destroy();
self.attribute.items.delete(id);
});
}, 'item_removed');
},
});
export default () => {
const systray = SysTray();
return Revealer({
transition: 'slide_right',
child: Box({
children: [
Box({
class_name: 'sys-tray',
children: [systray],
}),
Separator(SPACING),
],
}),
}).hook(SystemTray, (self) => {
self.reveal_child = systray.get_children().length > 0;
}, 'item_added');
};

View file

@ -1,24 +0,0 @@
const { Box, Label } = Widget;
import Tablet from '../../../services/tablet.ts';
import CursorBox from '../../misc/cursorbox.ts';
export default () => CursorBox({
class_name: 'toggle-off',
on_primary_click_release: () => Tablet.toggleMode(),
setup: (self) => {
self.hook(Tablet, () => {
self.toggleClassName('toggle-on', Tablet.tabletMode);
}, 'mode-toggled');
},
child: Box({
class_name: 'tablet-toggle',
vertical: false,
children: [Label(' 󰦧 ')],
}),
});

View file

@ -1,179 +0,0 @@
const Hyprland = await Service.import('hyprland');
const { timeout } = Utils;
const { Box, Overlay, Revealer } = Widget;
import CursorBox from '../../misc/cursorbox.ts';
const URGENT_DURATION = 1000;
// Types
import {
BoxGeneric,
EventBoxGeneric,
OverlayGeneric,
RevealerGeneric,
Workspace,
} from 'global-types';
const Workspace = ({ id }: { id: number }) => {
return Revealer({
transition: 'slide_right',
attribute: { id },
child: CursorBox({
tooltip_text: `${id}`,
on_primary_click_release: () => {
Hyprland.messageAsync(`dispatch workspace ${id}`);
},
child: Box({
vpack: 'center',
class_name: 'button',
setup: (self) => {
const update = (
_: BoxGeneric,
addr: string | undefined,
) => {
const workspace = Hyprland.getWorkspace(id);
const occupied = workspace && workspace.windows > 0;
self.toggleClassName('occupied', occupied);
if (!addr) {
return;
}
// Deal with urgent windows
const client = Hyprland.getClient(addr);
const isThisUrgent = client &&
client.workspace.id === id;
if (isThisUrgent) {
self.toggleClassName('urgent', true);
// Only show for a sec when urgent is current workspace
if (Hyprland.active.workspace.id === id) {
timeout(URGENT_DURATION, () => {
self.toggleClassName('urgent', false);
});
}
}
};
self
.hook(Hyprland, update)
// Deal with urgent windows
.hook(Hyprland, update, 'urgent-window')
.hook(Hyprland.active.workspace, () => {
if (Hyprland.active.workspace.id === id) {
self.toggleClassName('urgent', false);
}
});
},
}),
}),
});
};
export default () => {
const L_PADDING = 16;
const WS_WIDTH = 30;
const updateHighlight = (self: BoxGeneric) => {
const currentId = Hyprland.active.workspace.id;
const indicators = (((self.get_parent() as OverlayGeneric)
.child as EventBoxGeneric)
.child as BoxGeneric)
.children as Workspace[];
const currentIndex = indicators
.findIndex((w) => w.attribute.id === currentId);
if (currentIndex < 0) {
return;
}
self.setCss(`margin-left: ${L_PADDING + (currentIndex * WS_WIDTH)}px`);
};
const highlight = Box({
vpack: 'center',
hpack: 'start',
class_name: 'button active',
}).hook(Hyprland.active.workspace, updateHighlight);
const widget = Overlay({
pass_through: true,
overlays: [highlight],
child: CursorBox({
child: Box({
class_name: 'workspaces',
attribute: { workspaces: [] as Workspace[] },
setup: (self) => {
const refresh = () => {
(self.children as RevealerGeneric[])
.forEach((rev) => {
rev.reveal_child = false;
});
self.attribute.workspaces
.forEach((ws) => {
ws.reveal_child = true;
});
};
const updateWorkspaces = () => {
Hyprland.workspaces.forEach((ws) => {
const currentWs =
(self.children as Workspace[])
.find((ch) => ch.attribute.id === ws.id);
if (!currentWs && ws.id > 0) {
self.add(Workspace({ id: ws.id }));
}
});
self.show_all();
// Make sure the order is correct
self.attribute.workspaces.forEach((workspace, i) => {
(workspace.get_parent() as BoxGeneric)
.reorder_child(workspace, i);
});
};
self.hook(Hyprland, () => {
self.attribute.workspaces =
(self.children as Workspace[])
.filter((ch) => {
return Hyprland.workspaces.find((ws) => {
return ws.id === ch.attribute.id;
});
})
.sort((a, b) =>
a.attribute.id - b.attribute.id);
updateWorkspaces();
refresh();
// Make sure the highlight doesn't go too far
const TEMP_TIMEOUT = 10;
timeout(TEMP_TIMEOUT, () => updateHighlight(highlight));
});
},
}),
}),
});
return widget;
};

View file

@ -1,80 +0,0 @@
const { CenterBox, Box } = Widget;
import Separator from '../misc/separator.ts';
import Battery from './items/battery.ts';
import Clock from './items/cal-opener.ts';
import CurrentWindow from './items/current-window.ts';
import Heart from './items/heart.ts';
import NotifButton from './items/notif-button.ts';
import OskToggle from './items/osk-toggle.ts';
import QsToggle from './items/quick-settings.ts';
import SysTray from './items/systray.ts';
import TabletToggle from './items/tablet-toggle.ts';
import Workspaces from './items/workspaces.ts';
import BarRevealer from './fullscreen.ts';
const SPACING = 12;
export default () => BarRevealer({
anchor: ['top', 'left', 'right'],
exclusivity: 'exclusive',
bar: CenterBox({
css: 'margin: 5px 5px 5px 5px',
class_name: 'bar',
start_widget: Box({
hpack: 'start',
children: [
OskToggle(),
Separator(SPACING),
TabletToggle(),
Separator(SPACING),
SysTray(),
Workspaces(),
Separator(SPACING),
CurrentWindow(),
],
}),
center_widget: Box({
children: [
Separator(SPACING),
Clock(),
Separator(SPACING),
],
}),
end_widget: Box({
hpack: 'end',
children: [
Heart(),
Separator(SPACING),
Battery(),
Separator(SPACING),
NotifButton(),
Separator(SPACING),
QsToggle(),
],
}),
}),
});

View file

@ -1,80 +0,0 @@
const { Box, Button, Icon, Label, Revealer } = Widget;
const ImageClip = (key: number, val: string) => Box({
class_name: 'item',
name: key.toString(),
child: Icon({
icon: val.replace('img:', ''),
size: 100 * 2,
}),
});
const TextClip = (key: number, val: string) => {
const lines = val.split('\n');
if (lines.length <= 5) {
return Box({
class_name: 'item',
name: key.toString(),
child: Label({
label: val,
truncate: 'end',
max_width_chars: 100,
}),
});
}
else {
const revText = Revealer({
hpack: 'start',
child: Label({
label: lines.slice(2, lines.length).join('\n'),
truncate: 'end',
max_width_chars: 100,
}),
});
return Box({
class_name: 'item',
name: key.toString(),
vertical: true,
children: [
Label({
label: lines.slice(0, 2).join('\n'),
truncate: 'end',
max_width_chars: 100,
hpack: 'start',
}),
revText,
Button({
child: Icon({
class_name: 'down-arrow',
icon: 'down-large-symbolic',
size: 24,
}),
on_primary_click_release: (self) => {
const state = !revText.reveal_child;
revText.reveal_child = state;
self.child.setCss(`
-gtk-icon-transform: rotate(${state ? '180' : '0'}deg);
`);
},
}),
],
});
}
};
export default ({
key = 0,
isImage = false,
val = '',
}) => isImage ? ImageClip(key, val) : TextClip(key, val);

View file

@ -1,85 +0,0 @@
import { Fzf, FzfResultItem } from 'fzf';
import Gtk from 'gi://Gtk?version=3.0';
import Clipboard from '../../services/clipboard.ts';
import SortedList from '../misc/sorted-list.ts';
import ClipWidget from './clip.ts';
const getKey = (r: Gtk.ListBoxRow): number => parseInt(r.get_child()?.name ?? '0');
export default () => {
let fzfResults = [] as FzfResultItem<[number, { clip: string, isImage: boolean }]>[];
return SortedList({
name: 'clipboard',
class_name: 'clipboard',
transition: 'slide top',
on_select: (r) => {
Clipboard.copyOldItem(getKey(r));
App.closeWindow('win-clipboard');
},
setup_list: (list, entry) => {
list.cursor = 'pointer';
const CONNECT_ID = Clipboard.connect('history-searched', () => {
// Do every clip that existed before this widget
Clipboard.clips.forEach((clip, key) => {
const widget = ClipWidget({ key, isImage: clip.isImage, val: clip.clip });
list.add(widget);
widget.show_all();
});
// Setup connection for new clips
Clipboard.connect('clip-added', (_, [key, clip]) => {
const widget = ClipWidget({ key, isImage: clip.isImage, val: clip.clip });
list.add(widget);
widget.show_all();
});
list.set_sort_func((a, b) => {
if (entry.text === '' || entry.text === '-') {
a.set_visible(true);
b.set_visible(true);
return getKey(b) - getKey(a);
}
else {
const ROW_1 = fzfResults.find((f) => f.item[0] === getKey(a))?.score ?? 0;
const ROW_2 = fzfResults.find((f) => f.item[0] === getKey(b))?.score ?? 0;
a.set_visible(ROW_1 !== 0);
b.set_visible(ROW_2 !== 0);
return ROW_2 - ROW_1;
}
});
// Trigger on_text_change after init
entry.text = '-';
Clipboard.disconnect(CONNECT_ID);
});
},
on_text_change: (text, list, placeholder) => {
const fzf = new Fzf([...Clipboard.clips.entries()], {
selector: ([_key, { clip }]) => clip,
tiebreakers: [(a, b) => b[0] - a[0]],
});
fzfResults = fzf.find(text);
list.invalidate_sort();
const visibleApps = list.get_children().filter((row) => row.visible).length;
placeholder.reveal_child = visibleApps <= 0;
},
});
};

View file

@ -1,52 +0,0 @@
const { Window } = Widget;
import RoundedCorner from './screen-corners.ts';
const TopLeft = () => Window({
name: 'cornertl',
layer: 'overlay',
exclusivity: 'ignore',
anchor: ['top', 'left'],
visible: true,
click_through: true,
child: RoundedCorner('topleft'),
});
const TopRight = () => Window({
name: 'cornertr',
layer: 'overlay',
exclusivity: 'ignore',
anchor: ['top', 'right'],
visible: true,
click_through: true,
child: RoundedCorner('topright'),
});
const BottomLeft = () => Window({
name: 'cornerbl',
layer: 'overlay',
exclusivity: 'ignore',
anchor: ['bottom', 'left'],
visible: true,
click_through: true,
child: RoundedCorner('bottomleft'),
});
const BottomRight = () => Window({
name: 'cornerbr',
layer: 'overlay',
exclusivity: 'ignore',
anchor: ['bottom', 'right'],
visible: true,
click_through: true,
child: RoundedCorner('bottomright'),
});
export default () => [
TopLeft(),
TopRight(),
BottomLeft(),
BottomRight(),
];

View file

@ -1,79 +0,0 @@
const { Box, DrawingArea } = Widget;
const { Gtk } = imports.gi;
export default (
place = 'top left',
css = 'background-color: black;',
) => Box({
hpack: place.includes('left') ? 'start' : 'end',
vpack: place.includes('top') ? 'start' : 'end',
css: `
padding: 1px; margin:
${place.includes('top') ? '-1px' : '0'}
${place.includes('right') ? '-1px' : '0'}
${place.includes('bottom') ? '-1px' : '0'}
${place.includes('left') ? '-1px' : '0'};
`,
child: DrawingArea({
css: `
border-radius: 18px;
border-width: 0.068rem;
${css}
`,
setup: (widget) => {
let r = widget.get_style_context()
.get_property('border-radius', Gtk.StateFlags.NORMAL);
widget.set_size_request(r, r);
widget.connect('draw', (_, cr) => {
const c = widget.get_style_context()
.get_property('background-color', Gtk.StateFlags.NORMAL);
r = widget.get_style_context()
.get_property('border-radius', Gtk.StateFlags.NORMAL);
const borderColor = widget.get_style_context()
.get_property('color', Gtk.StateFlags.NORMAL);
// You're going to write border-width: something anyway
const borderWidth = widget.get_style_context()
.get_border(Gtk.StateFlags.NORMAL).left;
widget.set_size_request(r, r);
switch (place) {
case 'topleft':
cr.arc(r, r, r, Math.PI, 3 * Math.PI / 2);
cr.lineTo(0, 0);
break;
case 'topright':
cr.arc(0, r, r, 3 * Math.PI / 2, 2 * Math.PI);
cr.lineTo(r, 0);
break;
case 'bottomleft':
cr.arc(r, 0, r, Math.PI / 2, Math.PI);
cr.lineTo(0, r);
break;
case 'bottomright':
cr.arc(0, 0, r, 0, Math.PI / 2);
cr.lineTo(r, r);
break;
}
cr.closePath();
cr.setSourceRGBA(c.red, c.green, c.blue, c.alpha);
cr.fill();
cr.setLineWidth(borderWidth);
cr.setSourceRGBA(borderColor.red,
borderColor.green,
borderColor.blue,
borderColor.alpha);
cr.stroke();
});
},
}),
});

View file

@ -1,16 +0,0 @@
import PopupWindow from '../misc/popup.ts';
import CalendarWidget from './main.ts';
import { get_gdkmonitor_from_desc } from '../lib.ts';
const RIGHT_MARGIN = 20;
export default () => PopupWindow({
name: 'calendar',
anchor: ['bottom', 'right'],
margins: [0, RIGHT_MARGIN, 0, 0],
transition: 'slide bottom',
gdkmonitor: get_gdkmonitor_from_desc('desc:Acer Technologies Acer K212HQL T3EAA0014201'),
child: CalendarWidget(),
});

View file

@ -1,92 +0,0 @@
const { Box, Calendar, Label } = Widget;
const { new_now_local } = imports.gi.GLib.DateTime;
const Divider = () => Box({
class_name: 'divider',
vertical: true,
});
const Time = () => Box({
class_name: 'timebox',
vertical: true,
children: [
Box({
class_name: 'time-container',
hpack: 'center',
vpack: 'center',
children: [
Label({
class_name: 'content',
label: 'hour',
setup: (self) => {
self.poll(1000, () => {
self.label = new_now_local().format('%H') || '';
});
},
}),
Divider(),
Label({
class_name: 'content',
label: 'minute',
setup: (self) => {
self.poll(1000, () => {
self.label = new_now_local().format('%M') || '';
});
},
}),
],
}),
Box({
class_name: 'date-container',
hpack: 'center',
child: Label({
css: 'font-size: 20px',
label: 'complete date',
setup: (self) => {
self.poll(1000, () => {
const time = new_now_local();
const dayNameMonth = time.format('%A, %B ');
const dayNum = time.get_day_of_month();
const date = time.format(', %Y');
if (dayNum && dayNameMonth && date) {
self.label = dayNameMonth + dayNum + date;
}
});
},
}),
}),
],
});
const CalendarWidget = () => Box({
class_name: 'cal-box',
child: Calendar({
show_day_names: true,
show_heading: true,
class_name: 'cal',
}),
});
export default () => Box({
class_name: 'date',
vertical: true,
children: [
Time(),
CalendarWidget(),
],
});

View file

@ -1,12 +0,0 @@
import PopupWindow from '../misc/popup.ts';
import CalendarWidget from './main.ts';
const TOP_MARGIN = 6;
export default () => PopupWindow({
name: 'calendar',
anchor: ['top'],
margins: [TOP_MARGIN, 0, 0, 0],
child: CalendarWidget(),
});

View file

@ -1,43 +0,0 @@
const Hyprland = await Service.import('hyprland');
import Gdk from 'gi://Gdk?version=3.0';
/* Types */
import { Monitor } from 'types/service/hyprland';
export const get_hyprland_monitor = (monitor: Gdk.Monitor): Monitor | undefined => {
const manufacturer = monitor.manufacturer?.replace(',', '');
const model = monitor.model?.replace(',', '');
const start = `${manufacturer} ${model}`;
return Hyprland.monitors.find((m) => m.description.startsWith(start));
};
export const get_hyprland_monitor_desc = (monitor: Gdk.Monitor): string => {
const manufacturer = monitor.manufacturer?.replace(',', '');
const model = monitor.model?.replace(',', '');
const start = `${manufacturer} ${model}`;
return `desc:${Hyprland.monitors.find((m) => m.description.startsWith(start))?.description}`;
};
export const get_gdkmonitor_from_desc = (desc: string): Gdk.Monitor => {
const display = Gdk.Display.get_default();
for (let m = 0; m < (display?.get_n_monitors() ?? 0); m++) {
const monitor = display?.get_monitor(m);
if (monitor && desc === get_hyprland_monitor_desc(monitor)) {
return monitor;
}
}
throw Error(`Monitor ${desc} not found`);
};
export const get_monitor_desc_from_id = (id: number): string => {
const monitor = Hyprland.monitors.find((m) => m.id === id);
return `desc:${monitor?.description}`;
};

View file

@ -1,211 +0,0 @@
const { Box, Entry, Label, Window } = Widget;
import Gdk from 'gi://Gdk?version=3.0';
import Gtk from 'gi://Gtk?version=3.0';
import Lock from 'gi://GtkSessionLock?version=0.1';
// This file is generated by Nix
import Vars from './vars.ts';
import Separator from '../misc/separator.ts';
import { get_hyprland_monitor_desc } from '../lib.ts';
/* Types */
import { Box as AgsBox } from 'types/widgets/box';
const lock = Lock.prepare_lock();
const windows = new Map<Gdk.Monitor, Gtk.Window>();
const blurBGs: AgsBox<Gtk.Widget, { geometry: { w: number, h: number } }>[] = [];
const transition_duration = 1000;
const WINDOW_MARGINS = -2;
const ENTRY_SPACING = 20;
const CLOCK_SPACING = 60;
const bgCSS = ({ w = 1, h = 1 } = {}) => `
border: 2px solid rgba(189, 147, 249, 0.8);
background: rgba(0, 0, 0, 0.2);
min-height: ${h}px;
min-width: ${w}px;
transition: min-height ${transition_duration / 2}ms,
min-width ${transition_duration / 2}ms;
`;
const unlock = () => {
blurBGs.forEach((b) => {
b.css = bgCSS({
w: b.attribute.geometry.w,
h: 1,
});
Utils.timeout(transition_duration / 2, () => {
b.css = bgCSS({
w: 1,
h: 1,
});
});
});
Utils.timeout(transition_duration, () => {
lock.unlock_and_destroy();
Gdk.Display.get_default()?.sync();
App.quit();
});
};
const Clock = () => Label({ class_name: 'clock' })
.poll(1000, (self) => {
self.label = (new Date().toLocaleString([], {
hour: 'numeric',
minute: 'numeric',
hour12: true,
}) ?? '')
.replace('a.m.', 'AM')
.replace('p.m.', 'PM');
});
const PasswordPrompt = (monitor: Gdk.Monitor, visible: boolean) => {
const rev = Box({
css: bgCSS(),
attribute: {
geometry: {} as { w: number, h: number },
},
setup: (self) => Utils.idle(() => {
self.attribute.geometry = {
w: monitor.geometry.width,
h: monitor.geometry.height,
};
self.css = bgCSS({
w: self.attribute.geometry.w,
h: 1,
});
Utils.timeout(transition_duration / 2, () => {
self.css = bgCSS({
w: self.attribute.geometry.w,
h: self.attribute.geometry.h,
});
});
}),
});
blurBGs.push(rev);
Window({
name: `blur-bg-${monitor.get_model()}`,
gdkmonitor: monitor,
layer: 'overlay',
anchor: ['top', 'bottom', 'right', 'left'],
margins: [WINDOW_MARGINS],
exclusivity: 'ignore',
child: Box({
hexpand: false,
vexpand: false,
hpack: 'center',
vpack: 'center',
child: rev,
}),
});
const label = Label('Enter password:');
return new Gtk.Window({
child: visible ?
Box({
vertical: true,
vpack: 'center',
hpack: 'center',
spacing: 16,
children: [
Clock(),
Separator(CLOCK_SPACING, { vertical: true }),
Box({
hpack: 'center',
class_name: 'avatar',
}),
Box({
class_name: 'entry-box',
vertical: true,
children: [
label,
Separator(ENTRY_SPACING, { vertical: true }),
Entry({
hpack: 'center',
xalign: 0.5,
visibility: false,
placeholder_text: 'password',
on_accept: (self) => {
self.sensitive = false;
Utils.authenticate(self.text ?? '')
.then(() => unlock())
.catch((e) => {
self.text = '';
label.label = e.message;
self.sensitive = true;
});
},
}).on('realize', (entry) => entry.grab_focus()),
],
}),
],
}) :
Box(),
});
};
const createWindow = (monitor: Gdk.Monitor) => {
const hyprDesc = get_hyprland_monitor_desc(monitor);
const entryVisible = Vars.mainMonitor === hyprDesc || Vars.dupeLockscreen;
const win = PasswordPrompt(monitor, entryVisible);
windows.set(monitor, win);
};
const lock_screen = () => {
const display = Gdk.Display.get_default();
for (let m = 0; m < (display?.get_n_monitors() ?? 0); m++) {
const monitor = display?.get_monitor(m);
if (monitor) {
createWindow(monitor);
}
}
display?.connect('monitor-added', (_, monitor) => {
createWindow(monitor);
});
lock.lock_lock();
windows.forEach((win, monitor) => {
lock.new_surface(win, monitor);
win.show();
});
};
const on_finished = () => {
lock.destroy();
Gdk.Display.get_default()?.sync();
App.quit();
};
lock.connect('finished', on_finished);
if (Vars.hasFprintd) {
globalThis.authFinger = () => Utils.authenticate('')
.then(() => unlock())
.catch(logError);
globalThis.authFinger();
}
export default () => lock_screen();

View file

@ -1,4 +0,0 @@
import LaunchLockscreen from './lock.ts';
LaunchLockscreen();

View file

@ -1,167 +0,0 @@
const { timeout } = Utils;
const { Box, EventBox, Overlay } = Widget;
const { Gtk } = imports.gi;
const MAX_OFFSET = 200;
const OFFSCREEN = 500;
const ANIM_DURATION = 500;
const TRANSITION = `transition: margin ${ANIM_DURATION}ms ease,
opacity ${ANIM_DURATION}ms ease;`;
// Types
import {
CenterBoxGeneric,
Gesture,
OverlayGeneric,
PlayerBox,
} from 'global-types';
export default ({
setup = () => { /**/ },
...props
}: Gesture) => {
const widget = EventBox();
const gesture = Gtk.GestureDrag.new(widget);
// Have empty PlayerBox to define the size of the widget
const emptyPlayer = Box({
class_name: 'player',
attribute: { empty: true },
});
const content = Overlay({
...props,
attribute: {
players: new Map(),
setup: false,
dragging: false,
includesWidget: (playerW: OverlayGeneric) => {
return content.overlays.find((w) => w === playerW);
},
showTopOnly: () => content.overlays.forEach((over) => {
over.visible = over === content.overlays.at(-1);
}),
moveToTop: (player: CenterBoxGeneric) => {
player.visible = true;
content.reorder_overlay(player, -1);
timeout(ANIM_DURATION, () => {
content.attribute.showTopOnly();
});
},
},
child: emptyPlayer,
setup: (self) => {
setup(self);
self
.hook(gesture, (_, realGesture) => {
if (realGesture) {
self.overlays.forEach((over) => {
over.visible = true;
});
}
else {
self.attribute.showTopOnly();
}
// Don't allow gesture when only one player
if (self.overlays.length <= 1) {
return;
}
self.attribute.dragging = true;
let offset = gesture.get_offset()[1];
const playerBox = self.overlays.at(-1) as PlayerBox;
if (!offset) {
return;
}
// Slide right
if (offset >= 0) {
playerBox.setCss(`
margin-left: ${offset}px;
margin-right: -${offset}px;
${playerBox.attribute.bgStyle}
`);
}
// Slide left
else {
offset = Math.abs(offset);
playerBox.setCss(`
margin-left: -${offset}px;
margin-right: ${offset}px;
${playerBox.attribute.bgStyle}
`);
}
}, 'drag-update')
.hook(gesture, () => {
// Don't allow gesture when only one player
if (self.overlays.length <= 1) {
return;
}
self.attribute.dragging = false;
const offset = gesture.get_offset()[1];
const playerBox = self.overlays.at(-1) as PlayerBox;
// If crosses threshold after letting go, slide away
if (offset && Math.abs(offset) > MAX_OFFSET) {
// Disable inputs during animation
widget.sensitive = false;
// Slide away right
if (offset >= 0) {
playerBox.setCss(`
${TRANSITION}
margin-left: ${OFFSCREEN}px;
margin-right: -${OFFSCREEN}px;
opacity: 0.7; ${playerBox.attribute.bgStyle}
`);
}
// Slide away left
else {
playerBox.setCss(`
${TRANSITION}
margin-left: -${OFFSCREEN}px;
margin-right: ${OFFSCREEN}px;
opacity: 0.7; ${playerBox.attribute.bgStyle}
`);
}
timeout(ANIM_DURATION, () => {
// Put the player in the back after anim
self.reorder_overlay(playerBox, 0);
// Recenter player
playerBox.setCss(playerBox.attribute.bgStyle);
widget.sensitive = true;
self.attribute.showTopOnly();
});
}
else {
// Recenter with transition for animation
playerBox.setCss(`${TRANSITION}
${playerBox.attribute.bgStyle}`);
}
}, 'drag-end');
},
});
widget.add(content);
return widget;
};

View file

@ -1,473 +0,0 @@
const Mpris = await Service.import('mpris');
const { Button, Icon, Label, Stack, Slider, CenterBox, Box } = Widget;
const { execAsync, lookUpIcon, readFileAsync } = Utils;
import Separator from '../misc/separator.ts';
import CursorBox from '../misc/cursorbox.ts';
const ICON_SIZE = 32;
const icons = {
mpris: {
fallback: 'audio-x-generic-symbolic',
shuffle: {
enabled: '󰒝',
disabled: '󰒞',
},
loop: {
none: '󰑗',
track: '󰑘',
playlist: '󰑖',
},
playing: ' ',
paused: ' ',
stopped: ' ',
prev: '󰒮',
next: '󰒭',
},
};
// Types
import { MprisPlayer } from 'types/service/mpris.ts';
import { Variable as Var } from 'types/variable';
import {
AgsWidget,
CenterBoxPropsGeneric,
Colors,
PlayerBox,
PlayerButtonType,
PlayerDrag,
PlayerOverlay,
} from 'global-types';
export const CoverArt = (
player: MprisPlayer,
colors: Var<Colors>,
props: CenterBoxPropsGeneric,
) => CenterBox({
...props,
vertical: true,
attribute: {
bgStyle: '',
player,
},
setup: (self: PlayerBox) => {
// Give temp cover art
readFileAsync(player.cover_path).catch(() => {
if (!colors.value && !player.track_cover_url) {
colors.setValue({
imageAccent: '#6b4fa2',
buttonAccent: '#ecdcff',
buttonText: '#25005a',
hoverAccent: '#d4baff',
});
self.attribute.bgStyle = `
background: radial-gradient(circle,
rgba(0, 0, 0, 0.4) 30%,
${(colors as Var<Colors>).value.imageAccent}),
rgb(0, 0, 0);
background-size: cover;
background-position: center;
`;
self.setCss(self.attribute.bgStyle);
}
});
self.hook(player, () => {
execAsync(['bash', '-c', `[[ -f "${player.cover_path}" ]] &&
coloryou "${player.cover_path}" | grep -v Warning`])
.then((out) => {
if (!Mpris.players.find((p) => player === p)) {
return;
}
colors.setValue(JSON.parse(out));
self.attribute.bgStyle = `
background: radial-gradient(circle,
rgba(0, 0, 0, 0.4) 30%,
${colors.value.imageAccent}),
url("${player.cover_path}");
background-size: cover;
background-position: center;
`;
if (!(self.get_parent() as PlayerDrag)
.attribute.dragging) {
self.setCss(self.attribute.bgStyle);
}
}).catch((err) => {
if (err !== '') {
print(err);
}
});
});
},
});
export const TitleLabel = (player: MprisPlayer) => Label({
xalign: 0,
max_width_chars: 40,
truncate: 'end',
justification: 'left',
class_name: 'title',
label: player.bind('track_title'),
});
export const ArtistLabel = (player: MprisPlayer) => Label({
xalign: 0,
max_width_chars: 40,
truncate: 'end',
justification: 'left',
class_name: 'artist',
label: player.bind('track_artists')
.transform((a) => a.join(', ') || ''),
});
export const PlayerIcon = (player: MprisPlayer, overlay: PlayerOverlay) => {
const playerIcon = (
p: MprisPlayer,
widget?: PlayerOverlay,
playerBox?: PlayerBox,
) => CursorBox({
tooltip_text: p.identity || '',
on_primary_click_release: () => {
if (widget && playerBox) {
widget.attribute.moveToTop(playerBox);
}
},
child: Icon({
class_name: widget ? 'position-indicator' : 'player-icon',
size: widget ? 0 : ICON_SIZE,
setup: (self) => {
self.hook(p, () => {
self.icon = lookUpIcon(p.entry) ?
p.entry :
icons.mpris.fallback;
});
},
}),
});
return Box().hook(Mpris, (self) => {
const grandPa = self.get_parent()?.get_parent() as AgsWidget;
if (!grandPa) {
return;
}
const thisIndex = overlay.overlays
.indexOf(grandPa);
self.children = (overlay.overlays as PlayerBox[])
.map((playerBox, i) => {
self.children.push(Separator(2));
return i === thisIndex ?
playerIcon(player) :
playerIcon(playerBox.attribute.player, overlay, playerBox);
})
.reverse();
});
};
const { Gdk } = imports.gi;
const display = Gdk.Display.get_default();
export const PositionSlider = (
player: MprisPlayer,
colors: Var<Colors>,
) => Slider({
class_name: 'position-slider',
vpack: 'center',
hexpand: true,
draw_value: false,
on_change: ({ value }) => {
player.position = player.length * value;
},
setup: (self) => {
const update = () => {
if (!self.dragging) {
self.visible = player.length > 0;
if (player.length > 0) {
self.value = player.position / player.length;
}
}
};
self
.poll(1000, () => update())
.hook(player, () => update(), 'position')
.hook(colors, () => {
if (colors.value) {
const c = colors.value;
self.setCss(`
highlight { background-color: ${c.buttonAccent}; }
slider { background-color: ${c.buttonAccent}; }
slider:hover { background-color: ${c.hoverAccent}; }
trough { background-color: ${c.buttonText}; }
`);
}
})
// OnClick
.on('button-press-event', () => {
if (!display) {
return;
}
self.window.set_cursor(Gdk.Cursor.new_from_name(
display,
'grabbing',
));
})
// OnRelease
.on('button-release-event', () => {
if (!display) {
return;
}
self.window.set_cursor(Gdk.Cursor.new_from_name(
display,
'pointer',
));
})
// OnHover
.on('enter-notify-event', () => {
if (!display) {
return;
}
self.window.set_cursor(Gdk.Cursor.new_from_name(
display,
'pointer',
));
self.toggleClassName('hover', true);
})
// OnHoverLost
.on('leave-notify-event', () => {
self.window.set_cursor(null);
self.toggleClassName('hover', false);
});
},
});
const PlayerButton = ({
player,
colors,
children = {},
onClick,
prop,
}: PlayerButtonType) => CursorBox({
child: Button({
attribute: { hovered: false },
child: Stack({ children }),
on_primary_click_release: () => player[onClick](),
on_hover: (self) => {
self.attribute.hovered = true;
if (prop === 'playBackStatus' && colors.value) {
const c = colors.value;
Object.values(children).forEach((ch: AgsWidget) => {
ch.setCss(`
background-color: ${c.hoverAccent};
color: ${c.buttonText};
min-height: 40px;
min-width: 36px;
margin-bottom: 1px;
margin-right: 1px;
`);
});
}
},
on_hover_lost: (self) => {
self.attribute.hovered = false;
if (prop === 'playBackStatus' && colors.value) {
const c = colors.value;
Object.values(children).forEach((ch: AgsWidget) => {
ch.setCss(`
background-color: ${c.buttonAccent};
color: ${c.buttonText};
min-height: 42px;
min-width: 38px;
`);
});
}
},
setup: (self) => {
self
.hook(player, () => {
self.child.shown = `${player[prop]}`;
})
.hook(colors, () => {
if (!Mpris.players.find((p) => player === p)) {
return;
}
if (colors.value) {
const c = colors.value;
if (prop === 'playBackStatus') {
if (self.attribute.hovered) {
Object.values(children)
.forEach((ch: AgsWidget) => {
ch.setCss(`
background-color: ${c.hoverAccent};
color: ${c.buttonText};
min-height: 40px;
min-width: 36px;
margin-bottom: 1px;
margin-right: 1px;
`);
});
}
else {
Object.values(children)
.forEach((ch: AgsWidget) => {
ch.setCss(`
background-color: ${c.buttonAccent};
color: ${c.buttonText};
min-height: 42px;
min-width: 38px;
`);
});
}
}
else {
self.setCss(`
* { color: ${c.buttonAccent}; }
*:hover { color: ${c.hoverAccent}; }
`);
}
}
});
},
}),
});
export const ShuffleButton = (
player: MprisPlayer,
colors: Var<Colors>,
) => PlayerButton({
player,
colors,
children: {
true: Label({
class_name: 'shuffle enabled',
label: icons.mpris.shuffle.enabled,
}),
false: Label({
class_name: 'shuffle disabled',
label: icons.mpris.shuffle.disabled,
}),
},
onClick: 'shuffle',
prop: 'shuffleStatus',
});
export const LoopButton = (
player: MprisPlayer,
colors: Var<Colors>,
) => PlayerButton({
player,
colors,
children: {
None: Label({
class_name: 'loop none',
label: icons.mpris.loop.none,
}),
Track: Label({
class_name: 'loop track',
label: icons.mpris.loop.track,
}),
Playlist: Label({
class_name: 'loop playlist',
label: icons.mpris.loop.playlist,
}),
},
onClick: 'loop',
prop: 'loopStatus',
});
export const PlayPauseButton = (
player: MprisPlayer,
colors: Var<Colors>,
) => PlayerButton({
player,
colors,
children: {
Playing: Label({
class_name: 'pausebutton playing',
label: icons.mpris.playing,
}),
Paused: Label({
class_name: 'pausebutton paused',
label: icons.mpris.paused,
}),
Stopped: Label({
class_name: 'pausebutton stopped paused',
label: icons.mpris.stopped,
}),
},
onClick: 'playPause',
prop: 'playBackStatus',
});
export const PreviousButton = (
player: MprisPlayer,
colors: Var<Colors>,
) => PlayerButton({
player,
colors,
children: {
true: Label({
class_name: 'previous',
label: icons.mpris.prev,
}),
false: Label({
class_name: 'previous',
label: icons.mpris.prev,
}),
},
onClick: 'previous',
prop: 'canGoPrev',
});
export const NextButton = (
player: MprisPlayer,
colors: Var<Colors>,
) => PlayerButton({
player,
colors,
children: {
true: Label({
class_name: 'next',
label: icons.mpris.next,
}),
false: Label({
class_name: 'next',
label: icons.mpris.next,
}),
},
onClick: 'next',
prop: 'canGoNext',
});

View file

@ -1,201 +0,0 @@
const Mpris = await Service.import('mpris');
const { Box, CenterBox } = Widget;
import * as mpris from './mpris.ts';
import PlayerGesture from './gesture.ts';
import Separator from '../misc/separator.ts';
const FAVE_PLAYER = 'org.mpris.MediaPlayer2.spotify';
const SPACING = 8;
// Types
import { MprisPlayer } from 'types/service/mpris.ts';
import { Variable as Var } from 'types/variable';
import { Colors, PlayerBox, PlayerOverlay } from 'global-types';
const Top = (
player: MprisPlayer,
overlay: PlayerOverlay,
) => Box({
class_name: 'top',
hpack: 'start',
vpack: 'start',
children: [
mpris.PlayerIcon(player, overlay),
],
});
const Center = (
player: MprisPlayer,
colors: Var<Colors>,
) => Box({
class_name: 'center',
children: [
CenterBox({
vertical: true,
start_widget: Box({
class_name: 'metadata',
vertical: true,
hpack: 'start',
vpack: 'center',
hexpand: true,
children: [
mpris.TitleLabel(player),
mpris.ArtistLabel(player),
],
}),
}),
CenterBox({
vertical: true,
center_widget: mpris.PlayPauseButton(player, colors),
}),
],
});
const Bottom = (
player: MprisPlayer,
colors: Var<Colors>,
) => Box({
class_name: 'bottom',
children: [
mpris.PreviousButton(player, colors),
Separator(SPACING),
mpris.PositionSlider(player, colors),
Separator(SPACING),
mpris.NextButton(player, colors),
Separator(SPACING),
mpris.ShuffleButton(player, colors),
Separator(SPACING),
mpris.LoopButton(player, colors),
],
});
const PlayerBox = (
player: MprisPlayer,
colors: Var<Colors>,
overlay: PlayerOverlay,
) => {
const widget = mpris.CoverArt(player, colors, {
class_name: `player ${player.name}`,
hexpand: true,
start_widget: Top(player, overlay),
center_widget: Center(player, colors),
end_widget: Bottom(player, colors),
});
widget.visible = false;
return widget;
};
export default () => {
const content = PlayerGesture({
setup: (self: PlayerOverlay) => {
self
.hook(Mpris, (_, bus_name) => {
const players = self.attribute.players;
if (players.has(bus_name)) {
return;
}
// Sometimes the signal doesn't give the bus_name
if (!bus_name) {
const player = Mpris.players.find((p) => {
return !players.has(p.bus_name);
});
if (player) {
bus_name = player.bus_name;
}
else {
return;
}
}
// Get the one on top so we can move it up later
const previousFirst = self.overlays.at(-1) as PlayerBox;
// Make the new player
const player = Mpris.getPlayer(bus_name);
const colorsVar = Variable({
imageAccent: '#6b4fa2',
buttonAccent: '#ecdcff',
buttonText: '#25005a',
hoverAccent: '#d4baff',
});
if (!player) {
return;
}
players.set(
bus_name,
PlayerBox(player, colorsVar, self),
);
self.overlays = Array.from(players.values())
.map((widget) => widget) as PlayerBox[];
const includes = self.attribute
.includesWidget(previousFirst);
// Select favorite player at startup
const attrs = self.attribute;
if (!attrs.setup && players.has(FAVE_PLAYER)) {
attrs.moveToTop(players.get(FAVE_PLAYER));
attrs.setup = true;
}
// Move previousFirst on top again
else if (includes) {
attrs.moveToTop(previousFirst);
}
}, 'player-added')
.hook(Mpris, (_, bus_name) => {
const players = self.attribute.players;
if (!bus_name || !players.has(bus_name)) {
return;
}
// Get the one on top so we can move it up later
const previousFirst = self.overlays.at(-1) as PlayerBox;
// Remake overlays without deleted one
players.delete(bus_name);
self.overlays = Array.from(players.values())
.map((widget) => widget) as PlayerBox[];
// Move previousFirst on top again
const includes = self.attribute
.includesWidget(previousFirst);
if (includes) {
self.attribute.moveToTop(previousFirst);
}
}, 'player-closed');
},
});
return Box({
class_name: 'media',
child: content,
});
};

View file

@ -1,57 +0,0 @@
const Audio = await Service.import('audio');
const speakerIcons = {
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 micIcons = {
67: 'audio-input-microphone-high-symbolic',
34: 'audio-input-microphone-medium-symbolic',
1: 'audio-input-microphone-low-symbolic',
0: 'audio-input-microphone-muted-symbolic',
};
export const SpeakerIcon = Variable('');
Audio.connect('speaker-changed', () => {
if (!Audio.speaker) {
return;
}
if (Audio.speaker.stream?.is_muted) {
SpeakerIcon.setValue(speakerIcons[0]);
}
else {
const vol = Audio.speaker.volume * 100;
for (const threshold of [-1, 0, 33, 66, 100]) {
if (vol > threshold + 1) {
SpeakerIcon.setValue(speakerIcons[threshold + 1]);
}
}
}
});
export const MicIcon = Variable('');
Audio.connect('microphone-changed', () => {
if (!Audio.microphone) {
return;
}
if (Audio.microphone.stream?.is_muted) {
MicIcon.setValue(micIcons[0]);
}
else {
const vol = Audio.microphone.volume * 100;
for (const threshold of [-1, 0, 33, 66]) {
if (vol > threshold + 1) {
MicIcon.setValue(micIcons[threshold + 1]);
}
}
}
});

View file

@ -1,15 +0,0 @@
const { Window } = Widget;
export default () => Window({
name: 'bg-gradient',
layer: 'background',
exclusivity: 'ignore',
anchor: ['top', 'bottom', 'left', 'right'],
css: `
background-image: -gtk-gradient (linear,
left top, left bottom,
from(rgba(0, 0, 0, 0.5)),
to(rgba(0, 0, 0, 0)));
`,
});

View file

@ -1,13 +0,0 @@
// Types
import { PopupWindow } from 'global-types';
export default () => {
(App.windows as PopupWindow[])
.filter((w) => w &&
w.close_on_unfocus &&
w.close_on_unfocus !== 'stay')
.forEach((w) => {
App.closeWindow(w.name);
});
};

View file

@ -1,270 +0,0 @@
import Gtk from 'gi://Gtk?version=3.0';
import Gdk from 'gi://Gdk?version=3.0';
// Types
import { BaseProps, Widget as AgsWidget } from 'types/widgets/widget';
type EventHandler<Self> = (self: Self, event: Gdk.Event) => boolean | unknown;
// eslint-disable-next-line
export interface CursorBox<Child, Attr> extends AgsWidget<Attr> { }
export type CursorBoxProps<
Child extends Gtk.Widget,
Attr = unknown,
Self = CursorBox<Child, Attr>,
> = BaseProps<Self, Gtk.EventBox.ConstructorProperties & {
child?: Child
on_hover?: EventHandler<Self>
on_hover_lost?: EventHandler<Self>
on_scroll_up?: EventHandler<Self>
on_scroll_down?: EventHandler<Self>
on_primary_click?: EventHandler<Self>
on_middle_click?: EventHandler<Self>
on_secondary_click?: EventHandler<Self>
on_primary_click_release?: EventHandler<Self>
on_middle_click_release?: EventHandler<Self>
on_secondary_click_release?: EventHandler<Self>
}, Attr>;
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
export class CursorBox<Child extends Gtk.Widget, Attr> extends Gtk.EventBox {
static {
Widget.register(this, {
properties: {
'on-clicked': ['jsobject', 'rw'],
'on-hover': ['jsobject', 'rw'],
'on-hover-lost': ['jsobject', 'rw'],
'on-scroll-up': ['jsobject', 'rw'],
'on-scroll-down': ['jsobject', 'rw'],
'on-primary-click': ['jsobject', 'rw'],
'on-secondary-click': ['jsobject', 'rw'],
'on-middle-click': ['jsobject', 'rw'],
'on-primary-click-release': ['jsobject', 'rw'],
'on-secondary-click-release': ['jsobject', 'rw'],
'on-middle-click-release': ['jsobject', 'rw'],
},
});
}
constructor(props: CursorBoxProps<Child, Attr> = {}) {
super(props as Gtk.EventBox.ConstructorProperties);
this.add_events(Gdk.EventMask.SCROLL_MASK);
this.add_events(Gdk.EventMask.SMOOTH_SCROLL_MASK);
// Gesture stuff
const gesture = Gtk.GestureMultiPress.new(this);
this.hook(gesture, (_, _n, x, y) => {
if (!x || !y) {
return;
}
this.#canRun.setValue(!(
x > this.get_allocated_width() ||
x <= 0 ||
y > this.get_allocated_height() ||
y <= 0
));
}, 'released');
this.connect('enter-notify-event', (_, event: Gdk.Event) => {
this.set_state_flags(Gtk.StateFlags.PRELIGHT, false);
if (!this.#display) {
return;
}
this.window.set_cursor(Gdk.Cursor.new_from_name(
this.#display,
this.#disabled.value ?
'not-allowed' :
'pointer',
));
return this.on_hover?.(this, event);
});
this.connect('leave-notify-event', (_, event: Gdk.Event) => {
this.unset_state_flags(Gtk.StateFlags.PRELIGHT);
this.window.set_cursor(null);
return this.on_hover_lost?.(this, event);
});
this.connect('button-press-event', (_, event: Gdk.Event) => {
this.set_state_flags(Gtk.StateFlags.ACTIVE, false);
if (this.#disabled.value) {
return;
}
if (event.get_button()[1] === Gdk.BUTTON_PRIMARY) {
return this.on_primary_click?.(this, event);
}
else if (event.get_button()[1] === Gdk.BUTTON_MIDDLE) {
return this.on_middle_click?.(this, event);
}
else if (event.get_button()[1] === Gdk.BUTTON_SECONDARY) {
return this.on_secondary_click?.(this, event);
}
});
this.connect('button-release-event', (_, event: Gdk.Event) => {
this.unset_state_flags(Gtk.StateFlags.ACTIVE);
if (this.#disabled.value) {
return;
}
if (event.get_button()[1] === Gdk.BUTTON_PRIMARY) {
// Every click, do a one shot connect to
// CanRun to wait for location of click
const id = this.#canRun.connect('changed', () => {
if (this.#canRun.value) {
this.on_primary_click_release?.(this, event);
}
this.#canRun.disconnect(id);
});
}
else if (event.get_button()[1] === Gdk.BUTTON_MIDDLE) {
return this.on_middle_click_release?.(this, event);
}
else if (event.get_button()[1] === Gdk.BUTTON_SECONDARY) {
return this.on_secondary_click_release?.(this, event);
}
});
this.connect('scroll-event', (_, event: Gdk.Event) => {
if (event.get_scroll_deltas()[2] < 0) {
return this.on_scroll_up?.(this, event);
}
else if (event.get_scroll_deltas()[2] > 0) {
return this.on_scroll_down?.(this, event);
}
});
this.hook(this.#disabled, () => {
this.toggleClassName('disabled', this.#disabled.value);
});
}
#display = Gdk.Display.get_default();
// Make this variable to know if the function should
// be executed depending on where the click is released
#canRun = Variable(true);
#disabled = Variable(false);
get disabled() {
return this.#disabled.value;
}
set disabled(value: boolean) {
this.#disabled.setValue(value);
}
get child() {
return super.child as Child;
}
set child(child: Child) {
super.child = child;
}
get on_hover() {
return this._get('on-hover');
}
set on_hover(callback: EventHandler<this>) {
this._set('on-hover', callback);
}
get on_hover_lost() {
return this._get('on-hover-lost');
}
set on_hover_lost(callback: EventHandler<this>) {
this._set('on-hover-lost', callback);
}
get on_scroll_up() {
return this._get('on-scroll-up');
}
set on_scroll_up(callback: EventHandler<this>) {
this._set('on-scroll-up', callback);
}
get on_scroll_down() {
return this._get('on-scroll-down');
}
set on_scroll_down(callback: EventHandler<this>) {
this._set('on-scroll-down', callback);
}
get on_primary_click() {
return this._get('on-primary-click');
}
set on_primary_click(callback: EventHandler<this>) {
this._set('on-primary-click', callback);
}
get on_middle_click() {
return this._get('on-middle-click');
}
set on_middle_click(callback: EventHandler<this>) {
this._set('on-middle-click', callback);
}
get on_secondary_click() {
return this._get('on-secondary-click');
}
set on_secondary_click(callback: EventHandler<this>) {
this._set('on-secondary-click', callback);
}
get on_primary_click_release() {
return this._get('on-primary-click-release');
}
set on_primary_click_release(callback: EventHandler<this>) {
this._set('on-primary-click-release', callback);
}
get on_middle_click_release() {
return this._get('on-middle-click-release');
}
set on_middle_click_release(callback: EventHandler<this>) {
this._set('on-middle-click-release', callback);
}
get on_secondary_click_release() {
return this._get('on-secondary-click-release');
}
set on_secondary_click_release(callback: EventHandler<this>) {
this._set('on-secondary-click-release', callback);
}
}
export default <Child extends Gtk.Widget, Attr>(
props?: CursorBoxProps<Child, Attr>,
) => new CursorBox(props ?? {});

View file

@ -1,54 +0,0 @@
const { execAsync, readFileAsync, timeout } = Utils;
const { get_home_dir } = imports.gi.GLib;
import GObject from 'types/@girs/gobject-2.0/gobject-2.0';
interface Persist {
name: string
gobject: GObject.Object
prop: string
condition?: boolean | string // If string, compare following props to this
whenTrue?: boolean | string
whenFalse?: boolean | string
signal?: string
}
export default ({
name,
gobject,
prop,
condition = true,
whenTrue = condition,
whenFalse = false,
signal = 'changed',
}: Persist) => {
const cacheFile = `${get_home_dir()}/.cache/ags/.${name}`;
const stateCmd = () => ['bash', '-c',
`echo ${gobject[prop] === condition} > ${cacheFile}`];
const monitorState = () => {
gobject.connect(signal, () => {
execAsync(stateCmd()).catch(print);
});
};
readFileAsync(cacheFile)
.then((content) => {
// JSON.parse was the only way I found to reliably
// convert a string of 'true' or 'false' into a bool
gobject[prop] = JSON.parse(content) ? whenTrue : whenFalse;
timeout(1000, () => {
monitorState();
});
})
.catch(() => {
execAsync(stateCmd())
.then(() => {
monitorState();
})
.catch(print);
});
};

View file

@ -1,183 +0,0 @@
import Gtk from 'gi://Gtk?version=3.0';
const Hyprland = await Service.import('hyprland');
import { get_hyprland_monitor } from '../lib.ts';
/* Types */
import { Window } from 'resource:///com/github/Aylur/ags/widgets/window.js';
import type { WindowProps } from 'types/widgets/window';
import type { Widget as AgsWidget } from 'types/widgets/widget';
// eslint-disable-next-line
export interface PopupWindow<Child, Attr> extends AgsWidget<Attr> { }
import {
CloseType,
HyprTransition,
} from 'global-types';
export type PopupWindowProps<
Child extends Gtk.Widget,
Attr = unknown,
Self = PopupWindow<Child, Attr>,
> = WindowProps<Child, Attr, Self> & {
transition?: HyprTransition
on_open?(self: PopupWindow<Child, Attr>): void
on_close?(self: PopupWindow<Child, Attr>): void
close_on_unfocus?: CloseType
anchor?: ('top' | 'bottom' | 'right' | 'left')[]
};
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
export class PopupWindow<
Child extends Gtk.Widget,
Attr,
> extends Window<Child, Attr> {
static {
Widget.register(this, {
properties: {
close_on_unfocus: ['string', 'rw'],
transition: ['string', 'rw'],
},
});
}
private _close_on_unfocus: CloseType;
get close_on_unfocus() {
return this._close_on_unfocus;
}
set close_on_unfocus(value: CloseType) {
this._close_on_unfocus = value;
}
private _transition = 'unset' as HyprTransition;
get transition() {
return this._transition;
}
set transition(t: HyprTransition) {
Hyprland.messageAsync(
`keyword layerrule animation ${t}, ${this.name}`,
).catch(logError);
this._transition = t;
}
// eslint-disable-next-line no-use-before-define
protected _on_open: (self: PopupWindow<Child, Attr>) => void;
get on_open() {
return this._on_open;
}
set on_open(fun: (self: PopupWindow<Child, Attr>) => void) {
this._on_open = fun;
}
// eslint-disable-next-line no-use-before-define
private _on_close: (self: PopupWindow<Child, Attr>) => void;
get on_close() {
return this._on_close;
}
set on_close(fun: (self: PopupWindow<Child, Attr>) => void) {
this._on_close = fun;
}
constructor({
transition = 'slide top',
on_open = () => { /**/ },
on_close = () => { /**/ },
// Window props
name,
visible = false,
anchor = [],
layer = 'overlay',
close_on_unfocus = 'released',
...rest
}: PopupWindowProps<Child, Attr>) {
super({
...rest,
name: `win-${name}`,
visible,
anchor,
layer,
setup: () => {
const id = App.connect('config-parsed', () => {
// Add way to make window open on startup
if (visible) {
App.openWindow(`win-${name}`);
}
// This connection should always run only once
App.disconnect(id);
});
},
});
this._close_on_unfocus = close_on_unfocus;
this._on_open = on_open;
this._on_close = on_close;
this.hook(App, (_, currentName, isOpen) => {
if (currentName === `win-${name}`) {
// Make sure we have the right animation
this.transition = transition;
if (isOpen) {
this._on_open(this);
}
else {
this._on_close(this);
}
}
});
}
set_x_pos(
alloc: Gtk.Allocation,
side = 'right' as 'left' | 'right',
) {
const monitor = this.gdkmonitor ??
this.get_display().get_monitor_at_point(alloc.x, alloc.y);
const transform = get_hyprland_monitor(monitor)?.transform;
let width: number;
if (transform && (transform === 1 || transform === 3)) {
width = monitor.get_geometry().height;
}
else {
width = monitor.get_geometry().width;
}
this.margins = [
this.margins[0],
side === 'right' ?
(width - alloc.x - alloc.width) :
this.margins[1],
this.margins[2],
side === 'right' ?
this.margins[3] :
(alloc.x - alloc.width),
];
}
}
export default <Child extends Gtk.Widget, Attr>(
props: PopupWindowProps<Child, Attr>,
) => new PopupWindow(props);

View file

@ -1,13 +0,0 @@
const { Box } = Widget;
export default (size: number, {
vertical = false,
css = '',
...props
} = {}) => {
return Box({
css: `${vertical ? 'min-height' : 'min-width'}: ${size}px; ${css}`,
...props,
});
};

View file

@ -1,201 +0,0 @@
const Hyprland = await Service.import('hyprland');
const { Box, Entry, Icon, Label, ListBox, Revealer, Scrollable } = Widget;
/* Types */
import { PopupWindow, PopupWindowProps } from '../misc/popup.ts';
import type { Widget as AgsWidget } from 'types/widgets/widget';
import { ListBoxRow } from 'types/@girs/gtk-3.0/gtk-3.0.cjs';
import { Monitor } from 'types/service/hyprland';
import { Binding } from 'types/service';
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export interface SortedList<Attr> extends AgsWidget<Attr> { }
// eslint-disable-next-line no-use-before-define
type MakeChild = ReturnType<typeof makeChild>;
type SortedListProps<Attr = unknown, Self = SortedList<Attr>> =
PopupWindowProps<MakeChild['child'], Attr, Self> & {
on_select?: (row: ListBoxRow) => void
init_rows?: (list: MakeChild['list']) => void
on_text_change?: (
text: string,
list: MakeChild['list'],
placeholder: MakeChild['placeholder'],
) => void
setup_list?: (list: MakeChild['list'], entry: MakeChild['entry']) => void
};
const centerCursor = async(): Promise<void> => {
let x: number;
let y: number;
const monitor = (JSON.parse(await Hyprland.messageAsync('j/monitors')) as Monitor[])
.find((m) => m.focused) as Monitor;
switch (monitor.transform) {
case 1:
x = monitor.x - (monitor.height / 2);
y = monitor.y - (monitor.width / 2);
break;
case 2:
x = monitor.x - (monitor.width / 2);
y = monitor.y - (monitor.height / 2);
break;
case 3:
x = monitor.x + (monitor.height / 2);
y = monitor.y + (monitor.width / 2);
break;
default:
x = monitor.x + (monitor.width / 2);
y = monitor.y + (monitor.height / 2);
break;
}
await Hyprland.messageAsync(`dispatch movecursor ${x} ${y}`);
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const makeChild = (class_name: string | Binding<any, any, string>) => {
const list = ListBox();
const placeholder = Revealer({
child: Label({
label: " Couldn't find a match",
class_name: 'placeholder',
}),
});
const entry = Entry({
// Set some text so on-change works the first time
text: '-',
hexpand: true,
});
const scrollable = Scrollable({
hscroll: 'never',
vscroll: 'automatic',
child: Box({
vertical: true,
children: [list, placeholder],
}),
});
return {
list,
entry,
placeholder,
scrollable,
child: Box({
class_name,
vertical: true,
children: [
Box({
class_name: 'header',
children: [
Icon('preferences-system-search-symbolic'),
entry,
],
}),
scrollable,
],
}),
};
};
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
export class SortedList<
Attr,
> extends PopupWindow<MakeChild['child'], Attr> {
static {
Widget.register(this, {
properties: {
},
});
}
private _list: MakeChild['list'];
private _entry: MakeChild['entry'];
private _placeholder: MakeChild['placeholder'];
private _scrollable: MakeChild['scrollable'];
private _on_select: (row: ListBoxRow) => void;
private _init_rows: (list: MakeChild['list']) => void;
private _on_text_change: (
text: string,
list: MakeChild['list'],
placeholder: MakeChild['placeholder'],
) => void;
set on_open(fun: (self: PopupWindow<MakeChild['child'], Attr>) => void) {
this._on_open = () => {
this._entry.text = '';
fun(this);
this._init_rows(this._list);
centerCursor();
const adjustScroll = this._scrollable.vadjustment;
this._scrollable.vadjustment.set_value(adjustScroll.lower);
this._entry.grab_focus();
};
}
constructor({
on_select = () => { /**/ },
init_rows = () => { /**/ },
on_text_change = () => { /**/ },
setup_list = () => { /**/ },
on_open = () => { /**/ },
class_name = '',
keymode = 'on-demand',
...rest
}: SortedListProps<Attr, PopupWindow<MakeChild['child'], Attr>>) {
const makeChildResult = makeChild(class_name);
// PopupWindow
super({
child: makeChildResult.child,
keymode,
...rest,
});
this.on_open = on_open;
// SortedList
this._on_select = on_select;
this._init_rows = init_rows;
this._on_text_change = on_text_change;
this._placeholder = makeChildResult.placeholder;
this._scrollable = makeChildResult.scrollable;
this._list = makeChildResult.list;
this._list.on('row-activated', (_, row) => {
this._on_select(row);
});
this._entry = makeChildResult.entry;
this._entry.on_change = ({ text }) => {
if (text !== null || typeof text === 'string') {
this._on_text_change(text, this._list, this._placeholder);
}
};
// TODO: add on_accept where it just selects the first visible one
setup_list(this._list, this._entry);
this._init_rows(this._list);
this._on_text_change('', this._list, this._placeholder);
}
}
export default <Attr>(
props: SortedListProps<Attr, PopupWindow<MakeChild['child'], Attr>>,
) => new SortedList(props);

View file

@ -1,289 +0,0 @@
const Applications = await Service.import('applications');
const Hyprland = await Service.import('hyprland');
const Notifications = await Service.import('notifications');
const { Box, Icon, Label, Button } = Widget;
const { lookUpIcon } = Utils;
const { GLib } = imports.gi;
import Gesture from './gesture.ts';
import CursorBox from '../misc/cursorbox.ts';
import { launchApp } from '../applauncher/launch.ts';
// Types
import { Notification as NotifObj } from 'types/service/notifications.ts';
import { Client } from 'types/service/hyprland.ts';
interface NotificationWidget {
notif: NotifObj
slideIn?: 'Left' | 'Right'
command?(): void
}
import {
CursorBox as CBox,
EventBoxGeneric,
NotifGesture,
} from 'global-types';
// Set Notifications settings
Notifications.popupTimeout = 5000;
Notifications.cacheActions = true;
const setTime = (time: number) => {
return GLib.DateTime
.new_from_unix_local(time)
.format('%H:%M');
};
const getDragState = (box: EventBoxGeneric) => (box
.get_parent()
?.get_parent()
?.get_parent()
?.get_parent()
?.get_parent() as NotifGesture)
?.attribute.dragging;
const NotificationIcon = (notif: NotifObj) => {
let iconCmd = (box: CBox): void => {
if (!box) {
console.log();
}
};
if (notif.app_entry && Applications.query(notif.app_entry).length > 0) {
const app = Applications.query(notif.app_entry)[0];
let wmClass = app.app.get_string('StartupWMClass');
if (app.app?.get_filename()?.includes('discord')) {
wmClass = 'discord';
}
if (wmClass != null) {
iconCmd = (box) => {
if (!getDragState(box)) {
if (wmClass === 'Proton Mail') {
Hyprland.messageAsync('dispatch ' +
'togglespecialworkspace thunder');
}
else if (wmClass === 'Spotify') {
Hyprland.messageAsync('dispatch ' +
'togglespecialworkspace spot');
}
else {
Hyprland.messageAsync('j/clients').then((msg) => {
const clients = JSON.parse(msg) as Client[];
const classes = [] as string[];
for (const key of clients) {
if (key.class) {
classes.push(key.class);
}
}
if (wmClass && classes.includes(wmClass)) {
Hyprland.messageAsync('dispatch ' +
`focuswindow ^(${wmClass})`);
}
else {
Hyprland.messageAsync('dispatch workspace empty')
.then(() => {
launchApp(app);
});
}
});
}
globalThis.closeAll();
}
};
}
}
if (notif.image) {
return CursorBox({
on_primary_click_release: (self) => {
iconCmd(self);
},
child: Box({
vpack: 'start',
hexpand: false,
class_name: 'icon img',
css: `
background-image: url("${notif.image}");
background-size: contain;
background-repeat: no-repeat;
background-position: center;
min-width: 78px;
min-height: 78px;
`,
}),
});
}
let icon = 'dialog-information-symbolic';
if (lookUpIcon(notif.app_icon)) {
icon = notif.app_icon;
}
if (notif.app_entry && lookUpIcon(notif.app_entry)) {
icon = notif.app_entry;
}
return CursorBox({
on_primary_click_release: (self) => {
iconCmd(self);
},
child: Box({
vpack: 'start',
hexpand: false,
class_name: 'icon',
css: `
min-width: 78px;
min-height: 78px;
`,
children: [Icon({
icon,
size: 58,
hpack: 'center',
hexpand: true,
vpack: 'center',
vexpand: true,
})],
}),
});
};
// Make a variable to connect to for Widgets
// to know when there are notifs or not
export const HasNotifs = Variable(false);
export const Notification = ({
notif,
slideIn = 'Left',
command = () => { /**/ },
}: NotificationWidget) => {
if (!notif) {
return;
}
const BlockedApps = [
'Spotify',
];
if (BlockedApps.find((app) => app === notif.app_name)) {
notif.close();
return;
}
HasNotifs.setValue(Notifications.notifications.length > 0);
// Init notif
const notifWidget = Gesture({
command,
slideIn,
id: notif.id,
});
// Add body to notif
(notifWidget.child as EventBoxGeneric).add(Box({
class_name: `notification ${notif.urgency}`,
vexpand: false,
// Notification
child: Box({
vertical: true,
children: [
// Content
Box({
children: [
NotificationIcon(notif),
Box({
hexpand: true,
vertical: true,
children: [
// Top of Content
Box({
children: [
// Title
Label({
class_name: 'title',
xalign: 0,
justification: 'left',
hexpand: true,
max_width_chars: 24,
truncate: 'end',
wrap: true,
label: notif.summary,
use_markup: notif.summary
.startsWith('<'),
}),
// Time
Label({
class_name: 'time',
vpack: 'start',
label: setTime(notif.time),
}),
// Close button
CursorBox({
child: Button({
class_name: 'close-button',
vpack: 'start',
on_primary_click_release: () =>
notif.close(),
child: Icon('window-close' +
'-symbolic'),
}),
}),
],
}),
// Description
Label({
class_name: 'description',
hexpand: true,
use_markup: true,
xalign: 0,
justification: 'left',
label: notif.body,
wrap: true,
}),
],
}),
],
}),
// Actions
Box({
class_name: 'actions',
children: notif.actions.map((action) => Button({
class_name: 'action-button',
hexpand: true,
on_primary_click_release: () => notif.invoke(action.id),
child: Label(action.label),
})),
}),
],
}),
}));
return notifWidget;
};

View file

@ -1,27 +0,0 @@
const { Window } = Widget;
import NotifCenterWidget from './center.ts';
import PopUpsWidget from './popup.ts';
import PopupWindow from '../misc/popup.ts';
import { get_gdkmonitor_from_desc } from '../lib.ts';
export const NotifPopups = () => Window({
name: 'notifications',
anchor: ['bottom', 'left'],
layer: 'overlay',
gdkmonitor: get_gdkmonitor_from_desc('desc:Acer Technologies Acer K212HQL T3EAA0014201'),
child: PopUpsWidget(),
});
export const NotifCenter = () => PopupWindow({
name: 'notification-center',
anchor: ['bottom', 'right'],
transition: 'slide bottom',
gdkmonitor: get_gdkmonitor_from_desc('desc:Acer Technologies Acer K212HQL T3EAA0014201'),
child: NotifCenterWidget(),
});

View file

@ -1,167 +0,0 @@
const Notifications = await Service.import('notifications');
const { Label, Box, Icon, Scrollable, Revealer } = Widget;
const { timeout } = Utils;
import { Notification, HasNotifs } from './base.ts';
import CursorBox from '../misc/cursorbox.ts';
// Types
import { Notification as NotifObj } from 'resource:///com/github/Aylur/ags/service/notifications.js';
import { BoxGeneric, NotifGesture } from 'global-types';
const addNotif = (box: BoxGeneric, notif: NotifObj) => {
if (notif) {
const NewNotif = Notification({
notif,
slideIn: 'Right',
command: () => notif.close(),
});
if (NewNotif) {
box.pack_end(NewNotif, false, false, 0);
box.show_all();
}
}
};
const NotificationList = () => Box({
vertical: true,
vexpand: true,
vpack: 'start',
visible: HasNotifs.bind(),
setup: (self) => {
const initNotifId = 1337;
const delay = 2000;
// Fix the no notif placement with a fake notif
Notifications.Notify('', initNotifId, '', '', '', [''], {}, 0);
setTimeout(() => {
Notifications.getNotification(initNotifId)?.close();
}, delay);
self
.hook(Notifications, (box, id) => {
// Handle cached notifs
if (box.children.length === 0) {
Notifications.notifications.forEach((n) => {
addNotif(box, n);
});
}
else if (id) {
const notifObj = Notifications.getNotification(id);
if (notifObj) {
addNotif(box, notifObj);
}
}
}, 'notified')
.hook(Notifications, (box, id) => {
const notif = (box.children as NotifGesture[])
.find((ch) => ch.attribute.id === id);
if (notif?.sensitive) {
notif.attribute.slideAway('Right');
}
}, 'closed');
},
});
const ClearButton = () => CursorBox({
class_name: 'clear',
on_primary_click_release: () => {
Notifications.clear();
timeout(1000, () => App.closeWindow('win-notification-center'));
},
setup: (self) => {
self.hook(HasNotifs, () => {
self.disabled = !HasNotifs.value;
});
},
child: Box({
children: [
Label('Clear '),
Icon({
setup: (self) => {
self.hook(Notifications, () => {
self.icon = Notifications.notifications.length > 0 ?
'user-trash-full-symbolic' :
'user-trash-symbolic';
});
},
}),
],
}),
});
const Header = () => Box({
class_name: 'header',
children: [
Label({
label: 'Notifications',
hexpand: true,
xalign: 0,
}),
ClearButton(),
],
});
const Placeholder = () => Revealer({
transition: 'crossfade',
reveal_child: HasNotifs.bind()
.transform((v) => !v),
child: Box({
class_name: 'placeholder',
vertical: true,
vpack: 'center',
hpack: 'center',
vexpand: true,
hexpand: true,
children: [
Icon('notification-disabled-symbolic'),
Label('Your inbox is empty'),
],
}),
});
export default () => Box({
class_name: 'notification-center',
vertical: true,
children: [
Header(),
Box({
class_name: 'notification-wallpaper-box',
children: [
Scrollable({
class_name: 'notification-list-box',
hscroll: 'never',
vscroll: 'automatic',
child: Box({
class_name: 'notification-list',
vertical: true,
children: [
NotificationList(),
Placeholder(),
],
}),
}),
],
}),
],
});

View file

@ -1,223 +0,0 @@
const Notifications = await Service.import('notifications');
const { Box, EventBox } = Widget;
const { timeout } = Utils;
import { HasNotifs } from './base.ts';
const { Gdk, Gtk } = imports.gi;
const display = Gdk.Display.get_default();
// Types
import { BoxGeneric } from 'global-types';
const MAX_OFFSET = 200;
const OFFSCREEN = 300;
const ANIM_DURATION = 500;
const SLIDE_MIN_THRESHOLD = 10;
const TRANSITION = 'transition: margin 0.5s ease, opacity 0.5s ease;';
const SQUEEZED = 'margin-bottom: -70px; margin-top: -70px;';
const MAX_LEFT = `
margin-left: -${Number(MAX_OFFSET + OFFSCREEN)}px;
margin-right: ${Number(MAX_OFFSET + OFFSCREEN)}px;
`;
const MAX_RIGHT = `
margin-left: ${Number(MAX_OFFSET + OFFSCREEN)}px;
margin-right: -${Number(MAX_OFFSET + OFFSCREEN)}px;
`;
const slideLeft = `${TRANSITION} ${MAX_LEFT}
margin-top: 0px;
margin-bottom: 0px;
opacity: 0;`;
const squeezeLeft = `${TRANSITION} ${MAX_LEFT} ${SQUEEZED} opacity: 0;`;
const slideRight = `${TRANSITION} ${MAX_RIGHT}
margin-top: 0px;
margin-bottom: 0px;
opacity: 0;`;
const squeezeRight = `${TRANSITION} ${MAX_RIGHT} ${SQUEEZED} opacity: 0;`;
const defaultStyle = `${TRANSITION} margin: unset; opacity: 1;`;
export default ({
id,
slideIn = 'Left',
command = () => { /**/ },
...props
}) => {
const widget = EventBox({
...props,
setup: (self) => {
self
// OnClick
.on('button-press-event', () => {
if (!display) {
return;
}
self.window.set_cursor(Gdk.Cursor.new_from_name(
display,
'grabbing',
));
})
// OnRelease
.on('button-release-event', () => {
if (!display) {
return;
}
self.window.set_cursor(Gdk.Cursor.new_from_name(
display,
'grab',
));
})
// OnHover
.on('enter-notify-event', () => {
if (!display) {
return;
}
self.window.set_cursor(Gdk.Cursor.new_from_name(
display,
'grab',
));
self.toggleClassName('hover', true);
if (!self.attribute.hovered) {
self.attribute.hovered = true;
}
})
// OnHoverLost
.on('leave-notify-event', () => {
self.window.set_cursor(null);
self.toggleClassName('hover', false);
if (self.attribute.hovered) {
self.attribute.hovered = false;
}
});
},
attribute: {
dragging: false,
hovered: false,
ready: false,
id,
slideAway: (side: 'Left' | 'Right') => {
(widget.child as BoxGeneric)
.setCss(side === 'Left' ? slideLeft : slideRight);
// Make it uninteractable
widget.sensitive = false;
timeout(ANIM_DURATION - 100, () => {
// Reduce height after sliding away
(widget.child as BoxGeneric)?.setCss(side === 'Left' ?
squeezeLeft :
squeezeRight);
timeout(ANIM_DURATION, () => {
// Kill notif and update HasNotifs after anim is done
command();
HasNotifs.setValue(Notifications
.notifications.length > 0);
(widget.get_parent() as BoxGeneric)?.remove(widget);
});
});
},
},
});
const gesture = Gtk.GestureDrag.new(widget);
widget.add(Box({
css: squeezeLeft,
setup: (self) => {
self
// When dragging
.hook(gesture, () => {
let offset = gesture.get_offset()[1];
if (!offset || offset === 0) {
return;
}
// Slide right
if (offset > 0) {
self.setCss(`
margin-top: 0px; margin-bottom: 0px;
opacity: 1; transition: none;
margin-left: ${offset}px;
margin-right: -${offset}px;
`);
}
// Slide left
else {
offset = Math.abs(offset);
self.setCss(`
margin-top: 0px; margin-bottom: 0px;
opacity: 1; transition: none;
margin-right: ${offset}px;
margin-left: -${offset}px;
`);
}
// Put a threshold on if a click is actually dragging
widget.attribute.dragging =
Math.abs(offset) > SLIDE_MIN_THRESHOLD;
widget.cursor = 'grabbing';
}, 'drag-update')
// On drag end
.hook(gesture, () => {
// Make it slide in on init
if (!widget.attribute.ready) {
// Reverse of slideAway, so it started at squeeze, then we go to slide
self.setCss(slideIn === 'Left' ?
slideLeft :
slideRight);
timeout(ANIM_DURATION, () => {
// Then we go to center
self.setCss(defaultStyle);
timeout(ANIM_DURATION, () => {
widget.attribute.ready = true;
});
});
return;
}
const offset = gesture.get_offset()[1];
if (!offset) {
return;
}
// If crosses threshold after letting go, slide away
if (Math.abs(offset) > MAX_OFFSET) {
if (offset > 0) {
widget.attribute.slideAway('Right');
}
else {
widget.attribute.slideAway('Left');
}
}
else {
self.setCss(defaultStyle);
widget.cursor = 'grab';
widget.attribute.dragging = false;
}
}, 'drag-end');
},
}));
return widget;
};

View file

@ -1,77 +0,0 @@
const Notifications = await Service.import('notifications');
const { Box } = Widget;
const { interval } = Utils;
const { GLib } = imports.gi;
import { Notification } from './base.ts';
const DELAY = 2000;
// Types
import { NotifGesture } from 'global-types';
export default () => Box({
vertical: true,
// Needed so it occupies space at the start
css: 'padding: 1px;',
setup: (self) => {
const addPopup = (id: number) => {
if (!id) {
return;
}
const notif = Notifications.getNotification(id);
if (notif) {
const NewNotif = Notification({
notif,
command: () => {
if (notif.popup) {
notif.dismiss();
}
},
});
if (NewNotif) {
// Use this instead of add to put it at the top
self.pack_end(NewNotif, false, false, 0);
self.show_all();
}
}
};
const handleDismiss = (id: number, force = false) => {
const notif = (self.children as NotifGesture[])
.find((ch) => ch.attribute.id === id);
if (!notif) {
return;
}
// If notif isn't hovered or was closed, slide away
if (!notif.attribute.hovered || force) {
notif.attribute.slideAway('Left');
}
// If notif is hovered, delay close
else if (notif.attribute.hovered) {
const intervalId = interval(DELAY, () => {
if (!notif.attribute.hovered && intervalId) {
notif.attribute.slideAway('Left');
GLib.source_remove(intervalId);
}
});
}
};
self
.hook(Notifications, (_, id) => addPopup(id), 'notified')
.hook(Notifications, (_, id) => handleDismiss(id), 'dismissed')
.hook(Notifications, (_, id) => handleDismiss(id, true), 'closed');
},
});

View file

@ -1,25 +0,0 @@
const { Window } = Widget;
import NotifCenterWidget from './center.ts';
import PopUpsWidget from './popup.ts';
import PopupWindow from '../misc/popup.ts';
export const NotifPopups = () => Window({
name: 'notifications',
layer: 'overlay',
anchor: ['top', 'left'],
child: PopUpsWidget(),
});
const TOP_MARGIN = 6;
export const NotifCenter = () => PopupWindow({
name: 'notification-center',
anchor: ['top', 'right'],
margins: [TOP_MARGIN, 0, 0, 0],
child: NotifCenterWidget(),
});

View file

@ -1,164 +0,0 @@
const Hyprland = await Service.import('hyprland');
const { execAsync, timeout } = Utils;
const { Gtk } = imports.gi;
import Tablet from '../../services/tablet.ts';
const KEY_N = 249;
const HIDDEN_MARGIN = 340;
const ANIM_DURATION = 700;
// Types
import { OskWindow } from 'global-types';
const releaseAllKeys = () => {
const keycodes = Array.from(Array(KEY_N).keys());
execAsync([
'ydotool', 'key',
...keycodes.map((keycode) => `${keycode}:0`),
]).catch(print);
};
export default (window: OskWindow) => {
const gesture = Gtk.GestureDrag.new(window);
window.child.setCss(`margin-bottom: -${HIDDEN_MARGIN}px;`);
let signals = [] as number[];
window.attribute = {
startY: null,
setVisible: (state: boolean) => {
if (state) {
window.visible = true;
window.attribute.setSlideDown();
window.child.setCss(`
transition: margin-bottom 0.7s
cubic-bezier(0.36, 0, 0.66, -0.56);
margin-bottom: 0px;
`);
}
else {
timeout(ANIM_DURATION + 100 + 100, () => {
if (!Tablet.tabletMode) {
window.visible = false;
}
});
releaseAllKeys();
window.attribute.setSlideUp();
window.child.setCss(`
transition: margin-bottom 0.7s
cubic-bezier(0.36, 0, 0.66, -0.56);
margin-bottom: -${HIDDEN_MARGIN}px;
`);
}
},
killGestureSigs: () => {
signals.forEach((id) => {
gesture.disconnect(id);
});
signals = [];
window.attribute.startY = null;
},
setSlideUp: () => {
window.attribute.killGestureSigs();
// Begin drag
signals.push(
gesture.connect('drag-begin', () => {
Hyprland.messageAsync('j/cursorpos').then((out) => {
window.attribute.startY = JSON.parse(out).y;
});
}),
);
// Update drag
signals.push(
gesture.connect('drag-update', () => {
Hyprland.messageAsync('j/cursorpos').then((out) => {
if (!window.attribute.startY) {
return;
}
const currentY = JSON.parse(out).y;
const offset = window.attribute.startY - currentY;
if (offset < 0) {
return;
}
window.child.setCss(`
margin-bottom: ${offset - HIDDEN_MARGIN}px;
`);
});
}),
);
// End drag
signals.push(
gesture.connect('drag-end', () => {
window.child.setCss(`
transition: margin-bottom 0.5s ease-in-out;
margin-bottom: -${HIDDEN_MARGIN}px;
`);
}),
);
},
setSlideDown: () => {
window.attribute.killGestureSigs();
// Begin drag
signals.push(
gesture.connect('drag-begin', () => {
Hyprland.messageAsync('j/cursorpos').then((out) => {
window.attribute.startY = JSON.parse(out).y;
});
}),
);
// Update drag
signals.push(
gesture.connect('drag-update', () => {
Hyprland.messageAsync('j/cursorpos').then((out) => {
if (!window.attribute.startY) {
return;
}
const currentY = JSON.parse(out).y;
const offset = window.attribute.startY - currentY;
if (offset > 0) {
return;
}
window.child.setCss(`
margin-bottom: ${offset}px;
`);
});
}),
);
// End drag
signals.push(
gesture.connect('drag-end', () => {
window.child.setCss(`
transition: margin-bottom 0.5s ease-in-out;
margin-bottom: 0px;
`);
}),
);
},
};
return window;
};

View file

@ -1,551 +0,0 @@
// TODO: right Ctrl https://handwiki.org/wiki/images/4/41/KB_Canadian_Multilingual_Standard.svg
export const defaultOskLayout = 'qwerty_custom';
export const oskLayouts = {
qwerty_custom: {
name: 'QWERTY - Custom',
name_short: 'CSA',
comment: 'Like physical keyboard',
// A normal key looks like this: {label: "a", labelShift: "A", shape: "normal", keycode: 30, type: "normal"}
// A modkey looks like this: {label: "Ctrl", shape: "control", keycode: 29, type: "modkey"}
// key types are: normal, tab, caps, shift, control, fn (normal w/ half height), space, expand
keys: [
[
{
keytype: 'normal',
label: 'Esc',
shape: 'fn',
keycode: 1,
},
{
keytype: 'normal',
label: 'F1',
shape: 'fn',
keycode: 59,
},
{
keytype: 'normal',
label: 'F2',
shape: 'fn',
keycode: 60,
},
{
keytype: 'normal',
label: 'F3',
shape: 'fn',
keycode: 61,
},
{
keytype: 'normal',
label: 'F4',
shape: 'fn',
keycode: 62,
},
{
keytype: 'normal',
label: 'F5',
shape: 'fn',
keycode: 63,
},
{
keytype: 'normal',
label: 'F6',
shape: 'fn',
keycode: 64,
},
{
keytype: 'normal',
label: 'F7',
shape: 'fn',
keycode: 65,
},
{
keytype: 'normal',
label: 'F8',
shape: 'fn',
keycode: 66,
},
{
keytype: 'normal',
label: 'F9',
shape: 'fn',
keycode: 67,
},
{
keytype: 'normal',
label: 'F10',
shape: 'fn',
keycode: 68,
},
{
keytype: 'normal',
label: 'F11',
shape: 'fn',
keycode: 87,
},
{
keytype: 'normal',
label: 'F12',
shape: 'fn',
keycode: 88,
},
{
keytype: 'normal',
label: 'Home',
shape: 'fn',
keycode: 110,
},
{
keytype: 'normal',
label: 'End',
shape: 'fn',
keycode: 115,
},
{
keytype: 'normal',
label: 'Del',
shape: 'fn',
keycode: 111,
},
],
[
{
keytype: 'normal',
label: '/',
labelShift: '\\',
labelAltGr: '|',
shape: 'normal',
keycode: 41,
},
{
keytype: 'normal',
label: '1',
labelShift: '!',
shape: 'normal',
keycode: 2,
},
{
keytype: 'normal',
label: '2',
labelShift: '@',
shape: 'normal',
keycode: 3,
},
{
keytype: 'normal',
label: '3',
labelShift: '#',
labelAltGr: '¤',
shape: 'normal',
keycode: 4,
},
{
keytype: 'normal',
label: '4',
labelShift: '$',
shape: 'normal',
keycode: 5,
},
{
keytype: 'normal',
label: '5',
labelShift: '%',
shape: 'normal',
keycode: 6,
},
{
keytype: 'normal',
label: '6',
labelShift: '?',
shape: 'normal',
keycode: 7,
},
{
keytype: 'normal',
label: '7',
labelShift: '&',
labelAltGr: '{',
shape: 'normal',
keycode: 8,
},
{
keytype: 'normal',
label: '8',
labelShift: '*',
labelAltGr: '}',
shape: 'normal',
keycode: 9,
},
{
keytype: 'normal',
label: '9',
labelShift: '(',
labelAltGr: '[',
shape: 'normal',
keycode: 10,
},
{
keytype: 'normal',
label: '0',
labelShift: ')',
labelAltGr: ']',
shape: 'normal',
keycode: 11,
},
{
keytype: 'normal',
label: '-',
labelShift: '_',
shape: 'normal',
keycode: 12,
},
{
keytype: 'normal',
label: '=',
labelShift: '+',
labelAltGr: '¬',
shape: 'normal',
keycode: 13,
},
{
keytype: 'normal',
label: 'Backspace',
shape: 'expand',
keycode: 14,
},
],
[
{
keytype: 'normal',
label: 'Tab',
shape: 'tab',
keycode: 15,
},
{
keytype: 'normal',
label: 'q',
labelShift: 'Q',
shape: 'normal',
keycode: 16,
},
{
keytype: 'normal',
label: 'w',
labelShift: 'W',
shape: 'normal',
keycode: 17,
},
{
keytype: 'normal',
label: 'e',
labelShift: 'E',
labelAltGr: '€',
shape: 'normal',
keycode: 18,
},
{
keytype: 'normal',
label: 'r',
labelShift: 'R',
shape: 'normal',
keycode: 19,
},
{
keytype: 'normal',
label: 't',
labelShift: 'T',
shape: 'normal',
keycode: 20,
},
{
keytype: 'normal',
label: 'y',
labelShift: 'Y',
shape: 'normal',
keycode: 21,
},
{
keytype: 'normal',
label: 'u',
labelShift: 'U',
shape: 'normal',
keycode: 22,
},
{
keytype: 'normal',
label: 'i',
labelShift: 'I',
shape: 'normal',
keycode: 23,
},
{
keytype: 'normal',
label: 'o',
labelShift: 'O',
shape: 'normal',
keycode: 24,
},
{
keytype: 'normal',
label: 'p',
labelShift: 'P',
shape: 'normal',
keycode: 25,
},
{
keytype: 'normal',
label: '^',
labelShift: '"',
labelAltGr: '`',
shape: 'normal',
keycode: 26,
},
{
keytype: 'normal',
label: 'ç',
labelShift: 'Ç',
labelAltGr: '~',
shape: 'normal',
keycode: 27,
},
{
keytype: 'normal',
label: 'à',
labelShift: 'À',
shape: 'expand',
keycode: 43,
},
],
[
{
keytype: 'normal',
label: 'Caps',
shape: 'caps',
keycode: 58,
},
{
keytype: 'normal',
label: 'a',
labelShift: 'A',
shape: 'normal',
keycode: 30,
},
{
keytype: 'normal',
label: 's',
labelShift: 'S',
shape: 'normal',
keycode: 31,
},
{
keytype: 'normal',
label: 'd',
labelShift: 'D',
shape: 'normal',
keycode: 32,
},
{
keytype: 'normal',
label: 'f',
labelShift: 'F',
shape: 'normal',
keycode: 33,
},
{
keytype: 'normal',
label: 'g',
labelShift: 'G',
shape: 'normal',
keycode: 34,
},
{
keytype: 'normal',
label: 'h',
labelShift: 'H',
shape: 'normal',
keycode: 35,
},
{
keytype: 'normal',
label: 'j',
labelShift: 'J',
shape: 'normal',
keycode: 36,
},
{
keytype: 'normal',
label: 'k',
labelShift: 'K',
shape: 'normal',
keycode: 37,
},
{
keytype: 'normal',
label: 'l',
labelShift: 'L',
shape: 'normal',
keycode: 38,
},
{
keytype: 'normal',
label: ';',
labelShift: ':',
labelAltGr: '°',
shape: 'normal',
keycode: 39,
},
{
keytype: 'normal',
label: 'è',
labelShift: 'È',
shape: 'normal',
keycode: 40,
},
{
keytype: 'normal',
label: 'Enter',
shape: 'expand',
keycode: 28,
},
],
[
{
keytype: 'modkey',
label: 'Shift',
shape: 'shift',
keycode: 42,
},
{
keytype: 'normal',
label: 'z',
labelShift: 'Z',
labelAltGr: '«',
shape: 'normal',
keycode: 44,
},
{
keytype: 'normal',
label: 'x',
labelShift: 'X',
labelAltGr: '»',
shape: 'normal',
keycode: 45,
},
{
keytype: 'normal',
label: 'c',
labelShift: 'C',
shape: 'normal',
keycode: 46,
},
{
keytype: 'normal',
label: 'v',
labelShift: 'V',
shape: 'normal',
keycode: 47,
},
{
keytype: 'normal',
label: 'b',
labelShift: 'B',
shape: 'normal',
keycode: 48,
},
{
keytype: 'normal',
label: 'n',
labelShift: 'N',
shape: 'normal',
keycode: 49,
},
{
keytype: 'normal',
label: 'm',
labelShift: 'M',
shape: 'normal',
keycode: 50,
},
{
keytype: 'normal',
label: ',',
labelShift: "'",
labelAltGr: '<',
shape: 'normal',
keycode: 51,
},
{
keytype: 'normal',
label: '.',
labelShift: '"',
labelAltGr: '>',
shape: 'normal',
keycode: 52,
},
{
keytype: 'normal',
label: 'é',
labelShift: 'É',
shape: 'normal',
keycode: 53,
},
{
keytype: 'modkey',
label: 'Shift',
shape: 'expand',
keycode: 54,
},
],
[
{
keytype: 'modkey',
label: 'Ctrl',
shape: 'control',
keycode: 29,
},
{
keytype: 'modkey',
label: 'Super',
shape: 'normal',
keycode: 125,
},
{
keytype: 'modkey',
label: 'Alt',
shape: 'normal',
keycode: 56,
},
{
keytype: 'normal',
label: 'Space',
shape: 'space',
keycode: 57,
},
{
keytype: 'normal',
label: 'Space',
shape: 'space',
keycode: 57,
},
{
keytype: 'modkey',
label: 'AltGr',
shape: 'normal',
keycode: 100,
},
{
keytype: 'normal',
label: 'PrtSc',
shape: 'fn',
keycode: 99,
},
{
keytype: 'modkey',
label: 'Ctrl',
shape: 'control',
keycode: 97,
},
],
],
},
};

View file

@ -1,169 +0,0 @@
const { Box, CenterBox, Label, ToggleButton } = Widget;
const { Gdk } = imports.gi;
const display = Gdk.Display.get_default();
import Separator from '../misc/separator.ts';
import RoundedCorner from '../corners/screen-corners.ts';
import Key from './keys.ts';
import { defaultOskLayout, oskLayouts } from './keyboard-layouts.ts';
const keyboardLayout = defaultOskLayout;
const keyboardJson = oskLayouts[keyboardLayout];
const L_KEY_PER_ROW = [8, 7, 6, 6, 6, 4]; // eslint-disable-line
const COLOR = 'rgba(0, 0, 0, 0.3)';
const SPACING = 4;
// Types
import { BoxGeneric, OskWindow } from 'global-types';
export default (window: OskWindow) => Box({
vertical: true,
children: [
CenterBox({
hpack: 'center',
start_widget: RoundedCorner('bottomright', `
background-color: ${COLOR};
`),
center_widget: CenterBox({
class_name: 'thingy',
css: `background: ${COLOR};`,
center_widget: Box({
hpack: 'center',
class_name: 'settings',
children: [
ToggleButton({
class_name: 'button',
active: true,
vpack: 'center',
setup: (self) => {
self
.on('toggled', () => {
self.toggleClassName(
'toggled',
self.get_active(),
);
window.exclusivity = self.get_active() ?
'exclusive' :
'normal';
})
// OnHover
.on('enter-notify-event', () => {
if (!display) {
return;
}
self.window.set_cursor(
Gdk.Cursor.new_from_name(
display,
'pointer',
),
);
self.toggleClassName('hover', true);
})
// OnHoverLost
.on('leave-notify-event', () => {
self.window.set_cursor(null);
self.toggleClassName('hover', false);
});
},
child: Label('Exclusive'),
}),
],
}),
}),
end_widget: RoundedCorner('bottomleft', `
background-color: ${COLOR};
`),
}),
CenterBox({
css: `background: ${COLOR};`,
class_name: 'osk',
hexpand: true,
start_widget: Box({
class_name: 'left-side side',
hpack: 'start',
vertical: true,
children: keyboardJson.keys.map((row, rowIndex) => {
const keys = [] as BoxGeneric[];
row.forEach((key, keyIndex) => {
if (keyIndex < L_KEY_PER_ROW[rowIndex]) {
keys.push(Key(key));
}
});
return Box({
vertical: true,
children: [
Box({
class_name: 'row',
children: [
Separator(SPACING),
...keys,
],
}),
Separator(SPACING, { vertical: true }),
],
});
}),
}),
center_widget: Box({
hpack: 'center',
vpack: 'center',
children: [
],
}),
end_widget: Box({
class_name: 'right-side side',
hpack: 'end',
vertical: true,
children: keyboardJson.keys.map((row, rowIndex) => {
const keys = [] as BoxGeneric[];
row.forEach((key, keyIndex) => {
if (keyIndex >= L_KEY_PER_ROW[rowIndex]) {
keys.push(Key(key));
}
});
return Box({
vertical: true,
children: [
Box({
hpack: 'end',
class_name: 'row',
children: keys,
}),
Separator(SPACING, { vertical: true }),
],
});
}),
}),
}),
],
});

View file

@ -1,254 +0,0 @@
import Brightness from '../../services/brightness.ts';
const { Box, EventBox, Label } = Widget;
const { execAsync } = Utils;
const { Gdk, Gtk } = imports.gi;
const display = Gdk.Display.get_default();
import Separator from '../misc/separator.ts';
// Keep track of when a non modifier key
// is clicked to release all modifiers
const NormalClick = Variable(false);
// Keep track of modifier statuses
const Super = Variable(false);
const LAlt = Variable(false);
const LCtrl = Variable(false);
const AltGr = Variable(false);
const RCtrl = Variable(false);
const Caps = Variable(false);
Brightness.connect('caps', (_, state) => {
Caps.setValue(state);
});
// Assume both shifts are the same for key.labelShift
const LShift = Variable(false);
const RShift = Variable(false);
const Shift = Variable(false);
LShift.connect('changed', () => {
Shift.setValue(LShift.value || RShift.value);
});
RShift.connect('changed', () => {
Shift.setValue(LShift.value || RShift.value);
});
const SPACING = 4;
const LSHIFT_CODE = 42;
const LALT_CODE = 56;
const LCTRL_CODE = 29;
// Types
import { Variable as Var } from 'types/variable.ts';
interface Key {
keytype: string
label: string
labelShift?: string
labelAltGr?: string
shape: string
keycode: number
}
const ModKey = (key: Key) => {
let Mod: Var<boolean>;
if (key.label === 'Super') {
Mod = Super;
}
// Differentiate left and right mods
else if (key.label === 'Shift' && key.keycode === LSHIFT_CODE) {
Mod = LShift;
}
else if (key.label === 'Alt' && key.keycode === LALT_CODE) {
Mod = LAlt;
}
else if (key.label === 'Ctrl' && key.keycode === LCTRL_CODE) {
Mod = LCtrl;
}
else if (key.label === 'Shift') {
Mod = RShift;
}
else if (key.label === 'AltGr') {
Mod = AltGr;
}
else if (key.label === 'Ctrl') {
Mod = RCtrl;
}
const label = Label({
class_name: `mod ${key.label}`,
label: key.label,
});
const button = EventBox({
class_name: 'key',
on_primary_click_release: () => {
console.log('mod toggled');
execAsync(`ydotool key ${key.keycode}:${Mod.value ? 0 : 1}`);
label.toggleClassName('active', !Mod.value);
Mod.setValue(!Mod.value);
},
setup: (self) => {
self
.hook(NormalClick, () => {
Mod.setValue(false);
label.toggleClassName('active', false);
execAsync(`ydotool key ${key.keycode}:0`);
})
// OnHover
.on('enter-notify-event', () => {
if (!display) {
return;
}
self.window.set_cursor(Gdk.Cursor.new_from_name(
display,
'pointer',
));
self.toggleClassName('hover', true);
})
// OnHoverLost
.on('leave-notify-event', () => {
self.window.set_cursor(null);
self.toggleClassName('hover', false);
});
},
child: label,
});
return Box({
children: [
button,
Separator(SPACING),
],
});
};
const RegularKey = (key: Key) => {
const widget = EventBox({
class_name: 'key',
child: Label({
class_name: `normal ${key.label}`,
label: key.label,
setup: (self) => {
self
.hook(Shift, () => {
if (!key.labelShift) {
return;
}
self.label = Shift.value ? key.labelShift : key.label;
})
.hook(Caps, () => {
if (key.label === 'Caps') {
self.toggleClassName('active', Caps.value);
return;
}
if (!key.labelShift) {
return;
}
if (key.label.match(/[A-Za-z]/)) {
self.label = Caps.value ?
key.labelShift :
key.label;
}
})
.hook(AltGr, () => {
if (!key.labelAltGr) {
return;
}
self.toggleClassName('altgr', AltGr.value);
self.label = AltGr.value ? key.labelAltGr : key.label;
})
// OnHover
.on('enter-notify-event', () => {
if (!display) {
return;
}
self.window.set_cursor(Gdk.Cursor.new_from_name(
display,
'pointer',
));
self.toggleClassName('hover', true);
})
// OnHoverLost
.on('leave-notify-event', () => {
self.window.set_cursor(null);
self.toggleClassName('hover', false);
});
},
}),
});
const gesture = Gtk.GestureLongPress.new(widget);
gesture.delay_factor = 1.0;
// Long press
widget.hook(gesture, () => {
const pointer = gesture.get_point(null);
const x = pointer[1];
const y = pointer[2];
if ((!x || !y) || (x === 0 && y === 0)) {
return;
}
console.log('Not implemented yet');
// TODO: popup menu for accents
}, 'pressed');
// OnPrimaryClickRelease
widget.hook(gesture, () => {
const pointer = gesture.get_point(null);
const x = pointer[1];
const y = pointer[2];
if ((!x || !y) || (x === 0 && y === 0)) {
return;
}
console.log('key clicked');
execAsync(`ydotool key ${key.keycode}:1`);
execAsync(`ydotool key ${key.keycode}:0`);
NormalClick.setValue(true);
}, 'cancelled');
return Box({
children: [
widget,
Separator(SPACING),
],
});
};
export default (key: Key) => key.keytype === 'normal' ?
RegularKey(key) :
ModKey(key);

View file

@ -1,32 +0,0 @@
const { Window } = Widget;
const { execAsync } = Utils;
import Tablet from '../../services/tablet.ts';
import Gesture from './gesture.ts';
import Keyboard from './keyboard.ts';
/* Types */
import { OskWindow } from 'global-types';
// Start ydotool daemon
execAsync('ydotoold').catch(print);
// Window
export default () => {
const window = Window({
name: 'osk',
layer: 'overlay',
anchor: ['left', 'bottom', 'right'],
})
.hook(Tablet, (self: OskWindow, state) => {
self.attribute.setVisible(state);
}, 'osk-toggled')
.hook(Tablet, () => {
window.visible = !(!Tablet.tabletMode && !Tablet.oskState);
}, 'mode-toggled');
window.child = Keyboard(window);
return Gesture(window);
};

View file

@ -1,62 +0,0 @@
const { Box, Icon, ProgressBar } = Widget;
const Y_POS = 80;
// Types
import { ConnectFunc, OSD, OSDStack } from 'global-types';
export default ({ name, icon, info }: OSD) => {
let connectFunc: ConnectFunc;
const status = info.widget ?
info.widget :
ProgressBar({ vpack: 'center' });
// Wrapper to get sliding up anim
const osd = Box({
name,
css: `margin-bottom: ${Y_POS}px;`,
children: [
// Actual OSD
Box({
class_name: 'osd',
children: [
Icon({
hpack: 'start',
icon,
}),
status,
],
}),
],
});
// Handle requests to show the OSD
// Different wether it's a bar or static
if (info.logic) {
connectFunc = (self) => new Promise<void>((r) => {
if (info.logic && self) {
info.logic(self);
}
r();
}).then(() => (osd.get_parent() as OSDStack)?.attribute?.popup(name))
.catch(console.error);
}
else {
connectFunc = () => (osd.get_parent() as OSDStack)
?.attribute?.popup(name);
}
if (info.signal) {
if (typeof info.signal === 'string') {
status.hook(info.mod, connectFunc, info.signal);
}
else {
info.signal.forEach((sig) => {
status.hook(info.mod, connectFunc, sig);
});
}
}
return osd;
};

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