Rework the bar

This commit is contained in:
2023-08-22 21:50:02 +02:00
parent 685de1c899
commit e56cb60d71
105 changed files with 585 additions and 3443 deletions

View File

@@ -44,7 +44,6 @@
user = "zoriya";
mkSystem = system: hostname: {
nixModules,
homeModules,
}: let
inputs = rawInput // {inherit user;};
@@ -55,7 +54,6 @@
./modules/misc
# ./modules/gnome
./modules/dwl
nixModules
nur.nixosModules.nur
{
nixpkgs.overlays = [
@@ -137,7 +135,6 @@
git.enable = true;
nvim.enable = true;
direnv.enable = true;
ntfy.enable = true;
};
};
};

View File

@@ -0,0 +1,13 @@
root = true
[*]
charset = utf-8
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = tab
indent_size = tab
[{*.yaml,*.yml}]
indent_style = space
indent_size = 2

View File

@@ -1,76 +0,0 @@
env:
es2021: true
extends: eslint:recommended
overrides: []
parserOptions:
ecmaVersion: latest
sourceType: 'module'
rules:
arrow-parens:
- error
- as-needed
comma-dangle:
- error
- always-multiline
comma-spacing:
- error
- before: false
after: true
comma-style:
- error
- last
curly:
- error
- multi-or-nest
- consistent
dot-location:
- error
- property
eol-last: error
indent:
- error
- 4
- SwitchCase: 1
keyword-spacing:
- error
- before: true
lines-between-class-members:
- error
- always
- exceptAfterSingleLine: true
padded-blocks:
- error
- never
- allowSingleLineBlocks: false
prefer-const: error
quotes:
- error
- single
- avoidEscape: true
semi:
- error
- always
nonblock-statement-body-position:
- error
- below
no-trailing-spaces:
- error
array-bracket-spacing:
- error
- never
key-spacing:
- error
- beforeColon: false
afterColon: true
object-curly-spacing:
- error
- always
no-useless-escape:
- off
globals:
ags: readonly
ARGV: readonly
imports: readonly
print: readonly
console: readonly
logError: readonly

View File

@@ -1,8 +1,4 @@
node_modules
package-lock.json
weather_key
*.css
settings.json
scss/generated.scss
scss/additional.scss
scss/.goutputstream-*

View File

@@ -1,14 +0,0 @@
extends: stylelint-config-standard-scss
ignoreFiles: "**/*.js"
rules:
selector-type-no-unknown: null
declaration-empty-line-before: null
no-descending-specificity: null
selector-pseudo-class-no-unknown: null
color-function-notation: legacy
alpha-value-notation: number
scss/operator-no-unspaced: null
scss/no-global-function-names: null
scss/dollar-variable-empty-line-before: null
scss/dollar-variable-pattern: ^[a-z]+(_[a-z]+)*$
scss/at-mixin-pattern: ^[a-z]+(_[a-z]+)*$

View File

@@ -1,5 +1,4 @@
import topbar from './layouts/topbar.js';
import * as shared from './layouts/shared.js';
import topbar from "./layouts/bar.js";
// TODO: (ags) dwl patch
// const monitors = ags.Service.Hyprland.HyprctlGet('monitors')
@@ -7,14 +6,15 @@ import * as shared from './layouts/shared.js';
const monitors = [0];
export default {
closeWindowDelay: {
'quicksettings': 300,
'dashboard': 300,
},
windows: [
...topbar(monitors),
// shared.ApplauncherPopup(),
shared.PowermenuPopup(),
// shared.VerificationPopup(),
],
closeWindowDelay: {
quicksettings: 300,
dashboard: 300,
},
style: ags.App.configDir + "/style.css",
windows: [
...topbar(monitors),
// shared.ApplauncherPopup(),
// shared.PowermenuPopup(),
// shared.VerificationPopup(),
],
};

View File

@@ -0,0 +1,74 @@
// import { Separator } from '../modules/misc.js';
// import { PanelIndicator as NotificationIndicator } from './widgets/notifications.js';
// import { PanelButton as ColorPicker } from '../modules/colorpicker.js';
// import { PanelButton as DashBoard } from './widgets/dashboard.js';
// import { PanelButton as ScreenRecord } from '../modules/screenrecord.js';
// import { PanelButton as QuickSettings } from './widgets/quicksettings.js';
import { Clock } from "../modules/clock.js";
import { BatteryIndicator } from "../modules/battery.js";
const { App } = ags;
const { Window, CenterBox, Box, Button } = ags.Widget;
const Bar = (monitor) =>
Window({
name: `bar${monitor}`,
className: "transparent",
exclusive: true,
anchor: "top left right",
monitor,
child: CenterBox({
startWidget: Box({
children: [
// Workspaces(),
// Separator({ valign: "center" }),
// Client(),
],
}),
endWidget: Box({
halign: "end",
children: [
// NotificationIndicator({
// direction: "right",
// hexpand: true,
// halign: "start",
// }),
// ags.Widget.Box({ hexpand: true }),
// ScreenRecord(),
// ColorPicker(),
// Separator({ valign: "center" }),
Button({
onClicked: () => App.toggleWindow("quicksettings"),
connections: [[App, (btn, win, visible) => {
btn.toggleClassName( "active", win === "quicksettings" && visible);
}]],
child: Box({
children: [
// audio.MicrophoneMuteIndicator({ unmuted: null }),
// notifications.DNDIndicator({ noisy: null }),
// BluetoothIndicator(),
// bluetooth.Indicator({ disabled: null }),
// network.Indicator(),
// audio.SpeakerIndicator(),
BatteryIndicator(),
],
}),
}),
Clock({ format: "%a %d %b", className: "module bold" }),
Clock({ format: "%H:%M", className: "module accent bold" }),
],
}),
}),
});
export default (monitors) =>
[
...monitors.map((mon) => [
Bar(mon),
// shared.Notifications(mon, 'slide_down', 'top'),
// shared.OSDIndicator(mon),
]),
// shared.Quicksettings({ position: 'top right' }),
// shared.Dashboard({ position: 'top' }),
].flat(2);

View File

@@ -1,39 +0,0 @@
import * as shared from './shared.js';
import { Separator } from '../modules/misc.js';
import { PanelIndicator as NotificationIndicator } from './widgets/notifications.js';
import { PanelButton as ColorPicker } from '../modules/colorpicker.js';
import { PanelButton as DashBoard } from './widgets/dashboard.js';
import { PanelButton as ScreenRecord } from '../modules/screenrecord.js';
import { PanelButton as QuickSettings } from './widgets/quicksettings.js';
const Bar = monitor => shared.Bar({
anchor: 'top left right',
monitor,
start: [
// Workspaces(),
Separator({ valign: 'center' }),
// Client(),
],
center: [
DashBoard(),
],
end: [
NotificationIndicator({ direction: 'right', hexpand: true, halign: 'start' }),
ags.Widget.Box({ hexpand: true }),
ScreenRecord(),
ColorPicker(),
Separator({ valign: 'center' }),
QuickSettings(),
Separator({ valign: 'center' }),
],
});
export default monitors => ([
...monitors.map(mon => [
Bar(mon),
shared.Notifications(mon, 'slide_down', 'top'),
shared.OSDIndicator(mon),
]),
shared.Quicksettings({ position: 'top right' }),
shared.Dashboard({ position: 'top' }),
]).flat(2);

View File

@@ -1,73 +1,117 @@
const { Battery } = ags.Service;
const { Label, Icon, Stack, ProgressBar, Overlay, Box } = ags.Widget;
const icons = charging => ([
...Array.from({ length: 9 }, (_, i) => i * 10).map(i => ([
`${i}`, Icon({
className: `${i} ${charging ? 'charging' : 'discharging'}`,
icon: `battery-level-${i}${charging ? '-charging' : ''}-symbolic`,
}),
])),
['100', Icon({
className: `100 ${charging ? 'charging' : 'discharging'}`,
icon: `battery-level-100${charging ? '-charged' : ''}-symbolic`,
})],
]);
const icons = (charging) =>
Array.from({ length: 10 }, (_, i) => i * 10).map((i) => [
`${i}`,
Icon({
className: `${i} ${charging ? "charging" : "discharging"}`,
icon: `battery-level-${i}${charging ? "-charging" : ""}-symbolic`,
}),
]);
const Indicators = charging => Stack({
items: icons(charging),
connections: [[Battery, stack => {
stack.shown = `${Math.floor(Battery.percent / 10) * 10}`;
}]],
});
const Indicators = (charging) =>
Stack({
items: icons(charging),
connections: [
[
Battery,
(stack) => {
stack.shown = `${Math.floor(Battery.percent / 10) * 10}`;
},
],
],
});
export const Indicator = ({
charging = Indicators(true),
discharging = Indicators(false),
...props
} = {}) => Stack({
...props,
className: 'battery',
items: [
['true', charging],
['false', discharging],
],
connections: [[Battery, stack => {
const { charging, charged } = Battery;
stack.shown = `${charging || charged}`;
stack.toggleClassName('charging', Battery.charging);
stack.toggleClassName('charged', Battery.charged);
stack.toggleClassName('low', Battery.percent < 30);
}]],
});
const BatteryIconIndicator = ({
charging = Indicators(true),
discharging = Indicators(false),
...props
} = {}) =>
Stack({
...props,
className: "battery",
items: [
["true", charging],
["false", discharging],
],
connections: [
[
Battery,
(stack) => {
const { charging, charged } = Battery;
log("battery:", charging, charged, Battery.available);
stack.shown = `${charging || charged}`;
stack.toggleClassName("charging", Battery.charging);
stack.toggleClassName("charged", Battery.charged);
stack.toggleClassName("low", Battery.percent < 30);
},
],
],
});
export const LevelLabel = props => Label({
...props,
connections: [[Battery, label => label.label = `${Battery.percent}%`]],
});
const LevelLabel = (props) =>
Label({
...props,
connections: [[Battery, (label) => (label.label = `${Battery.percent}%`)]],
});
export const BatteryProgress = props => Box({
...props,
className: 'battery-progress',
connections: [[Battery, w => {
w.toggleClassName('half', Battery.percent < 46);
w.toggleClassName('charging', Battery.charging);
w.toggleClassName('charged', Battery.charged);
w.toggleClassName('low', Battery.percent < 30);
}]],
children: [Overlay({
child: ProgressBar({
hexpand: true,
connections: [[Battery, progress => {
progress.fraction = Battery.percent / 100;
}]],
}),
overlays: [Label({
connections: [[Battery, l => {
l.label = Battery.charging || Battery.charged
? '󱐋'
: `${Battery.percent}%`;
}]],
})],
})],
});
export const BatteryIndicator = () =>
Box({
children: [BatteryIconIndicator(), LevelLabel()],
connections: [
[
Battery,
(box) => {
box.visible = Battery.available;
},
],
],
});
// export const BatteryProgress = (props) =>
// Box({
// ...props,
// className: "battery-progress",
// connections: [
// [
// Battery,
// (w) => {
// w.toggleClassName("half", Battery.percent < 46);
// w.toggleClassName("charging", Battery.charging);
// w.toggleClassName("charged", Battery.charged);
// w.toggleClassName("low", Battery.percent < 30);
// },
// ],
// ],
// children: [
// Overlay({
// child: ProgressBar({
// hexpand: true,
// connections: [
// [
// Battery,
// (progress) => {
// progress.fraction = Battery.percent / 100;
// },
// ],
// ],
// }),
// overlays: [
// Label({
// connections: [
// [
// Battery,
// (l) => {
// l.label =
// Battery.charging || Battery.charged
// ? "󱐋"
// : `${Battery.percent}%`;
// },
// ],
// ],
// }),
// ],
// }),
// ],
// });

View File

@@ -2,13 +2,16 @@ const { Label } = ags.Widget;
const { DateTime } = imports.gi.GLib;
export const Clock = ({
format = '%a %d %b %H:%M ',
interval = 1000,
...props
} = {}) => Label({
className: 'clock',
...props,
connections: [[interval, label =>
label.label = DateTime.new_now_local().format(format),
]],
});
format = "%a %d %b %H:%M ",
interval = 1000,
...props
} = {}) =>
Label({
...props,
connections: [
[
interval,
(label) => (label.label = DateTime.new_now_local().format(format)),
],
],
});

View File

@@ -0,0 +1,315 @@
window {
background-color: transparent;
}
//
// @mixin common{
// all: unset;
//
// * {
// font-size: $font_size;
// font-family: $font, sans-serif;
// }
// }
//
// @mixin widget{
// @include common;
// border-radius: $radii;
// color: $fg_color;
// background-color: $widget_bg;
// border: $border;
// }
//
// @mixin button_focus() {
// box-shadow: inset 0 0 0 $border_width $accent;
// background-color: $hover;
// color: $hover_fg;
// }
//
// @mixin button_hover() {
// box-shadow: inset 0 0 0 $border_width $border_color;
// background-color: $hover;
// color: $hover_fg;
// }
//
// @mixin button_active() {
// box-shadow: inset 0 0 0 $border_width $border_color;
// background-image: $active_gradient;
// background-color: $accent;
// color: $accent_fg;
// }
//
// @mixin button_disabled() {
// box-shadow: none;
// background-color: transparent;
// color: transparentize($fg_color, 0.7);
// }
//
// @mixin button($flat: false, $reactive: true, $radii: $radii, $focusable: true){
// @include common;
// transition: $transition;
// border-radius: $radii;
// color: $fg_color;
//
// @if $flat{
// background-color: transparent;
// background-image: none;
// box-shadow: none;
// } @else{
// background-color: $widget_bg;
// box-shadow: inset 0 0 0 $border_width $border_color;
// }
//
// @if $reactive{
// @if $focusable {
// &:focus{
// @include button_focus;
// }
// }
//
// &:hover{
// @include button_hover;
// }
//
// &:active, &.on, &.active, &:checked {
// @include button_active;
//
// &:hover {
// box-shadow: inset 0 0 0 $border_width $border_color,
// inset 0 0 0 99px $hover;
// }
// }
// }
//
// &:disabled {
// @include button_disabled;
// }
// }
//
// @mixin accs_button($flat: false, $reactive: true){
// @include button($flat: true, $reactive: false, $focusable: false);
// color: $fg_color;
//
// > * {
// border-radius: $radii;
// transition: $transition;
//
// @if $flat{
// background-color: transparent;
// box-shadow: none;
// } @else{
// background-color: $widget_bg;
// box-shadow: inset 0 0 0 $border_width $border_color;
// }
// }
//
//
// @if $reactive{
// &:focus > *, &.focused > * {
// @include button_focus;
// }
//
// &:hover > * {
// @include button_hover;
// }
//
// &:active, &.active, &.on, &:checked {
// > * {
// @include button_active;
// }
//
// &:hover > * {
// box-shadow: inset 0 0 0 $border_width $border_color,
// inset 0 0 0 99px $hover;
// }
// }
// }
// }
//
// @mixin floating_widget {
// @include common;
//
// @if $drop_shadow {
// box-shadow: 0 0 6px 0 $shadow;
// }
// margin: max($spacing, 8px);
// border: $border_width solid $popover_border_color;
// border-radius: $popover_radius;
// background-color: $bg_color;
// color: $fg_color;
// padding: $popover_padding;
// }
//
// @mixin slider($width: .7em, $slider_width: .5em, $gradient: $active_gradient, $slider: true, $focusable: true, $radii: $radii){
// @include common;
// * { all:unset; }
//
// trough{
// transition: $transition;
// border-radius: $radii;
// border: $border;
// background-color: $widget_bg;
// min-height: $width;
// min-width: $width;
//
// highlight, progress{
// border-radius: max($radii - $border_width, 0);
// background-image: $gradient;
// min-height: $width;
// min-width: $width;
// }
// }
//
// slider {
// box-shadow: none;
// background-color: transparent;
// border: $border_width solid transparent;
// transition: $transition;
// border-radius: $radii;
// min-height: $width;
// min-width: $width;
// margin: -$slider_width;
// }
//
// &:hover {
// trough {
// background-color: $hover;
// }
//
// slider {
// @if $slider{
// background-color: $fg_color;
// border-color: $border-color;
//
// @if $drop_shadow {
// box-shadow: 0 0 3px 0 $shadow;
// }
// }
// }
// }
//
// &:disabled {
// highlight, progress{
// background-color: transparentize($fg_color, 0.4);
// background-image: none;
// }
//
// slider {
// @if $slider {
// background-color: transparentize($fg_color, 0.5);
// }
// }
// }
//
// @if $focusable {
// trough:focus{
// background-color: $hover;
// box-shadow: inset 0 0 0 $border_width $accent;
//
// slider {
// @if $slider {
// background-color: $fg_color;
// box-shadow: inset 0 0 0 $border_width $accent;
// }
// }
// }
//
// }
// }
//
// @mixin shader($width: 3em){
// @include common;
//
// label {
// color: $shader_fg;
// text-shadow: $text_shadow;
// }
//
// @if $theme == 'dark' {
// box-shadow: inset 0 0 $width $width/3 transparentize($bg_color, 0.3);
// }
//
// @if $theme == 'light' {
// background-color: transparentize($bg_color, 0.8);
// }
// }
//
// @mixin text_border{
// text-shadow:
// -1 * $border_width -1 * $border_width 0 $border_color,
// $border_width $border_width 0 $border_color,
// -1 * $border_width $border_width 0 $border_color,
// $border_width -1 * $border_width 0 $border_color;
// }
//
// @mixin scrollbar{
// scrollbar, scrollbar * {
// all: unset;
// }
//
// scrollbar.vertical{
// slider{
// background: $widget_bg;
// border-radius: $radii;
// min-width: .6em;
// min-height: 2em;
// transition: $transition;
//
// &:hover {
// background-color: transparentize($fg_color, 0.6);
// min-width: .8em;
// }
// }
// }
// }
//
// @mixin switch{
// @include button;
//
// slider {
// background-color: $fg_color;
// border-radius: $radii;
// min-width: 24px;
// min-height: 24px;
// }
//
// image { color: transparent; }
// }
//
// tooltip {
// @include common;
// background-color: transparent;
// border: none;
//
// > * > *{
// background-color: $bg_color;
// border-radius: $radii;
// border: $border_width solid $popover_border_color;
// color: $fg_color;
// padding: 8px;
// margin: 4px;
// box-shadow: 0 0 3px 0 $shadow;
// }
// }
//
// window.popup {
// > * {
// border: none;
// box-shadow: none;
// }
//
// menu {
// border-radius: $popover_radius;
// background-color: $bg_color;
// padding: $spacing;
// border: $border;
//
// menuitem {
// @include button;
// padding: $spacing/2;
// margin: $spacing/2 0;
// > * { margin-left: -30px; }
// &:first-child { margin-top: 0; }
// &:last-child { margin-bottom: 0; }
// }
// }
// }

View File

@@ -0,0 +1,14 @@
// @import './generated';
@import './common';
// @import './widgets/notifications';
// @import './widgets/media';
// @import './widgets/datemenu';
// @import './widgets/applauncher';
// @import './widgets/quicksettings';
// @import './widgets/powermenu';
// @import './widgets/overview';
// @import './widgets/desktop';
// @import './widgets/dashboard';
// @import './widgets/bar';
// @import './widgets/settings';
// @import './additional';

View File

@@ -58,9 +58,6 @@ export async function setupScss(theme) {
try {
await writeFile(generated(scss(theme)), `${path}/scss/generated.scss`);
await writeFile(generated(theme.additional_scss || ''), `${path}/scss/additional.scss`);
await execAsync(['sassc', `${path}/scss/main.scss`, `${path}/style.css`]);
ags.App.resetCss();
ags.App.applyCss(`${path}/style.css`);
} catch (error) {
logError(error);
}

View File

@@ -16,7 +16,6 @@ class ThemeService extends Service {
constructor() {
super();
exec('swww init');
this.setup();
}
@@ -39,7 +38,6 @@ class ThemeService extends Service {
};
setupScss(theme);
this.setupOther();
this.setupWallpaper();
}
reset() {
@@ -59,15 +57,6 @@ class ThemeService extends Service {
// execAsync(`cp ${wezterm}/charm${darkmode ? '' : '-light'}.lua ${wezterm}/theme.lua`).catch(print);
}
setupWallpaper() {
execAsync([
'swww', 'img',
'--transition-type', 'grow',
// '--transition-pos', exec('hyprctl cursorpos').replace(' ', ''),
this.getSetting('wallpaper'),
]).catch(print);
}
get settings() {
if (this._settings)
return this._settings;

View File

@@ -1,25 +0,0 @@
{
"name": "ags-dotfiles",
"version": "1.0.0",
"description": "My config files for AGS",
"main": "config.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"lint": "eslint . --fix",
"stylelint": "stylelint"
},
"repository": {
"type": "git",
"url": "git+https://github.com/Aylur/dotfiles.git"
},
"author": "Aylur",
"license": "GPL-3.0-or-later",
"bugs": {
"url": "https://github.com/Aylur/dotfiles/issues"
},
"homepage": "https://github.com/Aylur/dotfiles#readme",
"devDependencies": {
"eslint": "^8.44.0",
"stylelint-config-standard-scss": "^10.0.0"
}
}

View File

@@ -1,315 +0,0 @@
window {
background-color: transparent;
}
@mixin common{
all: unset;
* {
font-size: $font_size;
font-family: $font, sans-serif;
}
}
@mixin widget{
@include common;
border-radius: $radii;
color: $fg_color;
background-color: $widget_bg;
border: $border;
}
@mixin button_focus() {
box-shadow: inset 0 0 0 $border_width $accent;
background-color: $hover;
color: $hover_fg;
}
@mixin button_hover() {
box-shadow: inset 0 0 0 $border_width $border_color;
background-color: $hover;
color: $hover_fg;
}
@mixin button_active() {
box-shadow: inset 0 0 0 $border_width $border_color;
background-image: $active_gradient;
background-color: $accent;
color: $accent_fg;
}
@mixin button_disabled() {
box-shadow: none;
background-color: transparent;
color: transparentize($fg_color, 0.7);
}
@mixin button($flat: false, $reactive: true, $radii: $radii, $focusable: true){
@include common;
transition: $transition;
border-radius: $radii;
color: $fg_color;
@if $flat{
background-color: transparent;
background-image: none;
box-shadow: none;
} @else{
background-color: $widget_bg;
box-shadow: inset 0 0 0 $border_width $border_color;
}
@if $reactive{
@if $focusable {
&:focus{
@include button_focus;
}
}
&:hover{
@include button_hover;
}
&:active, &.on, &.active, &:checked {
@include button_active;
&:hover {
box-shadow: inset 0 0 0 $border_width $border_color,
inset 0 0 0 99px $hover;
}
}
}
&:disabled {
@include button_disabled;
}
}
@mixin accs_button($flat: false, $reactive: true){
@include button($flat: true, $reactive: false, $focusable: false);
color: $fg_color;
> * {
border-radius: $radii;
transition: $transition;
@if $flat{
background-color: transparent;
box-shadow: none;
} @else{
background-color: $widget_bg;
box-shadow: inset 0 0 0 $border_width $border_color;
}
}
@if $reactive{
&:focus > *, &.focused > * {
@include button_focus;
}
&:hover > * {
@include button_hover;
}
&:active, &.active, &.on, &:checked {
> * {
@include button_active;
}
&:hover > * {
box-shadow: inset 0 0 0 $border_width $border_color,
inset 0 0 0 99px $hover;
}
}
}
}
@mixin floating_widget {
@include common;
@if $drop_shadow {
box-shadow: 0 0 6px 0 $shadow;
}
margin: max($spacing, 8px);
border: $border_width solid $popover_border_color;
border-radius: $popover_radius;
background-color: $bg_color;
color: $fg_color;
padding: $popover_padding;
}
@mixin slider($width: .7em, $slider_width: .5em, $gradient: $active_gradient, $slider: true, $focusable: true, $radii: $radii){
@include common;
* { all:unset; }
trough{
transition: $transition;
border-radius: $radii;
border: $border;
background-color: $widget_bg;
min-height: $width;
min-width: $width;
highlight, progress{
border-radius: max($radii - $border_width, 0);
background-image: $gradient;
min-height: $width;
min-width: $width;
}
}
slider {
box-shadow: none;
background-color: transparent;
border: $border_width solid transparent;
transition: $transition;
border-radius: $radii;
min-height: $width;
min-width: $width;
margin: -$slider_width;
}
&:hover {
trough {
background-color: $hover;
}
slider {
@if $slider{
background-color: $fg_color;
border-color: $border-color;
@if $drop_shadow {
box-shadow: 0 0 3px 0 $shadow;
}
}
}
}
&:disabled {
highlight, progress{
background-color: transparentize($fg_color, 0.4);
background-image: none;
}
slider {
@if $slider {
background-color: transparentize($fg_color, 0.5);
}
}
}
@if $focusable {
trough:focus{
background-color: $hover;
box-shadow: inset 0 0 0 $border_width $accent;
slider {
@if $slider {
background-color: $fg_color;
box-shadow: inset 0 0 0 $border_width $accent;
}
}
}
}
}
@mixin shader($width: 3em){
@include common;
label {
color: $shader_fg;
text-shadow: $text_shadow;
}
@if $theme == 'dark' {
box-shadow: inset 0 0 $width $width/3 transparentize($bg_color, 0.3);
}
@if $theme == 'light' {
background-color: transparentize($bg_color, 0.8);
}
}
@mixin text_border{
text-shadow:
-1 * $border_width -1 * $border_width 0 $border_color,
$border_width $border_width 0 $border_color,
-1 * $border_width $border_width 0 $border_color,
$border_width -1 * $border_width 0 $border_color;
}
@mixin scrollbar{
scrollbar, scrollbar * {
all: unset;
}
scrollbar.vertical{
slider{
background: $widget_bg;
border-radius: $radii;
min-width: .6em;
min-height: 2em;
transition: $transition;
&:hover {
background-color: transparentize($fg_color, 0.6);
min-width: .8em;
}
}
}
}
@mixin switch{
@include button;
slider {
background-color: $fg_color;
border-radius: $radii;
min-width: 24px;
min-height: 24px;
}
image { color: transparent; }
}
tooltip {
@include common;
background-color: transparent;
border: none;
> * > *{
background-color: $bg_color;
border-radius: $radii;
border: $border_width solid $popover_border_color;
color: $fg_color;
padding: 8px;
margin: 4px;
box-shadow: 0 0 3px 0 $shadow;
}
}
window.popup {
> * {
border: none;
box-shadow: none;
}
menu {
border-radius: $popover_radius;
background-color: $bg_color;
padding: $spacing;
border: $border;
menuitem {
@include button;
padding: $spacing/2;
margin: $spacing/2 0;
> * { margin-left: -30px; }
&:first-child { margin-top: 0; }
&:last-child { margin-bottom: 0; }
}
}
}

View File

@@ -1,14 +0,0 @@
@import './generated';
@import './common';
@import './widgets/notifications';
@import './widgets/media';
@import './widgets/datemenu';
@import './widgets/applauncher';
@import './widgets/quicksettings';
@import './widgets/powermenu';
@import './widgets/overview';
@import './widgets/desktop';
@import './widgets/dashboard';
@import './widgets/bar';
@import './widgets/settings';
@import './additional';

31
modules/dwl/ags/style.css Normal file
View File

@@ -0,0 +1,31 @@
* {
all: unset;
font-family: monospace;
padding: 0;
margin: 0;
}
window {
background-color: rgba(0, 0, 0, 0.6);
}
.module {
padding: 0 10px;
}
.bold {
font-weight: bold;
}
.accent {
background-color: #94e2d5;
color: #1e1e2e;
}
.battery.low {
color: red;
}
.battery.charged, .battery.charging {
color: green;
}

View File

@@ -31,6 +31,8 @@
extraPortals = [ pkgs.xdg-desktop-portal-gtk ];
};
services.upower.enable = true;
# i18n.inputMethod.enabled = "ibus";
# i18n.inputMethod.ibus.engines = with pkgs.ibus-engines; [mozc];
}

View File

@@ -12,6 +12,7 @@ in {
sassc
brightnessctl
pavucontrol
wbg
wallpaper
];

View File

@@ -1,7 +1,6 @@
{
imports = [
./hyprland
./eww
./rofi
./apps
./zsh
@@ -10,7 +9,6 @@
./direnv
./fcitx5
./colors
./ntfy
];
home.stateVersion = "22.11";

View File

@@ -1,10 +0,0 @@
(defpoll icon :interval "1s" "./bar/battery/get-battery.sh icon")
(defpoll percent :interval "1s" "./bar/battery/get-battery.sh percent")
(defwidget battery []
(box :class "battery module floating ${percent < 18 ? "red" : ""}"
:spacing 6
(label :valign "center" :class "icon" :text "${icon}")
(label :valign "center" :class "percent" :text "${percent}%"))
)

View File

@@ -1,41 +0,0 @@
#!/usr/bin/env bash
bat=/sys/class/power_supply/BAT0/
per="$(cat "$bat/capacity")"
icon() {
[ $(cat "$bat/status") = Charging ] && echo "" && exit
if [ "$per" -gt "90" ]; then
icon=""
elif [ "$per" -gt "80" ]; then
icon=""
elif [ "$per" -gt "70" ]; then
icon=""
elif [ "$per" -gt "60" ]; then
icon=""
elif [ "$per" -gt "50" ]; then
icon=""
elif [ "$per" -gt "40" ]; then
icon=""
elif [ "$per" -gt "30" ]; then
icon=""
elif [ "$per" -gt "20" ]; then
icon=""
elif [ "$per" -gt "10" ]; then
icon=""
elif [ "$per" -gt "0" ]; then
icon=""
else
icon=""
fi
echo "$icon"
}
percent() {
echo $per
}
[ "$1" = "icon" ] && icon && exit
[ "$1" = "percent" ] && percent

View File

@@ -1,13 +0,0 @@
(defpoll time :initial '{}' :interval "5s" `date +'{"date": "%a %d %b", "hour": "%H", "minute": "%M", "day": "%A"}'`)
(defwidget clock []
(box
:class "module accent bold"
(label
:text "${time.hour}:${time.minute}"
:class "hour")))
(defwidget date []
(box
:class "module bold"
(label :text "${time.date}")))

View File

@@ -1,43 +0,0 @@
.bar {
background-color: $base00;
color: $base05;
}
.module {
padding: 0 10px;
}
.module-margin {
margin: 0 10px;
}
.bold {
font-weight: bold;
}
.floating {
background: $base01;
border-radius: 10px;
margin: 5px;
}
.round {
border-radius: 50%;
margin: 5px;
}
.px-2 {
padding: 8px 6px;
}
.battery {
.icon {
font-size: 20px;
}
.percent {
margin-left: -5px;
font-size: 16px;
font-weight: 500;
}
}

View File

@@ -1,43 +0,0 @@
(include "./bar/workspaces/eww.yuck")
(include "./bar/layout/eww.yuck")
(include "./bar/window/eww.yuck")
(include "./bar/battery/eww.yuck")
(include "./bar/language/eww.yuck")
(include "./bar/clock.yuck")
(defwidget left []
(box
:space-evenly false
:halign "start"
(workspaces)
(layout)
(window)))
(defwidget right []
(box
:space-evenly false
:halign "end"
;; (notification)
(battery)
(language)
(date)
(clock)
))
(defwidget bar []
(box
:class "bar"
(left)
(right)))
(defwindow bar
:monitor 0
:geometry (geometry
:x "0%"
:y "0%"
:width "100%"
:height "36px"
:anchor "top center")
:stacking "fg"
:exclusive true
(bar))

View File

@@ -1,8 +0,0 @@
(defpoll lang :interval "1s" :initial "en" "./bar/language/language.sh")
(defwidget language []
(box :class "module floating"
:spacing 6
(label :valign "center" :class "icon" :text "")
(label :valign "center" :class "percent" :text "${lang}")))

View File

@@ -1,60 +0,0 @@
#!/usr/bin/env bash
IMLIST_FILE="/tmp/fcitx5-imlist"
# Print out identifier of current input method
current() {
dbus-send --session --print-reply \
--dest=org.fcitx.Fcitx5 \
/controller \
org.fcitx.Fcitx.Controller1.CurrentInputMethod \
| grep -Po '(?<=")[^"]+' \
|| echo "en"
}
# List all input methods added to Fcitx
imlist() {
if [ ! -f "${IMLIST_FILE}" ]; then
dbus-send --session --print-reply --dest=org.fcitx.Fcitx5 /controller org.fcitx.Fcitx.Controller1.AvailableInputMethods \
| awk 'BEGIN{i=0}{
if($0~/struct {/) i=0;
else if(i<6){gsub(/"/,"",$2); printf("%s,",$2); i++}
else if(i==6){printf("%s\n",$2); i++}
}' > ${IMLIST_FILE}
# Output like this:
# pinyin, 拼音, 拼音, fcitx-pinyin, 拼, zh_CN, true
# rime, 中州韻, , fcitx-rime, ㄓ, zh, true
# ......
fi
cat ${IMLIST_FILE}
}
# This script wait for events from `watch` and
# update the text by printing a new line.
#
# Strip `Keyboard - ` part from IM name then print
print_pretty_name() {
name=$(imlist | grep "^$(current)," | cut -d',' -f5)
if [[ -z "$name" ]]; then
return
fi
echo "${name}"
}
react() {
print_pretty_name
# Track input method changes. Each new line read is an event fired from IM switch
while true; do
# When read someting from dbus-monitor
read -r unused
print_pretty_name
done
}
# Watch for events from Fcitx.
# Need --line-buffered to avoid messages being hold in buffer
# dbus-monitor --session destination=org.freedesktop.IBus | grep --line-buffered '65505\|65509' | react
# Dbus watching does not seems to work so /shrug
print_pretty_name

View File

@@ -1,7 +0,0 @@
(deflisten layout :initial "..." "./bar/layout/get-layout.sh")
(defwidget layout []
(box
;:class "round module-margin accent px-2"
:class "secondary-txt module"
(label :text "${layout}")))

View File

@@ -1,15 +0,0 @@
#!/usr/bin/env bash
layout() {
WORKSPACE=$(hyprctl monitors -j | jq '.[0].activeWorkspace.id')
IS_FULLSCREEN=$(hyprctl workspaces -j | jq ".[] | select(.id == $WORKSPACE).hasfullscreen")
[ $IS_FULLSCREEN = "true" ] \
&& echo "[$(hyprctl workspaces -j | jq ".[] | select(.id == $WORKSPACE).windows")]" \
|| echo "[]="
}
layout
socat -u UNIX-CONNECT:/tmp/hypr/$HYPRLAND_INSTANCE_SIGNATURE/.socket2.sock - | while read -r line; do
layout
done

View File

@@ -1,6 +0,0 @@
(deflisten title :initial "" "./bar/window/get-window-title.sh")
(defwidget window []
(box
:class {(title != "" && title != 'null') ? "floating module" : ""}
(label :text {title != 'null' ? title : ""})))

View File

@@ -1,7 +0,0 @@
#!/usr/bin/env bash
MAXCHAR=40
hyprctl activewindow -j | jq --raw-output .title | cut -c -$MAXCHAR
socat -u UNIX-CONNECT:/tmp/hypr/$HYPRLAND_INSTANCE_SIGNATURE/.socket2.sock - | grep --line-buffered '^activewindow>>' | stdbuf -o0 awk -F '>>|,' '{printf "%.40s\n", $3}'

View File

@@ -1 +0,0 @@
//@include "colors";

View File

@@ -1,17 +0,0 @@
(defvar ws-icons '["一", "二", "三", "四", "五", "六", "七", "八", "九", "十"]')
(deflisten curr :initial "1" "./bar/workspaces/get-active-workspace.sh")
(deflisten wps :initial "[1, 2, 3]" "./bar/workspaces/get-workspaces.sh")
(defwidget workspaces []
(box
:space-evenly true
(for workspace in wps
(eventbox :onclick "hyprctl dispatch workspace ${workspace}"
(box
:width 36
:height 36
:class "${workspace == curr ? "accent" : ""}"
(label
:style "font-size: 1.3rem;"
:text "${ws-icons[workspace - 1]}"
))))))

View File

@@ -1,5 +0,0 @@
#!/usr/bin/env bash
hyprctl monitors -j | jq --raw-output .[0].activeWorkspace.id
socat -u UNIX-CONNECT:/tmp/hypr/$HYPRLAND_INSTANCE_SIGNATURE/.socket2.sock - | stdbuf -o0 grep '^workspace>>' | stdbuf -o0 awk -F '>>|,' '{print $2}'

View File

@@ -1,10 +0,0 @@
#!/usr/bin/env bash
spaces (){
hyprctl workspaces -j | jq -c 'map(.id | select(. > 0)) | sort'
}
spaces
socat -u UNIX-CONNECT:/tmp/hypr/$HYPRLAND_INSTANCE_SIGNATURE/.socket2.sock - | while read -r line; do
spaces
done

View File

@@ -1,62 +0,0 @@
{
lib,
config,
pkgs,
...
}:
with lib; let
cfg = config.modules.eww;
in {
options.modules.eww = {enable = mkEnableOption "eww";};
config = mkIf cfg.enable {
home.packages = with pkgs; [
pamixer
brightnessctl
material-icons
blueberry
bluez
gnunet
jaq
networkmanagerapplet
pavucontrol
playerctl
procps
socat
udev
upower
util-linux
wget
wireplumber
wlogout
bc
jq
fusuma
eww-wayland
];
xdg.configFile."eww" = {
source = ./.;
recursive = true;
};
xdg.configFile."eww/_colors.scss".text = with config.colorScheme.colors; ''
$base00: #${base00};
$base01: #${base01};
$base02: #${base02};
$base03: #${base03};
$base04: #${base04};
$base05: #${base05};
$base06: #${base06};
$base07: #${base07};
$base08: #${base08};
$base09: #${base09};
$base0A: #${base0A};
$base0B: #${base0B};
$base0C: #${base0C};
$base0D: #${base0D};
$base0E: #${base0E};
$base0F: #${base0F};
'';
xdg.configFile."fusuma/config.yaml".source = ./fusuma.yaml;
};
}

View File

@@ -1,49 +0,0 @@
@import 'colors';
@import "bar/eww";
@import "panel/scss/_init.scss";
* {
all: unset;
font-family: "monospace";
}
.accent {
background-color: $base0C;
color: $base00;
}
.accent-txt {
color: $base0C;
}
.secondary {
background-color: $base0E;
color: $base00;
}
.secondary-txt {
color: $base0E;
}
.red {
background-color: $base08;
color: $base02;
}
.red-txt {
color: $base0E;
}
.green {
background-color: $base0B;
}
.green-txt {
color: $base0B;
}
.blue {
background-color: $base0D;
}
.blue-txt {
color: $base0D;
}

View File

@@ -1,2 +0,0 @@
(include "./bar/eww.yuck")
(include "./panel/yuck/_init.yuck")

View File

@@ -1,15 +0,0 @@
swipe:
3:
down:
command: "hyprctl dispatch fullscreen 1"
up:
command: "hyprctl dispatch fullscreen 1"
4:
left:
command: "hyprctl dispatch cyclenext prev"
right:
command: "hyprctl dispatch cyclenext"
up:
command: bash -c "eww open panel-closer && eww open panel"
down:
command: bash -c "eww close panel-closer && eww close panel"

View File

@@ -1,87 +0,0 @@
#!/usr/bin/env bash
XDG_CACHE_HOME="${XDG_CACHE_HOME:-$HOME/.cache/}"
get_status() {
s=$1
if [ "$s" = "Playing" ]; then
echo "󰏦"
else
echo "󰐍"
fi
}
get_length_time() {
len=$1
if [ -n "$len" ]; then
len=$(bc <<< "$len / 1000000 + 1")
date -d@"$len" +%M:%S
else
echo ""
fi
}
get_position() {
pos=$1
len=$2
if [[ -n "$pos" && -n $len ]]; then
bc -l <<< "$pos / $len * 100"
else
echo 0
fi
}
get_position_time() {
pos=$1
len=$2
if [ -n "$pos" ]; then
date -d@"$(bc <<< "$pos / 1000000")" +%M:%S
else
echo ""
fi
}
get_cover() {
# COVER_URL=$1
mkdir -p "$XDG_CACHE_HOME/eww_covers"
cd "$XDG_CACHE_HOME/eww_covers" || exit
IMGPATH="$XDG_CACHE_HOME/eww_covers/cover_art.png"
playerctl -F metadata mpris:artUrl 2>/dev/null | while read -r COVER_URL; do
if [[ "$COVER_URL" = https* ]]; then
if [ ! -e "$XDG_CACHE_HOME/eww_covers/$(basename "$COVER_URL")" ]; then
wget -N "$COVER_URL" -o /dev/null
fi
rm "$IMGPATH"
ln -s "$(basename "$COVER_URL")" "$IMGPATH"
echo "$IMGPATH"
elif [ "$COVER_URL" = "" ]; then
echo ""
else
echo "$COVER_URL"
fi
done
}
sanitize() {
echo "$1" | sed 's/"/\"/g'
}
if [ "$1" = "cover" ]; then
get_cover
else
playerctl -F metadata -f '{{title}}\{{artist}}\{{status}}\{{position}}\{{mpris:length}}\' 2>/dev/null | while IFS="$(printf '\\')" read -r title artist status position len; do
jaq --null-input \
-r -c \
--arg artist "$(sanitize "$artist")" \
--arg title "$(sanitize "$title")" \
--arg status "$(get_status "$status")" \
--arg pos "$(get_position "$position" "$len")" \
--arg pos_time "$(get_position_time "$position" "$len")" \
--arg length "$(get_length_time "$len")" \
'{"artist": $artist, "title": $title, "status": $status, "position": $pos, "position_time": $pos_time, "length": $length}'
done
fi

View File

@@ -1,87 +0,0 @@
.disclose-cardimage-eventbox {
background: linear-gradient(45deg, $base02 20%, $base01 100%);
border-radius: 0.5em;
}
.disclose-cardimage-eventbox:hover {
background: linear-gradient(170deg, $base01 20%, $shade01 100%);
}
.disclose-cardimage-icon {
margin: 0 0 0 -0.21em;
background-size: cover;
background-repeat: no-repeat;
border-radius: 50%;
}
.disclose-cardimage-image-box {
padding: 0 1em 0 1em;
}
.disclose-cardimage-image {
background-size: cover;
background-repeat: no-repeat;
box-shadow: -0.4px -0.3px 4px 4px $shade08;
border-radius: 50%;
border: 0.25em solid $base09;
}
.disclose-cardimage-separator {
background-color: $shade02;
font-size: 1;
padding: 3.4em 0 0 0;
margin: 0 10em 0 10em;
}
.disclose-cardimage-body-box {
font-size: 15;
padding: 1em 0 1em 0;
}
.disclose-cardimage-summary-box {
padding: 0.5em 0 0.5em 0.9em;
background-color: darken($base09, 10%);
border-radius: 0.3em 0.3em 0 0;
color: $base02;
}
.disclose-cardimage-appname-label {
font-size: 16;
font-weight: bold;
}
.disclose-cardimage-close-button {
margin: 0.6em 1em 0.6em 0;
padding: 0.2em;
background-color: $base11;
color: $base01;
border-radius: 50%;
box-shadow: -0.4px -0.3px 4px -1.8px $shade09;
}
.disclose-cardimage-close-button:hover {
background-color: $base01;
color: $base11;
}
.disclose-cardimage-body-outer {
padding: 0 1em 0 0;
}
.disclose-cardimage-summary-label {
font-size: 20;
font-weight: bold;
color: $base05;
}
.disclose-cardimage-body-label {
font-size: 16;
color: $base05;
}
.disclose-cardimage-timestamp {
color: $base10;
font-weight: bold;
}
// vim:ft=scss

View File

@@ -1,121 +0,0 @@
.disclose-cardprog-eventbox {
background: linear-gradient(45deg, $base02 20%, $base01 100%);
border-radius: 0.5em;
}
.disclose-cardprog-eventbox:hover {
background: linear-gradient(170deg, $base01 20%, $shade01 100%);
}
.disclose-cardprog-icon {
margin: 0 0 0 -0.21em;
background-size: cover;
background-repeat: no-repeat;
}
.disclose-cardprog-image-box {
padding: 0 1em 0 1em;
}
.disclose-cardprog-image {
background-size: cover;
background-repeat: no-repeat;
box-shadow: -0.4px -0.3px 4px 4px $shade08;
border-radius: 50%;
border: 0.25em solid $base09;
}
.disclose-cardprog-separator {
background-color: $shade02;
font-size: 1;
padding: 3.4em 0 0 0;
margin: 0 10em 0 10em;
}
.disclose-cardprog-body-box {
font-size: 15;
padding: 1em 0 1em 0;
}
.disclose-cardprog-summary-box {
padding: 0.5em 0 0.5em 0.9em;
background-color: darken($base09, 10%);
border-radius: 0.3em 0.3em 0 0;
color: $base02;
}
.disclose-cardprog-appname-label {
font-size: 16;
font-weight: bold;
}
.disclose-cardprog-close-button {
margin: 0.6em 1em 0.6em 0;
padding: 0.2em;
background-color: $base11;
color: $base01;
border-radius: 50%;
box-shadow: -0.4px -0.3px 4px -1.8px $shade09;
}
.disclose-cardprog-close-button:hover {
background-color: $base01;
color: $base11;
}
.disclose-cardprog-body-outer {
padding: 0 1em 0 0;
}
.disclose-cardprog-summary-label {
font-size: 20;
font-weight: bold;
color: $base05;
}
.disclose-cardprog-body-label {
font-size: 16;
color: $base05;
}
.disclose-cardprog-timestamp {
color: $base10;
font-weight: bold;
}
.disclose-cardprog-scale {
background-color: $shade01;
border-radius: 0.2em;
margin: 0.5em 0 0.5em;
contents {
trough {
border-radius: 0.2em;
// Uncomment to enable slider pointer
// slider {
// margin: -8px;
// background-color: $base15;
// box-shadow: -0.4px -0.3px 4px -1.8px $shade09;
// border-radius: 20%;
// }
highlight {
background-color: $base09;
padding: 0.7em;
border-radius: 0.2em;
}
}
}
}
.disclose-cardprog-value-label {
padding: 0 0.5em 0 0.5em;
margin: 0.2em 0 0.2em 0;
color: $base02;
font-weight: 700;
background-color: $base10;
border-radius: 10%;
}
// vim:ft=scss

View File

@@ -1,84 +0,0 @@
.disclose-cardradial-eventbox {
background: linear-gradient(45deg, $base02 20%, $base01 100%);
border-radius: 0.5em;
}
.disclose-cardradial-eventbox:hover {
background: linear-gradient(170deg, $base01 20%, $shade01 100%);
}
.disclose-cardradial-separator {
background-color: $shade02;
font-size: 1;
padding: 3.4em 0 0 0;
margin: 0 10em 0 10em;
}
.disclose-cardradial-body-box {
font-size: 15;
padding: 1em 0 1em 0;
}
.disclose-cardradial-summary-box {
padding: 0.5em 0 0.5em 0.9em;
background-color: darken($base09, 10%);
border-radius: 0.3em 0.3em 0 0;
color: $base02;
}
.disclose-cardradial-appname-label {
font-size: 16;
font-weight: bold;
}
.disclose-cardradial-close-button {
margin: 0.6em 1em 0.6em 0;
padding: 0.2em;
background-color: $base11;
color: $base01;
border-radius: 50%;
box-shadow: -0.4px -0.3px 4px -1.8px $shade09;
}
.disclose-cardradial-close-button:hover {
background-color: $base01;
color: $base11;
}
.disclose-cardradial-body-outer {
padding: 0 1em 0 0;
}
.disclose-cardradial-summary-label {
font-size: 20;
font-weight: bold;
color: $base05;
}
.disclose-cardradial-body-label {
font-size: 16;
color: $base05;
}
.disclose-cardradial-timestamp {
color: $base10;
font-weight: bold;
}
.disclose-cardradial-circle-box {
padding: 0 1em 0 1em;
}
.disclose-cardradial-circle {
font-size: 25;
color: $base09;
background-color: $shade01;
}
.disclose-cardradial-tasks {
padding: 1.4em;
font-weight: bolder;
color: $base10;
}
// vim:ft=scss

View File

@@ -1,86 +0,0 @@
.disclose-cardscr-eventbox {
background: linear-gradient(45deg, $base02 20%, $base01 100%);
border-radius: 0.5em;
}
.disclose-cardscr-eventbox:hover {
background: linear-gradient(170deg, $shade01 20%, $base01 100%);
}
.disclose-cardscr-summary-box {
padding: 0.5em 0 0 1em;
border-radius: 0.3em 0.3em 0 0;
color: $base02;
}
.disclose-cardscr-close-button {
margin: 0.6em 1em 0.6em 0;
padding: 0.2em;
background-color: $base11;
color: $base01;
border-radius: 50%;
box-shadow: -0.4px -0.3px 4px -1.8px $shade09;
}
.disclose-cardscr-close-button:hover {
background-color: $base01;
color: $base11;
}
.disclose-cardscr-summary-label {
font-size: 20;
font-weight: bold;
color: $base05;
}
.disclose-cardscr-body-label {
font-size: 16;
color: $base05;
}
.disclose-cardscr-timestamp {
color: $base10;
font-weight: bold;
}
.disclose-cardscr-image {
margin: 1em;
border-radius: 0.5em;
background-size: cover;
background-repeat: no-repeat;
}
.disclose-cardscr-timestamp {
padding-right: 1em;
}
.disclose-cardscr-open-button,
.disclose-cardscr-delete-button {
font-size: 20;
font-weight: bold;
padding: 0.6em 3.72em 0.6em 3.72em;
margin-bottom: 0.7em;
border-radius: 0.2em;
color: $base02;
}
.disclose-cardscr-open-button {
background-color: darken($base09, 10%);
}
.disclose-cardscr-open-button:hover {
color: $base09;
background-color: $shade02;
}
.disclose-cardscr-delete-button {
background-color: $shade02;
color: $base11;
}
.disclose-cardscr-delete-button:hover {
background-color: $base11;
color: $shade02;
}
// vim:ft=scss

View File

@@ -1,21 +0,0 @@
.disclose-empty-banner {
padding-top: 8em;
}
.disclose-empty-box {
padding: 1em 1em 10em 0;
}
.disclose-empty-label {
padding-top: 3em;
color: $shade00;
font-size: 16;
}
.disclose-separator {
font-size: 0.5;
background-color: $base03;
margin: 35em 25em 35em 25em;
}
// vim:ft=scss

View File

@@ -1,65 +0,0 @@
$base10: $base0A;
$base11: $base0B;
$base12: $base0C;
$base13: $base0D;
$base14: $base0E;
$base15: $base0F;
$shade00: #3d464e;
$shade01: #2b3238;
$shade02: #24292e;
$shade03: #1f2429;
$shade04: #1c2126;
$shade05: #191e23;
$shade06: #161b20;
$shade07: #151a1f;
$shade08: #13181d;
$shade09: #11161b;
$shade10: #0e1115;
$shade11: #0c0f13;
$shade12: #0a0d11;
$normal: darken(#7ab0df, 50%);
$critical: darken(#f87070, 50%);
$low: darken(#79dcaa, 50%);
$other: darken(#ffe59e, 50%);
.disclose-closer {
padding-top: 36px;
background-color: transparent;
}
.disclose-layout-box {
background-color: $base00;
font-family: monospace;
padding-bottom: 1em;
}
.disclose-headers {
padding: 1em 2em 0 2em;
margin-bottom: 1.5em;
color: $base05;
}
.disclose-headers-label {
font-size: 20;
color: $base05;
opacity: 0.8;
}
.disclose-scroll {
margin: 0 1.5em 0 1.5em;
}
@import "./_empty.scss";
@import "./_cardprog.scss";
@import "./_cardscr.scss";
@import "./_cardimage.scss";
@import "./_cardradial.scss";
@import "./_urgency.scss";
@import "./_stats.scss";
@import "./_music.scss";
@import "./_modifiers.scss";
@import "./_override.scss";
// vim:ft=scss

View File

@@ -1,16 +0,0 @@
.shot-image-bord-dashed {
border-style: dashed;
}
.Spotify-rectangle {
margin: 0.3em;
border-radius: 0.2em;
border-width: 0
}
.Qbittorrent-rectangle {
border-radius: 0;
border-width: 0;
}
// vim:ft=scss

View File

@@ -1,49 +0,0 @@
.disclose-music-box {
background-image: linear-gradient(to bottom left, rgba(0, 0, 0, 1.0), rgba(30, 33, 40, 0.5));
background-size: cover;
background-repeat: no-repeat;
margin-right: 1em;
border-radius: 1.5em;
.play-status {
padding: 4px;
font-size: 25;
}
.volume-container {
background-color: $base03;
border-radius: 2em;
opacity: 0.8;
padding-right: 8px;
.volume-icon {
margin: 0.3em;
padding: 0.2em;
border-radius: 1em;
font-size: 20;
}
.volume-text {
font-weight: 600;
}
}
}
.disclose-dnd-header {
font-size: 20;
font-weight: bold;
color: $base05;
}
.disclose-dnd-footer {
font-size: 15;
color: darken($base06, 10%);
}
.disclose-dnd-labels {
padding: 1.5em;
}
// vim:filetype=scss

View File

@@ -1,7 +0,0 @@
.disclose-cardprog-separator,
.disclose-cardradial-separator,
.disclose-cardimage-separator {
opacity: 0.0;
}
// vim:ft=scss

View File

@@ -1,41 +0,0 @@
.disclose-big-button {
font-size: 15;
border-radius: 2em;
padding: 16px 0;
}
.disclose-stats-box {
background-color: $base01;
border-radius: 3em;
margin: 0 1em 0 1em;
padding: 0.8em;
.stats-label {
padding: 0.9em 0.9em 0.9em 0.83em;
font-size: 23;
font-weight: bold;
}
circular-progress {
background-color: $base02;
}
.stats-separator {
font-size: 1;
background-color: $base03;
padding: 1em;
margin-left: 15em;
}
.info-value {
font-size: 20;
font-weight: bold;
}
.info-label {
color: $base0F;
padding: 0 8px;
}
}
// vim:filetype=scss

View File

@@ -1,9 +0,0 @@
.disclose-cardimage-summary-box-CRITICAL {
background-color: darken($base11, 10%);
}
.disclose-cardimage-summary-box-LOW {
background-color: darken($base15, 10%);
}
// vim:ft=scss

View File

@@ -1,209 +0,0 @@
"""A DBus eavesdropper for org.freedesktop.Notifications
interface.
This is created mainly to cache the raw image data that is
sent by stupid applications like Spotify, Discord, etc.
Now that I think about it all of the electron clients do this.
Usually any application, if they had to they'd send the
notifications as a path i.e. caching the image themselves
and then returning the path to it.
Also, <https://specifications.freedesktop.org/notification-spec/latest/>
is a really nice manual. Give it a read.
"""
# Authored By dharmx <dharmx@gmail.com> under:
# GNU GENERAL PUBLIC LICENSE
# Version 3, 29 June 2007
#
# Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
# Everyone is permitted to copy and distribute verbatim copies
# of this license document, but changing it is not allowed.
#
# Permissions of this strong copyleft license are conditioned on
# making available complete source code of licensed works and
# modifications, which include larger works using a licensed work,
# under the same license. Copyright and license notices must be
# preserved. Contributors provide an express grant of patent rights.
#
# Read the complete license here:
# <https://github.com/dharmx/vile/blob/main/LICENSE.txt>
import datetime
import os
import pathlib
import sys
import typing
import dbus
import utils
from dbus.mainloop.glib import DBusGMainLoop
from gi.repository import GLib
class Urgency:
"""Acts as an Enum for indicating the urgency levels as per
the notifications specification.
You may use these to match wheter a specific notification belongs
to a specific urgency class or, not.
Attributes:
LOW: Ads, Login, etc.
NORMAL: USB unplugged, Drive mounted, etc.
CRITICAL: Your PC is on fire, Storage Full, etc.
"""
LOW = b"\x00"
NORMAL = b"\x01"
CRITICAL = b"\x02"
class Eavesdropper:
"""A quick and naive way of saving the image-data.
The main idea is to keep a notification server running that
implements image-data and image-path as per the freedesktop
notification specification.
And, then you'd run the eavesdropper which will connect to that
interface (org.freedesktop.Notifications) and will continuously
monitor that interface.
And, if any application sends a notification, that contains a raw
icon then it will be saved into the cache directory.
Attributes:
callback:
The arbitrary subroutine that will executed on getting a notification.
cache_dir:
The directory path that all of those image-data would be saved.
"""
def __init__(
self,
callback: typing.Callable = print,
cache_dir: str = "/tmp"
):
"""Assigns the CTOR parameters to the field variables (duh..)
Arguments:
callback: The arbitrary subroutine that will executed on getting a notification.
cache_dir: The directory path that all of those image-data would be saved.
"""
self.callback = callback
self.cache_dir = f"{os.path.expandvars(cache_dir)}/image-data"
# translation: mkdir --parents cache_dir
pathlib.PosixPath(self.cache_dir).mkdir(parents=True, exist_ok=True)
def _message_callback(
self, _,
message: dbus.lowlevel.MethodReturnMessage
or dbus.lowlevel.MethodCallMessage
):
"""A filter callback for parsing the specific messages that are received from the DBus interface.
Arguments:
proxy_bus:
The bus that sent the message.
message:
In this case a message is sent when the
Notify method is called AND when the Notify method returns something.
If the message is of type dbus.lowlevel.MethodCallMessage then this will NOT call the passed callback.
"""
# we will be filtering this out as we only need the value that the method call returns
# i.e. dbus.lowlevel.MethodReturnMessage
if type(message) != dbus.lowlevel.MethodCallMessage:
return
args_list = message.get_args_list()
# convert dbus data types to pythonic ones
# eg: dbus.String('Hello') -> 'Hello'
args_list = [utils.unwrap(item) for item in args_list]
# set fallbacks like a fine gentleman
details = {
"appname": args_list[0].strip() or "Unknown",
"summary": args_list[3].strip() or "Summary Unavailable.",
"body": args_list[4].strip() or "Body Unavailable.",
"id": datetime.datetime.now().strftime("%s"),
"urgency": "unknown",
}
if "urgency" in args_list[6]:
details["urgency"] = args_list[6]["urgency"]
if args_list[2].strip():
# if the iconpath value is a path i.e. if it has slashes on them
# then assign that as the iconpath
if "/" in args_list[2] or "." in args_list[2]:
details["iconpath"] = args_list[2]
else:
# and if the iconpath is just a string that has no extensions or,
# a pathlike structure like: 'bell' or 'custom-folder-bookmark'
# it might have a dash (-) sign but not all the time.
# then fetch that actual path of that icon as that is a part of the
# icon theme naming convention and the current icon theme should probably have it
details["iconpath"] = utils.get_gtk_icon_path(args_list[2])
else:
# if there are no icon hints then use fallback (generic bell)
details["iconpath"] = utils.get_gtk_icon_path(
"custom-notification")
if "image-data" in args_list[6]:
# capture the raw image bytes and save them to the cache_dir/x.png path
details["iconpath"] = f"{self.cache_dir}/{details['appname']}-{details['id']}.png"
utils.save_img_byte(
args_list[6]["image-data"], details["iconpath"])
# BUG: add a print statement -> init logger.py and disown the process
# BUG: then you'll notice the notifications with value (progress) hint does not get logged
if "value" in args_list[6]:
details["progress"] = args_list[6]["value"]
# execute arbitrary callback and passing details about the current notification.
self.callback(details)
def eavesdrop(
self,
timeout: int or bool = False,
timeout_callback: typing.Callable = print
):
"""Primes the session bus instance and starts a GLib mainloop.
Arguments:
timeout:
Intervals for executing the callback.
timeout_callback:
Callback that will be executed on intervals.
"""
DBusGMainLoop(set_as_default=True)
rules = {
"interface": "org.freedesktop.Notifications",
"member": "Notify",
"eavesdrop": "true", # https://bugs.freedesktop.org/show_bug.cgi?id=39450
}
bus = dbus.SessionBus()
# discard all other interfaces except org.freedesktop.Notifications
# setting eavesdrop to true enables DBus to send the messages that are
# not meant for you.
# removing the eavesdrop key from rules will not send the Notify method's
# contents to you (you can try and see what happens)
bus.add_match_string(",".join([f"{key}={value}" for key, value in rules.items()]))
bus.add_message_filter(self._message_callback)
try:
loop = GLib.MainLoop()
if timeout:
# executes a callback in intervals
GLib.set_timeout(timeout, timeout_callback)
loop.run()
except (KeyboardInterrupt, Exception) as excep:
sys.stderr.write(str(excep) + "\n")
bus.close()
# vim:filetype=python

View File

@@ -1,89 +0,0 @@
#!/usr/bin/env bash
CACHE_PATH="$XDG_CACHE_HOME/eww/dunst/notifications.txt"
QUOTE_PATH="$XDG_CACHE_HOME/eww/dunst/quotes.txt"
DEFAULT_QUOTE="To fake it is to stand guard over emptiness. \u2500\u2500 Arthur Herzog"
mkdir -p "${CACHE_PATH:h}" "${QUOTE_PATH:h}" 2> /dev/null
touch "$CACHE_PATH" "$QUOTE_PATH" 2> /dev/null
INTERVAL='0.2'
function rand_quote() {
local format
format="$(tr '\n \t\r' 's' < "$QUOTE_PATH")"
if [[ "$format" != "" ]]; then
shuf "$QUOTE_PATH" | head -n1
else
echo "$DEFAULT_QUOTE"
fi
}
function empty_format() {
local format
format=(
"(box"
":class"
"'disclose-empty-box'"
":height"
"750"
":orientation"
"'vertical'"
":space-evenly"
"false"
"(image"
":class"
"'disclose-empty-banner'"
":valign"
"'end'"
":vexpand"
"true"
":path"
"'./assets/wedding-bells.png'"
":image-width"
"250"
":image-height"
"250)"
"(label"
":vexpand"
"true"
":valign"
"'start'"
":wrap"
"true"
":class"
"'disclose-empty-label'"
":text"
"'$(rand_quote)'))"
)
echo "${format[@]}"
}
function not_empty() {
echo -n "(box :spacing 20 :orientation 'vertical' :space-evenly false"
if [[ "$(echo "$1" | tr -d ' ')" != "" ]]; then
echo -n "$1"
else
echo -n "$(empty_format)"
fi
echo ")"
}
case "$1" in
rmid) sed -i "/:identity ':::###::::XXXWWW$2===::'/d" "$CACHE_PATH" ;;
sub)
old="$(tr '\n' ' ' < "$CACHE_PATH")"
not_empty "$old"
while sleep "$INTERVAL"; do
new="$(tr '\n' ' ' < "$CACHE_PATH")"
if [[ "$old" != "$new" ]]; then
not_empty "$new"
old="$new"
fi
done
;;
quote) rand_quote ;;
cls) echo > "$CACHE_PATH" ;;
esac
# vim:filetype=sh

View File

@@ -1,22 +0,0 @@
#!/bin/bash
CONFIG="$XDG_CONFIG_HOME/eww/ewwrc"
CACHE_DIR=$(jq --raw-output .github.cache_dir "$CONFIG")
eval "CACHE_DIR=${CACHE_DIR}"
USER_NAME=$(jq --raw-output .github.username "$CONFIG")
DATE=$(date +%F)
[ -f "$CACHE_DIR" ] || mkdir --parents "$CACHE_DIR"
fetch_user_info() {
[ -f "$CACHE_DIR/users-$USER_NAME-$DATE.json" ] || curl --silent https://api.github.com/users/dharmx > "$CACHE_DIR/users-$USER_NAME-$DATE.json"
[ -f "$CACHE_DIR/repos-$USER_NAME-$DATE.json" ] || curl --silent https://api.github.com/users/dharmx/repos > "$CACHE_DIR/repos-$USER_NAME-$DATE.json"
}
fetch_user_info
case "$1" in
users) cat "$CACHE_DIR/users-$USER_NAME-$DATE.json" ;;
repos) cat "$CACHE_DIR/repos-$USER_NAME-$DATE.json" ;;
esac
# vim:filetype=sh

View File

@@ -1,320 +0,0 @@
"""This file is tightly integrated with logger.py and won't work without it.
Unlike the files cache.py and utils.py.
This module just redirects specific presets of messages based on the source
(the application) that sent that message.
The redirector will call the specific function based off the appname, then
the called handler function will evaluate the YUCK literal and replace all
of the items on the format string with the passed attributes and then return it.
"""
# Authored By dharmx <dharmx.dev@gmail.com> under:
# GNU GENERAL PUBLIC LICENSE
# Version 3, 29 June 2007
#
# Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
# Everyone is permitted to copy and distribute verbatim copies
# of this license document, but changing it is not allowed.
#
# Permissions of this strong copyleft license are conditioned on
# making available complete source code of licensed works and
# modifications, which include larger works using a licensed work,
# under the same license. Copyright and license notices must be
# preserved. Contributors provide an express grant of patent rights.
#
# Read the complete license here:
# <https://github.com/dharmx/vile/blob/main/LICENSE.txt>
import datetime
import cache
import utils
# WARN: Subject to heavy change.
def redir_to_handlers(formats, attributes: dict) -> str:
r"""Function for evaluating which handler function will be called.
Before calling the handler function it will do some filtering and
then actually call the handler which should return a fully evaluated
YUCK literal string.
Arguments:
formats:
All of the YUCK literal format strings.
attributes:
Details about the currently sent notification like summary, body, appname, etc.
Returns:
A str that is a primed YUCK literal with passed attributes.
Example:
Format:
(_cardimage :identity ':::###::::XXXWWW%(id)s===::'
:close_action './src/shell/logger.py rmid %(id)s'
:limit_body '%(BODY_LIMITER)s'
:limit_summary '%(SUMMARY_LIMITER)s'
:summary '%(summary)s'
:body '%(body)s'
:close ''
:image_height 100
:image_width 100
:image '%(iconpath)s'
:appname '%(appname)s'
:icon '%(iconpath)s'
:icon_height 32
:icon_width 32
:timestamp '%(TIMESTAMP)s'
:urgency '%(URGENCY)s')
Primed:
(_cardimage :identity ':::###::::XXXWWW1658665761===::'
:close_action './src/shell/logger.py rmid 1658665761'
:limit_body '110'
:limit_summary '30'
:summary 'Picom'
:body 'The compositer is now disabled.'
:close ''
:image_height 100
:image_width 100
:image '/home/maker/.icons/custom/stock/128/custom-crying.png'
:appname 'Picom'
:icon '/home/maker/.icons/custom/stock/128/custom-crying.png'
:icon_height 32
:icon_width 32
:timestamp '17:59'
:urgency 'CRITICAL')
"""
# assign the timestamp of the notification.
attributes["TIMESTAMP"] = datetime.datetime.now().strftime(
attributes["TIMESTAMP_FORMAT"])
# turn the urgency values (which are in bytes) to a more readable format (string).
match attributes["urgency"]:
case cache.Urgency.LOW:
attributes["URGENCY"] = "LOW"
case cache.Urgency.NORMAL:
attributes["URGENCY"] = "NORMAL"
case cache.Urgency.CRITICAL:
attributes["URGENCY"] = "CRITICAL"
case _:
attributes["URGENCY"] = "NORMAL"
# handle next lines (especially discord code blocks)
# NOTE: may make this only discord / firefox specific
attributes["body"] = attributes["body"].replace("\n", " ")
attributes["summary"] = attributes["summary"].replace("\n", " ")
# check if there are any pango tags on the body and summary and if so
# it will then remove it.
if utils.contains_pango(attributes["body"]):
attributes["body"] = utils.strip_pango_tags(attributes["body"])
if utils.contains_pango(attributes["summary"]):
attributes["summary"] = utils.strip_pango_tags(
attributes["summary"]
)
# it will replace all of the apostrophes with \' i.e. escape them.
if "'" in attributes["body"]:
attributes["body"] = attributes["body"].replace("'", "\\'")
if "'" in attributes["summary"]:
attributes["summary"] = attributes["summary"].replace("'", "\\'")
# only 15 chars would be taken if the summary has non-english charecters.
attributes["SUMMARY_LIMITER"] = ""
summary_lang_char_check = utils.has_non_english_chars(
attributes["summary"][:15]
)
# if summary has asian characters like Japanese, Hindi or, Chinese
if summary_lang_char_check["CJK"]:
attributes["SUMMARY_LIMITER"] = 14
# if summary has Cyrillic characters
elif summary_lang_char_check["CYR"]:
attributes["SUMMARY_LIMITER"] = 30
attributes["BODY_LIMITER"] = ""
body_lang_char_check = utils.has_non_english_chars(attributes["body"][:70])
if body_lang_char_check["CJK"]:
attributes["BODY_LIMITER"] = 80
elif body_lang_char_check["CYR"]:
attributes["BODY_LIMITER"] = 110
else:
attributes["BODY_LIMITER"] = 100
# redirect to handlers
match attributes["appname"]:
case "notify-send":
return notify_send_handler(formats, attributes)
case "volume":
return volume_handler(formats, attributes)
case "brightness":
return brightness_handler(formats, attributes)
case "shot":
return shot_handler(formats, attributes)
case "shot_icon":
return shot_icon_handler(formats, attributes)
case "todo":
return todo_handler(formats, attributes)
case "Spotify":
return Spotify_handler(formats, attributes)
case _:
return default_handler(formats, attributes)
# TODO: Segregate redir_to_handlers into more utility / helper functions.
# NOTE: Currently it is a bit hard to utilize config values without making the code dirtier.
# NOTE: For instance shot_handler will eventually load the command strings from the config JSON for attributes["DELETE"]. See logger.py
def shot_handler(formats, attributes: dict) -> str:
"""Handler for screenshot related notifications.
Note that this handler will only handle the screenshots itself.
That is, it won't handle it if say.. the screenshot is copied to the clipboard, etc.
All of those are handled by shot_icon_handler.
Arguments:
formats: See redir_to_handlers.
attributes: See redir_to_handlers.
Returns:
See redir_to_handlers
"""
# TODO: Make this better
attributes["DELETE"] = f"rm --force \\'{attributes['iconpath']}\\' && ./src/shell/logger.py rmid {attributes['id']}"
attributes["OPEN"] = f"xdg-open \\'{attributes['iconpath']}\\'"
# capitalize words i.e. notify_send -> Notify Send
attributes["appname"] = utils.prettify_name(attributes["appname"])
return formats["shot"] % attributes
def Spotify_handler(formats, attributes: dict) -> str:
"""Handler for notifications related to the official electron client for Spotify.
Arguments:
formats: See redir_to_handlers.
attributes: See redir_to_handlers.
Returns:
See redir_to_handlers
"""
return formats["Spotify"] % attributes
def default_handler(formats, attributes: dict) -> str:
r"""Handler for basic notifications. The notifications that are ordinary.
Or, rather the notifications that do not match any of the match-cases in
the redir_to_handlers function.
Arguments:
formats: See redir_to_handlers.
attributes: See redir_to_handlers.
Example:
notify-send Hello
notify-send Greetings
notify-send -u low -i bell Greetings Ding\!
notify-send -a appname-does-not-exist -i bell Yo
Returns:
See redir_to_handlers
"""
attributes["appname"] = utils.prettify_name(attributes["appname"])
return formats["default"] % attributes
def notify_send_handler(formats, attributes: dict) -> str:
"""Handler for notifications related to the notify-send command.
See:
default_handler
Arguments:
formats: See redir_to_handlers.
attributes: See redir_to_handlers.
Returns:
See redir_to_handlers
"""
attributes["appname"] = utils.prettify_name(attributes["appname"])
return formats["notify-send"] % attributes
def brightness_handler(formats, attributes: dict) -> str:
"""Handler for notifications related to brightness control.
Arguments:
formats: See redir_to_handlers.
attributes: See redir_to_handlers.
Returns:
See redir_to_handlers
"""
attributes["appname"] = utils.prettify_name(attributes["appname"])
return formats["brightness"] % attributes
def volume_handler(formats, attributes: dict) -> str:
"""Handler for notifications related to volume control.
Arguments:
formats: See redir_to_handlers.
attributes: See redir_to_handlers.
Returns:
See redir_to_handlers
"""
attributes["appname"] = utils.prettify_name(attributes["appname"])
return formats["volume"] % attributes
# TODO: Make this general purpose and not just todo specific.
def todo_handler(formats, attributes: dict) -> str:
"""Handler for notifications related to todo-bin CLI application by Siddomy.
The notification body needs to be in a particular format in order for it to register.
That is: <completed_tasks> tasks done and <total_tasks> are remaining.
The fragments <completed_tasks> and <total_tasks> will be picked up by this handler.
Arguments:
formats: See redir_to_handlers.
attributes: See redir_to_handlers.
Returns:
See redir_to_handlers
"""
splitted = attributes["body"].split(" ")
attributes["TOTAL"] = int(splitted[4])
attributes["DONE"] = int(splitted[0])
# handle division by zero
attributes["PERC"] = (attributes["DONE"] / attributes["TOTAL"]
) * 100 if attributes["DONE"] > 0 else 0
attributes["appname"] = utils.prettify_name(attributes["appname"])
return formats["todo"] % attributes
def shot_icon_handler(formats, attributes: dict) -> str:
"""Almost same as default_handler only just it uses a different icon.
Redundant, but still nice to if you want to add additional
functionalities on this particular appname.
See:
default_handler
shot_handler
Arguments:
formats: See redir_to_handlers.
attributes: See redir_to_handlers.
Returns:
See redir_to_handlers
"""
attributes["appname"] = utils.prettify_name(attributes["appname"])
return formats["shot_icon"] % attributes
# vim:filetype=python

View File

@@ -1,146 +0,0 @@
#!/usr/bin/env --split-string=python -u
"""Script for logging desktop notifications in the form of YUCK literal.
General idea is to have a file log the notifications in the form of YUCK
literal containing a widget structure which will then be concatenated into
a box widget to take a list-like structure.
The said structure needs to be re-rendered whenever the log file notices
a change. Like, deleting an entry or, adding an entry or, editing an entry.
Note, if you still have not guessed already, if you make any changes to the
log file then the list of notifications will be re-rendered again.
"""
# Authored By dharmx <dharmx.dev@gmail.com> under:
# GNU GENERAL PUBLIC LICENSE
# Version 3, 29 June 2007
#
# Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
# Everyone is permitted to copy and distribute verbatim copies
# of this license document, but changing it is not allowed.
#
# Permissions of this strong copyleft license are conditioned on
# making available complete source code of licensed works and
# modifications, which include larger works using a licensed work,
# under the same license. Copyright and license notices must be
# preserved. Contributors provide an express grant of patent rights.
#
# Read the complete license here:
# <https://github.com/dharmx/vile/blob/main/LICENSE.txt>
# WARN: This script is under active development and is subject to frequent changes.
# WARN: Currently this script is quite inefficient. So, you have been warned.
import json
import os
import pathlib
import sys
import cache
import handlers
import utils
# presetted notification format strings i.e. they are just utility items that help reducing the size of code
# another reason is convenience i.e. it would be easier to have all the formats in one place and defining them
# would be less cumbersome.
FORMATS = {
"spotifyd": "(_cardimage :identity ':::###::::XXXWWW%(id)s===::' :close_action './src/shell/combine.bash rmid %(id)s' :limit_body '%(BODY_LIMITER)s' :limit_summary '%(SUMMARY_LIMITER)s' :summary '%(summary)s' :body '%(body)s' :close '' :image_height 100 :image_width 100 :image '%(iconpath)s' :appname '%(appname)s' :icon '%(iconpath)s' :icon_height 32 :icon_width 32 :timestamp '%(TIMESTAMP)s' :urgency '%(URGENCY)s')",
"ncspot": "(_cardimage :identity ':::###::::XXXWWW%(id)s===::' :close_action './src/shell/combine.bash rmid %(id)s' :limit_body '%(BODY_LIMITER)s' :limit_summary '%(SUMMARY_LIMITER)s' :summary '%(summary)s' :body '%(body)s' :close '' :image_height 100 :image_width 100 :image '%(iconpath)s' :appname '%(appname)s' :icon '%(iconpath)s' :icon_height 32 :icon_width 32 :timestamp '%(TIMESTAMP)s' :urgency '%(URGENCY)s')",
"Spotify": "(_cardimage :class 'Spotify-rectangle' :identity ':::###::::XXXWWW%(id)s===::' :close_action './src/shell/combine.bash rmid %(id)s' :limit_body '%(BODY_LIMITER)s' :limit_summary '%(SUMMARY_LIMITER)s' :summary '%(summary)s' :body '%(body)s' :close '' :image_height 100 :image_width 100 :image '%(iconpath)s' :appname '%(appname)s' :icon '%(iconpath)s' :icon_height 32 :icon_width 32 :timestamp '%(TIMESTAMP)s' :urgency '%(URGENCY)s')",
"shot_icon": "(_cardimage :class 'shot-image-bord-dashed' :identity ':::###::::XXXWWW%(id)s===::' :close_action './src/shell/combine.bash rmid %(id)s' :limit_body '%(BODY_LIMITER)s' :limit_summary '%(SUMMARY_LIMITER)s' :summary '%(summary)s' :body '%(body)s' :close '' :image_height 100 :image_width 100 :image '%(iconpath)s' :appname '%(appname)s' :icon '%(iconpath)s' :icon_height 32 :icon_width 32 :timestamp '%(TIMESTAMP)s' :urgency '%(URGENCY)s')",
"shot": "(_cardscr :identity ':::###::::XXXWWW%(id)s===::' :close_action './src/shell/combine.bash rmid %(id)s' :limit_body '%(BODY_LIMITER)s' :limit_summary '%(SUMMARY_LIMITER)s' :delete '%(DELETE)s' :open '%(OPEN)s' :summary '%(summary)s' :image '%(iconpath)s' :image_height 250 :image_width 100 :urgency '%(URGENCY)s' :close '' :timestamp '%(TIMESTAMP)s')",
"default": "(_cardimage :identity ':::###::::XXXWWW%(id)s===::' :close_action './src/shell/combine.bash rmid %(id)s' :limit_body '%(BODY_LIMITER)s' :limit_summary '%(SUMMARY_LIMITER)s' :summary '%(summary)s' :body '%(body)s' :close '' :image_height 100 :image_width 100 :image '%(iconpath)s' :appname '%(appname)s' :icon '%(iconpath)s' :icon_height 32 :icon_width 32 :timestamp '%(TIMESTAMP)s' :urgency '%(URGENCY)s')",
"notify-send": "(_cardimage :identity ':::###::::XXXWWW%(id)s===::' :close_action './src/shell/combine.bash rmid %(id)s' :limit_body '%(BODY_LIMITER)s' :limit_summary '%(SUMMARY_LIMITER)s' :summary '%(summary)s' :body '%(body)s' :close '' :image_height 100 :image_width 100 :image '%(iconpath)s' :appname '%(appname)s' :icon './assets/browser.png' :icon_height 32 :icon_width 32 :timestamp '%(TIMESTAMP)s' :urgency '%(URGENCY)s')",
"empty": "(box :class 'disclose-empty-box' :height 750 :orientation 'vertical' :space-evenly false (image :class 'disclose-empty-banner' :valign 'end' :vexpand true :path './assets/wedding-bells.png' :image-width 250 :image-height 250) (label :vexpand true :valign 'start' :wrap true :class 'disclose-empty-label' :text '%(QUOTE)s'))",
"brightness": "(_cardprog :identity ':::###::::XXXWWW%(id)s===::' :close_action './src/shell/combine.bash rmid %(id)s' :limit_body '%(BODY_LIMITER)s' :limit_summary '%(SUMMARY_LIMITER)s' :summary '%(summary)s' :body '%(body)s' :close '' :image_height 100 :image_width 100 :image '%(iconpath)s' :appname '%(appname)s' :icon '%(iconpath)s' :icon_height 32 :icon_width 32 :timestamp '%(TIMESTAMP)s' :urgency '%(URGENCY)s' :progress '%(progress)s')",
"todo": "(_cardradial :identity ':::###::::XXXWWW%(id)s===::' :close_action './src/shell/combine.bash rmid %(id)s' :limit_body '%(BODY_LIMITER)s' :limit_summary '%(SUMMARY_LIMITER)s' :summary '%(summary)s' :body '%(body)s' :close '' :appname '%(appname)s' :progress %(PERC)s :thickness 20.0 :total %(TOTAL)s :done %(DONE)s :timestamp '%(TIMESTAMP)s' :urgency '%(URGENCY)s')",
"volume": "(_cardprog :identity ':::###::::XXXWWW%(id)s===::' :close_action './src/shell/combine.bash rmid %(id)s' :limit_body '%(BODY_LIMITER)s' :limit_summary '%(SUMMARY_LIMITER)s' :summary '%(summary)s' :body '%(body)s' :close '' :image_height 100 :image_width 100 :image '%(iconpath)s' :appname '%(appname)s' :icon '%(iconpath)s' :icon_height 32 :icon_width 32 :timestamp '%(TIMESTAMP)s' :urgency '%(URGENCY)s' :progress '%(progress)s')"
}
if __name__ == "__main__":
# load only notification related options from the config JSON
config = json.loads(
pathlib.PosixPath(
os.path.expandvars("$XDG_CONFIG_HOME/eww/ewwrc")
).read_text()
)["notify"]
# kind of like the sliding window algorithm i.e. will pop the notifications if this number is reached
HISTORY_LIMIT = config["limit"]
# file path where the notifications will be saved
CACHE_PATH = os.path.expandvars(config["cache_path"])
# directory path where the notifications will be saved
# WARN: Do not edit this; Only edit this if you know what you are doing!
CACHE_DIR = os.path.dirname(CACHE_PATH)
# file path where the quotes are stored
QUOTE_PATH = os.path.expandvars(config["quote_path"])
# fallback if the quote DB has no quotes in them
DEFAULT_QUOTE = config["default_quote"]
# Watcher interval; Reflects how fast disclose will be rendered.
INTERVAL = config["interval"]
# handle IndexError
if len(sys.argv) < 2:
sys.argv = ("dummy", "dummy")
match sys.argv[1]:
case "subscribe":
utils.create_parents_file(CACHE_PATH) # mkdir --parents
utils.create_parents_file(QUOTE_PATH) # mkdir --parents
utils.watcher(
CACHE_PATH,
lambda contents: sys.stdout.write(
"(box :spacing 20 :orientation 'vertical' :space-evenly false " +
# handle empty and display fallback
contents.replace("\n", " ") + ")\n"
if contents.strip()
else (
(FORMATS["empty"] + "\n")
% {"QUOTE": utils.get_rand_quote(QUOTE_PATH, DEFAULT_QUOTE)}
)
),
INTERVAL,
)
case "rmid":
# grep based off the notification id and then remove that YUCK literal entry from the log file
utils.file_matched_index_rm(
CACHE_PATH, f":identity ':::###::::XXXWWW{sys.argv[2]}===::'"
)
case "stats":
sys.stdout.write(
json.dumps(
utils.parse_and_print_stats(
pathlib.PosixPath(CACHE_PATH).read_text()
)
)
+ "\n"
)
case "rm":
utils.file_rm_line(CACHE_PATH, int(sys.argv[2]))
case "quote":
sys.stdout.write(utils.get_rand_quote(QUOTE_PATH, DEFAULT_QUOTE))
case "cls":
pathlib.PosixPath(CACHE_PATH).write_text("")
case "init":
def master_callback(details: dict):
r"""Callback function that handles fetching and logging the notification details.
Arguments:
details: the JSON that should have a similar structure to the following:
{
"summary": "Hello",
"body": "Who's there?",
"id": 16444442,
"urgency": "LOW"
}
"""
details["TIMESTAMP_FORMAT"] = config["timestamp"]
if not config["excluded_appnames"] or details["appname"] not in config["excluded_appnames"]:
saved_path = handlers.redir_to_handlers(FORMATS, details)
# actual point where the notification is being logged.
utils.file_add_line(CACHE_PATH, saved_path, HISTORY_LIMIT)
# start eavesdropping on the org.freedesktop.Notifications interface and log the notification info
cache.Eavesdropper(master_callback, CACHE_DIR).eavesdrop()
# vim:filetype=python

View File

@@ -1,463 +0,0 @@
#!/usr/bin/env python
"""Utility module. Shared across almost all of the python scripts / modules."""
# Authored By dharmx <dharmx.dev@gmail.com> under:
# GNU GENERAL PUBLIC LICENSE
# Version 3, 29 June 2007
#
# Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
# Everyone is permitted to copy and distribute verbatim copies
# of this license document, but changing it is not allowed.
#
# Permissions of this strong copyleft license are conditioned on
# making available complete source code of licensed works and
# modifications, which include larger works using a licensed work,
# under the same license. Copyright and license notices must be
# preserved. Contributors provide an express grant of patent rights.
#
# Read the complete license here:
# <https://github.com/dharmx/vile/blob/main/LICENSE.txt>
import json
import os
import pathlib
import random
import re
import sys
import time
import typing
import unicodedata
from wand.image import COLORSPACE_TYPES, Image
from html.parser import HTMLParser
from io import StringIO
import dbus
import gi
# supress GIO warnings
gi.require_version("Gtk", "3.0")
gi.require_version("GdkPixbuf", "2.0")
import requests
from gi.repository import GdkPixbuf, Gio, GLib, Gtk
class PangoStripper(HTMLParser):
def __init__(self):
super().__init__()
self.reset()
self.strict = False
self.convert_charrefs = True
self.text = StringIO()
def handle_data(self, d):
self.text.write(d)
def get_data(self):
return self.text.getvalue()
def contains_pango(string: str) -> bool:
"""Checks if a string contains HTML tags or not. Since a pango string has to have at least two tags
Arguments:
string: The Pango markup format string.
Returns:
A bool value i.e. whether a has pango markup or not.
"""
return any(item in string for item in ["<span>", "</a>", "</span>"])
def strip_pango_tags(pango: str) -> str:
"""Removes HTML tags like <span>, <span align='left'>, etc.
Arguments:
pango: The Pango format string that needs to be stripped of its tags.
Returns:
A str without the tags.
"""
stripper = PangoStripper()
stripper.feed(pango)
return stripper.get_data()
def create_parents_file(file_path: str):
'''A replication of [ -f "$FILE_PATH" ] || mkdir --parents "$FILE_PATH"
Arguments:
file_path: The location of the file.
'''
pathlib.PosixPath(os.path.dirname(file_path)).mkdir(
parents=True, exist_ok=True)
pathlib.PosixPath(file_path).touch(exist_ok=True)
def watcher(file_path: str, callback: typing.Callable, interval: int, initial: bool = True):
"""A file watcher function that executes a callback function if any change is made to a file.
Arguments:
file_path: The file location that needs to be monitored.
callback: A callback function that would be invoked when any change is noticed.
interval: The time rate at which the file will be checked for changes.
initial: Initially execute the callback once before entering the watch loop.
"""
try:
old = pathlib.PosixPath(file_path).read_text()
if initial:
callback(old)
while not time.sleep(interval):
new = pathlib.PosixPath(file_path).read_text()
if new != old:
callback(new)
old = new
except KeyboardInterrupt:
sys.stdout.write("Closed.\n")
except FileNotFoundError:
sys.stderr.write("The path does not exist!\n")
except Exception as excep:
sys.stderr.write(f"{excep}\n")
def get_rand_quote(file_path: str, default_quote: str) -> str:
"""Get a random quote or, rather a random line.
Arguments:
file_path: The location of the file.
default_quote: Fallback quote if the file doesn't exist r, is empty.
"""
loaded_quotes: str = pathlib.PosixPath(file_path).read_text().strip()
return random.choice(loaded_quotes.splitlines()) if loaded_quotes else default_quote
def file_matched_index_rm(file_path: str, pattern: str):
"""Removes line from a file if a pattern matches that line.
Arguments:
file_path: The location of the file.
pattern: Pattern to match the line that needs to be removed.
"""
posix_file_path = pathlib.PosixPath(file_path)
lines = posix_file_path.read_text().splitlines()
# skip the line if pattern matches
rm_index_lines = [
lines[index]
for index in range(len(lines))
if not re.search(pattern, lines[index])
]
if len(lines) != len(rm_index_lines):
posix_file_path.write_text("\n".join(rm_index_lines))
def file_rm_line(file_path: str, position: int | bool | range = True):
"""Ranged line removal, indexed line removal and stack-like (pop / reverse pop) line removal.
Arguments:
file_path: The location of the file.
position: If the value is of type:
range: then that the lines starting from that range will be removed.
int: then that specific file line number will be removed.
bool: True will pop and False will pop from the end.
"""
file = pathlib.PosixPath(file_path)
match str(type(position)):
case "<class 'int'>":
file_contents = file.read_text().splitlines()
# no need to run the loop if all you need to do is remove the first line
if position == 0:
file_rm_line(file_path, position=True)
return
elif position == len(file_contents) - 1:
file_rm_line(file_path, position=False)
return
line_removed_contents = []
# skip the line that matches the position value.
for index in range(len(file_contents)):
if index != position:
line_removed_contents += [file_contents[index]]
file.write_text("\n".join(line_removed_contents))
# filter falsey and truthy and use the actual bool class signature
case "<class 'bool'>":
file_contents = file.read_text().splitlines()
file_contents = file_contents[1:] if position else file_contents[:-1]
file.write_text("\n".join(file_contents))
case "<class 'range'>":
if not position:
file.write_text("")
return
file_contents = file.read_text().splitlines()
write_contents = []
# keep on skipping the lines if they are in the range.
for index in range(len(file_contents)):
if index not in position:
write_contents += [file_contents[index]]
file.write_text("\n".join(write_contents))
def prettify_name(name: str) -> str:
"""Transforms 'Hello-World' -> 'Hello World', 'notify-send++lol' -> 'Notify Send Lol'
Arguments:
name: The string that needs to be prettified.
Returns:
A prettified str.
"""
return " ".join(
item.capitalize()
for item in name.replace("-", " ").replace("_", " ").split(" ")
)
def file_add_line(file_path: str, write_contents: str, limit: int, top: bool = True):
"""Functions like sliding window algorithm <https://towardsdev.com/sliding-window-algorithm-145f8e201a64>
That is after the file hits a certain line limit, lines from the behind will be dropped (older entries)
and THEN the newer ones will be entered.
Arguments:
file_path: The location of the file.
write_contents: The contents that needs to be written to the file.
limit: The line limit.
top: Adds new entry at the beginning of the file if True, at the end otherwise.
"""
file = pathlib.PosixPath(file_path)
file_contents = file.read_text().splitlines()
if len(file_contents) == limit:
file_contents = file_contents[:-1]
file_contents = (
[write_contents] + file_contents if top else file_contents + [write_contents]
)
file.write_text("\n".join(file_contents))
def parse_and_print_stats(file_contents: str) -> dict:
"""Looks the words CRITICAL, LOW and NORMAL and calcuates its frequency.
Arguments:
file_contents: the string that needs to be calculated for frequency.
Returns:
Individual frequency of the words in dict format.
{"CRITICAL": 10.00, "NORMAL": 85.00, "LOW": 5.00}
"""
stats = {"critical": 0, "low": 0, "normal": 0, "total": 0}
for line in file_contents.splitlines():
if "CRITICAL" in line:
stats["critical"] += 1
stats["total"] += 1
elif "LOW" in line:
stats["low"] += 1
stats["total"] += 1
elif "NORMAL" in line:
stats["normal"] += 1
stats["total"] += 1
# handle division / zero
stats["critical"] = (
stats["critical"] * 100 /
stats["total"] if stats["critical"] > 0 else 0
)
stats["normal"] = (
stats["normal"] * 100 / stats["total"] if stats["normal"] > 0 else 0
)
stats["low"] = stats["low"] * 100 / \
stats["total"] if stats["low"] > 0 else 0
return stats
def has_non_english_chars(string: str) -> dict:
"""Check if there is any CJK / Cyrillic characters in the given string.
Arguments:
string: the string that needs to be checked.
Returns:
A dict containing True / False for keys representing if such characters exist or not.
{"CJK": True, "CYR": False} for string value: "おはようございます means Good Morning!"
"""
return {
"CJK": any(unicodedata.category(char) == "Lo" for char in string),
"CYR": any(unicodedata.category(char) == "Lu" for char in string),
}
def unwrap(value: dbus.Array
| dbus.Boolean
| dbus.Byte
| dbus.Dictionary
| dbus.Double
| dbus.Int16
| dbus.ByteArray
| dbus.Int32
| dbus.Int64
| dbus.Signature
| dbus.UInt16
| dbus.UInt32
| dbus.UInt64
| dbus.String) -> str | int | list | tuple | float | dict | bool | bytes:
"""Try to trivially translate a dictionary's elements into nice string formatting.
Arguments:
value: A type out of:
dbus.Boolean,
dbus.Byte,
dbus.Dictionary,
dbus.Double,
dbus.Int16,
dbus.ByteArray,
dbus.Int32,
dbus.Int64,
dbus.Signature,
dbus.UInt16,
dbus.UInt32,
dbus.UInt64 and dbus.String
Returns:
A str or int or list or tuple or float or dict or bool or bytes depending on the value.
"""
if isinstance(value, dbus.ByteArray):
return "".join([str(byte) for byte in value])
if isinstance(value, (dbus.Array, list, tuple)):
return [unwrap(item) for item in value]
if isinstance(value, (dbus.Dictionary, dict)):
return dict([(unwrap(x), unwrap(y)) for x, y in value.items()])
if isinstance(value, (dbus.Signature, dbus.String)):
return str(value)
if isinstance(value, dbus.Boolean):
return bool(value)
if isinstance(
value,
(dbus.Int16, dbus.UInt16, dbus.Int32,
dbus.UInt32, dbus.Int64, dbus.UInt64),
):
return int(value)
if isinstance(value, dbus.Byte):
return bytes([int(value)])
return value
def save_img_byte(px_args: typing.Iterable, save_path: str):
"""Converts image data to an image file.
See <https://docs.gtk.org/gdk-pixbuf/ctor.Pixbuf.new_from_bytes.html> for the whole description.
Arguments:
px_args: Should contain an iterable in the following format.
[image_width, image_height, rowstride, has_alpha, bits_per_sample, _, image_bytes]
save_path: the filepath where this pixbuf should be saved to.
"""
# https://specifications.freedesktop.org/notification-spec/latest/ar01s08.html
# https://specifications.freedesktop.org/notification-spec/latest/ar01s05.html
GdkPixbuf.Pixbuf.new_from_bytes(
width=px_args[0],
height=px_args[1],
has_alpha=px_args[3],
data=GLib.Bytes(px_args[6]),
colorspace=GdkPixbuf.Colorspace.RGB,
rowstride=px_args[2],
bits_per_sample=px_args[4],
).savev(save_path, "png")
def get_gtk_icon_path(icon_name: str, size: int = 128) -> str:
"""Returns the icon path by the specified name from your current icon theme.
Recursively search for lower sizes until 32x32 and
return a fallback if the requested path does not exists.
Arguments:
icon_name: Icon name as per the icon naming specification:
<https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html>
size: the pixel size (widthxheight -> heightxheight -> size) of the requested icon.
"""
if size < 32:
return os.path.expandvars("$XDG_CONFIG_HOME/eww/assets/bell.png")
if info := Gtk.IconTheme.get_default().lookup_icon(icon_name, size, 0):
return info.get_filename()
return get_gtk_icon_path(icon_name, size - 1)
def get_mime_icon_path(mimetype: str, size: int = 32) -> str:
"""Gets the default icon path from the current GTK icon theme for the specified mime type.
Arguments:
mimetype: The file type like png, json, etc.
size: The of the returned icon
Returns:
A str that is the path to the requested icon.
"""
icon = Gio.content_type_get_icon(mimetype)
theme = Gtk.IconTheme.get_default()
if info := theme.choose_icon(icon.get_names(), size, 0):
return info.get_filename()
def get_location() -> dict | None:
try:
response = requests.get('https://api64.ipify.org?format=json').json()
ip_address = response["ip"]
response = requests.get(f'https://ipapi.co/{ip_address}/json/').json()
return {
"latitude": response.get("latitude"),
"longitude": response.get("longitude"),
"city": response.get("city"),
"country": response.get("country_name"),
"lang": response.get("languages").split(",")[0],
}
except requests.exceptions.ConnectionError:
return None
def auto_locate(cache_dir: str) -> dict | None:
cache_posix_path = pathlib.PosixPath(f"{cache_dir}/location.json")
if not cache_posix_path.is_file(): # assuming the directory exists
fetched_location = get_location()
if not fetched_location:
return None
cache_posix_path.write_text(json.dumps(fetched_location))
return fetched_location
return json.loads(cache_posix_path.read_text())
def fetch_save(link: str, save_path: str, callback: typing.Callable = None) -> bool:
try:
data = requests.get(link)
if data.status_code == 200:
metadata = data.json()
if callback:
metadata = callback(metadata)
pathlib.PosixPath(save_path).write_text(json.dumps(metadata))
return True
return False
except requests.exceptions.ConnectionError:
return False
def img_dark_bright_col(filepath: str, colors: int = 10) -> tuple:
with Image(filename=filepath) as image:
image.quantize(
number_colors=colors,
colorspace_type=COLORSPACE_TYPES[21],
dither=True, measure_error=False, treedepth=8)
return tuple(
"#%02X%02X%02X" % (item.red_int8, item.green_int8, item.blue_int8)
for item in image.histogram)
if __name__ == "__main__":
match sys.argv[1]:
case "histogram":
image_path = sys.argv[2]
histogram_colors = int(sys.argv[3])
print(json.dumps(img_dark_bright_col(image_path, histogram_colors)))
# vim:filetype=python

View File

@@ -1,4 +0,0 @@
; REAL THINGS
(defpoll disclose_stats :interval "5s" "./src/shell/logger.py stats")
(deflisten disclose_sub "./src/shell/combine.bash sub")
(defpoll disclose_dnd_state :interval "1s" "dunstctl is-paused")

View File

@@ -1,29 +0,0 @@
(include "./panel/yuck/_env.yuck")
(include "./panel/yuck/cards/_cardimage.yuck")
(include "./panel/yuck/cards/_cardprog.yuck")
(include "./panel/yuck/cards/_cardscr.yuck")
(include "./panel/yuck/cards/_cardradial.yuck")
(include "./panel/yuck/_stats.yuck")
(include "./panel/yuck/_music.yuck")
(include "./panel/yuck/_layout.yuck")
(defwidget closer [window]
(eventbox :onclick "eww close ${window} && eww close ${window}-closer"))
(defwindow panel-closer
:monitor 0
:geometry (geometry :width "100%" :height "100%")
:stacking "fg"
:focusable false
(closer :window "panel"))
(defwindow panel :stacking "fg"
:windowtype "normal"
:wm-ignore true
:monitor 0
:geometry (geometry :width "26%" :height "100%" :anchor "right bottom")
(panel_layout))
;; vim:ft=yuck

View File

@@ -1,25 +0,0 @@
(defwidget panel_layout []
(box :class "disclose-closer"
(box :orientation "vertical"
:space-evenly false
:class "disclose-layout-box"
(box :space-evenly false
:class "disclose-headers"
:spacing 6
(label :text "Notifications"
:class "disclose-headers-label"
:halign "start"
:hexpand true))
(scroll :hscroll false
:vscroll true
:vexpand true
:hexpand true
:class "disclose-scroll"
(literal :content disclose_sub))
(label :class "disclose-separator" :text "")
(wifi)
(box :space-evenly false :class "disclose-misc-box"
(dstats)
(dmusic)))))
;; vim:filetype=yuck

View File

@@ -1,47 +0,0 @@
(defwidget _sundialinfo [class halign ?hexpand ?prefix]
(label :class "${class}-sundial-label" :halign halign :hexpand hexpand
:text "${prefix}${time.hour >= 2 && time.hour <= 4 ? "Early Morning" :
time.hour <= 5 ? "Dawn" :
time.hour >= 6 && (time.hour <= 8 && time.min <= 59) ? "Morning" :
time.hour >= 9 && (time.hour <= 11 && time.min <= 59) ? "Late Morning" :
time.hour == 12 && time.min <= 29 ? "Midday" :
time.hour >= 12 && time.hour <= 16 ? "Afternoon" :
time.hour > 16 && time.hour <= 17 ? "Late Afternoon" :
(time.hour >= 17 && time.min <= 1) || (time.hour <= 18 && time.min <= 20) ? "Early Evening" :
time.hour >= 18 && time.hour <= 19 ? "Dusk" :
time.hour > 19 && time.hour <= 21 ? "Late Evening" :
time.hour > 21 ? "Night" : "Midnight"}"))
(defwidget _profile [path size ?tooltip ?button-class ?image-class ?M ?L ?R]
(button :onmiddleclick M
:onclick L
:onrightclick R
:timeout "2s"
:tooltip tooltip
:class "vertigo-button ${button-class}"
(image :path path
:image-width size
:class "vertigo-image ${image-class}")))
(defwidget _infobatnolbl [battery status one two three four five six seven charge ?class]
(box :class "lumin-battery-box ${class}"
:space-evenly false
:spacing 8
(label :class "lumin-battery-icon ${class}" :text {status == 'Charging' ? charge :
battery < 15 ? seven :
battery < 30 ? six :
battery < 45 ? five :
battery < 60 ? four :
battery < 75 ? three :
battery < 95 ? two : one})))
(defwidget _infonetnolbl [strength offline excellent good okay slow ?class]
(box :class "lumin-network-box ${class}"
:space-evenly false
:spacing 8
(label :class "lumin-network-icon ${class}" :text {strength == "" ? offline :
strength < 26 ? slow :
strength < 51 ? okay :
strength < 76 ? good : excellent})))
; vim:filetype=yuck

View File

@@ -1,38 +0,0 @@
(deflisten music :initial '{"title": "", "status": "󰐍"}' "./panel/music.sh")
(deflisten music_cover "./panel/music.sh cover")
(defpoll volume :interval "1s" "pamixer --get-volume")
(defpoll is_mute :interval "1s" "pamixer --get-mute")
(defwidget dmusic []
(box :vexpand true
:hexpand true
:class "disclose-music-box"
:style "background-image: linear-gradient(to bottom left, rgba(0, 0, 0, 0.7), rgba(30, 33, 40, 0.2)), url(\"${music_cover}\");"
:space-evenly false
(box :class "disclose-dnd-labels"
:hexpand true
:orientation "vertical"
:space-evenly false
(label :halign "start"
:wrap true
:class "disclose-dnd-header"
:text {music.title == '' ? "No music" : music.title})
(label :halign "start"
:valign "start"
:wrap true
:class "disclose-dnd-footer"
:vexpand true
:text {music.artist == 'null' ? "" : music.artist})
(box :valign "end" :space-evenly false :class "disclose-dnd-waiting-toggle"
(button :onclick "action"
:hexpand true
:halign "start"
:valign "end"
:class "play-status accent-txt" {music.status})
(box :space-evenly false :class "volume-container"
(label :class "volume-icon"
:text {is_mute == 'true' ? "婢" : "墳"})
(label :class "volume-text" :text "${volume}%"))))))
;; vim:filetype=yuck

View File

@@ -1,64 +0,0 @@
; TODO: Update this value when pressing the button.
(defpoll bright :interval '1s' "brightnessctl -m | cut -d , -f 4 | head -c -2")
(defpoll ssid :interval "1s" `nmcli -terse -fields SSID,ACTIVE device wifi | awk --field-separator ':' '{if($2=="yes")print$1}'`)
(defpoll bluethooth :interval '1s' "echo info | timeout 1 bluetoothctl | grep 'Name'")
(defpoll theme :interval '1s' "cat ~/.local/state/theme")
(defwidget stat_card [icon name percent value colorClass ?click]
(button
:click click
(box
:space-evenly false
:class "disclose-stats-box"
(circular-progress :value percent
:thickness 10
:class "${colorClass}-txt"
(label :halign "center" :class "stats-label ${colorClass}-txt" :text icon))
(label :class "stats-separator" :text "")
(box :hexpand true :halign "center" :orientation "vertical" :class "disclose-stats-info-box"
(label :halign "center" :class "info-value ${colorClass}-txt" :text value)
(label :halign "start" :class "info-label" :text name)))))
(defwidget dstats []
(box :space-evenly false
:orientation "vertical"
:class "disclose-stats"
:spacing 10
(stat_card
:icon ""
:name "CPU"
:percent 0
:value {round(EWW_CPU.avg, 2)}
:colorClass "red")
(stat_card
:icon ""
:name "Memory"
:percent {EWW_RAM.used_mem_perc}
:value "${round(EWW_RAM.used_mem_perc, 0)}%"
:colorClass "green")
(stat_card
:icon {theme == "light" ? "󰃞" : "󰃝"}
:name "Brightness"
:percent bright
:value "${bright}%"
:colorClass "blue"
:click "settheme")))
(defwidget wifi []
(box
:class "module"
:spacing 16
:style "margin: 8px 16px 16px 16px"
(button
:class "accent disclose-big-button"
:click "nm-connection-editor"
"直 : ${ssid != "" ? ssid : "Disconnected"}")
(button
:class "secondary disclose-big-button"
:onclick "blueberry"
" : ${bluethooth != "" ? bluethooth : 'Disconnected'}")))
;; vim:filetype=yuck

View File

@@ -1,85 +0,0 @@
(defwidget _cardimage [
summary
body
?limit_summary
?limit_body
appname
timestamp
urgency
icon
icon_width
icon_height
close
?close_action
?style
?class
image
image_width
image_height
]
(eventbox :class "disclose-cardimage-eventbox disclose-cardimage-eventbox-${urgency} disclose-cardimage-eventbox-${appname}"
(box :orientation "vertical"
:space-evenly false
:class "disclose-cardimage-container-box disclose-cardimage-container-box-${urgency} disclose-cardimage-container-box-${appname}"
(box :class "disclose-cardimage-summary-box disclose-cardimage-summary-box-${urgency} disclose-cardimage-summary-box-${appname}"
:space-evenly false
:spacing 6
(box :style "background-image: url('${icon}')"
:width icon_width
:height icon_height
:space-evenly false
:class "disclose-cardimage-icon disclose-cardimage-icon-${urgency} disclose-cardimage-icon-${appname}")
(label :text appname
:hexpand true
:halign "start"
:class "disclose-cardimage-appname-label disclose-cardimage-appname-label-${urgency} disclose-cardimage-appname-label-${appname}")
(button :class "disclose-cardimage-close-button disclose-cardimage-close-button-${urgency} disclose-cardimage-close-button-${appname}"
:onclick close_action
:timeout "2s"
(label :text close
:class "disclose-cardimage-close-icon disclose-cardimage-close-icon-${urgency} disclose-cardimage-close-icon-${appname}")))
(box :class "disclose-cardimage-separator disclose-cardimage-separator-${urgency} disclose-cardimage-separator-${appname}"
:space-evenly false)
(box :class "disclose-cardimage-body-box disclose-cardimage-body-box-${urgency} disclose-cardimage-body-box-${appname}"
:space-evenly false
(box :halign "center"
:valign "center"
:class "disclose-cardimage-image-box disclose-cardimage-image-box-${urgency} disclose-cardimage-image-box-${appname}"
(box :style "background-image: url('${image}');${style}"
:hexpand false
:vexpand false
:width image_width
:height image_height
:space-evenly false
:class "disclose-cardimage-image disclose-cardimage-image-${urgency} disclose-cardimage-image-${appname} ${class}"))
(box :hexpand true
:vexpand true
:valign "center"
:orientation "vertical"
:spacing 5
:class "disclose-cardimage-body-outer disclose-cardimage-body-outer-${urgency} disclose-cardimage-body-outer-${appname}"
:space-evenly false
(label :text summary
:limit-width {limit_summary != "" ? limit_summary : 25}
:halign "start"
:class "disclose-cardimage-summary-label disclose-cardimage-summary-label-${urgency} disclose-cardimage-summary-label-${appname}")
(label :text body
:halign "start"
:limit-width {limit_body != "" ? limit_body : 110}
:xalign 0.0
:wrap true
:class "disclose-cardimage-body-label disclose-cardimage-body-${urgency} disclose-cardimage-body-${appname}")
(label :text timestamp
:halign "end"
:class "disclose-cardimage-timestamp disclose-cardimage-timestamp-${urgency} disclose-cardimage-timestamp-${appname}"))))))
;; vim:ft=yuck

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