simple ags launcher
This commit is contained in:
parent
e2bd5335d8
commit
1d4189b040
186 changed files with 24097 additions and 335 deletions
27
homes/me/ags-end4/modules/.commondata/hyprlanddata.js
Normal file
27
homes/me/ags-end4/modules/.commondata/hyprlanddata.js
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
const { Gdk } = imports.gi;
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
const { execAsync, exec } = Utils;
|
||||
|
||||
export let monitors;
|
||||
|
||||
// Mixes with Gdk monitor size cuz it reports monitor size scaled
|
||||
async function updateStuff() {
|
||||
monitors = JSON.parse(exec('hyprctl monitors -j'))
|
||||
const display = Gdk.Display.get_default();
|
||||
monitors.forEach((monitor, i) => {
|
||||
const gdkMonitor = display.get_monitor(i);
|
||||
monitor.realWidth = monitor.width;
|
||||
monitor.realHeight = monitor.height;
|
||||
if (userOptions.monitors.scaleMethod.toLowerCase == "gdk") {
|
||||
monitor.width = gdkMonitor.get_geometry().width;
|
||||
monitor.height = gdkMonitor.get_geometry().height;
|
||||
}
|
||||
else { // == "division"
|
||||
monitor.width = Math.ceil(monitor.realWidth / monitor.scale);
|
||||
monitor.height = Math.ceil(monitor.realHeight / monitor.scale);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
updateStuff().catch(print);
|
||||
|
||||
14
homes/me/ags-end4/modules/.commondata/quotes.js
Normal file
14
homes/me/ags-end4/modules/.commondata/quotes.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
export const quotes = [
|
||||
{
|
||||
quote: 'Nvidia, fuck you',
|
||||
author: 'Linus Torvalds',
|
||||
},
|
||||
{
|
||||
quote: 'reproducible system? cock and vagina?',
|
||||
author: 'vaxry',
|
||||
},
|
||||
{
|
||||
quote: "haha pointers hee hee i love pointe-\\\nProcess Vaxry exited with signal SIGSEGV",
|
||||
author: 'vaxry',
|
||||
}
|
||||
];
|
||||
94
homes/me/ags-end4/modules/.commondata/weather.js
Normal file
94
homes/me/ags-end4/modules/.commondata/weather.js
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
export const WWO_CODE = {
|
||||
"113": "Sunny",
|
||||
"116": "PartlyCloudy",
|
||||
"119": "Cloudy",
|
||||
"122": "VeryCloudy",
|
||||
"143": "Fog",
|
||||
"176": "LightShowers",
|
||||
"179": "LightSleetShowers",
|
||||
"182": "LightSleet",
|
||||
"185": "LightSleet",
|
||||
"200": "ThunderyShowers",
|
||||
"227": "LightSnow",
|
||||
"230": "HeavySnow",
|
||||
"248": "Fog",
|
||||
"260": "Fog",
|
||||
"263": "LightShowers",
|
||||
"266": "LightRain",
|
||||
"281": "LightSleet",
|
||||
"284": "LightSleet",
|
||||
"293": "LightRain",
|
||||
"296": "LightRain",
|
||||
"299": "HeavyShowers",
|
||||
"302": "HeavyRain",
|
||||
"305": "HeavyShowers",
|
||||
"308": "HeavyRain",
|
||||
"311": "LightSleet",
|
||||
"314": "LightSleet",
|
||||
"317": "LightSleet",
|
||||
"320": "LightSnow",
|
||||
"323": "LightSnowShowers",
|
||||
"326": "LightSnowShowers",
|
||||
"329": "HeavySnow",
|
||||
"332": "HeavySnow",
|
||||
"335": "HeavySnowShowers",
|
||||
"338": "HeavySnow",
|
||||
"350": "LightSleet",
|
||||
"353": "LightShowers",
|
||||
"356": "HeavyShowers",
|
||||
"359": "HeavyRain",
|
||||
"362": "LightSleetShowers",
|
||||
"365": "LightSleetShowers",
|
||||
"368": "LightSnowShowers",
|
||||
"371": "HeavySnowShowers",
|
||||
"374": "LightSleetShowers",
|
||||
"377": "LightSleet",
|
||||
"386": "ThunderyShowers",
|
||||
"389": "ThunderyHeavyRain",
|
||||
"392": "ThunderySnowShowers",
|
||||
"395": "HeavySnowShowers",
|
||||
}
|
||||
|
||||
export const WEATHER_SYMBOL = {
|
||||
"Unknown": "air",
|
||||
"Cloudy": "cloud",
|
||||
"Fog": "foggy",
|
||||
"HeavyRain": "rainy",
|
||||
"HeavyShowers": "rainy",
|
||||
"HeavySnow": "snowing",
|
||||
"HeavySnowShowers": "snowing",
|
||||
"LightRain": "rainy",
|
||||
"LightShowers": "rainy",
|
||||
"LightSleet": "rainy",
|
||||
"LightSleetShowers": "rainy",
|
||||
"LightSnow": "cloudy_snowing",
|
||||
"LightSnowShowers": "cloudy_snowing",
|
||||
"PartlyCloudy": "partly_cloudy_day",
|
||||
"Sunny": "clear_day",
|
||||
"ThunderyHeavyRain": "thunderstorm",
|
||||
"ThunderyShowers": "thunderstorm",
|
||||
"ThunderySnowShowers": "thunderstorm",
|
||||
"VeryCloudy": "cloud",
|
||||
}
|
||||
|
||||
export const NIGHT_WEATHER_SYMBOL = {
|
||||
"Unknown": "air",
|
||||
"Cloudy": "cloud",
|
||||
"Fog": "foggy",
|
||||
"HeavyRain": "rainy",
|
||||
"HeavyShowers": "rainy",
|
||||
"HeavySnow": "snowing",
|
||||
"HeavySnowShowers": "snowing",
|
||||
"LightRain": "rainy",
|
||||
"LightShowers": "rainy",
|
||||
"LightSleet": "rainy",
|
||||
"LightSleetShowers": "rainy",
|
||||
"LightSnow": "cloudy_snowing",
|
||||
"LightSnowShowers": "cloudy_snowing",
|
||||
"PartlyCloudy": "partly_cloudy_night",
|
||||
"Sunny": "clear_night",
|
||||
"ThunderyHeavyRain": "thunderstorm",
|
||||
"ThunderyShowers": "thunderstorm",
|
||||
"ThunderySnowShowers": "thunderstorm",
|
||||
"VeryCloudy": "cloud",
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
const { Gtk } = imports.gi;
|
||||
const Lang = imports.lang;
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js'
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'
|
||||
|
||||
// -- Styling --
|
||||
// min-height for diameter
|
||||
// min-width for trough stroke
|
||||
// padding for space between trough and progress
|
||||
// margin for space between widget and parent
|
||||
// background-color for trough color
|
||||
// color for progress color
|
||||
// -- Usage --
|
||||
// font size for progress value (0-100px) (hacky i know, but i want animations)
|
||||
export const AnimatedCircProg = ({
|
||||
initFrom = 0,
|
||||
initTo = 0,
|
||||
initAnimTime = 2900,
|
||||
initAnimPoints = 1,
|
||||
extraSetup = () => { },
|
||||
...rest
|
||||
}) => Widget.DrawingArea({
|
||||
...rest,
|
||||
css: `${initFrom != initTo ? 'font-size: ' + initFrom + 'px; transition: ' + initAnimTime + 'ms linear;' : ''}`,
|
||||
setup: (area) => {
|
||||
const styleContext = area.get_style_context();
|
||||
const width = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
|
||||
const height = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
|
||||
const padding = styleContext.get_padding(Gtk.StateFlags.NORMAL).left;
|
||||
const marginLeft = styleContext.get_margin(Gtk.StateFlags.NORMAL).left;
|
||||
const marginRight = styleContext.get_margin(Gtk.StateFlags.NORMAL).right;
|
||||
const marginTop = styleContext.get_margin(Gtk.StateFlags.NORMAL).top;
|
||||
const marginBottom = styleContext.get_margin(Gtk.StateFlags.NORMAL).bottom;
|
||||
area.set_size_request(width + marginLeft + marginRight, height + marginTop + marginBottom);
|
||||
area.connect('draw', Lang.bind(area, (area, cr) => {
|
||||
const styleContext = area.get_style_context();
|
||||
const width = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
|
||||
const height = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
|
||||
const padding = styleContext.get_padding(Gtk.StateFlags.NORMAL).left;
|
||||
const marginLeft = styleContext.get_margin(Gtk.StateFlags.NORMAL).left;
|
||||
const marginRight = styleContext.get_margin(Gtk.StateFlags.NORMAL).right;
|
||||
const marginTop = styleContext.get_margin(Gtk.StateFlags.NORMAL).top;
|
||||
const marginBottom = styleContext.get_margin(Gtk.StateFlags.NORMAL).bottom;
|
||||
area.set_size_request(width + marginLeft + marginRight, height + marginTop + marginBottom);
|
||||
|
||||
const progressValue = styleContext.get_property('font-size', Gtk.StateFlags.NORMAL) / 100.0;
|
||||
|
||||
const bg_stroke = styleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
|
||||
const fg_stroke = bg_stroke - padding;
|
||||
const radius = Math.min(width, height) / 2.0 - Math.max(bg_stroke, fg_stroke) / 2.0;
|
||||
const center_x = width / 2.0 + marginLeft;
|
||||
const center_y = height / 2.0 + marginTop;
|
||||
const start_angle = -Math.PI / 2.0;
|
||||
const end_angle = start_angle + (2 * Math.PI * progressValue);
|
||||
const start_x = center_x + Math.cos(start_angle) * radius;
|
||||
const start_y = center_y + Math.sin(start_angle) * radius;
|
||||
const end_x = center_x + Math.cos(end_angle) * radius;
|
||||
const end_y = center_y + Math.sin(end_angle) * radius;
|
||||
|
||||
// Draw background
|
||||
const background_color = styleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
|
||||
cr.setSourceRGBA(background_color.red, background_color.green, background_color.blue, background_color.alpha);
|
||||
cr.arc(center_x, center_y, radius, 0, 2 * Math.PI);
|
||||
cr.setLineWidth(bg_stroke);
|
||||
cr.stroke();
|
||||
|
||||
if (progressValue == 0) return;
|
||||
|
||||
// Draw progress
|
||||
const color = styleContext.get_property('color', Gtk.StateFlags.NORMAL);
|
||||
cr.setSourceRGBA(color.red, color.green, color.blue, color.alpha);
|
||||
cr.arc(center_x, center_y, radius, start_angle, end_angle);
|
||||
cr.setLineWidth(fg_stroke);
|
||||
cr.stroke();
|
||||
|
||||
// Draw rounded ends for progress arcs
|
||||
cr.setLineWidth(0);
|
||||
cr.arc(start_x, start_y, fg_stroke / 2, 0, 0 - 0.01);
|
||||
cr.fill();
|
||||
cr.arc(end_x, end_y, fg_stroke / 2, 0, 0 - 0.01);
|
||||
cr.fill();
|
||||
}));
|
||||
|
||||
// Init animation
|
||||
if (initFrom != initTo) {
|
||||
area.css = `font-size: ${initFrom}px; transition: ${initAnimTime}ms linear;`;
|
||||
Utils.timeout(20, () => {
|
||||
area.css = `font-size: ${initTo}px;`;
|
||||
}, area)
|
||||
const transitionDistance = initTo - initFrom;
|
||||
const oneStep = initAnimTime / initAnimPoints;
|
||||
area.css = `
|
||||
font-size: ${initFrom}px;
|
||||
transition: ${oneStep}ms linear;
|
||||
`;
|
||||
for (let i = 0; i < initAnimPoints; i++) {
|
||||
Utils.timeout(Math.max(10, i * oneStep), () => {
|
||||
if(!area) return;
|
||||
area.css = `${initFrom != initTo ? 'font-size: ' + (initFrom + (transitionDistance / initAnimPoints * (i + 1))) + 'px;' : ''}`;
|
||||
});
|
||||
}
|
||||
}
|
||||
else area.css = 'font-size: 0px;';
|
||||
extraSetup(area);
|
||||
},
|
||||
})
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
const { Gtk } = imports.gi;
|
||||
const Lang = imports.lang;
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
|
||||
// min-height/min-width for height/width
|
||||
// background-color/color for background/indicator color
|
||||
// padding for pad of indicator
|
||||
// font-size for selected index (0-based)
|
||||
export const NavigationIndicator = ({count, vertical, ...props}) => Widget.DrawingArea({
|
||||
...props,
|
||||
setup: (area) => {
|
||||
const styleContext = area.get_style_context();
|
||||
const width = Math.max(styleContext.get_property('min-width', Gtk.StateFlags.NORMAL), area.get_allocated_width());
|
||||
const height = Math.max(styleContext.get_property('min-height', Gtk.StateFlags.NORMAL), area.get_allocated_height());
|
||||
area.set_size_request(width, height);
|
||||
|
||||
area.connect('draw', Lang.bind(area, (area, cr) => {
|
||||
const styleContext = area.get_style_context();
|
||||
const width = Math.max(styleContext.get_property('min-width', Gtk.StateFlags.NORMAL), area.get_allocated_width());
|
||||
const height = Math.max(styleContext.get_property('min-height', Gtk.StateFlags.NORMAL), area.get_allocated_height());
|
||||
// console.log('allocated width/height:', area.get_allocated_width(), '/', area.get_allocated_height())
|
||||
area.set_size_request(width, height);
|
||||
const paddingLeft = styleContext.get_padding(Gtk.StateFlags.NORMAL).left;
|
||||
const paddingRight = styleContext.get_padding(Gtk.StateFlags.NORMAL).right;
|
||||
const paddingTop = styleContext.get_padding(Gtk.StateFlags.NORMAL).top;
|
||||
const paddingBottom = styleContext.get_padding(Gtk.StateFlags.NORMAL).bottom;
|
||||
|
||||
const selectedCell = styleContext.get_property('font-size', Gtk.StateFlags.NORMAL);
|
||||
|
||||
let cellWidth = width;
|
||||
let cellHeight = height;
|
||||
if (vertical) cellHeight /= count;
|
||||
else cellWidth /= count;
|
||||
const indicatorWidth = cellWidth - paddingLeft - paddingRight;
|
||||
const indicatorHeight = cellHeight - paddingTop - paddingBottom;
|
||||
|
||||
const background_color = styleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
|
||||
const color = styleContext.get_property('color', Gtk.StateFlags.NORMAL);
|
||||
cr.setLineWidth(2);
|
||||
// Background
|
||||
cr.setSourceRGBA(background_color.red, background_color.green, background_color.blue, background_color.alpha);
|
||||
cr.rectangle(0, 0, width, height);
|
||||
cr.fill();
|
||||
|
||||
// The indicator line
|
||||
cr.setSourceRGBA(color.red, color.green, color.blue, color.alpha);
|
||||
if (vertical) {
|
||||
cr.rectangle(paddingLeft, paddingTop + cellHeight * selectedCell + indicatorWidth / 2, indicatorWidth, indicatorHeight - indicatorWidth);
|
||||
cr.stroke();
|
||||
cr.rectangle(paddingLeft, paddingTop + cellHeight * selectedCell + indicatorWidth / 2, indicatorWidth, indicatorHeight - indicatorWidth);
|
||||
cr.fill();
|
||||
cr.arc(paddingLeft + indicatorWidth / 2, paddingTop + cellHeight * selectedCell + indicatorWidth / 2, indicatorWidth / 2, Math.PI, 2 * Math.PI);
|
||||
cr.fill();
|
||||
cr.arc(paddingLeft + indicatorWidth / 2, paddingTop + cellHeight * selectedCell + indicatorHeight - indicatorWidth / 2, indicatorWidth / 2, 0, Math.PI);
|
||||
cr.fill();
|
||||
}
|
||||
else {
|
||||
cr.rectangle(paddingLeft + cellWidth * selectedCell + indicatorHeight / 2, paddingTop, indicatorWidth - indicatorHeight, indicatorHeight);
|
||||
cr.stroke();
|
||||
cr.rectangle(paddingLeft + cellWidth * selectedCell + indicatorHeight / 2, paddingTop, indicatorWidth - indicatorHeight, indicatorHeight);
|
||||
cr.fill();
|
||||
cr.arc(paddingLeft + cellWidth * selectedCell + indicatorHeight / 2, paddingTop + indicatorHeight / 2, indicatorHeight / 2, 0.5 * Math.PI, 1.5 * Math.PI);
|
||||
cr.fill();
|
||||
cr.arc(paddingLeft + cellWidth * selectedCell + indicatorWidth - indicatorHeight / 2, paddingTop + indicatorHeight / 2, indicatorHeight / 2, -0.5 * Math.PI, 0.5 * Math.PI);
|
||||
cr.fill();
|
||||
}
|
||||
}))
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
const { Gtk } = imports.gi;
|
||||
const Lang = imports.lang;
|
||||
|
||||
export const RoundedCorner = (place, props) => Widget.DrawingArea({
|
||||
...props,
|
||||
hpack: place.includes('left') ? 'start' : 'end',
|
||||
vpack: place.includes('top') ? 'start' : 'end',
|
||||
setup: (widget) => Utils.timeout(1, () => {
|
||||
const c = widget.get_style_context().get_property('background-color', Gtk.StateFlags.NORMAL);
|
||||
const r = widget.get_style_context().get_property('border-radius', Gtk.StateFlags.NORMAL);
|
||||
widget.set_size_request(r, r);
|
||||
widget.connect('draw', Lang.bind(widget, (widget, cr) => {
|
||||
const c = widget.get_style_context().get_property('background-color', Gtk.StateFlags.NORMAL);
|
||||
const r = widget.get_style_context().get_property('border-radius', Gtk.StateFlags.NORMAL);
|
||||
// const borderColor = widget.get_style_context().get_property('color', Gtk.StateFlags.NORMAL);
|
||||
// const borderWidth = widget.get_style_context().get_border(Gtk.StateFlags.NORMAL).left; // ur going to write border-width: something anyway
|
||||
widget.set_size_request(r, r);
|
||||
|
||||
switch (place) {
|
||||
case 'topleft':
|
||||
cr.arc(r, r, r, Math.PI, 3 * Math.PI / 2);
|
||||
cr.lineTo(0, 0);
|
||||
break;
|
||||
|
||||
case 'topright':
|
||||
cr.arc(0, r, r, 3 * Math.PI / 2, 2 * Math.PI);
|
||||
cr.lineTo(r, 0);
|
||||
break;
|
||||
|
||||
case 'bottomleft':
|
||||
cr.arc(r, 0, r, Math.PI / 2, Math.PI);
|
||||
cr.lineTo(0, r);
|
||||
break;
|
||||
|
||||
case 'bottomright':
|
||||
cr.arc(0, 0, r, 0, Math.PI / 2);
|
||||
cr.lineTo(r, r);
|
||||
break;
|
||||
}
|
||||
|
||||
cr.closePath();
|
||||
cr.setSourceRGBA(c.red, c.green, c.blue, c.alpha);
|
||||
cr.fill();
|
||||
// cr.setLineWidth(borderWidth);
|
||||
// cr.setSourceRGBA(borderColor.red, borderColor.green, borderColor.blue, borderColor.alpha);
|
||||
// cr.stroke();
|
||||
}));
|
||||
}),
|
||||
});
|
||||
49
homes/me/ags-end4/modules/.commonwidgets/cairo_slider.js
Normal file
49
homes/me/ags-end4/modules/.commonwidgets/cairo_slider.js
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
const { Gtk } = imports.gi;
|
||||
const Lang = imports.lang;
|
||||
|
||||
export const AnimatedSlider = ({
|
||||
className,
|
||||
value,
|
||||
...rest
|
||||
}) => {
|
||||
return Widget.DrawingArea({
|
||||
className: `${className}`,
|
||||
setup: (self) => {
|
||||
self.connect('draw', Lang.bind(self, (self, cr) => {
|
||||
const styleContext = self.get_style_context();
|
||||
const allocatedWidth = self.get_allocated_width();
|
||||
const allocatedHeight = self.get_allocated_height();
|
||||
console.log(allocatedHeight, allocatedWidth)
|
||||
const minWidth = styleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
|
||||
const minHeight = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
|
||||
const radius = styleContext.get_property('border-radius', Gtk.StateFlags.NORMAL);
|
||||
const bg = styleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
|
||||
const fg = styleContext.get_property('color', Gtk.StateFlags.NORMAL);
|
||||
const value = styleContext.get_property('font-size', Gtk.StateFlags.NORMAL) / 100;
|
||||
self.set_size_request(-1, minHeight);
|
||||
const width = allocatedHeight;
|
||||
const height = minHeight;
|
||||
|
||||
cr.arc(radius, radius, radius, -1 * Math.PI, -0.5 * Math.PI); // Top-left
|
||||
cr.arc(width - radius, radius, radius, -0.5 * Math.PI, 0); // Top-right
|
||||
cr.arc(width - radius, height - radius, radius, 0, 0.5 * Math.PI); // Bottom-left
|
||||
cr.arc(radius, height - radius, radius, 0.5 * Math.PI, 1 * Math.PI); // Bottom-right
|
||||
cr.setSourceRGBA(bg.red, bg.green, bg.blue, bg.alpha);
|
||||
cr.closePath();
|
||||
cr.fill();
|
||||
|
||||
// const valueWidth = width * value;
|
||||
// cr.arc(radius, radius, radius, -1 * Math.PI, -0.5 * Math.PI); // Top-left
|
||||
// cr.arc(valueWidth - radius, radius, radius, -0.5 * Math.PI, 0); // Top-right
|
||||
// cr.arc(valueWidth - radius, height - radius, radius, 0, 0.5 * Math.PI); // Bottom-left
|
||||
// cr.arc(radius, height - radius, radius, 0.5 * Math.PI, 1 * Math.PI); // Bottom-right
|
||||
// cr.setSourceRGBA(fg.red, fg.green, fg.blue, fg.alpha);
|
||||
// cr.closePath();
|
||||
// cr.fill();
|
||||
|
||||
}));
|
||||
},
|
||||
...rest,
|
||||
})
|
||||
}
|
||||
23
homes/me/ags-end4/modules/.commonwidgets/clickcloseregion.js
Normal file
23
homes/me/ags-end4/modules/.commonwidgets/clickcloseregion.js
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import App from 'resource:///com/github/Aylur/ags/app.js';
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import { monitors } from '../.commondata/hyprlanddata.js';
|
||||
const { Box, EventBox } = Widget;
|
||||
|
||||
export const clickCloseRegion = ({ name, multimonitor = true, monitor = 0, expand = true, fillMonitor = '' }) => {
|
||||
return EventBox({
|
||||
child: Box({
|
||||
expand: expand,
|
||||
css: `
|
||||
min-width: ${fillMonitor.includes('h') ? monitors[monitor].width : 0}px;
|
||||
min-height: ${fillMonitor.includes('v') ? monitors[monitor].height : 0}px;
|
||||
`,
|
||||
}),
|
||||
setup: (self) => self.on('button-press-event', (self, event) => { // Any mouse button
|
||||
if (multimonitor) closeWindowOnAllMonitors(name);
|
||||
else App.closeWindow(name);
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
export default clickCloseRegion;
|
||||
|
||||
219
homes/me/ags-end4/modules/.commonwidgets/configwidgets.js
Normal file
219
homes/me/ags-end4/modules/.commonwidgets/configwidgets.js
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
import Variable from 'resource:///com/github/Aylur/ags/variable.js';
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
import { MaterialIcon } from './materialicon.js';
|
||||
import { setupCursorHover } from '../.widgetutils/cursorhover.js';
|
||||
const { Box, Button, Label, Revealer, SpinButton } = Widget;
|
||||
|
||||
export const ConfigToggle = ({
|
||||
icon, name, desc = '', initValue,
|
||||
expandWidget = true,
|
||||
onChange = () => { }, extraSetup = () => { },
|
||||
...rest
|
||||
}) => {
|
||||
const enabled = Variable(initValue);
|
||||
const toggleIcon = Label({
|
||||
className: `icon-material txt-bold ${enabled.value ? '' : 'txt-poof'}`,
|
||||
label: `${enabled.value ? 'check' : ''}`,
|
||||
setup: (self) => self.hook(enabled, (self) => {
|
||||
self.toggleClassName('switch-fg-toggling-false', false);
|
||||
if (!enabled.value) {
|
||||
self.label = '';
|
||||
self.toggleClassName('txt-poof', true);
|
||||
}
|
||||
else Utils.timeout(1, () => {
|
||||
toggleIcon.label = 'check';
|
||||
toggleIcon.toggleClassName('txt-poof', false);
|
||||
})
|
||||
}),
|
||||
})
|
||||
const toggleButtonIndicator = Box({
|
||||
className: `switch-fg ${enabled.value ? 'switch-fg-true' : ''}`,
|
||||
vpack: 'center',
|
||||
hpack: 'start',
|
||||
homogeneous: true,
|
||||
children: [toggleIcon,],
|
||||
setup: (self) => self.hook(enabled, (self) => {
|
||||
self.toggleClassName('switch-fg-true', enabled.value);
|
||||
}),
|
||||
});
|
||||
const toggleButton = Box({
|
||||
hpack: 'end',
|
||||
className: `switch-bg ${enabled.value ? 'switch-bg-true' : ''}`,
|
||||
homogeneous: true,
|
||||
children: [toggleButtonIndicator],
|
||||
setup: (self) => self.hook(enabled, (self) => {
|
||||
self.toggleClassName('switch-bg-true', enabled.value);
|
||||
}),
|
||||
});
|
||||
const widgetContent = Box({
|
||||
tooltipText: desc,
|
||||
className: 'txt spacing-h-5 configtoggle-box',
|
||||
children: [
|
||||
...(icon !== undefined ? [MaterialIcon(icon, 'norm')] : []),
|
||||
...(name !== undefined ? [Label({
|
||||
className: 'txt txt-small',
|
||||
label: name,
|
||||
})] : []),
|
||||
...(expandWidget ? [Box({ hexpand: true })] : []),
|
||||
toggleButton,
|
||||
]
|
||||
});
|
||||
const interactionWrapper = Button({
|
||||
attribute: {
|
||||
enabled: enabled,
|
||||
toggle: (newValue) => {
|
||||
enabled.value = !enabled.value;
|
||||
onChange(interactionWrapper, enabled.value);
|
||||
}
|
||||
},
|
||||
child: widgetContent,
|
||||
onClicked: (self) => self.attribute.toggle(self),
|
||||
setup: (self) => {
|
||||
setupCursorHover(self);
|
||||
self.connect('pressed', () => { // mouse down
|
||||
toggleIcon.toggleClassName('txt-poof', true);
|
||||
toggleIcon.toggleClassName('switch-fg-true', false);
|
||||
if (!enabled.value) toggleIcon.toggleClassName('switch-fg-toggling-false', true);
|
||||
});
|
||||
extraSetup(self)
|
||||
},
|
||||
...rest,
|
||||
});
|
||||
interactionWrapper.enabled = enabled;
|
||||
return interactionWrapper;
|
||||
}
|
||||
|
||||
export const ConfigSegmentedSelection = ({
|
||||
icon, name, desc = '',
|
||||
options = [{ name: 'Option 1', value: 0 }, { name: 'Option 2', value: 1 }],
|
||||
initIndex = 0,
|
||||
onChange,
|
||||
...rest
|
||||
}) => {
|
||||
let lastSelected = initIndex;
|
||||
let value = options[initIndex].value;
|
||||
const widget = Box({
|
||||
tooltipText: desc,
|
||||
className: 'segment-container',
|
||||
// homogeneous: true,
|
||||
children: options.map((option, id) => {
|
||||
const selectedIcon = Revealer({
|
||||
revealChild: id == initIndex,
|
||||
transition: 'slide_right',
|
||||
transitionDuration: userOptions.animations.durationSmall,
|
||||
child: MaterialIcon('check', 'norm')
|
||||
});
|
||||
return Button({
|
||||
setup: setupCursorHover,
|
||||
className: `segment-btn ${id == initIndex ? 'segment-btn-enabled' : ''}`,
|
||||
child: Box({
|
||||
hpack: 'center',
|
||||
className: 'spacing-h-5',
|
||||
children: [
|
||||
selectedIcon,
|
||||
Label({
|
||||
label: option.name,
|
||||
})
|
||||
]
|
||||
}),
|
||||
onClicked: (self) => {
|
||||
value = option.value;
|
||||
const kids = widget.get_children();
|
||||
kids[lastSelected].toggleClassName('segment-btn-enabled', false);
|
||||
kids[lastSelected].get_children()[0].get_children()[0].revealChild = false;
|
||||
lastSelected = id;
|
||||
self.toggleClassName('segment-btn-enabled', true);
|
||||
selectedIcon.revealChild = true;
|
||||
onChange(option.value, option.name);
|
||||
}
|
||||
})
|
||||
}),
|
||||
...rest,
|
||||
});
|
||||
return widget;
|
||||
|
||||
}
|
||||
|
||||
export const ConfigMulipleSelection = ({
|
||||
icon, name, desc = '',
|
||||
optionsArr = [
|
||||
[{ name: 'Option 1', value: 0 }, { name: 'Option 2', value: 1 }],
|
||||
[{ name: 'Option 3', value: 0 }, { name: 'Option 4', value: 1 }],
|
||||
],
|
||||
initIndex = [0, 0],
|
||||
onChange,
|
||||
...rest
|
||||
}) => {
|
||||
let lastSelected = initIndex;
|
||||
const widget = Box({
|
||||
tooltipText: desc,
|
||||
className: 'multipleselection-container spacing-v-3',
|
||||
vertical: true,
|
||||
children: optionsArr.map((options, grp) => Box({
|
||||
className: 'spacing-h-5',
|
||||
hpack: 'center',
|
||||
children: options.map((option, id) => Button({
|
||||
setup: setupCursorHover,
|
||||
className: `multipleselection-btn ${id == initIndex[1] && grp == initIndex[0] ? 'multipleselection-btn-enabled' : ''}`,
|
||||
label: option.name,
|
||||
onClicked: (self) => {
|
||||
const kidsg = widget.get_children();
|
||||
const kids = kidsg.flatMap(widget => widget.get_children());
|
||||
kids.forEach(kid => {
|
||||
kid.toggleClassName('multipleselection-btn-enabled', false);
|
||||
});
|
||||
lastSelected = id;
|
||||
self.toggleClassName('multipleselection-btn-enabled', true);
|
||||
onChange(option.value, option.name);
|
||||
}
|
||||
})),
|
||||
})),
|
||||
...rest,
|
||||
});
|
||||
return widget;
|
||||
|
||||
}
|
||||
|
||||
export const ConfigGap = ({ vertical = true, size = 5, ...rest }) => Box({
|
||||
className: `gap-${vertical ? 'v' : 'h'}-${size}`,
|
||||
...rest,
|
||||
})
|
||||
|
||||
export const ConfigSpinButton = ({
|
||||
icon, name, desc = '', initValue,
|
||||
minValue = 0, maxValue = 100, step = 1,
|
||||
expandWidget = true,
|
||||
onChange = () => { }, extraSetup = () => { },
|
||||
...rest
|
||||
}) => {
|
||||
const value = Variable(initValue);
|
||||
const spinButton = SpinButton({
|
||||
className: 'spinbutton',
|
||||
range: [minValue, maxValue],
|
||||
increments: [step, step],
|
||||
onValueChanged: ({ value: newValue }) => {
|
||||
value.value = newValue;
|
||||
onChange(spinButton, newValue);
|
||||
},
|
||||
});
|
||||
spinButton.value = value.value;
|
||||
const widgetContent = Box({
|
||||
tooltipText: desc,
|
||||
className: 'txt spacing-h-5 configtoggle-box',
|
||||
children: [
|
||||
...(icon !== undefined ? [MaterialIcon(icon, 'norm')] : []),
|
||||
...(name !== undefined ? [Label({
|
||||
className: 'txt txt-small',
|
||||
label: name,
|
||||
})] : []),
|
||||
...(expandWidget ? [Box({ hexpand: true })] : []),
|
||||
spinButton,
|
||||
],
|
||||
setup: (self) => {
|
||||
extraSetup(self);
|
||||
},
|
||||
...rest,
|
||||
});
|
||||
return widgetContent;
|
||||
}
|
||||
7
homes/me/ags-end4/modules/.commonwidgets/materialicon.js
Normal file
7
homes/me/ags-end4/modules/.commonwidgets/materialicon.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
|
||||
export const MaterialIcon = (icon, size, props = {}) => Widget.Label({
|
||||
className: `icon-material txt-${size}`,
|
||||
label: icon,
|
||||
...props,
|
||||
})
|
||||
462
homes/me/ags-end4/modules/.commonwidgets/notification.js
Normal file
462
homes/me/ags-end4/modules/.commonwidgets/notification.js
Normal file
|
|
@ -0,0 +1,462 @@
|
|||
// This file is for the actual widget for each single notification
|
||||
const { GLib, Gdk, Gtk } = imports.gi;
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js'
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'
|
||||
const { Box, EventBox, Icon, Overlay, Label, Button, Revealer } = Widget;
|
||||
import { MaterialIcon } from './materialicon.js';
|
||||
import { setupCursorHover } from "../.widgetutils/cursorhover.js";
|
||||
import { AnimatedCircProg } from "./cairo_circularprogress.js";
|
||||
|
||||
function guessMessageType(summary) {
|
||||
const str = summary.toLowerCase();
|
||||
if (str.includes('reboot')) return 'restart_alt';
|
||||
if (str.includes('recording')) return 'screen_record';
|
||||
if (str.includes('battery') || summary.includes('power')) return 'power';
|
||||
if (str.includes('screenshot')) return 'screenshot_monitor';
|
||||
if (str.includes('welcome')) return 'waving_hand';
|
||||
if (str.includes('time')) return 'scheduleb';
|
||||
if (str.includes('installed')) return 'download';
|
||||
if (str.includes('update')) return 'update';
|
||||
if (str.startsWith('file')) return 'folder_copy';
|
||||
return 'chat';
|
||||
}
|
||||
|
||||
function exists(widget) {
|
||||
return widget !== null;
|
||||
}
|
||||
|
||||
const getFriendlyNotifTimeString = (timeObject) => {
|
||||
const messageTime = GLib.DateTime.new_from_unix_local(timeObject);
|
||||
const oneMinuteAgo = GLib.DateTime.new_now_local().add_seconds(-60);
|
||||
if (messageTime.compare(oneMinuteAgo) > 0)
|
||||
return getString('Now');
|
||||
else if (messageTime.get_day_of_year() == GLib.DateTime.new_now_local().get_day_of_year())
|
||||
return messageTime.format(userOptions.time.format);
|
||||
else if (messageTime.get_day_of_year() == GLib.DateTime.new_now_local().get_day_of_year() - 1)
|
||||
return getString('Yesterday');
|
||||
else
|
||||
return messageTime.format(userOptions.time.dateFormat);
|
||||
}
|
||||
|
||||
const NotificationIcon = (notifObject) => {
|
||||
// { appEntry, appIcon, image }, urgency = 'normal'
|
||||
if (notifObject.image) {
|
||||
return Box({
|
||||
valign: Gtk.Align.CENTER,
|
||||
hexpand: false,
|
||||
className: 'notif-icon',
|
||||
css: `
|
||||
background-image: url("${notifObject.image}");
|
||||
background-size: auto 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
`,
|
||||
});
|
||||
}
|
||||
|
||||
let icon = 'NO_ICON';
|
||||
if (Utils.lookUpIcon(notifObject.appIcon))
|
||||
icon = notifObject.appIcon;
|
||||
if (Utils.lookUpIcon(notifObject.appEntry))
|
||||
icon = notifObject.appEntry;
|
||||
|
||||
return Box({
|
||||
vpack: 'center',
|
||||
hexpand: false,
|
||||
className: `notif-icon notif-icon-material-${notifObject.urgency}`,
|
||||
homogeneous: true,
|
||||
children: [
|
||||
(icon != 'NO_ICON' ?
|
||||
Icon({
|
||||
vpack: 'center',
|
||||
icon: icon,
|
||||
})
|
||||
:
|
||||
MaterialIcon(`${notifObject.urgency == 'critical' ? 'release_alert' : guessMessageType(notifObject.summary.toLowerCase())}`, 'hugerass', {
|
||||
hexpand: true,
|
||||
})
|
||||
)
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
export default ({
|
||||
notifObject,
|
||||
isPopup = false,
|
||||
props = {},
|
||||
} = {}) => {
|
||||
const popupTimeout = notifObject.timeout || (notifObject.urgency == 'critical' ? 8000 : 3000);
|
||||
const command = (isPopup ?
|
||||
() => notifObject.dismiss() :
|
||||
() => notifObject.close()
|
||||
)
|
||||
const destroyWithAnims = () => {
|
||||
widget.sensitive = false;
|
||||
notificationBox.setCss(middleClickClose);
|
||||
Utils.timeout(userOptions.animations.durationSmall, () => {
|
||||
if (wholeThing) wholeThing.revealChild = false;
|
||||
}, wholeThing);
|
||||
Utils.timeout(userOptions.animations.durationSmall * 2, () => {
|
||||
command();
|
||||
if (wholeThing) {
|
||||
wholeThing.destroy();
|
||||
wholeThing = null;
|
||||
}
|
||||
}, wholeThing);
|
||||
}
|
||||
const widget = EventBox({
|
||||
onHover: (self) => {
|
||||
self.window.set_cursor(Gdk.Cursor.new_from_name(display, 'grab'));
|
||||
if (!wholeThing.attribute.hovered)
|
||||
wholeThing.attribute.hovered = true;
|
||||
},
|
||||
onHoverLost: (self) => {
|
||||
self.window.set_cursor(null);
|
||||
if (wholeThing.attribute.hovered)
|
||||
wholeThing.attribute.hovered = false;
|
||||
if (isPopup) {
|
||||
command();
|
||||
}
|
||||
},
|
||||
onMiddleClick: (self) => {
|
||||
destroyWithAnims();
|
||||
},
|
||||
setup: (self) => {
|
||||
self.on("button-press-event", () => {
|
||||
wholeThing.attribute.held = true;
|
||||
notificationContent.toggleClassName(`${isPopup ? 'popup-' : ''}notif-clicked-${notifObject.urgency}`, true);
|
||||
Utils.timeout(800, () => {
|
||||
if (wholeThing?.attribute.held) {
|
||||
Utils.execAsync(['wl-copy', `${notifObject.body}`]).catch(print);
|
||||
notifTextSummary.label = notifObject.summary + " (copied)";
|
||||
Utils.timeout(3000, () => notifTextSummary.label = notifObject.summary)
|
||||
}
|
||||
})
|
||||
}).on("button-release-event", () => {
|
||||
wholeThing.attribute.held = false;
|
||||
notificationContent.toggleClassName(`${isPopup ? 'popup-' : ''}notif-clicked-${notifObject.urgency}`, false);
|
||||
})
|
||||
}
|
||||
});
|
||||
let wholeThing = Revealer({
|
||||
attribute: {
|
||||
'close': undefined,
|
||||
'destroyWithAnims': destroyWithAnims,
|
||||
'dragging': false,
|
||||
'held': false,
|
||||
'hovered': false,
|
||||
'id': notifObject.id,
|
||||
},
|
||||
revealChild: false,
|
||||
transition: 'slide_down',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
child: Box({ // Box to make sure css-based spacing works
|
||||
homogeneous: true,
|
||||
}),
|
||||
});
|
||||
|
||||
const display = Gdk.Display.get_default();
|
||||
const notifTextPreview = Revealer({
|
||||
transition: 'slide_down',
|
||||
transitionDuration: userOptions.animations.durationSmall,
|
||||
revealChild: true,
|
||||
child: Label({
|
||||
xalign: 0,
|
||||
className: `txt-smallie notif-body-${notifObject.urgency}`,
|
||||
useMarkup: true,
|
||||
xalign: 0,
|
||||
justify: Gtk.Justification.LEFT,
|
||||
maxWidthChars: 1,
|
||||
truncate: 'end',
|
||||
label: notifObject.body.split("\n")[0],
|
||||
}),
|
||||
});
|
||||
const notifTextExpanded = Revealer({
|
||||
transition: 'slide_up',
|
||||
transitionDuration: userOptions.animations.durationSmall,
|
||||
revealChild: false,
|
||||
child: Box({
|
||||
vertical: true,
|
||||
className: 'spacing-v-10',
|
||||
children: [
|
||||
Label({
|
||||
xalign: 0,
|
||||
className: `txt-smallie notif-body-${notifObject.urgency}`,
|
||||
useMarkup: true,
|
||||
xalign: 0,
|
||||
justify: Gtk.Justification.LEFT,
|
||||
maxWidthChars: 1,
|
||||
wrap: true,
|
||||
label: notifObject.body,
|
||||
}),
|
||||
Box({
|
||||
className: 'notif-actions spacing-h-5',
|
||||
children: [
|
||||
Button({
|
||||
hexpand: true,
|
||||
className: `notif-action notif-action-${notifObject.urgency}`,
|
||||
onClicked: () => destroyWithAnims(),
|
||||
setup: setupCursorHover,
|
||||
child: Label({
|
||||
label: getString('Close'),
|
||||
}),
|
||||
}),
|
||||
...notifObject.actions.map(action => Widget.Button({
|
||||
hexpand: true,
|
||||
className: `notif-action notif-action-${notifObject.urgency}`,
|
||||
onClicked: () => notifObject.invoke(action.id),
|
||||
setup: setupCursorHover,
|
||||
child: Label({
|
||||
label: action.label,
|
||||
}),
|
||||
}))
|
||||
],
|
||||
})
|
||||
]
|
||||
}),
|
||||
});
|
||||
const notifIcon = Box({
|
||||
vpack: 'start',
|
||||
homogeneous: true,
|
||||
children: [
|
||||
Overlay({
|
||||
child: NotificationIcon(notifObject),
|
||||
overlays: isPopup ? [AnimatedCircProg({
|
||||
className: `notif-circprog-${notifObject.urgency}`,
|
||||
vpack: 'center', hpack: 'center',
|
||||
initFrom: (isPopup ? 100 : 0),
|
||||
initTo: 0,
|
||||
initAnimTime: popupTimeout,
|
||||
})] : [],
|
||||
}),
|
||||
]
|
||||
});
|
||||
|
||||
const notifTextSummary = Label({
|
||||
xalign: 0,
|
||||
className: 'txt-small txt-semibold titlefont',
|
||||
justify: Gtk.Justification.LEFT,
|
||||
hexpand: true,
|
||||
maxWidthChars: 1,
|
||||
truncate: 'end',
|
||||
ellipsize: 3,
|
||||
useMarkup: notifObject.summary.startsWith('<'),
|
||||
label: notifObject.summary,
|
||||
});
|
||||
const initTimeString = getFriendlyNotifTimeString(notifObject.time);
|
||||
const notifTextBody = Label({
|
||||
vpack: 'center',
|
||||
justification: 'right',
|
||||
className: 'txt-smaller txt-semibold',
|
||||
label: initTimeString,
|
||||
setup: initTimeString == 'Now' ? (self) => {
|
||||
let id = Utils.timeout(60000, () => {
|
||||
self.label = getFriendlyNotifTimeString(notifObject.time);
|
||||
id = null;
|
||||
});
|
||||
self.connect('destroy', () => { if (id) GLib.source_remove(id) });
|
||||
} : () => { },
|
||||
});
|
||||
const notifText = Box({
|
||||
valign: Gtk.Align.CENTER,
|
||||
vertical: true,
|
||||
hexpand: true,
|
||||
children: [
|
||||
Box({
|
||||
children: [
|
||||
notifTextSummary,
|
||||
notifTextBody,
|
||||
]
|
||||
}),
|
||||
notifTextPreview,
|
||||
notifTextExpanded,
|
||||
]
|
||||
});
|
||||
const notifExpandButton = Button({
|
||||
vpack: 'start',
|
||||
className: 'notif-expand-btn',
|
||||
onClicked: (self) => {
|
||||
if (notifTextPreview.revealChild) { // Expanding...
|
||||
notifTextPreview.revealChild = false;
|
||||
notifTextExpanded.revealChild = true;
|
||||
self.child.label = 'expand_less';
|
||||
expanded = true;
|
||||
}
|
||||
else {
|
||||
notifTextPreview.revealChild = true;
|
||||
notifTextExpanded.revealChild = false;
|
||||
self.child.label = 'expand_more';
|
||||
expanded = false;
|
||||
}
|
||||
},
|
||||
child: MaterialIcon('expand_more', 'norm', {
|
||||
vpack: 'center',
|
||||
}),
|
||||
setup: setupCursorHover,
|
||||
});
|
||||
const notificationContent = Box({
|
||||
...props,
|
||||
className: `${isPopup ? 'popup-' : ''}notif-${notifObject.urgency} spacing-h-10`,
|
||||
children: [
|
||||
notifIcon,
|
||||
Box({
|
||||
className: 'spacing-h-5',
|
||||
children: [
|
||||
notifText,
|
||||
notifExpandButton,
|
||||
]
|
||||
})
|
||||
]
|
||||
})
|
||||
|
||||
// Gesture stuff
|
||||
const gesture = Gtk.GestureDrag.new(widget);
|
||||
var initDirX = 0;
|
||||
var initDirVertical = -1; // -1: unset, 0: horizontal, 1: vertical
|
||||
var expanded = false;
|
||||
// in px
|
||||
const startMargin = 0;
|
||||
const MOVE_THRESHOLD = 10;
|
||||
const DRAG_CONFIRM_THRESHOLD = 100;
|
||||
// in rem
|
||||
const maxOffset = 10.227;
|
||||
const endMargin = 20.455;
|
||||
const disappearHeight = 6.818;
|
||||
const leftAnim1 = `transition: ${userOptions.animations.durationSmall}ms cubic-bezier(0.05, 0.7, 0.1, 1);
|
||||
margin-left: -${Number(maxOffset + endMargin)}rem;
|
||||
margin-right: ${Number(maxOffset + endMargin)}rem;
|
||||
opacity: 0;`;
|
||||
|
||||
const rightAnim1 = `transition: ${userOptions.animations.durationSmall}ms cubic-bezier(0.05, 0.7, 0.1, 1);
|
||||
margin-left: ${Number(maxOffset + endMargin)}rem;
|
||||
margin-right: -${Number(maxOffset + endMargin)}rem;
|
||||
opacity: 0;`;
|
||||
|
||||
const middleClickClose = `transition: ${userOptions.animations.durationSmall}ms cubic-bezier(0.85, 0, 0.15, 1);
|
||||
margin-left: ${Number(maxOffset + endMargin)}rem;
|
||||
margin-right: -${Number(maxOffset + endMargin)}rem;
|
||||
opacity: 0;`;
|
||||
|
||||
const notificationBox = Box({
|
||||
attribute: {
|
||||
'leftAnim1': leftAnim1,
|
||||
'rightAnim1': rightAnim1,
|
||||
'middleClickClose': middleClickClose,
|
||||
'ready': false,
|
||||
},
|
||||
homogeneous: true,
|
||||
children: [notificationContent],
|
||||
setup: (self) => self
|
||||
.hook(gesture, self => {
|
||||
var offset_x = gesture.get_offset()[1];
|
||||
var offset_y = gesture.get_offset()[2];
|
||||
// Which dir?
|
||||
if (initDirVertical == -1) {
|
||||
if (Math.abs(offset_y) > MOVE_THRESHOLD)
|
||||
initDirVertical = 1;
|
||||
if (initDirX == 0 && Math.abs(offset_x) > MOVE_THRESHOLD) {
|
||||
initDirVertical = 0;
|
||||
initDirX = (offset_x > 0 ? 1 : -1);
|
||||
}
|
||||
}
|
||||
// Horizontal drag
|
||||
if (initDirVertical == 0 && offset_x > MOVE_THRESHOLD) {
|
||||
if (initDirX < 0)
|
||||
self.setCss(`margin-left: 0px; margin-right: 0px;`);
|
||||
else
|
||||
self.setCss(`
|
||||
margin-left: ${Number(offset_x + startMargin - MOVE_THRESHOLD)}px;
|
||||
margin-right: -${Number(offset_x + startMargin - MOVE_THRESHOLD)}px;
|
||||
`);
|
||||
}
|
||||
else if (initDirVertical == 0 && offset_x < -MOVE_THRESHOLD) {
|
||||
if (initDirX > 0)
|
||||
self.setCss(`margin-left: 0px; margin-right: 0px;`);
|
||||
else {
|
||||
offset_x = Math.abs(offset_x);
|
||||
self.setCss(`
|
||||
margin-right: ${Number(offset_x + startMargin - MOVE_THRESHOLD)}px;
|
||||
margin-left: -${Number(offset_x + startMargin - MOVE_THRESHOLD)}px;
|
||||
`);
|
||||
}
|
||||
}
|
||||
// Update dragging
|
||||
wholeThing.attribute.dragging = Math.abs(offset_x) > MOVE_THRESHOLD;
|
||||
if (Math.abs(offset_x) > MOVE_THRESHOLD ||
|
||||
Math.abs(offset_y) > MOVE_THRESHOLD) wholeThing.attribute.held = false;
|
||||
widget.window?.set_cursor(Gdk.Cursor.new_from_name(display, 'grabbing'));
|
||||
// Vertical drag
|
||||
if (initDirVertical == 1 && offset_y > MOVE_THRESHOLD && !expanded) {
|
||||
notifTextPreview.revealChild = false;
|
||||
notifTextExpanded.revealChild = true;
|
||||
expanded = true;
|
||||
notifExpandButton.child.label = 'expand_less';
|
||||
}
|
||||
else if (initDirVertical == 1 && offset_y < -MOVE_THRESHOLD && expanded) {
|
||||
notifTextPreview.revealChild = true;
|
||||
notifTextExpanded.revealChild = false;
|
||||
expanded = false;
|
||||
notifExpandButton.child.label = 'expand_more';
|
||||
}
|
||||
|
||||
}, 'drag-update')
|
||||
.hook(gesture, self => {
|
||||
if (!self.attribute.ready) {
|
||||
wholeThing.revealChild = true;
|
||||
self.attribute.ready = true;
|
||||
return;
|
||||
}
|
||||
const offset_h = gesture.get_offset()[1];
|
||||
|
||||
if (Math.abs(offset_h) > DRAG_CONFIRM_THRESHOLD && offset_h * initDirX > 0) {
|
||||
if (offset_h > 0) {
|
||||
self.setCss(rightAnim1);
|
||||
widget.sensitive = false;
|
||||
}
|
||||
else {
|
||||
self.setCss(leftAnim1);
|
||||
widget.sensitive = false;
|
||||
}
|
||||
Utils.timeout(userOptions.animations.durationSmall, () => {
|
||||
if (wholeThing) wholeThing.revealChild = false;
|
||||
}, wholeThing);
|
||||
Utils.timeout(userOptions.animations.durationSmall * 2, () => {
|
||||
command();
|
||||
if (wholeThing) {
|
||||
wholeThing.destroy();
|
||||
wholeThing = null;
|
||||
}
|
||||
}, wholeThing);
|
||||
}
|
||||
else {
|
||||
self.setCss(`transition: margin 200ms cubic-bezier(0.05, 0.7, 0.1, 1), opacity 200ms cubic-bezier(0.05, 0.7, 0.1, 1);
|
||||
margin-left: ${startMargin}px;
|
||||
margin-right: ${startMargin}px;
|
||||
margin-bottom: unset; margin-top: unset;
|
||||
opacity: 1;`);
|
||||
if (widget.window)
|
||||
widget.window.set_cursor(Gdk.Cursor.new_from_name(display, 'grab'));
|
||||
|
||||
wholeThing.attribute.dragging = false;
|
||||
}
|
||||
initDirX = 0;
|
||||
initDirVertical = -1;
|
||||
}, 'drag-end')
|
||||
,
|
||||
})
|
||||
widget.add(notificationBox);
|
||||
wholeThing.child.children = [widget];
|
||||
if (isPopup) Utils.timeout(popupTimeout, () => {
|
||||
if (wholeThing) {
|
||||
wholeThing.revealChild = false;
|
||||
Utils.timeout(userOptions.animations.durationSmall, () => {
|
||||
if (wholeThing) {
|
||||
wholeThing.destroy();
|
||||
wholeThing = null;
|
||||
}
|
||||
command();
|
||||
}, wholeThing);
|
||||
}
|
||||
})
|
||||
return wholeThing;
|
||||
}
|
||||
306
homes/me/ags-end4/modules/.commonwidgets/statusicons.js
Normal file
306
homes/me/ags-end4/modules/.commonwidgets/statusicons.js
Normal file
|
|
@ -0,0 +1,306 @@
|
|||
import App from 'resource:///com/github/Aylur/ags/app.js';
|
||||
import Audio from 'resource:///com/github/Aylur/ags/service/audio.js';
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
|
||||
import { MaterialIcon } from './materialicon.js';
|
||||
import Bluetooth from 'resource:///com/github/Aylur/ags/service/bluetooth.js';
|
||||
import Network from 'resource:///com/github/Aylur/ags/service/network.js';
|
||||
import Notifications from 'resource:///com/github/Aylur/ags/service/notifications.js';
|
||||
import { languages } from './statusicons_languages.js';
|
||||
|
||||
// A guessing func to try to support langs not listed in data/languages.js
|
||||
function isLanguageMatch(abbreviation, word) {
|
||||
const lowerAbbreviation = abbreviation.toLowerCase();
|
||||
const lowerWord = word.toLowerCase();
|
||||
let j = 0;
|
||||
for (let i = 0; i < lowerWord.length; i++) {
|
||||
if (lowerWord[i] === lowerAbbreviation[j]) {
|
||||
j++;
|
||||
}
|
||||
if (j === lowerAbbreviation.length) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export const MicMuteIndicator = () => Widget.Revealer({
|
||||
transition: 'slide_left',
|
||||
transitionDuration: userOptions.animations.durationSmall,
|
||||
revealChild: false,
|
||||
setup: (self) => self.hook(Audio, (self) => {
|
||||
self.revealChild = Audio.microphone?.stream?.isMuted;
|
||||
}),
|
||||
child: MaterialIcon('mic_off', 'norm'),
|
||||
});
|
||||
|
||||
export const NotificationIndicator = (notifCenterName = 'sideright') => {
|
||||
const widget = Widget.Revealer({
|
||||
transition: 'slide_left',
|
||||
transitionDuration: userOptions.animations.durationSmall,
|
||||
revealChild: false,
|
||||
setup: (self) => self
|
||||
.hook(Notifications, (self, id) => {
|
||||
if (!id || Notifications.dnd) return;
|
||||
if (!Notifications.getNotification(id)) return;
|
||||
self.revealChild = true;
|
||||
}, 'notified')
|
||||
.hook(App, (self, currentName, visible) => {
|
||||
if (visible && currentName === notifCenterName) {
|
||||
self.revealChild = false;
|
||||
}
|
||||
})
|
||||
,
|
||||
child: Widget.Box({
|
||||
children: [
|
||||
MaterialIcon('notifications', 'norm'),
|
||||
Widget.Label({
|
||||
className: 'txt-small titlefont',
|
||||
attribute: {
|
||||
unreadCount: 0,
|
||||
update: (self) => self.label = `${self.attribute.unreadCount}`,
|
||||
},
|
||||
setup: (self) => self
|
||||
.hook(Notifications, (self, id) => {
|
||||
if (!id || Notifications.dnd) return;
|
||||
if (!Notifications.getNotification(id)) return;
|
||||
self.attribute.unreadCount++;
|
||||
self.attribute.update(self);
|
||||
}, 'notified')
|
||||
.hook(App, (self, currentName, visible) => {
|
||||
if (visible && currentName === notifCenterName) {
|
||||
self.attribute.unreadCount = 0;
|
||||
self.attribute.update(self);
|
||||
}
|
||||
})
|
||||
,
|
||||
})
|
||||
]
|
||||
})
|
||||
});
|
||||
return widget;
|
||||
}
|
||||
|
||||
export const BluetoothIndicator = () => Widget.Stack({
|
||||
transition: 'slide_up_down',
|
||||
transitionDuration: userOptions.animations.durationSmall,
|
||||
children: {
|
||||
'false': Widget.Label({ className: 'txt-norm icon-material', label: 'bluetooth_disabled' }),
|
||||
'true': Widget.Label({ className: 'txt-norm icon-material', label: 'bluetooth' }),
|
||||
},
|
||||
setup: (self) => self
|
||||
.hook(Bluetooth, stack => {
|
||||
stack.shown = String(Bluetooth.enabled);
|
||||
})
|
||||
,
|
||||
});
|
||||
|
||||
const BluetoothDevices = () => Widget.Box({
|
||||
className: 'spacing-h-5',
|
||||
setup: self => self.hook(Bluetooth, self => {
|
||||
self.children = Bluetooth.connected_devices.map((device) => {
|
||||
return Widget.Box({
|
||||
className: 'bar-bluetooth-device spacing-h-5',
|
||||
vpack: 'center',
|
||||
tooltipText: device.name,
|
||||
children: [
|
||||
Widget.Icon(`${device.iconName}-symbolic`),
|
||||
...(device.batteryPercentage ? [Widget.Label({
|
||||
className: 'txt-smallie',
|
||||
label: `${device.batteryPercentage}`,
|
||||
setup: (self) => {
|
||||
self.hook(device, (self) => {
|
||||
self.label = `${device.batteryPercentage}`;
|
||||
}, 'notify::batteryPercentage')
|
||||
}
|
||||
})] : []),
|
||||
]
|
||||
});
|
||||
});
|
||||
self.visible = Bluetooth.connected_devices.length > 0;
|
||||
}, 'notify::connected-devices'),
|
||||
})
|
||||
|
||||
const NetworkWiredIndicator = () => Widget.Stack({
|
||||
transition: 'slide_up_down',
|
||||
transitionDuration: userOptions.animations.durationSmall,
|
||||
children: {
|
||||
'fallback': SimpleNetworkIndicator(),
|
||||
'unknown': Widget.Label({ className: 'txt-norm icon-material', label: 'wifi_off' }),
|
||||
'disconnected': Widget.Label({ className: 'txt-norm icon-material', label: 'signal_wifi_off' }),
|
||||
'connected': Widget.Label({ className: 'txt-norm icon-material', label: 'lan' }),
|
||||
'connecting': Widget.Label({ className: 'txt-norm icon-material', label: 'settings_ethernet' }),
|
||||
},
|
||||
setup: (self) => self.hook(Network, stack => {
|
||||
if (!Network.wired)
|
||||
return;
|
||||
|
||||
const { internet } = Network.wired;
|
||||
if (['connecting', 'connected'].includes(internet))
|
||||
stack.shown = internet;
|
||||
else if (Network.connectivity !== 'full')
|
||||
stack.shown = 'disconnected';
|
||||
else
|
||||
stack.shown = 'fallback';
|
||||
}),
|
||||
});
|
||||
|
||||
const SimpleNetworkIndicator = () => Widget.Icon({
|
||||
setup: (self) => self.hook(Network, self => {
|
||||
const icon = Network[Network.primary || 'wifi']?.iconName;
|
||||
self.icon = icon || '';
|
||||
self.visible = icon;
|
||||
}),
|
||||
});
|
||||
|
||||
const NetworkWifiIndicator = () => Widget.Stack({
|
||||
transition: 'slide_up_down',
|
||||
transitionDuration: userOptions.animations.durationSmall,
|
||||
children: {
|
||||
'disabled': Widget.Label({ className: 'txt-norm icon-material', label: 'wifi_off' }),
|
||||
'disconnected': Widget.Label({ className: 'txt-norm icon-material', label: 'signal_wifi_off' }),
|
||||
'connecting': Widget.Label({ className: 'txt-norm icon-material', label: 'settings_ethernet' }),
|
||||
'0': Widget.Label({ className: 'txt-norm icon-material', label: 'signal_wifi_0_bar' }),
|
||||
'1': Widget.Label({ className: 'txt-norm icon-material', label: 'network_wifi_1_bar' }),
|
||||
'2': Widget.Label({ className: 'txt-norm icon-material', label: 'network_wifi_2_bar' }),
|
||||
'3': Widget.Label({ className: 'txt-norm icon-material', label: 'network_wifi_3_bar' }),
|
||||
'4': Widget.Label({ className: 'txt-norm icon-material', label: 'signal_wifi_4_bar' }),
|
||||
},
|
||||
setup: (self) => self.hook(Network, (stack) => {
|
||||
if (!Network.wifi) {
|
||||
return;
|
||||
}
|
||||
if (Network.wifi.internet == 'connected') {
|
||||
stack.shown = String(Math.ceil(Network.wifi.strength / 25));
|
||||
}
|
||||
else if (["disconnected", "connecting"].includes(Network.wifi.internet)) {
|
||||
stack.shown = Network.wifi.internet;
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
export const NetworkIndicator = () => Widget.Stack({
|
||||
transition: 'slide_up_down',
|
||||
transitionDuration: userOptions.animations.durationSmall,
|
||||
children: {
|
||||
'fallback': SimpleNetworkIndicator(),
|
||||
'wifi': NetworkWifiIndicator(),
|
||||
'wired': NetworkWiredIndicator(),
|
||||
},
|
||||
setup: (self) => self.hook(Network, stack => {
|
||||
if (!Network.primary) {
|
||||
stack.shown = 'wifi';
|
||||
return;
|
||||
}
|
||||
const primary = Network.primary || 'fallback';
|
||||
if (['wifi', 'wired'].includes(primary))
|
||||
stack.shown = primary;
|
||||
else
|
||||
stack.shown = 'fallback';
|
||||
}),
|
||||
});
|
||||
|
||||
const HyprlandXkbKeyboardLayout = async ({ useFlag } = {}) => {
|
||||
try {
|
||||
const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
|
||||
var languageStackArray = [];
|
||||
|
||||
const updateCurrentKeyboards = () => {
|
||||
var initLangs = [];
|
||||
JSON.parse(Utils.exec('hyprctl -j devices')).keyboards
|
||||
.forEach(keyboard => {
|
||||
initLangs.push(...keyboard.layout.split(',').map(lang => lang.trim()));
|
||||
});
|
||||
initLangs = [...new Set(initLangs)];
|
||||
languageStackArray = Array.from({ length: initLangs.length }, (_, i) => {
|
||||
const lang = languages.find(lang => lang.layout == initLangs[i]);
|
||||
// if (!lang) return [
|
||||
// initLangs[i],
|
||||
// Widget.Label({ label: initLangs[i] })
|
||||
// ];
|
||||
// return [
|
||||
// lang.layout,
|
||||
// Widget.Label({ label: (useFlag ? lang.flag : lang.layout) })
|
||||
// ];
|
||||
// Object
|
||||
if (!lang) return {
|
||||
[initLangs[i]]: Widget.Label({ label: initLangs[i] })
|
||||
};
|
||||
return {
|
||||
[lang.layout]: Widget.Label({ label: (useFlag ? lang.flag : lang.layout) })
|
||||
};
|
||||
});
|
||||
};
|
||||
updateCurrentKeyboards();
|
||||
const widgetRevealer = Widget.Revealer({
|
||||
transition: 'slide_left',
|
||||
transitionDuration: userOptions.animations.durationSmall,
|
||||
revealChild: languageStackArray.length > 1,
|
||||
});
|
||||
const widgetKids = {
|
||||
...languageStackArray.reduce((obj, lang) => {
|
||||
return { ...obj, ...lang };
|
||||
}, {}),
|
||||
'undef': Widget.Label({ label: '?' }),
|
||||
}
|
||||
const widgetContent = Widget.Stack({
|
||||
transition: 'slide_up_down',
|
||||
transitionDuration: userOptions.animations.durationSmall,
|
||||
children: widgetKids,
|
||||
setup: (self) => self.hook(Hyprland, (stack, kbName, layoutName) => {
|
||||
if (!kbName) {
|
||||
return;
|
||||
}
|
||||
var lang = languages.find(lang => layoutName.includes(lang.name));
|
||||
if (lang) {
|
||||
widgetContent.shown = lang.layout;
|
||||
}
|
||||
else { // Attempt to support langs not listed
|
||||
lang = languageStackArray.find(lang => isLanguageMatch(lang[0], layoutName));
|
||||
if (!lang) stack.shown = 'undef';
|
||||
else stack.shown = lang[0];
|
||||
}
|
||||
}, 'keyboard-layout'),
|
||||
});
|
||||
widgetRevealer.child = widgetContent;
|
||||
return widgetRevealer;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const OptionalKeyboardLayout = async () => {
|
||||
try {
|
||||
return await HyprlandXkbKeyboardLayout({ useFlag: userOptions.appearance.keyboardUseFlag });
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
const createKeyboardLayoutInstances = async () => {
|
||||
const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
|
||||
const monitorsCount = Hyprland.monitors.length
|
||||
const instances = await Promise.all(
|
||||
Array.from({ length: monitorsCount }, () => OptionalKeyboardLayout())
|
||||
);
|
||||
|
||||
return instances;
|
||||
};
|
||||
const optionalKeyboardLayoutInstances = await createKeyboardLayoutInstances()
|
||||
|
||||
export const StatusIcons = (props = {}, monitor = 0) => Widget.Box({
|
||||
...props,
|
||||
child: Widget.Box({
|
||||
className: 'spacing-h-15',
|
||||
children: [
|
||||
MicMuteIndicator(),
|
||||
optionalKeyboardLayoutInstances[monitor],
|
||||
NotificationIndicator(),
|
||||
NetworkIndicator(),
|
||||
Widget.Box({
|
||||
className: 'spacing-h-5',
|
||||
children: [BluetoothIndicator(), BluetoothDevices()]
|
||||
})
|
||||
]
|
||||
})
|
||||
});
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
// For keyboard layout in statusicons.js
|
||||
// This list is not exhaustive. It just includes known/possible languages of users of my dotfiles
|
||||
// Add your language here if you use multi-lang xkb input. Else, ignore
|
||||
// Note that something like "French (Canada)" should go before "French"
|
||||
// and "English (US)" should go before "English"
|
||||
export const languages = [
|
||||
{
|
||||
layout: 'us',
|
||||
name: 'English (US)',
|
||||
flag: '🇺🇸'
|
||||
},
|
||||
{
|
||||
layout: 'ru',
|
||||
name: 'Russian',
|
||||
flag: '🇷🇺',
|
||||
},
|
||||
{
|
||||
layout: 'pl',
|
||||
name: 'Polish',
|
||||
flag: '🇷🇵🇵🇱',
|
||||
},
|
||||
{
|
||||
layout: 'ro',
|
||||
name: 'Romanian',
|
||||
flag: '🇷🇴',
|
||||
},
|
||||
{
|
||||
layout: 'ca',
|
||||
name: 'French (Canada)',
|
||||
flag: '🇫🇷',
|
||||
},
|
||||
{
|
||||
layout: 'fr',
|
||||
name: 'French',
|
||||
flag: '🇫🇷',
|
||||
},
|
||||
{
|
||||
layout: 'tr',
|
||||
name: 'Turkish',
|
||||
flag: '🇹🇷',
|
||||
},
|
||||
{
|
||||
layout: 'jp',
|
||||
name: 'Japanese',
|
||||
flag: '🇯🇵',
|
||||
},
|
||||
{
|
||||
layout: 'cn',
|
||||
name: 'Chinese',
|
||||
flag: '🇨🇳',
|
||||
},
|
||||
{
|
||||
layout: 'vn',
|
||||
name: 'Vietnamese',
|
||||
flag: '🇻🇳',
|
||||
},
|
||||
{
|
||||
layout: 'undef',
|
||||
name: 'Undefined',
|
||||
flag: '🧐',
|
||||
},
|
||||
]
|
||||
279
homes/me/ags-end4/modules/.commonwidgets/tabcontainer.js
Normal file
279
homes/me/ags-end4/modules/.commonwidgets/tabcontainer.js
Normal file
|
|
@ -0,0 +1,279 @@
|
|||
import Variable from 'resource:///com/github/Aylur/ags/variable.js';
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
const { Box, Button, EventBox, Label, Overlay, Stack } = Widget;
|
||||
import { MaterialIcon } from './materialicon.js';
|
||||
import { NavigationIndicator } from './cairo_navigationindicator.js';
|
||||
import { setupCursorHover } from '../.widgetutils/cursorhover.js';
|
||||
import { DoubleRevealer } from '../.widgethacks/advancedrevealers.js';
|
||||
|
||||
export const TabContainer = ({ icons, names, children, className = '', setup = () => { }, ...rest }) => {
|
||||
const shownIndex = Variable(0);
|
||||
let previousShownIndex = 0;
|
||||
const count = Math.min(icons.length, names.length, children.length);
|
||||
const tabs = Box({
|
||||
homogeneous: true,
|
||||
children: Array.from({ length: count }, (_, i) => Button({ // Tab button
|
||||
className: 'tab-btn',
|
||||
onClicked: () => shownIndex.value = i,
|
||||
setup: setupCursorHover,
|
||||
child: Box({
|
||||
hpack: 'center',
|
||||
vpack: 'center',
|
||||
className: 'spacing-h-5 txt-small',
|
||||
children: [
|
||||
MaterialIcon(icons[i], 'norm'),
|
||||
Label({
|
||||
label: names[i],
|
||||
})
|
||||
]
|
||||
})
|
||||
})),
|
||||
setup: (self) => self.hook(shownIndex, (self) => {
|
||||
self.children[previousShownIndex].toggleClassName('tab-btn-active', false);
|
||||
self.children[shownIndex.value].toggleClassName('tab-btn-active', true);
|
||||
previousShownIndex = shownIndex.value;
|
||||
}),
|
||||
});
|
||||
const tabIndicatorLine = Box({
|
||||
vertical: true,
|
||||
homogeneous: true,
|
||||
setup: (self) => self.hook(shownIndex, (self) => {
|
||||
self.children[0].css = `font-size: ${shownIndex.value}px;`;
|
||||
}),
|
||||
children: [NavigationIndicator({
|
||||
className: 'tab-indicator',
|
||||
count: count,
|
||||
css: `font-size: ${shownIndex.value}px;`,
|
||||
})],
|
||||
});
|
||||
const tabSection = Box({
|
||||
homogeneous: true,
|
||||
children: [EventBox({
|
||||
onScrollUp: () => mainBox.prevTab(),
|
||||
onScrollDown: () => mainBox.nextTab(),
|
||||
child: Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
tabs,
|
||||
tabIndicatorLine
|
||||
]
|
||||
})
|
||||
})]
|
||||
});
|
||||
const contentStack = Stack({
|
||||
transition: 'slide_left_right',
|
||||
children: children.reduce((acc, currentValue, index) => {
|
||||
acc[index] = currentValue;
|
||||
return acc;
|
||||
}, {}),
|
||||
setup: (self) => self.hook(shownIndex, (self) => {
|
||||
self.shown = `${shownIndex.value}`;
|
||||
}),
|
||||
});
|
||||
const mainBox = Box({
|
||||
attribute: {
|
||||
children: children,
|
||||
shown: shownIndex,
|
||||
names: names,
|
||||
},
|
||||
vertical: true,
|
||||
className: `spacing-v-5 ${className}`,
|
||||
setup: (self) => {
|
||||
self.pack_start(tabSection, false, false, 0);
|
||||
self.pack_end(contentStack, true, true, 0);
|
||||
setup(self);
|
||||
},
|
||||
...rest,
|
||||
});
|
||||
mainBox.nextTab = () => shownIndex.value = Math.min(shownIndex.value + 1, count - 1);
|
||||
mainBox.prevTab = () => shownIndex.value = Math.max(shownIndex.value - 1, 0);
|
||||
mainBox.cycleTab = () => shownIndex.value = (shownIndex.value + 1) % count;
|
||||
|
||||
return mainBox;
|
||||
}
|
||||
|
||||
|
||||
export const IconTabContainer = ({
|
||||
iconWidgets, names, children, className = '',
|
||||
setup = () => { }, onChange = () => { },
|
||||
tabsHpack = 'center', tabSwitcherClassName = '',
|
||||
...rest
|
||||
}) => {
|
||||
const shownIndex = Variable(0);
|
||||
let previousShownIndex = 0;
|
||||
const count = Math.min(iconWidgets.length, names.length, children.length);
|
||||
const tabs = Box({
|
||||
hpack: tabsHpack,
|
||||
className: `spacing-h-5 ${tabSwitcherClassName}`,
|
||||
children: iconWidgets.map((icon, i) => Button({
|
||||
className: 'tab-icon',
|
||||
tooltipText: names[i],
|
||||
child: icon,
|
||||
setup: setupCursorHover,
|
||||
onClicked: () => shownIndex.value = i,
|
||||
})),
|
||||
setup: (self) => self.hook(shownIndex, (self) => {
|
||||
self.children[previousShownIndex].toggleClassName('tab-icon-active', false);
|
||||
self.children[shownIndex.value].toggleClassName('tab-icon-active', true);
|
||||
previousShownIndex = shownIndex.value;
|
||||
}),
|
||||
});
|
||||
const tabSection = Box({
|
||||
homogeneous: true,
|
||||
children: [EventBox({
|
||||
onScrollUp: () => mainBox.prevTab(),
|
||||
onScrollDown: () => mainBox.nextTab(),
|
||||
child: Box({
|
||||
vertical: true,
|
||||
hexpand: true,
|
||||
children: [
|
||||
tabs,
|
||||
]
|
||||
})
|
||||
})]
|
||||
});
|
||||
const contentStack = Stack({
|
||||
transition: 'slide_left_right',
|
||||
children: children.reduce((acc, currentValue, index) => {
|
||||
acc[index] = currentValue;
|
||||
return acc;
|
||||
}, {}),
|
||||
setup: (self) => self.hook(shownIndex, (self) => {
|
||||
self.shown = `${shownIndex.value}`;
|
||||
}),
|
||||
});
|
||||
const mainBox = Box({
|
||||
attribute: {
|
||||
children: children,
|
||||
shown: shownIndex,
|
||||
names: names,
|
||||
},
|
||||
vertical: true,
|
||||
className: `spacing-v-5 ${className}`,
|
||||
setup: (self) => {
|
||||
self.pack_start(tabSection, false, false, 0);
|
||||
self.pack_end(contentStack, true, true, 0);
|
||||
setup(self);
|
||||
self.hook(shownIndex, (self) => onChange(self, shownIndex.value));
|
||||
},
|
||||
...rest,
|
||||
});
|
||||
mainBox.nextTab = () => shownIndex.value = Math.min(shownIndex.value + 1, count - 1);
|
||||
mainBox.prevTab = () => shownIndex.value = Math.max(shownIndex.value - 1, 0);
|
||||
mainBox.cycleTab = () => shownIndex.value = (shownIndex.value + 1) % count;
|
||||
mainBox.shown = shownIndex;
|
||||
|
||||
return mainBox;
|
||||
}
|
||||
|
||||
export const ExpandingIconTabContainer = ({
|
||||
icons, names, children, className = '',
|
||||
setup = () => { }, onChange = () => { },
|
||||
tabsHpack = 'center', tabSwitcherClassName = '',
|
||||
transitionDuration = userOptions.animations.durationLarge,
|
||||
...rest
|
||||
}) => {
|
||||
const shownIndex = Variable(0);
|
||||
let previousShownIndex = 0;
|
||||
const count = Math.min(icons.length, names.length, children.length);
|
||||
const tabs = Box({
|
||||
hpack: tabsHpack,
|
||||
className: `spacing-h-5 ${tabSwitcherClassName}`,
|
||||
children: icons.map((icon, i) => {
|
||||
const tabIcon = MaterialIcon(icon, 'norm', { hexpand: true });
|
||||
const tabName = DoubleRevealer({
|
||||
transition1: 'slide_right',
|
||||
transition2: 'crossfade',
|
||||
duration1: 0,
|
||||
duration2: 0,
|
||||
// duration1: userOptions.animations.durationSmall,
|
||||
// duration2: userOptions.animations.durationSmall,
|
||||
child: Label({
|
||||
className: 'margin-left-5 txt-small',
|
||||
label: names[i],
|
||||
}),
|
||||
revealChild: i === shownIndex.value,
|
||||
})
|
||||
const button = Button({
|
||||
className: 'tab-icon-expandable',
|
||||
tooltipText: names[i],
|
||||
child: Box({
|
||||
homogeneous: true,
|
||||
children: [Box({
|
||||
hpack: 'center',
|
||||
children: [
|
||||
tabIcon,
|
||||
tabName,
|
||||
]
|
||||
})],
|
||||
}),
|
||||
setup: setupCursorHover,
|
||||
onClicked: () => shownIndex.value = i,
|
||||
});
|
||||
button.toggleFocus = (value) => {
|
||||
tabIcon.hexpand = !value;
|
||||
button.toggleClassName('tab-icon-expandable-active', value);
|
||||
tabName.toggleRevealChild(value);
|
||||
}
|
||||
return button;
|
||||
}),
|
||||
setup: (self) => self.hook(shownIndex, (self) => {
|
||||
self.children[previousShownIndex].toggleFocus(false);
|
||||
self.children[shownIndex.value].toggleFocus(true);
|
||||
previousShownIndex = shownIndex.value;
|
||||
}),
|
||||
});
|
||||
const tabSection = Box({
|
||||
homogeneous: true,
|
||||
children: [EventBox({
|
||||
onScrollUp: () => mainBox.prevTab(),
|
||||
onScrollDown: () => mainBox.nextTab(),
|
||||
child: Box({
|
||||
vertical: true,
|
||||
hexpand: true,
|
||||
children: [
|
||||
tabs,
|
||||
]
|
||||
})
|
||||
})]
|
||||
});
|
||||
const contentStack = Stack({
|
||||
transition: 'slide_left_right',
|
||||
transitionDuration: transitionDuration,
|
||||
children: children.reduce((acc, currentValue, index) => {
|
||||
acc[index] = currentValue;
|
||||
return acc;
|
||||
}, {}),
|
||||
setup: (self) => self.hook(shownIndex, (self) => {
|
||||
self.shown = `${shownIndex.value}`;
|
||||
}),
|
||||
});
|
||||
const mainBox = Box({
|
||||
attribute: {
|
||||
children: children,
|
||||
shown: shownIndex,
|
||||
names: names,
|
||||
},
|
||||
vertical: true,
|
||||
className: `spacing-v-5 ${className}`,
|
||||
setup: (self) => {
|
||||
self.pack_start(tabSection, false, false, 0);
|
||||
self.pack_end(contentStack, true, true, 0);
|
||||
setup(self);
|
||||
self.hook(shownIndex, (self) => onChange(self, shownIndex.value));
|
||||
},
|
||||
...rest,
|
||||
});
|
||||
mainBox.nextTab = () => shownIndex.value = Math.min(shownIndex.value + 1, count - 1);
|
||||
mainBox.prevTab = () => shownIndex.value = Math.max(shownIndex.value - 1, 0);
|
||||
mainBox.cycleTab = () => shownIndex.value = (shownIndex.value + 1) % count;
|
||||
mainBox.focusName = (name) => {
|
||||
const focusIndex = names.indexOf(name);
|
||||
if (focusIndex !== -1) {
|
||||
shownIndex.value = focusIndex;
|
||||
}
|
||||
}
|
||||
mainBox.shown = shownIndex;
|
||||
|
||||
return mainBox;
|
||||
}
|
||||
262
homes/me/ags-end4/modules/.configuration/user_options.js
Normal file
262
homes/me/ags-end4/modules/.configuration/user_options.js
Normal file
|
|
@ -0,0 +1,262 @@
|
|||
import GLib from 'gi://GLib';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'
|
||||
import userOverrides from '../../user_options.js';
|
||||
|
||||
// Default options.
|
||||
// Add overrides in ~/.config/ags/user_options.js
|
||||
let configOptions = {
|
||||
// General stuff
|
||||
'ai': {
|
||||
'defaultGPTProvider': "openai",
|
||||
'defaultTemperature': 0.9,
|
||||
'enhancements': true,
|
||||
'useHistory': true,
|
||||
'safety': true,
|
||||
'writingCursor': " ...", // Warning: Using weird characters can mess up Markdown rendering
|
||||
'proxyUrl': null, // Can be "socks5://127.0.0.1:9050" or "http://127.0.0.1:8080" for example. Leave it blank if you don't need it.
|
||||
},
|
||||
'animations': {
|
||||
'choreographyDelay': 35,
|
||||
'durationSmall': 110,
|
||||
'durationLarge': 180,
|
||||
},
|
||||
'appearance': {
|
||||
'autoDarkMode': { // Turns on dark mode in certain hours. Time in 24h format
|
||||
'enabled': false,
|
||||
'from': "18:10",
|
||||
'to': "6:10",
|
||||
},
|
||||
'keyboardUseFlag': false, // Use flag emoji instead of abbreviation letters
|
||||
'layerSmoke': false,
|
||||
'layerSmokeStrength': 0.2,
|
||||
'barRoundCorners': 1, // 0: No, 1: Yes
|
||||
'fakeScreenRounding': 1, // 0: None | 1: Always | 2: When not fullscreen
|
||||
},
|
||||
'apps': {
|
||||
'bluetooth': "blueberry",
|
||||
'imageViewer': "loupe",
|
||||
'network': "XDG_CURRENT_DESKTOP=\"gnome\" gnome-control-center wifi",
|
||||
'settings': "XDG_CURRENT_DESKTOP=\"gnome\" gnome-control-center",
|
||||
'taskManager': "gnome-usage",
|
||||
'terminal': "foot", // This is only for shell actions
|
||||
},
|
||||
'battery': {
|
||||
'low': 20,
|
||||
'critical': 10,
|
||||
'warnLevels': [20, 15, 5],
|
||||
'warnTitles': ["Low battery", "Very low battery", 'Critical Battery'],
|
||||
'warnMessages': ["Plug in the charger", "You there?", 'PLUG THE CHARGER ALREADY'],
|
||||
'suspendThreshold': 3,
|
||||
},
|
||||
'brightness': {
|
||||
// Object of controller names for each monitor, either "brightnessctl" or "ddcutil" or "auto"
|
||||
// 'default' one will be used if unspecified
|
||||
// Examples
|
||||
// 'eDP-1': "brightnessctl",
|
||||
// 'DP-1': "ddcutil",
|
||||
'controllers': {
|
||||
'default': "auto",
|
||||
},
|
||||
},
|
||||
'cheatsheet': {
|
||||
'keybinds': {
|
||||
'configPath': "" // Path to hyprland keybind config file. Leave empty for default (~/.config/hypr/hyprland/keybinds.conf)
|
||||
}
|
||||
},
|
||||
'gaming': {
|
||||
'crosshair': {
|
||||
'size': 20,
|
||||
'color': 'rgba(113,227,32,0.9)',
|
||||
},
|
||||
},
|
||||
'i18n': {
|
||||
'langCode': "",//Customize the locale, such as zh_CN,Optional value references "~/.config/ags/i18n/locales/"
|
||||
'extraLogs': false
|
||||
},
|
||||
'monitors': {
|
||||
'scaleMethod': "division", // Either "division" [default] or "gdk"
|
||||
},
|
||||
'music': {
|
||||
'preferredPlayer': "plasma-browser-integration",
|
||||
},
|
||||
'onScreenKeyboard': {
|
||||
'layout': "qwerty_full", // See modules/onscreenkeyboard/onscreenkeyboard.js for available layouts
|
||||
},
|
||||
'overview': {
|
||||
'scale': 0.18, // Relative to screen size
|
||||
'numOfRows': 2,
|
||||
'numOfCols': 5,
|
||||
'wsNumScale': 0.09,
|
||||
'wsNumMarginScale': 0.07,
|
||||
},
|
||||
'sidebar': {
|
||||
'ai': {
|
||||
'extraGptModels': {
|
||||
'oxygen3': {
|
||||
'name': 'Oxygen (GPT-3.5)',
|
||||
'logo_name': 'ai-oxygen-symbolic',
|
||||
'description': 'An API from Tornado Softwares\nPricing: Free: 100/day\nRequires you to join their Discord for a key',
|
||||
'base_url': 'https://app.oxyapi.uk/v1/chat/completions',
|
||||
'key_get_url': 'https://discord.com/invite/kM6MaCqGKA',
|
||||
'key_file': 'oxygen_key.txt',
|
||||
'model': 'gpt-3.5-turbo',
|
||||
},
|
||||
}
|
||||
},
|
||||
'image': {
|
||||
'columns': 2,
|
||||
'batchCount': 20,
|
||||
'allowNsfw': false,
|
||||
'saveInFolderByTags': false,
|
||||
},
|
||||
'pages': {
|
||||
'order': ["apis", "tools"],
|
||||
'apis': {
|
||||
'order': ["gemini", "gpt", "waifu", "booru"],
|
||||
}
|
||||
},
|
||||
},
|
||||
'search': {
|
||||
'enableFeatures': {
|
||||
'actions': true,
|
||||
'commands': true,
|
||||
'mathResults': true,
|
||||
'directorySearch': true,
|
||||
'aiSearch': true,
|
||||
'webSearch': true,
|
||||
},
|
||||
'engineBaseUrl': "https://www.google.com/search?q=",
|
||||
'excludedSites': ["quora.com"],
|
||||
},
|
||||
'time': {
|
||||
// See https://docs.gtk.org/glib/method.DateTime.format.html
|
||||
// Here's the 12h format: "%I:%M%P"
|
||||
// For seconds, add "%S" and set interval to 1000
|
||||
'format': "%H:%M",
|
||||
'interval': 5000,
|
||||
'dateFormatLong': "%A, %d/%m", // On bar
|
||||
'dateInterval': 5000,
|
||||
'dateFormat': "%d/%m", // On notif time
|
||||
},
|
||||
'weather': {
|
||||
'city': "",
|
||||
'preferredUnit': "C", // Either C or F
|
||||
},
|
||||
'workspaces': {
|
||||
'shown': 10,
|
||||
},
|
||||
'dock': {
|
||||
'enabled': false,
|
||||
'hiddenThickness': 5,
|
||||
'pinnedApps': ['firefox', 'org.gnome.Nautilus'],
|
||||
'layer': 'top',
|
||||
'monitorExclusivity': true, // Dock will move to other monitor along with focus if enabled
|
||||
'searchPinnedAppIcons': false, // Try to search for the correct icon if the app class isn't an icon name
|
||||
'trigger': ['client-added', 'client-removed'], // client_added, client_move, workspace_active, client_active
|
||||
// Automatically hide dock after `interval` ms since trigger
|
||||
'autoHide': [
|
||||
{
|
||||
'trigger': 'client-added',
|
||||
'interval': 500,
|
||||
},
|
||||
{
|
||||
'trigger': 'client-removed',
|
||||
'interval': 500,
|
||||
},
|
||||
],
|
||||
},
|
||||
// Longer stuff
|
||||
'icons': {
|
||||
// Find the window's icon by its class with levenshteinDistance
|
||||
// The file names are processed at startup, so if there
|
||||
// are too many files in the search path it'll affect performance
|
||||
// Example: ['/usr/share/icons/Tela-nord/scalable/apps']
|
||||
'searchPaths': [''],
|
||||
'symbolicIconTheme': {
|
||||
"dark": "Adwaita",
|
||||
"light": "Adwaita",
|
||||
},
|
||||
substitutions: {
|
||||
'code-url-handler': "visual-studio-code",
|
||||
'Code': "visual-studio-code",
|
||||
'GitHub Desktop': "github-desktop",
|
||||
'Minecraft* 1.20.1': "minecraft",
|
||||
'gnome-tweaks': "org.gnome.tweaks",
|
||||
'pavucontrol-qt': "pavucontrol",
|
||||
'wps': "wps-office2019-kprometheus",
|
||||
'wpsoffice': "wps-office2019-kprometheus",
|
||||
'': "image-missing",
|
||||
},
|
||||
regexSubstitutions: [
|
||||
{
|
||||
regex: /^steam_app_(\d+)$/,
|
||||
replace: "steam_icon_$1",
|
||||
}
|
||||
]
|
||||
},
|
||||
'keybinds': {
|
||||
// Format: Mod1+Mod2+key. CaSe SeNsItIvE!
|
||||
// Modifiers: Shift Ctrl Alt Hyper Meta
|
||||
// See https://docs.gtk.org/gdk3/index.html#constants for the other keys (they are listed as KEY_key)
|
||||
'overview': {
|
||||
'altMoveLeft': "Ctrl+b",
|
||||
'altMoveRight': "Ctrl+f",
|
||||
'deleteToEnd': "Ctrl+k",
|
||||
},
|
||||
'sidebar': {
|
||||
'apis': {
|
||||
'nextTab': "Page_Down",
|
||||
'prevTab': "Page_Up",
|
||||
},
|
||||
'options': { // Right sidebar
|
||||
'nextTab': "Page_Down",
|
||||
'prevTab': "Page_Up",
|
||||
},
|
||||
'pin': "Ctrl+p",
|
||||
'cycleTab': "Ctrl+Tab",
|
||||
'nextTab': "Ctrl+Page_Down",
|
||||
'prevTab': "Ctrl+Page_Up",
|
||||
},
|
||||
'cheatsheet': {
|
||||
'keybinds': {
|
||||
'nextTab': "Page_Down",
|
||||
'prevTab': "Page_Up",
|
||||
},
|
||||
'nextTab': "Ctrl+Page_Down",
|
||||
'prevTab': "Ctrl+Page_Up",
|
||||
'cycleTab': "Ctrl+Tab",
|
||||
}
|
||||
},
|
||||
'bar': {
|
||||
// Array of bar modes for each monitor. Hit Ctrl+Alt+Slash to cycle.
|
||||
// Modes: "normal", "focus" (workspace indicator only), "nothing"
|
||||
// Example for four monitors: ["normal", "focus", "normal", "nothing"]
|
||||
'modes': ["normal"]
|
||||
},
|
||||
}
|
||||
|
||||
// Override defaults with user's options
|
||||
let optionsOkay = true;
|
||||
function overrideConfigRecursive(userOverrides, configOptions = {}, check = true) {
|
||||
for (const [key, value] of Object.entries(userOverrides)) {
|
||||
if (configOptions[key] === undefined && check) {
|
||||
optionsOkay = false;
|
||||
}
|
||||
else if (typeof value === 'object' && !(value instanceof Array)) {
|
||||
if (key === "substitutions" || key === "regexSubstitutions" || key === "extraGptModels") {
|
||||
overrideConfigRecursive(value, configOptions[key], false);
|
||||
} else overrideConfigRecursive(value, configOptions[key]);
|
||||
} else {
|
||||
configOptions[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
overrideConfigRecursive(userOverrides, configOptions);
|
||||
if (!optionsOkay) Utils.timeout(2000, () => Utils.execAsync(['notify-send',
|
||||
'Update your user options',
|
||||
'One or more config options don\'t exist',
|
||||
'-a', 'ags',
|
||||
]).catch(print))
|
||||
|
||||
globalThis['userOptions'] = configOptions;
|
||||
export default configOptions;
|
||||
14
homes/me/ags-end4/modules/.miscutils/files.js
Normal file
14
homes/me/ags-end4/modules/.miscutils/files.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
const { Gio, GLib, Gtk } = imports.gi;
|
||||
|
||||
export function fileExists(filePath) {
|
||||
let file = Gio.File.new_for_path(filePath);
|
||||
return file.query_exists(null);
|
||||
}
|
||||
|
||||
export function expandTilde(path) {
|
||||
if (path.startsWith('~')) {
|
||||
return GLib.get_home_dir() + path.slice(1);
|
||||
} else {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
28
homes/me/ags-end4/modules/.miscutils/icons.js
Normal file
28
homes/me/ags-end4/modules/.miscutils/icons.js
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
const { Gtk } = imports.gi;
|
||||
|
||||
export function iconExists(iconName) {
|
||||
let iconTheme = Gtk.IconTheme.get_default();
|
||||
return iconTheme.has_icon(iconName);
|
||||
}
|
||||
|
||||
export function substitute(str) {
|
||||
// Normal substitutions
|
||||
if (userOptions.icons.substitutions[str])
|
||||
return userOptions.icons.substitutions[str];
|
||||
|
||||
// Regex substitutions
|
||||
for (let i = 0; i < userOptions.icons.regexSubstitutions.length; i++) {
|
||||
const substitution = userOptions.icons.regexSubstitutions[i];
|
||||
const replacedName = str.replace(
|
||||
substitution.regex,
|
||||
substitution.replace,
|
||||
);
|
||||
if (replacedName != str) return replacedName;
|
||||
}
|
||||
|
||||
// Guess: convert to kebab case
|
||||
if (!iconExists(str)) str = str.toLowerCase().replace(/\s+/g, "-");
|
||||
|
||||
// Original string
|
||||
return str;
|
||||
}
|
||||
4
homes/me/ags-end4/modules/.miscutils/mathfuncs.js
Normal file
4
homes/me/ags-end4/modules/.miscutils/mathfuncs.js
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
export function clamp(x, min, max) {
|
||||
return Math.min(Math.max(x, min), max);
|
||||
}
|
||||
78
homes/me/ags-end4/modules/.miscutils/md2pango.js
Normal file
78
homes/me/ags-end4/modules/.miscutils/md2pango.js
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
// Converts from Markdown to Pango. This does not support code blocks.
|
||||
// For illogical-impulse, code blocks are treated separately, in their own GtkSourceView widgets.
|
||||
// Partly inherited from https://github.com/ubunatic/md2pango
|
||||
|
||||
const monospaceFonts = 'JetBrains Mono NF, JetBrains Mono Nerd Font, JetBrains Mono NL, SpaceMono NF, SpaceMono Nerd Font, monospace';
|
||||
|
||||
const replacements = {
|
||||
'indents': [
|
||||
{ name: 'BULLET', re: /^(\s*)([\*\-]\s)(.*)(\s*)$/, sub: ' $1- $3' },
|
||||
{ name: 'NUMBERING', re: /^(\s*[0-9]+\.\s)(.*)(\s*)$/, sub: ' $1 $2' },
|
||||
],
|
||||
'escapes': [
|
||||
{ name: 'COMMENT', re: /<!--[\s\S]*?-->/, sub: '' },
|
||||
{ name: 'AMPERSTAND', re: /&/g, sub: '&' },
|
||||
{ name: 'LESSTHAN', re: /</g, sub: '<' },
|
||||
{ name: 'GREATERTHAN', re: />/g, sub: '>' },
|
||||
],
|
||||
'sections': [
|
||||
{ name: 'H1', re: /^(#\s+)(.*)(\s*)$/, sub: '<span font_weight="bold" size="170%">$2</span>' },
|
||||
{ name: 'H2', re: /^(##\s+)(.*)(\s*)$/, sub: '<span font_weight="bold" size="150%">$2</span>' },
|
||||
{ name: 'H3', re: /^(###\s+)(.*)(\s*)$/, sub: '<span font_weight="bold" size="125%">$2</span>' },
|
||||
{ name: 'H4', re: /^(####\s+)(.*)(\s*)$/, sub: '<span font_weight="bold" size="100%">$2</span>' },
|
||||
{ name: 'H5', re: /^(#####\s+)(.*)(\s*)$/, sub: '<span font_weight="bold" size="90%">$2</span>' },
|
||||
],
|
||||
'styles': [
|
||||
{ name: 'BOLD', re: /(\*\*)(\S[\s\S]*?\S)(\*\*)/g, sub: "<b>$2</b>" },
|
||||
{ name: 'UND', re: /(__)(\S[\s\S]*?\S)(__)/g, sub: "<u>$2</u>" },
|
||||
{ name: 'EMPH', re: /\*(\S.*?\S)\*/g, sub: "<i>$1</i>" },
|
||||
// { name: 'EMPH', re: /_(\S.*?\S)_/g, sub: "<i>$1</i>" },
|
||||
{ name: 'HEXCOLOR', re: /#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/g, sub: '<span bgcolor="#$1" fgcolor="#000000" font_family="' + monospaceFonts + '">#$1</span>' },
|
||||
{ name: 'INLCODE', re: /(`)([^`]*)(`)/g, sub: '<span font_weight="bold" font_family="' + monospaceFonts + '">$2</span>' },
|
||||
// { name: 'UND', re: /(__|\*\*)(\S[\s\S]*?\S)(__|\*\*)/g, sub: "<u>$2</u>" },
|
||||
],
|
||||
}
|
||||
|
||||
const replaceCategory = (text, replaces) => {
|
||||
for (const type of replaces) {
|
||||
text = text.replace(type.re, type.sub);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
// Main function
|
||||
|
||||
export default (text) => {
|
||||
let lines = text.split('\n')
|
||||
let output = [];
|
||||
// Replace
|
||||
for (const line of lines) {
|
||||
let result = line;
|
||||
result = replaceCategory(result, replacements.indents);
|
||||
result = replaceCategory(result, replacements.escapes);
|
||||
result = replaceCategory(result, replacements.sections);
|
||||
result = replaceCategory(result, replacements.styles);
|
||||
output.push(result)
|
||||
}
|
||||
// Remove trailing whitespaces
|
||||
output = output.map(line => line.replace(/ +$/, ''))
|
||||
return output.join('\n');
|
||||
}
|
||||
|
||||
export const markdownTest = `## Inline formatting
|
||||
- **Bold** *Italics* __Underline__
|
||||
- \`Monospace text\` 🤓
|
||||
- Colors
|
||||
- Nvidia green #7ABB08
|
||||
- Soundcloud orange #FF5500
|
||||
## Code block
|
||||
\`\`\`cpp
|
||||
#include <bits/stdc++.h>
|
||||
const std::string GREETING="UwU";
|
||||
int main() { std::cout << GREETING; }
|
||||
\`\`\`
|
||||
## LaTeX
|
||||
\`\`\`latex
|
||||
\\frac{d}{dx} \\left( \\frac{x-438}{x^2+23x-7} \\right) = \\frac{-x^2 + 869}{(x^2+23x-7)^2} \\\\ → \\\\ cos(2x) = 2cos^2(x) - 1 = 1 - 2sin^2(x) = cos^2(x) - sin^2(x)
|
||||
\`\`\`
|
||||
`;
|
||||
61
homes/me/ags-end4/modules/.miscutils/system.js
Normal file
61
homes/me/ags-end4/modules/.miscutils/system.js
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
const { GLib } = imports.gi;
|
||||
import Variable from 'resource:///com/github/Aylur/ags/variable.js';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
const { execAsync, exec } = Utils;
|
||||
|
||||
export const distroID = exec(`bash -c 'cat /etc/os-release | grep "^ID=" | cut -d "=" -f 2 | sed "s/\\"//g"'`).trim();
|
||||
export const isDebianDistro = (distroID == 'linuxmint' || distroID == 'ubuntu' || distroID == 'debian' || distroID == 'zorin' || distroID == 'popos' || distroID == 'raspbian' || distroID == 'kali');
|
||||
export const isArchDistro = (distroID == 'arch' || distroID == 'endeavouros' || distroID == 'cachyos');
|
||||
export const hasFlatpak = !!exec(`bash -c 'command -v flatpak'`);
|
||||
|
||||
const LIGHTDARK_FILE_LOCATION = `${GLib.get_user_state_dir()}/ags/user/colormode.txt`;
|
||||
export const darkMode = Variable(!(Utils.readFile(LIGHTDARK_FILE_LOCATION).split('\n')[0].trim() == 'light'));
|
||||
darkMode.connect('changed', ({ value }) => {
|
||||
let lightdark = value ? "dark" : "light";
|
||||
execAsync([`bash`, `-c`, `mkdir -p ${GLib.get_user_state_dir()}/ags/user && sed -i "1s/.*/${lightdark}/" ${GLib.get_user_state_dir()}/ags/user/colormode.txt`])
|
||||
.then(execAsync(['bash', '-c', `${App.configDir}/scripts/color_generation/switchcolor.sh`]))
|
||||
.then(execAsync(['bash', '-c', `command -v darkman && darkman set ${lightdark}`])) // Optional darkman integration
|
||||
.catch(print);
|
||||
});
|
||||
globalThis['darkMode'] = darkMode;
|
||||
export const hasPlasmaIntegration = !!Utils.exec('bash -c "command -v plasma-browser-integration-host"');
|
||||
|
||||
export const getDistroIcon = () => {
|
||||
// Arches
|
||||
if(distroID == 'arch') return 'arch-symbolic';
|
||||
if(distroID == 'endeavouros') return 'endeavouros-symbolic';
|
||||
if(distroID == 'cachyos') return 'cachyos-symbolic';
|
||||
// Funny flake
|
||||
if(distroID == 'nixos') return 'nixos-symbolic';
|
||||
// Cool thing
|
||||
if(distroID == 'fedora') return 'fedora-symbolic';
|
||||
// Debians
|
||||
if(distroID == 'linuxmint') return 'ubuntu-symbolic';
|
||||
if(distroID == 'ubuntu') return 'ubuntu-symbolic';
|
||||
if(distroID == 'debian') return 'debian-symbolic';
|
||||
if(distroID == 'zorin') return 'ubuntu-symbolic';
|
||||
if(distroID == 'popos') return 'ubuntu-symbolic';
|
||||
if(distroID == 'raspbian') return 'debian-symbolic';
|
||||
if(distroID == 'kali') return 'debian-symbolic';
|
||||
return 'linux-symbolic';
|
||||
}
|
||||
|
||||
export const getDistroName = () => {
|
||||
// Arches
|
||||
if(distroID == 'arch') return 'Arch Linux';
|
||||
if(distroID == 'endeavouros') return 'EndeavourOS';
|
||||
if(distroID == 'cachyos') return 'CachyOS';
|
||||
// Funny flake
|
||||
if(distroID == 'nixos') return 'NixOS';
|
||||
// Cool thing
|
||||
if(distroID == 'fedora') return 'Fedora';
|
||||
// Debians
|
||||
if(distroID == 'linuxmint') return 'Linux Mint';
|
||||
if(distroID == 'ubuntu') return 'Ubuntu';
|
||||
if(distroID == 'debian') return 'Debian';
|
||||
if(distroID == 'zorin') return 'Zorin';
|
||||
if(distroID == 'popos') return 'Pop!_OS';
|
||||
if(distroID == 'raspbian') return 'Raspbian';
|
||||
if(distroID == 'kali') return 'Kali Linux';
|
||||
return 'Linux';
|
||||
}
|
||||
86
homes/me/ags-end4/modules/.widgethacks/advancedrevealers.js
Normal file
86
homes/me/ags-end4/modules/.widgethacks/advancedrevealers.js
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
|
||||
const { Revealer, Scrollable } = Widget;
|
||||
|
||||
export const MarginRevealer = ({
|
||||
transition = 'slide_down',
|
||||
child,
|
||||
revealChild,
|
||||
showClass = 'element-show', // These are for animation curve, they don't really hide
|
||||
hideClass = 'element-hide', // Don't put margins in these classes!
|
||||
extraSetup = () => { },
|
||||
...rest
|
||||
}) => {
|
||||
const widget = Scrollable({
|
||||
...rest,
|
||||
attribute: {
|
||||
'revealChild': true, // It'll be set to false after init if it's supposed to hide
|
||||
'transition': transition,
|
||||
'show': () => {
|
||||
if (widget.attribute.revealChild) return;
|
||||
widget.hscroll = 'never';
|
||||
widget.vscroll = 'never';
|
||||
child.toggleClassName(hideClass, false);
|
||||
child.toggleClassName(showClass, true);
|
||||
widget.attribute.revealChild = true;
|
||||
child.css = 'margin: 0px;';
|
||||
},
|
||||
'hide': () => {
|
||||
if (!widget.attribute.revealChild) return;
|
||||
child.toggleClassName(hideClass, true);
|
||||
child.toggleClassName(showClass, false);
|
||||
widget.attribute.revealChild = false;
|
||||
if (widget.attribute.transition == 'slide_left')
|
||||
child.css = `margin-right: -${child.get_allocated_width()}px;`;
|
||||
else if (widget.attribute.transition == 'slide_right')
|
||||
child.css = `margin-left: -${child.get_allocated_width()}px;`;
|
||||
else if (widget.attribute.transition == 'slide_up')
|
||||
child.css = `margin-bottom: -${child.get_allocated_height()}px;`;
|
||||
else if (widget.attribute.transition == 'slide_down')
|
||||
child.css = `margin-top: -${child.get_allocated_height()}px;`;
|
||||
},
|
||||
'toggle': () => {
|
||||
if (widget.attribute.revealChild) widget.attribute.hide();
|
||||
else widget.attribute.show();
|
||||
},
|
||||
},
|
||||
child: child,
|
||||
hscroll: `${revealChild ? 'never' : 'always'}`,
|
||||
vscroll: `${revealChild ? 'never' : 'always'}`,
|
||||
setup: (self) => {
|
||||
extraSetup(self);
|
||||
}
|
||||
});
|
||||
child.toggleClassName(`${revealChild ? showClass : hideClass}`, true);
|
||||
return widget;
|
||||
}
|
||||
|
||||
// TODO: Allow reveal update. Currently this just helps at declaration
|
||||
export const DoubleRevealer = ({
|
||||
transition1 = 'slide_right',
|
||||
transition2 = 'slide_left',
|
||||
duration1 = 150,
|
||||
duration2 = 150,
|
||||
child,
|
||||
revealChild,
|
||||
...rest
|
||||
}) => {
|
||||
const r2 = Revealer({
|
||||
transition: transition2,
|
||||
transitionDuration: duration2,
|
||||
revealChild: revealChild,
|
||||
child: child,
|
||||
});
|
||||
const r1 = Revealer({
|
||||
transition: transition1,
|
||||
transitionDuration: duration1,
|
||||
revealChild: revealChild,
|
||||
child: r2,
|
||||
...rest,
|
||||
})
|
||||
r1.toggleRevealChild = (value) => {
|
||||
r1.revealChild = value;
|
||||
r2.revealChild = value;
|
||||
}
|
||||
return r1;
|
||||
}
|
||||
36
homes/me/ags-end4/modules/.widgethacks/popupwindow.js
Normal file
36
homes/me/ags-end4/modules/.widgethacks/popupwindow.js
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import App from 'resource:///com/github/Aylur/ags/app.js';
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
const { Box, Window } = Widget;
|
||||
|
||||
|
||||
export default ({
|
||||
name,
|
||||
child,
|
||||
showClassName = "",
|
||||
hideClassName = "",
|
||||
...props
|
||||
}) => {
|
||||
return Window({
|
||||
name,
|
||||
visible: false,
|
||||
layer: 'top',
|
||||
...props,
|
||||
|
||||
child: Box({
|
||||
setup: (self) => {
|
||||
self.keybind("Escape", () => closeEverything());
|
||||
if (showClassName != "" && hideClassName !== "") {
|
||||
self.hook(App, (self, currentName, visible) => {
|
||||
if (currentName === name) {
|
||||
self.toggleClassName(hideClassName, !visible);
|
||||
}
|
||||
});
|
||||
|
||||
if (showClassName !== "" && hideClassName !== "")
|
||||
self.className = `${showClassName} ${hideClassName}`;
|
||||
}
|
||||
},
|
||||
child: child,
|
||||
}),
|
||||
});
|
||||
}
|
||||
4
homes/me/ags-end4/modules/.widgetutils/clickthrough.js
Normal file
4
homes/me/ags-end4/modules/.widgetutils/clickthrough.js
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
import Cairo from 'gi://cairo?version=1.0';
|
||||
|
||||
export const dummyRegion = new Cairo.Region();
|
||||
export const enableClickthrough = (self) => self.input_shape_combine_region(dummyRegion);
|
||||
57
homes/me/ags-end4/modules/.widgetutils/cursorhover.js
Normal file
57
homes/me/ags-end4/modules/.widgetutils/cursorhover.js
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
const { Gdk } = imports.gi;
|
||||
|
||||
export function setupCursorHover(button) { // Hand pointing cursor on hover
|
||||
const display = Gdk.Display.get_default();
|
||||
button.connect('enter-notify-event', () => {
|
||||
const cursor = Gdk.Cursor.new_from_name(display, 'pointer');
|
||||
button.get_window().set_cursor(cursor);
|
||||
});
|
||||
|
||||
button.connect('leave-notify-event', () => {
|
||||
const cursor = Gdk.Cursor.new_from_name(display, 'default');
|
||||
button.get_window().set_cursor(cursor);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
export function setupCursorHoverAim(button) { // Crosshair cursor on hover
|
||||
button.connect('enter-notify-event', () => {
|
||||
const display = Gdk.Display.get_default();
|
||||
const cursor = Gdk.Cursor.new_from_name(display, 'crosshair');
|
||||
button.get_window().set_cursor(cursor);
|
||||
});
|
||||
|
||||
button.connect('leave-notify-event', () => {
|
||||
const display = Gdk.Display.get_default();
|
||||
const cursor = Gdk.Cursor.new_from_name(display, 'default');
|
||||
button.get_window().set_cursor(cursor);
|
||||
});
|
||||
}
|
||||
|
||||
export function setupCursorHoverGrab(button) { // Hand ready to grab on hover
|
||||
button.connect('enter-notify-event', () => {
|
||||
const display = Gdk.Display.get_default();
|
||||
const cursor = Gdk.Cursor.new_from_name(display, 'grab');
|
||||
button.get_window().set_cursor(cursor);
|
||||
});
|
||||
|
||||
button.connect('leave-notify-event', () => {
|
||||
const display = Gdk.Display.get_default();
|
||||
const cursor = Gdk.Cursor.new_from_name(display, 'default');
|
||||
button.get_window().set_cursor(cursor);
|
||||
});
|
||||
}
|
||||
|
||||
export function setupCursorHoverInfo(button) { // "?" mark cursor on hover
|
||||
const display = Gdk.Display.get_default();
|
||||
button.connect('enter-notify-event', () => {
|
||||
const cursor = Gdk.Cursor.new_from_name(display, 'help');
|
||||
button.get_window().set_cursor(cursor);
|
||||
});
|
||||
|
||||
button.connect('leave-notify-event', () => {
|
||||
const cursor = Gdk.Cursor.new_from_name(display, 'default');
|
||||
button.get_window().set_cursor(cursor);
|
||||
});
|
||||
}
|
||||
|
||||
25
homes/me/ags-end4/modules/.widgetutils/keybind.js
Normal file
25
homes/me/ags-end4/modules/.widgetutils/keybind.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
const { Gdk } = imports.gi;
|
||||
|
||||
const MODS = {
|
||||
'Shift': Gdk.ModifierType.SHIFT_MASK,
|
||||
'Ctrl': Gdk.ModifierType.CONTROL_MASK,
|
||||
'Alt': Gdk.ModifierType.ALT_MASK,
|
||||
'Hyper': Gdk.ModifierType.HYPER_MASK,
|
||||
'Meta': Gdk.ModifierType.META_MASK
|
||||
}
|
||||
|
||||
export const checkKeybind = (event, keybind) => {
|
||||
const pressedModMask = event.get_state()[1];
|
||||
const pressedKey = event.get_keyval()[1];
|
||||
const keys = keybind.split('+');
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
if (keys[i] in MODS) {
|
||||
if (!(pressedModMask & MODS[keys[i]])) {
|
||||
return false;
|
||||
}
|
||||
} else if (pressedKey !== Gdk[`KEY_${keys[i]}`]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
213
homes/me/ags-end4/modules/bar/focus/workspaces_hyprland.js
Normal file
213
homes/me/ags-end4/modules/bar/focus/workspaces_hyprland.js
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
const { GLib, Gdk, Gtk } = imports.gi;
|
||||
const Lang = imports.lang;
|
||||
const Cairo = imports.cairo;
|
||||
const Pango = imports.gi.Pango;
|
||||
const PangoCairo = imports.gi.PangoCairo;
|
||||
import App from 'resource:///com/github/Aylur/ags/app.js';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
const { Box, DrawingArea, EventBox } = Widget;
|
||||
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
|
||||
|
||||
const dummyWs = Box({ className: 'bar-ws-focus' }); // Not shown. Only for getting size props
|
||||
const dummyActiveWs = Box({ className: 'bar-ws-focus bar-ws-focus-active' }); // Not shown. Only for getting size props
|
||||
const dummyOccupiedWs = Box({ className: 'bar-ws-focus bar-ws-focus-occupied' }); // Not shown. Only for getting size props
|
||||
|
||||
const WS_TAKEN_WIDTH_MULTIPLIER = 1.4;
|
||||
const floor = Math.floor;
|
||||
const ceil = Math.ceil;
|
||||
|
||||
// Font size = workspace id
|
||||
const WorkspaceContents = (count = 10) => {
|
||||
return DrawingArea({
|
||||
className: 'menu-decel',
|
||||
attribute: {
|
||||
lastImmediateActiveWs: 0,
|
||||
immediateActiveWs: 0,
|
||||
initialized: false,
|
||||
workspaceMask: 0,
|
||||
workspaceGroup: 0,
|
||||
updateMask: (self) => {
|
||||
const offset = Math.floor((Hyprland.active.workspace.id - 1) / count) * userOptions.workspaces.shown;
|
||||
// if (self.attribute.initialized) return; // We only need this to run once
|
||||
const workspaces = Hyprland.workspaces;
|
||||
let workspaceMask = 0;
|
||||
for (let i = 0; i < workspaces.length; i++) {
|
||||
const ws = workspaces[i];
|
||||
if (ws.id <= offset || ws.id > offset + count) continue; // Out of range, ignore
|
||||
if (workspaces[i].windows > 0)
|
||||
workspaceMask |= (1 << (ws.id - offset));
|
||||
}
|
||||
// console.log('Mask:', workspaceMask.toString(2));
|
||||
self.attribute.workspaceMask = workspaceMask;
|
||||
// self.attribute.initialized = true;
|
||||
self.queue_draw();
|
||||
},
|
||||
toggleMask: (self, occupied, name) => {
|
||||
if (occupied) self.attribute.workspaceMask |= (1 << parseInt(name));
|
||||
else self.attribute.workspaceMask &= ~(1 << parseInt(name));
|
||||
self.queue_draw();
|
||||
},
|
||||
},
|
||||
setup: (area) => area
|
||||
.hook(Hyprland.active.workspace, (self) => {
|
||||
const newActiveWs = (Hyprland.active.workspace.id - 1) % count + 1;
|
||||
self.setCss(`font-size: ${newActiveWs}px;`);
|
||||
self.attribute.lastImmediateActiveWs = self.attribute.immediateActiveWs;
|
||||
self.attribute.immediateActiveWs = newActiveWs;
|
||||
const previousGroup = self.attribute.workspaceGroup;
|
||||
const currentGroup = Math.floor((Hyprland.active.workspace.id - 1) / count);
|
||||
if (currentGroup !== previousGroup) {
|
||||
self.attribute.updateMask(self);
|
||||
self.attribute.workspaceGroup = currentGroup;
|
||||
}
|
||||
})
|
||||
.hook(Hyprland, (self) => self.attribute.updateMask(self), 'notify::workspaces')
|
||||
.on('draw', Lang.bind(area, (area, cr) => {
|
||||
const offset = Math.floor((Hyprland.active.workspace.id - 1) / count) * userOptions.workspaces.shown;
|
||||
|
||||
const allocation = area.get_allocation();
|
||||
const { width, height } = allocation;
|
||||
|
||||
const workspaceStyleContext = dummyWs.get_style_context();
|
||||
const workspaceDiameter = workspaceStyleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
|
||||
const workspaceRadius = workspaceDiameter / 2;
|
||||
const wsbg = workspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
|
||||
|
||||
const occupiedWorkspaceStyleContext = dummyOccupiedWs.get_style_context();
|
||||
const occupiedbg = occupiedWorkspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
|
||||
|
||||
const activeWorkspaceStyleContext = dummyActiveWs.get_style_context();
|
||||
const activeWorkspaceWidth = activeWorkspaceStyleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
|
||||
// const activeWorkspaceWidth = 100;
|
||||
const activebg = activeWorkspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
|
||||
|
||||
const widgetStyleContext = area.get_style_context();
|
||||
const activeWs = widgetStyleContext.get_property('font-size', Gtk.StateFlags.NORMAL);
|
||||
const lastImmediateActiveWs = area.attribute.lastImmediateActiveWs;
|
||||
const immediateActiveWs = area.attribute.immediateActiveWs;
|
||||
|
||||
// Draw
|
||||
area.set_size_request(workspaceDiameter * WS_TAKEN_WIDTH_MULTIPLIER * (count - 1) + activeWorkspaceWidth, -1);
|
||||
for (let i = 1; i <= count; i++) {
|
||||
if (i == immediateActiveWs) continue;
|
||||
let colors = {};
|
||||
if (area.attribute.workspaceMask & (1 << i)) colors = occupiedbg;
|
||||
else colors = wsbg;
|
||||
|
||||
// if ((i == immediateActiveWs + 1 && immediateActiveWs < activeWs) ||
|
||||
// (i == immediateActiveWs + 1 && immediateActiveWs < activeWs)) {
|
||||
// const widthPercentage = (i == immediateActiveWs - 1) ?
|
||||
// 1 - (immediateActiveWs - activeWs) :
|
||||
// activeWs - immediateActiveWs;
|
||||
// cr.setSourceRGBA(colors.red * widthPercentage + activebg.red * (1 - widthPercentage),
|
||||
// colors.green * widthPercentage + activebg.green * (1 - widthPercentage),
|
||||
// colors.blue * widthPercentage + activebg.blue * (1 - widthPercentage),
|
||||
// colors.alpha);
|
||||
// }
|
||||
// else
|
||||
cr.setSourceRGBA(colors.red, colors.green, colors.blue, colors.alpha)
|
||||
|
||||
const centerX = (i <= activeWs) ?
|
||||
(-workspaceRadius + (workspaceDiameter * WS_TAKEN_WIDTH_MULTIPLIER * i))
|
||||
: -workspaceRadius + workspaceDiameter * WS_TAKEN_WIDTH_MULTIPLIER * (count - 1) + activeWorkspaceWidth - ((count - i) * workspaceDiameter * WS_TAKEN_WIDTH_MULTIPLIER);
|
||||
cr.arc(centerX, height / 2, workspaceRadius, 0, 2 * Math.PI);
|
||||
cr.fill();
|
||||
// What if shrinking
|
||||
if (i == floor(activeWs) && immediateActiveWs > activeWs) { // To right
|
||||
const widthPercentage = 1 - (ceil(activeWs) - activeWs);
|
||||
const leftX = centerX;
|
||||
const wsWidth = (activeWorkspaceWidth - (workspaceDiameter * 1.5)) * (1 - widthPercentage);
|
||||
cr.rectangle(leftX, height / 2 - workspaceRadius, wsWidth, workspaceDiameter);
|
||||
cr.fill();
|
||||
cr.arc(leftX + wsWidth, height / 2, workspaceRadius, 0, Math.PI * 2);
|
||||
cr.fill();
|
||||
}
|
||||
else if (i == ceil(activeWs) && immediateActiveWs < activeWs) { // To left
|
||||
const widthPercentage = activeWs - floor(activeWs);
|
||||
const rightX = centerX;
|
||||
const wsWidth = (activeWorkspaceWidth - (workspaceDiameter * 1.5)) * widthPercentage;
|
||||
const leftX = rightX - wsWidth;
|
||||
cr.rectangle(leftX, height / 2 - workspaceRadius, wsWidth, workspaceDiameter);
|
||||
cr.fill();
|
||||
cr.arc(leftX, height / 2, workspaceRadius, 0, Math.PI * 2);
|
||||
cr.fill();
|
||||
}
|
||||
}
|
||||
|
||||
let widthPercentage, leftX, rightX, activeWsWidth;
|
||||
cr.setSourceRGBA(activebg.red, activebg.green, activebg.blue, activebg.alpha);
|
||||
if (immediateActiveWs > activeWs) { // To right
|
||||
const immediateActiveWs = ceil(activeWs);
|
||||
widthPercentage = immediateActiveWs - activeWs;
|
||||
rightX = -workspaceRadius + workspaceDiameter * WS_TAKEN_WIDTH_MULTIPLIER * (count - 1) + activeWorkspaceWidth - ((count - immediateActiveWs) * workspaceDiameter * WS_TAKEN_WIDTH_MULTIPLIER);
|
||||
activeWsWidth = (activeWorkspaceWidth - (workspaceDiameter * 1.5)) * (1 - widthPercentage);
|
||||
leftX = rightX - activeWsWidth;
|
||||
|
||||
cr.arc(leftX, height / 2, workspaceRadius, 0, Math.PI * 2); // Should be 0.5 * Math.PI, 1.5 * Math.PI in theory but it leaves a weird 1px gap
|
||||
cr.fill();
|
||||
cr.rectangle(leftX, height / 2 - workspaceRadius, activeWsWidth, workspaceDiameter);
|
||||
cr.fill();
|
||||
cr.arc(leftX + activeWsWidth, height / 2, workspaceRadius, 0, Math.PI * 2);
|
||||
cr.fill();
|
||||
}
|
||||
else { // To left
|
||||
const immediateActiveWs = floor(activeWs);
|
||||
widthPercentage = 1 - (activeWs - immediateActiveWs);
|
||||
leftX = -workspaceRadius + (workspaceDiameter * WS_TAKEN_WIDTH_MULTIPLIER * immediateActiveWs);
|
||||
activeWsWidth = (activeWorkspaceWidth - (workspaceDiameter * 1.5)) * widthPercentage
|
||||
|
||||
cr.arc(leftX, height / 2, workspaceRadius, 0, Math.PI * 2); // Should be 0.5 * Math.PI, 1.5 * Math.PI in theory but it leaves a weird 1px gap
|
||||
cr.fill();
|
||||
cr.rectangle(leftX, height / 2 - workspaceRadius, activeWsWidth, workspaceDiameter);
|
||||
cr.fill();
|
||||
cr.arc(leftX + activeWsWidth, height / 2, workspaceRadius, 0, Math.PI * 2);
|
||||
cr.fill();
|
||||
}
|
||||
}))
|
||||
,
|
||||
})
|
||||
}
|
||||
|
||||
export default () => EventBox({
|
||||
onScrollUp: () => Hyprland.messageAsync(`dispatch workspace -1`).catch(print),
|
||||
onScrollDown: () => Hyprland.messageAsync(`dispatch workspace +1`).catch(print),
|
||||
onMiddleClick: () => toggleWindowOnAllMonitors('osk'),
|
||||
onSecondaryClick: () => App.toggleWindow('overview'),
|
||||
attribute: {
|
||||
clicked: false,
|
||||
ws_group: 0,
|
||||
},
|
||||
child: Box({
|
||||
homogeneous: true,
|
||||
// className: 'bar-group-margin',
|
||||
children: [Box({
|
||||
// className: 'bar-group bar-group-standalone bar-group-pad',
|
||||
css: 'min-width: 2px;',
|
||||
children: [WorkspaceContents(userOptions.workspaces.shown)],
|
||||
})]
|
||||
}),
|
||||
setup: (self) => {
|
||||
self.add_events(Gdk.EventMask.POINTER_MOTION_MASK);
|
||||
self.on('motion-notify-event', (self, event) => {
|
||||
if (!self.attribute.clicked) return;
|
||||
const [_, cursorX, cursorY] = event.get_coords();
|
||||
const widgetWidth = self.get_allocation().width;
|
||||
const wsId = Math.ceil(cursorX * userOptions.workspaces.shown / widgetWidth);
|
||||
Utils.execAsync([`${App.configDir}/scripts/hyprland/workspace_action.sh`, 'workspace', `${wsId}`])
|
||||
.catch(print);
|
||||
})
|
||||
self.on('button-press-event', (self, event) => {
|
||||
if (!(event.get_button()[1] === 1)) return; // We're only interested in left-click here
|
||||
self.attribute.clicked = true;
|
||||
const [_, cursorX, cursorY] = event.get_coords();
|
||||
const widgetWidth = self.get_allocation().width;
|
||||
// const wsId = Math.ceil(cursorX * NUM_OF_WORKSPACES_PER_GROUP / widgetWidth) + self.attribute.ws_group * NUM_OF_WORKSPACES_PER_GROUP;
|
||||
// Hyprland.messageAsync(`dispatch workspace ${wsId}`).catch(print);
|
||||
const wsId = Math.ceil(cursorX * userOptions.workspaces.shown / widgetWidth);
|
||||
Utils.execAsync([`${App.configDir}/scripts/hyprland/workspace_action.sh`, 'workspace', `${wsId}`])
|
||||
.catch(print);
|
||||
})
|
||||
self.on('button-release-event', (self) => self.attribute.clicked = false);
|
||||
}
|
||||
})
|
||||
183
homes/me/ags-end4/modules/bar/focus/workspaces_sway.js
Normal file
183
homes/me/ags-end4/modules/bar/focus/workspaces_sway.js
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
const { GLib, Gdk, Gtk } = imports.gi;
|
||||
const Lang = imports.lang;
|
||||
const Cairo = imports.cairo;
|
||||
const Pango = imports.gi.Pango;
|
||||
const PangoCairo = imports.gi.PangoCairo;
|
||||
import Widget from "resource:///com/github/Aylur/ags/widget.js";
|
||||
import Sway from "../../../services/sway.js";
|
||||
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
|
||||
const { execAsync, exec } = Utils;
|
||||
const { Box, DrawingArea, EventBox } = Widget;
|
||||
|
||||
const dummyWs = Box({ className: 'bar-ws' }); // Not shown. Only for getting size props
|
||||
const dummyActiveWs = Box({ className: 'bar-ws bar-ws-active' }); // Not shown. Only for getting size props
|
||||
const dummyOccupiedWs = Box({ className: 'bar-ws bar-ws-occupied' }); // Not shown. Only for getting size props
|
||||
|
||||
const switchToWorkspace = (arg) => Utils.execAsync(`swaymsg workspace ${arg}`).catch(print);
|
||||
const switchToRelativeWorkspace = (self, num) =>
|
||||
execAsync([`${App.configDir}/scripts/sway/swayToRelativeWs.sh`, `${num}`]).catch(print);
|
||||
|
||||
const WorkspaceContents = (count = 10) => {
|
||||
return DrawingArea({
|
||||
css: `transition: 90ms cubic-bezier(0.1, 1, 0, 1);`,
|
||||
attribute: {
|
||||
initialized: false,
|
||||
workspaceMask: 0,
|
||||
updateMask: (self) => {
|
||||
if (self.attribute.initialized) return; // We only need this to run once
|
||||
const workspaces = Sway.workspaces;
|
||||
let workspaceMask = 0;
|
||||
// console.log('----------------')
|
||||
for (let i = 0; i < workspaces.length; i++) {
|
||||
const ws = workspaces[i];
|
||||
// console.log(ws.name, ',', ws.num);
|
||||
if (!Number(ws.name)) return;
|
||||
const id = Number(ws.name);
|
||||
if (id <= 0) continue; // Ignore scratchpads
|
||||
if (id > count) return; // Not rendered
|
||||
if (workspaces[i].windows > 0) {
|
||||
workspaceMask |= (1 << id);
|
||||
}
|
||||
}
|
||||
self.attribute.workspaceMask = workspaceMask;
|
||||
self.attribute.initialized = true;
|
||||
},
|
||||
toggleMask: (self, occupied, name) => {
|
||||
if (occupied) self.attribute.workspaceMask |= (1 << parseInt(name));
|
||||
else self.attribute.workspaceMask &= ~(1 << parseInt(name));
|
||||
},
|
||||
},
|
||||
setup: (area) => area
|
||||
.hook(Sway.active.workspace, (area) => {
|
||||
area.setCss(`font-size: ${Sway.active.workspace.name}px;`)
|
||||
})
|
||||
.hook(Sway, (self) => self.attribute.updateMask(self), 'notify::workspaces')
|
||||
// .hook(Hyprland, (self, name) => self.attribute.toggleMask(self, true, name), 'workspace-added')
|
||||
// .hook(Hyprland, (self, name) => self.attribute.toggleMask(self, false, name), 'workspace-removed')
|
||||
.on('draw', Lang.bind(area, (area, cr) => {
|
||||
const allocation = area.get_allocation();
|
||||
const { width, height } = allocation;
|
||||
|
||||
const workspaceStyleContext = dummyWs.get_style_context();
|
||||
const workspaceDiameter = workspaceStyleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
|
||||
const workspaceRadius = workspaceDiameter / 2;
|
||||
const workspaceFontSize = workspaceStyleContext.get_property('font-size', Gtk.StateFlags.NORMAL) / 4 * 3;
|
||||
const workspaceFontFamily = workspaceStyleContext.get_property('font-family', Gtk.StateFlags.NORMAL);
|
||||
const wsbg = workspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
|
||||
const wsfg = workspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL);
|
||||
|
||||
const occupiedWorkspaceStyleContext = dummyOccupiedWs.get_style_context();
|
||||
const occupiedbg = occupiedWorkspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
|
||||
const occupiedfg = occupiedWorkspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL);
|
||||
|
||||
const activeWorkspaceStyleContext = dummyActiveWs.get_style_context();
|
||||
const activebg = activeWorkspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
|
||||
const activefg = activeWorkspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL);
|
||||
area.set_size_request(workspaceDiameter * count, -1);
|
||||
const widgetStyleContext = area.get_style_context();
|
||||
const activeWs = widgetStyleContext.get_property('font-size', Gtk.StateFlags.NORMAL);
|
||||
|
||||
const activeWsCenterX = -(workspaceDiameter / 2) + (workspaceDiameter * activeWs);
|
||||
const activeWsCenterY = height / 2;
|
||||
|
||||
// Font
|
||||
const layout = PangoCairo.create_layout(cr);
|
||||
const fontDesc = Pango.font_description_from_string(`${workspaceFontFamily[0]} ${workspaceFontSize}`);
|
||||
layout.set_font_description(fontDesc);
|
||||
cr.setAntialias(Cairo.Antialias.BEST);
|
||||
// Get kinda min radius for number indicators
|
||||
layout.set_text("0".repeat(count.toString().length), -1);
|
||||
const [layoutWidth, layoutHeight] = layout.get_pixel_size();
|
||||
const indicatorRadius = Math.max(layoutWidth, layoutHeight) / 2 * 1.2; // a bit smaller than sqrt(2)*radius
|
||||
const indicatorGap = workspaceRadius - indicatorRadius;
|
||||
|
||||
// Draw workspace numbers
|
||||
for (let i = 1; i <= count; i++) {
|
||||
if (area.attribute.workspaceMask & (1 << i)) {
|
||||
// Draw bg highlight
|
||||
cr.setSourceRGBA(occupiedbg.red, occupiedbg.green, occupiedbg.blue, occupiedbg.alpha);
|
||||
const wsCenterX = -(workspaceRadius) + (workspaceDiameter * i);
|
||||
const wsCenterY = height / 2;
|
||||
if (!(area.attribute.workspaceMask & (1 << (i - 1)))) { // Left
|
||||
cr.arc(wsCenterX, wsCenterY, workspaceRadius, 0.5 * Math.PI, 1.5 * Math.PI);
|
||||
cr.fill();
|
||||
}
|
||||
else {
|
||||
cr.rectangle(wsCenterX - workspaceRadius, wsCenterY - workspaceRadius, workspaceRadius, workspaceRadius * 2)
|
||||
cr.fill();
|
||||
}
|
||||
if (!(area.attribute.workspaceMask & (1 << (i + 1)))) { // Right
|
||||
cr.arc(wsCenterX, wsCenterY, workspaceRadius, -0.5 * Math.PI, 0.5 * Math.PI);
|
||||
cr.fill();
|
||||
}
|
||||
else {
|
||||
cr.rectangle(wsCenterX, wsCenterY - workspaceRadius, workspaceRadius, workspaceRadius * 2)
|
||||
cr.fill();
|
||||
}
|
||||
|
||||
// Set color for text
|
||||
cr.setSourceRGBA(occupiedfg.red, occupiedfg.green, occupiedfg.blue, occupiedfg.alpha);
|
||||
}
|
||||
else
|
||||
cr.setSourceRGBA(wsfg.red, wsfg.green, wsfg.blue, wsfg.alpha);
|
||||
layout.set_text(`${i}`, -1);
|
||||
const [layoutWidth, layoutHeight] = layout.get_pixel_size();
|
||||
const x = -workspaceRadius + (workspaceDiameter * i) - (layoutWidth / 2);
|
||||
const y = (height - layoutHeight) / 2;
|
||||
cr.moveTo(x, y);
|
||||
// cr.showText(text);
|
||||
PangoCairo.show_layout(cr, layout);
|
||||
cr.stroke();
|
||||
}
|
||||
|
||||
// Draw active ws
|
||||
// base
|
||||
cr.setSourceRGBA(activebg.red, activebg.green, activebg.blue, activebg.alpha);
|
||||
cr.arc(activeWsCenterX, activeWsCenterY, indicatorRadius, 0, 2 * Math.PI);
|
||||
cr.fill();
|
||||
// inner decor
|
||||
cr.setSourceRGBA(activefg.red, activefg.green, activefg.blue, activefg.alpha);
|
||||
cr.arc(activeWsCenterX, activeWsCenterY, indicatorRadius * 0.2, 0, 2 * Math.PI);
|
||||
cr.fill();
|
||||
}))
|
||||
,
|
||||
})
|
||||
}
|
||||
|
||||
export default () => EventBox({
|
||||
onScrollUp: (self) => switchToRelativeWorkspace(self, -1),
|
||||
onScrollDown: (self) => switchToRelativeWorkspace(self, +1),
|
||||
onMiddleClick: () => toggleWindowOnAllMonitors('osk'),
|
||||
onSecondaryClick: () => App.toggleWindow('overview'),
|
||||
attribute: { clicked: false },
|
||||
child: Box({
|
||||
homogeneous: true,
|
||||
className: 'bar-group-margin',
|
||||
children: [Box({
|
||||
className: 'bar-group bar-group-standalone bar-group-pad',
|
||||
css: 'min-width: 2px;',
|
||||
children: [
|
||||
WorkspaceContents(10),
|
||||
]
|
||||
})]
|
||||
}),
|
||||
setup: (self) => {
|
||||
self.add_events(Gdk.EventMask.POINTER_MOTION_MASK);
|
||||
self.on('motion-notify-event', (self, event) => {
|
||||
if (!self.attribute.clicked) return;
|
||||
const [_, cursorX, cursorY] = event.get_coords();
|
||||
const widgetWidth = self.get_allocation().width;
|
||||
const wsId = Math.ceil(cursorX * userOptions.workspaces.shown / widgetWidth);
|
||||
switchToWorkspace(wsId);
|
||||
})
|
||||
self.on('button-press-event', (self, event) => {
|
||||
if (!(event.get_button()[1] === 1)) return; // We're only interested in left-click here
|
||||
self.attribute.clicked = true;
|
||||
const [_, cursorX, cursorY] = event.get_coords();
|
||||
const widgetWidth = self.get_allocation().width;
|
||||
const wsId = Math.ceil(cursorX * userOptions.workspaces.shown / widgetWidth);
|
||||
switchToWorkspace(wsId);
|
||||
})
|
||||
self.on('button-release-event', (self) => self.attribute.clicked = false);
|
||||
}
|
||||
});
|
||||
129
homes/me/ags-end4/modules/bar/main.js
Normal file
129
homes/me/ags-end4/modules/bar/main.js
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
const { Gtk } = imports.gi;
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import Battery from 'resource:///com/github/Aylur/ags/service/battery.js';
|
||||
|
||||
import WindowTitle from "./normal/spaceleft.js";
|
||||
import Indicators from "./normal/spaceright.js";
|
||||
import Music from "./normal/music.js";
|
||||
import System from "./normal/system.js";
|
||||
import { enableClickthrough } from "../.widgetutils/clickthrough.js";
|
||||
import { RoundedCorner } from "../.commonwidgets/cairo_roundedcorner.js";
|
||||
import { currentShellMode } from '../../variables.js';
|
||||
|
||||
const NormalOptionalWorkspaces = async () => {
|
||||
try {
|
||||
return (await import('./normal/workspaces_hyprland.js')).default();
|
||||
} catch {
|
||||
try {
|
||||
return (await import('./normal/workspaces_sway.js')).default();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const FocusOptionalWorkspaces = async () => {
|
||||
try {
|
||||
return (await import('./focus/workspaces_hyprland.js')).default();
|
||||
} catch {
|
||||
try {
|
||||
return (await import('./focus/workspaces_sway.js')).default();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const Bar = async (monitor = 0) => {
|
||||
const SideModule = (children) => Widget.Box({
|
||||
className: 'bar-sidemodule',
|
||||
children: children,
|
||||
});
|
||||
const normalBarContent = Widget.CenterBox({
|
||||
className: 'bar-bg',
|
||||
setup: (self) => {
|
||||
const styleContext = self.get_style_context();
|
||||
const minHeight = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
|
||||
// execAsync(['bash', '-c', `hyprctl keyword monitor ,addreserved,${minHeight},0,0,0`]).catch(print);
|
||||
},
|
||||
startWidget: (await WindowTitle(monitor)),
|
||||
centerWidget: Widget.Box({
|
||||
className: 'spacing-h-4',
|
||||
children: [
|
||||
SideModule([Music()]),
|
||||
Widget.Box({
|
||||
homogeneous: true,
|
||||
children: [await NormalOptionalWorkspaces()],
|
||||
}),
|
||||
SideModule([System()]),
|
||||
]
|
||||
}),
|
||||
endWidget: Indicators(monitor),
|
||||
});
|
||||
const focusedBarContent = Widget.CenterBox({
|
||||
className: 'bar-bg-focus',
|
||||
startWidget: Widget.Box({}),
|
||||
centerWidget: Widget.Box({
|
||||
className: 'spacing-h-4',
|
||||
children: [
|
||||
SideModule([]),
|
||||
Widget.Box({
|
||||
homogeneous: true,
|
||||
children: [await FocusOptionalWorkspaces()],
|
||||
}),
|
||||
SideModule([]),
|
||||
]
|
||||
}),
|
||||
endWidget: Widget.Box({}),
|
||||
setup: (self) => {
|
||||
self.hook(Battery, (self) => {
|
||||
if (!Battery.available) return;
|
||||
self.toggleClassName('bar-bg-focus-batterylow', Battery.percent <= userOptions.battery.low);
|
||||
})
|
||||
}
|
||||
});
|
||||
const nothingContent = Widget.Box({
|
||||
className: 'bar-bg-nothing',
|
||||
})
|
||||
return Widget.Window({
|
||||
monitor,
|
||||
name: `bar${monitor}`,
|
||||
anchor: ['top', 'left', 'right'],
|
||||
exclusivity: 'exclusive',
|
||||
visible: true,
|
||||
child: Widget.Stack({
|
||||
homogeneous: false,
|
||||
transition: 'slide_up_down',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
children: {
|
||||
'normal': normalBarContent,
|
||||
'focus': focusedBarContent,
|
||||
'nothing': nothingContent,
|
||||
},
|
||||
setup: (self) => self.hook(currentShellMode, (self) => {
|
||||
self.shown = currentShellMode.value[monitor];
|
||||
})
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
export const BarCornerTopleft = (monitor = 0) => Widget.Window({
|
||||
monitor,
|
||||
name: `barcornertl${monitor}`,
|
||||
layer: 'top',
|
||||
anchor: ['top', 'left'],
|
||||
exclusivity: 'normal',
|
||||
visible: true,
|
||||
child: RoundedCorner('topleft', { className: 'corner', }),
|
||||
setup: enableClickthrough,
|
||||
});
|
||||
export const BarCornerTopright = (monitor = 0) => Widget.Window({
|
||||
monitor,
|
||||
name: `barcornertr${monitor}`,
|
||||
layer: 'top',
|
||||
anchor: ['top', 'right'],
|
||||
exclusivity: 'normal',
|
||||
visible: true,
|
||||
child: RoundedCorner('topright', { className: 'corner', }),
|
||||
setup: enableClickthrough,
|
||||
});
|
||||
230
homes/me/ags-end4/modules/bar/normal/music.js
Normal file
230
homes/me/ags-end4/modules/bar/normal/music.js
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
const { GLib } = imports.gi;
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
import Mpris from 'resource:///com/github/Aylur/ags/service/mpris.js';
|
||||
const { Box, Button, EventBox, Label, Overlay, Revealer, Scrollable } = Widget;
|
||||
const { execAsync, exec } = Utils;
|
||||
import { AnimatedCircProg } from "../../.commonwidgets/cairo_circularprogress.js";
|
||||
import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
|
||||
import { showMusicControls } from '../../../variables.js';
|
||||
|
||||
const CUSTOM_MODULE_CONTENT_INTERVAL_FILE = `${GLib.get_user_cache_dir()}/ags/user/scripts/custom-module-interval.txt`;
|
||||
const CUSTOM_MODULE_CONTENT_SCRIPT = `${GLib.get_user_cache_dir()}/ags/user/scripts/custom-module-poll.sh`;
|
||||
const CUSTOM_MODULE_LEFTCLICK_SCRIPT = `${GLib.get_user_cache_dir()}/ags/user/scripts/custom-module-leftclick.sh`;
|
||||
const CUSTOM_MODULE_RIGHTCLICK_SCRIPT = `${GLib.get_user_cache_dir()}/ags/user/scripts/custom-module-rightclick.sh`;
|
||||
const CUSTOM_MODULE_MIDDLECLICK_SCRIPT = `${GLib.get_user_cache_dir()}/ags/user/scripts/custom-module-middleclick.sh`;
|
||||
const CUSTOM_MODULE_SCROLLUP_SCRIPT = `${GLib.get_user_cache_dir()}/ags/user/scripts/custom-module-scrollup.sh`;
|
||||
const CUSTOM_MODULE_SCROLLDOWN_SCRIPT = `${GLib.get_user_cache_dir()}/ags/user/scripts/custom-module-scrolldown.sh`;
|
||||
|
||||
function trimTrackTitle(title) {
|
||||
if (!title) return '';
|
||||
const cleanPatterns = [
|
||||
/【[^】]*】/, // Touhou n weeb stuff
|
||||
" [FREE DOWNLOAD]", // F-777
|
||||
];
|
||||
cleanPatterns.forEach((expr) => title = title.replace(expr, ''));
|
||||
return title;
|
||||
}
|
||||
|
||||
const BarGroup = ({ child }) => Box({
|
||||
className: 'bar-group-margin bar-sides',
|
||||
children: [
|
||||
Box({
|
||||
className: 'bar-group bar-group-standalone bar-group-pad-system',
|
||||
children: [child],
|
||||
}),
|
||||
]
|
||||
});
|
||||
|
||||
const BarResource = (name, icon, command, circprogClassName = 'bar-batt-circprog', textClassName = 'txt-onSurfaceVariant', iconClassName = 'bar-batt') => {
|
||||
const resourceCircProg = AnimatedCircProg({
|
||||
className: `${circprogClassName}`,
|
||||
vpack: 'center',
|
||||
hpack: 'center',
|
||||
});
|
||||
const resourceProgress = Box({
|
||||
homogeneous: true,
|
||||
children: [Overlay({
|
||||
child: Box({
|
||||
vpack: 'center',
|
||||
className: `${iconClassName}`,
|
||||
homogeneous: true,
|
||||
children: [
|
||||
MaterialIcon(icon, 'small'),
|
||||
],
|
||||
}),
|
||||
overlays: [resourceCircProg]
|
||||
})]
|
||||
});
|
||||
const resourceLabel = Label({
|
||||
className: `txt-smallie ${textClassName}`,
|
||||
});
|
||||
const widget = Button({
|
||||
onClicked: () => Utils.execAsync(['bash', '-c', `${userOptions.apps.taskManager}`]).catch(print),
|
||||
child: Box({
|
||||
className: `spacing-h-4 ${textClassName}`,
|
||||
children: [
|
||||
resourceProgress,
|
||||
resourceLabel,
|
||||
],
|
||||
setup: (self) => self.poll(5000, () => execAsync(['bash', '-c', command])
|
||||
.then((output) => {
|
||||
resourceCircProg.css = `font-size: ${Number(output)}px;`;
|
||||
resourceLabel.label = `${Math.round(Number(output))}%`;
|
||||
widget.tooltipText = `${name}: ${Math.round(Number(output))}%`;
|
||||
}).catch(print))
|
||||
,
|
||||
})
|
||||
});
|
||||
return widget;
|
||||
}
|
||||
|
||||
const TrackProgress = () => {
|
||||
const _updateProgress = (circprog) => {
|
||||
const mpris = Mpris.getPlayer('');
|
||||
if (!mpris) return;
|
||||
// Set circular progress value
|
||||
circprog.css = `font-size: ${Math.max(mpris.position / mpris.length * 100, 0)}px;`
|
||||
}
|
||||
return AnimatedCircProg({
|
||||
className: 'bar-music-circprog',
|
||||
vpack: 'center', hpack: 'center',
|
||||
extraSetup: (self) => self
|
||||
.hook(Mpris, _updateProgress)
|
||||
.poll(3000, _updateProgress)
|
||||
,
|
||||
})
|
||||
}
|
||||
|
||||
const switchToRelativeWorkspace = async (self, num) => {
|
||||
try {
|
||||
const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
|
||||
Hyprland.messageAsync(`dispatch workspace ${num > 0 ? '+' : ''}${num}`).catch(print);
|
||||
} catch {
|
||||
execAsync([`${App.configDir}/scripts/sway/swayToRelativeWs.sh`, `${num}`]).catch(print);
|
||||
}
|
||||
}
|
||||
|
||||
export default () => {
|
||||
// TODO: use cairo to make button bounce smaller on click, if that's possible
|
||||
const playingState = Box({ // Wrap a box cuz overlay can't have margins itself
|
||||
homogeneous: true,
|
||||
children: [Overlay({
|
||||
child: Box({
|
||||
vpack: 'center',
|
||||
className: 'bar-music-playstate',
|
||||
homogeneous: true,
|
||||
children: [Label({
|
||||
vpack: 'center',
|
||||
className: 'bar-music-playstate-txt',
|
||||
justification: 'center',
|
||||
setup: (self) => self.hook(Mpris, label => {
|
||||
const mpris = Mpris.getPlayer('');
|
||||
label.label = `${mpris !== null && mpris.playBackStatus == 'Playing' ? 'pause' : 'play_arrow'}`;
|
||||
}),
|
||||
})],
|
||||
setup: (self) => self.hook(Mpris, label => {
|
||||
const mpris = Mpris.getPlayer('');
|
||||
if (!mpris) return;
|
||||
label.toggleClassName('bar-music-playstate-playing', mpris !== null && mpris.playBackStatus == 'Playing');
|
||||
label.toggleClassName('bar-music-playstate', mpris !== null || mpris.playBackStatus == 'Paused');
|
||||
}),
|
||||
}),
|
||||
overlays: [
|
||||
TrackProgress(),
|
||||
]
|
||||
})]
|
||||
});
|
||||
const trackTitle = Label({
|
||||
hexpand: true,
|
||||
className: 'txt-smallie bar-music-txt',
|
||||
truncate: 'end',
|
||||
maxWidthChars: 1, // Doesn't matter, just needs to be non negative
|
||||
setup: (self) => self.hook(Mpris, label => {
|
||||
const mpris = Mpris.getPlayer('');
|
||||
if (mpris)
|
||||
label.label = `${trimTrackTitle(mpris.trackTitle)} • ${mpris.trackArtists.join(', ')}`;
|
||||
else
|
||||
label.label = getString('No media');
|
||||
}),
|
||||
})
|
||||
const musicStuff = Box({
|
||||
className: 'spacing-h-10',
|
||||
hexpand: true,
|
||||
children: [
|
||||
playingState,
|
||||
trackTitle,
|
||||
]
|
||||
})
|
||||
const SystemResourcesOrCustomModule = () => {
|
||||
// Check if $XDG_CACHE_HOME/ags/user/scripts/custom-module-poll.sh exists
|
||||
if (GLib.file_test(CUSTOM_MODULE_CONTENT_SCRIPT, GLib.FileTest.EXISTS)) {
|
||||
const interval = Number(Utils.readFile(CUSTOM_MODULE_CONTENT_INTERVAL_FILE)) || 5000;
|
||||
return BarGroup({
|
||||
child: Button({
|
||||
child: Label({
|
||||
className: 'txt-smallie txt-onSurfaceVariant',
|
||||
useMarkup: true,
|
||||
setup: (self) => Utils.timeout(1, () => {
|
||||
self.label = exec(CUSTOM_MODULE_CONTENT_SCRIPT);
|
||||
self.poll(interval, (self) => {
|
||||
const content = exec(CUSTOM_MODULE_CONTENT_SCRIPT);
|
||||
self.label = content;
|
||||
})
|
||||
})
|
||||
}),
|
||||
onPrimaryClickRelease: () => execAsync(CUSTOM_MODULE_LEFTCLICK_SCRIPT).catch(print),
|
||||
onSecondaryClickRelease: () => execAsync(CUSTOM_MODULE_RIGHTCLICK_SCRIPT).catch(print),
|
||||
onMiddleClickRelease: () => execAsync(CUSTOM_MODULE_MIDDLECLICK_SCRIPT).catch(print),
|
||||
onScrollUp: () => execAsync(CUSTOM_MODULE_SCROLLUP_SCRIPT).catch(print),
|
||||
onScrollDown: () => execAsync(CUSTOM_MODULE_SCROLLDOWN_SCRIPT).catch(print),
|
||||
})
|
||||
});
|
||||
} else return BarGroup({
|
||||
child: Box({
|
||||
children: [
|
||||
BarResource(getString('RAM Usage'), 'memory', `LANG=C free | awk '/^Mem/ {printf("%.2f\\n", ($3/$2) * 100)}'`,
|
||||
'bar-ram-circprog', 'bar-ram-txt', 'bar-ram-icon'),
|
||||
Revealer({
|
||||
revealChild: true,
|
||||
transition: 'slide_left',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
child: Box({
|
||||
className: 'spacing-h-10 margin-left-10',
|
||||
children: [
|
||||
BarResource(getString('Swap Usage'), 'swap_horiz', `LANG=C free | awk '/^Swap/ {if ($2 > 0) printf("%.2f\\n", ($3/$2) * 100); else print "0";}'`,
|
||||
'bar-swap-circprog', 'bar-swap-txt', 'bar-swap-icon'),
|
||||
BarResource(getString('CPU Usage'), 'settings_motion_mode', `LANG=C top -bn1 | grep Cpu | sed 's/\\,/\\./g' | awk '{print $2}'`,
|
||||
'bar-cpu-circprog', 'bar-cpu-txt', 'bar-cpu-icon'),
|
||||
]
|
||||
}),
|
||||
setup: (self) => self.hook(Mpris, label => {
|
||||
const mpris = Mpris.getPlayer('');
|
||||
self.revealChild = (!mpris);
|
||||
}),
|
||||
})
|
||||
],
|
||||
})
|
||||
});
|
||||
}
|
||||
return EventBox({
|
||||
onScrollUp: (self) => switchToRelativeWorkspace(self, -1),
|
||||
onScrollDown: (self) => switchToRelativeWorkspace(self, +1),
|
||||
child: Box({
|
||||
className: 'spacing-h-4',
|
||||
children: [
|
||||
SystemResourcesOrCustomModule(),
|
||||
EventBox({
|
||||
child: BarGroup({ child: musicStuff }),
|
||||
onPrimaryClick: () => showMusicControls.setValue(!showMusicControls.value),
|
||||
onSecondaryClick: () => execAsync(['bash', '-c', 'playerctl next || playerctl position `bc <<< "100 * $(playerctl metadata mpris:length) / 1000000 / 100"` &']).catch(print),
|
||||
onMiddleClick: () => execAsync('playerctl play-pause').catch(print),
|
||||
setup: (self) => self.on('button-press-event', (self, event) => {
|
||||
if (event.get_button()[1] === 8) // Side button
|
||||
execAsync('playerctl previous').catch(print)
|
||||
}),
|
||||
})
|
||||
]
|
||||
})
|
||||
});
|
||||
}
|
||||
78
homes/me/ags-end4/modules/bar/normal/spaceleft.js
Normal file
78
homes/me/ags-end4/modules/bar/normal/spaceleft.js
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import App from 'resource:///com/github/Aylur/ags/app.js';
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import Brightness from '../../../services/brightness.js';
|
||||
import Indicator from '../../../services/indicator.js';
|
||||
|
||||
const WindowTitle = async () => {
|
||||
try {
|
||||
const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
|
||||
return Widget.Scrollable({
|
||||
hexpand: true, vexpand: true,
|
||||
hscroll: 'automatic', vscroll: 'never',
|
||||
child: Widget.Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
Widget.Label({
|
||||
xalign: 0,
|
||||
truncate: 'end',
|
||||
maxWidthChars: 1, // Doesn't matter, just needs to be non negative
|
||||
className: 'txt-smaller bar-wintitle-topdesc txt',
|
||||
setup: (self) => self.hook(Hyprland.active.client, label => { // Hyprland.active.client
|
||||
label.label = Hyprland.active.client.class.length === 0 ? 'Desktop' : Hyprland.active.client.class;
|
||||
}),
|
||||
}),
|
||||
Widget.Label({
|
||||
xalign: 0,
|
||||
truncate: 'end',
|
||||
maxWidthChars: 1, // Doesn't matter, just needs to be non negative
|
||||
className: 'txt-smallie bar-wintitle-txt',
|
||||
setup: (self) => self.hook(Hyprland.active.client, label => { // Hyprland.active.client
|
||||
label.label = Hyprland.active.client.title.length === 0 ? `Workspace ${Hyprland.active.workspace.id}` : Hyprland.active.client.title;
|
||||
}),
|
||||
})
|
||||
]
|
||||
})
|
||||
});
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default async (monitor = 0) => {
|
||||
const optionalWindowTitleInstance = await WindowTitle();
|
||||
return Widget.EventBox({
|
||||
onScrollUp: () => {
|
||||
Indicator.popup(1); // Since the brightness and speaker are both on the same window
|
||||
Brightness[monitor].screen_value += 0.05;
|
||||
},
|
||||
onScrollDown: () => {
|
||||
Indicator.popup(1); // Since the brightness and speaker are both on the same window
|
||||
Brightness[monitor].screen_value -= 0.05;
|
||||
},
|
||||
onPrimaryClick: () => {
|
||||
App.toggleWindow('sideleft');
|
||||
},
|
||||
child: Widget.Box({
|
||||
homogeneous: false,
|
||||
children: [
|
||||
Widget.Box({ className: 'bar-corner-spacing' }),
|
||||
Widget.Overlay({
|
||||
overlays: [
|
||||
Widget.Box({ hexpand: true }),
|
||||
Widget.Box({
|
||||
className: 'bar-sidemodule', hexpand: true,
|
||||
children: [Widget.Box({
|
||||
vertical: true,
|
||||
className: 'bar-space-button',
|
||||
children: [
|
||||
optionalWindowTitleInstance,
|
||||
]
|
||||
})]
|
||||
}),
|
||||
]
|
||||
})
|
||||
]
|
||||
})
|
||||
});
|
||||
}
|
||||
91
homes/me/ags-end4/modules/bar/normal/spaceright.js
Normal file
91
homes/me/ags-end4/modules/bar/normal/spaceright.js
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
import App from 'resource:///com/github/Aylur/ags/app.js';
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
|
||||
import Audio from 'resource:///com/github/Aylur/ags/service/audio.js';
|
||||
import SystemTray from 'resource:///com/github/Aylur/ags/service/systemtray.js';
|
||||
const { execAsync } = Utils;
|
||||
import Indicator from '../../../services/indicator.js';
|
||||
import { StatusIcons } from '../../.commonwidgets/statusicons.js';
|
||||
import { Tray } from "./tray.js";
|
||||
|
||||
const SeparatorDot = () => Widget.Revealer({
|
||||
transition: 'slide_left',
|
||||
revealChild: false,
|
||||
attribute: {
|
||||
'count': SystemTray.items.length,
|
||||
'update': (self, diff) => {
|
||||
self.attribute.count += diff;
|
||||
self.revealChild = (self.attribute.count > 0);
|
||||
}
|
||||
},
|
||||
child: Widget.Box({
|
||||
vpack: 'center',
|
||||
className: 'separator-circle',
|
||||
}),
|
||||
setup: (self) => self
|
||||
.hook(SystemTray, (self) => self.attribute.update(self, 1), 'added')
|
||||
.hook(SystemTray, (self) => self.attribute.update(self, -1), 'removed')
|
||||
,
|
||||
});
|
||||
|
||||
export default (monitor = 0) => {
|
||||
const barTray = Tray();
|
||||
const barStatusIcons = StatusIcons({
|
||||
className: 'bar-statusicons',
|
||||
setup: (self) => self.hook(App, (self, currentName, visible) => {
|
||||
if (currentName === 'sideright') {
|
||||
self.toggleClassName('bar-statusicons-active', visible);
|
||||
}
|
||||
}),
|
||||
}, monitor);
|
||||
const SpaceRightDefaultClicks = (child) => Widget.EventBox({
|
||||
onHover: () => { barStatusIcons.toggleClassName('bar-statusicons-hover', true) },
|
||||
onHoverLost: () => { barStatusIcons.toggleClassName('bar-statusicons-hover', false) },
|
||||
onPrimaryClick: () => App.toggleWindow('sideright'),
|
||||
onSecondaryClick: () => execAsync(['bash', '-c', 'playerctl next || playerctl position `bc <<< "100 * $(playerctl metadata mpris:length) / 1000000 / 100"` &']).catch(print),
|
||||
onMiddleClick: () => execAsync('playerctl play-pause').catch(print),
|
||||
setup: (self) => self.on('button-press-event', (self, event) => {
|
||||
if (event.get_button()[1] === 8)
|
||||
execAsync('playerctl previous').catch(print)
|
||||
}),
|
||||
child: child,
|
||||
});
|
||||
const emptyArea = SpaceRightDefaultClicks(Widget.Box({ hexpand: true, }));
|
||||
const indicatorArea = SpaceRightDefaultClicks(Widget.Box({
|
||||
children: [
|
||||
SeparatorDot(),
|
||||
barStatusIcons
|
||||
],
|
||||
}));
|
||||
const actualContent = Widget.Box({
|
||||
hexpand: true,
|
||||
className: 'spacing-h-5 bar-spaceright',
|
||||
children: [
|
||||
emptyArea,
|
||||
barTray,
|
||||
indicatorArea
|
||||
],
|
||||
});
|
||||
|
||||
return Widget.EventBox({
|
||||
onScrollUp: () => {
|
||||
if (!Audio.speaker) return;
|
||||
if (Audio.speaker.volume <= 0.09) Audio.speaker.volume += 0.01;
|
||||
else Audio.speaker.volume += 0.03;
|
||||
Indicator.popup(1);
|
||||
},
|
||||
onScrollDown: () => {
|
||||
if (!Audio.speaker) return;
|
||||
if (Audio.speaker.volume <= 0.09) Audio.speaker.volume -= 0.01;
|
||||
else Audio.speaker.volume -= 0.03;
|
||||
Indicator.popup(1);
|
||||
},
|
||||
child: Widget.Box({
|
||||
children: [
|
||||
actualContent,
|
||||
SpaceRightDefaultClicks(Widget.Box({ className: 'bar-corner-spacing' })),
|
||||
]
|
||||
})
|
||||
});
|
||||
}
|
||||
236
homes/me/ags-end4/modules/bar/normal/system.js
Normal file
236
homes/me/ags-end4/modules/bar/normal/system.js
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
// This is for the right pills of the bar.
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
const { Box, Label, Button, Overlay, Revealer, Scrollable, Stack, EventBox } = Widget;
|
||||
const { exec, execAsync } = Utils;
|
||||
const { GLib } = imports.gi;
|
||||
import Battery from 'resource:///com/github/Aylur/ags/service/battery.js';
|
||||
import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
|
||||
import { AnimatedCircProg } from "../../.commonwidgets/cairo_circularprogress.js";
|
||||
import { WWO_CODE, WEATHER_SYMBOL, NIGHT_WEATHER_SYMBOL } from '../../.commondata/weather.js';
|
||||
|
||||
const WEATHER_CACHE_FOLDER = `${GLib.get_user_cache_dir()}/ags/weather`;
|
||||
Utils.exec(`mkdir -p ${WEATHER_CACHE_FOLDER}`);
|
||||
|
||||
const BarBatteryProgress = () => {
|
||||
const _updateProgress = (circprog) => { // Set circular progress value
|
||||
circprog.css = `font-size: ${Math.abs(Battery.percent)}px;`
|
||||
|
||||
circprog.toggleClassName('bar-batt-circprog-low', Battery.percent <= userOptions.battery.low);
|
||||
circprog.toggleClassName('bar-batt-circprog-full', Battery.charged);
|
||||
}
|
||||
return AnimatedCircProg({
|
||||
className: 'bar-batt-circprog',
|
||||
vpack: 'center', hpack: 'center',
|
||||
extraSetup: (self) => self
|
||||
.hook(Battery, _updateProgress)
|
||||
,
|
||||
})
|
||||
}
|
||||
|
||||
const time = Variable('', {
|
||||
poll: [
|
||||
userOptions.time.interval,
|
||||
() => GLib.DateTime.new_now_local().format(userOptions.time.format),
|
||||
],
|
||||
})
|
||||
|
||||
const date = Variable('', {
|
||||
poll: [
|
||||
userOptions.time.dateInterval,
|
||||
() => GLib.DateTime.new_now_local().format(userOptions.time.dateFormatLong),
|
||||
],
|
||||
})
|
||||
|
||||
const BarClock = () => Widget.Box({
|
||||
vpack: 'center',
|
||||
className: 'spacing-h-4 bar-clock-box',
|
||||
children: [
|
||||
Widget.Label({
|
||||
className: 'bar-time',
|
||||
label: time.bind(),
|
||||
}),
|
||||
Widget.Label({
|
||||
className: 'txt-norm txt-onLayer1',
|
||||
label: '•',
|
||||
}),
|
||||
Widget.Label({
|
||||
className: 'txt-smallie bar-date',
|
||||
label: date.bind(),
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const UtilButton = ({ name, icon, onClicked }) => Button({
|
||||
vpack: 'center',
|
||||
tooltipText: name,
|
||||
onClicked: onClicked,
|
||||
className: 'bar-util-btn icon-material txt-norm',
|
||||
label: `${icon}`,
|
||||
})
|
||||
|
||||
const Utilities = () => Box({
|
||||
hpack: 'center',
|
||||
className: 'spacing-h-4',
|
||||
children: [
|
||||
UtilButton({
|
||||
name: getString('Screen snip'), icon: 'screenshot_region', onClicked: () => {
|
||||
Utils.execAsync(`${App.configDir}/scripts/grimblast.sh copy area`)
|
||||
.catch(print)
|
||||
}
|
||||
}),
|
||||
UtilButton({
|
||||
name: getString('Color picker'), icon: 'colorize', onClicked: () => {
|
||||
Utils.execAsync(['hyprpicker', '-a']).catch(print)
|
||||
}
|
||||
}),
|
||||
UtilButton({
|
||||
name: getString('Toggle on-screen keyboard'), icon: 'keyboard', onClicked: () => {
|
||||
toggleWindowOnAllMonitors('osk');
|
||||
}
|
||||
}),
|
||||
]
|
||||
})
|
||||
|
||||
const BarBattery = () => Box({
|
||||
className: 'spacing-h-4 bar-batt-txt',
|
||||
children: [
|
||||
Revealer({
|
||||
transitionDuration: userOptions.animations.durationSmall,
|
||||
revealChild: false,
|
||||
transition: 'slide_right',
|
||||
child: MaterialIcon('bolt', 'norm', { tooltipText: "Charging" }),
|
||||
setup: (self) => self.hook(Battery, revealer => {
|
||||
self.revealChild = Battery.charging;
|
||||
}),
|
||||
}),
|
||||
Label({
|
||||
className: 'txt-smallie',
|
||||
setup: (self) => self.hook(Battery, label => {
|
||||
label.label = `${Number.parseFloat(Battery.percent.toFixed(1))}%`;
|
||||
}),
|
||||
}),
|
||||
Overlay({
|
||||
child: Widget.Box({
|
||||
vpack: 'center',
|
||||
className: 'bar-batt',
|
||||
homogeneous: true,
|
||||
children: [
|
||||
MaterialIcon('battery_full', 'small'),
|
||||
],
|
||||
setup: (self) => self.hook(Battery, box => {
|
||||
box.toggleClassName('bar-batt-low', Battery.percent <= userOptions.battery.low);
|
||||
box.toggleClassName('bar-batt-full', Battery.charged);
|
||||
}),
|
||||
}),
|
||||
overlays: [
|
||||
BarBatteryProgress(),
|
||||
]
|
||||
}),
|
||||
]
|
||||
});
|
||||
|
||||
const BarGroup = ({ child }) => Widget.Box({
|
||||
className: 'bar-group-margin bar-sides',
|
||||
children: [
|
||||
Widget.Box({
|
||||
className: 'bar-group bar-group-standalone bar-group-pad-system',
|
||||
children: [child],
|
||||
}),
|
||||
]
|
||||
});
|
||||
const BatteryModule = () => Stack({
|
||||
transition: 'slide_up_down',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
children: {
|
||||
'laptop': Box({
|
||||
className: 'spacing-h-4', children: [
|
||||
BarGroup({ child: Utilities() }),
|
||||
BarGroup({ child: BarBattery() }),
|
||||
]
|
||||
}),
|
||||
'desktop': BarGroup({
|
||||
child: Box({
|
||||
hexpand: true,
|
||||
hpack: 'center',
|
||||
className: 'spacing-h-4 txt-onSurfaceVariant',
|
||||
children: [
|
||||
MaterialIcon('device_thermostat', 'small'),
|
||||
Label({
|
||||
label: 'Weather',
|
||||
})
|
||||
],
|
||||
setup: (self) => self.poll(900000, async (self) => {
|
||||
const WEATHER_CACHE_PATH = WEATHER_CACHE_FOLDER + '/wttr.in.txt';
|
||||
const updateWeatherForCity = (city) => execAsync(`curl https://wttr.in/${city.replace(/ /g, '%20')}?format=j1`)
|
||||
.then(output => {
|
||||
const weather = JSON.parse(output);
|
||||
Utils.writeFile(JSON.stringify(weather), WEATHER_CACHE_PATH)
|
||||
.catch(print);
|
||||
const weatherCode = weather.current_condition[0].weatherCode;
|
||||
const weatherDesc = weather.current_condition[0].weatherDesc[0].value;
|
||||
const temperature = weather.current_condition[0][`temp_${userOptions.weather.preferredUnit}`];
|
||||
const feelsLike = weather.current_condition[0][`FeelsLike${userOptions.weather.preferredUnit}`];
|
||||
const weatherSymbol = WEATHER_SYMBOL[WWO_CODE[weatherCode]];
|
||||
self.children[0].label = weatherSymbol;
|
||||
self.children[1].label = `${temperature}°${userOptions.weather.preferredUnit} • Feels like ${feelsLike}°${userOptions.weather.preferredUnit}`;
|
||||
self.tooltipText = weatherDesc;
|
||||
}).catch((err) => {
|
||||
try { // Read from cache
|
||||
const weather = JSON.parse(
|
||||
Utils.readFile(WEATHER_CACHE_PATH)
|
||||
);
|
||||
const weatherCode = weather.current_condition[0].weatherCode;
|
||||
const weatherDesc = weather.current_condition[0].weatherDesc[0].value;
|
||||
const temperature = weather.current_condition[0][`temp_${userOptions.weather.preferredUnit}`];
|
||||
const feelsLike = weather.current_condition[0][`FeelsLike${userOptions.weather.preferredUnit}`];
|
||||
const weatherSymbol = WEATHER_SYMBOL[WWO_CODE[weatherCode]];
|
||||
self.children[0].label = weatherSymbol;
|
||||
self.children[1].label = `${temperature}°${userOptions.weather.preferredUnit} • Feels like ${feelsLike}°${userOptions.weather.preferredUnit}`;
|
||||
self.tooltipText = weatherDesc;
|
||||
} catch (err) {
|
||||
print(err);
|
||||
}
|
||||
});
|
||||
if (userOptions.weather.city != '' && userOptions.weather.city != null) {
|
||||
updateWeatherForCity(userOptions.weather.city.replace(/ /g, '%20'));
|
||||
}
|
||||
else {
|
||||
Utils.execAsync('curl ipinfo.io')
|
||||
.then(output => {
|
||||
return JSON.parse(output)['city'].toLowerCase();
|
||||
})
|
||||
.then(updateWeatherForCity)
|
||||
.catch(print)
|
||||
}
|
||||
}),
|
||||
})
|
||||
}),
|
||||
},
|
||||
setup: (stack) => Utils.timeout(10, () => {
|
||||
if (!Battery.available) stack.shown = 'desktop';
|
||||
else stack.shown = 'laptop';
|
||||
})
|
||||
})
|
||||
|
||||
const switchToRelativeWorkspace = async (self, num) => {
|
||||
try {
|
||||
const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
|
||||
Hyprland.messageAsync(`dispatch workspace ${num > 0 ? '+' : ''}${num}`).catch(print);
|
||||
} catch {
|
||||
execAsync([`${App.configDir}/scripts/sway/swayToRelativeWs.sh`, `${num}`]).catch(print);
|
||||
}
|
||||
}
|
||||
|
||||
export default () => Widget.EventBox({
|
||||
onScrollUp: (self) => switchToRelativeWorkspace(self, -1),
|
||||
onScrollDown: (self) => switchToRelativeWorkspace(self, +1),
|
||||
onPrimaryClick: () => App.toggleWindow('sideright'),
|
||||
child: Widget.Box({
|
||||
className: 'spacing-h-4',
|
||||
children: [
|
||||
BarGroup({ child: BarClock() }),
|
||||
BatteryModule(),
|
||||
]
|
||||
})
|
||||
});
|
||||
36
homes/me/ags-end4/modules/bar/normal/tray.js
Normal file
36
homes/me/ags-end4/modules/bar/normal/tray.js
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import SystemTray from 'resource:///com/github/Aylur/ags/service/systemtray.js';
|
||||
const { Box, Icon, Button, Revealer } = Widget;
|
||||
const { Gravity } = imports.gi.Gdk;
|
||||
|
||||
const SysTrayItem = (item) => item.id !== null ? Button({
|
||||
className: 'bar-systray-item',
|
||||
child: Icon({ hpack: 'center' }).bind('icon', item, 'icon'),
|
||||
setup: (self) => self
|
||||
.hook(item, (self) => self.tooltipMarkup = item['tooltip-markup'])
|
||||
,
|
||||
onPrimaryClick: (_, event) => item.activate(event),
|
||||
onSecondaryClick: (btn, event) => item.menu.popup_at_widget(btn, Gravity.SOUTH, Gravity.NORTH, null),
|
||||
}) : null;
|
||||
|
||||
export const Tray = (props = {}) => {
|
||||
const trayContent = Box({
|
||||
className: 'margin-right-5 spacing-h-15',
|
||||
setup: (self) => self
|
||||
.hook(SystemTray, (self) => {
|
||||
self.children = SystemTray.items.map(SysTrayItem);
|
||||
self.show_all();
|
||||
})
|
||||
,
|
||||
});
|
||||
const trayRevealer = Widget.Revealer({
|
||||
revealChild: true,
|
||||
transition: 'slide_left',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
child: trayContent,
|
||||
});
|
||||
return Box({
|
||||
...props,
|
||||
children: [trayRevealer],
|
||||
});
|
||||
}
|
||||
224
homes/me/ags-end4/modules/bar/normal/workspaces_hyprland.js
Normal file
224
homes/me/ags-end4/modules/bar/normal/workspaces_hyprland.js
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
const { GLib, Gdk, Gtk } = imports.gi;
|
||||
const Lang = imports.lang;
|
||||
const Cairo = imports.cairo;
|
||||
const Pango = imports.gi.Pango;
|
||||
const PangoCairo = imports.gi.PangoCairo;
|
||||
import App from 'resource:///com/github/Aylur/ags/app.js';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
const { Box, DrawingArea, EventBox } = Widget;
|
||||
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
|
||||
|
||||
const dummyWs = Box({ className: 'bar-ws' }); // Not shown. Only for getting size props
|
||||
const dummyActiveWs = Box({ className: 'bar-ws bar-ws-active' }); // Not shown. Only for getting size props
|
||||
const dummyOccupiedWs = Box({ className: 'bar-ws bar-ws-occupied' }); // Not shown. Only for getting size props
|
||||
|
||||
const mix = (value1, value2, perc) => {
|
||||
return value1 * perc + value2 * (1 - perc);
|
||||
}
|
||||
|
||||
const getFontWeightName = (weight) => {
|
||||
switch (weight) {
|
||||
case Pango.Weight.ULTRA_LIGHT:
|
||||
return 'UltraLight';
|
||||
case Pango.Weight.LIGHT:
|
||||
return 'Light';
|
||||
case Pango.Weight.NORMAL:
|
||||
return 'Normal';
|
||||
case Pango.Weight.BOLD:
|
||||
return 'Bold';
|
||||
case Pango.Weight.ULTRA_BOLD:
|
||||
return 'UltraBold';
|
||||
case Pango.Weight.HEAVY:
|
||||
return 'Heavy';
|
||||
default:
|
||||
return 'Normal';
|
||||
}
|
||||
}
|
||||
|
||||
// Font size = workspace id
|
||||
const WorkspaceContents = (count = 10) => {
|
||||
return DrawingArea({
|
||||
className: 'bar-ws-container',
|
||||
attribute: {
|
||||
initialized: false,
|
||||
workspaceMask: 0,
|
||||
workspaceGroup: 0,
|
||||
updateMask: (self) => {
|
||||
const offset = Math.floor((Hyprland.active.workspace.id - 1) / count) * userOptions.workspaces.shown;
|
||||
// if (self.attribute.initialized) return; // We only need this to run once
|
||||
const workspaces = Hyprland.workspaces;
|
||||
let workspaceMask = 0;
|
||||
for (let i = 0; i < workspaces.length; i++) {
|
||||
const ws = workspaces[i];
|
||||
if (ws.id <= offset || ws.id > offset + count) continue; // Out of range, ignore
|
||||
if (workspaces[i].windows > 0)
|
||||
workspaceMask |= (1 << (ws.id - offset));
|
||||
}
|
||||
// console.log('Mask:', workspaceMask.toString(2));
|
||||
self.attribute.workspaceMask = workspaceMask;
|
||||
// self.attribute.initialized = true;
|
||||
self.queue_draw();
|
||||
},
|
||||
toggleMask: (self, occupied, name) => {
|
||||
if (occupied) self.attribute.workspaceMask |= (1 << parseInt(name));
|
||||
else self.attribute.workspaceMask &= ~(1 << parseInt(name));
|
||||
self.queue_draw();
|
||||
},
|
||||
},
|
||||
setup: (area) => area
|
||||
.hook(Hyprland.active.workspace, (self) => {
|
||||
self.setCss(`font-size: ${(Hyprland.active.workspace.id - 1) % count + 1}px;`);
|
||||
const previousGroup = self.attribute.workspaceGroup;
|
||||
const currentGroup = Math.floor((Hyprland.active.workspace.id - 1) / count);
|
||||
if (currentGroup !== previousGroup) {
|
||||
self.attribute.updateMask(self);
|
||||
self.attribute.workspaceGroup = currentGroup;
|
||||
}
|
||||
})
|
||||
.hook(Hyprland, (self) => self.attribute.updateMask(self), 'notify::workspaces')
|
||||
.on('draw', Lang.bind(area, (area, cr) => {
|
||||
const offset = Math.floor((Hyprland.active.workspace.id - 1) / count) * userOptions.workspaces.shown;
|
||||
|
||||
const allocation = area.get_allocation();
|
||||
const { width, height } = allocation;
|
||||
|
||||
const workspaceStyleContext = dummyWs.get_style_context();
|
||||
const workspaceDiameter = workspaceStyleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
|
||||
const workspaceRadius = workspaceDiameter / 2;
|
||||
const workspaceFontSize = workspaceStyleContext.get_property('font-size', Gtk.StateFlags.NORMAL) / 4 * 3;
|
||||
const workspaceFontFamily = workspaceStyleContext.get_property('font-family', Gtk.StateFlags.NORMAL);
|
||||
const workspaceFontWeight = workspaceStyleContext.get_property('font-weight', Gtk.StateFlags.NORMAL);
|
||||
const wsbg = workspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
|
||||
const wsfg = workspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL);
|
||||
|
||||
const occupiedWorkspaceStyleContext = dummyOccupiedWs.get_style_context();
|
||||
const occupiedbg = occupiedWorkspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
|
||||
const occupiedfg = occupiedWorkspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL);
|
||||
|
||||
const activeWorkspaceStyleContext = dummyActiveWs.get_style_context();
|
||||
const activebg = activeWorkspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
|
||||
const activefg = activeWorkspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL);
|
||||
area.set_size_request(workspaceDiameter * count, -1);
|
||||
const widgetStyleContext = area.get_style_context();
|
||||
const activeWs = widgetStyleContext.get_property('font-size', Gtk.StateFlags.NORMAL);
|
||||
|
||||
const activeWsCenterX = -(workspaceDiameter / 2) + (workspaceDiameter * activeWs);
|
||||
const activeWsCenterY = height / 2;
|
||||
|
||||
// Font
|
||||
const layout = PangoCairo.create_layout(cr);
|
||||
const fontDesc = Pango.font_description_from_string(`${workspaceFontFamily[0]} ${getFontWeightName(workspaceFontWeight)} ${workspaceFontSize}`);
|
||||
layout.set_font_description(fontDesc);
|
||||
cr.setAntialias(Cairo.Antialias.BEST);
|
||||
// Get kinda min radius for number indicators
|
||||
layout.set_text("0".repeat(count.toString().length), -1);
|
||||
const [layoutWidth, layoutHeight] = layout.get_pixel_size();
|
||||
const indicatorRadius = Math.max(layoutWidth, layoutHeight) / 2 * 1.15; // smaller than sqrt(2)*radius
|
||||
const indicatorGap = workspaceRadius - indicatorRadius;
|
||||
|
||||
for (let i = 1; i <= count; i++) {
|
||||
if (area.attribute.workspaceMask & (1 << i)) {
|
||||
// Draw bg highlight
|
||||
cr.setSourceRGBA(occupiedbg.red, occupiedbg.green, occupiedbg.blue, occupiedbg.alpha);
|
||||
const wsCenterX = -(workspaceRadius) + (workspaceDiameter * i);
|
||||
const wsCenterY = height / 2;
|
||||
if (!(area.attribute.workspaceMask & (1 << (i - 1)))) { // Left
|
||||
cr.arc(wsCenterX, wsCenterY, workspaceRadius, 0.5 * Math.PI, 1.5 * Math.PI);
|
||||
cr.fill();
|
||||
}
|
||||
else {
|
||||
cr.rectangle(wsCenterX - workspaceRadius, wsCenterY - workspaceRadius, workspaceRadius, workspaceRadius * 2)
|
||||
cr.fill();
|
||||
}
|
||||
if (!(area.attribute.workspaceMask & (1 << (i + 1)))) { // Right
|
||||
cr.arc(wsCenterX, wsCenterY, workspaceRadius, -0.5 * Math.PI, 0.5 * Math.PI);
|
||||
cr.fill();
|
||||
}
|
||||
else {
|
||||
cr.rectangle(wsCenterX, wsCenterY - workspaceRadius, workspaceRadius, workspaceRadius * 2)
|
||||
cr.fill();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw active ws
|
||||
cr.setSourceRGBA(activebg.red, activebg.green, activebg.blue, activebg.alpha);
|
||||
cr.arc(activeWsCenterX, activeWsCenterY, indicatorRadius, 0, 2 * Math.PI);
|
||||
cr.fill();
|
||||
|
||||
// Draw workspace numbers
|
||||
for (let i = 1; i <= count; i++) {
|
||||
const inactivecolors = area.attribute.workspaceMask & (1 << i) ? occupiedfg : wsfg;
|
||||
if (i == activeWs) {
|
||||
cr.setSourceRGBA(activefg.red, activefg.green, activefg.blue, activefg.alpha);
|
||||
}
|
||||
// Moving to
|
||||
else if ((i == Math.floor(activeWs) && Hyprland.active.workspace.id < activeWs) || (i == Math.ceil(activeWs) && Hyprland.active.workspace.id > activeWs)) {
|
||||
cr.setSourceRGBA(mix(activefg.red, inactivecolors.red, 1 - Math.abs(activeWs - i)), mix(activefg.green, inactivecolors.green, 1 - Math.abs(activeWs - i)), mix(activefg.blue, inactivecolors.blue, 1 - Math.abs(activeWs - i)), activefg.alpha);
|
||||
}
|
||||
// Moving from
|
||||
else if ((i == Math.floor(activeWs) && Hyprland.active.workspace.id > activeWs) || (i == Math.ceil(activeWs) && Hyprland.active.workspace.id < activeWs)) {
|
||||
cr.setSourceRGBA(mix(activefg.red, inactivecolors.red, 1 - Math.abs(activeWs - i)), mix(activefg.green, inactivecolors.green, 1 - Math.abs(activeWs - i)), mix(activefg.blue, inactivecolors.blue, 1 - Math.abs(activeWs - i)), activefg.alpha);
|
||||
}
|
||||
// Inactive
|
||||
else
|
||||
cr.setSourceRGBA(inactivecolors.red, inactivecolors.green, inactivecolors.blue, inactivecolors.alpha);
|
||||
|
||||
layout.set_text(`${i + offset}`, -1);
|
||||
const [layoutWidth, layoutHeight] = layout.get_pixel_size();
|
||||
const x = -workspaceRadius + (workspaceDiameter * i) - (layoutWidth / 2);
|
||||
const y = (height - layoutHeight) / 2;
|
||||
cr.moveTo(x, y);
|
||||
PangoCairo.show_layout(cr, layout);
|
||||
cr.stroke();
|
||||
}
|
||||
}))
|
||||
,
|
||||
})
|
||||
}
|
||||
|
||||
export default () => EventBox({
|
||||
onScrollUp: () => Hyprland.messageAsync(`dispatch workspace -1`).catch(print),
|
||||
onScrollDown: () => Hyprland.messageAsync(`dispatch workspace +1`).catch(print),
|
||||
onMiddleClick: () => toggleWindowOnAllMonitors('osk'),
|
||||
onSecondaryClick: () => App.toggleWindow('overview'),
|
||||
attribute: {
|
||||
clicked: false,
|
||||
ws_group: 0,
|
||||
},
|
||||
child: Box({
|
||||
homogeneous: true,
|
||||
className: 'bar-group-margin',
|
||||
children: [Box({
|
||||
className: 'bar-group bar-group-standalone bar-group-pad',
|
||||
css: 'min-width: 2px;',
|
||||
children: [WorkspaceContents(userOptions.workspaces.shown)],
|
||||
})]
|
||||
}),
|
||||
setup: (self) => {
|
||||
self.add_events(Gdk.EventMask.POINTER_MOTION_MASK);
|
||||
self.on('motion-notify-event', (self, event) => {
|
||||
if (!self.attribute.clicked) return;
|
||||
const [_, cursorX, cursorY] = event.get_coords();
|
||||
const widgetWidth = self.get_allocation().width;
|
||||
const wsId = Math.ceil(cursorX * userOptions.workspaces.shown / widgetWidth);
|
||||
Utils.execAsync([`${App.configDir}/scripts/hyprland/workspace_action.sh`, 'workspace', `${wsId}`])
|
||||
.catch(print);
|
||||
})
|
||||
self.on('button-press-event', (self, event) => {
|
||||
if (event.get_button()[1] === 1) {
|
||||
self.attribute.clicked = true;
|
||||
const [_, cursorX, cursorY] = event.get_coords();
|
||||
const widgetWidth = self.get_allocation().width;
|
||||
const wsId = Math.ceil(cursorX * userOptions.workspaces.shown / widgetWidth);
|
||||
Utils.execAsync([`${App.configDir}/scripts/hyprland/workspace_action.sh`, 'workspace', `${wsId}`])
|
||||
.catch(print);
|
||||
}
|
||||
else if (event.get_button()[1] === 8) {
|
||||
Hyprland.messageAsync(`dispatch togglespecialworkspace`).catch(print);
|
||||
}
|
||||
})
|
||||
self.on('button-release-event', (self) => self.attribute.clicked = false);
|
||||
}
|
||||
})
|
||||
183
homes/me/ags-end4/modules/bar/normal/workspaces_sway.js
Normal file
183
homes/me/ags-end4/modules/bar/normal/workspaces_sway.js
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
const { GLib, Gdk, Gtk } = imports.gi;
|
||||
const Lang = imports.lang;
|
||||
const Cairo = imports.cairo;
|
||||
const Pango = imports.gi.Pango;
|
||||
const PangoCairo = imports.gi.PangoCairo;
|
||||
import Widget from "resource:///com/github/Aylur/ags/widget.js";
|
||||
import Sway from "../../../services/sway.js";
|
||||
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
|
||||
const { execAsync, exec } = Utils;
|
||||
const { Box, DrawingArea, EventBox } = Widget;
|
||||
|
||||
const dummyWs = Box({ className: 'bar-ws' }); // Not shown. Only for getting size props
|
||||
const dummyActiveWs = Box({ className: 'bar-ws bar-ws-active' }); // Not shown. Only for getting size props
|
||||
const dummyOccupiedWs = Box({ className: 'bar-ws bar-ws-occupied' }); // Not shown. Only for getting size props
|
||||
|
||||
const switchToWorkspace = (arg) => Utils.execAsync(`swaymsg workspace ${arg}`).catch(print);
|
||||
const switchToRelativeWorkspace = (self, num) =>
|
||||
execAsync([`${App.configDir}/scripts/sway/swayToRelativeWs.sh`, `${num}`]).catch(print);
|
||||
|
||||
const WorkspaceContents = (count = 10) => {
|
||||
return DrawingArea({
|
||||
css: `transition: 90ms cubic-bezier(0.1, 1, 0, 1);`,
|
||||
attribute: {
|
||||
initialized: false,
|
||||
workspaceMask: 0,
|
||||
updateMask: (self) => {
|
||||
if (self.attribute.initialized) return; // We only need this to run once
|
||||
const workspaces = Sway.workspaces;
|
||||
let workspaceMask = 0;
|
||||
// console.log('----------------')
|
||||
for (let i = 0; i < workspaces.length; i++) {
|
||||
const ws = workspaces[i];
|
||||
// console.log(ws.name, ',', ws.num);
|
||||
if (!Number(ws.name)) return;
|
||||
const id = Number(ws.name);
|
||||
if (id <= 0) continue; // Ignore scratchpads
|
||||
if (id > count) return; // Not rendered
|
||||
if (workspaces[i].windows > 0) {
|
||||
workspaceMask |= (1 << id);
|
||||
}
|
||||
}
|
||||
self.attribute.workspaceMask = workspaceMask;
|
||||
self.attribute.initialized = true;
|
||||
},
|
||||
toggleMask: (self, occupied, name) => {
|
||||
if (occupied) self.attribute.workspaceMask |= (1 << parseInt(name));
|
||||
else self.attribute.workspaceMask &= ~(1 << parseInt(name));
|
||||
},
|
||||
},
|
||||
setup: (area) => area
|
||||
.hook(Sway.active.workspace, (area) => {
|
||||
area.setCss(`font-size: ${Sway.active.workspace.name}px;`)
|
||||
})
|
||||
.hook(Sway, (self) => self.attribute.updateMask(self), 'notify::workspaces')
|
||||
// .hook(Hyprland, (self, name) => self.attribute.toggleMask(self, true, name), 'workspace-added')
|
||||
// .hook(Hyprland, (self, name) => self.attribute.toggleMask(self, false, name), 'workspace-removed')
|
||||
.on('draw', Lang.bind(area, (area, cr) => {
|
||||
const allocation = area.get_allocation();
|
||||
const { width, height } = allocation;
|
||||
|
||||
const workspaceStyleContext = dummyWs.get_style_context();
|
||||
const workspaceDiameter = workspaceStyleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
|
||||
const workspaceRadius = workspaceDiameter / 2;
|
||||
const workspaceFontSize = workspaceStyleContext.get_property('font-size', Gtk.StateFlags.NORMAL) / 4 * 3;
|
||||
const workspaceFontFamily = workspaceStyleContext.get_property('font-family', Gtk.StateFlags.NORMAL);
|
||||
const wsbg = workspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
|
||||
const wsfg = workspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL);
|
||||
|
||||
const occupiedWorkspaceStyleContext = dummyOccupiedWs.get_style_context();
|
||||
const occupiedbg = occupiedWorkspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
|
||||
const occupiedfg = occupiedWorkspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL);
|
||||
|
||||
const activeWorkspaceStyleContext = dummyActiveWs.get_style_context();
|
||||
const activebg = activeWorkspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
|
||||
const activefg = activeWorkspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL);
|
||||
area.set_size_request(workspaceDiameter * count, -1);
|
||||
const widgetStyleContext = area.get_style_context();
|
||||
const activeWs = widgetStyleContext.get_property('font-size', Gtk.StateFlags.NORMAL);
|
||||
|
||||
const activeWsCenterX = -(workspaceDiameter / 2) + (workspaceDiameter * activeWs);
|
||||
const activeWsCenterY = height / 2;
|
||||
|
||||
// Font
|
||||
const layout = PangoCairo.create_layout(cr);
|
||||
const fontDesc = Pango.font_description_from_string(`${workspaceFontFamily[0]} ${workspaceFontSize}`);
|
||||
layout.set_font_description(fontDesc);
|
||||
cr.setAntialias(Cairo.Antialias.BEST);
|
||||
// Get kinda min radius for number indicators
|
||||
layout.set_text("0".repeat(count.toString().length), -1);
|
||||
const [layoutWidth, layoutHeight] = layout.get_pixel_size();
|
||||
const indicatorRadius = Math.max(layoutWidth, layoutHeight) / 2 * 1.2; // a bit smaller than sqrt(2)*radius
|
||||
const indicatorGap = workspaceRadius - indicatorRadius;
|
||||
|
||||
// Draw workspace numbers
|
||||
for (let i = 1; i <= count; i++) {
|
||||
if (area.attribute.workspaceMask & (1 << i)) {
|
||||
// Draw bg highlight
|
||||
cr.setSourceRGBA(occupiedbg.red, occupiedbg.green, occupiedbg.blue, occupiedbg.alpha);
|
||||
const wsCenterX = -(workspaceRadius) + (workspaceDiameter * i);
|
||||
const wsCenterY = height / 2;
|
||||
if (!(area.attribute.workspaceMask & (1 << (i - 1)))) { // Left
|
||||
cr.arc(wsCenterX, wsCenterY, workspaceRadius, 0.5 * Math.PI, 1.5 * Math.PI);
|
||||
cr.fill();
|
||||
}
|
||||
else {
|
||||
cr.rectangle(wsCenterX - workspaceRadius, wsCenterY - workspaceRadius, workspaceRadius, workspaceRadius * 2)
|
||||
cr.fill();
|
||||
}
|
||||
if (!(area.attribute.workspaceMask & (1 << (i + 1)))) { // Right
|
||||
cr.arc(wsCenterX, wsCenterY, workspaceRadius, -0.5 * Math.PI, 0.5 * Math.PI);
|
||||
cr.fill();
|
||||
}
|
||||
else {
|
||||
cr.rectangle(wsCenterX, wsCenterY - workspaceRadius, workspaceRadius, workspaceRadius * 2)
|
||||
cr.fill();
|
||||
}
|
||||
|
||||
// Set color for text
|
||||
cr.setSourceRGBA(occupiedfg.red, occupiedfg.green, occupiedfg.blue, occupiedfg.alpha);
|
||||
}
|
||||
else
|
||||
cr.setSourceRGBA(wsfg.red, wsfg.green, wsfg.blue, wsfg.alpha);
|
||||
layout.set_text(`${i}`, -1);
|
||||
const [layoutWidth, layoutHeight] = layout.get_pixel_size();
|
||||
const x = -workspaceRadius + (workspaceDiameter * i) - (layoutWidth / 2);
|
||||
const y = (height - layoutHeight) / 2;
|
||||
cr.moveTo(x, y);
|
||||
// cr.showText(text);
|
||||
PangoCairo.show_layout(cr, layout);
|
||||
cr.stroke();
|
||||
}
|
||||
|
||||
// Draw active ws
|
||||
// base
|
||||
cr.setSourceRGBA(activebg.red, activebg.green, activebg.blue, activebg.alpha);
|
||||
cr.arc(activeWsCenterX, activeWsCenterY, indicatorRadius, 0, 2 * Math.PI);
|
||||
cr.fill();
|
||||
// inner decor
|
||||
cr.setSourceRGBA(activefg.red, activefg.green, activefg.blue, activefg.alpha);
|
||||
cr.arc(activeWsCenterX, activeWsCenterY, indicatorRadius * 0.2, 0, 2 * Math.PI);
|
||||
cr.fill();
|
||||
}))
|
||||
,
|
||||
})
|
||||
}
|
||||
|
||||
export default () => EventBox({
|
||||
onScrollUp: (self) => switchToRelativeWorkspace(self, -1),
|
||||
onScrollDown: (self) => switchToRelativeWorkspace(self, +1),
|
||||
onMiddleClick: () => toggleWindowOnAllMonitors('osk'),
|
||||
onSecondaryClick: () => App.toggleWindow('overview'),
|
||||
attribute: { clicked: false },
|
||||
child: Box({
|
||||
homogeneous: true,
|
||||
className: 'bar-group-margin',
|
||||
children: [Box({
|
||||
className: 'bar-group bar-group-standalone bar-group-pad',
|
||||
css: 'min-width: 2px;',
|
||||
children: [
|
||||
WorkspaceContents(10),
|
||||
]
|
||||
})]
|
||||
}),
|
||||
setup: (self) => {
|
||||
self.add_events(Gdk.EventMask.POINTER_MOTION_MASK);
|
||||
self.on('motion-notify-event', (self, event) => {
|
||||
if (!self.attribute.clicked) return;
|
||||
const [_, cursorX, cursorY] = event.get_coords();
|
||||
const widgetWidth = self.get_allocation().width;
|
||||
const wsId = Math.ceil(cursorX * userOptions.workspaces.shown / widgetWidth);
|
||||
switchToWorkspace(wsId);
|
||||
})
|
||||
self.on('button-press-event', (self, event) => {
|
||||
if (!(event.get_button()[1] === 1)) return; // We're only interested in left-click here
|
||||
self.attribute.clicked = true;
|
||||
const [_, cursorX, cursorY] = event.get_coords();
|
||||
const widgetWidth = self.get_allocation().width;
|
||||
const wsId = Math.ceil(cursorX * userOptions.workspaces.shown / widgetWidth);
|
||||
switchToWorkspace(wsId);
|
||||
})
|
||||
self.on('button-release-event', (self) => self.attribute.clicked = false);
|
||||
}
|
||||
});
|
||||
122
homes/me/ags-end4/modules/cheatsheet/data_keybinds.js
Normal file
122
homes/me/ags-end4/modules/cheatsheet/data_keybinds.js
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
export const keybindList = [[
|
||||
{
|
||||
"icon": "pin_drop",
|
||||
"name": "Workspaces: navigation",
|
||||
"binds": [
|
||||
{ "keys": ["", "+", "#"], "action": "Go to workspace #" },
|
||||
{ "keys": ["", "+", "S"], "action": "Toggle special workspace" },
|
||||
{ "keys": ["", "+", "(Scroll ↑↓)"], "action": "Go to workspace -1/+1" },
|
||||
{ "keys": ["Ctrl", "", "+", "←"], "action": "Go to workspace on the left" },
|
||||
{ "keys": ["Ctrl", "", "+", "→"], "action": "Go to workspace on the right" },
|
||||
{ "keys": ["", "+", "PageUp"], "action": "Go to workspace on the left" },
|
||||
{ "keys": ["", "+", "PageDown"], "action": "Go to workspace on the right" }
|
||||
],
|
||||
"id": 1
|
||||
},
|
||||
{
|
||||
"icon": "overview_key",
|
||||
"name": "Workspaces: management",
|
||||
"binds": [
|
||||
{ "keys": ["", "Alt", "+", "#"], "action": "Move window to workspace #" },
|
||||
{ "keys": ["", "Alt", "+", "S"], "action": "Move window to special workspace" },
|
||||
{ "keys": ["", "Alt", "+", "PageUp"], "action": "Move window to workspace on the left" },
|
||||
{ "keys": ["", "Alt", "+", "PageDown"], "action": "Move window to workspace on the right" }
|
||||
],
|
||||
"id": 2
|
||||
},
|
||||
{
|
||||
"icon": "move_group",
|
||||
"name": "Windows",
|
||||
"binds": [
|
||||
{ "keys": ["", "+", "←↑→↓"], "action": "Focus window in direction" },
|
||||
{ "keys": ["", "Shift", "+", "←↑→↓"], "action": "Swap window in direction" },
|
||||
{ "keys": ["", "+", ";"], "action": "Split ratio -" },
|
||||
{ "keys": ["", "+", "'"], "action": "Split ratio +" },
|
||||
{ "keys": ["", "+", "Lmb"], "action": "Move window" },
|
||||
{ "keys": ["", "+", "Rmb"], "action": "Resize window" },
|
||||
{ "keys": ["", "Alt", "+", "Space"], "action": "Float window" },
|
||||
{ "keys": ["", "+", "F"], "action": "Fullscreen" },
|
||||
{ "keys": ["", "Alt", "+", "F"], "action": "Fake fullscreen" }
|
||||
],
|
||||
"id": 3
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"icon": "widgets",
|
||||
"name": "Widgets (AGS)",
|
||||
"binds": [
|
||||
{ "keys": ["", "OR", "", "+", "Tab"], "action": "Toggle overview/launcher" },
|
||||
{ "keys": ["Ctrl", "", "+", "R"], "action": "Restart AGS" },
|
||||
{ "keys": ["", "+", "/"], "action": "Toggle this cheatsheet" },
|
||||
{ "keys": ["", "+", "N"], "action": "Toggle system sidebar" },
|
||||
{ "keys": ["", "+", "B", "OR", "", "+", "O"], "action": "Toggle utilities sidebar" },
|
||||
{ "keys": ["", "+", "K"], "action": "Toggle virtual keyboard" },
|
||||
{ "keys": ["Ctrl", "Alt", "+", "Del"], "action": "Power/Session menu" },
|
||||
|
||||
{ "keys": ["Esc"], "action": "Exit a window" },
|
||||
{ "keys": ["rightCtrl"], "action": "Dismiss/close sidebar" },
|
||||
|
||||
{ "keys": ["Ctrl", "", "+", "T"], "action": "Change wallpaper+colorscheme" },
|
||||
|
||||
// { "keys": ["", "+", "B"], "action": "Toggle left sidebar" },
|
||||
// { "keys": ["", "+", "N"], "action": "Toggle right sidebar" },
|
||||
// { "keys": ["", "+", "G"], "action": "Toggle volume mixer" },
|
||||
// { "keys": ["", "+", "M"], "action": "Toggle useless audio visualizer" },
|
||||
// { "keys": ["(right)Ctrl"], "action": "Dismiss notification & close menus" }
|
||||
],
|
||||
"id": 4
|
||||
},
|
||||
{
|
||||
"icon": "construction",
|
||||
"name": "Utilities",
|
||||
"binds": [
|
||||
{ "keys": ["PrtSc"], "action": "Screenshot >> clipboard" },
|
||||
{ "keys": ["Ctrl", "PrtSc"], "action": "Screenshot >> file + clipboard" },
|
||||
{ "keys": ["", "Shift", "+", "S"], "action": "Screen snip >> clipboard" },
|
||||
{ "keys": ["", "Shift", "+", "T"], "action": "Image to text >> clipboard" },
|
||||
{ "keys": ["", "Shift", "+", "C"], "action": "Color picker" },
|
||||
{ "keys": ["", "Alt", "+", "R"], "action": "Record region" },
|
||||
{ "keys": ["Ctrl", "Alt", "+", "R"], "action": "Record region with sound" },
|
||||
{ "keys": ["", "Shift", "Alt", "+", "R"], "action": "Record screen with sound" }
|
||||
],
|
||||
"id": 5
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
"icon": "apps",
|
||||
"name": "Apps",
|
||||
"binds": [
|
||||
{ "keys": ["", "+", "T"], "action": "Launch terminal: foot" },
|
||||
{ "keys": ["", "+", "W"], "action": "Launch browser: Firefox" },
|
||||
{ "keys": ["", "+", "C"], "action": "Launch editor: vscode" },
|
||||
{ "keys": ["", "+", "X"], "action": "Launch editor: GNOME Text Editor" },
|
||||
{ "keys": ["", "+", "I"], "action": "Launch settings: GNOME Control center" }
|
||||
],
|
||||
"id": 6
|
||||
},
|
||||
{
|
||||
"icon": "keyboard",
|
||||
"name": "Typing",
|
||||
"binds": [
|
||||
{ "keys": ["", "+", "V"], "action": "Clipboard history >> clipboard" },
|
||||
{ "keys": ["", "+", "."], "action": "Emoji picker >> clipboard" },
|
||||
],
|
||||
"id": 7
|
||||
},
|
||||
{
|
||||
"icon": "terminal",
|
||||
"name": "Launcher actions",
|
||||
"binds": [
|
||||
{ "keys": [">raw"], "action": "Toggle mouse acceleration" },
|
||||
{ "keys": [">img"], "action": "Select wallpaper and generate colorscheme" },
|
||||
{ "keys": [">light"], "action": "Switch to light theme" },
|
||||
{ "keys": [">dark"], "action": "Switch to dark theme" },
|
||||
{ "keys": [">badapple"], "action": "Apply black n' white colorscheme" },
|
||||
{ "keys": [">color"], "action": "Pick acccent color" },
|
||||
{ "keys": [">todo"], "action": "Type something after that to add a To-do item" },
|
||||
],
|
||||
"id": 8
|
||||
}
|
||||
]];
|
||||
195
homes/me/ags-end4/modules/cheatsheet/data_periodictable.js
Normal file
195
homes/me/ags-end4/modules/cheatsheet/data_periodictable.js
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
export const periodicTable = [
|
||||
[
|
||||
{ name: 'Hydrogen', symbol: 'H', number: 1, weight: 1.01, type: 'nonmetal' },
|
||||
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
|
||||
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
|
||||
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
|
||||
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
|
||||
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
|
||||
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
|
||||
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
|
||||
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
|
||||
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
|
||||
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
|
||||
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
|
||||
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
|
||||
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
|
||||
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
|
||||
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
|
||||
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
|
||||
{ name: 'Helium', symbol: 'He', number: 2, weight: 4.00, type: 'noblegas' },
|
||||
],
|
||||
[
|
||||
{ name: 'Lithium', symbol: 'Li', number: 3, weight: 6.94, type: 'metal' },
|
||||
{ name: 'Beryllium', symbol: 'Be', number: 4, weight: 9.01, type: 'metal' },
|
||||
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
|
||||
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
|
||||
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
|
||||
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
|
||||
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
|
||||
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
|
||||
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
|
||||
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
|
||||
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
|
||||
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
|
||||
{ name: 'Boron', symbol: 'B', number: 5, weight: 10.81, type: 'nonmetal' },
|
||||
{ name: 'Carbon', symbol: 'C', number: 6, weight: 12.01, type: 'nonmetal' },
|
||||
{ name: 'Nitrogen', symbol: 'N', number: 7, weight: 14.01, type: 'nonmetal' },
|
||||
{ name: 'Oxygen', symbol: 'O', number: 8, weight: 16, type: 'nonmetal' },
|
||||
{ name: 'Fluorine', symbol: 'F', number: 9, weight: 19, type: 'nonmetal' },
|
||||
{ name: 'Neon', symbol: 'Ne', number: 10, weight: 20.18, type: 'noblegas' },
|
||||
|
||||
|
||||
],
|
||||
[
|
||||
{ name: 'Sodium', symbol: 'Na', number: 11, weight: 22.99, type: 'metal' },
|
||||
{ name: 'Magnesium', symbol: 'Mg', number: 12, weight: 24.31, type: 'metal' },
|
||||
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
|
||||
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
|
||||
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
|
||||
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
|
||||
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
|
||||
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
|
||||
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
|
||||
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
|
||||
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
|
||||
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
|
||||
{ name: 'Aluminum', symbol: 'Al', number: 13, weight: 26.98, type: 'metal' },
|
||||
{ name: 'Silicon', symbol: 'Si', number: 14, weight: 28.09, type: 'nonmetal' },
|
||||
{ name: 'Phosphorus', symbol: 'P', number: 15, weight: 30.97, type: 'nonmetal' },
|
||||
{ name: 'Sulfur', symbol: 'S', number: 16, weight: 32.07, type: 'nonmetal' },
|
||||
{ name: 'Chlorine', symbol: 'Cl', number: 17, weight: 35.45, type: 'nonmetal' },
|
||||
{ name: 'Argon', symbol: 'Ar', number: 18, weight: 39.95, type: 'noblegas' },
|
||||
],
|
||||
[
|
||||
{ name: 'Kalium', symbol: 'K', number: 19, weight: 39.098, type: 'metal' },
|
||||
{ name: 'Calcium', symbol: 'Ca', number: 20, weight: 40.078, type: 'metal' },
|
||||
{ name: 'Scandium', symbol: 'Sc', number: 21, weight: 44.956, type: 'metal' },
|
||||
{ name: 'Titanium', symbol: 'Ti', number: 22, weight: 47.87, type: 'metal' },
|
||||
{ name: 'Vanadium', symbol: 'V', number: 23, weight: 50.94, type: 'metal' },
|
||||
{ name: 'Chromium', symbol: 'Cr', number: 24, weight: 52, type: 'metal', icon: 'chromium-browser' },
|
||||
{ name: 'Manganese', symbol: 'Mn', number: 25, weight: 54.94, type: 'metal' },
|
||||
{ name: 'Iron', symbol: 'Fe', number: 26, weight: 55.85, type: 'metal' },
|
||||
{ name: 'Cobalt', symbol: 'Co', number: 27, weight: 58.93, type: 'metal' },
|
||||
{ name: 'Nickel', symbol: 'Ni', number: 28, weight: 58.69, type: 'metal' },
|
||||
{ name: 'Copper', symbol: 'Cu', number: 29, weight: 63.55, type: 'metal' },
|
||||
{ name: 'Zinc', symbol: 'Zn', number: 30, weight: 65.38, type: 'metal' },
|
||||
{ name: 'Gallium', symbol: 'Ga', number: 31, weight: 69.72, type: 'metal' },
|
||||
{ name: 'Germanium', symbol: 'Ge', number: 32, weight: 72.63, type: 'metal' },
|
||||
{ name: 'Arsenic', symbol: 'As', number: 33, weight: 74.92, type: 'nonmetal' },
|
||||
{ name: 'Selenium', symbol: 'Se', number: 34, weight: 78.96, type: 'nonmetal' },
|
||||
{ name: 'Bromine', symbol: 'Br', number: 35, weight: 79.904, type: 'nonmetal' },
|
||||
{ name: 'Krypton', symbol: 'Kr', number: 36, weight: 83.8, type: 'noblegas' },
|
||||
],
|
||||
[
|
||||
{ name: 'Rubidium', symbol: 'Rb', number: 37, weight: 85.47, type: 'metal' },
|
||||
{ name: 'Strontium', symbol: 'Sr', number: 38, weight: 87.62, type: 'metal' },
|
||||
{ name: 'Yttrium', symbol: 'Y', number: 39, weight: 88.91, type: 'metal' },
|
||||
{ name: 'Zirconium', symbol: 'Zr', number: 40, weight: 91.22, type: 'metal' },
|
||||
{ name: 'Niobium', symbol: 'Nb', number: 41, weight: 92.91, type: 'metal' },
|
||||
{ name: 'Molybdenum', symbol: 'Mo', number: 42, weight: 95.94, type: 'metal' },
|
||||
{ name: 'Technetium', symbol: 'Tc', number: 43, weight: 98, type: 'metal' },
|
||||
{ name: 'Ruthenium', symbol: 'Ru', number: 44, weight: 101.07, type: 'metal' },
|
||||
{ name: 'Rhodium', symbol: 'Rh', number: 45, weight: 102.91, type: 'metal' },
|
||||
{ name: 'Palladium', symbol: 'Pd', number: 46, weight: 106.42, type: 'metal' },
|
||||
{ name: 'Silver', symbol: 'Ag', number: 47, weight: 107.87, type: 'metal' },
|
||||
{ name: 'Cadmium', symbol: 'Cd', number: 48, weight: 112.41, type: 'metal' },
|
||||
{ name: 'Indium', symbol: 'In', number: 49, weight: 114.82, type: 'metal' },
|
||||
{ name: 'Tin', symbol: 'Sn', number: 50, weight: 118.71, type: 'metal' },
|
||||
{ name: 'Antimony', symbol: 'Sb', number: 51, weight: 121.76, type: 'metal' },
|
||||
{ name: 'Tellurium', symbol: 'Te', number: 52, weight: 127.6, type: 'nonmetal' },
|
||||
{ name: 'Iodine', symbol: 'I', number: 53, weight: 126.9, type: 'nonmetal' },
|
||||
{ name: 'Xenon', symbol: 'Xe', number: 54, weight: 131.29, type: 'noblegas' },
|
||||
],
|
||||
[
|
||||
{ name: 'Cesium', symbol: 'Cs', number: 55, weight: 132.91, type: 'metal' },
|
||||
{ name: 'Barium', symbol: 'Ba', number: 56, weight: 137.33, type: 'metal' },
|
||||
{ name: 'Lanthanum', symbol: 'La', number: 57, weight: 138.91, type: 'lanthanum' },
|
||||
{ name: 'Hafnium', symbol: 'Hf', number: 72, weight: 178.49, type: 'metal' },
|
||||
{ name: 'Tantalum', symbol: 'Ta', number: 73, weight: 180.95, type: 'metal' },
|
||||
{ name: 'Tungsten', symbol: 'W', number: 74, weight: 183.84, type: 'metal' },
|
||||
{ name: 'Rhenium', symbol: 'Re', number: 75, weight: 186.21, type: 'metal' },
|
||||
{ name: 'Osmium', symbol: 'Os', number: 76, weight: 190.23, type: 'metal' },
|
||||
{ name: 'Iridium', symbol: 'Ir', number: 77, weight: 192.22, type: 'metal' },
|
||||
{ name: 'Platinum', symbol: 'Pt', number: 78, weight: 195.09, type: 'metal' },
|
||||
{ name: 'Gold', symbol: 'Au', number: 79, weight: 196.97, type: 'metal' },
|
||||
{ name: 'Mercury', symbol: 'Hg', number: 80, weight: 200.59, type: 'metal' },
|
||||
{ name: 'Thallium', symbol: 'Tl', number: 81, weight: 204.38, type: 'metal' },
|
||||
{ name: 'Lead', symbol: 'Pb', number: 82, weight: 207.2, type: 'metal' },
|
||||
{ name: 'Bismuth', symbol: 'Bi', number: 83, weight: 208.98, type: 'metal' },
|
||||
{ name: 'Polonium', symbol: 'Po', number: 84, weight: 209, type: 'metal' },
|
||||
{ name: 'Astatine', symbol: 'At', number: 85, weight: 210, type: 'nonmetal' },
|
||||
{ name: 'Radon', symbol: 'Rn', number: 86, weight: 222, type: 'noblegas' },
|
||||
],
|
||||
[
|
||||
{ name: 'Francium', symbol: 'Fr', number: 87, weight: 223, type: 'metal' },
|
||||
{ name: 'Radium', symbol: 'Ra', number: 88, weight: 226, type: 'metal' },
|
||||
{ name: 'Actinium', symbol: 'Ac', number: 89, weight: 227, type: 'actinium' },
|
||||
{ name: 'Rutherfordium', symbol: 'Rf', number: 104, weight: 267, type: 'metal' },
|
||||
{ name: 'Dubnium', symbol: 'Db', number: 105, weight: 268, type: 'metal' },
|
||||
{ name: 'Seaborgium', symbol: 'Sg', number: 106, weight: 271, type: 'metal' },
|
||||
{ name: 'Bohrium', symbol: 'Bh', number: 107, weight: 272, type: 'metal' },
|
||||
{ name: 'Hassium', symbol: 'Hs', number: 108, weight: 277, type: 'metal' },
|
||||
{ name: 'Meitnerium', symbol: 'Mt', number: 109, weight: 278, type: 'metal' },
|
||||
{ name: 'Darmstadtium', symbol: 'Ds', number: 110, weight: 281, type: 'metal' },
|
||||
{ name: 'Roentgenium', symbol: 'Rg', number: 111, weight: 280, type: 'metal' },
|
||||
{ name: 'Copernicium', symbol: 'Cn', number: 112, weight: 285, type: 'metal' },
|
||||
{ name: 'Nihonium', symbol: 'Nh', number: 113, weight: 286, type: 'metal' },
|
||||
{ name: 'Flerovium', symbol: 'Fl', number: 114, weight: 289, type: 'metal' },
|
||||
{ name: 'Moscovium', symbol: 'Mc', number: 115, weight: 290, type: 'metal' },
|
||||
{ name: 'Livermorium', symbol: 'Lv', number: 116, weight: 293, type: 'metal' },
|
||||
{ name: 'Tennessine', symbol: 'Ts', number: 117, weight: 294, type: 'metal' },
|
||||
{ name: 'Oganesson', symbol: 'Og', number: 118, weight: 294, type: 'noblegas' },
|
||||
],
|
||||
]
|
||||
|
||||
export const series = [
|
||||
[
|
||||
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
|
||||
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
|
||||
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
|
||||
{ name: 'Cerium', symbol: 'Ce', number: 58, weight: 140.12, type: 'lanthanum' },
|
||||
{ name: 'Praseodymium', symbol: 'Pr', number: 59, weight: 140.91, type: 'lanthanum' },
|
||||
{ name: 'Neodymium', symbol: 'Nd', number: 60, weight: 144.24, type: 'lanthanum' },
|
||||
{ name: 'Promethium', symbol: 'Pm', number: 61, weight: 145, type: 'lanthanum' },
|
||||
{ name: 'Samarium', symbol: 'Sm', number: 62, weight: 150.36, type: 'lanthanum' },
|
||||
{ name: 'Europium', symbol: 'Eu', number: 63, weight: 151.96, type: 'lanthanum' },
|
||||
{ name: 'Gadolinium', symbol: 'Gd', number: 64, weight: 157.25, type: 'lanthanum' },
|
||||
{ name: 'Terbium', symbol: 'Tb', number: 65, weight: 158.93, type: 'lanthanum' },
|
||||
{ name: 'Dysprosium', symbol: 'Dy', number: 66, weight: 162.5, type: 'lanthanum' },
|
||||
{ name: 'Holmium', symbol: 'Ho', number: 67, weight: 164.93, type: 'lanthanum' },
|
||||
{ name: 'Erbium', symbol: 'Er', number: 68, weight: 167.26, type: 'lanthanum' },
|
||||
{ name: 'Thulium', symbol: 'Tm', number: 69, weight: 168.93, type: 'lanthanum' },
|
||||
{ name: 'Ytterbium', symbol: 'Yb', number: 70, weight: 173.04, type: 'lanthanum' },
|
||||
{ name: 'Lutetium', symbol: 'Lu', number: 71, weight: 174.97, type: 'lanthanum' },
|
||||
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
|
||||
],
|
||||
[
|
||||
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
|
||||
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
|
||||
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
|
||||
{ name: 'Thorium', symbol: 'Th', number: 90, weight: 232.04, type: 'actinium' },
|
||||
{ name: 'Protactinium', symbol: 'Pa', number: 91, weight: 231.04, type: 'actinium' },
|
||||
{ name: 'Uranium', symbol: 'U', number: 92, weight: 238.03, type: 'actinium' },
|
||||
{ name: 'Neptunium', symbol: 'Np', number: 93, weight: 237, type: 'actinium' },
|
||||
{ name: 'Plutonium', symbol: 'Pu', number: 94, weight: 244, type: 'actinium' },
|
||||
{ name: 'Americium', symbol: 'Am', number: 95, weight: 243, type: 'actinium' },
|
||||
{ name: 'Curium', symbol: 'Cm', number: 96, weight: 247, type: 'actinium' },
|
||||
{ name: 'Berkelium', symbol: 'Bk', number: 97, weight: 247, type: 'actinium' },
|
||||
{ name: 'Californium', symbol: 'Cf', number: 98, weight: 251, type: 'actinium' },
|
||||
{ name: 'Einsteinium', symbol: 'Es', number: 99, weight: 252, type: 'actinium' },
|
||||
{ name: 'Fermium', symbol: 'Fm', number: 100, weight: 257, type: 'actinium' },
|
||||
{ name: 'Mendelevium', symbol: 'Md', number: 101, weight: 258, type: 'actinium' },
|
||||
{ name: 'Nobelium', symbol: 'No', number: 102, weight: 259, type: 'actinium' },
|
||||
{ name: 'Lawrencium', symbol: 'Lr', number: 103, weight: 262, type: 'actinium' },
|
||||
{ name: '', symbol: '', number: -1, weight: 0, type: 'empty' },
|
||||
],
|
||||
];
|
||||
|
||||
export const niceTypes = {
|
||||
'metal': "Metal",
|
||||
'nonmetal': "Nonmetal",
|
||||
'noblegas': "Noble gas",
|
||||
'lanthanum': "Lanthanum",
|
||||
'actinium': "Actinium"
|
||||
}
|
||||
126
homes/me/ags-end4/modules/cheatsheet/keybinds.js
Normal file
126
homes/me/ags-end4/modules/cheatsheet/keybinds.js
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
const { GLib, Gtk } = imports.gi;
|
||||
import App from "resource:///com/github/Aylur/ags/app.js";
|
||||
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
|
||||
import Widget from "resource:///com/github/Aylur/ags/widget.js";
|
||||
import { IconTabContainer } from "../.commonwidgets/tabcontainer.js";
|
||||
const { Box, Label, Scrollable } = Widget;
|
||||
|
||||
const HYPRLAND_KEYBIND_CONFIG_FILE = userOptions.cheatsheet.keybinds.configPath ?
|
||||
userOptions.cheatsheet.keybinds.configPath : `${GLib.get_user_config_dir()}/hypr/hyprland/keybinds.conf`;
|
||||
const KEYBIND_SECTIONS_PER_PAGE = 3;
|
||||
const getKeybindList = () => {
|
||||
let data = Utils.exec(`${App.configDir}/scripts/hyprland/get_keybinds.py --path ${HYPRLAND_KEYBIND_CONFIG_FILE}`);
|
||||
if (data == "\"error\"") {
|
||||
Utils.timeout(2000, () => Utils.execAsync(['notify-send',
|
||||
'Update path to keybinds',
|
||||
'Keybinds hyprland config file not found. Check your user options.',
|
||||
'-a', 'ags',
|
||||
]).catch(print))
|
||||
return { children: [] };
|
||||
}
|
||||
return JSON.parse(data);
|
||||
};
|
||||
const keybindList = getKeybindList();
|
||||
|
||||
const keySubstitutions = {
|
||||
"Super": "",
|
||||
"mouse_up": "Scroll ↓", // ikr, weird
|
||||
"mouse_down": "Scroll ↑", // trust me bro
|
||||
"mouse:272": "LMB",
|
||||
"mouse:273": "RMB",
|
||||
"mouse:275": "MouseBack",
|
||||
"Slash": "/",
|
||||
"Hash": "#"
|
||||
}
|
||||
|
||||
const substituteKey = (key) => {
|
||||
return keySubstitutions[key] || key;
|
||||
}
|
||||
|
||||
const Keybind = (keybindData, type) => { // type: either "keys" or "actions"
|
||||
const Key = (key) => Label({ // Specific keys
|
||||
vpack: 'center',
|
||||
className: `${['OR', '+'].includes(key) ? 'cheatsheet-key-notkey' : 'cheatsheet-key'} txt-small`,
|
||||
label: substituteKey(key),
|
||||
});
|
||||
const Action = (text) => Label({ // Binds
|
||||
xalign: 0,
|
||||
label: getString(text),
|
||||
className: "txt txt-small cheatsheet-action",
|
||||
})
|
||||
return Widget.Box({
|
||||
className: "spacing-h-10 cheatsheet-bind-lineheight",
|
||||
children: type == "keys" ? [
|
||||
...(keybindData.mods.length > 0 ? [
|
||||
...keybindData.mods.map(Key),
|
||||
Key("+"),
|
||||
] : []),
|
||||
Key(keybindData.key),
|
||||
] : [Action(keybindData.comment)],
|
||||
})
|
||||
}
|
||||
|
||||
const Section = (sectionData, scope) => {
|
||||
const keys = Box({
|
||||
vertical: true,
|
||||
className: 'spacing-v-5',
|
||||
children: sectionData.keybinds.map((data) => Keybind(data, "keys"))
|
||||
})
|
||||
const actions = Box({
|
||||
vertical: true,
|
||||
className: 'spacing-v-5',
|
||||
children: sectionData.keybinds.map((data) => Keybind(data, "actions"))
|
||||
})
|
||||
const name = Label({
|
||||
xalign: 0,
|
||||
className: "cheatsheet-category-title txt margin-bottom-10",
|
||||
label: getString(sectionData.name),
|
||||
})
|
||||
const binds = Box({
|
||||
className: 'spacing-h-10',
|
||||
children: [
|
||||
keys,
|
||||
actions,
|
||||
]
|
||||
})
|
||||
const childrenSections = Box({
|
||||
vertical: true,
|
||||
className: 'spacing-v-15',
|
||||
children: sectionData.children.map((data) => Section(data, scope + 1))
|
||||
})
|
||||
return Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
...((sectionData.name && sectionData.name.length > 0) ? [name] : []),
|
||||
Box({
|
||||
className: 'spacing-v-10',
|
||||
children: [
|
||||
binds,
|
||||
childrenSections,
|
||||
]
|
||||
})
|
||||
]
|
||||
})
|
||||
};
|
||||
|
||||
export default () => {
|
||||
const numOfTabs = Math.ceil(keybindList.children.length / KEYBIND_SECTIONS_PER_PAGE);
|
||||
const keybindPages = Array.from({ length: numOfTabs }, (_, i) => ({
|
||||
iconWidget: Label({
|
||||
className: "txt txt-small",
|
||||
label: `${i + 1}`,
|
||||
}),
|
||||
name: `${i + 1}`,
|
||||
child: Box({
|
||||
className: 'spacing-h-30',
|
||||
children: keybindList.children.slice(
|
||||
KEYBIND_SECTIONS_PER_PAGE * i, 0 + KEYBIND_SECTIONS_PER_PAGE * (i + 1),
|
||||
).map(data => Section(data, 1)),
|
||||
}),
|
||||
}));
|
||||
return IconTabContainer({
|
||||
iconWidgets: keybindPages.map((kbp) => kbp.iconWidget),
|
||||
names: keybindPages.map((kbp) => kbp.name),
|
||||
children: keybindPages.map((kbp) => kbp.child),
|
||||
});
|
||||
};
|
||||
146
homes/me/ags-end4/modules/cheatsheet/main.js
Normal file
146
homes/me/ags-end4/modules/cheatsheet/main.js
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import { setupCursorHover } from "../.widgetutils/cursorhover.js";
|
||||
import PopupWindow from '../.widgethacks/popupwindow.js';
|
||||
import Keybinds from "./keybinds.js";
|
||||
import PeriodicTable from "./periodictable.js";
|
||||
import { ExpandingIconTabContainer } from '../.commonwidgets/tabcontainer.js';
|
||||
import { checkKeybind } from '../.widgetutils/keybind.js';
|
||||
import clickCloseRegion from '../.commonwidgets/clickcloseregion.js';
|
||||
|
||||
const cheatsheets = [
|
||||
{
|
||||
name: getString('Keybinds'),
|
||||
materialIcon: 'keyboard',
|
||||
contentWidget: Keybinds,
|
||||
},
|
||||
{
|
||||
name: getString('Periodic table'),
|
||||
materialIcon: 'experiment',
|
||||
contentWidget: PeriodicTable,
|
||||
},
|
||||
];
|
||||
|
||||
const CheatsheetHeader = () => Widget.CenterBox({
|
||||
vertical: false,
|
||||
startWidget: Widget.Box({}),
|
||||
centerWidget: Widget.Box({
|
||||
vertical: true,
|
||||
className: "spacing-h-15",
|
||||
children: [
|
||||
Widget.Box({
|
||||
hpack: 'center',
|
||||
className: 'spacing-h-5 cheatsheet-title',
|
||||
children: [
|
||||
Widget.Label({
|
||||
hpack: 'center',
|
||||
css: 'margin-right: 0.682rem;',
|
||||
className: 'txt-title',
|
||||
label: getString('Cheat sheet'),
|
||||
}),
|
||||
Widget.Label({
|
||||
vpack: 'center',
|
||||
className: "cheatsheet-key txt-small",
|
||||
label: "",
|
||||
}),
|
||||
Widget.Label({
|
||||
vpack: 'center',
|
||||
className: "cheatsheet-key-notkey txt-small",
|
||||
label: "+",
|
||||
}),
|
||||
Widget.Label({
|
||||
vpack: 'center',
|
||||
className: "cheatsheet-key txt-small",
|
||||
label: "/",
|
||||
})
|
||||
]
|
||||
}),
|
||||
]
|
||||
}),
|
||||
endWidget: Widget.Button({
|
||||
vpack: 'start',
|
||||
hpack: 'end',
|
||||
className: "cheatsheet-closebtn icon-material txt txt-hugeass",
|
||||
onClicked: () => {
|
||||
closeWindowOnAllMonitors('cheatsheet');
|
||||
},
|
||||
child: Widget.Label({
|
||||
className: 'icon-material txt txt-hugeass',
|
||||
label: 'close'
|
||||
}),
|
||||
setup: setupCursorHover,
|
||||
}),
|
||||
});
|
||||
|
||||
const sheetContents = [];
|
||||
const SheetContent = (id) => {
|
||||
sheetContents[id] = ExpandingIconTabContainer({
|
||||
tabsHpack: 'center',
|
||||
tabSwitcherClassName: 'sidebar-icontabswitcher',
|
||||
transitionDuration: userOptions.animations.durationLarge * 1.4,
|
||||
icons: cheatsheets.map((api) => api.materialIcon),
|
||||
names: cheatsheets.map((api) => api.name),
|
||||
children: cheatsheets.map((api) => api.contentWidget()),
|
||||
onChange: (self, id) => {
|
||||
self.shown = cheatsheets[id].name;
|
||||
}
|
||||
});
|
||||
return sheetContents[id];
|
||||
}
|
||||
|
||||
export default (id) => {
|
||||
const sheets = SheetContent(id);
|
||||
const widgetContent = Widget.Box({
|
||||
vertical: true,
|
||||
className: "cheatsheet-bg spacing-v-5",
|
||||
children: [
|
||||
CheatsheetHeader(),
|
||||
sheets,
|
||||
]
|
||||
});
|
||||
return PopupWindow({
|
||||
monitor: id,
|
||||
name: `cheatsheet${id}`,
|
||||
layer: 'top',
|
||||
keymode: 'on-demand',
|
||||
visible: false,
|
||||
anchor: ['top', 'bottom', 'left', 'right'],
|
||||
child: Widget.Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
clickCloseRegion({ name: 'cheatsheet' }),
|
||||
Widget.Box({
|
||||
children: [
|
||||
clickCloseRegion({ name: 'cheatsheet' }),
|
||||
widgetContent,
|
||||
clickCloseRegion({ name: 'cheatsheet' }),
|
||||
]
|
||||
}),
|
||||
clickCloseRegion({ name: 'cheatsheet' }),
|
||||
],
|
||||
setup: (self) => self.on('key-press-event', (widget, event) => { // Typing
|
||||
// Whole sheet
|
||||
if (checkKeybind(event, userOptions.keybinds.cheatsheet.nextTab))
|
||||
sheetContents.forEach(tab => tab.nextTab())
|
||||
else if (checkKeybind(event, userOptions.keybinds.cheatsheet.prevTab))
|
||||
sheetContents.forEach(tab => tab.prevTab())
|
||||
else if (checkKeybind(event, userOptions.keybinds.cheatsheet.cycleTab))
|
||||
sheetContents.forEach(tab => tab.cycleTab())
|
||||
// Keybinds
|
||||
if (sheets.attribute.names[sheets.attribute.shown.value] == 'Keybinds') { // If Keybinds tab is focused
|
||||
if (checkKeybind(event, userOptions.keybinds.cheatsheet.keybinds.nextTab)) {
|
||||
sheetContents.forEach((sheet) => {
|
||||
const toSwitchTab = sheet.attribute.children[sheet.attribute.shown.value];
|
||||
toSwitchTab.nextTab();
|
||||
})
|
||||
}
|
||||
else if (checkKeybind(event, userOptions.keybinds.cheatsheet.keybinds.prevTab)) {
|
||||
sheetContents.forEach((sheet) => {
|
||||
const toSwitchTab = sheet.attribute.children[sheet.attribute.shown.value];
|
||||
toSwitchTab.prevTab();
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
94
homes/me/ags-end4/modules/cheatsheet/periodictable.js
Normal file
94
homes/me/ags-end4/modules/cheatsheet/periodictable.js
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import { niceTypes, periodicTable, series } from "./data_periodictable.js";
|
||||
const { Box, Button, Icon, Label, Revealer } = Widget;
|
||||
|
||||
export default () => {
|
||||
const ElementTile = (element) => {
|
||||
return Box({
|
||||
vertical: true,
|
||||
tooltipText: element.electronConfig ? `${element.electronConfig}` : null,
|
||||
className: `cheatsheet-periodictable-${element.type}`,
|
||||
children: element.name == '' ? null : [
|
||||
Box({
|
||||
className: 'padding-left-8 padding-right-8 padding-top-8',
|
||||
children: [
|
||||
Label({
|
||||
label: `${element.number}`,
|
||||
className: "cheatsheet-periodictable-elementnum txt-tiny txt-bold",
|
||||
}),
|
||||
Box({ hexpand: true }),
|
||||
Label({
|
||||
label: `${element.weight}`,
|
||||
className: "txt-smaller",
|
||||
})
|
||||
]
|
||||
}),
|
||||
element.icon ? Icon({
|
||||
icon: element.icon,
|
||||
className: "txt-hugerass txt-bold",
|
||||
}) : Label({
|
||||
label: `${element.symbol}`,
|
||||
className: "cheatsheet-periodictable-elementsymbol",
|
||||
}),
|
||||
Label({
|
||||
label: `${element.name}`,
|
||||
className: "txt-tiny",
|
||||
})
|
||||
]
|
||||
})
|
||||
}
|
||||
const BoardColor = (type) => Box({
|
||||
className: 'spacing-h-5',
|
||||
children: [
|
||||
Box({
|
||||
homogeneous: true,
|
||||
className: `cheatsheet-periodictable-legend-color-wrapper`,
|
||||
children: [Box({
|
||||
className: `cheatsheet-periodictable-legend-color-${type}`,
|
||||
})]
|
||||
}),
|
||||
Label({
|
||||
label: `${niceTypes[type]}`,
|
||||
className: "txt txt-small",
|
||||
})
|
||||
]
|
||||
})
|
||||
const mainBoard = Box({
|
||||
hpack: 'center',
|
||||
vertical: true,
|
||||
className: "spacing-v-3",
|
||||
children: periodicTable.map((row, _) => Box({ // Rows
|
||||
className: "spacing-h-5",
|
||||
children: row.map((element, _) => ElementTile(element))
|
||||
})),
|
||||
});
|
||||
const seriesBoard = Box({
|
||||
hpack: 'center',
|
||||
vertical: true,
|
||||
className: "spacing-v-3",
|
||||
children: series.map((row, _) => Box({ // Rows
|
||||
className: "spacing-h-5",
|
||||
children: row.map((element, _) => ElementTile(element))
|
||||
})),
|
||||
});
|
||||
const legend = Box({
|
||||
hpack: 'center',
|
||||
className: 'spacing-h-20',
|
||||
children: [
|
||||
BoardColor('metal'),
|
||||
BoardColor('nonmetal'),
|
||||
BoardColor('noblegas'),
|
||||
BoardColor('lanthanum'),
|
||||
BoardColor('actinium'),
|
||||
]
|
||||
})
|
||||
return Box({
|
||||
vertical: true,
|
||||
className: 'spacing-v-20',
|
||||
children: [
|
||||
mainBoard,
|
||||
seriesBoard,
|
||||
legend
|
||||
]
|
||||
})
|
||||
}
|
||||
21
homes/me/ags-end4/modules/crosshair/main.js
Normal file
21
homes/me/ags-end4/modules/crosshair/main.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import { enableClickthrough } from "../.widgetutils/clickthrough.js";
|
||||
|
||||
export default (monitor = 0, ) => {
|
||||
return Widget.Window({
|
||||
monitor,
|
||||
name: `crosshair${monitor}`,
|
||||
layer: 'overlay',
|
||||
exclusivity: 'ignore',
|
||||
visible: false,
|
||||
child: Widget.Icon({
|
||||
icon: 'crosshair-symbolic',
|
||||
css: `
|
||||
font-size: ${userOptions.gaming.crosshair.size}px;
|
||||
color: ${userOptions.gaming.crosshair.color};
|
||||
`,
|
||||
}),
|
||||
setup: enableClickthrough,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
export const quickLaunchItems = [
|
||||
{
|
||||
"name": "GitHub + Files×2",
|
||||
"command": "github-desktop & nautilus --new-window & nautilus --new-window &"
|
||||
},
|
||||
{
|
||||
"name": "Terminal×2",
|
||||
"command": "foot & foot &"
|
||||
},
|
||||
{
|
||||
"name": "Discord + Youtube + Github",
|
||||
"command": "xdg-open 'https://discord.com/app' && xdg-open 'https://youtube.com/' && xdg-open 'https://github.com/' &"
|
||||
},
|
||||
]
|
||||
24
homes/me/ags-end4/modules/desktopbackground/main.js
Normal file
24
homes/me/ags-end4/modules/desktopbackground/main.js
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
|
||||
import WallpaperImage from './wallpaper.js';
|
||||
import TimeAndLaunchesWidget from './timeandlaunches.js'
|
||||
import SystemWidget from './system.js'
|
||||
|
||||
export default (monitor) => Widget.Window({
|
||||
name: `desktopbackground${monitor}`,
|
||||
// anchor: ['top', 'bottom', 'left', 'right'],
|
||||
layer: 'background',
|
||||
exclusivity: 'ignore',
|
||||
visible: true,
|
||||
child: Widget.Overlay({
|
||||
child: WallpaperImage(monitor),
|
||||
// child: Widget.Box({}),
|
||||
overlays: [
|
||||
TimeAndLaunchesWidget(),
|
||||
SystemWidget(),
|
||||
],
|
||||
setup: (self) => {
|
||||
self.set_overlay_pass_through(self.get_children()[1], true);
|
||||
},
|
||||
}),
|
||||
});
|
||||
161
homes/me/ags-end4/modules/desktopbackground/system.js
Normal file
161
homes/me/ags-end4/modules/desktopbackground/system.js
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
const { execAsync, exec } = Utils;
|
||||
const { Box, EventBox, Label, Revealer, Overlay } = Widget;
|
||||
import { AnimatedCircProg } from "../.commonwidgets/cairo_circularprogress.js";
|
||||
import { MaterialIcon } from '../.commonwidgets/materialicon.js';
|
||||
|
||||
const ResourceValue = (name, icon, interval, valueUpdateCmd, displayFunc, props = {}) => Box({
|
||||
...props,
|
||||
className: 'bg-system-bg txt',
|
||||
children: [
|
||||
Revealer({
|
||||
transition: 'slide_left',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
child: Box({
|
||||
vpack: 'center',
|
||||
vertical: true,
|
||||
className: 'margin-right-15',
|
||||
children: [
|
||||
Label({
|
||||
xalign: 1,
|
||||
className: 'txt-small txt',
|
||||
label: `${name}`,
|
||||
}),
|
||||
Label({
|
||||
xalign: 1,
|
||||
className: 'titlefont txt-norm txt-onSecondaryContainer',
|
||||
setup: (self) => self
|
||||
.poll(interval, (label) => displayFunc(label))
|
||||
,
|
||||
})
|
||||
]
|
||||
})
|
||||
}),
|
||||
Overlay({
|
||||
child: AnimatedCircProg({
|
||||
className: 'bg-system-circprog',
|
||||
extraSetup: (self) => self
|
||||
.poll(interval, (self) => {
|
||||
execAsync(['bash', '-c', `${valueUpdateCmd}`]).then((newValue) => {
|
||||
self.css = `font-size: ${Math.round(newValue)}px;`
|
||||
}).catch(print);
|
||||
})
|
||||
,
|
||||
}),
|
||||
overlays: [
|
||||
MaterialIcon(`${icon}`, 'hugeass'),
|
||||
],
|
||||
setup: self => self.set_overlay_pass_through(self.get_children()[1], true),
|
||||
}),
|
||||
]
|
||||
})
|
||||
|
||||
const resources = Box({
|
||||
vpack: 'fill',
|
||||
vertical: true,
|
||||
className: 'spacing-v-15',
|
||||
children: [
|
||||
ResourceValue('Memory', 'memory', 10000, `free | awk '/^Mem/ {printf("%.2f\\n", ($3/$2) * 100)}'`,
|
||||
(label) => {
|
||||
execAsync(['bash', '-c', `free -h | awk '/^Mem/ {print $3 " / " $2}' | sed 's/Gi/Gib/g'`])
|
||||
.then((output) => {
|
||||
label.label = `${output}`
|
||||
}).catch(print);
|
||||
}, { hpack: 'end' }),
|
||||
ResourceValue('Swap', 'swap_horiz', 10000, `free | awk '/^Swap/ {if ($2 > 0) printf("%.2f\\n", ($3/$2) * 100); else print "0";}'`,
|
||||
(label) => {
|
||||
execAsync(['bash', '-c', `free -h | awk '/^Swap/ {if ($2 != "0") print $3 " / " $2; else print "No swap"}' | sed 's/Gi/Gib/g'`])
|
||||
.then((output) => {
|
||||
label.label = `${output}`
|
||||
}).catch(print);
|
||||
}, { hpack: 'end' }),
|
||||
ResourceValue('Disk space', 'hard_drive_2', 3600000, `echo $(df --output=pcent / | tr -dc '0-9')`,
|
||||
(label) => {
|
||||
execAsync(['bash', '-c', `df -h --output=avail / | awk 'NR==2{print $1}'`])
|
||||
.then((output) => {
|
||||
label.label = `${output} available`
|
||||
}).catch(print);
|
||||
}, { hpack: 'end' }),
|
||||
]
|
||||
});
|
||||
|
||||
const distroAndVersion = Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
Box({
|
||||
hpack: 'end',
|
||||
children: [
|
||||
Label({
|
||||
className: 'bg-distro-txt',
|
||||
xalign: 0,
|
||||
label: 'Hyping on ',
|
||||
}),
|
||||
Label({
|
||||
className: 'bg-distro-name',
|
||||
xalign: 0,
|
||||
label: '<distro>',
|
||||
setup: (label) => {
|
||||
execAsync([`grep`, `-oP`, `PRETTY_NAME="\\K[^"]+`, `/etc/os-release`]).then(distro => {
|
||||
label.label = distro;
|
||||
}).catch(print);
|
||||
},
|
||||
}),
|
||||
]
|
||||
}),
|
||||
Box({
|
||||
hpack: 'end',
|
||||
children: [
|
||||
Label({
|
||||
className: 'bg-distro-txt',
|
||||
xalign: 0,
|
||||
label: 'with ',
|
||||
}),
|
||||
Label({
|
||||
className: 'bg-distro-name',
|
||||
xalign: 0,
|
||||
label: 'An environment idk',
|
||||
setup: (label) => {
|
||||
// hyprctl will return unsuccessfully if Hyprland isn't running
|
||||
execAsync([`bash`, `-c`, `hyprctl version | grep -oP "Tag: v\\K\\d+\\.\\d+\\.\\d+"`]).then(version => {
|
||||
label.label = `Hyprland ${version}`;
|
||||
}).catch(() => execAsync([`bash`, `-c`, `sway -v | cut -d'-' -f1 | sed 's/sway version /v/'`]).then(version => {
|
||||
label.label = `Sway ${version}`;
|
||||
}).catch(print));
|
||||
},
|
||||
}),
|
||||
]
|
||||
})
|
||||
]
|
||||
})
|
||||
|
||||
export default () => Box({
|
||||
hpack: 'end',
|
||||
vpack: 'end',
|
||||
children: [
|
||||
EventBox({
|
||||
child: Box({
|
||||
hpack: 'end',
|
||||
vpack: 'end',
|
||||
className: 'bg-distro-box spacing-v-20',
|
||||
vertical: true,
|
||||
children: [
|
||||
resources,
|
||||
distroAndVersion,
|
||||
]
|
||||
}),
|
||||
onPrimaryClickRelease: () => {
|
||||
const kids = resources.get_children();
|
||||
for (let i = 0; i < kids.length; i++) {
|
||||
const child = kids[i];
|
||||
const firstChild = child.get_children()[0];
|
||||
firstChild.revealChild = !firstChild.revealChild;
|
||||
}
|
||||
|
||||
},
|
||||
})
|
||||
],
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
const { GLib } = imports.gi;
|
||||
import App from 'resource:///com/github/Aylur/ags/app.js';
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import Service from 'resource:///com/github/Aylur/ags/service.js';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
|
||||
import Variable from 'resource:///com/github/Aylur/ags/variable.js';
|
||||
const { execAsync, exec } = Utils;
|
||||
const { Box, Label, Button, Revealer, EventBox } = Widget;
|
||||
import { setupCursorHover } from '../.widgetutils/cursorhover.js';
|
||||
import { quickLaunchItems } from './data_quicklaunches.js'
|
||||
|
||||
const TimeAndDate = () => Box({
|
||||
vertical: true,
|
||||
className: 'spacing-v--5',
|
||||
children: [
|
||||
Label({
|
||||
className: 'bg-time-clock',
|
||||
xalign: 0,
|
||||
label: GLib.DateTime.new_now_local().format(userOptions.time.format),
|
||||
setup: (self) => self.poll(userOptions.time.interval, label => {
|
||||
label.label = GLib.DateTime.new_now_local().format(userOptions.time.format);
|
||||
}),
|
||||
}),
|
||||
Label({
|
||||
className: 'bg-time-date',
|
||||
xalign: 0,
|
||||
label: GLib.DateTime.new_now_local().format(userOptions.time.dateFormatLong),
|
||||
setup: (self) => self.poll(userOptions.time.dateInterval, (label) => {
|
||||
label.label = GLib.DateTime.new_now_local().format(userOptions.time.dateFormatLong);
|
||||
}),
|
||||
}),
|
||||
]
|
||||
})
|
||||
|
||||
const QuickLaunches = () => Box({
|
||||
vertical: true,
|
||||
className: 'spacing-v-10',
|
||||
children: [
|
||||
Label({
|
||||
xalign: 0,
|
||||
className: 'bg-quicklaunch-title',
|
||||
label: 'Quick Launches',
|
||||
}),
|
||||
Box({
|
||||
hpack: 'start',
|
||||
className: 'spacing-h-5',
|
||||
children: quickLaunchItems.map((item, i) => Button({
|
||||
onClicked: () => {
|
||||
execAsync(['bash', '-c', `${item["command"]}`]).catch(print);
|
||||
},
|
||||
className: 'bg-quicklaunch-btn',
|
||||
child: Label({
|
||||
label: `${item["name"]}`,
|
||||
}),
|
||||
setup: (self) => {
|
||||
setupCursorHover(self);
|
||||
}
|
||||
})),
|
||||
})
|
||||
]
|
||||
})
|
||||
|
||||
export default () => Box({
|
||||
hpack: 'start',
|
||||
vpack: 'end',
|
||||
vertical: true,
|
||||
className: 'bg-time-box spacing-h--10',
|
||||
children: [
|
||||
TimeAndDate(),
|
||||
// QuickLaunches(),
|
||||
],
|
||||
})
|
||||
|
||||
119
homes/me/ags-end4/modules/desktopbackground/wallpaper.js
Normal file
119
homes/me/ags-end4/modules/desktopbackground/wallpaper.js
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
const { Gdk, GdkPixbuf, Gio, GLib, Gtk } = imports.gi;
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
const { exec, execAsync } = Utils;
|
||||
const { Box, Button, Label, Stack } = Widget;
|
||||
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
|
||||
|
||||
import Wallpaper from '../../services/wallpaper.js';
|
||||
import { setupCursorHover } from '../.widgetutils/cursorhover.js';
|
||||
import { clamp } from '../.miscutils/mathfuncs.js';
|
||||
import { monitors } from '../.commondata/hyprlanddata.js';
|
||||
|
||||
const DISABLE_AGS_WALLPAPER = true;
|
||||
|
||||
const SWITCHWALL_SCRIPT_PATH = `${App.configDir}/scripts/color_generation/switchwall.sh`;
|
||||
const WALLPAPER_ZOOM_SCALE = 1.25; // For scrolling when we switch workspace
|
||||
const MAX_WORKSPACES = 10;
|
||||
|
||||
export default (monitor = 0) => {
|
||||
const WALLPAPER_OFFSCREEN_X = (WALLPAPER_ZOOM_SCALE - 1) * monitors[monitor].width;
|
||||
const WALLPAPER_OFFSCREEN_Y = (WALLPAPER_ZOOM_SCALE - 1) * monitors[monitor].height;
|
||||
const wallpaperImage = Widget.DrawingArea({
|
||||
attribute: {
|
||||
pixbuf: undefined,
|
||||
workspace: 1,
|
||||
sideleft: 0,
|
||||
sideright: 0,
|
||||
updatePos: (self) => {
|
||||
self.setCss(`font-size: ${self.attribute.workspace - self.attribute.sideleft + self.attribute.sideright}px;`)
|
||||
},
|
||||
},
|
||||
className: 'bg-wallpaper-transition',
|
||||
setup: (self) => {
|
||||
self.set_size_request(monitors[monitor].width, monitors[monitor].height);
|
||||
self
|
||||
// TODO: reduced updates using timeouts to reduce lag
|
||||
// .hook(Hyprland.active.workspace, (self) => {
|
||||
// self.attribute.workspace = Hyprland.active.workspace.id
|
||||
// self.attribute.updatePos(self);
|
||||
// })
|
||||
// .hook(App, (box, name, visible) => { // Update on open
|
||||
// if (self.attribute[name] === undefined) return;
|
||||
// self.attribute[name] = (visible ? 1 : 0);
|
||||
// self.attribute.updatePos(self);
|
||||
// })
|
||||
.on('draw', (self, cr) => {
|
||||
if (!self.attribute.pixbuf) return;
|
||||
const styleContext = self.get_style_context();
|
||||
const workspace = styleContext.get_property('font-size', Gtk.StateFlags.NORMAL);
|
||||
// Draw
|
||||
Gdk.cairo_set_source_pixbuf(cr, self.attribute.pixbuf,
|
||||
-(WALLPAPER_OFFSCREEN_X / (MAX_WORKSPACES + 1) * (clamp(workspace, 0, MAX_WORKSPACES + 1))),
|
||||
-WALLPAPER_OFFSCREEN_Y / 2);
|
||||
cr.paint();
|
||||
})
|
||||
.hook(Wallpaper, (self) => {
|
||||
if (DISABLE_AGS_WALLPAPER) return;
|
||||
const wallPath = Wallpaper.get(monitor);
|
||||
if (!wallPath || wallPath === "") return;
|
||||
self.attribute.pixbuf = GdkPixbuf.Pixbuf.new_from_file(wallPath);
|
||||
|
||||
const scale_x = monitors[monitor].width * WALLPAPER_ZOOM_SCALE / self.attribute.pixbuf.get_width();
|
||||
const scale_y = monitors[monitor].height * WALLPAPER_ZOOM_SCALE / self.attribute.pixbuf.get_height();
|
||||
const scale_factor = Math.max(scale_x, scale_y);
|
||||
|
||||
self.attribute.pixbuf = self.attribute.pixbuf.scale_simple(
|
||||
Math.round(self.attribute.pixbuf.get_width() * scale_factor),
|
||||
Math.round(self.attribute.pixbuf.get_height() * scale_factor),
|
||||
GdkPixbuf.InterpType.BILINEAR
|
||||
);
|
||||
self.queue_draw();
|
||||
}, 'updated');
|
||||
;
|
||||
}
|
||||
,
|
||||
});
|
||||
const wallpaperPrompt = Box({
|
||||
hpack: 'center',
|
||||
vpack: 'center',
|
||||
vertical: true,
|
||||
className: 'spacing-v-10',
|
||||
children: [
|
||||
Label({
|
||||
hpack: 'center',
|
||||
justification: 'center',
|
||||
className: 'txt-large',
|
||||
label: `No wallpaper loaded.\nAn image ≥ ${monitors[monitor].width * WALLPAPER_ZOOM_SCALE} × ${monitors[monitor].height * WALLPAPER_ZOOM_SCALE} is recommended.`,
|
||||
}),
|
||||
Button({
|
||||
hpack: 'center',
|
||||
className: 'btn-primary',
|
||||
label: `Select one`,
|
||||
setup: setupCursorHover,
|
||||
onClicked: (self) => Utils.execAsync([SWITCHWALL_SCRIPT_PATH]).catch(print),
|
||||
}),
|
||||
]
|
||||
});
|
||||
const stack = Stack({
|
||||
transition: 'crossfade',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
children: {
|
||||
'disabled': Box({}),
|
||||
'image': wallpaperImage,
|
||||
'prompt': wallpaperPrompt,
|
||||
},
|
||||
setup: (self) => self
|
||||
.hook(Wallpaper, (self) => {
|
||||
if (DISABLE_AGS_WALLPAPER) {
|
||||
self.shown = 'disabled';
|
||||
return;
|
||||
}
|
||||
const wallPath = Wallpaper.get(monitor);
|
||||
self.shown = ((wallPath && wallPath != "") ? 'image' : 'prompt');
|
||||
}, 'updated')
|
||||
,
|
||||
})
|
||||
return stack;
|
||||
// return wallpaperImage;
|
||||
}
|
||||
300
homes/me/ags-end4/modules/dock/dock.js
Executable file
300
homes/me/ags-end4/modules/dock/dock.js
Executable file
|
|
@ -0,0 +1,300 @@
|
|||
const { Gtk, GLib } = imports.gi;
|
||||
import App from 'resource:///com/github/Aylur/ags/app.js';
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
const { EventBox, Button } = Widget;
|
||||
|
||||
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
|
||||
import Applications from 'resource:///com/github/Aylur/ags/service/applications.js';
|
||||
const { execAsync, exec } = Utils;
|
||||
const { Box, Revealer } = Widget;
|
||||
import { setupCursorHover } from '../.widgetutils/cursorhover.js';
|
||||
import { getAllFiles, searchIcons } from './icons.js'
|
||||
import { MaterialIcon } from '../.commonwidgets/materialicon.js';
|
||||
import { substitute } from '../.miscutils/icons.js';
|
||||
|
||||
const icon_files = userOptions.icons.searchPaths.map(e => getAllFiles(e)).flat(1)
|
||||
|
||||
let isPinned = false
|
||||
let cachePath = new Map()
|
||||
|
||||
let timers = []
|
||||
|
||||
function clearTimes() {
|
||||
timers.forEach(e => GLib.source_remove(e))
|
||||
timers = []
|
||||
}
|
||||
|
||||
function ExclusiveWindow(client) {
|
||||
const fn = [
|
||||
(client) => !(client !== null && client !== undefined),
|
||||
// Jetbrains
|
||||
(client) => client.title.includes("win"),
|
||||
// Vscode
|
||||
(client) => client.title === '' && client.class === ''
|
||||
]
|
||||
|
||||
for (const item of fn) { if (item(client)) { return true } }
|
||||
return false
|
||||
}
|
||||
|
||||
const focus = ({ address }) => Utils.execAsync(`hyprctl dispatch focuswindow address:${address}`).catch(print);
|
||||
|
||||
const DockSeparator = (props = {}) => Box({
|
||||
...props,
|
||||
className: 'dock-separator',
|
||||
})
|
||||
|
||||
const PinButton = () => Widget.Button({
|
||||
className: 'dock-app-btn dock-app-btn-animate',
|
||||
tooltipText: 'Pin Dock',
|
||||
child: Widget.Box({
|
||||
homogeneous: true,
|
||||
className: 'dock-app-icon txt',
|
||||
child: MaterialIcon('push_pin', 'hugeass')
|
||||
}),
|
||||
onClicked: (self) => {
|
||||
isPinned = !isPinned
|
||||
self.className = `${isPinned ? "pinned-dock-app-btn" : "dock-app-btn animate"} dock-app-btn-animate`
|
||||
},
|
||||
setup: setupCursorHover,
|
||||
})
|
||||
|
||||
const LauncherButton = () => Widget.Button({
|
||||
className: 'dock-app-btn dock-app-btn-animate',
|
||||
tooltipText: 'Open launcher',
|
||||
child: Widget.Box({
|
||||
homogeneous: true,
|
||||
className: 'dock-app-icon txt',
|
||||
child: MaterialIcon('apps', 'hugerass')
|
||||
}),
|
||||
onClicked: (self) => {
|
||||
App.toggleWindow('overview');
|
||||
},
|
||||
setup: setupCursorHover,
|
||||
})
|
||||
|
||||
const AppButton = ({ icon, ...rest }) => Widget.Revealer({
|
||||
attribute: {
|
||||
'workspace': 0
|
||||
},
|
||||
revealChild: false,
|
||||
transition: 'slide_right',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
child: Widget.Button({
|
||||
...rest,
|
||||
className: 'dock-app-btn dock-app-btn-animate',
|
||||
child: Widget.Box({
|
||||
child: Widget.Overlay({
|
||||
child: Widget.Box({
|
||||
homogeneous: true,
|
||||
className: 'dock-app-icon',
|
||||
child: Widget.Icon({
|
||||
icon: icon,
|
||||
}),
|
||||
}),
|
||||
overlays: [Widget.Box({
|
||||
class_name: 'indicator',
|
||||
vpack: 'end',
|
||||
hpack: 'center',
|
||||
})],
|
||||
}),
|
||||
}),
|
||||
setup: (button) => {
|
||||
setupCursorHover(button);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const Taskbar = (monitor) => Widget.Box({
|
||||
className: 'dock-apps',
|
||||
attribute: {
|
||||
monitor: monitor,
|
||||
'map': new Map(),
|
||||
'clientSortFunc': (a, b) => {
|
||||
return a.attribute.workspace > b.attribute.workspace;
|
||||
},
|
||||
'update': (box, monitor) => {
|
||||
for (let i = 0; i < Hyprland.clients.length; i++) {
|
||||
const client = Hyprland.clients[i];
|
||||
if (client["pid"] == -1) return;
|
||||
const appClass = substitute(client.class);
|
||||
// for (const appName of userOptions.dock.pinnedApps) {
|
||||
// if (appClass.includes(appName.toLowerCase()))
|
||||
// return null;
|
||||
// }
|
||||
let appClassLower = appClass.toLowerCase()
|
||||
let path = ''
|
||||
if (cachePath[appClassLower]) { path = cachePath[appClassLower] }
|
||||
else {
|
||||
path = searchIcons(appClass.toLowerCase(), icon_files)
|
||||
cachePath[appClassLower] = path
|
||||
}
|
||||
if (path === '') { path = substitute(appClass) }
|
||||
const newButton = AppButton({
|
||||
icon: path,
|
||||
tooltipText: `${client.title} (${appClass})`,
|
||||
onClicked: () => focus(client),
|
||||
});
|
||||
newButton.attribute.workspace = client.workspace.id;
|
||||
newButton.revealChild = true;
|
||||
box.attribute.map.set(client.address, newButton);
|
||||
}
|
||||
box.children = Array.from(box.attribute.map.values());
|
||||
},
|
||||
'add': (box, address, monitor) => {
|
||||
if (!address) { // First active emit is undefined
|
||||
box.attribute.update(box);
|
||||
return;
|
||||
}
|
||||
const newClient = Hyprland.clients.find(client => {
|
||||
return client.address == address;
|
||||
});
|
||||
if (ExclusiveWindow(newClient)) { return }
|
||||
let appClass = newClient.class
|
||||
let appClassLower = appClass.toLowerCase()
|
||||
let path = ''
|
||||
if (cachePath[appClassLower]) { path = cachePath[appClassLower] }
|
||||
else {
|
||||
path = searchIcons(appClassLower, icon_files)
|
||||
cachePath[appClassLower] = path
|
||||
}
|
||||
if (path === '') { path = substitute(appClass) }
|
||||
const newButton = AppButton({
|
||||
icon: path,
|
||||
tooltipText: `${newClient.title} (${appClass})`,
|
||||
onClicked: () => focus(newClient),
|
||||
})
|
||||
newButton.attribute.workspace = newClient.workspace.id;
|
||||
box.attribute.map.set(address, newButton);
|
||||
box.children = Array.from(box.attribute.map.values());
|
||||
newButton.revealChild = true;
|
||||
},
|
||||
'remove': (box, address) => {
|
||||
if (!address) return;
|
||||
|
||||
const removedButton = box.attribute.map.get(address);
|
||||
if (!removedButton) return;
|
||||
removedButton.revealChild = false;
|
||||
|
||||
Utils.timeout(userOptions.animations.durationLarge, () => {
|
||||
removedButton.destroy();
|
||||
box.attribute.map.delete(address);
|
||||
box.children = Array.from(box.attribute.map.values());
|
||||
})
|
||||
},
|
||||
},
|
||||
setup: (self) => {
|
||||
self.hook(Hyprland, (box, address) => box.attribute.add(box, address, self.monitor), 'client-added')
|
||||
.hook(Hyprland, (box, address) => box.attribute.remove(box, address, self.monitor), 'client-removed')
|
||||
Utils.timeout(100, () => self.attribute.update(self));
|
||||
},
|
||||
});
|
||||
|
||||
const PinnedApps = () => Widget.Box({
|
||||
class_name: 'dock-apps',
|
||||
homogeneous: true,
|
||||
children: userOptions.dock.pinnedApps
|
||||
.map(term => ({ app: Applications.query(term)?.[0], term }))
|
||||
.filter(({ app }) => app)
|
||||
.map(({ app, term = true }) => {
|
||||
const newButton = AppButton({
|
||||
// different icon, emm...
|
||||
icon: userOptions.dock.searchPinnedAppIcons ?
|
||||
searchIcons(app.name, icon_files) :
|
||||
app.icon_name,
|
||||
onClicked: () => {
|
||||
for (const client of Hyprland.clients) {
|
||||
if (client.class.toLowerCase().includes(term))
|
||||
return focus(client);
|
||||
}
|
||||
|
||||
app.launch();
|
||||
},
|
||||
onMiddleClick: () => app.launch(),
|
||||
tooltipText: app.name,
|
||||
setup: (self) => {
|
||||
self.revealChild = true;
|
||||
self.hook(Hyprland, button => {
|
||||
const running = Hyprland.clients
|
||||
.find(client => client.class.toLowerCase().includes(term)) || false;
|
||||
|
||||
button.toggleClassName('notrunning', !running);
|
||||
button.toggleClassName('focused', Hyprland.active.client.address == running.address);
|
||||
button.set_tooltip_text(running ? running.title : app.name);
|
||||
}, 'notify::clients')
|
||||
},
|
||||
})
|
||||
newButton.revealChild = true;
|
||||
return newButton;
|
||||
}),
|
||||
});
|
||||
|
||||
export default (monitor = 0) => {
|
||||
const dockContent = Box({
|
||||
className: 'dock-bg spacing-h-5',
|
||||
children: [
|
||||
PinButton(),
|
||||
PinnedApps(),
|
||||
DockSeparator(),
|
||||
Taskbar(),
|
||||
LauncherButton(),
|
||||
]
|
||||
})
|
||||
const dockRevealer = Revealer({
|
||||
attribute: {
|
||||
'updateShow': self => { // I only use mouse to resize. I don't care about keyboard resize if that's a thing
|
||||
if (userOptions.dock.monitorExclusivity)
|
||||
self.revealChild = Hyprland.active.monitor.id === monitor;
|
||||
else
|
||||
self.revealChild = true;
|
||||
|
||||
return self.revealChild
|
||||
}
|
||||
},
|
||||
revealChild: false,
|
||||
transition: 'slide_up',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
child: dockContent,
|
||||
setup: (self) => {
|
||||
const callback = (self, trigger) => {
|
||||
if (!userOptions.dock.trigger.includes(trigger)) return
|
||||
const flag = self.attribute.updateShow(self)
|
||||
|
||||
if (flag) clearTimes();
|
||||
|
||||
const hidden = userOptions.dock.autoHide.find(e => e["trigger"] === trigger)
|
||||
|
||||
if (hidden) {
|
||||
let id = Utils.timeout(hidden.interval, () => {
|
||||
if (!isPinned) { self.revealChild = false }
|
||||
timers = timers.filter(e => e !== id)
|
||||
})
|
||||
timers.push(id)
|
||||
}
|
||||
}
|
||||
|
||||
self
|
||||
// .hook(Hyprland, (self) => self.attribute.updateShow(self))
|
||||
.hook(Hyprland.active.workspace, self => callback(self, "workspace-active"))
|
||||
.hook(Hyprland.active.client, self => callback(self, "client-active"))
|
||||
.hook(Hyprland, self => callback(self, "client-added"), "client-added")
|
||||
.hook(Hyprland, self => callback(self, "client-removed"), "client-removed")
|
||||
},
|
||||
})
|
||||
return EventBox({
|
||||
onHover: () => {
|
||||
dockRevealer.revealChild = true;
|
||||
clearTimes()
|
||||
},
|
||||
child: Box({
|
||||
homogeneous: true,
|
||||
css: `min-height: ${userOptions.dock.hiddenThickness}px;`,
|
||||
children: [dockRevealer],
|
||||
}),
|
||||
setup: self => self.on("leave-notify-event", () => {
|
||||
if (!isPinned) dockRevealer.revealChild = false;
|
||||
clearTimes()
|
||||
})
|
||||
})
|
||||
}
|
||||
63
homes/me/ags-end4/modules/dock/icons.js
Normal file
63
homes/me/ags-end4/modules/dock/icons.js
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
const { Gio, GLib } = imports.gi
|
||||
|
||||
const exists = (path) => Gio.File.new_for_path(path).query_exists(null);
|
||||
|
||||
export const levenshteinDistance = (a, b) => {
|
||||
if (!a.length) { return b.length }
|
||||
if (!b.length) { return a.length }
|
||||
|
||||
let f = Array.from(new Array(a.length + 1),
|
||||
() => new Array(b.length + 1).fill(0))
|
||||
|
||||
for (let i = 0; i <= b.length; i++) { f[0][i] = i; }
|
||||
for (let i = 0; i <= a.length; i++) { f[i][0] = i; }
|
||||
|
||||
for (let i = 1; i <= a.length; i++) {
|
||||
for (let j = 1; j <= b.length; j++) {
|
||||
if (a.charAt(i - 1) === b.charAt(j - 1)) {
|
||||
f[i][j] = f[i-1][j-1]
|
||||
} else {
|
||||
f[i][j] = Math.min(f[i-1][j-1], Math.min(f[i][j-1], f[i-1][j])) + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return f[a.length][b.length]
|
||||
}
|
||||
|
||||
export const getAllFiles = (dir, files = []) => {
|
||||
if (!exists(dir)) { return [] }
|
||||
const file = Gio.File.new_for_path(dir);
|
||||
const enumerator = file.enumerate_children('standard::name,standard::type',
|
||||
Gio.FileQueryInfoFlags.NONE, null);
|
||||
|
||||
for (const info of enumerator) {
|
||||
if (info.get_file_type() === Gio.FileType.DIRECTORY) {
|
||||
files.push(getAllFiles(`${dir}/${info.get_name()}`))
|
||||
} else {
|
||||
files.push(`${dir}/${info.get_name()}`)
|
||||
}
|
||||
}
|
||||
|
||||
return files.flat(1);
|
||||
}
|
||||
|
||||
export const searchIcons = (appClass, files) => {
|
||||
appClass = appClass.toLowerCase()
|
||||
|
||||
if (!files.length) { return "" }
|
||||
|
||||
let appro = 0x3f3f3f3f
|
||||
let path = ""
|
||||
|
||||
for (const item of files) {
|
||||
let score = levenshteinDistance(item.split("/").pop().toLowerCase().split(".")[0], appClass)
|
||||
|
||||
if (score < appro) {
|
||||
appro = score
|
||||
path = item
|
||||
}
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
12
homes/me/ags-end4/modules/dock/main.js
Normal file
12
homes/me/ags-end4/modules/dock/main.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import Dock from './dock.js';
|
||||
|
||||
export default (monitor = 0) => Widget.Window({
|
||||
monitor,
|
||||
name: `dock${monitor}`,
|
||||
layer: userOptions.dock.layer,
|
||||
anchor: ['bottom'],
|
||||
exclusivity: 'normal',
|
||||
visible: true,
|
||||
child: Dock(monitor),
|
||||
});
|
||||
264
homes/me/ags-end4/modules/indicators/colorscheme.js
Normal file
264
homes/me/ags-end4/modules/indicators/colorscheme.js
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
const { Gio, GLib } = imports.gi;
|
||||
import Variable from 'resource:///com/github/Aylur/ags/variable.js';
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import { ConfigToggle, ConfigMulipleSelection } from '../.commonwidgets/configwidgets.js';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
const { execAsync } = Utils;
|
||||
import { setupCursorHover } from '../.widgetutils/cursorhover.js';
|
||||
import { showColorScheme } from '../../variables.js';
|
||||
import { MaterialIcon } from '../.commonwidgets/materialicon.js';
|
||||
import { darkMode } from '../.miscutils/system.js';
|
||||
|
||||
const ColorBox = ({
|
||||
name = 'Color',
|
||||
...rest
|
||||
}) => Widget.Box({
|
||||
...rest,
|
||||
homogeneous: true,
|
||||
children: [
|
||||
Widget.Label({
|
||||
label: `${name}`,
|
||||
})
|
||||
]
|
||||
})
|
||||
|
||||
const ColorSchemeSettingsRevealer = () => {
|
||||
const headerButtonIcon = MaterialIcon('expand_more', 'norm');
|
||||
const header = Widget.Button({
|
||||
className: 'osd-settings-btn-arrow',
|
||||
onClicked: () => {
|
||||
content.revealChild = !content.revealChild;
|
||||
headerButtonIcon.label = content.revealChild ? 'expand_less' : 'expand_more';
|
||||
},
|
||||
setup: setupCursorHover,
|
||||
hpack: 'end',
|
||||
child: headerButtonIcon,
|
||||
});
|
||||
const content = Widget.Revealer({
|
||||
revealChild: false,
|
||||
transition: 'slide_down',
|
||||
transitionDuration: 200,
|
||||
child: ColorSchemeSettings(),
|
||||
setup: (self) => self.hook(isHoveredColorschemeSettings, (revealer) => {
|
||||
if (isHoveredColorschemeSettings.value == false) {
|
||||
setTimeout(() => {
|
||||
if (isHoveredColorschemeSettings.value == false)
|
||||
revealer.revealChild = false;
|
||||
headerButtonIcon.label = 'expand_more';
|
||||
}, 1500);
|
||||
}
|
||||
}),
|
||||
});
|
||||
return Widget.EventBox({
|
||||
onHover: (self) => {
|
||||
isHoveredColorschemeSettings.setValue(true);
|
||||
},
|
||||
onHoverLost: (self) => {
|
||||
isHoveredColorschemeSettings.setValue(false);
|
||||
},
|
||||
child: Widget.Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
header,
|
||||
content,
|
||||
]
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
function calculateSchemeInitIndex(optionsArr, searchValue = 'vibrant') {
|
||||
if (searchValue == '')
|
||||
searchValue = 'vibrant';
|
||||
const flatArray = optionsArr.flatMap(subArray => subArray);
|
||||
const result = flatArray.findIndex(element => element.value === searchValue);
|
||||
const rowIndex = Math.floor(result / optionsArr[0].length);
|
||||
const columnIndex = result % optionsArr[0].length;
|
||||
return [rowIndex, columnIndex];
|
||||
}
|
||||
|
||||
const schemeOptionsArr = [
|
||||
[
|
||||
{ name: getString('Tonal Spot'), value: 'tonalspot' },
|
||||
{ name: getString('Fruit Salad'), value: 'fruitsalad' },
|
||||
{ name: getString('Fidelity'), value: 'fidelity' },
|
||||
{ name: getString('Rainbow'), value: 'rainbow' },
|
||||
],
|
||||
[
|
||||
{ name: getString('Neutral'), value: 'neutral' },
|
||||
{ name: getString('Monochrome'), value: 'monochrome' },
|
||||
{ name: getString('Expressive'), value: 'expressive' },
|
||||
{ name: getString('Vibrant'), value: 'vibrant' },
|
||||
],
|
||||
[
|
||||
{ name: getString('Vibrant+'), value: 'morevibrant' },
|
||||
],
|
||||
//[
|
||||
// { name: getString('Content'), value: 'content' },
|
||||
//]
|
||||
];
|
||||
|
||||
const LIGHTDARK_FILE_LOCATION = `${GLib.get_user_state_dir()}/ags/user/colormode.txt`;
|
||||
const initTransparency = Utils.exec(`bash -c "sed -n \'2p\' ${LIGHTDARK_FILE_LOCATION}"`);
|
||||
const initTransparencyVal = (initTransparency == "transparent") ? 1 : 0;
|
||||
const initScheme = Utils.exec(`bash -c "sed -n \'3p\' ${LIGHTDARK_FILE_LOCATION}"`);
|
||||
const initSchemeIndex = calculateSchemeInitIndex(schemeOptionsArr, initScheme);
|
||||
|
||||
const ColorSchemeSettings = () => Widget.Box({
|
||||
className: 'osd-colorscheme-settings spacing-v-5 margin-20',
|
||||
vertical: true,
|
||||
vpack: 'center',
|
||||
children: [
|
||||
Widget.Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
Widget.Label({
|
||||
xalign: 0,
|
||||
className: 'txt-norm titlefont txt',
|
||||
label: getString('Options'),
|
||||
hpack: 'center',
|
||||
}),
|
||||
//////////////////
|
||||
ConfigToggle({
|
||||
icon: 'dark_mode',
|
||||
name: getString('Dark Mode'),
|
||||
desc: getString('Ya should go to sleep!'),
|
||||
initValue: darkMode.value,
|
||||
onChange: (_, newValue) => {
|
||||
darkMode.value = !!newValue;
|
||||
},
|
||||
extraSetup: (self) => self.hook(darkMode, (self) => {
|
||||
self.enabled.value = darkMode.value;
|
||||
}),
|
||||
}),
|
||||
ConfigToggle({
|
||||
icon: 'border_clear',
|
||||
name: getString('Transparency'),
|
||||
desc: getString('Make shell elements transparent'),
|
||||
initValue: initTransparencyVal,
|
||||
onChange: (self, newValue) => {
|
||||
let transparency = newValue == 0 ? "opaque" : "transparent";
|
||||
execAsync([`bash`, `-c`, `mkdir -p ${GLib.get_user_state_dir()}/ags/user && sed -i "2s/.*/${transparency}/" ${GLib.get_user_state_dir()}/ags/user/colormode.txt`])
|
||||
.then(execAsync(['bash', '-c', `${App.configDir}/scripts/color_generation/switchcolor.sh`]))
|
||||
.catch(print);
|
||||
},
|
||||
}),
|
||||
Widget.Box({
|
||||
tooltipText: getString('Theme GTK apps using accent color\n(drawback: dark/light mode switching requires restart)'),
|
||||
className: 'txt spacing-h-5 configtoggle-box',
|
||||
children: [
|
||||
MaterialIcon('imagesearch_roller', 'norm'),
|
||||
Widget.Label({
|
||||
className: 'txt txt-small',
|
||||
label: getString('Use Gradience'),
|
||||
}),
|
||||
Widget.Box({ hexpand: true }),
|
||||
ConfigMulipleSelection({
|
||||
hpack: 'center',
|
||||
vpack: 'center',
|
||||
optionsArr: [
|
||||
[{ name: 'Off', value: 0 }, { name: 'On', value: 1 }],
|
||||
],
|
||||
initIndex: [-1, -1],
|
||||
onChange: (value, name) => {
|
||||
const ADWAITA_BLUE = "#3584E4";
|
||||
if (value) execAsync([`bash`, `-c`, `${App.configDir}/scripts/color_generation/switchcolor.sh - --yes-gradience`, `&`])
|
||||
.catch(print);
|
||||
else execAsync([`bash`, `-c`, `${App.configDir}/scripts/color_generation/switchcolor.sh "${ADWAITA_BLUE}" --no-gradience`, `&`])
|
||||
.catch(print);
|
||||
|
||||
},
|
||||
}),
|
||||
]
|
||||
}),
|
||||
]
|
||||
}),
|
||||
Widget.Box({
|
||||
vertical: true,
|
||||
className: 'spacing-v-5',
|
||||
children: [
|
||||
Widget.Label({
|
||||
xalign: 0,
|
||||
className: 'txt-norm titlefont txt margin-top-5',
|
||||
label: getString('Scheme styles'),
|
||||
hpack: 'center',
|
||||
}),
|
||||
//////////////////
|
||||
ConfigMulipleSelection({
|
||||
hpack: 'center',
|
||||
vpack: 'center',
|
||||
optionsArr: schemeOptionsArr,
|
||||
initIndex: initSchemeIndex,
|
||||
onChange: (value, name) => {
|
||||
execAsync([`bash`, `-c`, `mkdir -p ${GLib.get_user_state_dir()}/ags/user && sed -i "3s/.*/${value}/" ${GLib.get_user_state_dir()}/ags/user/colormode.txt`])
|
||||
.then(execAsync(['bash', '-c', `${App.configDir}/scripts/color_generation/switchcolor.sh`]))
|
||||
.catch(print);
|
||||
},
|
||||
}),
|
||||
]
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
const ColorschemeContent = () => Widget.Box({
|
||||
className: 'osd-colorscheme spacing-v-5',
|
||||
vertical: true,
|
||||
hpack: 'center',
|
||||
children: [
|
||||
Widget.Label({
|
||||
xalign: 0,
|
||||
className: 'txt-norm titlefont txt',
|
||||
label: getString('Color scheme'),
|
||||
hpack: 'center',
|
||||
}),
|
||||
Widget.Box({
|
||||
className: 'spacing-h-5',
|
||||
hpack: 'center',
|
||||
children: [
|
||||
ColorBox({ name: 'P', className: 'osd-color osd-color-primary' }),
|
||||
ColorBox({ name: 'S', className: 'osd-color osd-color-secondary' }),
|
||||
ColorBox({ name: 'T', className: 'osd-color osd-color-tertiary' }),
|
||||
ColorBox({ name: 'Sf', className: 'osd-color osd-color-surface' }),
|
||||
ColorBox({ name: 'Sf-i', className: 'osd-color osd-color-inverseSurface' }),
|
||||
ColorBox({ name: 'E', className: 'osd-color osd-color-error' }),
|
||||
]
|
||||
}),
|
||||
Widget.Box({
|
||||
className: 'spacing-h-5',
|
||||
hpack: 'center',
|
||||
children: [
|
||||
ColorBox({ name: 'P-c', className: 'osd-color osd-color-primaryContainer' }),
|
||||
ColorBox({ name: 'S-c', className: 'osd-color osd-color-secondaryContainer' }),
|
||||
ColorBox({ name: 'T-c', className: 'osd-color osd-color-tertiaryContainer' }),
|
||||
ColorBox({ name: 'Sf-c', className: 'osd-color osd-color-surfaceContainer' }),
|
||||
ColorBox({ name: 'Sf-v', className: 'osd-color osd-color-surfaceVariant' }),
|
||||
ColorBox({ name: 'E-c', className: 'osd-color osd-color-errorContainer' }),
|
||||
]
|
||||
}),
|
||||
ColorSchemeSettingsRevealer(),
|
||||
]
|
||||
});
|
||||
|
||||
const isHoveredColorschemeSettings = Variable(false);
|
||||
|
||||
export default () => Widget.Revealer({
|
||||
transition: 'slide_down',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
child: ColorschemeContent(),
|
||||
setup: (self) => {
|
||||
self
|
||||
.hook(showColorScheme, (revealer) => {
|
||||
if (showColorScheme.value == true)
|
||||
revealer.revealChild = true;
|
||||
else
|
||||
revealer.revealChild = isHoveredColorschemeSettings.value;
|
||||
})
|
||||
.hook(isHoveredColorschemeSettings, (revealer) => {
|
||||
if (isHoveredColorschemeSettings.value == false) {
|
||||
setTimeout(() => {
|
||||
if (isHoveredColorschemeSettings.value == false)
|
||||
revealer.revealChild = showColorScheme.value;
|
||||
}, 2000);
|
||||
}
|
||||
})
|
||||
},
|
||||
})
|
||||
124
homes/me/ags-end4/modules/indicators/indicatorvalues.js
Normal file
124
homes/me/ags-end4/modules/indicators/indicatorvalues.js
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
// This file is for brightness/volume indicators
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import Audio from 'resource:///com/github/Aylur/ags/service/audio.js';
|
||||
const { Box, Label, ProgressBar } = Widget;
|
||||
import { MarginRevealer } from '../.widgethacks/advancedrevealers.js';
|
||||
import Brightness from '../../services/brightness.js';
|
||||
import Indicator from '../../services/indicator.js';
|
||||
|
||||
const OsdValue = ({
|
||||
name, nameSetup = undefined, labelSetup, progressSetup,
|
||||
extraClassName = '', extraProgressClassName = '',
|
||||
...rest
|
||||
}) => {
|
||||
const valueName = Label({
|
||||
xalign: 0, yalign: 0, hexpand: true,
|
||||
className: 'osd-label',
|
||||
label: `${name}`,
|
||||
setup: nameSetup,
|
||||
});
|
||||
const valueNumber = Label({
|
||||
hexpand: false, className: 'osd-value-txt',
|
||||
setup: labelSetup,
|
||||
});
|
||||
return Box({ // Volume
|
||||
vertical: true,
|
||||
hexpand: true,
|
||||
className: `osd-bg osd-value ${extraClassName}`,
|
||||
attribute: {
|
||||
'disable': () => {
|
||||
valueNumber.label = '';
|
||||
}
|
||||
},
|
||||
children: [
|
||||
Box({
|
||||
vexpand: true,
|
||||
children: [
|
||||
valueName,
|
||||
valueNumber,
|
||||
]
|
||||
}),
|
||||
ProgressBar({
|
||||
className: `osd-progress ${extraProgressClassName}`,
|
||||
hexpand: true,
|
||||
vertical: false,
|
||||
setup: progressSetup,
|
||||
})
|
||||
],
|
||||
...rest,
|
||||
});
|
||||
}
|
||||
|
||||
export default (monitor = 0) => {
|
||||
const brightnessIndicator = OsdValue({
|
||||
name: 'Brightness',
|
||||
extraClassName: 'osd-brightness',
|
||||
extraProgressClassName: 'osd-brightness-progress',
|
||||
labelSetup: (self) => self.hook(Brightness[monitor], self => {
|
||||
self.label = `${Math.round(Brightness[monitor].screen_value * 100)}`;
|
||||
}, 'notify::screen-value'),
|
||||
progressSetup: (self) => self.hook(Brightness[monitor], (progress) => {
|
||||
const updateValue = Brightness[monitor].screen_value;
|
||||
if (updateValue !== progress.value) Indicator.popup(1);
|
||||
progress.value = updateValue;
|
||||
}, 'notify::screen-value'),
|
||||
});
|
||||
|
||||
const volumeIndicator = OsdValue({
|
||||
name: 'Volume',
|
||||
extraClassName: 'osd-volume',
|
||||
extraProgressClassName: 'osd-volume-progress',
|
||||
attribute: { headphones: undefined , device: undefined},
|
||||
nameSetup: (self) => Utils.timeout(1, () => {
|
||||
const updateAudioDevice = (self) => {
|
||||
const usingHeadphones = (Audio.speaker?.stream?.port)?.toLowerCase().includes('headphone');
|
||||
if (volumeIndicator.attribute.headphones === undefined ||
|
||||
volumeIndicator.attribute.headphones !== usingHeadphones) {
|
||||
volumeIndicator.attribute.headphones = usingHeadphones;
|
||||
self.label = usingHeadphones ? 'Headphones' : 'Speakers';
|
||||
// Indicator.popup(1);
|
||||
}
|
||||
}
|
||||
self.hook(Audio, updateAudioDevice);
|
||||
Utils.timeout(1000, updateAudioDevice);
|
||||
}),
|
||||
labelSetup: (self) => self.hook(Audio, (label) => {
|
||||
const newDevice = (Audio.speaker?.name);
|
||||
const updateValue = Math.round(Audio.speaker?.volume * 100);
|
||||
if (!isNaN(updateValue)) {
|
||||
if (newDevice === volumeIndicator.attribute.device && updateValue != label.label) {
|
||||
Indicator.popup(1);
|
||||
}
|
||||
}
|
||||
volumeIndicator.attribute.device = newDevice;
|
||||
label.label = `${updateValue}`;
|
||||
}),
|
||||
progressSetup: (self) => self.hook(Audio, (progress) => {
|
||||
const updateValue = Audio.speaker?.volume;
|
||||
if (!isNaN(updateValue)) {
|
||||
if (updateValue > 1) progress.value = 1;
|
||||
else progress.value = updateValue;
|
||||
}
|
||||
}),
|
||||
});
|
||||
return MarginRevealer({
|
||||
transition: 'slide_down',
|
||||
showClass: 'osd-show',
|
||||
hideClass: 'osd-hide',
|
||||
extraSetup: (self) => self
|
||||
.hook(Indicator, (revealer, value) => {
|
||||
if (value > -1) revealer.attribute.show();
|
||||
else revealer.attribute.hide();
|
||||
}, 'popup')
|
||||
,
|
||||
child: Box({
|
||||
hpack: 'center',
|
||||
vertical: false,
|
||||
className: 'spacing-h--10',
|
||||
children: [
|
||||
brightnessIndicator,
|
||||
volumeIndicator,
|
||||
]
|
||||
})
|
||||
});
|
||||
}
|
||||
32
homes/me/ags-end4/modules/indicators/main.js
Normal file
32
homes/me/ags-end4/modules/indicators/main.js
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import Indicator from '../../services/indicator.js';
|
||||
import IndicatorValues from './indicatorvalues.js';
|
||||
import MusicControls from './musiccontrols.js';
|
||||
import ColorScheme from './colorscheme.js';
|
||||
import NotificationPopups from './notificationpopups.js';
|
||||
|
||||
export default (monitor = 0) => Widget.Window({
|
||||
name: `indicator${monitor}`,
|
||||
monitor,
|
||||
className: 'indicator',
|
||||
layer: 'overlay',
|
||||
// exclusivity: 'ignore',
|
||||
visible: true,
|
||||
anchor: ['top'],
|
||||
child: Widget.EventBox({
|
||||
onHover: () => { //make the widget hide when hovering
|
||||
Indicator.popup(-1);
|
||||
},
|
||||
child: Widget.Box({
|
||||
vertical: true,
|
||||
className: 'osd-window',
|
||||
css: 'min-height: 2px;',
|
||||
children: [
|
||||
IndicatorValues(monitor),
|
||||
MusicControls(),
|
||||
NotificationPopups(),
|
||||
ColorScheme(),
|
||||
]
|
||||
})
|
||||
}),
|
||||
});
|
||||
408
homes/me/ags-end4/modules/indicators/musiccontrols.js
Normal file
408
homes/me/ags-end4/modules/indicators/musiccontrols.js
Normal file
|
|
@ -0,0 +1,408 @@
|
|||
const { GLib } = imports.gi;
|
||||
import App from 'resource:///com/github/Aylur/ags/app.js';
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
import Mpris from 'resource:///com/github/Aylur/ags/service/mpris.js';
|
||||
const { exec, execAsync } = Utils;
|
||||
const { Box, EventBox, Icon, Scrollable, Label, Button, Revealer } = Widget;
|
||||
|
||||
import { fileExists } from '../.miscutils/files.js';
|
||||
import { AnimatedCircProg } from "../.commonwidgets/cairo_circularprogress.js";
|
||||
import { showMusicControls } from '../../variables.js';
|
||||
import { darkMode, hasPlasmaIntegration } from '../.miscutils/system.js';
|
||||
|
||||
const COMPILED_STYLE_DIR = `${GLib.get_user_cache_dir()}/ags/user/generated`
|
||||
const LIGHTDARK_FILE_LOCATION = `${GLib.get_user_state_dir()}/ags/user/colormode.txt`;
|
||||
const colorMode = Utils.exec(`bash -c "sed -n \'1p\' '${LIGHTDARK_FILE_LOCATION}'"`);
|
||||
const lightDark = (colorMode == "light") ? '-l' : '';
|
||||
const COVER_COLORSCHEME_SUFFIX = '_colorscheme.css';
|
||||
var lastCoverPath = '';
|
||||
|
||||
function isRealPlayer(player) {
|
||||
return (
|
||||
// Remove unecessary native buses from browsers if there's plasma integration
|
||||
!(hasPlasmaIntegration && player.busName.startsWith('org.mpris.MediaPlayer2.firefox')) &&
|
||||
!(hasPlasmaIntegration && player.busName.startsWith('org.mpris.MediaPlayer2.chromium')) &&
|
||||
// playerctld just copies other buses and we don't need duplicates
|
||||
!player.busName.startsWith('org.mpris.MediaPlayer2.playerctld') &&
|
||||
// Non-instance mpd bus
|
||||
!(player.busName.endsWith('.mpd') && !player.busName.endsWith('MediaPlayer2.mpd'))
|
||||
);
|
||||
}
|
||||
|
||||
export const getPlayer = (name = userOptions.music.preferredPlayer) => Mpris.getPlayer(name) || Mpris.players[0] || null;
|
||||
function lengthStr(length) {
|
||||
const min = Math.floor(length / 60);
|
||||
const sec = Math.floor(length % 60);
|
||||
const sec0 = sec < 10 ? '0' : '';
|
||||
return `${min}:${sec0}${sec}`;
|
||||
}
|
||||
|
||||
function detectMediaSource(link) {
|
||||
if (link.startsWith("file://")) {
|
||||
if (link.includes('firefox-mpris'))
|
||||
return ' Firefox'
|
||||
return " File";
|
||||
}
|
||||
let url = link.replace(/(^\w+:|^)\/\//, '');
|
||||
let domain = url.match(/(?:[a-z]+\.)?([a-z]+\.[a-z]+)/i)[1];
|
||||
if (domain == 'ytimg.com') return ' Youtube';
|
||||
if (domain == 'discordapp.net') return ' Discord';
|
||||
if (domain == 'sndcdn.com') return ' SoundCloud';
|
||||
return domain;
|
||||
}
|
||||
|
||||
const DEFAULT_MUSIC_FONT = 'Gabarito, sans-serif';
|
||||
function getTrackfont(player) {
|
||||
const title = player.trackTitle;
|
||||
const artists = player.trackArtists.join(' ');
|
||||
if (artists.includes('TANO*C') || artists.includes('USAO') || artists.includes('Kobaryo'))
|
||||
return 'Chakra Petch'; // Rigid square replacement
|
||||
if (title.includes('東方'))
|
||||
return 'Crimson Text, serif'; // Serif for Touhou stuff
|
||||
return DEFAULT_MUSIC_FONT;
|
||||
}
|
||||
function trimTrackTitle(title) {
|
||||
if (!title) return '';
|
||||
const cleanPatterns = [
|
||||
/【[^】]*】/, // Touhou n weeb stuff
|
||||
" [FREE DOWNLOAD]", // F-777
|
||||
];
|
||||
cleanPatterns.forEach((expr) => title = title.replace(expr, ''));
|
||||
return title;
|
||||
}
|
||||
|
||||
const TrackProgress = ({ player, ...rest }) => {
|
||||
const _updateProgress = (circprog) => {
|
||||
// const player = Mpris.getPlayer();
|
||||
if (!player) return;
|
||||
// Set circular progress (see definition of AnimatedCircProg for explanation)
|
||||
circprog.css = `font-size: ${Math.max(player.position / player.length * 100, 0)}px;`
|
||||
}
|
||||
return AnimatedCircProg({
|
||||
...rest,
|
||||
className: 'osd-music-circprog',
|
||||
vpack: 'center',
|
||||
extraSetup: (self) => self
|
||||
.hook(Mpris, _updateProgress)
|
||||
.poll(3000, _updateProgress)
|
||||
,
|
||||
})
|
||||
}
|
||||
|
||||
const TrackTitle = ({ player, ...rest }) => Label({
|
||||
...rest,
|
||||
label: 'No music playing',
|
||||
xalign: 0,
|
||||
truncate: 'end',
|
||||
// wrap: true,
|
||||
className: 'osd-music-title',
|
||||
setup: (self) => self.hook(player, (self) => {
|
||||
// Player name
|
||||
self.label = player.trackTitle.length > 0 ? trimTrackTitle(player.trackTitle) : 'No media';
|
||||
// Font based on track/artist
|
||||
const fontForThisTrack = getTrackfont(player);
|
||||
self.css = `font-family: ${fontForThisTrack}, ${DEFAULT_MUSIC_FONT};`;
|
||||
}, 'notify::track-title'),
|
||||
});
|
||||
|
||||
const TrackArtists = ({ player, ...rest }) => Label({
|
||||
...rest,
|
||||
xalign: 0,
|
||||
className: 'osd-music-artists',
|
||||
truncate: 'end',
|
||||
setup: (self) => self.hook(player, (self) => {
|
||||
self.label = player.trackArtists.length > 0 ? player.trackArtists.join(', ') : '';
|
||||
}, 'notify::track-artists'),
|
||||
})
|
||||
|
||||
const CoverArt = ({ player, ...rest }) => {
|
||||
const fallbackCoverArt = Box({ // Fallback
|
||||
className: 'osd-music-cover-fallback',
|
||||
homogeneous: true,
|
||||
children: [Label({
|
||||
className: 'icon-material txt-gigantic txt-thin',
|
||||
label: 'music_note',
|
||||
})]
|
||||
});
|
||||
// const coverArtDrawingArea = Widget.DrawingArea({ className: 'osd-music-cover-art' });
|
||||
// const coverArtDrawingAreaStyleContext = coverArtDrawingArea.get_style_context();
|
||||
const realCoverArt = Box({
|
||||
className: 'osd-music-cover-art',
|
||||
homogeneous: true,
|
||||
// children: [coverArtDrawingArea],
|
||||
attribute: {
|
||||
'pixbuf': null,
|
||||
// 'showImage': (self, imagePath) => {
|
||||
// const borderRadius = coverArtDrawingAreaStyleContext.get_property('border-radius', Gtk.StateFlags.NORMAL);
|
||||
// const frameHeight = coverArtDrawingAreaStyleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
|
||||
// const frameWidth = coverArtDrawingAreaStyleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
|
||||
// let imageHeight = frameHeight;
|
||||
// let imageWidth = frameWidth;
|
||||
// // Get image dimensions
|
||||
// execAsync(['identify', '-format', '{"w":%w,"h":%h}', imagePath])
|
||||
// .then((output) => {
|
||||
// const imageDimensions = JSON.parse(output);
|
||||
// const imageAspectRatio = imageDimensions.w / imageDimensions.h;
|
||||
// const displayedAspectRatio = imageWidth / imageHeight;
|
||||
// if (imageAspectRatio >= displayedAspectRatio) {
|
||||
// imageWidth = imageHeight * imageAspectRatio;
|
||||
// } else {
|
||||
// imageHeight = imageWidth / imageAspectRatio;
|
||||
// }
|
||||
// // Real stuff
|
||||
// // TODO: fix memory leak(?)
|
||||
// // if (self.attribute.pixbuf) {
|
||||
// // self.attribute.pixbuf.unref();
|
||||
// // self.attribute.pixbuf = null;
|
||||
// // }
|
||||
// self.attribute.pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(imagePath, imageWidth, imageHeight);
|
||||
|
||||
// coverArtDrawingArea.set_size_request(frameWidth, frameHeight);
|
||||
// coverArtDrawingArea.connect("draw", (widget, cr) => {
|
||||
// // Clip a rounded rectangle area
|
||||
// cr.arc(borderRadius, borderRadius, borderRadius, Math.PI, 1.5 * Math.PI);
|
||||
// cr.arc(frameWidth - borderRadius, borderRadius, borderRadius, 1.5 * Math.PI, 2 * Math.PI);
|
||||
// cr.arc(frameWidth - borderRadius, frameHeight - borderRadius, borderRadius, 0, 0.5 * Math.PI);
|
||||
// cr.arc(borderRadius, frameHeight - borderRadius, borderRadius, 0.5 * Math.PI, Math.PI);
|
||||
// cr.closePath();
|
||||
// cr.clip();
|
||||
// // Paint image as bg, centered
|
||||
// Gdk.cairo_set_source_pixbuf(cr, self.attribute.pixbuf,
|
||||
// frameWidth / 2 - imageWidth / 2,
|
||||
// frameHeight / 2 - imageHeight / 2
|
||||
// );
|
||||
// cr.paint();
|
||||
// });
|
||||
// }).catch(print)
|
||||
// },
|
||||
'updateCover': (self) => {
|
||||
// const player = Mpris.getPlayer(); // Maybe no need to re-get player.. can't remember why I had this
|
||||
// Player closed
|
||||
// Note that cover path still remains, so we're checking title
|
||||
if (!player || player.trackTitle == "" || !player.coverPath) {
|
||||
self.css = `background-image: none;`; // CSS image
|
||||
App.applyCss(`${COMPILED_STYLE_DIR}/style.css`);
|
||||
return;
|
||||
}
|
||||
|
||||
const coverPath = player.coverPath;
|
||||
const stylePath = `${player.coverPath}${darkMode.value ? '' : '-l'}${COVER_COLORSCHEME_SUFFIX}`;
|
||||
if (player.coverPath == lastCoverPath) { // Since 'notify::cover-path' emits on cover download complete
|
||||
Utils.timeout(200, () => {
|
||||
// self.attribute.showImage(self, coverPath);
|
||||
self.css = `background-image: url('${coverPath}');`; // CSS image
|
||||
});
|
||||
}
|
||||
lastCoverPath = player.coverPath;
|
||||
|
||||
// If a colorscheme has already been generated, skip generation
|
||||
if (fileExists(stylePath)) {
|
||||
// self.attribute.showImage(self, coverPath)
|
||||
self.css = `background-image: url('${coverPath}');`; // CSS image
|
||||
App.applyCss(stylePath);
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate colors
|
||||
execAsync(['bash', '-c',
|
||||
`${App.configDir}/scripts/color_generation/generate_colors_material.py --path '${coverPath}' --mode ${darkMode.value ? 'dark' : 'light'} > ${GLib.get_user_state_dir()}/ags/scss/_musicmaterial.scss`])
|
||||
.then(() => {
|
||||
exec(`wal -i "${player.coverPath}" -n -t -s -e -q ${darkMode.value ? '' : '-l'}`)
|
||||
exec(`cp ${GLib.get_user_cache_dir()}/wal/colors.scss ${GLib.get_user_state_dir()}/ags/scss/_musicwal.scss`);
|
||||
exec(`sass -I "${GLib.get_user_state_dir()}/ags/scss" -I "${App.configDir}/scss/fallback" "${App.configDir}/scss/_music.scss" "${stylePath}"`);
|
||||
Utils.timeout(200, () => {
|
||||
// self.attribute.showImage(self, coverPath)
|
||||
self.css = `background-image: url('${coverPath}');`; // CSS image
|
||||
});
|
||||
App.applyCss(`${stylePath}`);
|
||||
})
|
||||
.catch(print);
|
||||
},
|
||||
},
|
||||
setup: (self) => self
|
||||
.hook(player, (self) => {
|
||||
self.attribute.updateCover(self);
|
||||
}, 'notify::cover-path')
|
||||
,
|
||||
});
|
||||
return Box({
|
||||
...rest,
|
||||
className: 'osd-music-cover',
|
||||
children: [
|
||||
Widget.Overlay({
|
||||
child: fallbackCoverArt,
|
||||
overlays: [realCoverArt],
|
||||
})
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
const TrackControls = ({ player, ...rest }) => Widget.Revealer({
|
||||
revealChild: false,
|
||||
transition: 'slide_right',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
child: Widget.Box({
|
||||
...rest,
|
||||
vpack: 'center',
|
||||
className: 'osd-music-controls spacing-h-3',
|
||||
children: [
|
||||
Button({
|
||||
className: 'osd-music-controlbtn',
|
||||
onClicked: () => player.previous(),
|
||||
child: Label({
|
||||
className: 'icon-material osd-music-controlbtn-txt',
|
||||
label: 'skip_previous',
|
||||
})
|
||||
}),
|
||||
Button({
|
||||
className: 'osd-music-controlbtn',
|
||||
onClicked: () => player.next(),
|
||||
child: Label({
|
||||
className: 'icon-material osd-music-controlbtn-txt',
|
||||
label: 'skip_next',
|
||||
})
|
||||
}),
|
||||
],
|
||||
}),
|
||||
setup: (self) => self.hook(Mpris, (self) => {
|
||||
// const player = Mpris.getPlayer();
|
||||
if (!player)
|
||||
self.revealChild = false;
|
||||
else
|
||||
self.revealChild = true;
|
||||
}, 'notify::play-back-status'),
|
||||
});
|
||||
|
||||
const TrackSource = ({ player, ...rest }) => Widget.Revealer({
|
||||
revealChild: false,
|
||||
transition: 'slide_left',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
child: Widget.Box({
|
||||
...rest,
|
||||
className: 'osd-music-pill spacing-h-5',
|
||||
homogeneous: true,
|
||||
children: [
|
||||
Label({
|
||||
hpack: 'fill',
|
||||
justification: 'center',
|
||||
className: 'icon-nerd',
|
||||
setup: (self) => self.hook(player, (self) => {
|
||||
self.label = detectMediaSource(player.trackCoverUrl);
|
||||
}, 'notify::cover-path'),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
setup: (self) => self.hook(Mpris, (self) => {
|
||||
const mpris = Mpris.getPlayer('');
|
||||
if (!mpris)
|
||||
self.revealChild = false;
|
||||
else
|
||||
self.revealChild = true;
|
||||
}),
|
||||
});
|
||||
|
||||
const TrackTime = ({ player, ...rest }) => {
|
||||
return Widget.Revealer({
|
||||
revealChild: false,
|
||||
transition: 'slide_left',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
child: Widget.Box({
|
||||
...rest,
|
||||
vpack: 'center',
|
||||
className: 'osd-music-pill spacing-h-5',
|
||||
children: [
|
||||
Label({
|
||||
setup: (self) => self.poll(1000, (self) => {
|
||||
// const player = Mpris.getPlayer();
|
||||
if (!player) return;
|
||||
self.label = lengthStr(player.position);
|
||||
}),
|
||||
}),
|
||||
Label({ label: '/' }),
|
||||
Label({
|
||||
setup: (self) => self.hook(Mpris, (self) => {
|
||||
// const player = Mpris.getPlayer();
|
||||
if (!player) return;
|
||||
self.label = lengthStr(player.length);
|
||||
}),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
setup: (self) => self.hook(Mpris, (self) => {
|
||||
if (!player) self.revealChild = false;
|
||||
else self.revealChild = true;
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
const PlayState = ({ player }) => {
|
||||
var position = 0;
|
||||
const trackCircProg = TrackProgress({ player: player });
|
||||
return Widget.Button({
|
||||
className: 'osd-music-playstate',
|
||||
child: Widget.Overlay({
|
||||
child: trackCircProg,
|
||||
overlays: [
|
||||
Widget.Button({
|
||||
className: 'osd-music-playstate-btn',
|
||||
onClicked: () => player.playPause(),
|
||||
child: Widget.Label({
|
||||
justification: 'center',
|
||||
hpack: 'fill',
|
||||
vpack: 'center',
|
||||
setup: (self) => self.hook(player, (label) => {
|
||||
label.label = `${player.playBackStatus == 'Playing' ? 'pause' : 'play_arrow'}`;
|
||||
}, 'notify::play-back-status'),
|
||||
}),
|
||||
}),
|
||||
],
|
||||
passThrough: true,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
const MusicControlsWidget = (player) => Box({
|
||||
className: 'osd-music spacing-h-20 test',
|
||||
children: [
|
||||
CoverArt({ player: player, vpack: 'center' }),
|
||||
Box({
|
||||
vertical: true,
|
||||
className: 'spacing-v-5 osd-music-info',
|
||||
children: [
|
||||
Box({
|
||||
vertical: true,
|
||||
vpack: 'center',
|
||||
hexpand: true,
|
||||
children: [
|
||||
TrackTitle({ player: player }),
|
||||
TrackArtists({ player: player }),
|
||||
]
|
||||
}),
|
||||
Box({ vexpand: true }),
|
||||
Box({
|
||||
className: 'spacing-h-10',
|
||||
setup: (box) => {
|
||||
box.pack_start(TrackControls({ player: player }), false, false, 0);
|
||||
box.pack_end(PlayState({ player: player }), false, false, 0);
|
||||
if(hasPlasmaIntegration || player.busName.startsWith('org.mpris.MediaPlayer2.chromium')) box.pack_end(TrackTime({ player: player }), false, false, 0)
|
||||
// box.pack_end(TrackSource({ vpack: 'center', player: player }), false, false, 0);
|
||||
}
|
||||
})
|
||||
]
|
||||
})
|
||||
]
|
||||
})
|
||||
|
||||
export default () => Revealer({
|
||||
transition: 'slide_down',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
revealChild: false,
|
||||
child: Box({
|
||||
children: Mpris.bind("players")
|
||||
.as(players => players.map((player) => (isRealPlayer(player) ? MusicControlsWidget(player) : null)))
|
||||
}),
|
||||
setup: (self) => self.hook(showMusicControls, (revealer) => {
|
||||
revealer.revealChild = showMusicControls.value;
|
||||
}),
|
||||
})
|
||||
45
homes/me/ags-end4/modules/indicators/notificationpopups.js
Normal file
45
homes/me/ags-end4/modules/indicators/notificationpopups.js
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
// This file is for popup notifications
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import Notifications from 'resource:///com/github/Aylur/ags/service/notifications.js';
|
||||
const { Box } = Widget;
|
||||
import Notification from '../.commonwidgets/notification.js';
|
||||
|
||||
export default () => Box({
|
||||
vertical: true,
|
||||
hpack: 'center',
|
||||
className: 'osd-notifs spacing-v-5-revealer',
|
||||
attribute: {
|
||||
'map': new Map(),
|
||||
'dismiss': (box, id, force = false) => {
|
||||
if (!id || !box.attribute.map.has(id))
|
||||
return;
|
||||
const notifWidget = box.attribute.map.get(id);
|
||||
if (notifWidget == null || notifWidget.attribute.hovered && !force)
|
||||
return; // cuz already destroyed
|
||||
|
||||
notifWidget.revealChild = false;
|
||||
notifWidget.attribute.destroyWithAnims();
|
||||
box.attribute.map.delete(id);
|
||||
},
|
||||
'notify': (box, id) => {
|
||||
if (!id || Notifications.dnd) return;
|
||||
if (!Notifications.getNotification(id)) return;
|
||||
|
||||
box.attribute.map.delete(id);
|
||||
|
||||
const notif = Notifications.getNotification(id);
|
||||
const newNotif = Notification({
|
||||
notifObject: notif,
|
||||
isPopup: true,
|
||||
});
|
||||
box.attribute.map.set(id, newNotif);
|
||||
box.pack_end(box.attribute.map.get(id), false, false, 0);
|
||||
box.show_all();
|
||||
},
|
||||
},
|
||||
setup: (self) => self
|
||||
.hook(Notifications, (box, id) => box.attribute.notify(box, id), 'notified')
|
||||
.hook(Notifications, (box, id) => box.attribute.dismiss(box, id), 'dismissed')
|
||||
.hook(Notifications, (box, id) => box.attribute.dismiss(box, id, true), 'closed')
|
||||
,
|
||||
});
|
||||
|
|
@ -0,0 +1,218 @@
|
|||
// We're going to use ydotool
|
||||
// See /usr/include/linux/input-event-codes.h for keycodes
|
||||
|
||||
export const DEFAULT_OSK_LAYOUT = "qwerty_full"
|
||||
export const oskLayouts = {
|
||||
qwerty_full: {
|
||||
name: "QWERTY - Full",
|
||||
name_short: "US",
|
||||
comment: "Like physical keyboard",
|
||||
// A key looks like this: { k: "a", ks: "A", t: "normal" } (key, key-shift, type)
|
||||
// key types are: normal, tab, caps, shift, control, fn (normal w/ half height), space, expand
|
||||
// keys: [
|
||||
// [{ k: "Esc", t: "fn" }, { k: "F1", t: "fn" }, { k: "F2", t: "fn" }, { k: "F3", t: "fn" }, { k: "F4", t: "fn" }, { k: "F5", t: "fn" }, { k: "F6", t: "fn" }, { k: "F7", t: "fn" }, { k: "F8", t: "fn" }, { k: "F9", t: "fn" }, { k: "F10", t: "fn" }, { k: "F11", t: "fn" }, { k: "F12", t: "fn" }, { k: "PrtSc", t: "fn" }, { k: "Del", t: "fn" }],
|
||||
// [{ k: "`", ks: "~", t: "normal" }, { k: "1", ks: "!", t: "normal" }, { k: "2", ks: "@", t: "normal" }, { k: "3", ks: "#", t: "normal" }, { k: "4", ks: "$", t: "normal" }, { k: "5", ks: "%", t: "normal" }, { k: "6", ks: "^", t: "normal" }, { k: "7", ks: "&", t: "normal" }, { k: "8", ks: "*", t: "normal" }, { k: "9", ks: "(", t: "normal" }, { k: "0", ks: ")", t: "normal" }, { k: "-", ks: "_", t: "normal" }, { k: "=", ks: "+", t: "normal" }, { k: "Backspace", t: "shift" }],
|
||||
// [{ k: "Tab", t: "tab" }, { k: "q", ks: "Q", t: "normal" }, { k: "w", ks: "W", t: "normal" }, { k: "e", ks: "E", t: "normal" }, { k: "r", ks: "R", t: "normal" }, { k: "t", ks: "T", t: "normal" }, { k: "y", ks: "Y", t: "normal" }, { k: "u", ks: "U", t: "normal" }, { k: "i", ks: "I", t: "normal" }, { k: "o", ks: "O", t: "normal" }, { k: "p", ks: "P", t: "normal" }, { k: "[", ks: "{", t: "normal" }, { k: "]", ks: "}", t: "normal" }, { k: "\\", ks: "|", t: "expand" }],
|
||||
// [{ k: "Caps", t: "caps" }, { k: "a", ks: "A", t: "normal" }, { k: "s", ks: "S", t: "normal" }, { k: "d", ks: "D", t: "normal" }, { k: "f", ks: "F", t: "normal" }, { k: "g", ks: "G", t: "normal" }, { k: "h", ks: "H", t: "normal" }, { k: "j", ks: "J", t: "normal" }, { k: "k", ks: "K", t: "normal" }, { k: "l", ks: "L", t: "normal" }, { k: ";", ks: ":", t: "normal" }, { k: "'", ks: '"', t: "normal" }, { k: "Enter", t: "expand" }],
|
||||
// [{ k: "Shift", t: "shift" }, { k: "z", ks: "Z", t: "normal" }, { k: "x", ks: "X", t: "normal" }, { k: "c", ks: "C", t: "normal" }, { k: "v", ks: "V", t: "normal" }, { k: "b", ks: "B", t: "normal" }, { k: "n", ks: "N", t: "normal" }, { k: "m", ks: "M", t: "normal" }, { k: ",", ks: "<", t: "normal" }, { k: ".", ks: ">", t: "normal" }, { k: "/", ks: "?", t: "normal" }, { k: "Shift", t: "expand" }],
|
||||
// [{ k: "Ctrl", t: "control" }, { k: "Fn", t: "normal" }, { k: "Win", t: "normal" }, { k: "Alt", t: "normal" }, { k: "Space", t: "space" }, { k: "Alt", t: "normal" }, { k: "Menu", t: "normal" }, { k: "Ctrl", t: "control" }]
|
||||
// ]
|
||||
// A normal key looks like this: {label: "a", labelShift: "A", shape: "normal", keycode: 30, type: "normal"}
|
||||
// A modkey looks like this: {label: "Ctrl", shape: "control", keycode: 29, type: "modkey"}
|
||||
// key types are: normal, tab, caps, shift, control, fn (normal w/ half height), space, expand
|
||||
keys: [
|
||||
[
|
||||
{ keytype: "normal", label: "Esc", shape: "fn", keycode: 1 },
|
||||
{ keytype: "normal", label: "F1", shape: "fn", keycode: 59 },
|
||||
{ keytype: "normal", label: "F2", shape: "fn", keycode: 60 },
|
||||
{ keytype: "normal", label: "F3", shape: "fn", keycode: 61 },
|
||||
{ keytype: "normal", label: "F4", shape: "fn", keycode: 62 },
|
||||
{ keytype: "normal", label: "F5", shape: "fn", keycode: 63 },
|
||||
{ keytype: "normal", label: "F6", shape: "fn", keycode: 64 },
|
||||
{ keytype: "normal", label: "F7", shape: "fn", keycode: 65 },
|
||||
{ keytype: "normal", label: "F8", shape: "fn", keycode: 66 },
|
||||
{ keytype: "normal", label: "F9", shape: "fn", keycode: 67 },
|
||||
{ keytype: "normal", label: "F10", shape: "fn", keycode: 68 },
|
||||
{ keytype: "normal", label: "F11", shape: "fn", keycode: 87 },
|
||||
{ keytype: "normal", label: "F12", shape: "fn", keycode: 88 },
|
||||
{ keytype: "normal", label: "PrtSc", shape: "fn", keycode: 99 },
|
||||
{ keytype: "normal", label: "Del", shape: "fn", keycode: 111 }
|
||||
],
|
||||
[
|
||||
{ keytype: "normal", label: "`", labelShift: "~", shape: "normal", keycode: 41 },
|
||||
{ keytype: "normal", label: "1", labelShift: "!", shape: "normal", keycode: 2 },
|
||||
{ keytype: "normal", label: "2", labelShift: "@", shape: "normal", keycode: 3 },
|
||||
{ keytype: "normal", label: "3", labelShift: "#", shape: "normal", keycode: 4 },
|
||||
{ keytype: "normal", label: "4", labelShift: "$", shape: "normal", keycode: 5 },
|
||||
{ keytype: "normal", label: "5", labelShift: "%", shape: "normal", keycode: 6 },
|
||||
{ keytype: "normal", label: "6", labelShift: "^", shape: "normal", keycode: 7 },
|
||||
{ keytype: "normal", label: "7", labelShift: "&", shape: "normal", keycode: 8 },
|
||||
{ keytype: "normal", label: "8", labelShift: "*", shape: "normal", keycode: 9 },
|
||||
{ keytype: "normal", label: "9", labelShift: "(", shape: "normal", keycode: 10 },
|
||||
{ keytype: "normal", label: "0", labelShift: ")", shape: "normal", keycode: 11 },
|
||||
{ keytype: "normal", label: "-", labelShift: "_", shape: "normal", keycode: 12 },
|
||||
{ keytype: "normal", label: "=", labelShift: "+", shape: "normal", keycode: 13 },
|
||||
{ keytype: "normal", label: "Backspace", shape: "expand", keycode: 14 }
|
||||
],
|
||||
[
|
||||
{ keytype: "normal", label: "Tab", shape: "tab", keycode: 15 },
|
||||
{ keytype: "normal", label: "q", labelShift: "Q", shape: "normal", keycode: 16 },
|
||||
{ keytype: "normal", label: "w", labelShift: "W", shape: "normal", keycode: 17 },
|
||||
{ keytype: "normal", label: "e", labelShift: "E", shape: "normal", keycode: 18 },
|
||||
{ keytype: "normal", label: "r", labelShift: "R", shape: "normal", keycode: 19 },
|
||||
{ keytype: "normal", label: "t", labelShift: "T", shape: "normal", keycode: 20 },
|
||||
{ keytype: "normal", label: "y", labelShift: "Y", shape: "normal", keycode: 21 },
|
||||
{ keytype: "normal", label: "u", labelShift: "U", shape: "normal", keycode: 22 },
|
||||
{ keytype: "normal", label: "i", labelShift: "I", shape: "normal", keycode: 23 },
|
||||
{ keytype: "normal", label: "o", labelShift: "O", shape: "normal", keycode: 24 },
|
||||
{ keytype: "normal", label: "p", labelShift: "P", shape: "normal", keycode: 25 },
|
||||
{ keytype: "normal", label: "[", labelShift: "{", shape: "normal", keycode: 26 },
|
||||
{ keytype: "normal", label: "]", labelShift: "}", shape: "normal", keycode: 27 },
|
||||
{ keytype: "normal", label: "\\", labelShift: "|", shape: "expand", keycode: 43 }
|
||||
],
|
||||
[
|
||||
//{ keytype: "normal", label: "Caps", shape: "caps", keycode: 58 }, // not needed as double-pressing shift does that
|
||||
{ keytype: "spacer", label: "", shape: "empty" },
|
||||
{ keytype: "spacer", label: "", shape: "empty" },
|
||||
{ keytype: "normal", label: "a", labelShift: "A", shape: "normal", keycode: 30 },
|
||||
{ keytype: "normal", label: "s", labelShift: "S", shape: "normal", keycode: 31 },
|
||||
{ keytype: "normal", label: "d", labelShift: "D", shape: "normal", keycode: 32 },
|
||||
{ keytype: "normal", label: "f", labelShift: "F", shape: "normal", keycode: 33 },
|
||||
{ keytype: "normal", label: "g", labelShift: "G", shape: "normal", keycode: 34 },
|
||||
{ keytype: "normal", label: "h", labelShift: "H", shape: "normal", keycode: 35 },
|
||||
{ keytype: "normal", label: "j", labelShift: "J", shape: "normal", keycode: 36 },
|
||||
{ keytype: "normal", label: "k", labelShift: "K", shape: "normal", keycode: 37 },
|
||||
{ keytype: "normal", label: "l", labelShift: "L", shape: "normal", keycode: 38 },
|
||||
{ keytype: "normal", label: ";", labelShift: ":", shape: "normal", keycode: 39 },
|
||||
{ keytype: "normal", label: "'", labelShift: '"', shape: "normal", keycode: 40 },
|
||||
{ keytype: "normal", label: "Enter", shape: "expand", keycode: 28 }
|
||||
],
|
||||
[
|
||||
{ keytype: "modkey", label: "Shift", labelShift: "Shift ⇧", labelCaps: "Locked ⇩", shape: "shift", keycode: 42 },
|
||||
{ keytype: "normal", label: "z", labelShift: "Z", shape: "normal", keycode: 44 },
|
||||
{ keytype: "normal", label: "x", labelShift: "X", shape: "normal", keycode: 45 },
|
||||
{ keytype: "normal", label: "c", labelShift: "C", shape: "normal", keycode: 46 },
|
||||
{ keytype: "normal", label: "v", labelShift: "V", shape: "normal", keycode: 47 },
|
||||
{ keytype: "normal", label: "b", labelShift: "B", shape: "normal", keycode: 48 },
|
||||
{ keytype: "normal", label: "n", labelShift: "N", shape: "normal", keycode: 49 },
|
||||
{ keytype: "normal", label: "m", labelShift: "M", shape: "normal", keycode: 50 },
|
||||
{ keytype: "normal", label: ",", labelShift: "<", shape: "normal", keycode: 51 },
|
||||
{ keytype: "normal", label: ".", labelShift: ">", shape: "normal", keycode: 52 },
|
||||
{ keytype: "normal", label: "/", labelShift: "?", shape: "normal", keycode: 53 },
|
||||
{ keytype: "modkey", label: "Shift", labelShift: "Shift ⇧", labelCaps: "Locked ⇩", shape: "expand", keycode: 54 } // optional
|
||||
],
|
||||
[
|
||||
{ keytype: "modkey", label: "Ctrl", shape: "control", keycode: 29 },
|
||||
// { label: "Super", shape: "normal", keycode: 125 }, // dangerous
|
||||
{ keytype: "modkey", label: "Alt", shape: "normal", keycode: 56 },
|
||||
{ keytype: "normal", label: "Space", shape: "space", keycode: 57 },
|
||||
{ keytype: "modkey", label: "Alt", shape: "normal", keycode: 100 },
|
||||
// { label: "Super", shape: "normal", keycode: 126 }, // dangerous
|
||||
{ keytype: "normal", label: "Menu", shape: "normal", keycode: 139 },
|
||||
{ keytype: "modkey", label: "Ctrl", shape: "control", keycode: 97 }
|
||||
]
|
||||
]
|
||||
},
|
||||
qwertz_full: {
|
||||
name: "QWERTZ - Full",
|
||||
name_short: "DE",
|
||||
comment: "Keyboard layout commonly used in German-speaking countries",
|
||||
keys: [
|
||||
[
|
||||
{ keytype: "normal", label: "Esc", shape: "fn", keycode: 1 },
|
||||
{ keytype: "normal", label: "F1", shape: "fn", keycode: 59 },
|
||||
{ keytype: "normal", label: "F2", shape: "fn", keycode: 60 },
|
||||
{ keytype: "normal", label: "F3", shape: "fn", keycode: 61 },
|
||||
{ keytype: "normal", label: "F4", shape: "fn", keycode: 62 },
|
||||
{ keytype: "normal", label: "F5", shape: "fn", keycode: 63 },
|
||||
{ keytype: "normal", label: "F6", shape: "fn", keycode: 64 },
|
||||
{ keytype: "normal", label: "F7", shape: "fn", keycode: 65 },
|
||||
{ keytype: "normal", label: "F8", shape: "fn", keycode: 66 },
|
||||
{ keytype: "normal", label: "F9", shape: "fn", keycode: 67 },
|
||||
{ keytype: "normal", label: "F10", shape: "fn", keycode: 68 },
|
||||
{ keytype: "normal", label: "F11", shape: "fn", keycode: 87 },
|
||||
{ keytype: "normal", label: "F12", shape: "fn", keycode: 88 },
|
||||
{ keytype: "normal", label: "Druck", shape: "fn", keycode: 99 },
|
||||
{ keytype: "normal", label: "Entf", shape: "fn", keycode: 111 }
|
||||
],
|
||||
[
|
||||
{ keytype: "normal", label: "^", labelShift: "°", labelAlt: "′", shape: "normal", keycode: 41 },
|
||||
{ keytype: "normal", label: "1", labelShift: "!", labelAlt: "¹", shape: "normal", keycode: 2 },
|
||||
{ keytype: "normal", label: "2", labelShift: "\"", labelAlt: "²", shape: "normal", keycode: 3 },
|
||||
{ keytype: "normal", label: "3", labelShift: "§", labelAlt: "³", shape: "normal", keycode: 4 },
|
||||
{ keytype: "normal", label: "4", labelShift: "$", labelAlt: "¼", shape: "normal", keycode: 5 },
|
||||
{ keytype: "normal", label: "5", labelShift: "%", labelAlt: "½", shape: "normal", keycode: 6 },
|
||||
{ keytype: "normal", label: "6", labelShift: "&", labelAlt: "¬", shape: "normal", keycode: 7 },
|
||||
{ keytype: "normal", label: "7", labelShift: "/", labelAlt: "{", shape: "normal", keycode: 8 },
|
||||
{ keytype: "normal", label: "8", labelShift: "(", labelAlt: "[", shape: "normal", keycode: 9 },
|
||||
{ keytype: "normal", label: "9", labelShift: ")", labelAlt: "]", shape: "normal", keycode: 10 },
|
||||
{ keytype: "normal", label: "0", labelShift: "=", labelAlt: "}", shape: "normal", keycode: 11 },
|
||||
{ keytype: "normal", label: "ß", labelShift: "?", labelAlt: "\\", shape: "normal", keycode: 12 },
|
||||
{ keytype: "normal", label: "´", labelShift: "`", labelAlt: "¸", shape: "normal", keycode: 13 },
|
||||
{ keytype: "normal", label: "⟵", shape: "expand", keycode: 14 }
|
||||
],
|
||||
[
|
||||
{ keytype: "normal", label: "Tab ⇆", shape: "tab", keycode: 15 },
|
||||
{ keytype: "normal", label: "q", labelShift: "Q", labelAlt: "@", shape: "normal", keycode: 16 },
|
||||
{ keytype: "normal", label: "w", labelShift: "W", labelAlt: "ſ", shape: "normal", keycode: 17 },
|
||||
{ keytype: "normal", label: "e", labelShift: "E", labelAlt: "€", shape: "normal", keycode: 18 },
|
||||
{ keytype: "normal", label: "r", labelShift: "R", labelAlt: "¶", shape: "normal", keycode: 19 },
|
||||
{ keytype: "normal", label: "t", labelShift: "T", labelAlt: "ŧ", shape: "normal", keycode: 20 },
|
||||
{ keytype: "normal", label: "z", labelShift: "Z", labelAlt: "←", shape: "normal", keycode: 21 },
|
||||
{ keytype: "normal", label: "u", labelShift: "U", labelAlt: "↓", shape: "normal", keycode: 22 },
|
||||
{ keytype: "normal", label: "i", labelShift: "I", labelAlt: "→", shape: "normal", keycode: 23 },
|
||||
{ keytype: "normal", label: "o", labelShift: "O", labelAlt: "ø", shape: "normal", keycode: 24 },
|
||||
{ keytype: "normal", label: "p", labelShift: "P", labelAlt: "þ", shape: "normal", keycode: 25 },
|
||||
{ keytype: "normal", label: "ü", labelShift: "Ü", labelAlt: "¨", shape: "normal", keycode: 26 },
|
||||
{ keytype: "normal", label: "+", labelShift: "*", labelAlt: "~", shape: "normal", keycode: 27 },
|
||||
{ keytype: "normal", label: "↵", shape: "expand", keycode: 28 }
|
||||
],
|
||||
[
|
||||
//{ keytype: "normal", label: "Umschalt ⇩", shape: "caps", keycode: 58 },
|
||||
{ keytype: "spacer", label: "", shape: "empty" },
|
||||
{ keytype: "spacer", label: "", shape: "empty" },
|
||||
{ keytype: "normal", label: "a", labelShift: "A", labelAlt: "æ", shape: "normal", keycode: 30 },
|
||||
{ keytype: "normal", label: "s", labelShift: "S", labelAlt: "ſ", shape: "normal", keycode: 31 },
|
||||
{ keytype: "normal", label: "d", labelShift: "D", labelAlt: "ð", shape: "normal", keycode: 32 },
|
||||
{ keytype: "normal", label: "f", labelShift: "F", labelAlt: "đ", shape: "normal", keycode: 33 },
|
||||
{ keytype: "normal", label: "g", labelShift: "G", labelAlt: "ŋ", shape: "normal", keycode: 34 },
|
||||
{ keytype: "normal", label: "h", labelShift: "H", labelAlt: "ħ", shape: "normal", keycode: 35 },
|
||||
{ keytype: "normal", label: "j", labelShift: "J", labelAlt: "", shape: "normal", keycode: 36 },
|
||||
{ keytype: "normal", label: "k", labelShift: "K", labelAlt: "ĸ", shape: "normal", keycode: 37 },
|
||||
{ keytype: "normal", label: "l", labelShift: "L", labelAlt: "ł", shape: "normal", keycode: 38 },
|
||||
{ keytype: "normal", label: "ö", labelShift: "Ö", labelAlt: "˝", shape: "normal", keycode: 39 },
|
||||
{ keytype: "normal", label: "ä", labelShift: 'Ä', labelAlt: "^", shape: "normal", keycode: 40 },
|
||||
{ keytype: "normal", label: "#", labelShift: '\'', labelAlt: "’", shape: "normal", keycode: 43 },
|
||||
{ keytype: "spacer", label: "", shape: "empty" },
|
||||
//{ keytype: "normal", label: "↵", shape: "expand", keycode: 28 }
|
||||
],
|
||||
[
|
||||
{ keytype: "modkey", label: "Shift", labelShift: "Shift ⇧", labelCaps: "Locked ⇩", shape: "shift", keycode: 42 },
|
||||
{ keytype: "normal", label: "<", labelShift: ">", labelAlt: "|", shape: "normal", keycode: 86 },
|
||||
{ keytype: "normal", label: "y", labelShift: "Y", labelAlt: "»", shape: "normal", keycode: 44 },
|
||||
{ keytype: "normal", label: "x", labelShift: "X", labelAlt: "«", shape: "normal", keycode: 45 },
|
||||
{ keytype: "normal", label: "c", labelShift: "C", labelAlt: "¢", shape: "normal", keycode: 46 },
|
||||
{ keytype: "normal", label: "v", labelShift: "V", labelAlt: "„", shape: "normal", keycode: 47 },
|
||||
{ keytype: "normal", label: "b", labelShift: "B", labelAlt: "“", shape: "normal", keycode: 48 },
|
||||
{ keytype: "normal", label: "n", labelShift: "N", labelAlt: "”", shape: "normal", keycode: 49 },
|
||||
{ keytype: "normal", label: "m", labelShift: "M", labelAlt: "µ", shape: "normal", keycode: 50 },
|
||||
{ keytype: "normal", label: ",", labelShift: ";", labelAlt: "·", shape: "normal", keycode: 51 },
|
||||
{ keytype: "normal", label: ".", labelShift: ":", labelAlt: "…", shape: "normal", keycode: 52 },
|
||||
{ keytype: "normal", label: "-", labelShift: "_", labelAlt: "–", shape: "normal", keycode: 53 },
|
||||
{ keytype: "modkey", label: "Shift", labelShift: "Shift ⇧", labelCaps: "Locked ⇩", shape: "expand", keycode: 54 }, // optional
|
||||
],
|
||||
[
|
||||
{ keytype: "modkey", label: "Strg", shape: "control", keycode: 29 },
|
||||
//{ keytype: "normal", label: "", shape: "normal", keycode: 125 }, // dangerous
|
||||
{ keytype: "modkey", label: "Alt", shape: "normal", keycode: 56 },
|
||||
{ keytype: "normal", label: "Leertaste", shape: "space", keycode: 57 },
|
||||
{ keytype: "modkey", label: "Alt Gr", shape: "normal", keycode: 100 },
|
||||
// { label: "Super", shape: "normal", keycode: 126 }, // dangerous
|
||||
//{ keytype: "normal", label: "Menu", shape: "normal", keycode: 139 }, // doesn't work?
|
||||
{ keytype: "modkey", label: "Strg", shape: "control", keycode: 97 },
|
||||
{ keytype: "normal", label: "⇦", shape: "normal", keycode: 105 },
|
||||
{ keytype: "normal", label: "⇨", shape: "normal", keycode: 106 },
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
11
homes/me/ags-end4/modules/onscreenkeyboard/main.js
Normal file
11
homes/me/ags-end4/modules/onscreenkeyboard/main.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import PopupWindow from '../.widgethacks/popupwindow.js';
|
||||
import OnScreenKeyboard from "./onscreenkeyboard.js";
|
||||
|
||||
export default (id) => PopupWindow({
|
||||
monitor: id,
|
||||
anchor: ['bottom'],
|
||||
name: `osk${id}`,
|
||||
showClassName: 'osk-show',
|
||||
hideClassName: 'osk-hide',
|
||||
child: OnScreenKeyboard({ id: id }),
|
||||
});
|
||||
267
homes/me/ags-end4/modules/onscreenkeyboard/onscreenkeyboard.js
Normal file
267
homes/me/ags-end4/modules/onscreenkeyboard/onscreenkeyboard.js
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
const { Gtk } = imports.gi;
|
||||
import App from 'resource:///com/github/Aylur/ags/app.js';
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
|
||||
const { Box, EventBox, Button, Revealer } = Widget;
|
||||
const { execAsync } = Utils;
|
||||
import { MaterialIcon } from '../.commonwidgets/materialicon.js';
|
||||
import { DEFAULT_OSK_LAYOUT, oskLayouts } from './data_keyboardlayouts.js';
|
||||
import { setupCursorHoverGrab } from '../.widgetutils/cursorhover.js';
|
||||
|
||||
const keyboardLayout = oskLayouts[userOptions.onScreenKeyboard.layout] ? userOptions.onScreenKeyboard.layout : DEFAULT_OSK_LAYOUT;
|
||||
const keyboardJson = oskLayouts[keyboardLayout];
|
||||
|
||||
async function startYdotoolIfNeeded() {
|
||||
const running = exec('pidof ydotool')
|
||||
if (!running) execAsync(['ydotoold']).catch(print);
|
||||
}
|
||||
|
||||
function releaseAllKeys() {
|
||||
const keycodes = Array.from(Array(249).keys());
|
||||
execAsync([`ydotool`, `key`, ...keycodes.map(keycode => `${keycode}:0`)])
|
||||
.then(console.log('[OSK] Released all keys'))
|
||||
.catch(print);
|
||||
}
|
||||
class ShiftMode {
|
||||
static Off = new ShiftMode('Off');
|
||||
static Normal = new ShiftMode('Normal');
|
||||
static Locked = new ShiftMode('Locked');
|
||||
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
}
|
||||
toString() {
|
||||
return `ShiftMode.${this.name}`;
|
||||
}
|
||||
}
|
||||
var modsPressed = false;
|
||||
|
||||
const TopDecor = () => Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
Box({
|
||||
hpack: 'center',
|
||||
className: 'osk-dragline',
|
||||
homogeneous: true,
|
||||
children: [EventBox({
|
||||
setup: setupCursorHoverGrab,
|
||||
})]
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
const KeyboardControlButton = (icon, text, runFunction) => Button({
|
||||
className: 'osk-control-button spacing-h-10',
|
||||
onClicked: () => runFunction(),
|
||||
child: Widget.Box({
|
||||
children: [
|
||||
MaterialIcon(icon, 'norm'),
|
||||
Widget.Label({
|
||||
label: `${text}`,
|
||||
}),
|
||||
]
|
||||
})
|
||||
})
|
||||
|
||||
const KeyboardControls = () => Box({
|
||||
vertical: true,
|
||||
className: 'spacing-v-5',
|
||||
children: [
|
||||
Button({
|
||||
className: 'osk-control-button txt-norm icon-material',
|
||||
onClicked: () => {
|
||||
releaseAllKeys();
|
||||
toggleWindowOnAllMonitors('osk');
|
||||
},
|
||||
label: 'keyboard_hide',
|
||||
}),
|
||||
Button({
|
||||
className: 'osk-control-button txt-norm',
|
||||
label: `${keyboardJson['name_short']}`,
|
||||
}),
|
||||
Button({
|
||||
className: 'osk-control-button txt-norm icon-material',
|
||||
onClicked: () => { // TODO: Proper clipboard widget, since fuzzel doesn't receive mouse inputs
|
||||
execAsync([`bash`, `-c`, "pkill fuzzel || cliphist list | fuzzel --match-mode fzf --dmenu | cliphist decode | wl-copy"]).catch(print);
|
||||
},
|
||||
label: 'assignment',
|
||||
}),
|
||||
]
|
||||
})
|
||||
|
||||
var shiftMode = ShiftMode.Off;
|
||||
var shiftButton;
|
||||
var rightShiftButton;
|
||||
var allButtons = [];
|
||||
const KeyboardItself = (kbJson) => {
|
||||
return Box({
|
||||
vertical: true,
|
||||
className: 'spacing-v-5',
|
||||
children: kbJson.keys.map(row => Box({
|
||||
vertical: false,
|
||||
className: 'spacing-h-5',
|
||||
children: row.map(key => {
|
||||
return Button({
|
||||
className: `osk-key osk-key-${key.shape}`,
|
||||
hexpand: ["space", "expand"].includes(key.shape),
|
||||
label: key.label,
|
||||
attribute:
|
||||
{ key: key },
|
||||
setup: (button) => {
|
||||
let pressed = false;
|
||||
allButtons = allButtons.concat(button);
|
||||
if (key.keytype == "normal") {
|
||||
button.connect('pressed', () => { // mouse down
|
||||
execAsync(`ydotool key ${key.keycode}:1`).catch(print);
|
||||
});
|
||||
button.connect('clicked', () => { // release
|
||||
execAsync(`ydotool key ${key.keycode}:0`).catch(print);
|
||||
|
||||
if (shiftMode == ShiftMode.Normal) {
|
||||
shiftMode = ShiftMode.Off;
|
||||
if (typeof shiftButton !== 'undefined') {
|
||||
execAsync(`ydotool key 42:0`).catch(print);
|
||||
shiftButton.toggleClassName('osk-key-active', false);
|
||||
}
|
||||
if (typeof rightShiftButton !== 'undefined') {
|
||||
execAsync(`ydotool key 54:0`).catch(print);
|
||||
rightShiftButton.toggleClassName('osk-key-active', false);
|
||||
}
|
||||
allButtons.forEach(button => {
|
||||
if (typeof button.attribute.key.labelShift !== 'undefined') button.label = button.attribute.key.label;
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (key.keytype == "modkey") {
|
||||
button.connect('pressed', () => { // release
|
||||
if (pressed) {
|
||||
execAsync(`ydotool key ${key.keycode}:0`).catch(print);
|
||||
button.toggleClassName('osk-key-active', false);
|
||||
pressed = false;
|
||||
if (key.keycode == 100) { // Alt Gr button
|
||||
allButtons.forEach(button => { if (typeof button.attribute.key.labelAlt !== 'undefined') button.label = button.attribute.key.label; });
|
||||
}
|
||||
}
|
||||
else {
|
||||
execAsync(`ydotool key ${key.keycode}:1`).catch(print);
|
||||
button.toggleClassName('osk-key-active', true);
|
||||
if (!(key.keycode == 42 || key.keycode == 54)) pressed = true;
|
||||
else switch (shiftMode.name) { // This toggles the shift button state
|
||||
case "Off": {
|
||||
shiftMode = ShiftMode.Normal;
|
||||
allButtons.forEach(button => { if (typeof button.attribute.key.labelShift !== 'undefined') button.label = button.attribute.key.labelShift; })
|
||||
if (typeof shiftButton !== 'undefined') {
|
||||
shiftButton.toggleClassName('osk-key-active', true);
|
||||
}
|
||||
if (typeof rightShiftButton !== 'undefined') {
|
||||
rightShiftButton.toggleClassName('osk-key-active', true);
|
||||
}
|
||||
} break;
|
||||
case "Normal": {
|
||||
shiftMode = ShiftMode.Locked;
|
||||
if (typeof shiftButton !== 'undefined') shiftButton.label = key.labelCaps;
|
||||
if (typeof rightShiftButton !== 'undefined') rightShiftButton.label = key.labelCaps;
|
||||
} break;
|
||||
case "Locked": {
|
||||
shiftMode = ShiftMode.Off;
|
||||
if (typeof shiftButton !== 'undefined') {
|
||||
shiftButton.label = key.label;
|
||||
shiftButton.toggleClassName('osk-key-active', false);
|
||||
}
|
||||
if (typeof rightShiftButton !== 'undefined') {
|
||||
rightShiftButton.label = key.label;
|
||||
rightShiftButton.toggleClassName('osk-key-active', false);
|
||||
}
|
||||
execAsync(`ydotool key ${key.keycode}:0`).catch(print);
|
||||
|
||||
allButtons.forEach(button => { if (typeof button.attribute.key.labelShift !== 'undefined') button.label = button.attribute.key.label; }
|
||||
)
|
||||
};
|
||||
}
|
||||
if (key.keycode == 100) { // Alt Gr button
|
||||
allButtons.forEach(button => { if (typeof button.attribute.key.labelAlt !== 'undefined') button.label = button.attribute.key.labelAlt; });
|
||||
}
|
||||
modsPressed = true;
|
||||
}
|
||||
});
|
||||
if (key.keycode == 42) shiftButton = button;
|
||||
else if (key.keycode == 54) rightShiftButton = button;
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
const KeyboardWindow = () => Box({
|
||||
vexpand: true,
|
||||
hexpand: true,
|
||||
vertical: true,
|
||||
className: 'osk-window spacing-v-5',
|
||||
children: [
|
||||
TopDecor(),
|
||||
Box({
|
||||
className: 'osk-body spacing-h-10',
|
||||
children: [
|
||||
KeyboardControls(),
|
||||
Widget.Box({ className: 'separator-line' }),
|
||||
KeyboardItself(keyboardJson),
|
||||
],
|
||||
})
|
||||
],
|
||||
setup: (self) => self.hook(App, (self, name, visible) => { // Update on open
|
||||
if (!name) return;
|
||||
if (name.startsWith('osk') && visible) {
|
||||
self.setCss(`margin-bottom: -0px;`);
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
export default ({ id }) => {
|
||||
const kbWindow = KeyboardWindow();
|
||||
const gestureEvBox = EventBox({ child: kbWindow })
|
||||
const gesture = Gtk.GestureDrag.new(gestureEvBox);
|
||||
gesture.connect('drag-begin', async () => {
|
||||
try {
|
||||
const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
|
||||
Hyprland.messageAsync('j/cursorpos').then((out) => {
|
||||
gesture.startY = JSON.parse(out).y;
|
||||
}).catch(print);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
});
|
||||
gesture.connect('drag-update', async () => {
|
||||
try {
|
||||
const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
|
||||
Hyprland.messageAsync('j/cursorpos').then((out) => {
|
||||
const currentY = JSON.parse(out).y;
|
||||
const offset = gesture.startY - currentY;
|
||||
|
||||
if (offset > 0) return;
|
||||
|
||||
kbWindow.setCss(`
|
||||
margin-bottom: ${offset}px;
|
||||
`);
|
||||
}).catch(print);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
});
|
||||
gesture.connect('drag-end', () => {
|
||||
var offset = gesture.get_offset()[2];
|
||||
if (offset > 50) {
|
||||
App.closeWindow(`osk${id}`);
|
||||
}
|
||||
else {
|
||||
kbWindow.setCss(`
|
||||
transition: margin-bottom 170ms cubic-bezier(0.05, 0.7, 0.1, 1);
|
||||
margin-bottom: 0px;
|
||||
`);
|
||||
}
|
||||
})
|
||||
return gestureEvBox;
|
||||
};
|
||||
28
homes/me/ags-end4/modules/overview/actions.js
Normal file
28
homes/me/ags-end4/modules/overview/actions.js
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
|
||||
|
||||
function moveClientToWorkspace(address, workspace) {
|
||||
Utils.execAsync(['bash', '-c', `hyprctl dispatch movetoworkspacesilent ${workspace},address:${address} &`]);
|
||||
}
|
||||
|
||||
export function dumpToWorkspace(from, to) {
|
||||
if (from == to) return;
|
||||
Hyprland.clients.forEach(client => {
|
||||
if (client.workspace.id == from) {
|
||||
moveClientToWorkspace(client.address, to);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function swapWorkspace(workspaceA, workspaceB) {
|
||||
if (workspaceA == workspaceB) return;
|
||||
const clientsA = [];
|
||||
const clientsB = [];
|
||||
Hyprland.clients.forEach(client => {
|
||||
if (client.workspace.id == workspaceA) clientsA.push(client.address);
|
||||
if (client.workspace.id == workspaceB) clientsB.push(client.address);
|
||||
});
|
||||
|
||||
clientsA.forEach((address) => moveClientToWorkspace(address, workspaceB));
|
||||
clientsB.forEach((address) => moveClientToWorkspace(address, workspaceA));
|
||||
}
|
||||
28
homes/me/ags-end4/modules/overview/main.js
Normal file
28
homes/me/ags-end4/modules/overview/main.js
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import { SearchAndWindows } from "./windowcontent.js";
|
||||
import PopupWindow from '../.widgethacks/popupwindow.js';
|
||||
import { clickCloseRegion } from '../.commonwidgets/clickcloseregion.js';
|
||||
|
||||
export default (id = '') => PopupWindow({
|
||||
name: `overview${id}`,
|
||||
// exclusivity: 'ignore',
|
||||
keymode: 'on-demand',
|
||||
visible: false,
|
||||
anchor: ['top', 'bottom', 'left', 'right'],
|
||||
layer: 'top',
|
||||
child: Widget.Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
clickCloseRegion({ name: 'overview', multimonitor: false, expand: false }),
|
||||
Widget.Box({
|
||||
children: [
|
||||
clickCloseRegion({ name: 'overview', multimonitor: false }),
|
||||
SearchAndWindows(),
|
||||
clickCloseRegion({ name: 'overview', multimonitor: false }),
|
||||
]
|
||||
}),
|
||||
clickCloseRegion({ name: 'overview', multimonitor: false }),
|
||||
]
|
||||
}),
|
||||
})
|
||||
|
||||
165
homes/me/ags-end4/modules/overview/miscfunctions.js
Normal file
165
homes/me/ags-end4/modules/overview/miscfunctions.js
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
const { Gio, GLib } = imports.gi;
|
||||
import App from 'resource:///com/github/Aylur/ags/app.js';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
const { execAsync, exec } = Utils;
|
||||
import Todo from "../../services/todo.js";
|
||||
import { darkMode } from '../.miscutils/system.js';
|
||||
|
||||
export function hasUnterminatedBackslash(inputString) {
|
||||
// Use a regular expression to match a trailing odd number of backslashes
|
||||
const regex = /\\+$/;
|
||||
return regex.test(inputString);
|
||||
}
|
||||
|
||||
export function launchCustomCommand(command) {
|
||||
const args = command.toLowerCase().split(' ');
|
||||
if (args[0] == '>raw') { // Mouse raw input
|
||||
Utils.execAsync('hyprctl -j getoption input:accel_profile')
|
||||
.then((output) => {
|
||||
const value = JSON.parse(output)["str"].trim();
|
||||
if (value != "[[EMPTY]]" && value != "") {
|
||||
execAsync(['bash', '-c', `hyprctl keyword input:accel_profile '[[EMPTY]]'`]).catch(print);
|
||||
}
|
||||
else {
|
||||
execAsync(['bash', '-c', `hyprctl keyword input:accel_profile flat`]).catch(print);
|
||||
}
|
||||
})
|
||||
}
|
||||
else if (args[0] == '>img') { // Change wallpaper
|
||||
execAsync([`bash`, `-c`, `${App.configDir}/scripts/color_generation/switchwall.sh`, `&`]).catch(print);
|
||||
}
|
||||
else if (args[0] == '>color') { // Generate colorscheme from color picker
|
||||
if (!args[1])
|
||||
execAsync([`bash`, `-c`, `${App.configDir}/scripts/color_generation/switchcolor.sh --pick`, `&`]).catch(print);
|
||||
else if (args[1][0] === '#')
|
||||
execAsync([`bash`, `-c`, `${App.configDir}/scripts/color_generation/switchcolor.sh "${args[1]}"`, `&`]).catch(print);
|
||||
}
|
||||
else if (args[0] == '>light') { // Light mode
|
||||
darkMode.value = false;
|
||||
}
|
||||
else if (args[0] == '>dark') { // Dark mode
|
||||
darkMode.value = true;
|
||||
}
|
||||
else if (args[0] == '>badapple') { // Black and white
|
||||
execAsync([`bash`, `-c`, `mkdir -p ${GLib.get_user_state_dir()}/ags/user && sed -i "3s/.*/monochrome/" ${GLib.get_user_state_dir()}/ags/user/colormode.txt`])
|
||||
.then(execAsync(['bash', '-c', `${App.configDir}/scripts/color_generation/switchcolor.sh`]))
|
||||
.catch(print);
|
||||
}
|
||||
else if (args[0] == '>adw' || args[0] == '>adwaita') {
|
||||
const ADWAITA_BLUE = "#3584E4";
|
||||
execAsync([`bash`, `-c`, `${App.configDir}/scripts/color_generation/switchcolor.sh "${ADWAITA_BLUE}" --no-gradience`, `&`])
|
||||
.catch(print);
|
||||
}
|
||||
else if (args[0] == '>grad' || args[0] == '>gradience') {
|
||||
execAsync([`bash`, `-c`, `${App.configDir}/scripts/color_generation/switchcolor.sh - --yes-gradience`, `&`])
|
||||
.catch(print);
|
||||
}
|
||||
else if (args[0] == '>nograd' || args[0] == '>nogradience') {
|
||||
execAsync([`bash`, `-c`, `${App.configDir}/scripts/color_generation/switchcolor.sh - --no-gradience`, `&`])
|
||||
.catch(print);
|
||||
}
|
||||
else if (args[0] == '>material') { // Use material colors
|
||||
execAsync([`bash`, `-c`, `mkdir -p ${GLib.get_user_state_dir()}/ags/user && echo "material" > ${GLib.get_user_state_dir()}/ags/user/colorbackend.txt`]).catch(print)
|
||||
.then(execAsync(['bash', '-c', `${App.configDir}/scripts/color_generation/switchwall.sh --noswitch`]).catch(print))
|
||||
.catch(print);
|
||||
}
|
||||
else if (args[0] == '>pywal') { // Use Pywal (ik it looks shit but I'm not removing)
|
||||
execAsync([`bash`, `-c`, `mkdir -p ${GLib.get_user_state_dir()}/ags/user && echo "pywal" > ${GLib.get_user_state_dir()}/ags/user/colorbackend.txt`]).catch(print)
|
||||
.then(execAsync(['bash', '-c', `${App.configDir}/scripts/color_generation/switchwall.sh --noswitch`]).catch(print))
|
||||
.catch(print);
|
||||
}
|
||||
else if (args[0] == '>todo') { // Todo
|
||||
Todo.add(args.slice(1).join(' '));
|
||||
}
|
||||
else if (args[0] == '>shutdown') { // Shut down
|
||||
execAsync([`bash`, `-c`, `systemctl poweroff || loginctl poweroff`]).catch(print);
|
||||
}
|
||||
else if (args[0] == '>reboot') { // Reboot
|
||||
execAsync([`bash`, `-c`, `systemctl reboot || loginctl reboot`]).catch(print);
|
||||
}
|
||||
else if (args[0] == '>sleep') { // Sleep
|
||||
execAsync([`bash`, `-c`, `systemctl suspend || loginctl suspend`]).catch(print);
|
||||
}
|
||||
else if (args[0] == '>logout') { // Log out
|
||||
execAsync([`bash`, `-c`, `pkill Hyprland || pkill sway`]).catch(print);
|
||||
}
|
||||
}
|
||||
|
||||
export function execAndClose(command, terminal) {
|
||||
App.closeWindow('overview');
|
||||
if (terminal) {
|
||||
execAsync([`bash`, `-c`, `${userOptions.apps.terminal} fish -C "${command}"`, `&`]).catch(print);
|
||||
}
|
||||
else
|
||||
execAsync(command).catch(print);
|
||||
}
|
||||
|
||||
export function couldBeMath(str) {
|
||||
const regex = /^[0-9.+*/-]/;
|
||||
return regex.test(str);
|
||||
}
|
||||
|
||||
export function expandTilde(path) {
|
||||
if (path.startsWith('~')) {
|
||||
return GLib.get_home_dir() + path.slice(1);
|
||||
} else {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
function getFileIcon(fileInfo) {
|
||||
let icon = fileInfo.get_icon();
|
||||
if (icon) {
|
||||
// Get the icon's name
|
||||
return icon.get_names()[0];
|
||||
} else {
|
||||
// Default icon for files
|
||||
return 'text-x-generic';
|
||||
}
|
||||
}
|
||||
|
||||
export function ls({ path = '~', silent = false }) {
|
||||
let contents = [];
|
||||
try {
|
||||
let expandedPath = expandTilde(path);
|
||||
if (expandedPath.endsWith('/'))
|
||||
expandedPath = expandedPath.slice(0, -1);
|
||||
let folder = Gio.File.new_for_path(expandedPath);
|
||||
|
||||
let enumerator = folder.enumerate_children('standard::*', Gio.FileQueryInfoFlags.NONE, null);
|
||||
let fileInfo;
|
||||
while ((fileInfo = enumerator.next_file(null)) !== null) {
|
||||
let fileName = fileInfo.get_display_name();
|
||||
let fileType = fileInfo.get_file_type();
|
||||
|
||||
let item = {
|
||||
parentPath: expandedPath,
|
||||
name: fileName,
|
||||
type: fileType === Gio.FileType.DIRECTORY ? 'folder' : 'file',
|
||||
icon: getFileIcon(fileInfo),
|
||||
};
|
||||
|
||||
// Add file extension for files
|
||||
if (fileType === Gio.FileType.REGULAR) {
|
||||
let fileExtension = fileName.split('.').pop();
|
||||
item.type = `${fileExtension}`;
|
||||
}
|
||||
|
||||
contents.push(item);
|
||||
contents.sort((a, b) => {
|
||||
const aIsFolder = a.type.startsWith('folder');
|
||||
const bIsFolder = b.type.startsWith('folder');
|
||||
if (aIsFolder && !bIsFolder) {
|
||||
return -1;
|
||||
} else if (!aIsFolder && bIsFolder) {
|
||||
return 1;
|
||||
} else {
|
||||
return a.name.localeCompare(b.name); // Sort alphabetically within folders and files
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
if (!silent) console.log(e);
|
||||
}
|
||||
return contents;
|
||||
}
|
||||
431
homes/me/ags-end4/modules/overview/overview_hyprland.js
Normal file
431
homes/me/ags-end4/modules/overview/overview_hyprland.js
Normal file
|
|
@ -0,0 +1,431 @@
|
|||
// TODO
|
||||
// - Make client destroy/create not destroy and recreate the whole thing
|
||||
// - Active ws hook optimization: only update when moving to next group
|
||||
//
|
||||
const { Gdk, Gtk } = imports.gi;
|
||||
const { Gravity } = imports.gi.Gdk;
|
||||
import App from 'resource:///com/github/Aylur/ags/app.js';
|
||||
import Variable from 'resource:///com/github/Aylur/ags/variable.js';
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
|
||||
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
|
||||
const { execAsync, exec } = Utils;
|
||||
import { setupCursorHoverGrab } from '../.widgetutils/cursorhover.js';
|
||||
import { dumpToWorkspace, swapWorkspace } from "./actions.js";
|
||||
import { iconExists, substitute } from "../.miscutils/icons.js";
|
||||
import { monitors } from '../.commondata/hyprlanddata.js';
|
||||
import { MaterialIcon } from '../.commonwidgets/materialicon.js';
|
||||
|
||||
const NUM_OF_WORKSPACES_SHOWN = userOptions.overview.numOfCols * userOptions.overview.numOfRows;
|
||||
const TARGET = [Gtk.TargetEntry.new('text/plain', Gtk.TargetFlags.SAME_APP, 0)];
|
||||
|
||||
const overviewTick = Variable(false);
|
||||
|
||||
export default (overviewMonitor = 0) => {
|
||||
const clientMap = new Map();
|
||||
const ContextMenuWorkspaceArray = ({ label, actionFunc, thisWorkspace }) => Widget.MenuItem({
|
||||
label: `${label}`,
|
||||
setup: (menuItem) => {
|
||||
let submenu = new Gtk.Menu();
|
||||
submenu.className = 'menu';
|
||||
|
||||
const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN;
|
||||
const startWorkspace = offset + 1;
|
||||
const endWorkspace = startWorkspace + NUM_OF_WORKSPACES_SHOWN - 1;
|
||||
for (let i = startWorkspace; i <= endWorkspace; i++) {
|
||||
let button = new Gtk.MenuItem({
|
||||
label: `Workspace ${i}`
|
||||
});
|
||||
button.connect("activate", () => {
|
||||
// execAsync([`${onClickBinary}`, `${thisWorkspace}`, `${i}`]).catch(print);
|
||||
actionFunc(thisWorkspace, i);
|
||||
overviewTick.setValue(!overviewTick.value);
|
||||
});
|
||||
submenu.append(button);
|
||||
}
|
||||
menuItem.set_reserve_indicator(true);
|
||||
menuItem.set_submenu(submenu);
|
||||
}
|
||||
})
|
||||
|
||||
const Window = ({ address, at: [x, y], size: [w, h], workspace: { id, name }, class: c, initialClass, monitor, title, xwayland }, screenCoords) => {
|
||||
const revealInfoCondition = (Math.min(w, h) * userOptions.overview.scale > 70);
|
||||
if (w <= 0 || h <= 0 || (c === '' && title === '')) return null;
|
||||
// Non-primary monitors
|
||||
if (screenCoords.x != 0) x -= screenCoords.x;
|
||||
if (screenCoords.y != 0) y -= screenCoords.y;
|
||||
// Other offscreen adjustments
|
||||
if (x + w <= 0) x += (Math.floor(x / monitors[monitor].width) * monitors[monitor].width);
|
||||
else if (x < 0) { w = x + w; x = 0; }
|
||||
if (y + h <= 0) x += (Math.floor(y / monitors[monitor].height) * monitors[monitor].height);
|
||||
else if (y < 0) { h = y + h; y = 0; }
|
||||
// Truncate if offscreen
|
||||
if (x + w > monitors[monitor].width) w = monitors[monitor].width - x;
|
||||
if (y + h > monitors[monitor].height) h = monitors[monitor].height - y;
|
||||
|
||||
if(c.length == 0) c = initialClass;
|
||||
const iconName = substitute(c);
|
||||
const appIcon = iconExists(iconName) ? Widget.Icon({
|
||||
icon: iconName,
|
||||
size: Math.min(w, h) * userOptions.overview.scale / 2.5,
|
||||
}) : MaterialIcon('terminal', 'gigantic', {
|
||||
css: `font-size: ${Math.min(w, h) * userOptions.overview.scale / 2.5}px`,
|
||||
});
|
||||
return Widget.Button({
|
||||
attribute: {
|
||||
address, x, y, w, h, ws: id,
|
||||
updateIconSize: (self) => {
|
||||
appIcon.size = Math.min(self.attribute.w, self.attribute.h) * userOptions.overview.scale / 2.5;
|
||||
},
|
||||
},
|
||||
className: 'overview-tasks-window',
|
||||
hpack: 'start',
|
||||
vpack: 'start',
|
||||
css: `
|
||||
margin-left: ${Math.round(x * userOptions.overview.scale)}px;
|
||||
margin-top: ${Math.round(y * userOptions.overview.scale)}px;
|
||||
margin-right: -${Math.round((x + w) * userOptions.overview.scale)}px;
|
||||
margin-bottom: -${Math.round((y + h) * userOptions.overview.scale)}px;
|
||||
`,
|
||||
onClicked: (self) => {
|
||||
Hyprland.messageAsync(`dispatch focuswindow address:${address}`);
|
||||
App.closeWindow('overview');
|
||||
},
|
||||
onMiddleClickRelease: () => Hyprland.messageAsync(`dispatch closewindow address:${address}`),
|
||||
onSecondaryClick: (button) => {
|
||||
button.toggleClassName('overview-tasks-window-selected', true);
|
||||
const menu = Widget.Menu({
|
||||
className: 'menu',
|
||||
children: [
|
||||
Widget.MenuItem({
|
||||
child: Widget.Label({
|
||||
xalign: 0,
|
||||
label: "Close (Middle-click)",
|
||||
}),
|
||||
onActivate: () => Hyprland.messageAsync(`dispatch closewindow address:${address}`),
|
||||
}),
|
||||
ContextMenuWorkspaceArray({
|
||||
label: "Dump windows to workspace",
|
||||
actionFunc: dumpToWorkspace,
|
||||
thisWorkspace: Number(id)
|
||||
}),
|
||||
ContextMenuWorkspaceArray({
|
||||
label: "Swap windows with workspace",
|
||||
actionFunc: swapWorkspace,
|
||||
thisWorkspace: Number(id)
|
||||
}),
|
||||
],
|
||||
});
|
||||
menu.connect("deactivate", () => {
|
||||
button.toggleClassName('overview-tasks-window-selected', false);
|
||||
})
|
||||
menu.connect("selection-done", () => {
|
||||
button.toggleClassName('overview-tasks-window-selected', false);
|
||||
})
|
||||
menu.popup_at_widget(button.get_parent(), Gravity.SOUTH, Gravity.NORTH, null); // Show menu below the button
|
||||
button.connect("destroy", () => menu.destroy());
|
||||
},
|
||||
child: Widget.Box({
|
||||
homogeneous: true,
|
||||
child: Widget.Box({
|
||||
vertical: true,
|
||||
vpack: 'center',
|
||||
children: [
|
||||
appIcon,
|
||||
// TODO: Add xwayland tag instead of just having italics
|
||||
Widget.Revealer({
|
||||
transition: 'slide_right',
|
||||
revealChild: revealInfoCondition,
|
||||
child: Widget.Revealer({
|
||||
transition: 'slide_down',
|
||||
revealChild: revealInfoCondition,
|
||||
child: Widget.Label({
|
||||
maxWidthChars: 1, // Doesn't matter what number
|
||||
truncate: 'end',
|
||||
className: `margin-top-5 ${xwayland ? 'txt txt-italic' : 'txt'}`,
|
||||
css: `
|
||||
font-size: ${Math.min(monitors[monitor].width, monitors[monitor].height) * userOptions.overview.scale / 14.6}px;
|
||||
margin: 0px ${Math.min(monitors[monitor].width, monitors[monitor].height) * userOptions.overview.scale / 10}px;
|
||||
`,
|
||||
// If the title is too short, include the class
|
||||
label: (title.length <= 1 ? `${c}: ${title}` : title),
|
||||
})
|
||||
})
|
||||
})
|
||||
]
|
||||
})
|
||||
}),
|
||||
tooltipText: `${c}: ${title}`,
|
||||
setup: (button) => {
|
||||
setupCursorHoverGrab(button);
|
||||
|
||||
button.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, TARGET, Gdk.DragAction.MOVE);
|
||||
button.drag_source_set_icon_name(substitute(c));
|
||||
|
||||
button.connect('drag-begin', (button) => { // On drag start, add the dragging class
|
||||
button.toggleClassName('overview-tasks-window-dragging', true);
|
||||
});
|
||||
button.connect('drag-data-get', (_w, _c, data) => { // On drag finish, give address
|
||||
data.set_text(address, address.length);
|
||||
button.toggleClassName('overview-tasks-window-dragging', false);
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const Workspace = (index) => {
|
||||
// const fixed = Widget.Fixed({
|
||||
// attribute: {
|
||||
// put: (widget, x, y) => {
|
||||
// fixed.put(widget, x, y);
|
||||
// },
|
||||
// move: (widget, x, y) => {
|
||||
// fixed.move(widget, x, y);
|
||||
// },
|
||||
// }
|
||||
// });
|
||||
const fixed = Widget.Box({
|
||||
attribute: {
|
||||
put: (widget, x, y) => {
|
||||
if (!widget.attribute) return;
|
||||
// Note: x and y are already multiplied by userOptions.overview.scale
|
||||
const newCss = `
|
||||
margin-left: ${Math.round(x)}px;
|
||||
margin-top: ${Math.round(y)}px;
|
||||
margin-right: -${Math.round(x + (widget.attribute.w * userOptions.overview.scale))}px;
|
||||
margin-bottom: -${Math.round(y + (widget.attribute.h * userOptions.overview.scale))}px;
|
||||
`;
|
||||
widget.css = newCss;
|
||||
fixed.pack_start(widget, false, false, 0);
|
||||
},
|
||||
move: (widget, x, y) => {
|
||||
if (!widget) return;
|
||||
if (!widget.attribute) return;
|
||||
// Note: x and y are already multiplied by userOptions.overview.scale
|
||||
const newCss = `
|
||||
margin-left: ${Math.round(x)}px;
|
||||
margin-top: ${Math.round(y)}px;
|
||||
margin-right: -${Math.round(x + (widget.attribute.w * userOptions.overview.scale))}px;
|
||||
margin-bottom: -${Math.round(y + (widget.attribute.h * userOptions.overview.scale))}px;
|
||||
`;
|
||||
widget.css = newCss;
|
||||
},
|
||||
}
|
||||
})
|
||||
const WorkspaceNumber = ({ index, ...rest }) => Widget.Label({
|
||||
className: 'overview-tasks-workspace-number',
|
||||
label: `${index}`,
|
||||
css: `
|
||||
margin: ${Math.min(monitors[overviewMonitor].width, monitors[overviewMonitor].height) * userOptions.overview.scale * userOptions.overview.wsNumMarginScale}px;
|
||||
font-size: ${monitors[overviewMonitor].height * userOptions.overview.scale * userOptions.overview.wsNumScale}px;
|
||||
`,
|
||||
setup: (self) => self.hook(Hyprland.active.workspace, (self) => {
|
||||
// Update when going to new ws group
|
||||
const currentGroup = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN);
|
||||
self.label = `${currentGroup * NUM_OF_WORKSPACES_SHOWN + index}`;
|
||||
}),
|
||||
...rest,
|
||||
})
|
||||
const widget = Widget.Box({
|
||||
className: 'overview-tasks-workspace',
|
||||
vpack: 'center',
|
||||
// Rounding and adding 1px to minimum width/height to work around scaling inaccuracy:
|
||||
css: `
|
||||
min-width: ${1 + Math.round(monitors[overviewMonitor].width * userOptions.overview.scale)}px;
|
||||
min-height: ${1 + Math.round(monitors[overviewMonitor].height * userOptions.overview.scale)}px;
|
||||
`,
|
||||
children: [Widget.EventBox({
|
||||
hexpand: true,
|
||||
onPrimaryClick: () => {
|
||||
Hyprland.messageAsync(`dispatch workspace ${index}`);
|
||||
App.closeWindow('overview');
|
||||
},
|
||||
setup: (eventbox) => {
|
||||
eventbox.drag_dest_set(Gtk.DestDefaults.ALL, TARGET, Gdk.DragAction.COPY);
|
||||
eventbox.connect('drag-data-received', (_w, _c, _x, _y, data) => {
|
||||
const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN;
|
||||
Hyprland.messageAsync(`dispatch movetoworkspacesilent ${index + offset},address:${data.get_text()}`)
|
||||
overviewTick.setValue(!overviewTick.value);
|
||||
});
|
||||
},
|
||||
child: Widget.Overlay({
|
||||
child: Widget.Box({}),
|
||||
overlays: [
|
||||
WorkspaceNumber({ index: index, hpack: 'start', vpack: 'start' }),
|
||||
fixed
|
||||
]
|
||||
}),
|
||||
})],
|
||||
});
|
||||
const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN;
|
||||
fixed.attribute.put(WorkspaceNumber(offset + index), 0, 0);
|
||||
widget.clear = () => {
|
||||
const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN;
|
||||
clientMap.forEach((client, address) => {
|
||||
if (!client) return;
|
||||
if ((client.attribute.ws <= offset || client.attribute.ws > offset + NUM_OF_WORKSPACES_SHOWN) ||
|
||||
(client.attribute.ws == offset + index)) {
|
||||
client.destroy();
|
||||
client = null;
|
||||
clientMap.delete(address);
|
||||
}
|
||||
});
|
||||
}
|
||||
widget.set = (clientJson, screenCoords) => {
|
||||
let c = clientMap.get(clientJson.address);
|
||||
if (c) {
|
||||
if (c.attribute?.ws !== clientJson.workspace.id) {
|
||||
c.destroy();
|
||||
c = null;
|
||||
clientMap.delete(clientJson.address);
|
||||
}
|
||||
else if (c) {
|
||||
c.attribute.w = clientJson.size[0];
|
||||
c.attribute.h = clientJson.size[1];
|
||||
c.attribute.updateIconSize(c);
|
||||
fixed.attribute.move(c,
|
||||
Math.max(0, clientJson.at[0] * userOptions.overview.scale),
|
||||
Math.max(0, clientJson.at[1] * userOptions.overview.scale)
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
const newWindow = Window(clientJson, screenCoords);
|
||||
if (newWindow === null) return;
|
||||
// clientMap.set(clientJson.address, newWindow);
|
||||
fixed.attribute.put(newWindow,
|
||||
Math.max(0, newWindow.attribute.x * userOptions.overview.scale),
|
||||
Math.max(0, newWindow.attribute.y * userOptions.overview.scale)
|
||||
);
|
||||
clientMap.set(clientJson.address, newWindow);
|
||||
};
|
||||
widget.unset = (clientAddress) => {
|
||||
const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN;
|
||||
let c = clientMap.get(clientAddress);
|
||||
if (!c) return;
|
||||
c.destroy();
|
||||
c = null;
|
||||
clientMap.delete(clientAddress);
|
||||
};
|
||||
widget.show = () => {
|
||||
fixed.show_all();
|
||||
}
|
||||
return widget;
|
||||
};
|
||||
|
||||
const arr = (s, n) => {
|
||||
const array = [];
|
||||
for (let i = 0; i < n; i++)
|
||||
array.push(s + i);
|
||||
|
||||
return array;
|
||||
};
|
||||
|
||||
const OverviewRow = ({ startWorkspace, workspaces, windowName = 'overview' }) => Widget.Box({
|
||||
children: arr(startWorkspace, workspaces).map(Workspace),
|
||||
attribute: {
|
||||
workspaceGroup: Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN),
|
||||
monitorMap: [],
|
||||
getMonitorMap: (box) => {
|
||||
execAsync('hyprctl -j monitors').then(monitors => {
|
||||
box.attribute.monitorMap = JSON.parse(monitors).reduce((acc, item) => {
|
||||
acc[item.id] = { x: item.x, y: item.y };
|
||||
return acc;
|
||||
}, {});
|
||||
});
|
||||
},
|
||||
update: (box) => {
|
||||
const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN;
|
||||
Hyprland.messageAsync('j/clients').then(clients => {
|
||||
const allClients = JSON.parse(clients);
|
||||
const kids = box.get_children();
|
||||
kids.forEach(kid => kid.clear());
|
||||
for (let i = 0; i < allClients.length; i++) {
|
||||
const client = allClients[i];
|
||||
const childID = client.workspace.id - (offset + startWorkspace);
|
||||
if (offset + startWorkspace <= client.workspace.id &&
|
||||
client.workspace.id <= offset + startWorkspace + workspaces) {
|
||||
const screenCoords = box.attribute.monitorMap[client.monitor];
|
||||
if (kids[childID]) {
|
||||
kids[childID].set(client, screenCoords);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
kids.forEach(kid => kid.show());
|
||||
}).catch(print);
|
||||
},
|
||||
updateWorkspace: (box, id) => {
|
||||
const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN;
|
||||
if (!( // Not in range, ignore
|
||||
offset + startWorkspace <= id &&
|
||||
id <= offset + startWorkspace + workspaces
|
||||
)) return;
|
||||
// if (!App.getWindow(windowName)?.visible) return;
|
||||
Hyprland.messageAsync('j/clients').then(clients => {
|
||||
const allClients = JSON.parse(clients);
|
||||
const kids = box.get_children();
|
||||
for (let i = 0; i < allClients.length; i++) {
|
||||
const client = allClients[i];
|
||||
if (client.workspace.id != id) continue;
|
||||
const screenCoords = box.attribute.monitorMap[client.monitor];
|
||||
kids[id - (offset + startWorkspace)]?.set(client, screenCoords);
|
||||
}
|
||||
kids[id - (offset + startWorkspace)]?.show();
|
||||
}).catch(print);
|
||||
},
|
||||
},
|
||||
setup: (box) => {
|
||||
box.attribute.getMonitorMap(box);
|
||||
box
|
||||
.hook(overviewTick, (box) => box.attribute.update(box))
|
||||
.hook(Hyprland, (box, clientAddress) => {
|
||||
const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN;
|
||||
const kids = box.get_children();
|
||||
const client = Hyprland.getClient(clientAddress);
|
||||
if (!client) return;
|
||||
const id = client.workspace.id;
|
||||
|
||||
box.attribute.updateWorkspace(box, id);
|
||||
kids[id - (offset + startWorkspace)]?.unset(clientAddress);
|
||||
}, 'client-removed')
|
||||
.hook(Hyprland, (box, clientAddress) => {
|
||||
const client = Hyprland.getClient(clientAddress);
|
||||
if (!client) return;
|
||||
box.attribute.updateWorkspace(box, client.workspace.id);
|
||||
}, 'client-added')
|
||||
.hook(Hyprland.active.workspace, (box) => {
|
||||
// Full update when going to new ws group
|
||||
const previousGroup = box.attribute.workspaceGroup;
|
||||
const currentGroup = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN);
|
||||
if (currentGroup !== previousGroup) {
|
||||
if (!App.getWindow(windowName) || !App.getWindow(windowName).visible) return;
|
||||
box.attribute.update(box);
|
||||
box.attribute.workspaceGroup = currentGroup;
|
||||
}
|
||||
})
|
||||
.hook(App, (box, name, visible) => { // Update on open
|
||||
if (name == 'overview' && visible) box.attribute.update(box);
|
||||
})
|
||||
},
|
||||
});
|
||||
|
||||
return Widget.Revealer({
|
||||
revealChild: true,
|
||||
// hpack to prevent unneeded expansion in overview-tasks-workspace:
|
||||
hpack: 'center',
|
||||
transition: 'slide_down',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
child: Widget.Box({
|
||||
vertical: true,
|
||||
className: 'overview-tasks',
|
||||
children: Array.from({ length: userOptions.overview.numOfRows }, (_, index) =>
|
||||
OverviewRow({
|
||||
startWorkspace: 1 + index * userOptions.overview.numOfCols,
|
||||
workspaces: userOptions.overview.numOfCols,
|
||||
})
|
||||
)
|
||||
}),
|
||||
});
|
||||
}
|
||||
189
homes/me/ags-end4/modules/overview/searchbuttons.js
Normal file
189
homes/me/ags-end4/modules/overview/searchbuttons.js
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
const { Gtk } = imports.gi;
|
||||
import App from 'resource:///com/github/Aylur/ags/app.js';
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
const { execAsync, exec } = Utils;
|
||||
import { searchItem } from './searchitem.js';
|
||||
import { execAndClose, couldBeMath, launchCustomCommand } from './miscfunctions.js';
|
||||
import GeminiService from '../../services/gemini.js';
|
||||
|
||||
export const NoResultButton = () => searchItem({
|
||||
materialIconName: 'Error',
|
||||
name: "Search invalid",
|
||||
content: "No results found!",
|
||||
onActivate: () => {
|
||||
App.closeWindow('overview');
|
||||
},
|
||||
});
|
||||
|
||||
export const DirectoryButton = ({ parentPath, name, type, icon }) => {
|
||||
const actionText = Widget.Revealer({
|
||||
revealChild: false,
|
||||
transition: "crossfade",
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
child: Widget.Label({
|
||||
className: 'overview-search-results-txt txt txt-small txt-action',
|
||||
label: 'Open',
|
||||
})
|
||||
});
|
||||
const actionTextRevealer = Widget.Revealer({
|
||||
revealChild: false,
|
||||
transition: "slide_left",
|
||||
transitionDuration: userOptions.animations.durationSmall,
|
||||
child: actionText,
|
||||
});
|
||||
return Widget.Button({
|
||||
className: 'overview-search-result-btn',
|
||||
onClicked: () => {
|
||||
App.closeWindow('overview');
|
||||
execAsync(['bash', '-c', `xdg-open '${parentPath}/${name}'`, `&`]).catch(print);
|
||||
},
|
||||
child: Widget.Box({
|
||||
children: [
|
||||
Widget.Box({
|
||||
vertical: false,
|
||||
children: [
|
||||
Widget.Box({
|
||||
className: 'overview-search-results-icon',
|
||||
homogeneous: true,
|
||||
child: Widget.Icon({
|
||||
icon: icon,
|
||||
}),
|
||||
}),
|
||||
Widget.Label({
|
||||
className: 'overview-search-results-txt txt txt-norm',
|
||||
label: name,
|
||||
}),
|
||||
Widget.Box({ hexpand: true }),
|
||||
actionTextRevealer,
|
||||
]
|
||||
})
|
||||
]
|
||||
}),
|
||||
setup: (self) => self
|
||||
.on('focus-in-event', (button) => {
|
||||
actionText.revealChild = true;
|
||||
actionTextRevealer.revealChild = true;
|
||||
})
|
||||
.on('focus-out-event', (button) => {
|
||||
actionText.revealChild = false;
|
||||
actionTextRevealer.revealChild = false;
|
||||
})
|
||||
,
|
||||
})
|
||||
}
|
||||
|
||||
export const CalculationResultButton = ({ result, text }) => searchItem({
|
||||
materialIconName: 'calculate',
|
||||
name: `Math result`,
|
||||
actionName: "Copy",
|
||||
content: `${result}`,
|
||||
onActivate: () => {
|
||||
App.closeWindow('overview');
|
||||
execAsync(['wl-copy', `${result}`]).catch(print);
|
||||
},
|
||||
});
|
||||
|
||||
export const DesktopEntryButton = (app) => {
|
||||
const actionText = Widget.Revealer({
|
||||
revealChild: false,
|
||||
transition: "crossfade",
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
child: Widget.Label({
|
||||
className: 'overview-search-results-txt txt txt-small txt-action',
|
||||
label: 'Launch',
|
||||
})
|
||||
});
|
||||
const actionTextRevealer = Widget.Revealer({
|
||||
revealChild: false,
|
||||
transition: "slide_left",
|
||||
transitionDuration: userOptions.animations.durationSmall,
|
||||
child: actionText,
|
||||
});
|
||||
return Widget.Button({
|
||||
className: 'overview-search-result-btn',
|
||||
onClicked: () => {
|
||||
App.closeWindow('overview');
|
||||
app.launch();
|
||||
},
|
||||
child: Widget.Box({
|
||||
children: [
|
||||
Widget.Box({
|
||||
vertical: false,
|
||||
children: [
|
||||
Widget.Box({
|
||||
className: 'overview-search-results-icon',
|
||||
homogeneous: true,
|
||||
child: Widget.Icon({
|
||||
icon: app.iconName,
|
||||
}),
|
||||
}),
|
||||
Widget.Label({
|
||||
className: 'overview-search-results-txt txt txt-norm',
|
||||
label: app.name,
|
||||
}),
|
||||
Widget.Box({ hexpand: true }),
|
||||
actionTextRevealer,
|
||||
]
|
||||
})
|
||||
]
|
||||
}),
|
||||
setup: (self) => self
|
||||
.on('focus-in-event', (button) => {
|
||||
actionText.revealChild = true;
|
||||
actionTextRevealer.revealChild = true;
|
||||
})
|
||||
.on('focus-out-event', (button) => {
|
||||
actionText.revealChild = false;
|
||||
actionTextRevealer.revealChild = false;
|
||||
})
|
||||
,
|
||||
})
|
||||
}
|
||||
|
||||
export const ExecuteCommandButton = ({ command, terminal = false }) => searchItem({
|
||||
materialIconName: `${terminal ? 'terminal' : 'settings_b_roll'}`,
|
||||
name: `Run command`,
|
||||
actionName: `Execute ${terminal ? 'in terminal' : ''}`,
|
||||
content: `${command}`,
|
||||
onActivate: () => execAndClose(command, terminal),
|
||||
extraClassName: 'techfont',
|
||||
})
|
||||
|
||||
export const CustomCommandButton = ({ text = '' }) => searchItem({
|
||||
materialIconName: 'settings_suggest',
|
||||
name: 'Action',
|
||||
actionName: 'Run',
|
||||
content: `${text}`,
|
||||
onActivate: () => {
|
||||
App.closeWindow('overview');
|
||||
launchCustomCommand(text);
|
||||
},
|
||||
});
|
||||
|
||||
export const SearchButton = ({ text = '' }) => searchItem({
|
||||
materialIconName: 'travel_explore',
|
||||
name: 'Search the web',
|
||||
actionName: 'Go',
|
||||
content: `${text}`,
|
||||
onActivate: () => {
|
||||
App.closeWindow('overview');
|
||||
let search = userOptions.search.engineBaseUrl + text;
|
||||
for (let site of userOptions.search.excludedSites) {
|
||||
if (site) search += ` -site:${site}`;
|
||||
}
|
||||
execAsync(['bash', '-c', `xdg-open '${search}' &`]).catch(print);
|
||||
},
|
||||
});
|
||||
|
||||
export const AiButton = ({ text }) => searchItem({
|
||||
materialIconName: 'chat_paste_go',
|
||||
name: 'Ask Gemini',
|
||||
actionName: 'Ask',
|
||||
content: `${text}`,
|
||||
onActivate: () => {
|
||||
GeminiService.send(text);
|
||||
App.closeWindow('overview');
|
||||
App.openWindow('sideleft');
|
||||
},
|
||||
});
|
||||
65
homes/me/ags-end4/modules/overview/searchitem.js
Normal file
65
homes/me/ags-end4/modules/overview/searchitem.js
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
|
||||
export const searchItem = ({ materialIconName, name, actionName, content, onActivate, extraClassName = '', ...rest }) => {
|
||||
const actionText = Widget.Revealer({
|
||||
revealChild: false,
|
||||
transition: "crossfade",
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
child: Widget.Label({
|
||||
className: 'overview-search-results-txt txt txt-small txt-action',
|
||||
label: `${actionName}`,
|
||||
})
|
||||
});
|
||||
const actionTextRevealer = Widget.Revealer({
|
||||
revealChild: false,
|
||||
transition: "slide_left",
|
||||
transitionDuration: userOptions.animations.durationSmall,
|
||||
child: actionText,
|
||||
})
|
||||
return Widget.Button({
|
||||
className: `overview-search-result-btn txt ${extraClassName}`,
|
||||
onClicked: onActivate,
|
||||
child: Widget.Box({
|
||||
children: [
|
||||
Widget.Box({
|
||||
vertical: false,
|
||||
children: [
|
||||
Widget.Label({
|
||||
className: `icon-material overview-search-results-icon`,
|
||||
label: `${materialIconName}`,
|
||||
}),
|
||||
Widget.Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
Widget.Label({
|
||||
hpack: 'start',
|
||||
className: 'overview-search-results-txt txt-smallie txt-subtext',
|
||||
label: `${name}`,
|
||||
truncate: "end",
|
||||
}),
|
||||
Widget.Label({
|
||||
hpack: 'start',
|
||||
className: 'overview-search-results-txt txt-norm',
|
||||
label: `${content}`,
|
||||
truncate: "end",
|
||||
}),
|
||||
]
|
||||
}),
|
||||
Widget.Box({ hexpand: true }),
|
||||
actionTextRevealer,
|
||||
],
|
||||
})
|
||||
]
|
||||
}),
|
||||
setup: (self) => self
|
||||
.on('focus-in-event', (button) => {
|
||||
actionText.revealChild = true;
|
||||
actionTextRevealer.revealChild = true;
|
||||
})
|
||||
.on('focus-out-event', (button) => {
|
||||
actionText.revealChild = false;
|
||||
actionTextRevealer.revealChild = false;
|
||||
})
|
||||
,
|
||||
});
|
||||
}
|
||||
213
homes/me/ags-end4/modules/overview/windowcontent.js
Normal file
213
homes/me/ags-end4/modules/overview/windowcontent.js
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
const { Gdk, Gtk } = imports.gi;
|
||||
import App from 'resource:///com/github/Aylur/ags/app.js';
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
|
||||
import Applications from 'resource:///com/github/Aylur/ags/service/applications.js';
|
||||
const { execAsync, exec } = Utils;
|
||||
import { execAndClose, expandTilde, hasUnterminatedBackslash, couldBeMath, launchCustomCommand, ls } from './miscfunctions.js';
|
||||
import {
|
||||
CalculationResultButton, CustomCommandButton, DirectoryButton,
|
||||
DesktopEntryButton, ExecuteCommandButton, SearchButton, AiButton, NoResultButton,
|
||||
} from './searchbuttons.js';
|
||||
import { checkKeybind } from '../.widgetutils/keybind.js';
|
||||
import GeminiService from '../../services/gemini.js';
|
||||
|
||||
// Add math funcs
|
||||
const { abs, sin, cos, tan, cot, asin, acos, atan, acot } = Math;
|
||||
const pi = Math.PI;
|
||||
// trigonometric funcs for deg
|
||||
const sind = x => sin(x * pi / 180);
|
||||
const cosd = x => cos(x * pi / 180);
|
||||
const tand = x => tan(x * pi / 180);
|
||||
const cotd = x => cot(x * pi / 180);
|
||||
const asind = x => asin(x) * 180 / pi;
|
||||
const acosd = x => acos(x) * 180 / pi;
|
||||
const atand = x => atan(x) * 180 / pi;
|
||||
const acotd = x => acot(x) * 180 / pi;
|
||||
|
||||
const MAX_RESULTS = 10;
|
||||
const OVERVIEW_SCALE = 0.18; // = overview workspace box / screen size
|
||||
const OVERVIEW_WS_NUM_SCALE = 0.09;
|
||||
const OVERVIEW_WS_NUM_MARGIN_SCALE = 0.07;
|
||||
const TARGET = [Gtk.TargetEntry.new('text/plain', Gtk.TargetFlags.SAME_APP, 0)];
|
||||
|
||||
function iconExists(iconName) {
|
||||
let iconTheme = Gtk.IconTheme.get_default();
|
||||
return iconTheme.has_icon(iconName);
|
||||
}
|
||||
|
||||
const OptionalOverview = async () => {
|
||||
try {
|
||||
return (await import('./overview_hyprland.js')).default();
|
||||
} catch {
|
||||
return Widget.Box({});
|
||||
// return (await import('./overview_hyprland.js')).default();
|
||||
}
|
||||
};
|
||||
|
||||
const overviewContent = await OptionalOverview();
|
||||
|
||||
export const SearchAndWindows = () => {
|
||||
var _appSearchResults = [];
|
||||
|
||||
const resultsBox = Widget.Box({
|
||||
className: 'overview-search-results',
|
||||
vertical: true,
|
||||
});
|
||||
const resultsRevealer = Widget.Revealer({
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
revealChild: false,
|
||||
transition: 'slide_down',
|
||||
// duration: 200,
|
||||
hpack: 'center',
|
||||
child: resultsBox,
|
||||
});
|
||||
const entryPromptRevealer = Widget.Revealer({
|
||||
transition: 'crossfade',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
revealChild: true,
|
||||
hpack: 'center',
|
||||
child: Widget.Label({
|
||||
className: 'overview-search-prompt txt-small txt',
|
||||
label: getString('Type to search')
|
||||
}),
|
||||
});
|
||||
|
||||
const entryIconRevealer = Widget.Revealer({
|
||||
transition: 'crossfade',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
revealChild: false,
|
||||
hpack: 'end',
|
||||
child: Widget.Label({
|
||||
className: 'txt txt-large icon-material overview-search-icon',
|
||||
label: 'search',
|
||||
}),
|
||||
});
|
||||
|
||||
const entryIcon = Widget.Box({
|
||||
className: 'overview-search-prompt-box',
|
||||
setup: box => box.pack_start(entryIconRevealer, true, true, 0),
|
||||
});
|
||||
|
||||
const entry = Widget.Entry({
|
||||
className: 'overview-search-box txt-small txt',
|
||||
hpack: 'center',
|
||||
onAccept: (self) => { // This is when you hit Enter
|
||||
resultsBox.children[0].onClicked();
|
||||
},
|
||||
onChange: (entry) => { // this is when you type
|
||||
const isAction = entry.text[0] == '>';
|
||||
const isDir = (['/', '~'].includes(entry.text[0]));
|
||||
resultsBox.get_children().forEach(ch => ch.destroy());
|
||||
|
||||
// check empty if so then dont do stuff
|
||||
if (entry.text == '') {
|
||||
resultsRevealer.revealChild = false;
|
||||
overviewContent.revealChild = true;
|
||||
entryPromptRevealer.revealChild = true;
|
||||
entryIconRevealer.revealChild = false;
|
||||
entry.toggleClassName('overview-search-box-extended', false);
|
||||
return;
|
||||
}
|
||||
const text = entry.text;
|
||||
resultsRevealer.revealChild = true;
|
||||
overviewContent.revealChild = false;
|
||||
entryPromptRevealer.revealChild = false;
|
||||
entryIconRevealer.revealChild = true;
|
||||
entry.toggleClassName('overview-search-box-extended', true);
|
||||
_appSearchResults = Applications.query(text);
|
||||
|
||||
// Calculate
|
||||
if (userOptions.search.enableFeatures.mathResults && couldBeMath(text)) { // Eval on typing is dangerous; this is a small workaround.
|
||||
try {
|
||||
const fullResult = eval(text.replace(/\^/g, "**"));
|
||||
resultsBox.add(CalculationResultButton({ result: fullResult, text: text }));
|
||||
} catch (e) {
|
||||
// console.log(e);
|
||||
}
|
||||
}
|
||||
if (userOptions.search.enableFeatures.directorySearch && isDir) {
|
||||
var contents = [];
|
||||
contents = ls({ path: text, silent: true });
|
||||
contents.forEach((item) => {
|
||||
resultsBox.add(DirectoryButton(item));
|
||||
})
|
||||
}
|
||||
if (userOptions.search.enableFeatures.actions && isAction) { // Eval on typing is dangerous, this is a workaround.
|
||||
resultsBox.add(CustomCommandButton({ text: entry.text }));
|
||||
}
|
||||
// Add application entries
|
||||
let appsToAdd = MAX_RESULTS;
|
||||
_appSearchResults.forEach(app => {
|
||||
if (appsToAdd == 0) return;
|
||||
resultsBox.add(DesktopEntryButton(app));
|
||||
appsToAdd--;
|
||||
});
|
||||
|
||||
// Fallbacks
|
||||
// if the first word is an actual command
|
||||
if (userOptions.search.enableFeatures.commands && !isAction && !hasUnterminatedBackslash(text) && exec(`bash -c "command -v ${text.split(' ')[0]}"`) != '') {
|
||||
resultsBox.add(ExecuteCommandButton({ command: entry.text, terminal: entry.text.startsWith('sudo') }));
|
||||
}
|
||||
|
||||
// Add fallback: search
|
||||
if (userOptions.search.enableFeatures.aiSearch)
|
||||
resultsBox.add(AiButton({ text: entry.text }));
|
||||
if (userOptions.search.enableFeatures.webSearch)
|
||||
resultsBox.add(SearchButton({ text: entry.text }));
|
||||
if (resultsBox.children.length == 0) resultsBox.add(NoResultButton());
|
||||
resultsBox.show_all();
|
||||
},
|
||||
});
|
||||
return Widget.Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
Widget.Box({
|
||||
hpack: 'center',
|
||||
children: [
|
||||
entry,
|
||||
Widget.Box({
|
||||
className: 'overview-search-icon-box',
|
||||
setup: (box) => {
|
||||
box.pack_start(entryPromptRevealer, true, true, 0)
|
||||
},
|
||||
}),
|
||||
entryIcon,
|
||||
]
|
||||
}),
|
||||
overviewContent,
|
||||
resultsRevealer,
|
||||
],
|
||||
setup: (self) => self
|
||||
.hook(App, (_b, name, visible) => {
|
||||
if (name == 'overview' && !visible) {
|
||||
resultsBox.children = [];
|
||||
entry.set_text('');
|
||||
}
|
||||
})
|
||||
.on('key-press-event', (widget, event) => { // Typing
|
||||
const keyval = event.get_keyval()[1];
|
||||
const modstate = event.get_state()[1];
|
||||
if (checkKeybind(event, userOptions.keybinds.overview.altMoveLeft))
|
||||
entry.set_position(Math.max(entry.get_position() - 1, 0));
|
||||
else if (checkKeybind(event, userOptions.keybinds.overview.altMoveRight))
|
||||
entry.set_position(Math.min(entry.get_position() + 1, entry.get_text().length));
|
||||
else if (checkKeybind(event, userOptions.keybinds.overview.deleteToEnd)) {
|
||||
const text = entry.get_text();
|
||||
const pos = entry.get_position();
|
||||
const newText = text.slice(0, pos);
|
||||
entry.set_text(newText);
|
||||
entry.set_position(newText.length);
|
||||
}
|
||||
else if (!(modstate & Gdk.ModifierType.CONTROL_MASK)) { // Ctrl not held
|
||||
if (keyval >= 32 && keyval <= 126 && widget != entry) {
|
||||
Utils.timeout(1, () => entry.grab_focus());
|
||||
entry.set_text(entry.text + String.fromCharCode(keyval));
|
||||
entry.set_position(-1);
|
||||
}
|
||||
}
|
||||
})
|
||||
,
|
||||
});
|
||||
};
|
||||
38
homes/me/ags-end4/modules/screencorners/main.js
Normal file
38
homes/me/ags-end4/modules/screencorners/main.js
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
|
||||
import { enableClickthrough } from "../.widgetutils/clickthrough.js";
|
||||
import { RoundedCorner } from "../.commonwidgets/cairo_roundedcorner.js";
|
||||
|
||||
if(userOptions.appearance.fakeScreenRounding === 2) Hyprland.connect('event', (service, name, data) => {
|
||||
if (name == 'fullscreen') {
|
||||
const monitor = Hyprland.active.monitor.id;
|
||||
if (data == '1') {
|
||||
for (const window of App.windows) {
|
||||
if (window.name.startsWith("corner") && window.name.endsWith(monitor)) {
|
||||
App.closeWindow(window.name);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const window of App.windows) {
|
||||
if (window.name.startsWith("corner") && window.name.endsWith(monitor)) {
|
||||
App.openWindow(window.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export default (monitor = 0, where = 'bottom left', useOverlayLayer = true) => {
|
||||
const positionString = where.replace(/\s/, ""); // remove space
|
||||
return Widget.Window({
|
||||
monitor,
|
||||
name: `corner${positionString}${monitor}`,
|
||||
layer: useOverlayLayer ? 'overlay' : 'top',
|
||||
anchor: where.split(' '),
|
||||
exclusivity: 'ignore',
|
||||
visible: true,
|
||||
child: RoundedCorner(positionString, { className: 'corner-black', }),
|
||||
setup: enableClickthrough,
|
||||
});
|
||||
}
|
||||
|
||||
14
homes/me/ags-end4/modules/session/main.js
Normal file
14
homes/me/ags-end4/modules/session/main.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import SessionScreen from "./sessionscreen.js";
|
||||
import PopupWindow from '../.widgethacks/popupwindow.js';
|
||||
|
||||
export default (id = 0) => PopupWindow({ // On-screen keyboard
|
||||
monitor: id,
|
||||
name: `session${id}`,
|
||||
visible: false,
|
||||
keymode: 'on-demand',
|
||||
layer: 'overlay',
|
||||
exclusivity: 'ignore',
|
||||
anchor: ['top', 'bottom', 'left', 'right'],
|
||||
child: SessionScreen({ id: id }),
|
||||
})
|
||||
134
homes/me/ags-end4/modules/session/sessionscreen.js
Normal file
134
homes/me/ags-end4/modules/session/sessionscreen.js
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
// This is for the cool memory indicator on the sidebar
|
||||
// For the right pill of the bar, see system.js
|
||||
const { Gdk, Gtk } = imports.gi;
|
||||
import App from 'resource:///com/github/Aylur/ags/app.js';
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
import { monitors } from '../.commondata/hyprlanddata.js';
|
||||
|
||||
const { exec, execAsync } = Utils;
|
||||
|
||||
const SessionButton = (name, icon, command, props = {}, colorid = 0) => {
|
||||
const buttonDescription = Widget.Revealer({
|
||||
vpack: 'end',
|
||||
transitionDuration: userOptions.animations.durationSmall,
|
||||
transition: 'slide_down',
|
||||
revealChild: false,
|
||||
child: Widget.Label({
|
||||
className: 'txt-smaller session-button-desc',
|
||||
label: name,
|
||||
}),
|
||||
});
|
||||
return Widget.Button({
|
||||
onClicked: command,
|
||||
className: `session-button session-color-${colorid}`,
|
||||
child: Widget.Overlay({
|
||||
className: 'session-button-box',
|
||||
child: Widget.Label({
|
||||
vexpand: true,
|
||||
className: 'icon-material',
|
||||
label: icon,
|
||||
}),
|
||||
overlays: [
|
||||
buttonDescription,
|
||||
]
|
||||
}),
|
||||
onHover: (button) => {
|
||||
const display = Gdk.Display.get_default();
|
||||
const cursor = Gdk.Cursor.new_from_name(display, 'pointer');
|
||||
button.get_window().set_cursor(cursor);
|
||||
buttonDescription.revealChild = true;
|
||||
},
|
||||
onHoverLost: (button) => {
|
||||
const display = Gdk.Display.get_default();
|
||||
const cursor = Gdk.Cursor.new_from_name(display, 'default');
|
||||
button.get_window().set_cursor(cursor);
|
||||
buttonDescription.revealChild = false;
|
||||
},
|
||||
setup: (self) => self
|
||||
.on('focus-in-event', (self) => {
|
||||
buttonDescription.revealChild = true;
|
||||
self.toggleClassName('session-button-focused', true);
|
||||
})
|
||||
.on('focus-out-event', (self) => {
|
||||
buttonDescription.revealChild = false;
|
||||
self.toggleClassName('session-button-focused', false);
|
||||
})
|
||||
,
|
||||
...props,
|
||||
});
|
||||
}
|
||||
|
||||
export default ({ id = 0 }) => {
|
||||
// lock, logout, sleep
|
||||
const lockButton = SessionButton(getString('Lock'), 'lock', () => { closeWindowOnAllMonitors('session'); execAsync(['loginctl', 'lock-session']).catch(print) }, {}, 1);
|
||||
const logoutButton = SessionButton(getString('Logout'), 'logout', () => { closeWindowOnAllMonitors('session'); execAsync(['bash', '-c', 'pkill Hyprland || pkill sway || pkill niri || loginctl terminate-user $USER']).catch(print) }, {}, 2);
|
||||
const sleepButton = SessionButton(getString('Sleep'), 'sleep', () => { closeWindowOnAllMonitors('session'); execAsync(['bash', '-c', 'systemctl suspend || loginctl suspend']).catch(print) }, {}, 3);
|
||||
// hibernate, shutdown, reboot
|
||||
const hibernateButton = SessionButton(getString('Hibernate'), 'downloading', () => { closeWindowOnAllMonitors('session'); execAsync(['bash', '-c', 'systemctl hibernate || loginctl hibernate']).catch(print) }, {}, 4);
|
||||
const shutdownButton = SessionButton(getString('Shutdown'), 'power_settings_new', () => { closeWindowOnAllMonitors('session'); execAsync(['bash', '-c', 'systemctl poweroff || loginctl poweroff']).catch(print) }, {}, 5);
|
||||
const rebootButton = SessionButton(getString('Reboot'), 'restart_alt', () => { closeWindowOnAllMonitors('session'); execAsync(['bash', '-c', 'systemctl reboot || loginctl reboot']).catch(print) }, {}, 6);
|
||||
const cancelButton = SessionButton(getString('Cancel'), 'close', () => closeWindowOnAllMonitors('session'), { className: 'session-button-cancel' }, 7);
|
||||
|
||||
const sessionDescription = Widget.Box({
|
||||
vertical: true,
|
||||
css: 'margin-bottom: 0.682rem;',
|
||||
children: [
|
||||
Widget.Label({
|
||||
className: 'txt-title txt',
|
||||
label: getString('Session'),
|
||||
}),
|
||||
Widget.Label({
|
||||
justify: Gtk.Justification.CENTER,
|
||||
className: 'txt-small txt',
|
||||
label: getString('Use arrow keys to navigate.\nEnter to select, Esc to cancel.')
|
||||
}),
|
||||
]
|
||||
});
|
||||
const SessionButtonRow = (children) => Widget.Box({
|
||||
hpack: 'center',
|
||||
className: 'spacing-h-15',
|
||||
children: children,
|
||||
});
|
||||
const sessionButtonRows = [
|
||||
SessionButtonRow([lockButton, logoutButton, sleepButton]),
|
||||
SessionButtonRow([hibernateButton, shutdownButton, rebootButton]),
|
||||
SessionButtonRow([cancelButton]),
|
||||
]
|
||||
return Widget.Box({
|
||||
className: 'session-bg',
|
||||
css: `
|
||||
min-width: ${monitors[id].width}px;
|
||||
min-height: ${monitors[id].height}px;
|
||||
`, // idk why but height = screen height doesn't fill
|
||||
vertical: true,
|
||||
children: [
|
||||
Widget.EventBox({
|
||||
onPrimaryClick: () => closeWindowOnAllMonitors('session'),
|
||||
onSecondaryClick: () => closeWindowOnAllMonitors('session'),
|
||||
onMiddleClick: () => closeWindowOnAllMonitors('session'),
|
||||
}),
|
||||
Widget.Box({
|
||||
hpack: 'center',
|
||||
vexpand: true,
|
||||
vertical: true,
|
||||
children: [
|
||||
Widget.Box({
|
||||
vpack: 'center',
|
||||
vertical: true,
|
||||
className: 'spacing-v-15',
|
||||
children: [
|
||||
sessionDescription,
|
||||
...sessionButtonRows,
|
||||
]
|
||||
})
|
||||
]
|
||||
})
|
||||
],
|
||||
setup: (self) => self
|
||||
.hook(App, (_b, name, visible) => {
|
||||
if (visible) lockButton.grab_focus(); // Lock is the default option
|
||||
})
|
||||
,
|
||||
});
|
||||
}
|
||||
365
homes/me/ags-end4/modules/sideleft/apis/ai_chatmessage.js
Normal file
365
homes/me/ags-end4/modules/sideleft/apis/ai_chatmessage.js
Normal file
|
|
@ -0,0 +1,365 @@
|
|||
const { Gdk, Gio, GLib, Gtk } = imports.gi;
|
||||
import GtkSource from "gi://GtkSource?version=3.0";
|
||||
import App from 'resource:///com/github/Aylur/ags/app.js';
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
const { Box, Button, Label, Icon, Scrollable, Stack } = Widget;
|
||||
const { execAsync, exec } = Utils;
|
||||
import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
|
||||
import md2pango from '../../.miscutils/md2pango.js';
|
||||
import { darkMode } from "../../.miscutils/system.js";
|
||||
|
||||
const LATEX_DIR = `${GLib.get_user_cache_dir()}/ags/media/latex`;
|
||||
const CUSTOM_SOURCEVIEW_SCHEME_PATH = `${App.configDir}/assets/themes/sourceviewtheme${darkMode.value ? '' : '-light'}.xml`;
|
||||
const CUSTOM_SCHEME_ID = `custom${darkMode.value ? '' : '-light'}`;
|
||||
const USERNAME = GLib.get_user_name();
|
||||
|
||||
/////////////////////// Custom source view colorscheme /////////////////////////
|
||||
|
||||
function loadCustomColorScheme(filePath) {
|
||||
// Read the XML file content
|
||||
const file = Gio.File.new_for_path(filePath);
|
||||
const [success, contents] = file.load_contents(null);
|
||||
|
||||
if (!success) {
|
||||
logError('Failed to load the XML file.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse the XML content and set the Style Scheme
|
||||
const schemeManager = GtkSource.StyleSchemeManager.get_default();
|
||||
schemeManager.append_search_path(file.get_parent().get_path());
|
||||
}
|
||||
loadCustomColorScheme(CUSTOM_SOURCEVIEW_SCHEME_PATH);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
function substituteLang(str) {
|
||||
const subs = [
|
||||
{ from: 'javascript', to: 'js' },
|
||||
{ from: 'bash', to: 'sh' },
|
||||
];
|
||||
for (const { from, to } of subs) {
|
||||
if (from === str) return to;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
const HighlightedCode = (content, lang) => {
|
||||
const buffer = new GtkSource.Buffer();
|
||||
const sourceView = new GtkSource.View({
|
||||
buffer: buffer,
|
||||
wrap_mode: Gtk.WrapMode.NONE
|
||||
});
|
||||
const langManager = GtkSource.LanguageManager.get_default();
|
||||
let displayLang = langManager.get_language(substituteLang(lang)); // Set your preferred language
|
||||
if (displayLang) {
|
||||
buffer.set_language(displayLang);
|
||||
}
|
||||
const schemeManager = GtkSource.StyleSchemeManager.get_default();
|
||||
buffer.set_style_scheme(schemeManager.get_scheme(CUSTOM_SCHEME_ID));
|
||||
buffer.set_text(content, -1);
|
||||
return sourceView;
|
||||
}
|
||||
|
||||
const TextBlock = (content = '') => Label({
|
||||
hpack: 'fill',
|
||||
className: 'txt sidebar-chat-txtblock sidebar-chat-txt',
|
||||
useMarkup: true,
|
||||
xalign: 0,
|
||||
wrap: true,
|
||||
selectable: true,
|
||||
label: content,
|
||||
});
|
||||
|
||||
Utils.execAsync(['bash', '-c', `rm -rf ${LATEX_DIR}`])
|
||||
.then(() => Utils.execAsync(['bash', '-c', `mkdir -p ${LATEX_DIR}`]))
|
||||
.catch(print);
|
||||
const Latex = (content = '') => {
|
||||
const latexViewArea = Box({
|
||||
// vscroll: 'never',
|
||||
// hscroll: 'automatic',
|
||||
// homogeneous: true,
|
||||
attribute: {
|
||||
render: async (self, text) => {
|
||||
if (text.length == 0) return;
|
||||
const styleContext = self.get_style_context();
|
||||
const fontSize = styleContext.get_property('font-size', Gtk.StateFlags.NORMAL);
|
||||
|
||||
const timeSinceEpoch = Date.now();
|
||||
const fileName = `${timeSinceEpoch}.tex`;
|
||||
const outFileName = `${timeSinceEpoch}-symbolic.svg`;
|
||||
const outIconName = `${timeSinceEpoch}-symbolic`;
|
||||
const scriptFileName = `${timeSinceEpoch}-render.sh`;
|
||||
const filePath = `${LATEX_DIR}/${fileName}`;
|
||||
const outFilePath = `${LATEX_DIR}/${outFileName}`;
|
||||
const scriptFilePath = `${LATEX_DIR}/${scriptFileName}`;
|
||||
|
||||
Utils.writeFile(text, filePath).catch(print);
|
||||
// Since MicroTex doesn't support file path input properly, we gotta cat it
|
||||
// And escaping such a command is a fucking pain so I decided to just generate a script
|
||||
// Note: MicroTex doesn't support `&=`
|
||||
// You can add this line in the middle for debugging: echo "$text" > ${filePath}.tmp
|
||||
const renderScript = `#!/usr/bin/env bash
|
||||
text=$(cat ${filePath} | sed 's/$/ \\\\\\\\/g' | sed 's/&=/=/g')
|
||||
cd /opt/MicroTeX
|
||||
./LaTeX -headless -input="$text" -output=${outFilePath} -textsize=${fontSize * 1.1} -padding=0 -maxwidth=${latexViewArea.get_allocated_width() * 0.85} > /dev/null 2>&1
|
||||
sed -i 's/fill="rgb(0%, 0%, 0%)"/style="fill:#000000"/g' ${outFilePath}
|
||||
sed -i 's/stroke="rgb(0%, 0%, 0%)"/stroke="${darkMode.value ? '#ffffff' : '#000000'}"/g' ${outFilePath}
|
||||
`;
|
||||
Utils.writeFile(renderScript, scriptFilePath).catch(print);
|
||||
Utils.exec(`chmod a+x ${scriptFilePath}`)
|
||||
Utils.timeout(100, () => {
|
||||
Utils.exec(`bash ${scriptFilePath}`);
|
||||
Gtk.IconTheme.get_default().append_search_path(LATEX_DIR);
|
||||
|
||||
self.child?.destroy();
|
||||
self.child = Gtk.Image.new_from_icon_name(outIconName, 0);
|
||||
})
|
||||
}
|
||||
},
|
||||
setup: (self) => self.attribute.render(self, content).catch(print),
|
||||
});
|
||||
const wholeThing = Box({
|
||||
className: 'sidebar-chat-latex',
|
||||
homogeneous: true,
|
||||
attribute: {
|
||||
'updateText': (text) => {
|
||||
latexViewArea.attribute.render(latexViewArea, text).catch(print);
|
||||
}
|
||||
},
|
||||
children: [Scrollable({
|
||||
vscroll: 'never',
|
||||
hscroll: 'automatic',
|
||||
child: latexViewArea
|
||||
})]
|
||||
})
|
||||
return wholeThing;
|
||||
}
|
||||
|
||||
const CodeBlock = (content = '', lang = 'txt') => {
|
||||
if (lang == 'tex' || lang == 'latex') {
|
||||
return Latex(content);
|
||||
}
|
||||
const topBar = Box({
|
||||
className: 'sidebar-chat-codeblock-topbar',
|
||||
children: [
|
||||
Label({
|
||||
label: lang,
|
||||
className: 'sidebar-chat-codeblock-topbar-txt',
|
||||
}),
|
||||
Box({
|
||||
hexpand: true,
|
||||
}),
|
||||
Button({
|
||||
className: 'sidebar-chat-codeblock-topbar-btn',
|
||||
child: Box({
|
||||
className: 'spacing-h-5',
|
||||
children: [
|
||||
MaterialIcon('content_copy', 'small'),
|
||||
Label({
|
||||
label: 'Copy',
|
||||
})
|
||||
]
|
||||
}),
|
||||
onClicked: (self) => {
|
||||
const buffer = sourceView.get_buffer();
|
||||
const copyContent = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter(), false); // TODO: fix this
|
||||
execAsync([`wl-copy`, `${copyContent}`]).catch(print);
|
||||
},
|
||||
}),
|
||||
]
|
||||
})
|
||||
// Source view
|
||||
const sourceView = HighlightedCode(content, lang);
|
||||
|
||||
const codeBlock = Box({
|
||||
attribute: {
|
||||
'updateText': (text) => {
|
||||
sourceView.get_buffer().set_text(text, -1);
|
||||
}
|
||||
},
|
||||
className: 'sidebar-chat-codeblock',
|
||||
vertical: true,
|
||||
children: [
|
||||
topBar,
|
||||
Box({
|
||||
className: 'sidebar-chat-codeblock-code',
|
||||
homogeneous: true,
|
||||
children: [Scrollable({
|
||||
vscroll: 'never',
|
||||
hscroll: 'automatic',
|
||||
child: sourceView,
|
||||
})],
|
||||
})
|
||||
]
|
||||
})
|
||||
|
||||
// const schemeIds = styleManager.get_scheme_ids();
|
||||
|
||||
// print("Available Style Schemes:");
|
||||
// for (let i = 0; i < schemeIds.length; i++) {
|
||||
// print(schemeIds[i]);
|
||||
// }
|
||||
return codeBlock;
|
||||
}
|
||||
|
||||
const Divider = () => Box({
|
||||
className: 'sidebar-chat-divider',
|
||||
})
|
||||
|
||||
const MessageContent = (content) => {
|
||||
const contentBox = Box({
|
||||
vertical: true,
|
||||
attribute: {
|
||||
'fullUpdate': (self, content, useCursor = false) => {
|
||||
// Clear and add first text widget
|
||||
const children = contentBox.get_children();
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i];
|
||||
child.destroy();
|
||||
}
|
||||
contentBox.add(TextBlock())
|
||||
// Loop lines. Put normal text in markdown parser
|
||||
// and put code into code highlighter (TODO)
|
||||
let lines = content.split('\n');
|
||||
let lastProcessed = 0;
|
||||
let inCode = false;
|
||||
for (const [index, line] of lines.entries()) {
|
||||
// Code blocks
|
||||
const codeBlockRegex = /^\s*```([a-zA-Z0-9]+)?\n?/;
|
||||
if (codeBlockRegex.test(line)) {
|
||||
const kids = self.get_children();
|
||||
const lastLabel = kids[kids.length - 1];
|
||||
const blockContent = lines.slice(lastProcessed, index).join('\n');
|
||||
if (!inCode) {
|
||||
lastLabel.label = md2pango(blockContent);
|
||||
contentBox.add(CodeBlock('', codeBlockRegex.exec(line)[1]));
|
||||
}
|
||||
else {
|
||||
lastLabel.attribute.updateText(blockContent);
|
||||
contentBox.add(TextBlock());
|
||||
}
|
||||
|
||||
lastProcessed = index + 1;
|
||||
inCode = !inCode;
|
||||
}
|
||||
// Breaks
|
||||
const dividerRegex = /^\s*---/;
|
||||
if (!inCode && dividerRegex.test(line)) {
|
||||
const kids = self.get_children();
|
||||
const lastLabel = kids[kids.length - 1];
|
||||
const blockContent = lines.slice(lastProcessed, index).join('\n');
|
||||
lastLabel.label = md2pango(blockContent);
|
||||
contentBox.add(Divider());
|
||||
contentBox.add(TextBlock());
|
||||
lastProcessed = index + 1;
|
||||
}
|
||||
}
|
||||
if (lastProcessed < lines.length) {
|
||||
const kids = self.get_children();
|
||||
const lastLabel = kids[kids.length - 1];
|
||||
let blockContent = lines.slice(lastProcessed, lines.length).join('\n');
|
||||
if (!inCode)
|
||||
lastLabel.label = `${md2pango(blockContent)}${useCursor ? userOptions.ai.writingCursor : ''}`;
|
||||
else
|
||||
lastLabel.attribute.updateText(blockContent);
|
||||
}
|
||||
// Debug: plain text
|
||||
// contentBox.add(Label({
|
||||
// hpack: 'fill',
|
||||
// className: 'txt sidebar-chat-txtblock sidebar-chat-txt',
|
||||
// useMarkup: false,
|
||||
// xalign: 0,
|
||||
// wrap: true,
|
||||
// selectable: true,
|
||||
// label: '------------------------------\n' + md2pango(content),
|
||||
// }))
|
||||
contentBox.show_all();
|
||||
}
|
||||
}
|
||||
});
|
||||
contentBox.attribute.fullUpdate(contentBox, content, false);
|
||||
return contentBox;
|
||||
}
|
||||
|
||||
export const ChatMessage = (message, modelName = 'Model') => {
|
||||
const TextSkeleton = (extraClassName = '') => Box({
|
||||
className: `sidebar-chat-message-skeletonline ${extraClassName}`,
|
||||
})
|
||||
const messageContentBox = MessageContent(message.content);
|
||||
const messageLoadingSkeleton = Box({
|
||||
vertical: true,
|
||||
className: 'spacing-v-5',
|
||||
children: Array.from({ length: 3 }, (_, id) => TextSkeleton(`sidebar-chat-message-skeletonline-offset${id}`)),
|
||||
})
|
||||
const messageArea = Stack({
|
||||
homogeneous: message.role !== 'user',
|
||||
transition: 'crossfade',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
children: {
|
||||
'thinking': messageLoadingSkeleton,
|
||||
'message': messageContentBox,
|
||||
},
|
||||
shown: message.thinking ? 'thinking' : 'message',
|
||||
});
|
||||
const thisMessage = Box({
|
||||
className: 'sidebar-chat-message',
|
||||
homogeneous: true,
|
||||
children: [
|
||||
Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
Label({
|
||||
hpack: 'start',
|
||||
xalign: 0,
|
||||
className: `txt txt-bold sidebar-chat-name sidebar-chat-name-${message.role == 'user' ? 'user' : 'bot'}`,
|
||||
wrap: true,
|
||||
useMarkup: true,
|
||||
label: (message.role == 'user' ? USERNAME : modelName),
|
||||
}),
|
||||
Box({
|
||||
homogeneous: true,
|
||||
className: 'sidebar-chat-messagearea',
|
||||
children: [messageArea]
|
||||
})
|
||||
],
|
||||
setup: (self) => self
|
||||
.hook(message, (self, isThinking) => {
|
||||
messageArea.shown = message.thinking ? 'thinking' : 'message';
|
||||
}, 'notify::thinking')
|
||||
.hook(message, (self) => { // Message update
|
||||
messageContentBox.attribute.fullUpdate(messageContentBox, message.content, message.role != 'user');
|
||||
}, 'notify::content')
|
||||
.hook(message, (label, isDone) => { // Remove the cursor
|
||||
messageContentBox.attribute.fullUpdate(messageContentBox, message.content, false);
|
||||
}, 'notify::done')
|
||||
,
|
||||
})
|
||||
]
|
||||
});
|
||||
return thisMessage;
|
||||
}
|
||||
|
||||
export const SystemMessage = (content, commandName, scrolledWindow) => {
|
||||
const messageContentBox = MessageContent(content);
|
||||
const thisMessage = Box({
|
||||
className: 'sidebar-chat-message',
|
||||
children: [
|
||||
Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
Label({
|
||||
xalign: 0,
|
||||
hpack: 'start',
|
||||
className: 'txt txt-bold sidebar-chat-name sidebar-chat-name-system',
|
||||
wrap: true,
|
||||
label: `System • ${commandName}`,
|
||||
}),
|
||||
messageContentBox,
|
||||
],
|
||||
})
|
||||
],
|
||||
});
|
||||
return thisMessage;
|
||||
}
|
||||
518
homes/me/ags-end4/modules/sideleft/apis/booru.js
Normal file
518
homes/me/ags-end4/modules/sideleft/apis/booru.js
Normal file
|
|
@ -0,0 +1,518 @@
|
|||
const { Gdk, GdkPixbuf, Gio, GLib, Gtk } = imports.gi;
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
const { Box, Button, EventBox, Label, Overlay, Revealer, Scrollable, Stack } = Widget;
|
||||
const { execAsync, exec } = Utils;
|
||||
import { fileExists } from '../../.miscutils/files.js';
|
||||
import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
|
||||
import { MarginRevealer } from '../../.widgethacks/advancedrevealers.js';
|
||||
import { setupCursorHover, setupCursorHoverInfo } from '../../.widgetutils/cursorhover.js';
|
||||
import BooruService from '../../../services/booru.js';
|
||||
import { chatEntry } from '../apiwidgets.js';
|
||||
import { ConfigToggle } from '../../.commonwidgets/configwidgets.js';
|
||||
import { SystemMessage } from './ai_chatmessage.js';
|
||||
|
||||
const IMAGE_REVEAL_DELAY = 13; // Some wait for inits n other weird stuff
|
||||
const USER_CACHE_DIR = GLib.get_user_cache_dir();
|
||||
|
||||
// Create cache folder and clear pics from previous session
|
||||
Utils.exec(`bash -c 'mkdir -p ${USER_CACHE_DIR}/ags/media/waifus'`);
|
||||
Utils.exec(`bash -c 'rm ${USER_CACHE_DIR}/ags/media/waifus/*'`);
|
||||
|
||||
const TagButton = (command) => Button({
|
||||
className: 'sidebar-chat-chip sidebar-chat-chip-action txt txt-small',
|
||||
onClicked: () => { chatEntry.buffer.text += `${command} ` },
|
||||
setup: setupCursorHover,
|
||||
label: command,
|
||||
});
|
||||
|
||||
const CommandButton = (command, displayName = command) => Button({
|
||||
className: 'sidebar-chat-chip sidebar-chat-chip-action txt txt-small',
|
||||
onClicked: () => sendMessage(command),
|
||||
setup: setupCursorHover,
|
||||
label: displayName,
|
||||
});
|
||||
|
||||
export const booruTabIcon = Box({
|
||||
hpack: 'center',
|
||||
homogeneous: true,
|
||||
children: [
|
||||
MaterialIcon('gallery_thumbnail', 'norm'),
|
||||
]
|
||||
});
|
||||
|
||||
const BooruInfo = () => {
|
||||
const booruLogo = Label({
|
||||
hpack: 'center',
|
||||
className: 'sidebar-chat-welcome-logo',
|
||||
label: 'gallery_thumbnail',
|
||||
})
|
||||
return Box({
|
||||
vertical: true,
|
||||
vexpand: true,
|
||||
className: 'spacing-v-15',
|
||||
children: [
|
||||
booruLogo,
|
||||
Label({
|
||||
className: 'txt txt-title-small sidebar-chat-welcome-txt',
|
||||
wrap: true,
|
||||
justify: Gtk.Justification.CENTER,
|
||||
label: 'Anime booru',
|
||||
}),
|
||||
Box({
|
||||
className: 'spacing-h-5',
|
||||
hpack: 'center',
|
||||
children: [
|
||||
Label({
|
||||
className: 'txt-smallie txt-subtext',
|
||||
wrap: true,
|
||||
justify: Gtk.Justification.CENTER,
|
||||
label: getString('Powered by yande.re and konachan'),
|
||||
}),
|
||||
Button({
|
||||
className: 'txt-subtext txt-norm icon-material',
|
||||
label: 'info',
|
||||
tooltipText: getString('An image booru. May contain NSFW content.\nWatch your back.\n\nDisclaimer: Not affiliated with the provider\nnor responsible for any of its content.'),
|
||||
setup: setupCursorHoverInfo,
|
||||
}),
|
||||
]
|
||||
}),
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
export const BooruSettings = () => MarginRevealer({
|
||||
transition: 'slide_down',
|
||||
revealChild: true,
|
||||
child: Box({
|
||||
vertical: true,
|
||||
className: 'sidebar-chat-settings',
|
||||
children: [
|
||||
Box({
|
||||
vertical: true,
|
||||
hpack: 'fill',
|
||||
className: 'sidebar-chat-settings-toggles',
|
||||
children: [
|
||||
ConfigToggle({
|
||||
icon: 'menstrual_health',
|
||||
name: getString('Lewds'),
|
||||
desc: getString("Shows naughty stuff when enabled.\nYa like those? Add this to user_options.js:\n\t'sidebar': {\n\t'image': {\n\t\t'allowNsfw': true,\n\t}\n}"),
|
||||
initValue: BooruService.nsfw,
|
||||
onChange: (self, newValue) => {
|
||||
BooruService.nsfw = newValue;
|
||||
},
|
||||
extraSetup: (self) => self.hook(BooruService, (self) => {
|
||||
self.attribute.enabled.value = BooruService.nsfw;
|
||||
}, 'notify::nsfw')
|
||||
}),
|
||||
ConfigToggle({
|
||||
icon: 'sell',
|
||||
name: getString('Save in folder by tags'),
|
||||
desc: getString('Saves images in folders by their tags'),
|
||||
initValue: userOptions.sidebar.image.saveInFolderByTags,
|
||||
onChange: (self, newValue) => {
|
||||
userOptions.sidebar.image.saveInFolderByTags = newValue;
|
||||
},
|
||||
}),
|
||||
]
|
||||
})
|
||||
]
|
||||
})
|
||||
});
|
||||
|
||||
const booruWelcome = Box({
|
||||
vexpand: true,
|
||||
homogeneous: true,
|
||||
child: Box({
|
||||
className: 'spacing-v-15',
|
||||
vpack: 'center',
|
||||
vertical: true,
|
||||
children: [
|
||||
BooruInfo(),
|
||||
BooruSettings(),
|
||||
]
|
||||
})
|
||||
});
|
||||
|
||||
const BooruPage = (taglist, serviceName = 'Booru') => {
|
||||
const PageState = (icon, name) => Box({
|
||||
className: 'spacing-h-5 txt',
|
||||
children: [
|
||||
Label({
|
||||
className: 'sidebar-waifu-txt txt-smallie',
|
||||
xalign: 0,
|
||||
label: name,
|
||||
}),
|
||||
MaterialIcon(icon, 'norm'),
|
||||
]
|
||||
})
|
||||
const ImageAction = ({ name, icon, action }) => Button({
|
||||
className: 'sidebar-waifu-image-action txt-norm icon-material',
|
||||
tooltipText: name,
|
||||
label: icon,
|
||||
onClicked: action,
|
||||
setup: setupCursorHover,
|
||||
})
|
||||
const PreviewImage = (data, delay = 0) => {
|
||||
const imageArea = Widget.DrawingArea({
|
||||
className: 'sidebar-booru-image-drawingarea',
|
||||
});
|
||||
const imageBox = Box({
|
||||
className: 'sidebar-booru-image',
|
||||
// css: `background-image: url('${data.preview_url}');`,
|
||||
attribute: {
|
||||
'update': (self, data, force = false) => {
|
||||
const imagePath = `${USER_CACHE_DIR}/ags/media/waifus/${data.md5}.${data.file_ext}`;
|
||||
const widgetStyleContext = imageArea.get_style_context();
|
||||
const widgetWidth = widgetStyleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
|
||||
const widgetHeight = widgetWidth / data.aspect_ratio;
|
||||
imageArea.set_size_request(widgetWidth, widgetHeight);
|
||||
const showImage = () => {
|
||||
// const pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(imagePath, widgetWidth, widgetHeight);
|
||||
const pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(imagePath, widgetWidth, widgetHeight, false);
|
||||
imageArea.connect("draw", (widget, cr) => {
|
||||
const borderRadius = widget.get_style_context().get_property('border-radius', Gtk.StateFlags.NORMAL);
|
||||
|
||||
// Draw a rounded rectangle
|
||||
cr.arc(borderRadius, borderRadius, borderRadius, Math.PI, 1.5 * Math.PI);
|
||||
cr.arc(widgetWidth - borderRadius, borderRadius, borderRadius, 1.5 * Math.PI, 2 * Math.PI);
|
||||
cr.arc(widgetWidth - borderRadius, widgetHeight - borderRadius, borderRadius, 0, 0.5 * Math.PI);
|
||||
cr.arc(borderRadius, widgetHeight - borderRadius, borderRadius, 0.5 * Math.PI, Math.PI);
|
||||
cr.closePath();
|
||||
cr.clip();
|
||||
|
||||
// Paint image as bg
|
||||
Gdk.cairo_set_source_pixbuf(cr, pixbuf, (widgetWidth - widgetWidth) / 2, (widgetHeight - widgetHeight) / 2);
|
||||
cr.paint();
|
||||
});
|
||||
self.queue_draw();
|
||||
imageRevealer.revealChild = true;
|
||||
}
|
||||
// Show
|
||||
// const downloadCommand = `wget -O '${imagePath}' '${data.preview_url}'`;
|
||||
const downloadCommand = `curl -L -o '${imagePath}' '${data.preview_url}'`;
|
||||
if (!force && fileExists(imagePath)) showImage();
|
||||
else Utils.timeout(delay, () => Utils.execAsync(['bash', '-c', downloadCommand])
|
||||
.then(showImage)
|
||||
.catch(print)
|
||||
);
|
||||
},
|
||||
},
|
||||
child: imageArea,
|
||||
setup: (self) => {
|
||||
Utils.timeout(1000, () => self.attribute.update(self, data));
|
||||
}
|
||||
});
|
||||
const imageActions = Revealer({
|
||||
transition: 'crossfade',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
child: Box({
|
||||
vpack: 'start',
|
||||
className: 'sidebar-booru-image-actions spacing-h-3',
|
||||
children: [
|
||||
Box({ hexpand: true }),
|
||||
ImageAction({
|
||||
name: getString('Go to file url'),
|
||||
icon: 'file_open',
|
||||
action: () => execAsync(['xdg-open', `${data.file_url}`]).catch(print),
|
||||
}),
|
||||
ImageAction({
|
||||
name: getString('Go to source'),
|
||||
icon: 'open_in_new',
|
||||
action: () => execAsync(['xdg-open', `${data.source}`]).catch(print),
|
||||
}),
|
||||
ImageAction({
|
||||
name: getString('Save image'),
|
||||
icon: 'save',
|
||||
action: (self) => {
|
||||
const currentTags = BooruService.queries.at(-1).realTagList.filter(tag => !tag.includes('rating:'));
|
||||
const tagDirectory = currentTags.join('+');
|
||||
let fileExtension = data.file_ext || 'jpg';
|
||||
const saveCommand = `mkdir -p $(xdg-user-dir PICTURES)/homework/${data.is_nsfw ? '🌶️/' : ''}${userOptions.sidebar.image.saveInFolderByTags ? tagDirectory : ''} && curl -L -o $(xdg-user-dir PICTURES)/homework/${data.is_nsfw ? '🌶️/' : ''}${userOptions.sidebar.image.saveInFolderByTags ? (tagDirectory + '/') : ''}${data.md5}.${fileExtension} '${data.file_url}'`;
|
||||
execAsync(['bash', '-c', saveCommand])
|
||||
.then(() => self.label = 'done')
|
||||
.catch(print);
|
||||
},
|
||||
}),
|
||||
]
|
||||
})
|
||||
});
|
||||
const imageOverlay = Overlay({
|
||||
passThrough: true,
|
||||
child: imageBox,
|
||||
overlays: [imageActions]
|
||||
});
|
||||
const imageRevealer = Revealer({
|
||||
transition: 'slide_down',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
child: EventBox({
|
||||
onHover: () => { imageActions.revealChild = true },
|
||||
onHoverLost: () => { imageActions.revealChild = false },
|
||||
child: imageOverlay,
|
||||
})
|
||||
})
|
||||
return imageRevealer;
|
||||
}
|
||||
const downloadState = Stack({
|
||||
homogeneous: false,
|
||||
transition: 'slide_up_down',
|
||||
transitionDuration: userOptions.animations.durationSmall,
|
||||
children: {
|
||||
'api': PageState('api', getString('Calling API')),
|
||||
'download': PageState('downloading', getString('Downloading image')),
|
||||
'done': PageState('done', getString('Finished!')),
|
||||
'error': PageState('error', getString('Error')),
|
||||
},
|
||||
});
|
||||
const downloadIndicator = MarginRevealer({
|
||||
vpack: 'center',
|
||||
transition: 'slide_left',
|
||||
revealChild: true,
|
||||
child: downloadState,
|
||||
});
|
||||
const pageHeading = Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
Box({
|
||||
children: [
|
||||
Label({
|
||||
hpack: 'start',
|
||||
className: `sidebar-booru-provider`,
|
||||
label: `${serviceName}`,
|
||||
truncate: 'end',
|
||||
maxWidthChars: 20,
|
||||
}),
|
||||
Box({ hexpand: true }),
|
||||
downloadIndicator,
|
||||
]
|
||||
}),
|
||||
Box({
|
||||
children: [
|
||||
Scrollable({
|
||||
hexpand: true,
|
||||
vscroll: 'never',
|
||||
hscroll: 'automatic',
|
||||
child: Box({
|
||||
hpack: 'fill',
|
||||
className: 'spacing-h-5',
|
||||
children: [
|
||||
...taglist.map((tag) => TagButton(tag)),
|
||||
Box({ hexpand: true }),
|
||||
]
|
||||
})
|
||||
}),
|
||||
]
|
||||
})
|
||||
]
|
||||
});
|
||||
const pageImages = Box({
|
||||
hpack: 'start',
|
||||
homogeneous: true,
|
||||
className: 'sidebar-booru-imagegrid',
|
||||
})
|
||||
const pageImageRevealer = Revealer({
|
||||
transition: 'slide_down',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
revealChild: false,
|
||||
child: pageImages,
|
||||
});
|
||||
const thisPage = Box({
|
||||
homogeneous: true,
|
||||
className: 'sidebar-chat-message',
|
||||
attribute: {
|
||||
'imagePath': '',
|
||||
'isNsfw': false,
|
||||
'update': (data, force = false) => { // TODO: Use columns. Sort min to max h/w ratio then greedily put em in...
|
||||
// Sort by .aspect_ratio
|
||||
data = data.sort(
|
||||
(a, b) => a.aspect_ratio - b.aspect_ratio
|
||||
);
|
||||
if (data.length == 0) {
|
||||
downloadState.shown = 'error';
|
||||
return;
|
||||
}
|
||||
const imageColumns = userOptions.sidebar.image.columns;
|
||||
const imageRows = data.length / imageColumns;
|
||||
|
||||
// Init cols
|
||||
pageImages.children = Array.from(
|
||||
{ length: imageColumns },
|
||||
(_, i) => Box({
|
||||
attribute: { height: 0 },
|
||||
vertical: true,
|
||||
})
|
||||
);
|
||||
// Greedy add O(n^2) 😭
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
// Find column with lowest length
|
||||
let minHeight = Infinity;
|
||||
let minIndex = -1;
|
||||
for (let j = 0; j < imageColumns; j++) {
|
||||
const height = pageImages.children[j].attribute.height;
|
||||
if (height < minHeight) {
|
||||
minHeight = height;
|
||||
minIndex = j;
|
||||
}
|
||||
}
|
||||
// Add image to it
|
||||
pageImages.children[minIndex].pack_start(PreviewImage(data[i], minIndex), false, false, 0)
|
||||
pageImages.children[minIndex].attribute.height += 1 / data[i].aspect_ratio; // we want height/width
|
||||
}
|
||||
pageImages.show_all();
|
||||
|
||||
// Reveal stuff
|
||||
Utils.timeout(IMAGE_REVEAL_DELAY,
|
||||
() => pageImageRevealer.revealChild = true
|
||||
);
|
||||
downloadIndicator.attribute.hide();
|
||||
},
|
||||
},
|
||||
children: [Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
pageHeading,
|
||||
Box({
|
||||
vertical: true,
|
||||
children: [pageImageRevealer],
|
||||
})
|
||||
]
|
||||
})],
|
||||
});
|
||||
return thisPage;
|
||||
}
|
||||
|
||||
const booruContent = Box({
|
||||
className: 'spacing-v-15',
|
||||
vertical: true,
|
||||
attribute: {
|
||||
'map': new Map(),
|
||||
},
|
||||
setup: (self) => self
|
||||
.hook(BooruService, (box, id) => {
|
||||
if (id === undefined) return;
|
||||
const newPage = BooruPage(BooruService.queries[id].taglist, BooruService.queries[id].providerName);
|
||||
box.add(newPage);
|
||||
box.show_all();
|
||||
box.attribute.map.set(id, newPage);
|
||||
}, 'newResponse')
|
||||
.hook(BooruService, (box, id) => {
|
||||
if (id === undefined) return;
|
||||
if (!BooruService.responses[id]) return;
|
||||
box.attribute.map.get(id)?.attribute.update(BooruService.responses[id]);
|
||||
}, 'updateResponse')
|
||||
,
|
||||
});
|
||||
|
||||
export const booruView = Scrollable({
|
||||
className: 'sidebar-chat-viewport',
|
||||
vexpand: true,
|
||||
child: Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
booruWelcome,
|
||||
booruContent,
|
||||
]
|
||||
}),
|
||||
setup: (scrolledWindow) => {
|
||||
// Show scrollbar
|
||||
scrolledWindow.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
|
||||
const vScrollbar = scrolledWindow.get_vscrollbar();
|
||||
vScrollbar.get_style_context().add_class('sidebar-scrollbar');
|
||||
// Avoid click-to-scroll-widget-to-view behavior
|
||||
Utils.timeout(1, () => {
|
||||
const viewport = scrolledWindow.child;
|
||||
viewport.set_focus_vadjustment(new Gtk.Adjustment(undefined));
|
||||
})
|
||||
// Scroll to bottom with new content if chat entry not focused
|
||||
const adjustment = scrolledWindow.get_vadjustment();
|
||||
adjustment.connect("changed", () => {
|
||||
if (!chatEntry.hasFocus) return;
|
||||
adjustment.set_value(adjustment.get_upper() - adjustment.get_page_size());
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
const booruTags = Revealer({
|
||||
revealChild: false,
|
||||
transition: 'crossfade',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
child: Box({
|
||||
className: 'spacing-h-5',
|
||||
children: [
|
||||
Scrollable({
|
||||
vscroll: 'never',
|
||||
hscroll: 'automatic',
|
||||
hexpand: true,
|
||||
child: Box({
|
||||
className: 'spacing-h-5',
|
||||
children: [
|
||||
TagButton('( * )'),
|
||||
TagButton('hololive'),
|
||||
]
|
||||
})
|
||||
}),
|
||||
Box({ className: 'separator-line' }),
|
||||
]
|
||||
})
|
||||
});
|
||||
|
||||
export const booruCommands = Box({
|
||||
className: 'spacing-h-5',
|
||||
setup: (self) => {
|
||||
self.pack_end(CommandButton('/clear'), false, false, 0);
|
||||
self.pack_end(CommandButton('/next'), false, false, 0);
|
||||
self.pack_start(Button({
|
||||
className: 'sidebar-chat-chip-toggle',
|
||||
setup: setupCursorHover,
|
||||
label: getString('Tags →'),
|
||||
onClicked: () => {
|
||||
booruTags.revealChild = !booruTags.revealChild;
|
||||
}
|
||||
}), false, false, 0);
|
||||
self.pack_start(booruTags, true, true, 0);
|
||||
}
|
||||
});
|
||||
|
||||
const clearChat = () => { // destroy!!
|
||||
booruContent.attribute.map.forEach((value, key, map) => {
|
||||
value.destroy();
|
||||
value = null;
|
||||
});
|
||||
}
|
||||
|
||||
export const sendMessage = (text) => {
|
||||
// Commands
|
||||
if (text.startsWith('+')) { // Next page
|
||||
const lastQuery = BooruService.queries.at(-1);
|
||||
BooruService.fetch(`${lastQuery.realTagList.join(' ')} ${lastQuery.page + 1}`)
|
||||
}
|
||||
else if (text.startsWith('/')) {
|
||||
if (text.startsWith('/clear')) clearChat();
|
||||
else if (text.startsWith('/safe')) {
|
||||
BooruService.nsfw = false;
|
||||
const message = SystemMessage(`Switched to safe mode`, '/safe', booruView)
|
||||
booruContent.add(message);
|
||||
booruContent.show_all();
|
||||
booruContent.attribute.map.set(Date.now(), message);
|
||||
}
|
||||
else if (text.startsWith('/lewd')) {
|
||||
BooruService.nsfw = true;
|
||||
const message = SystemMessage(`Tiddies enabled`, '/lewd', booruView)
|
||||
booruContent.add(message);
|
||||
booruContent.show_all();
|
||||
booruContent.attribute.map.set(Date.now(), message);
|
||||
}
|
||||
else if (text.startsWith('/mode')) {
|
||||
const mode = text.slice(text.indexOf(' ') + 1);
|
||||
BooruService.mode = mode;
|
||||
const message = SystemMessage(`Changed provider to ${BooruService.providerName}`, '/mode', booruView)
|
||||
booruContent.add(message);
|
||||
booruContent.show_all();
|
||||
booruContent.attribute.map.set(Date.now(), message);
|
||||
}
|
||||
else if (text.startsWith('/next')) {
|
||||
sendMessage('+')
|
||||
}
|
||||
}
|
||||
else BooruService.fetch(text);
|
||||
}
|
||||
355
homes/me/ags-end4/modules/sideleft/apis/chatgpt.js
Normal file
355
homes/me/ags-end4/modules/sideleft/apis/chatgpt.js
Normal file
|
|
@ -0,0 +1,355 @@
|
|||
const { Gtk } = imports.gi;
|
||||
import App from 'resource:///com/github/Aylur/ags/app.js';
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
|
||||
const { Box, Button, Icon, Label, Revealer, Scrollable } = Widget;
|
||||
import GPTService from '../../../services/gpt.js';
|
||||
import { setupCursorHover, setupCursorHoverInfo } from '../../.widgetutils/cursorhover.js';
|
||||
import { SystemMessage, ChatMessage } from "./ai_chatmessage.js";
|
||||
import { ConfigToggle, ConfigSegmentedSelection, ConfigGap } from '../../.commonwidgets/configwidgets.js';
|
||||
import { markdownTest } from '../../.miscutils/md2pango.js';
|
||||
import { MarginRevealer } from '../../.widgethacks/advancedrevealers.js';
|
||||
import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
|
||||
import { chatEntry } from '../apiwidgets.js';
|
||||
|
||||
export const chatGPTTabIcon = Icon({
|
||||
hpack: 'center',
|
||||
icon: `openai-symbolic`,
|
||||
});
|
||||
|
||||
const ProviderSwitcher = () => {
|
||||
const ProviderChoice = (id, provider) => {
|
||||
const providerSelected = MaterialIcon('check', 'norm', {
|
||||
setup: (self) => self.hook(GPTService, (self) => {
|
||||
self.toggleClassName('invisible', GPTService.providerID !== id);
|
||||
}, 'providerChanged')
|
||||
});
|
||||
return Button({
|
||||
tooltipText: provider.description,
|
||||
onClicked: () => {
|
||||
GPTService.providerID = id;
|
||||
providerList.revealChild = false;
|
||||
indicatorChevron.label = 'expand_more';
|
||||
},
|
||||
child: Box({
|
||||
className: 'spacing-h-10 txt',
|
||||
children: [
|
||||
Icon({
|
||||
icon: provider['logo_name'],
|
||||
className: 'txt-large'
|
||||
}),
|
||||
Label({
|
||||
hexpand: true,
|
||||
xalign: 0,
|
||||
className: 'txt-small',
|
||||
label: provider.name,
|
||||
}),
|
||||
providerSelected
|
||||
],
|
||||
}),
|
||||
setup: setupCursorHover,
|
||||
});
|
||||
}
|
||||
const indicatorChevron = MaterialIcon('expand_more', 'norm');
|
||||
const indicatorButton = Button({
|
||||
tooltipText: getString('Select ChatGPT-compatible API provider'),
|
||||
child: Box({
|
||||
className: 'spacing-h-10 txt',
|
||||
children: [
|
||||
MaterialIcon('cloud', 'norm'),
|
||||
Label({
|
||||
hexpand: true,
|
||||
xalign: 0,
|
||||
className: 'txt-small',
|
||||
label: GPTService.providerID,
|
||||
setup: (self) => self.hook(GPTService, (self) => {
|
||||
self.label = `${GPTService.providers[GPTService.providerID]['name']}`;
|
||||
}, 'providerChanged')
|
||||
}),
|
||||
indicatorChevron,
|
||||
]
|
||||
}),
|
||||
onClicked: () => {
|
||||
providerList.revealChild = !providerList.revealChild;
|
||||
indicatorChevron.label = (providerList.revealChild ? 'expand_less' : 'expand_more');
|
||||
},
|
||||
setup: setupCursorHover,
|
||||
});
|
||||
const providerList = Revealer({
|
||||
revealChild: false,
|
||||
transition: 'slide_down',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
child: Box({
|
||||
vertical: true, className: 'spacing-v-5 sidebar-chat-providerswitcher-list',
|
||||
children: [
|
||||
Box({ className: 'separator-line margin-top-5 margin-bottom-5' }),
|
||||
Box({
|
||||
className: 'spacing-v-5',
|
||||
vertical: true,
|
||||
setup: (self) => self.hook(GPTService, (self) => {
|
||||
self.children = Object.entries(GPTService.providers)
|
||||
.map(([id, provider]) => ProviderChoice(id, provider));
|
||||
}, 'initialized'),
|
||||
})
|
||||
]
|
||||
})
|
||||
})
|
||||
return Box({
|
||||
hpack: 'center',
|
||||
vertical: true,
|
||||
className: 'sidebar-chat-providerswitcher',
|
||||
children: [
|
||||
indicatorButton,
|
||||
providerList,
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
const GPTInfo = () => {
|
||||
const openAiLogo = Icon({
|
||||
hpack: 'center',
|
||||
className: 'sidebar-chat-welcome-logo',
|
||||
icon: `openai-symbolic`,
|
||||
});
|
||||
return Box({
|
||||
vertical: true,
|
||||
className: 'spacing-v-15',
|
||||
children: [
|
||||
openAiLogo,
|
||||
Label({
|
||||
className: 'txt txt-title-small sidebar-chat-welcome-txt',
|
||||
wrap: true,
|
||||
justify: Gtk.Justification.CENTER,
|
||||
label: `Assistant (GPTs)`,
|
||||
}),
|
||||
Box({
|
||||
className: 'spacing-h-5',
|
||||
hpack: 'center',
|
||||
children: [
|
||||
Label({
|
||||
className: 'txt-smallie txt-subtext',
|
||||
wrap: true,
|
||||
justify: Gtk.Justification.CENTER,
|
||||
label: getString('Provider shown above'),
|
||||
}),
|
||||
Button({
|
||||
className: 'txt-subtext txt-norm icon-material',
|
||||
label: 'info',
|
||||
tooltipText: getString('Uses gpt-3.5-turbo.\nNot affiliated, endorsed, or sponsored by OpenAI.\n\nPrivacy: OpenAI claims they do not use your data\nwhen you use their API. Idk about others.'),
|
||||
setup: setupCursorHoverInfo,
|
||||
}),
|
||||
]
|
||||
}),
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
const GPTSettings = () => MarginRevealer({
|
||||
transition: 'slide_down',
|
||||
revealChild: true,
|
||||
extraSetup: (self) => self
|
||||
.hook(GPTService, (self) => Utils.timeout(200, () => {
|
||||
self.attribute.hide();
|
||||
}), 'newMsg')
|
||||
.hook(GPTService, (self) => Utils.timeout(200, () => {
|
||||
self.attribute.show();
|
||||
}), 'clear')
|
||||
,
|
||||
child: Box({
|
||||
vertical: true,
|
||||
className: 'sidebar-chat-settings',
|
||||
children: [
|
||||
ConfigSegmentedSelection({
|
||||
hpack: 'center',
|
||||
icon: 'casino',
|
||||
name: 'Randomness',
|
||||
desc: getString('The model\'s temperature value.\n Precise = 0\n Balanced = 0.5\n Creative = 1'),
|
||||
options: [
|
||||
{ value: 0.00, name: getString('Precise'), },
|
||||
{ value: 0.50, name: getString('Balanced'), },
|
||||
{ value: 1.00, name: getString('Creative'), },
|
||||
],
|
||||
initIndex: 2,
|
||||
onChange: (value, name) => {
|
||||
GPTService.temperature = value;
|
||||
},
|
||||
}),
|
||||
ConfigGap({ vertical: true, size: 10 }), // Note: size can only be 5, 10, or 15
|
||||
Box({
|
||||
vertical: true,
|
||||
hpack: 'fill',
|
||||
className: 'sidebar-chat-settings-toggles',
|
||||
children: [
|
||||
ConfigToggle({
|
||||
icon: 'model_training',
|
||||
name: getString('Enhancements'),
|
||||
desc: getString('Tells the model:\n- It\'s a Linux sidebar assistant\n- Be brief and use bullet points'),
|
||||
initValue: GPTService.assistantPrompt,
|
||||
onChange: (self, newValue) => {
|
||||
GPTService.assistantPrompt = newValue;
|
||||
},
|
||||
}),
|
||||
]
|
||||
})
|
||||
]
|
||||
})
|
||||
});
|
||||
|
||||
export const OpenaiApiKeyInstructions = () => Box({
|
||||
homogeneous: true,
|
||||
children: [Revealer({
|
||||
transition: 'slide_down',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
setup: (self) => self
|
||||
.hook(GPTService, (self, hasKey) => {
|
||||
self.revealChild = (GPTService.key.length == 0);
|
||||
}, 'hasKey')
|
||||
,
|
||||
child: Button({
|
||||
child: Label({
|
||||
useMarkup: true,
|
||||
wrap: true,
|
||||
className: 'txt sidebar-chat-welcome-txt',
|
||||
justify: Gtk.Justification.CENTER,
|
||||
label: getString('An API key is required\nYou can grab one <u>here</u>, then enter it below')
|
||||
}),
|
||||
setup: setupCursorHover,
|
||||
onClicked: () => {
|
||||
Utils.execAsync(['bash', '-c', `xdg-open ${GPTService.getKeyUrl}`]);
|
||||
}
|
||||
})
|
||||
})]
|
||||
});
|
||||
|
||||
const GPTWelcome = () => Box({
|
||||
vexpand: true,
|
||||
homogeneous: true,
|
||||
child: Box({
|
||||
className: 'spacing-v-15',
|
||||
vpack: 'center',
|
||||
vertical: true,
|
||||
children: [
|
||||
GPTInfo(),
|
||||
OpenaiApiKeyInstructions(),
|
||||
GPTSettings(),
|
||||
]
|
||||
})
|
||||
});
|
||||
|
||||
export const chatContent = Box({
|
||||
className: 'spacing-v-5',
|
||||
vertical: true,
|
||||
setup: (self) => self
|
||||
.hook(GPTService, (box, id) => {
|
||||
const message = GPTService.messages[id];
|
||||
if (!message) return;
|
||||
box.add(ChatMessage(message, `Model (${GPTService.providers[GPTService.providerID]['name']})`))
|
||||
}, 'newMsg')
|
||||
,
|
||||
});
|
||||
|
||||
const clearChat = () => {
|
||||
GPTService.clear();
|
||||
const children = chatContent.get_children();
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i];
|
||||
child.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
const CommandButton = (command) => Button({
|
||||
className: 'sidebar-chat-chip sidebar-chat-chip-action txt txt-small',
|
||||
onClicked: () => sendMessage(command),
|
||||
setup: setupCursorHover,
|
||||
label: command,
|
||||
});
|
||||
|
||||
export const chatGPTCommands = Box({
|
||||
className: 'spacing-h-5',
|
||||
children: [
|
||||
Box({ hexpand: true }),
|
||||
CommandButton('/key'),
|
||||
CommandButton('/model'),
|
||||
CommandButton('/clear'),
|
||||
]
|
||||
});
|
||||
|
||||
export const sendMessage = (text) => {
|
||||
// Check if text or API key is empty
|
||||
if (text.length == 0) return;
|
||||
if (GPTService.key.length == 0) {
|
||||
GPTService.key = text;
|
||||
chatContent.add(SystemMessage(`Key saved to\n\`${GPTService.keyPath}\``, 'API Key', chatGPTView));
|
||||
text = '';
|
||||
return;
|
||||
}
|
||||
// Commands
|
||||
if (text.startsWith('/')) {
|
||||
if (text.startsWith('/clear')) clearChat();
|
||||
else if (text.startsWith('/model')) chatContent.add(SystemMessage(`${getString("Currently using")} \`${GPTService.modelName}\``, '/model', chatGPTView))
|
||||
else if (text.startsWith('/prompt')) {
|
||||
const firstSpaceIndex = text.indexOf(' ');
|
||||
const prompt = text.slice(firstSpaceIndex + 1);
|
||||
if (firstSpaceIndex == -1 || prompt.length < 1) {
|
||||
chatContent.add(SystemMessage(`Usage: \`/prompt MESSAGE\``, '/prompt', chatGPTView))
|
||||
}
|
||||
else {
|
||||
GPTService.addMessage('user', prompt)
|
||||
}
|
||||
}
|
||||
else if (text.startsWith('/key')) {
|
||||
const parts = text.split(' ');
|
||||
if (parts.length == 1) chatContent.add(SystemMessage(
|
||||
`${getString("Key stored in:")}\n\`${GPTService.keyPath}\`\n${getString("To update this key, type")} \`/key YOUR_API_KEY\``,
|
||||
'/key',
|
||||
chatGPTView));
|
||||
else {
|
||||
GPTService.key = parts[1];
|
||||
chatContent.add(SystemMessage(`${getString("Updated API Key at")}\n\`${GPTService.keyPath}\``, '/key', chatGPTView));
|
||||
}
|
||||
}
|
||||
else if (text.startsWith('/test'))
|
||||
chatContent.add(SystemMessage(markdownTest, `Markdown test`, chatGPTView));
|
||||
else
|
||||
chatContent.add(SystemMessage(getString("Invalid command."), 'Error', chatGPTView))
|
||||
}
|
||||
else {
|
||||
GPTService.send(text);
|
||||
}
|
||||
}
|
||||
|
||||
export const chatGPTView = Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
ProviderSwitcher(),
|
||||
Scrollable({
|
||||
className: 'sidebar-chat-viewport',
|
||||
vexpand: true,
|
||||
child: Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
GPTWelcome(),
|
||||
chatContent,
|
||||
]
|
||||
}),
|
||||
setup: (scrolledWindow) => {
|
||||
// Show scrollbar
|
||||
scrolledWindow.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
|
||||
const vScrollbar = scrolledWindow.get_vscrollbar();
|
||||
vScrollbar.get_style_context().add_class('sidebar-scrollbar');
|
||||
// Avoid click-to-scroll-widget-to-view behavior
|
||||
Utils.timeout(1, () => {
|
||||
const viewport = scrolledWindow.child;
|
||||
viewport.set_focus_vadjustment(new Gtk.Adjustment(undefined));
|
||||
})
|
||||
// Always scroll to bottom with new content
|
||||
const adjustment = scrolledWindow.get_vadjustment();
|
||||
adjustment.connect("changed", () => {
|
||||
if (!chatEntry.hasFocus) return;
|
||||
adjustment.set_value(adjustment.get_upper() - adjustment.get_page_size());
|
||||
})
|
||||
}
|
||||
})
|
||||
]
|
||||
});
|
||||
288
homes/me/ags-end4/modules/sideleft/apis/gemini.js
Normal file
288
homes/me/ags-end4/modules/sideleft/apis/gemini.js
Normal file
|
|
@ -0,0 +1,288 @@
|
|||
const { Gtk } = imports.gi;
|
||||
import App from 'resource:///com/github/Aylur/ags/app.js';
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
|
||||
const { Box, Button, Icon, Label, Revealer, Scrollable } = Widget;
|
||||
import GeminiService from '../../../services/gemini.js';
|
||||
import { setupCursorHover, setupCursorHoverInfo } from '../../.widgetutils/cursorhover.js';
|
||||
import { SystemMessage, ChatMessage } from "./ai_chatmessage.js";
|
||||
import { ConfigToggle, ConfigSegmentedSelection, ConfigGap } from '../../.commonwidgets/configwidgets.js';
|
||||
import { markdownTest } from '../../.miscutils/md2pango.js';
|
||||
import { MarginRevealer } from '../../.widgethacks/advancedrevealers.js';
|
||||
import { chatEntry } from '../apiwidgets.js';
|
||||
|
||||
const MODEL_NAME = `Gemini`;
|
||||
|
||||
export const geminiTabIcon = Icon({
|
||||
hpack: 'center',
|
||||
icon: `google-gemini-symbolic`,
|
||||
})
|
||||
|
||||
const GeminiInfo = () => {
|
||||
const geminiLogo = Icon({
|
||||
hpack: 'center',
|
||||
className: 'sidebar-chat-welcome-logo',
|
||||
icon: `google-gemini-symbolic`,
|
||||
});
|
||||
return Box({
|
||||
vertical: true,
|
||||
className: 'spacing-v-15',
|
||||
children: [
|
||||
geminiLogo,
|
||||
Label({
|
||||
className: 'txt txt-title-small sidebar-chat-welcome-txt',
|
||||
wrap: true,
|
||||
justify: Gtk.Justification.CENTER,
|
||||
label: `Assistant (Gemini)`,
|
||||
}),
|
||||
Box({
|
||||
className: 'spacing-h-5',
|
||||
hpack: 'center',
|
||||
children: [
|
||||
Label({
|
||||
className: 'txt-smallie txt-subtext',
|
||||
wrap: true,
|
||||
justify: Gtk.Justification.CENTER,
|
||||
label: getString('Powered by Google'),
|
||||
}),
|
||||
Button({
|
||||
className: 'txt-subtext txt-norm icon-material',
|
||||
label: 'info',
|
||||
tooltipText: getString("Uses gemini-pro.\nNot affiliated, endorsed, or sponsored by Google.\n\nPrivacy: Chat messages aren't linked to your account,\n but will be read by human reviewers to improve the model."),
|
||||
setup: setupCursorHoverInfo,
|
||||
}),
|
||||
]
|
||||
}),
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
export const GeminiSettings = () => MarginRevealer({
|
||||
transition: 'slide_down',
|
||||
revealChild: true,
|
||||
extraSetup: (self) => self
|
||||
.hook(GeminiService, (self) => Utils.timeout(200, () => {
|
||||
self.attribute.hide();
|
||||
}), 'newMsg')
|
||||
.hook(GeminiService, (self) => Utils.timeout(200, () => {
|
||||
self.attribute.show();
|
||||
}), 'clear')
|
||||
,
|
||||
child: Box({
|
||||
vertical: true,
|
||||
className: 'sidebar-chat-settings',
|
||||
children: [
|
||||
ConfigSegmentedSelection({
|
||||
hpack: 'center',
|
||||
icon: 'casino',
|
||||
name: 'Randomness',
|
||||
desc: getString("Gemini's temperature value.\n Precise = 0\n Balanced = 0.5\n Creative = 1"),
|
||||
options: [
|
||||
{ value: 0.00, name: getString('Precise'), },
|
||||
{ value: 0.50, name: getString('Balanced'), },
|
||||
{ value: 1.00, name: getString('Creative'), },
|
||||
],
|
||||
initIndex: 2,
|
||||
onChange: (value, name) => {
|
||||
GeminiService.temperature = value;
|
||||
},
|
||||
}),
|
||||
ConfigGap({ vertical: true, size: 10 }), // Note: size can only be 5, 10, or 15
|
||||
Box({
|
||||
vertical: true,
|
||||
hpack: 'fill',
|
||||
className: 'sidebar-chat-settings-toggles',
|
||||
children: [
|
||||
ConfigToggle({
|
||||
icon: 'model_training',
|
||||
name: getString('Enhancements'),
|
||||
desc: getString("Tells Gemini:\n- It's a Linux sidebar assistant\n- Be brief and use bullet points"),
|
||||
initValue: GeminiService.assistantPrompt,
|
||||
onChange: (self, newValue) => {
|
||||
GeminiService.assistantPrompt = newValue;
|
||||
},
|
||||
}),
|
||||
ConfigToggle({
|
||||
icon: 'shield',
|
||||
name: getString('Safety'),
|
||||
desc: getString("When turned off, tells the API (not the model) \nto not block harmful/explicit content"),
|
||||
initValue: GeminiService.safe,
|
||||
onChange: (self, newValue) => {
|
||||
GeminiService.safe = newValue;
|
||||
},
|
||||
}),
|
||||
ConfigToggle({
|
||||
icon: 'history',
|
||||
name: getString('History'),
|
||||
desc: getString("Saves chat history\nMessages in previous chats won't show automatically, but they are there"),
|
||||
initValue: GeminiService.useHistory,
|
||||
onChange: (self, newValue) => {
|
||||
GeminiService.useHistory = newValue;
|
||||
},
|
||||
}),
|
||||
]
|
||||
})
|
||||
]
|
||||
})
|
||||
});
|
||||
|
||||
export const GoogleAiInstructions = () => Box({
|
||||
homogeneous: true,
|
||||
children: [Revealer({
|
||||
transition: 'slide_down',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
setup: (self) => self
|
||||
.hook(GeminiService, (self, hasKey) => {
|
||||
self.revealChild = (GeminiService.key.length == 0);
|
||||
}, 'hasKey')
|
||||
,
|
||||
child: Button({
|
||||
child: Label({
|
||||
useMarkup: true,
|
||||
wrap: true,
|
||||
className: 'txt sidebar-chat-welcome-txt',
|
||||
justify: Gtk.Justification.CENTER,
|
||||
label: 'A Google AI API key is required\nYou can grab one <u>here</u>, then enter it below',
|
||||
// setup: self => self.set_markup("This is a <a href=\"https://www.github.com\">test link</a>")
|
||||
}),
|
||||
setup: setupCursorHover,
|
||||
onClicked: () => {
|
||||
Utils.execAsync(['bash', '-c', `xdg-open https://makersuite.google.com/app/apikey &`]);
|
||||
}
|
||||
})
|
||||
})]
|
||||
});
|
||||
|
||||
const geminiWelcome = Box({
|
||||
vexpand: true,
|
||||
homogeneous: true,
|
||||
child: Box({
|
||||
className: 'spacing-v-15',
|
||||
vpack: 'center',
|
||||
vertical: true,
|
||||
children: [
|
||||
GeminiInfo(),
|
||||
GoogleAiInstructions(),
|
||||
GeminiSettings(),
|
||||
]
|
||||
})
|
||||
});
|
||||
|
||||
export const chatContent = Box({
|
||||
className: 'spacing-v-5',
|
||||
vertical: true,
|
||||
setup: (self) => self
|
||||
.hook(GeminiService, (box, id) => {
|
||||
const message = GeminiService.messages[id];
|
||||
if (!message) return;
|
||||
box.add(ChatMessage(message, MODEL_NAME))
|
||||
}, 'newMsg')
|
||||
,
|
||||
});
|
||||
|
||||
const clearChat = () => {
|
||||
GeminiService.clear();
|
||||
const children = chatContent.get_children();
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i];
|
||||
child.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
const CommandButton = (command) => Button({
|
||||
className: 'sidebar-chat-chip sidebar-chat-chip-action txt txt-small',
|
||||
onClicked: () => sendMessage(command),
|
||||
setup: setupCursorHover,
|
||||
label: command,
|
||||
});
|
||||
|
||||
export const geminiCommands = Box({
|
||||
className: 'spacing-h-5',
|
||||
children: [
|
||||
Box({ hexpand: true }),
|
||||
CommandButton('/key'),
|
||||
CommandButton('/model'),
|
||||
CommandButton('/clear'),
|
||||
]
|
||||
});
|
||||
|
||||
export const sendMessage = (text) => {
|
||||
// Check if text or API key is empty
|
||||
if (text.length == 0) return;
|
||||
if (GeminiService.key.length == 0) {
|
||||
GeminiService.key = text;
|
||||
chatContent.add(SystemMessage(`Key saved to\n\`${GeminiService.keyPath}\``, 'API Key', geminiView));
|
||||
text = '';
|
||||
return;
|
||||
}
|
||||
// Commands
|
||||
if (text.startsWith('/')) {
|
||||
if (text.startsWith('/clear')) clearChat();
|
||||
else if (text.startsWith('/load')) {
|
||||
clearChat();
|
||||
GeminiService.loadHistory();
|
||||
}
|
||||
else if (text.startsWith('/model')) chatContent.add(SystemMessage(`${getString("Currently using")} \`${GeminiService.modelName}\``, '/model', geminiView))
|
||||
else if (text.startsWith('/prompt')) {
|
||||
const firstSpaceIndex = text.indexOf(' ');
|
||||
const prompt = text.slice(firstSpaceIndex + 1);
|
||||
if (firstSpaceIndex == -1 || prompt.length < 1) {
|
||||
chatContent.add(SystemMessage(`Usage: \`/prompt MESSAGE\``, '/prompt', geminiView))
|
||||
}
|
||||
else {
|
||||
GeminiService.addMessage('user', prompt)
|
||||
}
|
||||
}
|
||||
else if (text.startsWith('/key')) {
|
||||
const parts = text.split(' ');
|
||||
if (parts.length == 1) chatContent.add(SystemMessage(
|
||||
`${getString("Key stored in:")} \n\`${GeminiService.keyPath}\`\n${getString("To update this key, type")} \`/key YOUR_API_KEY\``,
|
||||
'/key',
|
||||
geminiView));
|
||||
else {
|
||||
GeminiService.key = parts[1];
|
||||
chatContent.add(SystemMessage(`${getString("Updated API Key at")}\n\`${GeminiService.keyPath}\``, '/key', geminiView));
|
||||
}
|
||||
}
|
||||
else if (text.startsWith('/test'))
|
||||
chatContent.add(SystemMessage(markdownTest, `Markdown test`, geminiView));
|
||||
else
|
||||
chatContent.add(SystemMessage(getString(`Invalid command.`), 'Error', geminiView))
|
||||
}
|
||||
else {
|
||||
GeminiService.send(text);
|
||||
}
|
||||
}
|
||||
|
||||
export const geminiView = Box({
|
||||
homogeneous: true,
|
||||
children: [Scrollable({
|
||||
className: 'sidebar-chat-viewport',
|
||||
vexpand: true,
|
||||
child: Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
geminiWelcome,
|
||||
chatContent,
|
||||
]
|
||||
}),
|
||||
setup: (scrolledWindow) => {
|
||||
// Show scrollbar
|
||||
scrolledWindow.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
|
||||
const vScrollbar = scrolledWindow.get_vscrollbar();
|
||||
vScrollbar.get_style_context().add_class('sidebar-scrollbar');
|
||||
// Avoid click-to-scroll-widget-to-view behavior
|
||||
Utils.timeout(1, () => {
|
||||
const viewport = scrolledWindow.child;
|
||||
viewport.set_focus_vadjustment(new Gtk.Adjustment(undefined));
|
||||
})
|
||||
// Always scroll to bottom with new content
|
||||
const adjustment = scrolledWindow.get_vadjustment();
|
||||
adjustment.connect("changed", () => Utils.timeout(1, () => {
|
||||
if (!chatEntry.hasFocus) return;
|
||||
adjustment.set_value(adjustment.get_upper() - adjustment.get_page_size());
|
||||
}))
|
||||
}
|
||||
})]
|
||||
});
|
||||
419
homes/me/ags-end4/modules/sideleft/apis/waifu.js
Normal file
419
homes/me/ags-end4/modules/sideleft/apis/waifu.js
Normal file
|
|
@ -0,0 +1,419 @@
|
|||
// TODO: execAsync(['identify', '-format', '{"w":%w,"h":%h}', imagePath])
|
||||
// to detect img dimensions
|
||||
|
||||
const { Gdk, GdkPixbuf, Gio, GLib, Gtk } = imports.gi;
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
const { Box, Button, Label, Overlay, Revealer, Scrollable, Stack } = Widget;
|
||||
const { execAsync, exec } = Utils;
|
||||
import { fileExists } from '../../.miscutils/files.js';
|
||||
import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
|
||||
import { MarginRevealer } from '../../.widgethacks/advancedrevealers.js';
|
||||
import { setupCursorHover, setupCursorHoverInfo } from '../../.widgetutils/cursorhover.js';
|
||||
import WaifuService from '../../../services/waifus.js';
|
||||
import { darkMode } from '../../.miscutils/system.js';
|
||||
import { chatEntry } from '../apiwidgets.js';
|
||||
|
||||
async function getImageViewerApp(preferredApp) {
|
||||
Utils.execAsync(['bash', '-c', `command -v ${preferredApp}`])
|
||||
.then((output) => {
|
||||
if (output != '') return preferredApp;
|
||||
else return 'xdg-open';
|
||||
})
|
||||
.catch(print);
|
||||
}
|
||||
|
||||
const IMAGE_REVEAL_DELAY = 13; // Some wait for inits n other weird stuff
|
||||
const IMAGE_VIEWER_APP = getImageViewerApp(userOptions.apps.imageViewer); // Gnome's image viewer cuz very comfortable zooming
|
||||
const USER_CACHE_DIR = GLib.get_user_cache_dir();
|
||||
|
||||
// Create cache folder and clear pics from previous session
|
||||
Utils.exec(`bash -c 'mkdir -p ${USER_CACHE_DIR}/ags/media/waifus'`);
|
||||
Utils.exec(`bash -c 'rm ${USER_CACHE_DIR}/ags/media/waifus/*'`);
|
||||
|
||||
const CommandButton = (command) => Button({
|
||||
className: 'sidebar-chat-chip sidebar-chat-chip-action txt txt-small',
|
||||
onClicked: () => sendMessage(command),
|
||||
setup: setupCursorHover,
|
||||
label: command,
|
||||
});
|
||||
|
||||
export const waifuTabIcon = Box({
|
||||
hpack: 'center',
|
||||
children: [
|
||||
MaterialIcon('photo', 'norm'),
|
||||
]
|
||||
});
|
||||
|
||||
const WaifuInfo = () => {
|
||||
const waifuLogo = Label({
|
||||
hpack: 'center',
|
||||
className: 'sidebar-chat-welcome-logo',
|
||||
label: 'photo',
|
||||
})
|
||||
return Box({
|
||||
vertical: true,
|
||||
vexpand: true,
|
||||
className: 'spacing-v-15',
|
||||
children: [
|
||||
waifuLogo,
|
||||
Label({
|
||||
className: 'txt txt-title-small sidebar-chat-welcome-txt',
|
||||
wrap: true,
|
||||
justify: Gtk.Justification.CENTER,
|
||||
label: 'Waifus',
|
||||
}),
|
||||
Box({
|
||||
className: 'spacing-h-5',
|
||||
hpack: 'center',
|
||||
children: [
|
||||
Label({
|
||||
className: 'txt-smallie txt-subtext',
|
||||
wrap: true,
|
||||
justify: Gtk.Justification.CENTER,
|
||||
label: getString('Powered by waifu.im + other APIs'),
|
||||
}),
|
||||
Button({
|
||||
className: 'txt-subtext txt-norm icon-material',
|
||||
label: 'info',
|
||||
tooltipText: getString('Type tags for a random pic.\nNSFW content will not be returned unless\nyou explicitly request such a tag.\n\nDisclaimer: Not affiliated with the providers\nnor responsible for any of their content.'),
|
||||
setup: setupCursorHoverInfo,
|
||||
}),
|
||||
]
|
||||
}),
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
const waifuWelcome = Box({
|
||||
vexpand: true,
|
||||
homogeneous: true,
|
||||
child: Box({
|
||||
className: 'spacing-v-15',
|
||||
vpack: 'center',
|
||||
vertical: true,
|
||||
children: [
|
||||
WaifuInfo(),
|
||||
]
|
||||
})
|
||||
});
|
||||
|
||||
const WaifuImage = (taglist) => {
|
||||
const ImageState = (icon, name) => Box({
|
||||
className: 'spacing-h-5 txt',
|
||||
children: [
|
||||
Box({ hexpand: true }),
|
||||
Label({
|
||||
className: 'sidebar-waifu-txt txt-smallie',
|
||||
xalign: 0,
|
||||
label: name,
|
||||
}),
|
||||
MaterialIcon(icon, 'norm'),
|
||||
]
|
||||
})
|
||||
const ImageAction = ({ name, icon, action }) => Button({
|
||||
className: 'sidebar-waifu-image-action txt-norm icon-material',
|
||||
tooltipText: name,
|
||||
label: icon,
|
||||
onClicked: action,
|
||||
setup: setupCursorHover,
|
||||
})
|
||||
const downloadState = Stack({
|
||||
homogeneous: false,
|
||||
transition: 'slide_up_down',
|
||||
transitionDuration: userOptions.animations.durationSmall,
|
||||
children: {
|
||||
'api': ImageState('api', getString('Calling API')),
|
||||
'download': ImageState('downloading', getString('Downloading image')),
|
||||
'done': ImageState('done', getString('Finished!')),
|
||||
'error': ImageState('error', getString('Error')),
|
||||
'notfound': ImageState('error', getString('Not found!')),
|
||||
},
|
||||
});
|
||||
const downloadIndicator = MarginRevealer({
|
||||
vpack: 'center',
|
||||
transition: 'slide_left',
|
||||
revealChild: true,
|
||||
child: downloadState,
|
||||
});
|
||||
const blockHeading = Box({
|
||||
hpack: 'fill',
|
||||
className: 'spacing-h-5',
|
||||
children: [
|
||||
...taglist.map((tag) => CommandButton(tag)),
|
||||
Box({ hexpand: true }),
|
||||
downloadIndicator,
|
||||
]
|
||||
});
|
||||
const blockImageActions = Revealer({
|
||||
transition: 'crossfade',
|
||||
revealChild: false,
|
||||
child: Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
Box({
|
||||
className: 'sidebar-waifu-image-actions spacing-h-3',
|
||||
children: [
|
||||
Box({ hexpand: true }),
|
||||
ImageAction({
|
||||
name: getString('Go to source'),
|
||||
icon: 'link',
|
||||
action: () => execAsync(['xdg-open', `${thisBlock.attribute.imageData.source}`]).catch(print),
|
||||
}),
|
||||
ImageAction({
|
||||
name: getString('Hoard'),
|
||||
icon: 'save',
|
||||
action: (self) => {
|
||||
execAsync(['bash', '-c', `mkdir -p $(xdg-user-dir PICTURES)/homework${thisBlock.attribute.isNsfw ? '/🌶️' : ''} && cp ${thisBlock.attribute.imagePath} $(xdg-user-dir PICTURES)/homework${thisBlock.attribute.isNsfw ? '/🌶️/' : ''}`])
|
||||
.then(() => self.label = 'done')
|
||||
.catch(print);
|
||||
},
|
||||
}),
|
||||
ImageAction({
|
||||
name: getString('Open externally'),
|
||||
icon: 'open_in_new',
|
||||
action: () => execAsync([IMAGE_VIEWER_APP, `${thisBlock.attribute.imagePath}`]).catch(print),
|
||||
}),
|
||||
]
|
||||
})
|
||||
],
|
||||
})
|
||||
})
|
||||
const blockImage = Widget.DrawingArea({
|
||||
className: 'sidebar-waifu-image',
|
||||
});
|
||||
const blockImageRevealer = Revealer({
|
||||
transition: 'slide_down',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
revealChild: false,
|
||||
child: Box({
|
||||
className: 'margin-top-5',
|
||||
children: [Overlay({
|
||||
child: Box({
|
||||
homogeneous: true,
|
||||
className: 'sidebar-waifu-image',
|
||||
children: [blockImage],
|
||||
}),
|
||||
overlays: [blockImageActions],
|
||||
})]
|
||||
}),
|
||||
});
|
||||
const thisBlock = Box({
|
||||
className: 'sidebar-chat-message',
|
||||
attribute: {
|
||||
'imagePath': '',
|
||||
'isNsfw': false,
|
||||
'imageData': '',
|
||||
'update': (imageData, force = false) => {
|
||||
thisBlock.attribute.imageData = imageData;
|
||||
const { status, signature, url, extension, source, dominant_color, is_nsfw, width, height, tags } = thisBlock.attribute.imageData;
|
||||
thisBlock.attribute.isNsfw = is_nsfw;
|
||||
if (status == 404) {
|
||||
downloadState.shown = 'notfound';
|
||||
return;
|
||||
}
|
||||
if (status != 200) {
|
||||
downloadState.shown = 'error';
|
||||
return;
|
||||
}
|
||||
thisBlock.attribute.imagePath = `${USER_CACHE_DIR}/ags/media/waifus/${signature}${extension}`;
|
||||
downloadState.shown = 'download';
|
||||
// Width/height
|
||||
const widgetWidth = Math.min(Math.floor(waifuContent.get_allocated_width() * 0.85), width);
|
||||
const widgetHeight = Math.ceil(widgetWidth * height / width);
|
||||
blockImage.set_size_request(widgetWidth, widgetHeight);
|
||||
const showImage = () => {
|
||||
downloadState.shown = 'done';
|
||||
const pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(thisBlock.attribute.imagePath, widgetWidth, widgetHeight);
|
||||
// const pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(thisBlock.attribute.imagePath, widgetWidth, widgetHeight, false);
|
||||
|
||||
blockImage.set_size_request(widgetWidth, widgetHeight);
|
||||
blockImage.connect("draw", (widget, cr) => {
|
||||
const borderRadius = widget.get_style_context().get_property('border-radius', Gtk.StateFlags.NORMAL);
|
||||
|
||||
// Draw a rounded rectangle
|
||||
cr.arc(borderRadius, borderRadius, borderRadius, Math.PI, 1.5 * Math.PI);
|
||||
cr.arc(widgetWidth - borderRadius, borderRadius, borderRadius, 1.5 * Math.PI, 2 * Math.PI);
|
||||
cr.arc(widgetWidth - borderRadius, widgetHeight - borderRadius, borderRadius, 0, 0.5 * Math.PI);
|
||||
cr.arc(borderRadius, widgetHeight - borderRadius, borderRadius, 0.5 * Math.PI, Math.PI);
|
||||
cr.closePath();
|
||||
cr.clip();
|
||||
|
||||
// Paint image as bg
|
||||
Gdk.cairo_set_source_pixbuf(cr, pixbuf, 0, 0);
|
||||
cr.paint();
|
||||
});
|
||||
|
||||
// Reveal stuff
|
||||
Utils.timeout(IMAGE_REVEAL_DELAY, () => {
|
||||
blockImageRevealer.revealChild = true;
|
||||
})
|
||||
Utils.timeout(IMAGE_REVEAL_DELAY + blockImageRevealer.transitionDuration,
|
||||
() => blockImageActions.revealChild = true
|
||||
);
|
||||
downloadIndicator.attribute.hide();
|
||||
}
|
||||
// Show
|
||||
if (!force && fileExists(thisBlock.attribute.imagePath)) showImage();
|
||||
else Utils.execAsync(['bash', '-c', `wget -O '${thisBlock.attribute.imagePath}' '${url}'`])
|
||||
.then(showImage)
|
||||
.catch(print);
|
||||
thisBlock.css = `background-color: mix(${darkMode.value ? 'black' : 'white'}, ${dominant_color}, 0.97);`;
|
||||
},
|
||||
},
|
||||
children: [
|
||||
Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
blockHeading,
|
||||
Box({
|
||||
vertical: true,
|
||||
hpack: 'start',
|
||||
children: [blockImageRevealer],
|
||||
})
|
||||
]
|
||||
})
|
||||
],
|
||||
});
|
||||
return thisBlock;
|
||||
}
|
||||
|
||||
const waifuContent = Box({
|
||||
className: 'spacing-v-15',
|
||||
vertical: true,
|
||||
attribute: {
|
||||
'map': new Map(),
|
||||
},
|
||||
setup: (self) => self
|
||||
.hook(WaifuService, (box, id) => {
|
||||
if (id === undefined) return;
|
||||
const newImageBlock = WaifuImage(WaifuService.queries[id]);
|
||||
box.add(newImageBlock);
|
||||
box.show_all();
|
||||
box.attribute.map.set(id, newImageBlock);
|
||||
}, 'newResponse')
|
||||
.hook(WaifuService, (box, id) => {
|
||||
if (id === undefined) return;
|
||||
const data = WaifuService.responses[id];
|
||||
if (!data) return;
|
||||
const imageBlock = box.attribute.map.get(id);
|
||||
imageBlock?.attribute.update(data);
|
||||
}, 'updateResponse')
|
||||
,
|
||||
});
|
||||
|
||||
export const waifuView = Scrollable({
|
||||
className: 'sidebar-chat-viewport',
|
||||
vexpand: true,
|
||||
child: Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
waifuWelcome,
|
||||
waifuContent,
|
||||
]
|
||||
}),
|
||||
setup: (scrolledWindow) => {
|
||||
// Show scrollbar
|
||||
scrolledWindow.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
|
||||
const vScrollbar = scrolledWindow.get_vscrollbar();
|
||||
vScrollbar.get_style_context().add_class('sidebar-scrollbar');
|
||||
// Avoid click-to-scroll-widget-to-view behavior
|
||||
Utils.timeout(1, () => {
|
||||
const viewport = scrolledWindow.child;
|
||||
viewport.set_focus_vadjustment(new Gtk.Adjustment(undefined));
|
||||
})
|
||||
// Always scroll to bottom with new content
|
||||
const adjustment = scrolledWindow.get_vadjustment();
|
||||
adjustment.connect("changed", () => {
|
||||
if (!chatEntry.hasFocus) return;
|
||||
adjustment.set_value(adjustment.get_upper() - adjustment.get_page_size());
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
const waifuTags = Revealer({
|
||||
revealChild: false,
|
||||
transition: 'crossfade',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
child: Box({
|
||||
className: 'spacing-h-5',
|
||||
children: [
|
||||
Scrollable({
|
||||
vscroll: 'never',
|
||||
hscroll: 'automatic',
|
||||
hexpand: true,
|
||||
child: Box({
|
||||
className: 'spacing-h-5',
|
||||
children: [
|
||||
CommandButton('waifu'),
|
||||
CommandButton('maid'),
|
||||
CommandButton('uniform'),
|
||||
CommandButton('oppai'),
|
||||
CommandButton('selfies'),
|
||||
CommandButton('marin-kitagawa'),
|
||||
CommandButton('raiden-shogun'),
|
||||
CommandButton('mori-calliope'),
|
||||
]
|
||||
})
|
||||
}),
|
||||
Box({ className: 'separator-line' }),
|
||||
]
|
||||
})
|
||||
});
|
||||
|
||||
export const waifuCommands = Box({
|
||||
className: 'spacing-h-5',
|
||||
setup: (self) => {
|
||||
self.pack_end(CommandButton('/clear'), false, false, 0);
|
||||
self.pack_start(Button({
|
||||
className: 'sidebar-chat-chip-toggle',
|
||||
setup: setupCursorHover,
|
||||
label: getString('Tags →'),
|
||||
onClicked: () => {
|
||||
waifuTags.revealChild = !waifuTags.revealChild;
|
||||
}
|
||||
}), false, false, 0);
|
||||
self.pack_start(waifuTags, true, true, 0);
|
||||
}
|
||||
});
|
||||
|
||||
const clearChat = () => { // destroy!!
|
||||
waifuContent.attribute.map.forEach((value, key, map) => {
|
||||
value.destroy();
|
||||
value = null;
|
||||
});
|
||||
}
|
||||
|
||||
function newSimpleImageCall(name, url, width, height, dominantColor = '#9392A6') {
|
||||
const timeSinceEpoch = Date.now();
|
||||
const newImage = WaifuImage([`/${name}`]);
|
||||
waifuContent.add(newImage);
|
||||
waifuContent.attribute.map.set(timeSinceEpoch, newImage);
|
||||
Utils.timeout(IMAGE_REVEAL_DELAY, () => newImage?.attribute.update({
|
||||
status: 200,
|
||||
url: url,
|
||||
extension: '',
|
||||
signature: timeSinceEpoch,
|
||||
source: url,
|
||||
dominant_color: dominantColor,
|
||||
is_nsfw: false,
|
||||
width: width,
|
||||
height: height,
|
||||
tags: [`/${name}`],
|
||||
}, true));
|
||||
}
|
||||
|
||||
export const sendMessage = (text) => {
|
||||
// Commands
|
||||
if (text.startsWith('/')) {
|
||||
if (text.startsWith('/clear')) clearChat();
|
||||
else if (text.startsWith('/test'))
|
||||
newSimpleImageCall('test', 'https://picsum.photos/600/400', 300, 200);
|
||||
else if (text.startsWith('/chino'))
|
||||
newSimpleImageCall('chino', 'https://chino.pages.dev/chino', 300, 400, '#B2AEF3');
|
||||
else if (text.startsWith('/place'))
|
||||
newSimpleImageCall('place', 'https://placewaifu.com/image/400/600', 400, 600, '#F0A235');
|
||||
|
||||
}
|
||||
else WaifuService.fetch(text);
|
||||
}
|
||||
223
homes/me/ags-end4/modules/sideleft/apiwidgets.js
Normal file
223
homes/me/ags-end4/modules/sideleft/apiwidgets.js
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
const { Gtk, Gdk } = imports.gi;
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
const { Box, Button, CenterBox, Entry, EventBox, Icon, Label, Overlay, Revealer, Scrollable, Stack } = Widget;
|
||||
const { execAsync, exec } = Utils;
|
||||
import { setupCursorHover, setupCursorHoverInfo } from '../.widgetutils/cursorhover.js';
|
||||
// APIs
|
||||
import GPTService from '../../services/gpt.js';
|
||||
import Gemini from '../../services/gemini.js';
|
||||
import { geminiView, geminiCommands, sendMessage as geminiSendMessage, geminiTabIcon } from './apis/gemini.js';
|
||||
import { chatGPTView, chatGPTCommands, sendMessage as chatGPTSendMessage, chatGPTTabIcon } from './apis/chatgpt.js';
|
||||
import { waifuView, waifuCommands, sendMessage as waifuSendMessage, waifuTabIcon } from './apis/waifu.js';
|
||||
import { booruView, booruCommands, sendMessage as booruSendMessage, booruTabIcon } from './apis/booru.js';
|
||||
import { enableClickthrough } from "../.widgetutils/clickthrough.js";
|
||||
import { checkKeybind } from '../.widgetutils/keybind.js';
|
||||
const TextView = Widget.subclass(Gtk.TextView, "AgsTextView");
|
||||
|
||||
import { widgetContent } from './sideleft.js';
|
||||
import { IconTabContainer } from '../.commonwidgets/tabcontainer.js';
|
||||
|
||||
const EXPAND_INPUT_THRESHOLD = 30;
|
||||
const APILIST = {
|
||||
'gemini': {
|
||||
name: 'Assistant (Gemini Pro)',
|
||||
sendCommand: geminiSendMessage,
|
||||
contentWidget: geminiView,
|
||||
commandBar: geminiCommands,
|
||||
tabIcon: geminiTabIcon,
|
||||
placeholderText: getString('Message Gemini...'),
|
||||
},
|
||||
'gpt': {
|
||||
name: 'Assistant (GPTs)',
|
||||
sendCommand: chatGPTSendMessage,
|
||||
contentWidget: chatGPTView,
|
||||
commandBar: chatGPTCommands,
|
||||
tabIcon: chatGPTTabIcon,
|
||||
placeholderText: getString('Message the model...'),
|
||||
},
|
||||
'waifu': {
|
||||
name: 'Waifus',
|
||||
sendCommand: waifuSendMessage,
|
||||
contentWidget: waifuView,
|
||||
commandBar: waifuCommands,
|
||||
tabIcon: waifuTabIcon,
|
||||
placeholderText: getString('Enter tags'),
|
||||
},
|
||||
'booru': {
|
||||
name: 'Booru',
|
||||
sendCommand: booruSendMessage,
|
||||
contentWidget: booruView,
|
||||
commandBar: booruCommands,
|
||||
tabIcon: booruTabIcon,
|
||||
placeholderText: getString('Enter tags'),
|
||||
},
|
||||
}
|
||||
const APIS = userOptions.sidebar.pages.apis.order.map((apiName) => APILIST[apiName]);
|
||||
let currentApiId = 0;
|
||||
|
||||
function apiSendMessage(textView) {
|
||||
// Get text
|
||||
const buffer = textView.get_buffer();
|
||||
const [start, end] = buffer.get_bounds();
|
||||
const text = buffer.get_text(start, end, true).trimStart();
|
||||
if (!text || text.length == 0) return;
|
||||
// Send
|
||||
APIS[currentApiId].sendCommand(text)
|
||||
// Reset
|
||||
buffer.set_text("", -1);
|
||||
chatEntryWrapper.toggleClassName('sidebar-chat-wrapper-extended', false);
|
||||
chatEntry.set_valign(Gtk.Align.CENTER);
|
||||
}
|
||||
|
||||
export const chatEntry = TextView({
|
||||
hexpand: true,
|
||||
wrapMode: Gtk.WrapMode.WORD_CHAR,
|
||||
acceptsTab: false,
|
||||
className: 'sidebar-chat-entry txt txt-smallie',
|
||||
setup: (self) => self
|
||||
.hook(App, (self, currentName, visible) => {
|
||||
if (visible && currentName === 'sideleft') {
|
||||
self.grab_focus();
|
||||
}
|
||||
})
|
||||
.hook(GPTService, (self) => {
|
||||
if (APIS[currentApiId].name != 'Assistant (GPTs)') return;
|
||||
self.placeholderText = (GPTService.key.length > 0 ? getString('Message the model...') : getString('Enter API Key...'));
|
||||
}, 'hasKey')
|
||||
.hook(Gemini, (self) => {
|
||||
if (APIS[currentApiId].name != 'Assistant (Gemini Pro)') return;
|
||||
self.placeholderText = (Gemini.key.length > 0 ? getString('Message Gemini...') : getString('Enter Google AI API Key...'));
|
||||
}, 'hasKey')
|
||||
.on("key-press-event", (widget, event) => {
|
||||
// Don't send when Shift+Enter
|
||||
if (event.get_keyval()[1] === Gdk.KEY_Return || event.get_keyval()[1] === Gdk.KEY_KP_Enter) {
|
||||
if (event.get_state()[1] !== 17) {// SHIFT_MASK doesn't work but 17 should be shift
|
||||
apiSendMessage(widget);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// Keybinds
|
||||
if (checkKeybind(event, userOptions.keybinds.sidebar.cycleTab))
|
||||
widgetContent.cycleTab();
|
||||
else if (checkKeybind(event, userOptions.keybinds.sidebar.nextTab))
|
||||
widgetContent.nextTab();
|
||||
else if (checkKeybind(event, userOptions.keybinds.sidebar.prevTab))
|
||||
widgetContent.prevTab();
|
||||
else if (checkKeybind(event, userOptions.keybinds.sidebar.apis.nextTab)) {
|
||||
apiWidgets.attribute.nextTab();
|
||||
return true;
|
||||
}
|
||||
else if (checkKeybind(event, userOptions.keybinds.sidebar.apis.prevTab)) {
|
||||
apiWidgets.attribute.prevTab();
|
||||
return true;
|
||||
}
|
||||
})
|
||||
,
|
||||
});
|
||||
|
||||
chatEntry.get_buffer().connect("changed", (buffer) => {
|
||||
const bufferText = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter(), true);
|
||||
chatSendButton.toggleClassName('sidebar-chat-send-available', bufferText.length > 0);
|
||||
chatPlaceholderRevealer.revealChild = (bufferText.length == 0);
|
||||
if (buffer.get_line_count() > 1 || bufferText.length > EXPAND_INPUT_THRESHOLD) {
|
||||
chatEntryWrapper.toggleClassName('sidebar-chat-wrapper-extended', true);
|
||||
chatEntry.set_valign(Gtk.Align.FILL);
|
||||
chatPlaceholder.set_valign(Gtk.Align.FILL);
|
||||
}
|
||||
else {
|
||||
chatEntryWrapper.toggleClassName('sidebar-chat-wrapper-extended', false);
|
||||
chatEntry.set_valign(Gtk.Align.CENTER);
|
||||
chatPlaceholder.set_valign(Gtk.Align.CENTER);
|
||||
}
|
||||
});
|
||||
|
||||
const chatEntryWrapper = Scrollable({
|
||||
className: 'sidebar-chat-wrapper',
|
||||
hscroll: 'never',
|
||||
vscroll: 'always',
|
||||
child: chatEntry,
|
||||
});
|
||||
|
||||
const chatSendButton = Button({
|
||||
className: 'txt-norm icon-material sidebar-chat-send',
|
||||
vpack: 'end',
|
||||
label: 'arrow_upward',
|
||||
setup: setupCursorHover,
|
||||
onClicked: (self) => {
|
||||
APIS[currentApiId].sendCommand(chatEntry.get_buffer().text);
|
||||
chatEntry.get_buffer().set_text("", -1);
|
||||
},
|
||||
});
|
||||
|
||||
const chatPlaceholder = Label({
|
||||
className: 'txt-subtext txt-smallie margin-left-5',
|
||||
hpack: 'start',
|
||||
vpack: 'center',
|
||||
label: APIS[currentApiId].placeholderText,
|
||||
});
|
||||
|
||||
const chatPlaceholderRevealer = Revealer({
|
||||
revealChild: true,
|
||||
transition: 'crossfade',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
child: chatPlaceholder,
|
||||
setup: enableClickthrough,
|
||||
});
|
||||
|
||||
const textboxArea = Box({ // Entry area
|
||||
className: 'sidebar-chat-textarea',
|
||||
children: [
|
||||
Overlay({
|
||||
passThrough: true,
|
||||
child: chatEntryWrapper,
|
||||
overlays: [chatPlaceholderRevealer],
|
||||
}),
|
||||
Box({ className: 'width-10' }),
|
||||
chatSendButton,
|
||||
]
|
||||
});
|
||||
|
||||
const apiCommandStack = Stack({
|
||||
transition: 'slide_up_down',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
children: APIS.reduce((acc, api) => {
|
||||
acc[api.name] = api.commandBar;
|
||||
return acc;
|
||||
}, {}),
|
||||
})
|
||||
|
||||
export const apiContentStack = IconTabContainer({
|
||||
tabSwitcherClassName: 'sidebar-icontabswitcher',
|
||||
className: 'margin-top-5',
|
||||
iconWidgets: APIS.map((api) => api.tabIcon),
|
||||
names: APIS.map((api) => api.name),
|
||||
children: APIS.map((api) => api.contentWidget),
|
||||
onChange: (self, id) => {
|
||||
apiCommandStack.shown = APIS[id].name;
|
||||
chatPlaceholder.label = APIS[id].placeholderText;
|
||||
currentApiId = id;
|
||||
}
|
||||
});
|
||||
|
||||
function switchToTab(id) {
|
||||
apiContentStack.shown.value = id;
|
||||
}
|
||||
|
||||
const apiWidgets = Widget.Box({
|
||||
attribute: {
|
||||
'nextTab': () => switchToTab(Math.min(currentApiId + 1, APIS.length - 1)),
|
||||
'prevTab': () => switchToTab(Math.max(0, currentApiId - 1)),
|
||||
},
|
||||
vertical: true,
|
||||
className: 'spacing-v-10',
|
||||
homogeneous: false,
|
||||
children: [
|
||||
apiContentStack,
|
||||
apiCommandStack,
|
||||
textboxArea,
|
||||
],
|
||||
});
|
||||
|
||||
export default apiWidgets;
|
||||
18
homes/me/ags-end4/modules/sideleft/main.js
Normal file
18
homes/me/ags-end4/modules/sideleft/main.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import PopupWindow from '../.widgethacks/popupwindow.js';
|
||||
import SidebarLeft from "./sideleft.js";
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
const { Box } = Widget;
|
||||
import clickCloseRegion from '../.commonwidgets/clickcloseregion.js';
|
||||
|
||||
export default () => PopupWindow({
|
||||
keymode: 'on-demand',
|
||||
anchor: ['left', 'top', 'bottom'],
|
||||
name: 'sideleft',
|
||||
layer: 'top',
|
||||
child: Box({
|
||||
children: [
|
||||
SidebarLeft(),
|
||||
clickCloseRegion({ name: 'sideleft', multimonitor: false, fillMonitor: 'horizontal' }),
|
||||
]
|
||||
})
|
||||
});
|
||||
121
homes/me/ags-end4/modules/sideleft/sideleft.js
Normal file
121
homes/me/ags-end4/modules/sideleft/sideleft.js
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
const { Gdk } = imports.gi;
|
||||
import App from 'resource:///com/github/Aylur/ags/app.js';
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
const { Box, Button, EventBox, Label, Revealer, Scrollable, Stack } = Widget;
|
||||
const { execAsync, exec } = Utils;
|
||||
import { MaterialIcon } from '../.commonwidgets/materialicon.js';
|
||||
import { setupCursorHover } from '../.widgetutils/cursorhover.js';
|
||||
import toolBox from './toolbox.js';
|
||||
import apiWidgets from './apiwidgets.js';
|
||||
import { chatEntry } from './apiwidgets.js';
|
||||
import { TabContainer } from '../.commonwidgets/tabcontainer.js';
|
||||
import { checkKeybind } from '../.widgetutils/keybind.js';
|
||||
|
||||
const SIDEBARTABS = {
|
||||
'apis': {
|
||||
name: 'apis',
|
||||
content: apiWidgets,
|
||||
materialIcon: 'api',
|
||||
friendlyName: 'APIs',
|
||||
},
|
||||
'tools': {
|
||||
name: 'tools',
|
||||
content: toolBox,
|
||||
materialIcon: 'home_repair_service',
|
||||
friendlyName: 'Tools',
|
||||
},
|
||||
}
|
||||
const CONTENTS = userOptions.sidebar.pages.order.map((tabName) => SIDEBARTABS[tabName])
|
||||
|
||||
const pinButton = Button({
|
||||
attribute: {
|
||||
'enabled': false,
|
||||
'toggle': (self) => {
|
||||
self.attribute.enabled = !self.attribute.enabled;
|
||||
self.toggleClassName('sidebar-pin-enabled', self.attribute.enabled);
|
||||
|
||||
const sideleftWindow = App.getWindow('sideleft');
|
||||
const sideleftContent = sideleftWindow.get_children()[0].get_children()[0].get_children()[1];
|
||||
|
||||
sideleftContent.toggleClassName('sidebar-pinned', self.attribute.enabled);
|
||||
|
||||
if (self.attribute.enabled) {
|
||||
sideleftWindow.exclusivity = 'on-demad';
|
||||
}
|
||||
else {
|
||||
sideleftWindow.exclusivity = 'normal';
|
||||
}
|
||||
},
|
||||
},
|
||||
vpack: 'start',
|
||||
className: 'sidebar-pin',
|
||||
child: MaterialIcon('push_pin', 'larger'),
|
||||
tooltipText: 'Pin sidebar (Ctrl+P)',
|
||||
onClicked: (self) => self.attribute.toggle(self),
|
||||
setup: (self) => {
|
||||
setupCursorHover(self);
|
||||
self.hook(App, (self, currentName, visible) => {
|
||||
if (currentName === 'sideleft' && visible) self.grab_focus();
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
export const widgetContent = TabContainer({
|
||||
icons: CONTENTS.map((item) => item.materialIcon),
|
||||
names: CONTENTS.map((item) => item.friendlyName),
|
||||
children: CONTENTS.map((item) => item.content),
|
||||
className: 'sidebar-left spacing-v-10',
|
||||
setup: (self) => self.hook(App, (self, currentName, visible) => {
|
||||
if (currentName === 'sideleft')
|
||||
self.toggleClassName('sidebar-pinned', pinButton.attribute.enabled && visible);
|
||||
}),
|
||||
});
|
||||
|
||||
export default () => Box({
|
||||
// vertical: true,
|
||||
vexpand: true,
|
||||
css: 'min-width: 2px;',
|
||||
children: [
|
||||
widgetContent,
|
||||
],
|
||||
setup: (self) => self
|
||||
.on('key-press-event', (widget, event) => { // Handle keybinds
|
||||
if (checkKeybind(event, userOptions.keybinds.sidebar.pin))
|
||||
pinButton.attribute.toggle(pinButton);
|
||||
else if (checkKeybind(event, userOptions.keybinds.sidebar.cycleTab))
|
||||
widgetContent.cycleTab();
|
||||
else if (checkKeybind(event, userOptions.keybinds.sidebar.nextTab))
|
||||
widgetContent.nextTab();
|
||||
else if (checkKeybind(event, userOptions.keybinds.sidebar.prevTab))
|
||||
widgetContent.prevTab();
|
||||
|
||||
if (widgetContent.attribute.names[widgetContent.attribute.shown.value] == 'APIs') { // If api tab is focused
|
||||
// Focus entry when typing
|
||||
if ((
|
||||
!(event.get_state()[1] & Gdk.ModifierType.CONTROL_MASK) &&
|
||||
event.get_keyval()[1] >= 32 && event.get_keyval()[1] <= 126 &&
|
||||
widget != chatEntry && event.get_keyval()[1] != Gdk.KEY_space)
|
||||
||
|
||||
((event.get_state()[1] & Gdk.ModifierType.CONTROL_MASK) &&
|
||||
event.get_keyval()[1] === Gdk.KEY_v)
|
||||
) {
|
||||
chatEntry.grab_focus();
|
||||
const buffer = chatEntry.get_buffer();
|
||||
buffer.set_text(buffer.text + String.fromCharCode(event.get_keyval()[1]), -1);
|
||||
buffer.place_cursor(buffer.get_iter_at_offset(-1));
|
||||
}
|
||||
// Switch API type
|
||||
else if (checkKeybind(event, userOptions.keybinds.sidebar.apis.nextTab)) {
|
||||
const toSwitchTab = widgetContent.attribute.children[widgetContent.attribute.shown.value];
|
||||
toSwitchTab.nextTab();
|
||||
}
|
||||
else if (checkKeybind(event, userOptions.keybinds.sidebar.apis.prevTab)) {
|
||||
const toSwitchTab = widgetContent.attribute.children[widgetContent.attribute.shown.value];
|
||||
toSwitchTab.prevTab();
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
,
|
||||
});
|
||||
20
homes/me/ags-end4/modules/sideleft/toolbox.js
Normal file
20
homes/me/ags-end4/modules/sideleft/toolbox.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
const { Box, Label, Scrollable } = Widget;
|
||||
import QuickScripts from './tools/quickscripts.js';
|
||||
import ColorPicker from './tools/colorpicker.js';
|
||||
import Name from './tools/name.js';
|
||||
|
||||
export default Scrollable({
|
||||
hscroll: "never",
|
||||
vscroll: "automatic",
|
||||
child: Box({
|
||||
vertical: true,
|
||||
className: 'spacing-v-10',
|
||||
children: [
|
||||
QuickScripts(),
|
||||
ColorPicker(),
|
||||
Box({ vexpand: true }),
|
||||
Name(),
|
||||
]
|
||||
})
|
||||
});
|
||||
99
homes/me/ags-end4/modules/sideleft/tools/changeres.sh
Normal file
99
homes/me/ags-end4/modules/sideleft/tools/changeres.sh
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Function to get the current resolution
|
||||
get_current_resolution() {
|
||||
local output
|
||||
output=$(hyprctl monitors -j)
|
||||
local width height refreshRate
|
||||
width=$(echo "$output" | jq -r '.[0].width')
|
||||
height=$(echo "$output" | jq -r '.[0].height')
|
||||
refreshRate=$(echo "$output" | jq -r '.[0].refreshRate')
|
||||
echo "$width $height $refreshRate"
|
||||
}
|
||||
|
||||
# Function to update the Hyprland configuration with the new resolution
|
||||
update_resolution_config() {
|
||||
local newWidth="$1"
|
||||
local newHeight="$2"
|
||||
local newRefreshRate="$3"
|
||||
local currentRes
|
||||
currentRes=$(get_current_resolution)
|
||||
local width height refreshRate
|
||||
width=${newWidth:-$(echo "$currentRes" | awk '{print $1}')}
|
||||
height=${newHeight:-$(echo "$currentRes" | awk '{print $2}')}
|
||||
refreshRate=${newRefreshRate:-$(echo "$currentRes" | awk '{print $3}')}
|
||||
|
||||
local modelineOutput
|
||||
modelineOutput=$(gtf "$width" "$height" "$refreshRate")
|
||||
local modeline
|
||||
modeline=$(echo "$modelineOutput" | grep -oP 'Modeline "\K[^"]+')
|
||||
|
||||
if [ -z "$modeline" ]; then
|
||||
echo "Failed to generate modeline"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract the resolution and refresh rate from the modeline
|
||||
local resolution
|
||||
resolution=$(echo "$modeline" | grep -oP '^[0-9]+x[0-9]+')
|
||||
local rate
|
||||
rate=$(echo "$modeline" | grep -oP '[0-9]+.[0-9]+$')
|
||||
|
||||
if [ -z "$resolution" ] || [ -z "$rate" ]; then
|
||||
echo "Failed to extract resolution or refresh rate from modeline"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local configPath="${HOME}/.config/hypr/hyprland/general.conf"
|
||||
local newConfigContent
|
||||
newConfigContent=$(sed "s/^monitor=.*$/monitor=eDP-1, $resolution@$rate, auto, 1/" "$configPath")
|
||||
|
||||
echo "$newConfigContent" > "$configPath"
|
||||
}
|
||||
|
||||
# Main script
|
||||
echo "Welcome to the Resolution Configurator"
|
||||
echo ""
|
||||
echo " +---------------------------+"
|
||||
echo " | _____ |"
|
||||
echo " | | | |"
|
||||
echo " | | | |"
|
||||
echo " | |_____| |"
|
||||
echo " | |"
|
||||
echo " +---------------------------+"
|
||||
echo ""
|
||||
echo "Current resolution and refresh rate:"
|
||||
currentRes=$(get_current_resolution)
|
||||
width=$(echo "$currentRes" | awk '{print $1}')
|
||||
height=$(echo "$currentRes" | awk '{print $2}')
|
||||
refreshRate=$(echo "$currentRes" | awk '{print $3}')
|
||||
|
||||
echo "Width: $width px"
|
||||
echo "Height: $height px"
|
||||
echo "Refresh Rate: $refreshRate Hz"
|
||||
|
||||
echo ""
|
||||
|
||||
read -p "Enter new width (or press Enter to keep current width): " newWidth
|
||||
read -p "Enter new height (or press Enter to keep current height): " newHeight
|
||||
read -p "Enter new refresh rate (or press Enter to keep current refresh rate): " newRefreshRate
|
||||
|
||||
# Validate inputs (if provided)
|
||||
if [[ ! "$newWidth" =~ ^[0-9]+$ && -n "$newWidth" ]]; then
|
||||
echo "Invalid width value."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! "$newHeight" =~ ^[0-9]+$ && -n "$newHeight" ]]; then
|
||||
echo "Invalid height value."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! "$newRefreshRate" =~ ^[0-9]+$ && -n "$newRefreshRate" ]]; then
|
||||
echo "Invalid refresh rate value."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
update_resolution_config "$newWidth" "$newHeight" "$newRefreshRate"
|
||||
|
||||
echo "Resolution updated successfully."
|
||||
198
homes/me/ags-end4/modules/sideleft/tools/color.js
Normal file
198
homes/me/ags-end4/modules/sideleft/tools/color.js
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
// It's weird, I know
|
||||
const { Gio, GLib } = imports.gi;
|
||||
import Service from 'resource:///com/github/Aylur/ags/service.js';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
const { exec, execAsync } = Utils;
|
||||
import { clamp } from '../../.miscutils/mathfuncs.js';
|
||||
|
||||
export class ColorPickerSelection extends Service {
|
||||
static {
|
||||
Service.register(this, {
|
||||
'picked': [],
|
||||
'assigned': ['int'],
|
||||
'hue': [],
|
||||
'sl': [],
|
||||
});
|
||||
}
|
||||
|
||||
_hue = 198;
|
||||
_xAxis = 94;
|
||||
_yAxis = 80;
|
||||
|
||||
get hue() { return this._hue; }
|
||||
set hue(value) {
|
||||
this._hue = clamp(value, 0, 360);
|
||||
this.emit('hue');
|
||||
this.emit('picked');
|
||||
this.emit('changed');
|
||||
}
|
||||
get xAxis() { return this._xAxis; }
|
||||
set xAxis(value) {
|
||||
this._xAxis = clamp(value, 0, 100);
|
||||
this.emit('sl');
|
||||
this.emit('picked');
|
||||
this.emit('changed');
|
||||
}
|
||||
get yAxis() { return this._yAxis; }
|
||||
set yAxis(value) {
|
||||
this._yAxis = clamp(value, 0, 100);
|
||||
this.emit('sl');
|
||||
this.emit('picked');
|
||||
this.emit('changed');
|
||||
}
|
||||
setColorFromHex(hexString, id) {
|
||||
const hsl = hexToHSL(hexString);
|
||||
this._hue = hsl.hue;
|
||||
this._xAxis = hsl.saturation;
|
||||
// this._yAxis = hsl.lightness;
|
||||
this._yAxis = (100 - hsl.saturation / 2) / 100 * hsl.lightness;
|
||||
// console.log(this._hue, this._xAxis, this._yAxis)
|
||||
this.emit('assigned', id);
|
||||
this.emit('changed');
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.emit('changed');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function hslToRgbValues(h, s, l) {
|
||||
h /= 360;
|
||||
s /= 100;
|
||||
l /= 100;
|
||||
let r, g, b;
|
||||
if (s === 0) {
|
||||
r = g = b = l; // achromatic
|
||||
} else {
|
||||
const hue2rgb = (p, q, t) => {
|
||||
if (t < 0) t += 1;
|
||||
if (t > 1) t -= 1;
|
||||
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
||||
if (t < 1 / 2) return q;
|
||||
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
||||
return p;
|
||||
};
|
||||
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
||||
const p = 2 * l - q;
|
||||
r = hue2rgb(p, q, h + 1 / 3);
|
||||
g = hue2rgb(p, q, h);
|
||||
b = hue2rgb(p, q, h - 1 / 3);
|
||||
}
|
||||
const to255 = x => Math.round(x * 255);
|
||||
r = to255(r);
|
||||
g = to255(g);
|
||||
b = to255(b);
|
||||
return `${Math.round(r)},${Math.round(g)},${Math.round(b)}`;
|
||||
// return `rgb(${r},${g},${b})`;
|
||||
}
|
||||
export function hslToHex(h, s, l) {
|
||||
h /= 360;
|
||||
s /= 100;
|
||||
l /= 100;
|
||||
let r, g, b;
|
||||
if (s === 0) {
|
||||
r = g = b = l; // achromatic
|
||||
} else {
|
||||
const hue2rgb = (p, q, t) => {
|
||||
if (t < 0) t += 1;
|
||||
if (t > 1) t -= 1;
|
||||
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
||||
if (t < 1 / 2) return q;
|
||||
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
||||
return p;
|
||||
};
|
||||
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
||||
const p = 2 * l - q;
|
||||
r = hue2rgb(p, q, h + 1 / 3);
|
||||
g = hue2rgb(p, q, h);
|
||||
b = hue2rgb(p, q, h - 1 / 3);
|
||||
}
|
||||
const toHex = x => {
|
||||
const hex = Math.round(x * 255).toString(16);
|
||||
return hex.length === 1 ? "0" + hex : hex;
|
||||
};
|
||||
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
||||
}
|
||||
|
||||
// export function hexToHSL(hex) {
|
||||
// // Remove the '#' if present
|
||||
// hex = hex.replace(/^#/, '');
|
||||
// // Parse the hex value into RGB components
|
||||
// const bigint = parseInt(hex, 16);
|
||||
// const r = (bigint >> 16) & 255;
|
||||
// const g = (bigint >> 8) & 255;
|
||||
// const b = bigint & 255;
|
||||
// // Normalize RGB values to range [0, 1]
|
||||
// const normalizedR = r / 255;
|
||||
// const normalizedG = g / 255;
|
||||
// const normalizedB = b / 255;
|
||||
// // Find the maximum and minimum values
|
||||
// const max = Math.max(normalizedR, normalizedG, normalizedB);
|
||||
// const min = Math.min(normalizedR, normalizedG, normalizedB);
|
||||
// // Calculate the lightness
|
||||
// const lightness = (max + min) / 2;
|
||||
// // If the color is grayscale, set saturation to 0
|
||||
// if (max === min) {
|
||||
// return {
|
||||
// hue: 0,
|
||||
// saturation: 0,
|
||||
// lightness: lightness * 100 // Convert to percentage
|
||||
// };
|
||||
// }
|
||||
// // Calculate the saturation
|
||||
// const d = max - min;
|
||||
// const saturation = lightness > 0.5 ? d / (2 - max - min) : d / (max + min);
|
||||
// // Calculate the hue
|
||||
// let hue;
|
||||
// if (max === normalizedR) {
|
||||
// hue = ((normalizedG - normalizedB) / d + (normalizedG < normalizedB ? 6 : 0)) * 60;
|
||||
// } else if (max === normalizedG) {
|
||||
// hue = ((normalizedB - normalizedR) / d + 2) * 60;
|
||||
// } else {
|
||||
// hue = ((normalizedR - normalizedG) / d + 4) * 60;
|
||||
// }
|
||||
// return {
|
||||
// hue: Math.round(hue),
|
||||
// saturation: Math.round(saturation * 100), // Convert to percentage
|
||||
// lightness: Math.round(lightness * 100) // Convert to percentage
|
||||
// };
|
||||
// }
|
||||
|
||||
export function hexToHSL(hex) {
|
||||
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||
|
||||
var r = parseInt(result[1], 16);
|
||||
var g = parseInt(result[2], 16);
|
||||
var b = parseInt(result[3], 16);
|
||||
|
||||
r /= 255, g /= 255, b /= 255;
|
||||
var max = Math.max(r, g, b), min = Math.min(r, g, b);
|
||||
var h, s, l = (max + min) / 2;
|
||||
|
||||
if (max == min) {
|
||||
h = s = 0; // achromatic
|
||||
} else {
|
||||
var d = max - min;
|
||||
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
||||
switch (max) {
|
||||
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
|
||||
case g: h = (b - r) / d + 2; break;
|
||||
case b: h = (r - g) / d + 4; break;
|
||||
}
|
||||
h /= 6;
|
||||
}
|
||||
|
||||
s = s * 100;
|
||||
s = Math.round(s);
|
||||
l = l * 100;
|
||||
l = Math.round(l);
|
||||
h = Math.round(360 * h);
|
||||
|
||||
return {
|
||||
hue: h,
|
||||
saturation: s,
|
||||
lightness: l
|
||||
};
|
||||
}
|
||||
283
homes/me/ags-end4/modules/sideleft/tools/colorpicker.js
Normal file
283
homes/me/ags-end4/modules/sideleft/tools/colorpicker.js
Normal file
|
|
@ -0,0 +1,283 @@
|
|||
// TODO: Make selection update when entry changes
|
||||
const { Gtk } = imports.gi;
|
||||
import App from 'resource:///com/github/Aylur/ags/app.js';
|
||||
import Variable from 'resource:///com/github/Aylur/ags/variable.js';
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
const { execAsync, exec } = Utils;
|
||||
const { Box, Button, Entry, EventBox, Icon, Label, Overlay, Scrollable } = Widget;
|
||||
import SidebarModule from './module.js';
|
||||
import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
|
||||
import { setupCursorHover } from '../../.widgetutils/cursorhover.js';
|
||||
|
||||
import { ColorPickerSelection, hslToHex, hslToRgbValues, hexToHSL } from './color.js';
|
||||
import { clamp } from '../../.miscutils/mathfuncs.js';
|
||||
|
||||
export default () => {
|
||||
const selectedColor = new ColorPickerSelection();
|
||||
function shouldUseBlackColor() {
|
||||
return ((selectedColor.xAxis < 40 || (45 <= selectedColor.hue && selectedColor.hue <= 195)) &&
|
||||
selectedColor.yAxis > 60);
|
||||
}
|
||||
const colorBlack = 'rgba(0,0,0,0.9)';
|
||||
const colorWhite = 'rgba(255,255,255,0.9)';
|
||||
const hueRange = Box({
|
||||
homogeneous: true,
|
||||
className: 'sidebar-module-colorpicker-wrapper',
|
||||
children: [Box({
|
||||
className: 'sidebar-module-colorpicker-hue',
|
||||
css: `background: linear-gradient(to bottom, #ff6666, #ffff66, #66dd66, #66ffff, #6666ff, #ff66ff, #ff6666);`,
|
||||
})],
|
||||
});
|
||||
const hueSlider = Box({
|
||||
vpack: 'start',
|
||||
className: 'sidebar-module-colorpicker-cursorwrapper',
|
||||
css: `margin-top: ${13.636 * selectedColor.hue / 360}rem;`,
|
||||
homogeneous: true,
|
||||
children: [Box({
|
||||
className: 'sidebar-module-colorpicker-hue-cursor',
|
||||
})],
|
||||
setup: (self) => self.hook(selectedColor, () => {
|
||||
const widgetHeight = hueRange.children[0].get_allocated_height();
|
||||
self.setCss(`margin-top: ${13.636 * selectedColor.hue / 360}rem;`)
|
||||
}),
|
||||
});
|
||||
const hueSelector = Box({
|
||||
children: [EventBox({
|
||||
child: Overlay({
|
||||
child: hueRange,
|
||||
overlays: [hueSlider],
|
||||
}),
|
||||
attribute: {
|
||||
clicked: false,
|
||||
setHue: (self, event) => {
|
||||
const widgetHeight = hueRange.children[0].get_allocated_height();
|
||||
const [_, cursorX, cursorY] = event.get_coords();
|
||||
const cursorYPercent = clamp(cursorY / widgetHeight, 0, 1);
|
||||
selectedColor.hue = Math.round(cursorYPercent * 360);
|
||||
}
|
||||
},
|
||||
setup: (self) => self
|
||||
.on('motion-notify-event', (self, event) => {
|
||||
if (!self.attribute.clicked) return;
|
||||
self.attribute.setHue(self, event);
|
||||
})
|
||||
.on('button-press-event', (self, event) => {
|
||||
if (!(event.get_button()[1] === 1)) return; // We're only interested in left-click here
|
||||
self.attribute.clicked = true;
|
||||
self.attribute.setHue(self, event);
|
||||
})
|
||||
.on('button-release-event', (self) => self.attribute.clicked = false)
|
||||
,
|
||||
})]
|
||||
});
|
||||
const saturationAndLightnessRange = Box({
|
||||
homogeneous: true,
|
||||
children: [Box({
|
||||
className: 'sidebar-module-colorpicker-saturationandlightness',
|
||||
attribute: {
|
||||
update: (self) => {
|
||||
// css: `background: linear-gradient(to right, #ffffff, color);`,
|
||||
self.setCss(`background:
|
||||
linear-gradient(to bottom, rgba(0,0,0,0), rgba(0,0,0,1)),
|
||||
linear-gradient(to right, #ffffff, ${hslToHex(selectedColor.hue, 100, 50)});
|
||||
`);
|
||||
},
|
||||
},
|
||||
setup: (self) => self
|
||||
.hook(selectedColor, self.attribute.update, 'hue')
|
||||
.hook(selectedColor, self.attribute.update, 'assigned')
|
||||
,
|
||||
})],
|
||||
});
|
||||
const saturationAndLightnessCursor = Box({
|
||||
className: 'sidebar-module-colorpicker-saturationandlightness-cursorwrapper',
|
||||
children: [Box({
|
||||
vpack: 'start',
|
||||
hpack: 'start',
|
||||
homogeneous: true,
|
||||
css: `
|
||||
margin-left: ${13.636 * selectedColor.xAxis / 100}rem;
|
||||
margin-top: ${13.636 * (100 - selectedColor.yAxis) / 100}rem;
|
||||
`, // Why 13.636rem? see class name in stylesheet
|
||||
attribute: {
|
||||
update: (self) => {
|
||||
const allocation = saturationAndLightnessRange.children[0].get_allocation();
|
||||
self.setCss(`
|
||||
margin-left: ${13.636 * selectedColor.xAxis / 100}rem;
|
||||
margin-top: ${13.636 * (100 - selectedColor.yAxis) / 100}rem;
|
||||
`); // Why 13.636rem? see class name in stylesheet
|
||||
}
|
||||
},
|
||||
setup: (self) => self
|
||||
.hook(selectedColor, self.attribute.update, 'sl')
|
||||
.hook(selectedColor, self.attribute.update, 'assigned')
|
||||
,
|
||||
children: [Box({
|
||||
className: 'sidebar-module-colorpicker-saturationandlightness-cursor',
|
||||
css: `
|
||||
background-color: ${hslToHex(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100))};
|
||||
border-color: ${shouldUseBlackColor() ? colorBlack : colorWhite};
|
||||
`,
|
||||
attribute: {
|
||||
update: (self) => {
|
||||
self.setCss(`
|
||||
background-color: ${hslToHex(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100))};
|
||||
border-color: ${shouldUseBlackColor() ? colorBlack : colorWhite};
|
||||
`);
|
||||
}
|
||||
},
|
||||
setup: (self) => self
|
||||
.hook(selectedColor, self.attribute.update, 'sl')
|
||||
.hook(selectedColor, self.attribute.update, 'hue')
|
||||
.hook(selectedColor, self.attribute.update, 'assigned')
|
||||
,
|
||||
})],
|
||||
})]
|
||||
});
|
||||
const saturationAndLightnessSelector = Box({
|
||||
homogeneous: true,
|
||||
className: 'sidebar-module-colorpicker-saturationandlightness-wrapper',
|
||||
children: [EventBox({
|
||||
child: Overlay({
|
||||
child: saturationAndLightnessRange,
|
||||
overlays: [saturationAndLightnessCursor],
|
||||
}),
|
||||
attribute: {
|
||||
clicked: false,
|
||||
setSaturationAndLightness: (self, event) => {
|
||||
const allocation = saturationAndLightnessRange.children[0].get_allocation();
|
||||
const [_, cursorX, cursorY] = event.get_coords();
|
||||
const cursorXPercent = clamp(cursorX / allocation.width, 0, 1);
|
||||
const cursorYPercent = clamp(cursorY / allocation.height, 0, 1);
|
||||
selectedColor.xAxis = Math.round(cursorXPercent * 100);
|
||||
selectedColor.yAxis = Math.round(100 - cursorYPercent * 100);
|
||||
}
|
||||
},
|
||||
setup: (self) => self
|
||||
.on('motion-notify-event', (self, event) => {
|
||||
if (!self.attribute.clicked) return;
|
||||
self.attribute.setSaturationAndLightness(self, event);
|
||||
})
|
||||
.on('button-press-event', (self, event) => {
|
||||
if (!(event.get_button()[1] === 1)) return; // We're only interested in left-click here
|
||||
self.attribute.clicked = true;
|
||||
self.attribute.setSaturationAndLightness(self, event);
|
||||
})
|
||||
.on('button-release-event', (self) => self.attribute.clicked = false)
|
||||
,
|
||||
})]
|
||||
});
|
||||
const resultColorBox = Box({
|
||||
className: 'sidebar-module-colorpicker-result-box',
|
||||
homogeneous: true,
|
||||
css: `background-color: ${hslToHex(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100))};`,
|
||||
children: [Label({
|
||||
className: 'txt txt-small',
|
||||
label: getString('Result'),
|
||||
}),],
|
||||
attribute: {
|
||||
update: (self) => {
|
||||
self.setCss(`background-color: ${hslToHex(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100))};`);
|
||||
self.children[0].setCss(`color: ${shouldUseBlackColor() ? colorBlack : colorWhite};`)
|
||||
}
|
||||
},
|
||||
setup: (self) => self
|
||||
.hook(selectedColor, self.attribute.update, 'sl')
|
||||
.hook(selectedColor, self.attribute.update, 'hue')
|
||||
.hook(selectedColor, self.attribute.update, 'assigned')
|
||||
,
|
||||
});
|
||||
const ResultBox = ({ colorSystemName, updateCallback, copyCallback }) => Box({
|
||||
children: [
|
||||
Box({
|
||||
vertical: true,
|
||||
hexpand: true,
|
||||
children: [
|
||||
Label({
|
||||
xalign: 0,
|
||||
className: 'txt-tiny',
|
||||
label: colorSystemName,
|
||||
}),
|
||||
Overlay({
|
||||
child: Entry({
|
||||
widthChars: 10,
|
||||
className: 'txt-small techfont',
|
||||
attribute: {
|
||||
id: 0,
|
||||
update: updateCallback,
|
||||
},
|
||||
setup: (self) => self
|
||||
.hook(selectedColor, self.attribute.update, 'sl')
|
||||
.hook(selectedColor, self.attribute.update, 'hue')
|
||||
.hook(selectedColor, self.attribute.update, 'assigned')
|
||||
// .on('activate', (self) => {
|
||||
// const newColor = self.text;
|
||||
// if (newColor.length != 7) return;
|
||||
// selectedColor.setColorFromHex(self.text, self.attribute.id);
|
||||
// })
|
||||
,
|
||||
}),
|
||||
})
|
||||
]
|
||||
}),
|
||||
Button({
|
||||
child: MaterialIcon('content_copy', 'norm'),
|
||||
onClicked: (self) => {
|
||||
copyCallback(self);
|
||||
self.child.label = 'done';
|
||||
Utils.timeout(1000, () => self.child.label = 'content_copy');
|
||||
},
|
||||
setup: setupCursorHover,
|
||||
})
|
||||
]
|
||||
});
|
||||
const resultHex = ResultBox({
|
||||
colorSystemName: 'Hex',
|
||||
updateCallback: (self, id) => {
|
||||
if (id && self.attribute.id === id) return;
|
||||
self.text = hslToHex(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100));
|
||||
},
|
||||
copyCallback: () => Utils.execAsync(['wl-copy', `${hslToHex(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100))}`]),
|
||||
})
|
||||
const resultRgb = ResultBox({
|
||||
colorSystemName: 'RGB',
|
||||
updateCallback: (self, id) => {
|
||||
if (id && self.attribute.id === id) return;
|
||||
self.text = hslToRgbValues(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100));
|
||||
},
|
||||
copyCallback: () => Utils.execAsync(['wl-copy', `rgb(${hslToRgbValues(selectedColor.hue, selectedColor.xAxis, selectedColor.yAxis / (1 + selectedColor.xAxis / 100))})`]),
|
||||
})
|
||||
const resultHsl = ResultBox({
|
||||
colorSystemName: 'HSL',
|
||||
updateCallback: (self, id) => {
|
||||
if (id && self.attribute.id === id) return;
|
||||
self.text = `${selectedColor.hue},${selectedColor.xAxis}%,${Math.round(selectedColor.yAxis / (1 + selectedColor.xAxis / 100))}%`;
|
||||
},
|
||||
copyCallback: () => Utils.execAsync(['wl-copy', `hsl(${selectedColor.hue},${selectedColor.xAxis}%,${Math.round(selectedColor.yAxis / (1 + selectedColor.xAxis / 100))}%)`]),
|
||||
})
|
||||
const result = Box({
|
||||
className: 'sidebar-module-colorpicker-result-area spacing-v-5 txt',
|
||||
hexpand: true,
|
||||
vertical: true,
|
||||
children: [
|
||||
resultColorBox,
|
||||
resultHex,
|
||||
resultRgb,
|
||||
resultHsl,
|
||||
]
|
||||
})
|
||||
return SidebarModule({
|
||||
icon: MaterialIcon('colorize', 'norm'),
|
||||
name: getString('<span strikethrough="true">Inaccurate</span> Color picker'),
|
||||
revealChild: false,
|
||||
child: Box({
|
||||
className: 'spacing-h-5',
|
||||
children: [
|
||||
hueSelector,
|
||||
saturationAndLightnessSelector,
|
||||
result,
|
||||
]
|
||||
})
|
||||
});
|
||||
}
|
||||
57
homes/me/ags-end4/modules/sideleft/tools/module.js
Normal file
57
homes/me/ags-end4/modules/sideleft/tools/module.js
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import { setupCursorHover } from '../../.widgetutils/cursorhover.js';
|
||||
import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
|
||||
const { Box, Button, Icon, Label, Revealer } = Widget;
|
||||
|
||||
export default ({
|
||||
icon,
|
||||
name,
|
||||
child,
|
||||
revealChild = true,
|
||||
}) => {
|
||||
const headerButtonIcon = MaterialIcon(revealChild ? 'expand_less' : 'expand_more', 'norm');
|
||||
const header = Button({
|
||||
onClicked: () => {
|
||||
content.revealChild = !content.revealChild;
|
||||
headerButtonIcon.label = content.revealChild ? 'expand_less' : 'expand_more';
|
||||
},
|
||||
setup: setupCursorHover,
|
||||
child: Box({
|
||||
className: 'txt spacing-h-10',
|
||||
children: [
|
||||
icon,
|
||||
Label({
|
||||
className: 'txt-norm',
|
||||
label: `${name}`,
|
||||
useMarkup: true,
|
||||
}),
|
||||
Box({
|
||||
hexpand: true,
|
||||
}),
|
||||
Box({
|
||||
className: 'sidebar-module-btn-arrow',
|
||||
homogeneous: true,
|
||||
children: [headerButtonIcon],
|
||||
})
|
||||
]
|
||||
})
|
||||
});
|
||||
const content = Revealer({
|
||||
revealChild: revealChild,
|
||||
transition: 'slide_down',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
child: Box({
|
||||
className: 'margin-top-5',
|
||||
homogeneous: true,
|
||||
children: [child],
|
||||
}),
|
||||
});
|
||||
return Box({
|
||||
className: 'sidebar-module',
|
||||
vertical: true,
|
||||
children: [
|
||||
header,
|
||||
content,
|
||||
]
|
||||
});
|
||||
}
|
||||
26
homes/me/ags-end4/modules/sideleft/tools/name.js
Normal file
26
homes/me/ags-end4/modules/sideleft/tools/name.js
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
const { Gtk } = imports.gi;
|
||||
import App from 'resource:///com/github/Aylur/ags/app.js';
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
import { setupCursorHover } from '../../.widgetutils/cursorhover.js';
|
||||
const { execAsync, exec } = Utils;
|
||||
const { Box, Button, CenterBox, EventBox, Icon, Label, Scrollable } = Widget;
|
||||
|
||||
export default () => Box({
|
||||
className: 'txt sidebar-module techfont',
|
||||
children: [
|
||||
Label({
|
||||
label: getString('illogical-impulse')
|
||||
}),
|
||||
Box({ hexpand: true }),
|
||||
Button({
|
||||
className: 'sidebar-module-btn-arrow',
|
||||
onClicked: () => execAsync(['xdg-open', 'https://github.com/end-4/dots-hyprland']).catch(print),
|
||||
child: Icon({
|
||||
className: 'txt txt-norm',
|
||||
icon: 'github-symbolic',
|
||||
}),
|
||||
setup: setupCursorHover,
|
||||
})
|
||||
]
|
||||
})
|
||||
103
homes/me/ags-end4/modules/sideleft/tools/quickscripts.js
Normal file
103
homes/me/ags-end4/modules/sideleft/tools/quickscripts.js
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
const { Gtk } = imports.gi;
|
||||
import App from 'resource:///com/github/Aylur/ags/app.js';
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
const { execAsync, exec } = Utils;
|
||||
const { Box, Button, EventBox, Icon, Label, Scrollable } = Widget;
|
||||
import SidebarModule from './module.js';
|
||||
import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
|
||||
import { setupCursorHover } from '../../.widgetutils/cursorhover.js';
|
||||
|
||||
import { distroID, isArchDistro, isDebianDistro, hasFlatpak } from '../../.miscutils/system.js';
|
||||
|
||||
const scripts = [
|
||||
{
|
||||
icon: 'desktop-symbolic',
|
||||
name: getString('Change screen resolution'),
|
||||
command: `bash ${App.configDir}/modules/sideleft/tools/changeres.sh`,
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
icon: 'nixos-symbolic',
|
||||
name: getString('Trim system generations to 5'),
|
||||
command: `sudo ${App.configDir}/scripts/quickscripts/nixos-trim-generations.sh 5 0 system`,
|
||||
enabled: distroID == 'nixos',
|
||||
},
|
||||
{
|
||||
icon: 'nixos-symbolic',
|
||||
name: getString('Trim home manager generations to 5'),
|
||||
command: `${App.configDir}/scripts/quickscripts/nixos-trim-generations.sh 5 0 home-manager`,
|
||||
enabled: distroID == 'nixos',
|
||||
},
|
||||
{
|
||||
icon: 'ubuntu-symbolic',
|
||||
name: getString('Update packages'),
|
||||
command: `sudo apt update && sudo apt upgrade -y`,
|
||||
enabled: isDebianDistro,
|
||||
},
|
||||
{
|
||||
icon: 'fedora-symbolic',
|
||||
name: getString('Update packages'),
|
||||
command: `sudo dnf upgrade -y`,
|
||||
enabled: distroID == 'fedora',
|
||||
},
|
||||
{
|
||||
icon: 'arch-symbolic',
|
||||
name: getString('Update packages'),
|
||||
command: `sudo pacman -Syyu`,
|
||||
enabled: isArchDistro,
|
||||
},
|
||||
{
|
||||
icon: 'arch-symbolic',
|
||||
name: getString('Remove orphan packages'),
|
||||
command: `sudo pacman -R $(pacman -Qdtq)`,
|
||||
enabled: isArchDistro,
|
||||
},
|
||||
{
|
||||
icon: 'flatpak-symbolic',
|
||||
name: getString('Uninstall unused flatpak packages'),
|
||||
command: `flatpak uninstall --unused`,
|
||||
enabled: hasFlatpak,
|
||||
},
|
||||
];
|
||||
|
||||
export default () => SidebarModule({
|
||||
icon: MaterialIcon('code', 'norm'),
|
||||
name: getString('Quick scripts'),
|
||||
child: Box({
|
||||
vertical: true,
|
||||
className: 'spacing-v-5',
|
||||
children: scripts.map((script) => {
|
||||
if (!script.enabled) return null;
|
||||
const scriptStateIcon = MaterialIcon('not_started', 'norm');
|
||||
return Box({
|
||||
className: 'spacing-h-5 txt',
|
||||
children: [
|
||||
Icon({
|
||||
className: 'sidebar-module-btn-icon txt-large',
|
||||
icon: script.icon,
|
||||
}),
|
||||
Label({
|
||||
className: 'txt-small',
|
||||
hpack: 'start',
|
||||
hexpand: true,
|
||||
label: script.name,
|
||||
tooltipText: script.command,
|
||||
}),
|
||||
Button({
|
||||
className: 'sidebar-module-scripts-button',
|
||||
child: scriptStateIcon,
|
||||
onClicked: () => {
|
||||
closeEverything();
|
||||
execAsync([`bash`, `-c`, `${userOptions.apps.terminal} fish -C "${script.command}"`]).catch(print)
|
||||
.then(() => {
|
||||
scriptStateIcon.label = 'done';
|
||||
})
|
||||
},
|
||||
setup: setupCursorHover,
|
||||
}),
|
||||
],
|
||||
})
|
||||
}),
|
||||
})
|
||||
});
|
||||
203
homes/me/ags-end4/modules/sideright/calendar.js
Normal file
203
homes/me/ags-end4/modules/sideright/calendar.js
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
const { Gio } = imports.gi;
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
const { Box, Button, Label } = Widget;
|
||||
import { MaterialIcon } from '../.commonwidgets/materialicon.js';
|
||||
import { setupCursorHover } from '../.widgetutils/cursorhover.js';
|
||||
|
||||
import { TodoWidget } from "./todolist.js";
|
||||
import { getCalendarLayout } from "./calendar_layout.js";
|
||||
|
||||
let calendarJson = getCalendarLayout(undefined, true);
|
||||
let monthshift = 0;
|
||||
|
||||
function getDateInXMonthsTime(x) {
|
||||
var currentDate = new Date(); // Get the current date
|
||||
var targetMonth = currentDate.getMonth() + x; // Calculate the target month
|
||||
var targetYear = currentDate.getFullYear(); // Get the current year
|
||||
|
||||
// Adjust the year and month if necessary
|
||||
targetYear += Math.floor(targetMonth / 12);
|
||||
targetMonth = (targetMonth % 12 + 12) % 12;
|
||||
|
||||
// Create a new date object with the target year and month
|
||||
var targetDate = new Date(targetYear, targetMonth, 1);
|
||||
|
||||
// Set the day to the last day of the month to get the desired date
|
||||
// targetDate.setDate(0);
|
||||
|
||||
return targetDate;
|
||||
}
|
||||
|
||||
const weekDays = [ // MONDAY IS THE FIRST DAY OF THE WEEK :HESRIGHTYOUKNOW:
|
||||
{ day: getString('Mo'), today: 0 },
|
||||
{ day: getString('Tu'), today: 0 },
|
||||
{ day: getString('We'), today: 0 },
|
||||
{ day: getString('Th'), today: 0 },
|
||||
{ day: getString('Fr'), today: 0 },
|
||||
{ day: getString('Sa'), today: 0 },
|
||||
{ day: getString('Su'), today: 0 },
|
||||
]
|
||||
|
||||
const CalendarDay = (day, today) => Widget.Button({
|
||||
className: `sidebar-calendar-btn ${today == 1 ? 'sidebar-calendar-btn-today' : (today == -1 ? 'sidebar-calendar-btn-othermonth' : '')}`,
|
||||
child: Widget.Overlay({
|
||||
child: Box({}),
|
||||
overlays: [Label({
|
||||
hpack: 'center',
|
||||
className: 'txt-smallie txt-semibold sidebar-calendar-btn-txt',
|
||||
label: String(day),
|
||||
})],
|
||||
})
|
||||
})
|
||||
|
||||
const CalendarWidget = () => {
|
||||
const calendarMonthYear = Widget.Button({
|
||||
className: 'txt txt-large sidebar-calendar-monthyear-btn',
|
||||
onClicked: () => shiftCalendarXMonths(0),
|
||||
setup: (button) => {
|
||||
button.label = `${new Date().toLocaleString('default', { month: 'long' })} ${new Date().getFullYear()}`;
|
||||
setupCursorHover(button);
|
||||
}
|
||||
});
|
||||
const addCalendarChildren = (box, calendarJson) => {
|
||||
const children = box.get_children();
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i];
|
||||
child.destroy();
|
||||
}
|
||||
box.children = calendarJson.map((row, i) => Widget.Box({
|
||||
className: 'spacing-h-5',
|
||||
children: row.map((day, i) => CalendarDay(day.day, day.today)),
|
||||
}))
|
||||
}
|
||||
function shiftCalendarXMonths(x) {
|
||||
if (x == 0) monthshift = 0;
|
||||
else monthshift += x;
|
||||
var newDate;
|
||||
if (monthshift == 0) newDate = new Date();
|
||||
else newDate = getDateInXMonthsTime(monthshift);
|
||||
|
||||
calendarJson = getCalendarLayout(newDate, (monthshift == 0));
|
||||
calendarMonthYear.label = `${monthshift == 0 ? '' : '• '}${newDate.toLocaleString('default', { month: 'long' })} ${newDate.getFullYear()}`;
|
||||
addCalendarChildren(calendarDays, calendarJson);
|
||||
}
|
||||
const calendarHeader = Widget.Box({
|
||||
className: 'spacing-h-5 sidebar-calendar-header',
|
||||
setup: (box) => {
|
||||
box.pack_start(calendarMonthYear, false, false, 0);
|
||||
box.pack_end(Widget.Box({
|
||||
className: 'spacing-h-5',
|
||||
children: [
|
||||
Button({
|
||||
className: 'sidebar-calendar-monthshift-btn',
|
||||
onClicked: () => shiftCalendarXMonths(-1),
|
||||
child: MaterialIcon('chevron_left', 'norm'),
|
||||
setup: setupCursorHover,
|
||||
}),
|
||||
Button({
|
||||
className: 'sidebar-calendar-monthshift-btn',
|
||||
onClicked: () => shiftCalendarXMonths(1),
|
||||
child: MaterialIcon('chevron_right', 'norm'),
|
||||
setup: setupCursorHover,
|
||||
})
|
||||
]
|
||||
}), false, false, 0);
|
||||
}
|
||||
})
|
||||
const calendarDays = Widget.Box({
|
||||
hexpand: true,
|
||||
vertical: true,
|
||||
className: 'spacing-v-5',
|
||||
setup: (box) => {
|
||||
addCalendarChildren(box, calendarJson);
|
||||
}
|
||||
});
|
||||
return Widget.EventBox({
|
||||
onScrollUp: () => shiftCalendarXMonths(-1),
|
||||
onScrollDown: () => shiftCalendarXMonths(1),
|
||||
child: Widget.Box({
|
||||
hpack: 'center',
|
||||
children: [
|
||||
Widget.Box({
|
||||
hexpand: true,
|
||||
vertical: true,
|
||||
className: 'spacing-v-5',
|
||||
children: [
|
||||
calendarHeader,
|
||||
Widget.Box({
|
||||
homogeneous: true,
|
||||
className: 'spacing-h-5',
|
||||
children: weekDays.map((day, i) => CalendarDay(day.day, day.today))
|
||||
}),
|
||||
calendarDays,
|
||||
]
|
||||
})
|
||||
]
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
const defaultShown = 'calendar';
|
||||
const contentStack = Widget.Stack({
|
||||
hexpand: true,
|
||||
children: {
|
||||
'calendar': CalendarWidget(),
|
||||
'todo': TodoWidget(),
|
||||
// 'stars': Widget.Label({ label: 'GitHub feed will be here' }),
|
||||
},
|
||||
transition: 'slide_up_down',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
setup: (stack) => Utils.timeout(1, () => {
|
||||
stack.shown = defaultShown;
|
||||
})
|
||||
})
|
||||
|
||||
const StackButton = (stackItemName, icon, name) => Widget.Button({
|
||||
className: 'button-minsize sidebar-navrail-btn txt-small spacing-h-5',
|
||||
onClicked: (button) => {
|
||||
contentStack.shown = stackItemName;
|
||||
const kids = button.get_parent().get_children();
|
||||
for (let i = 0; i < kids.length; i++) {
|
||||
if (kids[i] != button) kids[i].toggleClassName('sidebar-navrail-btn-active', false);
|
||||
else button.toggleClassName('sidebar-navrail-btn-active', true);
|
||||
}
|
||||
},
|
||||
child: Box({
|
||||
className: 'spacing-v-5',
|
||||
vertical: true,
|
||||
children: [
|
||||
Label({
|
||||
className: `txt icon-material txt-hugeass`,
|
||||
label: icon,
|
||||
}),
|
||||
Label({
|
||||
label: name,
|
||||
className: 'txt txt-smallie',
|
||||
}),
|
||||
]
|
||||
}),
|
||||
setup: (button) => Utils.timeout(1, () => {
|
||||
setupCursorHover(button);
|
||||
button.toggleClassName('sidebar-navrail-btn-active', defaultShown === stackItemName);
|
||||
})
|
||||
});
|
||||
|
||||
export const ModuleCalendar = () => Box({
|
||||
className: 'sidebar-group spacing-h-5',
|
||||
setup: (box) => {
|
||||
box.pack_start(Box({
|
||||
vpack: 'center',
|
||||
homogeneous: true,
|
||||
vertical: true,
|
||||
className: 'sidebar-navrail spacing-v-10',
|
||||
children: [
|
||||
StackButton('calendar', 'calendar_month', getString('Calendar')),
|
||||
StackButton('todo', 'done_outline', getString('To Do')),
|
||||
// StackButton(box, 'stars', 'star', 'GitHub'),
|
||||
]
|
||||
}), false, false, 0);
|
||||
box.pack_end(contentStack, false, false, 0);
|
||||
}
|
||||
})
|
||||
|
||||
85
homes/me/ags-end4/modules/sideright/calendar_layout.js
Normal file
85
homes/me/ags-end4/modules/sideright/calendar_layout.js
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
function checkLeapYear(year) {
|
||||
return (
|
||||
year % 400 == 0 ||
|
||||
(year % 4 == 0 && year % 100 != 0));
|
||||
}
|
||||
|
||||
function getMonthDays(month, year) {
|
||||
const leapYear = checkLeapYear(year);
|
||||
if ((month <= 7 && month % 2 == 1) || (month >= 8 && month % 2 == 0)) return 31;
|
||||
if (month == 2 && leapYear) return 29;
|
||||
if (month == 2 && !leapYear) return 28;
|
||||
return 30;
|
||||
}
|
||||
|
||||
function getNextMonthDays(month, year) {
|
||||
const leapYear = checkLeapYear(year);
|
||||
if (month == 1 && leapYear) return 29;
|
||||
if (month == 1 && !leapYear) return 28;
|
||||
if (month == 12) return 31;
|
||||
if ((month <= 7 && month % 2 == 1) || (month >= 8 && month % 2 == 0)) return 30;
|
||||
return 31;
|
||||
}
|
||||
|
||||
function getPrevMonthDays(month, year) {
|
||||
const leapYear = checkLeapYear(year);
|
||||
if (month == 3 && leapYear) return 29;
|
||||
if (month == 3 && !leapYear) return 28;
|
||||
if (month == 1) return 31;
|
||||
if ((month <= 7 && month % 2 == 1) || (month >= 8 && month % 2 == 0)) return 30;
|
||||
return 31;
|
||||
}
|
||||
|
||||
export function getCalendarLayout(dateObject, highlight) {
|
||||
if (!dateObject) dateObject = new Date();
|
||||
const weekday = (dateObject.getDay() + 6) % 7; // MONDAY IS THE FIRST DAY OF THE WEEK
|
||||
const day = dateObject.getDate();
|
||||
const month = dateObject.getMonth() + 1;
|
||||
const year = dateObject.getFullYear();
|
||||
const weekdayOfMonthFirst = (weekday + 35 - (day - 1)) % 7;
|
||||
const daysInMonth = getMonthDays(month, year);
|
||||
const daysInNextMonth = getNextMonthDays(month, year);
|
||||
const daysInPrevMonth = getPrevMonthDays(month, year);
|
||||
|
||||
// Fill
|
||||
var monthDiff = (weekdayOfMonthFirst == 0 ? 0 : -1);
|
||||
var toFill, dim;
|
||||
if(weekdayOfMonthFirst == 0) {
|
||||
toFill = 1;
|
||||
dim = daysInMonth;
|
||||
}
|
||||
else {
|
||||
toFill = (daysInPrevMonth - (weekdayOfMonthFirst - 1));
|
||||
dim = daysInPrevMonth;
|
||||
}
|
||||
var calendar = [...Array(6)].map(() => Array(7));
|
||||
var i = 0, j = 0;
|
||||
while (i < 6 && j < 7) {
|
||||
calendar[i][j] = {
|
||||
"day": toFill,
|
||||
"today": ((toFill == day && monthDiff == 0 && highlight) ? 1 : (
|
||||
monthDiff == 0 ? 0 :
|
||||
-1
|
||||
))
|
||||
};
|
||||
// Increment
|
||||
toFill++;
|
||||
if (toFill > dim) { // Next month?
|
||||
monthDiff++;
|
||||
if (monthDiff == 0)
|
||||
dim = daysInMonth;
|
||||
else if (monthDiff == 1)
|
||||
dim = daysInNextMonth;
|
||||
toFill = 1;
|
||||
}
|
||||
// Next tile
|
||||
j++;
|
||||
if (j == 7) {
|
||||
j = 0;
|
||||
i++;
|
||||
}
|
||||
|
||||
}
|
||||
return calendar;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,217 @@
|
|||
import Audio from 'resource:///com/github/Aylur/ags/service/audio.js';
|
||||
import Variable from 'resource:///com/github/Aylur/ags/variable.js';
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
const { Box, Button, Icon, Label, Revealer, Scrollable, Slider, Stack } = Widget;
|
||||
import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
|
||||
import { setupCursorHover } from '../../.widgetutils/cursorhover.js';
|
||||
import { iconExists } from '../../.miscutils/icons.js';
|
||||
|
||||
const AppVolume = (stream) => Box({
|
||||
className: 'sidebar-volmixer-stream spacing-h-10',
|
||||
children: [
|
||||
Icon({
|
||||
className: 'sidebar-volmixer-stream-appicon',
|
||||
vpack: 'center',
|
||||
tooltipText: stream.stream.name,
|
||||
setup: (self) => {
|
||||
self.hook(stream, (self) => {
|
||||
self.icon = stream.stream.name.toLowerCase();
|
||||
})
|
||||
},
|
||||
}),
|
||||
Box({
|
||||
hexpand: true,
|
||||
vpack: 'center',
|
||||
vertical: true,
|
||||
className: 'spacing-v-5',
|
||||
children: [
|
||||
Label({
|
||||
xalign: 0,
|
||||
maxWidthChars: 1,
|
||||
truncate: 'end',
|
||||
label: stream.description,
|
||||
className: 'txt-small',
|
||||
setup: (self) => self.hook(stream, (self) => {
|
||||
self.label = `${stream.stream.name} • ${stream.description}`
|
||||
})
|
||||
}),
|
||||
Slider({
|
||||
drawValue: false,
|
||||
hpack: 'fill',
|
||||
className: 'sidebar-volmixer-stream-slider',
|
||||
value: stream.volume,
|
||||
min: 0, max: 1,
|
||||
onChange: ({ value }) => {
|
||||
stream.volume = value;
|
||||
},
|
||||
setup: (self) => self.hook(stream, (self) => {
|
||||
self.value = stream.volume;
|
||||
})
|
||||
}),
|
||||
// Box({
|
||||
// homogeneous: true,
|
||||
// className: 'test',
|
||||
// children: [AnimatedSlider({
|
||||
// className: 'sidebar-volmixer-stream-slider',
|
||||
// value: stream.volume,
|
||||
// })],
|
||||
// })
|
||||
]
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
const AudioDevices = (input = false) => {
|
||||
const dropdownShown = Variable(false);
|
||||
const DeviceStream = (stream) => Button({
|
||||
tooltipText: stream.description,
|
||||
child: Box({
|
||||
className: 'txt spacing-h-10',
|
||||
children: [
|
||||
iconExists(stream.iconName) ? Icon({
|
||||
className: 'txt-norm symbolic-icon',
|
||||
icon: stream.iconName,
|
||||
}) : MaterialIcon(input ? 'mic_external_on' : 'media_output', 'norm'),
|
||||
Label({
|
||||
hexpand: true,
|
||||
xalign: 0,
|
||||
className: 'txt-small',
|
||||
truncate: 'end',
|
||||
maxWidthChars: 1,
|
||||
label: stream.description,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
onClicked: (self) => {
|
||||
if (input) Audio.microphone = stream;
|
||||
else Audio.speaker = stream;
|
||||
dropdownShown.value = false;
|
||||
},
|
||||
setup: setupCursorHover,
|
||||
})
|
||||
const activeDevice = Button({
|
||||
onClicked: () => { dropdownShown.value = !dropdownShown.value; },
|
||||
child: Box({
|
||||
className: 'txt spacing-h-10',
|
||||
children: [
|
||||
MaterialIcon(input ? 'mic_external_on' : 'media_output', 'norm'),
|
||||
Label({
|
||||
hexpand: true,
|
||||
xalign: 0,
|
||||
className: 'txt-small',
|
||||
truncate: 'end',
|
||||
maxWidthChars: 1,
|
||||
label: `${input ? '[In]' : '[Out]'}`,
|
||||
setup: (self) => self.hook(Audio, (self) => {
|
||||
self.label = `${input ? '[In]' : '[Out]'} ${input ? Audio.microphone.description : Audio.speaker.description}`;
|
||||
})
|
||||
}),
|
||||
Label({
|
||||
className: `icon-material txt-norm`,
|
||||
setup: (self) => self.hook(dropdownShown, (self) => {
|
||||
self.label = dropdownShown.value ? 'expand_less' : 'expand_more';
|
||||
})
|
||||
})
|
||||
],
|
||||
}),
|
||||
setup: setupCursorHover,
|
||||
});
|
||||
const deviceSelector = Revealer({
|
||||
transition: 'slide_down',
|
||||
revealChild: dropdownShown.bind("value"),
|
||||
transitionDuration: userOptions.animations.durationSmall,
|
||||
child: Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
Box({ className: 'separator-line margin-top-5 margin-bottom-5' }),
|
||||
Box({
|
||||
vertical: true,
|
||||
className: 'spacing-v-5 margin-top-5',
|
||||
attribute: {
|
||||
'updateStreams': (self) => {
|
||||
const streams = input ? Audio.microphones : Audio.speakers;
|
||||
self.children = streams.map(stream => DeviceStream(stream));
|
||||
},
|
||||
},
|
||||
setup: (self) => self
|
||||
.hook(Audio, self.attribute.updateStreams, 'stream-added')
|
||||
.hook(Audio, self.attribute.updateStreams, 'stream-removed')
|
||||
,
|
||||
}),
|
||||
]
|
||||
})
|
||||
})
|
||||
return Box({
|
||||
hpack: 'fill',
|
||||
className: 'sidebar-volmixer-deviceselector',
|
||||
vertical: true,
|
||||
children: [
|
||||
activeDevice,
|
||||
deviceSelector,
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
export default (props) => {
|
||||
const emptyContent = Box({
|
||||
homogeneous: true,
|
||||
children: [Box({
|
||||
vertical: true,
|
||||
vpack: 'center',
|
||||
className: 'txt spacing-v-10',
|
||||
children: [
|
||||
Box({
|
||||
vertical: true,
|
||||
className: 'spacing-v-5 txt-subtext',
|
||||
children: [
|
||||
MaterialIcon('brand_awareness', 'gigantic'),
|
||||
Label({ label: getString('No audio source'), className: 'txt-small' }),
|
||||
]
|
||||
}),
|
||||
]
|
||||
})]
|
||||
});
|
||||
const appList = Scrollable({
|
||||
vexpand: true,
|
||||
child: Box({
|
||||
attribute: {
|
||||
'updateStreams': (self) => {
|
||||
const streams = Audio.apps;
|
||||
self.children = streams.map(stream => AppVolume(stream));
|
||||
},
|
||||
},
|
||||
vertical: true,
|
||||
className: 'spacing-v-5',
|
||||
setup: (self) => self
|
||||
.hook(Audio, self.attribute.updateStreams, 'stream-added')
|
||||
.hook(Audio, self.attribute.updateStreams, 'stream-removed')
|
||||
,
|
||||
})
|
||||
})
|
||||
const devices = Box({
|
||||
vertical: true,
|
||||
className: 'spacing-v-5',
|
||||
children: [
|
||||
AudioDevices(false),
|
||||
AudioDevices(true),
|
||||
]
|
||||
})
|
||||
const mainContent = Stack({
|
||||
children: {
|
||||
'empty': emptyContent,
|
||||
'list': appList,
|
||||
},
|
||||
setup: (self) => self.hook(Audio, (self) => {
|
||||
self.shown = (Audio.apps.length > 0 ? 'list' : 'empty')
|
||||
}),
|
||||
})
|
||||
return Box({
|
||||
...props,
|
||||
className: 'spacing-v-5',
|
||||
vertical: true,
|
||||
children: [
|
||||
mainContent,
|
||||
devices,
|
||||
]
|
||||
});
|
||||
}
|
||||
160
homes/me/ags-end4/modules/sideright/centermodules/bluetooth.js
Normal file
160
homes/me/ags-end4/modules/sideright/centermodules/bluetooth.js
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import Bluetooth from 'resource:///com/github/Aylur/ags/service/bluetooth.js';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
const { Box, Button, Icon, Label, Scrollable, Slider, Stack, Overlay } = Widget;
|
||||
const { execAsync, exec } = Utils;
|
||||
import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
|
||||
import { setupCursorHover } from '../../.widgetutils/cursorhover.js';
|
||||
import { ConfigToggle } from '../../.commonwidgets/configwidgets.js';
|
||||
|
||||
// can't connect: sync_problem
|
||||
|
||||
const USE_SYMBOLIC_ICONS = true;
|
||||
|
||||
const BluetoothDevice = (device) => {
|
||||
// console.log(device);
|
||||
const deviceIcon = Icon({
|
||||
className: 'sidebar-bluetooth-appicon',
|
||||
vpack: 'center',
|
||||
tooltipText: device.name,
|
||||
setup: (self) => self.hook(device, (self) => {
|
||||
self.icon = `${device.iconName}${USE_SYMBOLIC_ICONS ? '-symbolic' : ''}`;
|
||||
}),
|
||||
});
|
||||
const deviceStatus = Box({
|
||||
hexpand: true,
|
||||
vpack: 'center',
|
||||
vertical: true,
|
||||
children: [
|
||||
Label({
|
||||
xalign: 0,
|
||||
maxWidthChars: 1,
|
||||
truncate: 'end',
|
||||
label: device.name,
|
||||
className: 'txt-small',
|
||||
setup: (self) => self.hook(device, (self) => {
|
||||
self.label = device.name;
|
||||
}),
|
||||
}),
|
||||
Label({
|
||||
xalign: 0,
|
||||
maxWidthChars: 1,
|
||||
truncate: 'end',
|
||||
label: device.connected ? 'Connected' : (device.paired ? 'Paired' : ''),
|
||||
className: 'txt-subtext',
|
||||
setup: (self) => self.hook(device, (self) => {
|
||||
self.label = device.connected ? getString('Connected') : (device.paired ? getString('Paired') : '');
|
||||
}),
|
||||
}),
|
||||
]
|
||||
});
|
||||
const deviceConnectButton = ConfigToggle({
|
||||
vpack: 'center',
|
||||
expandWidget: false,
|
||||
desc: 'Toggle connection',
|
||||
initValue: device.connected,
|
||||
onChange: (self, newValue) => {
|
||||
device.setConnection(newValue);
|
||||
},
|
||||
extraSetup: (self) => self.hook(device, (self) => {
|
||||
Utils.timeout(200, () => self.enabled.value = device.connected);
|
||||
}),
|
||||
})
|
||||
const deviceRemoveButton = Button({
|
||||
vpack: 'center',
|
||||
className: 'sidebar-bluetooth-device-remove',
|
||||
child: MaterialIcon('delete', 'norm'),
|
||||
tooltipText: getString('Remove device'),
|
||||
setup: setupCursorHover,
|
||||
onClicked: () => execAsync(['bluetoothctl', 'remove', device.address]).catch(print),
|
||||
});
|
||||
return Box({
|
||||
className: 'sidebar-bluetooth-device spacing-h-10',
|
||||
children: [
|
||||
deviceIcon,
|
||||
deviceStatus,
|
||||
Box({
|
||||
className: 'spacing-h-5',
|
||||
children: [
|
||||
deviceConnectButton,
|
||||
deviceRemoveButton,
|
||||
]
|
||||
})
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
export default (props) => {
|
||||
const emptyContent = Box({
|
||||
homogeneous: true,
|
||||
children: [Box({
|
||||
vertical: true,
|
||||
vpack: 'center',
|
||||
className: 'txt spacing-v-10',
|
||||
children: [
|
||||
Box({
|
||||
vertical: true,
|
||||
className: 'spacing-v-5 txt-subtext',
|
||||
children: [
|
||||
MaterialIcon('bluetooth_disabled', 'gigantic'),
|
||||
Label({ label: 'No Bluetooth devices', className: 'txt-small' }),
|
||||
]
|
||||
}),
|
||||
]
|
||||
})]
|
||||
});
|
||||
const deviceList = Overlay({
|
||||
passThrough: true,
|
||||
child: Scrollable({
|
||||
vexpand: true,
|
||||
child: Box({
|
||||
attribute: {
|
||||
'updateDevices': (self) => {
|
||||
const devices = Bluetooth.devices;
|
||||
self.children = devices.map(d => BluetoothDevice(d));
|
||||
},
|
||||
},
|
||||
vertical: true,
|
||||
className: 'spacing-v-5 margin-bottom-15',
|
||||
setup: (self) => self
|
||||
.hook(Bluetooth, self.attribute.updateDevices, 'device-added')
|
||||
.hook(Bluetooth, self.attribute.updateDevices, 'device-removed')
|
||||
,
|
||||
})
|
||||
}),
|
||||
overlays: [Box({
|
||||
className: 'sidebar-centermodules-scrollgradient-bottom'
|
||||
})]
|
||||
});
|
||||
const mainContent = Stack({
|
||||
children: {
|
||||
'empty': emptyContent,
|
||||
'list': deviceList,
|
||||
},
|
||||
setup: (self) => self.hook(Bluetooth, (self) => {
|
||||
self.shown = (Bluetooth.devices.length > 0 ? 'list' : 'empty')
|
||||
}),
|
||||
})
|
||||
const bottomBar = Box({
|
||||
homogeneous: true,
|
||||
children: [Button({
|
||||
hpack: 'center',
|
||||
className: 'txt-small txt sidebar-centermodules-bottombar-button',
|
||||
onClicked: () => {
|
||||
execAsync(['bash', '-c', userOptions.apps.bluetooth]).catch(print);
|
||||
closeEverything();
|
||||
},
|
||||
label: getString('More'),
|
||||
setup: setupCursorHover,
|
||||
})],
|
||||
})
|
||||
return Box({
|
||||
...props,
|
||||
className: 'spacing-v-5',
|
||||
vertical: true,
|
||||
children: [
|
||||
mainContent,
|
||||
bottomBar
|
||||
]
|
||||
});
|
||||
}
|
||||
130
homes/me/ags-end4/modules/sideright/centermodules/configure.js
Normal file
130
homes/me/ags-end4/modules/sideright/centermodules/configure.js
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
const { GLib } = imports.gi;
|
||||
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
const { Box, Button, Icon, Label, Scrollable, Slider, Stack } = Widget;
|
||||
const { execAsync, exec } = Utils;
|
||||
import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
|
||||
import { setupCursorHover } from '../../.widgetutils/cursorhover.js';
|
||||
import { ConfigGap, ConfigSpinButton, ConfigToggle } from '../../.commonwidgets/configwidgets.js';
|
||||
|
||||
const HyprlandToggle = ({ icon, name, desc = null, option, enableValue = 1, disableValue = 0, extraOnChange = () => { } }) => ConfigToggle({
|
||||
icon: icon,
|
||||
name: name,
|
||||
desc: desc,
|
||||
initValue: JSON.parse(exec(`hyprctl getoption -j ${option}`))["int"] != 0,
|
||||
onChange: (self, newValue) => {
|
||||
execAsync(['hyprctl', 'keyword', option, `${newValue ? enableValue : disableValue}`]).catch(print);
|
||||
extraOnChange(self, newValue);
|
||||
}
|
||||
});
|
||||
|
||||
const HyprlandSpinButton = ({ icon, name, desc = null, option, ...rest }) => ConfigSpinButton({
|
||||
icon: icon,
|
||||
name: name,
|
||||
desc: desc,
|
||||
initValue: Number(JSON.parse(exec(`hyprctl getoption -j ${option}`))["int"]),
|
||||
onChange: (self, newValue) => {
|
||||
execAsync(['hyprctl', 'keyword', option, `${newValue}`]).catch(print);
|
||||
},
|
||||
...rest,
|
||||
});
|
||||
|
||||
const Subcategory = (children) => Box({
|
||||
className: 'margin-left-20',
|
||||
vertical: true,
|
||||
children: children,
|
||||
})
|
||||
|
||||
export default (props) => {
|
||||
const ConfigSection = ({ name, children }) => Box({
|
||||
vertical: true,
|
||||
className: 'spacing-v-5',
|
||||
children: [
|
||||
Label({
|
||||
hpack: 'center',
|
||||
className: 'txt txt-large margin-left-10',
|
||||
label: name,
|
||||
}),
|
||||
Box({
|
||||
className: 'margin-left-10 margin-right-10',
|
||||
vertical: true,
|
||||
children: children,
|
||||
})
|
||||
]
|
||||
})
|
||||
const mainContent = Scrollable({
|
||||
vexpand: true,
|
||||
child: Box({
|
||||
vertical: true,
|
||||
className: 'spacing-v-10',
|
||||
children: [
|
||||
ConfigSection({
|
||||
name: getString('Effects'), children: [
|
||||
ConfigToggle({
|
||||
icon: 'border_clear',
|
||||
name: getString('Transparency'),
|
||||
desc: getString('[AGS]\nMake shell elements transparent\nBlur is also recommended if you enable this'),
|
||||
initValue: exec(`bash -c "sed -n \'2p\' ${GLib.get_user_state_dir()}/ags/user/colormode.txt"`) == "transparent",
|
||||
onChange: (self, newValue) => {
|
||||
const transparency = newValue == 0 ? "opaque" : "transparent";
|
||||
console.log(transparency);
|
||||
execAsync([`bash`, `-c`, `mkdir -p ${GLib.get_user_state_dir()}/ags/user && sed -i "2s/.*/${transparency}/" ${GLib.get_user_state_dir()}/ags/user/colormode.txt`])
|
||||
.then(execAsync(['bash', '-c', `${App.configDir}/scripts/color_generation/switchcolor.sh`]))
|
||||
.catch(print);
|
||||
},
|
||||
}),
|
||||
HyprlandToggle({ icon: 'blur_on', name: getString('Blur'), desc: getString("[Hyprland]\nEnable blur on transparent elements\nDoesn't affect performance/power consumption unless you have transparent windows."), option: "decoration:blur:enabled" }),
|
||||
Subcategory([
|
||||
HyprlandToggle({ icon: 'stack_off', name: getString('X-ray'), desc: getString("[Hyprland]\nMake everything behind a window/layer except the wallpaper not rendered on its blurred surface\nRecommended to improve performance (if you don't abuse transparency/blur) "), option: "decoration:blur:xray" }),
|
||||
HyprlandSpinButton({ icon: 'target', name: getString('Size'), desc: getString('[Hyprland]\nAdjust the blur radius. Generally doesn\'t affect performance\nHigher = more color spread'), option: 'decoration:blur:size', minValue: 1, maxValue: 1000 }),
|
||||
HyprlandSpinButton({ icon: 'repeat', name: getString('Passes'), desc: getString('[Hyprland] Adjust the number of runs of the blur algorithm\nMore passes = more spread and power consumption\n4 is recommended\n2- would look weird and 6+ would look lame.'), option: 'decoration:blur:passes', minValue: 1, maxValue: 10 }),
|
||||
]),
|
||||
ConfigGap({}),
|
||||
HyprlandToggle({
|
||||
icon: 'animation', name: getString('Animations'), desc: getString('[Hyprland] [GTK]\nEnable animations'), option: 'animations:enabled',
|
||||
extraOnChange: (self, newValue) => execAsync(['gsettings', 'set', 'org.gnome.desktop.interface', 'enable-animations', `${newValue}`])
|
||||
}),
|
||||
Subcategory([
|
||||
ConfigSpinButton({
|
||||
icon: 'clear_all',
|
||||
name: getString('Choreography delay'),
|
||||
desc: getString('In milliseconds, the delay between animations of a series'),
|
||||
initValue: userOptions.animations.choreographyDelay,
|
||||
step: 10, minValue: 0, maxValue: 1000,
|
||||
onChange: (self, newValue) => {
|
||||
userOptions.animations.choreographyDelay = newValue
|
||||
},
|
||||
})
|
||||
]),
|
||||
]
|
||||
}),
|
||||
ConfigSection({
|
||||
name: getString('Developer'), children: [
|
||||
HyprlandToggle({ icon: 'speed', name: getString('Show FPS'), desc: getString("[Hyprland]\nShow FPS overlay on top-left corner"), option: "debug:overlay" }),
|
||||
HyprlandToggle({ icon: 'sort', name: getString('Log to stdout'), desc: getString("[Hyprland]\nPrint LOG, ERR, WARN, etc. messages to the console"), option: "debug:enable_stdout_logs" }),
|
||||
HyprlandToggle({ icon: 'motion_sensor_active', name: getString('Damage tracking'), desc: getString("[Hyprland]\nEnable damage tracking\nGenerally, leave it on.\nTurn off only when a shader doesn't work"), option: "debug:damage_tracking", enableValue: 2 }),
|
||||
HyprlandToggle({ icon: 'destruction', name: getString('Damage blink'), desc: getString("[Hyprland] [Epilepsy warning!]\nShow screen damage flashes"), option: "debug:damage_blink" }),
|
||||
]
|
||||
}),
|
||||
]
|
||||
})
|
||||
});
|
||||
const footNote = Box({
|
||||
homogeneous: true,
|
||||
children: [Label({
|
||||
hpack: 'center',
|
||||
className: 'txt txt-italic txt-subtext margin-5',
|
||||
label: getString('Not all changes are saved'),
|
||||
})]
|
||||
})
|
||||
return Box({
|
||||
...props,
|
||||
className: 'spacing-v-5',
|
||||
vertical: true,
|
||||
children: [
|
||||
mainContent,
|
||||
footNote,
|
||||
]
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,176 @@
|
|||
// This file is for the notification list on the sidebar
|
||||
// For the popup notifications, see onscreendisplay.js
|
||||
// The actual widget for each single notification is in ags/modules/.commonwidgets/notification.js
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import Notifications from 'resource:///com/github/Aylur/ags/service/notifications.js';
|
||||
const { Box, Button, Label, Revealer, Scrollable, Stack } = Widget;
|
||||
import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
|
||||
import { setupCursorHover } from '../../.widgetutils/cursorhover.js';
|
||||
import Notification from '../../.commonwidgets/notification.js';
|
||||
import { ConfigToggle } from '../../.commonwidgets/configwidgets.js';
|
||||
|
||||
export default (props) => {
|
||||
const notifEmptyContent = Box({
|
||||
homogeneous: true,
|
||||
children: [Box({
|
||||
vertical: true,
|
||||
vpack: 'center',
|
||||
className: 'txt spacing-v-10',
|
||||
children: [
|
||||
Box({
|
||||
vertical: true,
|
||||
className: 'spacing-v-5 txt-subtext',
|
||||
children: [
|
||||
MaterialIcon('notifications_active', 'gigantic'),
|
||||
Label({ label: getString('No notifications'), className: 'txt-small' }),
|
||||
]
|
||||
}),
|
||||
]
|
||||
})]
|
||||
});
|
||||
const notificationList = Box({
|
||||
vertical: true,
|
||||
vpack: 'start',
|
||||
className: 'spacing-v-5-revealer',
|
||||
setup: (self) => self
|
||||
.hook(Notifications, (box, id) => {
|
||||
if (box.get_children().length == 0) { // On init there's no notif, or 1st notif
|
||||
Notifications.notifications
|
||||
.forEach(n => {
|
||||
box.pack_end(Notification({
|
||||
notifObject: n,
|
||||
isPopup: false,
|
||||
}), false, false, 0)
|
||||
});
|
||||
box.show_all();
|
||||
return;
|
||||
}
|
||||
// 2nd or later notif
|
||||
const notif = Notifications.getNotification(id);
|
||||
const NewNotif = Notification({
|
||||
notifObject: notif,
|
||||
isPopup: false,
|
||||
});
|
||||
if (NewNotif) {
|
||||
box.pack_end(NewNotif, false, false, 0);
|
||||
box.show_all();
|
||||
}
|
||||
}, 'notified')
|
||||
.hook(Notifications, (box, id) => {
|
||||
if (!id) return;
|
||||
for (const ch of box.children) {
|
||||
if (ch._id === id) {
|
||||
ch.attribute.destroyWithAnims();
|
||||
}
|
||||
}
|
||||
}, 'closed')
|
||||
,
|
||||
});
|
||||
const ListActionButton = (icon, name, action) => Button({
|
||||
className: 'sidebar-centermodules-bottombar-button',
|
||||
onClicked: action,
|
||||
child: Box({
|
||||
hpack: 'center',
|
||||
className: 'spacing-h-5',
|
||||
children: [
|
||||
MaterialIcon(icon, 'norm'),
|
||||
Label({
|
||||
className: 'txt-small',
|
||||
label: name,
|
||||
})
|
||||
]
|
||||
}),
|
||||
setup: setupCursorHover,
|
||||
});
|
||||
const silenceButton = ListActionButton('notifications_paused', getString('Silence'), (self) => {
|
||||
Notifications.dnd = !Notifications.dnd;
|
||||
self.toggleClassName('notif-listaction-btn-enabled', Notifications.dnd);
|
||||
});
|
||||
// const silenceToggle = ConfigToggle({
|
||||
// expandWidget: false,
|
||||
// icon: 'do_not_disturb_on',
|
||||
// name: 'Do Not Disturb',
|
||||
// initValue: false,
|
||||
// onChange: (self, newValue) => {
|
||||
// Notifications.dnd = newValue;
|
||||
// },
|
||||
// })
|
||||
const clearButton = Revealer({
|
||||
transition: 'slide_right',
|
||||
transitionDuration: userOptions.animations.durationSmall,
|
||||
setup: (self) => self.hook(Notifications, (self) => {
|
||||
self.revealChild = Notifications.notifications.length > 0;
|
||||
}),
|
||||
child: ListActionButton('clear_all', getString('Clear'), () => {
|
||||
Notifications.clear();
|
||||
const kids = notificationList.get_children();
|
||||
for (let i = 0; i < kids.length; i++) {
|
||||
const kid = kids[i];
|
||||
Utils.timeout(userOptions.animations.choreographyDelay * i, () => kid.attribute.destroyWithAnims());
|
||||
}
|
||||
})
|
||||
})
|
||||
const notifCount = Label({
|
||||
attribute: {
|
||||
updateCount: (self) => {
|
||||
const count = Notifications.notifications.length;
|
||||
if (count > 0) self.label = `${count} ${getString("notifications")}`;
|
||||
else self.label = '';
|
||||
},
|
||||
},
|
||||
hexpand: true,
|
||||
xalign: 0,
|
||||
className: 'txt-small margin-left-10',
|
||||
label: `${Notifications.notifications.length}`,
|
||||
setup: (self) => self
|
||||
.hook(Notifications, (box, id) => self.attribute.updateCount(self), 'notified')
|
||||
.hook(Notifications, (box, id) => self.attribute.updateCount(self), 'dismissed')
|
||||
.hook(Notifications, (box, id) => self.attribute.updateCount(self), 'closed')
|
||||
,
|
||||
});
|
||||
const listTitle = Box({
|
||||
vpack: 'start',
|
||||
className: 'txt spacing-h-5',
|
||||
children: [
|
||||
notifCount,
|
||||
silenceButton,
|
||||
// silenceToggle,
|
||||
// Box({ hexpand: true }),
|
||||
clearButton,
|
||||
]
|
||||
});
|
||||
const notifList = Scrollable({
|
||||
hexpand: true,
|
||||
hscroll: 'never',
|
||||
vscroll: 'automatic',
|
||||
child: Box({
|
||||
vexpand: true,
|
||||
homogeneous: true,
|
||||
children: [notificationList],
|
||||
}),
|
||||
setup: (self) => {
|
||||
const vScrollbar = self.get_vscrollbar();
|
||||
vScrollbar.get_style_context().add_class('sidebar-scrollbar');
|
||||
}
|
||||
});
|
||||
const listContents = Stack({
|
||||
transition: 'crossfade',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
children: {
|
||||
'empty': notifEmptyContent,
|
||||
'list': notifList,
|
||||
},
|
||||
setup: (self) => self.hook(Notifications, (self) => {
|
||||
self.shown = (Notifications.notifications.length > 0 ? 'list' : 'empty')
|
||||
}),
|
||||
});
|
||||
return Box({
|
||||
...props,
|
||||
className: 'spacing-v-5',
|
||||
vertical: true,
|
||||
children: [
|
||||
listContents,
|
||||
listTitle,
|
||||
]
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,216 @@
|
|||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import Network from "resource:///com/github/Aylur/ags/service/network.js";
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
const { Box, Button, Entry, Icon, Label, Revealer, Scrollable, Slider, Stack, Overlay } = Widget;
|
||||
const { execAsync, exec } = Utils;
|
||||
import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
|
||||
import { setupCursorHover } from '../../.widgetutils/cursorhover.js';
|
||||
import { ConfigToggle } from '../../.commonwidgets/configwidgets.js';
|
||||
|
||||
const MATERIAL_SYMBOL_SIGNAL_STRENGTH = {
|
||||
'network-wireless-signal-excellent-symbolic': "signal_wifi_4_bar",
|
||||
'network-wireless-signal-good-symbolic': "network_wifi_3_bar",
|
||||
'network-wireless-signal-ok-symbolic': "network_wifi_2_bar",
|
||||
'network-wireless-signal-weak-symbolic': "network_wifi_1_bar",
|
||||
'network-wireless-signal-none-symbolic': "signal_wifi_0_bar",
|
||||
}
|
||||
|
||||
let connectAttempt = '';
|
||||
|
||||
const WifiNetwork = (accessPoint) => {
|
||||
const networkStrength = MaterialIcon(MATERIAL_SYMBOL_SIGNAL_STRENGTH[accessPoint.iconName], 'hugerass')
|
||||
const networkName = Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
Label({
|
||||
hpack: 'start',
|
||||
label: accessPoint.ssid
|
||||
}),
|
||||
accessPoint.active ? Label({
|
||||
hpack: 'start',
|
||||
className: 'txt-smaller txt-subtext',
|
||||
label: getString("Selected"),
|
||||
}) : null,
|
||||
]
|
||||
});
|
||||
return Button({
|
||||
onClicked: accessPoint.active ? () => { } : () => execAsync(`nmcli device wifi connect ${accessPoint.bssid}`)
|
||||
// .catch(e => {
|
||||
// Utils.notify({
|
||||
// summary: "Network",
|
||||
// body: e,
|
||||
// actions: { "Open network manager": () => execAsync("nm-connection-editor").catch(print) }
|
||||
// });
|
||||
// })
|
||||
.catch(print),
|
||||
child: Box({
|
||||
className: 'sidebar-wifinetworks-network spacing-h-10',
|
||||
children: [
|
||||
networkStrength,
|
||||
networkName,
|
||||
Box({ hexpand: true }),
|
||||
accessPoint.active ? MaterialIcon('check', 'large') : null,
|
||||
],
|
||||
}),
|
||||
setup: accessPoint.active ? () => { } : setupCursorHover,
|
||||
})
|
||||
}
|
||||
|
||||
const CurrentNetwork = () => {
|
||||
let authLock = false;
|
||||
// console.log(Network.wifi);
|
||||
const bottomSeparator = Box({
|
||||
className: 'separator-line',
|
||||
});
|
||||
const networkName = Box({
|
||||
vertical: true,
|
||||
hexpand: true,
|
||||
children: [
|
||||
Label({
|
||||
hpack: 'start',
|
||||
className: 'txt-smaller txt-subtext',
|
||||
label: getString("Current network"),
|
||||
}),
|
||||
Label({
|
||||
hpack: 'start',
|
||||
label: Network.wifi?.ssid,
|
||||
setup: (self) => self.hook(Network, (self) => {
|
||||
if (authLock) return;
|
||||
self.label = Network.wifi?.ssid;
|
||||
}),
|
||||
}),
|
||||
]
|
||||
});
|
||||
const networkStatus = Box({
|
||||
children: [Label({
|
||||
vpack: 'center',
|
||||
className: 'txt-subtext',
|
||||
setup: (self) => self.hook(Network, (self) => {
|
||||
if (authLock) return;
|
||||
self.label = Network.wifi.state;
|
||||
}),
|
||||
})]
|
||||
})
|
||||
const networkAuth = Revealer({
|
||||
transition: 'slide_down',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
child: Box({
|
||||
className: 'margin-top-10 spacing-v-5',
|
||||
vertical: true,
|
||||
children: [
|
||||
Label({
|
||||
className: 'margin-left-5',
|
||||
hpack: 'start',
|
||||
label: getString("Authentication"),
|
||||
}),
|
||||
Entry({
|
||||
className: 'sidebar-wifinetworks-auth-entry',
|
||||
visibility: false, // Password dots
|
||||
onAccept: (self) => {
|
||||
authLock = false;
|
||||
networkAuth.revealChild = false;
|
||||
execAsync(`nmcli device wifi connect '${connectAttempt}' password '${self.text}'`)
|
||||
.catch(print);
|
||||
}
|
||||
})
|
||||
]
|
||||
}),
|
||||
setup: (self) => self.hook(Network, (self) => {
|
||||
if (Network.wifi.state == 'failed' || Network.wifi.state == 'need_auth') {
|
||||
authLock = true;
|
||||
connectAttempt = Network.wifi.ssid;
|
||||
self.revealChild = true;
|
||||
}
|
||||
}),
|
||||
});
|
||||
const actualContent = Box({
|
||||
vertical: true,
|
||||
className: 'spacing-v-10',
|
||||
children: [
|
||||
Box({
|
||||
className: 'sidebar-wifinetworks-network',
|
||||
vertical: true,
|
||||
children: [
|
||||
Box({
|
||||
className: 'spacing-h-10',
|
||||
children: [
|
||||
MaterialIcon('language', 'hugerass'),
|
||||
networkName,
|
||||
networkStatus,
|
||||
|
||||
]
|
||||
}),
|
||||
networkAuth,
|
||||
]
|
||||
}),
|
||||
bottomSeparator,
|
||||
]
|
||||
});
|
||||
return Box({
|
||||
vertical: true,
|
||||
children: [Revealer({
|
||||
transition: 'slide_down',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
revealChild: Network.wifi,
|
||||
child: actualContent,
|
||||
})]
|
||||
})
|
||||
}
|
||||
|
||||
export default (props) => {
|
||||
const networkList = Box({
|
||||
vertical: true,
|
||||
className: 'spacing-v-10',
|
||||
children: [Overlay({
|
||||
passThrough: true,
|
||||
child: Scrollable({
|
||||
vexpand: true,
|
||||
child: Box({
|
||||
attribute: {
|
||||
'updateNetworks': (self) => {
|
||||
const accessPoints = Network.wifi?.access_points || [];
|
||||
self.children = Object.values(accessPoints.reduce((a, accessPoint) => {
|
||||
// Only keep max strength networks by ssid
|
||||
if (!a[accessPoint.ssid] || a[accessPoint.ssid].strength < accessPoint.strength) {
|
||||
a[accessPoint.ssid] = accessPoint;
|
||||
a[accessPoint.ssid].active |= accessPoint.active;
|
||||
}
|
||||
|
||||
return a;
|
||||
}, {})).map(n => WifiNetwork(n));
|
||||
},
|
||||
},
|
||||
vertical: true,
|
||||
className: 'spacing-v-5 margin-bottom-15',
|
||||
setup: (self) => self.hook(Network, self.attribute.updateNetworks),
|
||||
})
|
||||
}),
|
||||
overlays: [Box({
|
||||
className: 'sidebar-centermodules-scrollgradient-bottom'
|
||||
})]
|
||||
})]
|
||||
});
|
||||
const bottomBar = Box({
|
||||
homogeneous: true,
|
||||
children: [Button({
|
||||
hpack: 'center',
|
||||
className: 'txt-small txt sidebar-centermodules-bottombar-button',
|
||||
onClicked: () => {
|
||||
execAsync(['bash', '-c', userOptions.apps.network]).catch(print);
|
||||
closeEverything();
|
||||
},
|
||||
label: getString('More'),
|
||||
setup: setupCursorHover,
|
||||
})],
|
||||
})
|
||||
return Box({
|
||||
...props,
|
||||
className: 'spacing-v-10',
|
||||
vertical: true,
|
||||
children: [
|
||||
CurrentNetwork(),
|
||||
networkList,
|
||||
bottomBar,
|
||||
]
|
||||
});
|
||||
}
|
||||
18
homes/me/ags-end4/modules/sideright/main.js
Normal file
18
homes/me/ags-end4/modules/sideright/main.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import PopupWindow from '../.widgethacks/popupwindow.js';
|
||||
import SidebarRight from "./sideright.js";
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
const { Box } = Widget;
|
||||
import clickCloseRegion from '../.commonwidgets/clickcloseregion.js';
|
||||
|
||||
export default () => PopupWindow({
|
||||
keymode: 'on-demand',
|
||||
anchor: ['right', 'top', 'bottom'],
|
||||
name: 'sideright',
|
||||
layer: 'top',
|
||||
child: Box({
|
||||
children: [
|
||||
clickCloseRegion({ name: 'sideright', multimonitor: false, fillMonitor: 'horizontal' }),
|
||||
SidebarRight(),
|
||||
]
|
||||
})
|
||||
});
|
||||
267
homes/me/ags-end4/modules/sideright/quicktoggles.js
Normal file
267
homes/me/ags-end4/modules/sideright/quicktoggles.js
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
const { GLib } = imports.gi;
|
||||
import App from 'resource:///com/github/Aylur/ags/app.js';
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
|
||||
import Bluetooth from 'resource:///com/github/Aylur/ags/service/bluetooth.js';
|
||||
import Network from 'resource:///com/github/Aylur/ags/service/network.js';
|
||||
const { execAsync, exec } = Utils;
|
||||
import { BluetoothIndicator, NetworkIndicator } from '../.commonwidgets/statusicons.js';
|
||||
import { setupCursorHover } from '../.widgetutils/cursorhover.js';
|
||||
import { MaterialIcon } from '../.commonwidgets/materialicon.js';
|
||||
import { sidebarOptionsStack } from './sideright.js';
|
||||
|
||||
export const ToggleIconWifi = (props = {}) => Widget.Button({
|
||||
className: 'txt-small sidebar-iconbutton',
|
||||
tooltipText: getString('Wifi | Right-click to configure'),
|
||||
onClicked: () => Network.toggleWifi(),
|
||||
onSecondaryClickRelease: () => {
|
||||
execAsync(['bash', '-c', `${userOptions.apps.network}`]).catch(print);
|
||||
closeEverything();
|
||||
},
|
||||
child: NetworkIndicator(),
|
||||
setup: (self) => {
|
||||
setupCursorHover(self);
|
||||
self.hook(Network, button => {
|
||||
button.toggleClassName('sidebar-button-active', [Network.wifi?.internet, Network.wired?.internet].includes('connected'))
|
||||
button.tooltipText = (`${Network.wifi?.ssid} | ${getString("Right-click to configure")}` || getString('Unknown'));
|
||||
});
|
||||
},
|
||||
...props,
|
||||
});
|
||||
|
||||
export const ToggleIconBluetooth = (props = {}) => Widget.Button({
|
||||
className: 'txt-small sidebar-iconbutton',
|
||||
tooltipText: getString('Bluetooth | Right-click to configure'),
|
||||
onClicked: () => {
|
||||
const status = Bluetooth?.enabled;
|
||||
if (status)
|
||||
exec('rfkill block bluetooth');
|
||||
else
|
||||
exec('rfkill unblock bluetooth');
|
||||
},
|
||||
onSecondaryClickRelease: () => {
|
||||
execAsync(['bash', '-c', `${userOptions.apps.bluetooth}`]).catch(print);
|
||||
closeEverything();
|
||||
},
|
||||
child: BluetoothIndicator(),
|
||||
setup: (self) => {
|
||||
setupCursorHover(self);
|
||||
self.hook(Bluetooth, button => {
|
||||
button.toggleClassName('sidebar-button-active', Bluetooth?.enabled)
|
||||
});
|
||||
},
|
||||
...props,
|
||||
});
|
||||
|
||||
export const HyprToggleIcon = async (icon, name, hyprlandConfigValue, props = {}) => {
|
||||
try {
|
||||
return Widget.Button({
|
||||
className: 'txt-small sidebar-iconbutton',
|
||||
tooltipText: `${name}`,
|
||||
onClicked: (button) => {
|
||||
// Set the value to 1 - value
|
||||
Utils.execAsync(`hyprctl -j getoption ${hyprlandConfigValue}`).then((result) => {
|
||||
const currentOption = JSON.parse(result).int;
|
||||
execAsync(['bash', '-c', `hyprctl keyword ${hyprlandConfigValue} ${1 - currentOption} &`]).catch(print);
|
||||
button.toggleClassName('sidebar-button-active', currentOption == 0);
|
||||
}).catch(print);
|
||||
},
|
||||
child: MaterialIcon(icon, 'norm', { hpack: 'center' }),
|
||||
setup: button => {
|
||||
button.toggleClassName('sidebar-button-active', JSON.parse(Utils.exec(`hyprctl -j getoption ${hyprlandConfigValue}`)).int == 1);
|
||||
setupCursorHover(button);
|
||||
},
|
||||
...props,
|
||||
})
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export const ModuleNightLight = async (props = {}) => {
|
||||
if (!exec(`bash -c 'command -v gammastep'`)) return null;
|
||||
return Widget.Button({
|
||||
attribute: {
|
||||
enabled: false,
|
||||
},
|
||||
className: 'txt-small sidebar-iconbutton',
|
||||
tooltipText: getString('Night Light'),
|
||||
onClicked: (self) => {
|
||||
self.attribute.enabled = !self.attribute.enabled;
|
||||
self.toggleClassName('sidebar-button-active', self.attribute.enabled);
|
||||
if (self.attribute.enabled) Utils.execAsync('gammastep').catch(print)
|
||||
else Utils.execAsync('pkill gammastep')
|
||||
.then(() => {
|
||||
// disable the button until fully terminated to avoid race
|
||||
self.sensitive = false;
|
||||
const source = setInterval(() => {
|
||||
Utils.execAsync('pkill -0 gammastep')
|
||||
.catch(() => {
|
||||
self.sensitive = true;
|
||||
source.destroy();
|
||||
});
|
||||
}, 500);
|
||||
})
|
||||
.catch(print);
|
||||
},
|
||||
child: MaterialIcon('nightlight', 'norm'),
|
||||
setup: (self) => {
|
||||
setupCursorHover(self);
|
||||
self.attribute.enabled = !!exec('pidof gammastep');
|
||||
self.toggleClassName('sidebar-button-active', self.attribute.enabled);
|
||||
},
|
||||
...props,
|
||||
});
|
||||
}
|
||||
|
||||
export const ModuleCloudflareWarp = async (props = {}) => {
|
||||
if (!exec(`bash -c 'command -v warp-cli'`)) return null;
|
||||
return Widget.Button({
|
||||
attribute: {
|
||||
enabled: false,
|
||||
},
|
||||
className: 'txt-small sidebar-iconbutton',
|
||||
tooltipText: getString('Cloudflare WARP'),
|
||||
onClicked: (self) => {
|
||||
self.attribute.enabled = !self.attribute.enabled;
|
||||
self.toggleClassName('sidebar-button-active', self.attribute.enabled);
|
||||
if (self.attribute.enabled) Utils.execAsync('warp-cli connect').catch(print)
|
||||
else Utils.execAsync('warp-cli disconnect').catch(print);
|
||||
},
|
||||
child: Widget.Icon({
|
||||
icon: 'cloudflare-dns-symbolic',
|
||||
className: 'txt-norm',
|
||||
}),
|
||||
setup: (self) => {
|
||||
setupCursorHover(self);
|
||||
self.attribute.enabled = !exec(`bash -c 'warp-cli status | grep Disconnected'`);
|
||||
self.toggleClassName('sidebar-button-active', self.attribute.enabled);
|
||||
},
|
||||
...props,
|
||||
});
|
||||
}
|
||||
|
||||
export const ModuleInvertColors = async (props = {}) => {
|
||||
try {
|
||||
const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
|
||||
return Widget.Button({
|
||||
className: 'txt-small sidebar-iconbutton',
|
||||
tooltipText: getString('Color inversion'),
|
||||
onClicked: (button) => {
|
||||
// const shaderPath = JSON.parse(exec('hyprctl -j getoption decoration:screen_shader')).str;
|
||||
Hyprland.messageAsync('j/getoption decoration:screen_shader')
|
||||
.then((output) => {
|
||||
const shaderPath = JSON.parse(output)["str"].trim();
|
||||
if (shaderPath != "[[EMPTY]]" && shaderPath != "") {
|
||||
execAsync(['bash', '-c', `hyprctl keyword decoration:screen_shader '[[EMPTY]]'`]).catch(print);
|
||||
button.toggleClassName('sidebar-button-active', false);
|
||||
}
|
||||
else {
|
||||
Hyprland.messageAsync(`j/keyword decoration:screen_shader ${GLib.get_user_config_dir()}/hypr/shaders/invert.frag`)
|
||||
.catch(print);
|
||||
button.toggleClassName('sidebar-button-active', true);
|
||||
}
|
||||
})
|
||||
},
|
||||
child: MaterialIcon('invert_colors', 'norm'),
|
||||
setup: setupCursorHover,
|
||||
...props,
|
||||
})
|
||||
} catch {
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
export const ModuleRawInput = async (props = {}) => {
|
||||
try {
|
||||
const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
|
||||
return Widget.Button({
|
||||
className: 'txt-small sidebar-iconbutton',
|
||||
tooltipText: 'Raw input',
|
||||
onClicked: (button) => {
|
||||
Hyprland.messageAsync('j/getoption input:accel_profile')
|
||||
.then((output) => {
|
||||
const value = JSON.parse(output)["str"].trim();
|
||||
if (value != "[[EMPTY]]" && value != "") {
|
||||
execAsync(['bash', '-c', `hyprctl keyword input:accel_profile '[[EMPTY]]'`]).catch(print);
|
||||
button.toggleClassName('sidebar-button-active', false);
|
||||
}
|
||||
else {
|
||||
Hyprland.messageAsync(`j/keyword input:accel_profile flat`)
|
||||
.catch(print);
|
||||
button.toggleClassName('sidebar-button-active', true);
|
||||
}
|
||||
})
|
||||
},
|
||||
child: MaterialIcon('mouse', 'norm'),
|
||||
setup: setupCursorHover,
|
||||
...props,
|
||||
})
|
||||
} catch {
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
export const ModuleIdleInhibitor = (props = {}) => Widget.Button({ // TODO: Make this work
|
||||
attribute: {
|
||||
enabled: false,
|
||||
},
|
||||
className: 'txt-small sidebar-iconbutton',
|
||||
tooltipText: getString('Keep system awake'),
|
||||
onClicked: (self) => {
|
||||
self.attribute.enabled = !self.attribute.enabled;
|
||||
self.toggleClassName('sidebar-button-active', self.attribute.enabled);
|
||||
if (self.attribute.enabled) Utils.execAsync(['bash', '-c', `pidof wayland-idle-inhibitor.py || ${App.configDir}/scripts/wayland-idle-inhibitor.py`]).catch(print)
|
||||
else Utils.execAsync('pkill -f wayland-idle-inhibitor.py').catch(print);
|
||||
},
|
||||
child: MaterialIcon('coffee', 'norm'),
|
||||
setup: (self) => {
|
||||
setupCursorHover(self);
|
||||
self.attribute.enabled = !!exec('pidof wayland-idle-inhibitor.py');
|
||||
self.toggleClassName('sidebar-button-active', self.attribute.enabled);
|
||||
},
|
||||
...props,
|
||||
});
|
||||
|
||||
export const ModuleReloadIcon = (props = {}) => Widget.Button({
|
||||
...props,
|
||||
className: 'txt-small sidebar-iconbutton',
|
||||
tooltipText: getString('Reload Environment config'),
|
||||
onClicked: () => {
|
||||
execAsync(['bash', '-c', 'hyprctl reload || swaymsg reload &']);
|
||||
App.closeWindow('sideright');
|
||||
},
|
||||
child: MaterialIcon('refresh', 'norm'),
|
||||
setup: button => {
|
||||
setupCursorHover(button);
|
||||
}
|
||||
})
|
||||
|
||||
export const ModuleSettingsIcon = (props = {}) => Widget.Button({
|
||||
...props,
|
||||
className: 'txt-small sidebar-iconbutton',
|
||||
tooltipText: getString('Open Settings'),
|
||||
onClicked: () => {
|
||||
execAsync(['bash', '-c', `${userOptions.apps.settings}`, '&']);
|
||||
App.closeWindow('sideright');
|
||||
},
|
||||
child: MaterialIcon('settings', 'norm'),
|
||||
setup: button => {
|
||||
setupCursorHover(button);
|
||||
}
|
||||
})
|
||||
|
||||
export const ModulePowerIcon = (props = {}) => Widget.Button({
|
||||
...props,
|
||||
className: 'txt-small sidebar-iconbutton',
|
||||
tooltipText: getString('Session'),
|
||||
onClicked: () => {
|
||||
closeEverything();
|
||||
Utils.timeout(1, () => openWindowOnAllMonitors('session'));
|
||||
},
|
||||
child: MaterialIcon('power_settings_new', 'norm'),
|
||||
setup: button => {
|
||||
setupCursorHover(button);
|
||||
}
|
||||
})
|
||||
188
homes/me/ags-end4/modules/sideright/sideright.js
Normal file
188
homes/me/ags-end4/modules/sideright/sideright.js
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
const { execAsync, exec } = Utils;
|
||||
const { Box, EventBox } = Widget;
|
||||
import {
|
||||
ToggleIconBluetooth,
|
||||
ToggleIconWifi,
|
||||
HyprToggleIcon,
|
||||
ModuleNightLight,
|
||||
ModuleInvertColors,
|
||||
ModuleIdleInhibitor,
|
||||
ModuleReloadIcon,
|
||||
ModuleSettingsIcon,
|
||||
ModulePowerIcon,
|
||||
ModuleRawInput,
|
||||
ModuleCloudflareWarp
|
||||
} from "./quicktoggles.js";
|
||||
import ModuleNotificationList from "./centermodules/notificationlist.js";
|
||||
import ModuleAudioControls from "./centermodules/audiocontrols.js";
|
||||
import ModuleWifiNetworks from "./centermodules/wifinetworks.js";
|
||||
import ModuleBluetooth from "./centermodules/bluetooth.js";
|
||||
import ModuleConfigure from "./centermodules/configure.js";
|
||||
import { ModuleCalendar } from "./calendar.js";
|
||||
import { getDistroIcon } from '../.miscutils/system.js';
|
||||
import { MaterialIcon } from '../.commonwidgets/materialicon.js';
|
||||
import { ExpandingIconTabContainer } from '../.commonwidgets/tabcontainer.js';
|
||||
import { checkKeybind } from '../.widgetutils/keybind.js';
|
||||
|
||||
const centerWidgets = [
|
||||
{
|
||||
name: getString('Notifications'),
|
||||
materialIcon: 'notifications',
|
||||
contentWidget: ModuleNotificationList,
|
||||
},
|
||||
{
|
||||
name: getString('Audio controls'),
|
||||
materialIcon: 'volume_up',
|
||||
contentWidget: ModuleAudioControls,
|
||||
},
|
||||
{
|
||||
name: getString('Bluetooth'),
|
||||
materialIcon: 'bluetooth',
|
||||
contentWidget: ModuleBluetooth,
|
||||
},
|
||||
{
|
||||
name: getString('Wifi networks'),
|
||||
materialIcon: 'wifi',
|
||||
contentWidget: ModuleWifiNetworks,
|
||||
onFocus: () => execAsync('nmcli dev wifi list').catch(print),
|
||||
},
|
||||
{
|
||||
name: getString('Live config'),
|
||||
materialIcon: 'tune',
|
||||
contentWidget: ModuleConfigure,
|
||||
},
|
||||
];
|
||||
|
||||
const timeRow = Box({
|
||||
className: 'spacing-h-10 sidebar-group-invisible-morehorizpad',
|
||||
children: [
|
||||
Widget.Icon({
|
||||
icon: getDistroIcon(),
|
||||
className: 'txt txt-larger',
|
||||
}),
|
||||
Widget.Label({
|
||||
hpack: 'center',
|
||||
className: 'txt-small txt',
|
||||
setup: (self) => {
|
||||
const getUptime = async () => {
|
||||
try {
|
||||
await execAsync(['bash', '-c', 'uptime -p']);
|
||||
return execAsync(['bash', '-c', `uptime -p | sed -e 's/...//;s/ day\\| days/d/;s/ hour\\| hours/h/;s/ minute\\| minutes/m/;s/,[^,]*//2'`]);
|
||||
} catch {
|
||||
return execAsync(['bash', '-c', 'uptime']).then(output => {
|
||||
const uptimeRegex = /up\s+((\d+)\s+days?,\s+)?((\d+):(\d+)),/;
|
||||
const matches = uptimeRegex.exec(output);
|
||||
|
||||
if (matches) {
|
||||
const days = matches[2] ? parseInt(matches[2]) : 0;
|
||||
const hours = matches[4] ? parseInt(matches[4]) : 0;
|
||||
const minutes = matches[5] ? parseInt(matches[5]) : 0;
|
||||
|
||||
let formattedUptime = '';
|
||||
|
||||
if (days > 0) {
|
||||
formattedUptime += `${days} d `;
|
||||
}
|
||||
if (hours > 0) {
|
||||
formattedUptime += `${hours} h `;
|
||||
}
|
||||
formattedUptime += `${minutes} m`;
|
||||
|
||||
return formattedUptime;
|
||||
} else {
|
||||
throw new Error('Failed to parse uptime output');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
self.poll(5000, label => {
|
||||
getUptime().then(upTimeString => {
|
||||
label.label = `${getString("Uptime:"
|
||||
)} ${upTimeString}`;
|
||||
}).catch(err => {
|
||||
console.error(`Failed to fetch uptime: ${err}`);
|
||||
});
|
||||
});
|
||||
},
|
||||
}),
|
||||
Widget.Box({ hexpand: true }),
|
||||
ModuleReloadIcon({ hpack: 'end' }),
|
||||
// ModuleSettingsIcon({ hpack: 'end' }), // Button does work, gnome-control-center is kinda broken
|
||||
ModulePowerIcon({ hpack: 'end' }),
|
||||
]
|
||||
});
|
||||
|
||||
const togglesBox = Widget.Box({
|
||||
hpack: 'center',
|
||||
className: 'sidebar-togglesbox spacing-h-5',
|
||||
children: [
|
||||
ToggleIconWifi(),
|
||||
ToggleIconBluetooth(),
|
||||
// await ModuleRawInput(),
|
||||
// await HyprToggleIcon('touchpad_mouse', 'No touchpad while typing', 'input:touchpad:disable_while_typing', {}),
|
||||
await ModuleNightLight(),
|
||||
await ModuleInvertColors(),
|
||||
ModuleIdleInhibitor(),
|
||||
await ModuleCloudflareWarp(),
|
||||
]
|
||||
})
|
||||
|
||||
export const sidebarOptionsStack = ExpandingIconTabContainer({
|
||||
tabsHpack: 'center',
|
||||
tabSwitcherClassName: 'sidebar-icontabswitcher',
|
||||
icons: centerWidgets.map((api) => api.materialIcon),
|
||||
names: centerWidgets.map((api) => api.name),
|
||||
children: centerWidgets.map((api) => api.contentWidget()),
|
||||
onChange: (self, id) => {
|
||||
self.shown = centerWidgets[id].name;
|
||||
if (centerWidgets[id].onFocus) centerWidgets[id].onFocus();
|
||||
}
|
||||
});
|
||||
|
||||
export default () => Box({
|
||||
vexpand: true,
|
||||
hexpand: true,
|
||||
css: 'min-width: 2px;',
|
||||
children: [
|
||||
EventBox({
|
||||
onPrimaryClick: () => App.closeWindow('sideright'),
|
||||
onSecondaryClick: () => App.closeWindow('sideright'),
|
||||
onMiddleClick: () => App.closeWindow('sideright'),
|
||||
}),
|
||||
Box({
|
||||
vertical: true,
|
||||
vexpand: true,
|
||||
className: 'sidebar-right spacing-v-15',
|
||||
children: [
|
||||
Box({
|
||||
vertical: true,
|
||||
className: 'spacing-v-5',
|
||||
children: [
|
||||
timeRow,
|
||||
togglesBox,
|
||||
]
|
||||
}),
|
||||
Box({
|
||||
className: 'sidebar-group',
|
||||
children: [
|
||||
sidebarOptionsStack,
|
||||
],
|
||||
}),
|
||||
ModuleCalendar(),
|
||||
]
|
||||
}),
|
||||
],
|
||||
setup: (self) => self
|
||||
.on('key-press-event', (widget, event) => { // Handle keybinds
|
||||
if (checkKeybind(event, userOptions.keybinds.sidebar.options.nextTab)) {
|
||||
sidebarOptionsStack.nextTab();
|
||||
}
|
||||
else if (checkKeybind(event, userOptions.keybinds.sidebar.options.prevTab)) {
|
||||
sidebarOptionsStack.prevTab();
|
||||
}
|
||||
})
|
||||
,
|
||||
});
|
||||
224
homes/me/ags-end4/modules/sideright/todolist.js
Normal file
224
homes/me/ags-end4/modules/sideright/todolist.js
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
const { Box, Button, Label, Revealer } = Widget;
|
||||
import { MaterialIcon } from '../.commonwidgets/materialicon.js';
|
||||
import { TabContainer } from '../.commonwidgets/tabcontainer.js';
|
||||
import Todo from "../../services/todo.js";
|
||||
import { setupCursorHover } from '../.widgetutils/cursorhover.js';
|
||||
|
||||
const TodoListItem = (task, id, isDone, isEven = false) => {
|
||||
const taskName = Widget.Label({
|
||||
hexpand: true,
|
||||
xalign: 0,
|
||||
wrap: true,
|
||||
className: 'txt txt-small sidebar-todo-txt',
|
||||
label: task.content,
|
||||
selectable: true,
|
||||
});
|
||||
const actions = Box({
|
||||
hpack: 'end',
|
||||
className: 'spacing-h-5 sidebar-todo-actions',
|
||||
children: [
|
||||
Widget.Button({ // Check/Uncheck
|
||||
vpack: 'center',
|
||||
className: 'txt sidebar-todo-item-action',
|
||||
child: MaterialIcon(`${isDone ? 'remove_done' : 'check'}`, 'norm', { vpack: 'center' }),
|
||||
onClicked: (self) => {
|
||||
const contentWidth = todoContent.get_allocated_width();
|
||||
crosser.toggleClassName('sidebar-todo-crosser-crossed', true);
|
||||
crosser.css = `margin-left: -${contentWidth}px;`;
|
||||
Utils.timeout(200, () => {
|
||||
widgetRevealer.revealChild = false;
|
||||
})
|
||||
Utils.timeout(350, () => {
|
||||
if (isDone)
|
||||
Todo.uncheck(id);
|
||||
else
|
||||
Todo.check(id);
|
||||
})
|
||||
},
|
||||
setup: setupCursorHover,
|
||||
}),
|
||||
Widget.Button({ // Remove
|
||||
vpack: 'center',
|
||||
className: 'txt sidebar-todo-item-action',
|
||||
child: MaterialIcon('delete_forever', 'norm', { vpack: 'center' }),
|
||||
onClicked: () => {
|
||||
const contentWidth = todoContent.get_allocated_width();
|
||||
crosser.toggleClassName('sidebar-todo-crosser-removed', true);
|
||||
crosser.css = `margin-left: -${contentWidth}px;`;
|
||||
Utils.timeout(200, () => {
|
||||
widgetRevealer.revealChild = false;
|
||||
})
|
||||
Utils.timeout(350, () => {
|
||||
Todo.remove(id);
|
||||
})
|
||||
},
|
||||
setup: setupCursorHover,
|
||||
}),
|
||||
]
|
||||
})
|
||||
const crosser = Widget.Box({
|
||||
className: 'sidebar-todo-crosser',
|
||||
});
|
||||
const todoContent = Widget.Box({
|
||||
className: 'sidebar-todo-item spacing-h-5',
|
||||
children: [
|
||||
Widget.Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
taskName,
|
||||
actions,
|
||||
]
|
||||
}),
|
||||
crosser,
|
||||
]
|
||||
});
|
||||
const widgetRevealer = Widget.Revealer({
|
||||
revealChild: true,
|
||||
transition: 'slide_down',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
child: todoContent,
|
||||
})
|
||||
return Box({
|
||||
homogeneous: true,
|
||||
children: [widgetRevealer]
|
||||
});
|
||||
}
|
||||
|
||||
const todoItems = (isDone) => Widget.Scrollable({
|
||||
hscroll: 'never',
|
||||
vscroll: 'automatic',
|
||||
child: Widget.Box({
|
||||
vertical: true,
|
||||
className: 'spacing-v-5',
|
||||
setup: (self) => self
|
||||
.hook(Todo, (self) => {
|
||||
self.children = Todo.todo_json.map((task, i) => {
|
||||
if (task.done != isDone) return null;
|
||||
return TodoListItem(task, i, isDone);
|
||||
})
|
||||
if (self.children.length == 0) {
|
||||
self.homogeneous = true;
|
||||
self.children = [
|
||||
Widget.Box({
|
||||
hexpand: true,
|
||||
vertical: true,
|
||||
vpack: 'center',
|
||||
className: 'txt txt-subtext',
|
||||
children: [
|
||||
MaterialIcon(`${isDone ? 'checklist' : 'check_circle'}`, 'gigantic'),
|
||||
Label({ label: `${isDone ? getString('Finished tasks will go here') : getString('Nothing here!')}` })
|
||||
]
|
||||
})
|
||||
]
|
||||
}
|
||||
else self.homogeneous = false;
|
||||
}, 'updated')
|
||||
,
|
||||
}),
|
||||
setup: (listContents) => {
|
||||
const vScrollbar = listContents.get_vscrollbar();
|
||||
vScrollbar.get_style_context().add_class('sidebar-scrollbar');
|
||||
}
|
||||
});
|
||||
|
||||
const UndoneTodoList = () => {
|
||||
const newTaskButton = Revealer({
|
||||
transition: 'slide_left',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
revealChild: true,
|
||||
child: Button({
|
||||
className: 'txt-small sidebar-todo-new',
|
||||
halign: 'end',
|
||||
vpack: 'center',
|
||||
label: getString('+ New task'),
|
||||
setup: setupCursorHover,
|
||||
onClicked: (self) => {
|
||||
newTaskButton.revealChild = false;
|
||||
newTaskEntryRevealer.revealChild = true;
|
||||
confirmAddTask.revealChild = true;
|
||||
cancelAddTask.revealChild = true;
|
||||
newTaskEntry.grab_focus();
|
||||
}
|
||||
})
|
||||
});
|
||||
const cancelAddTask = Revealer({
|
||||
transition: 'slide_right',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
revealChild: false,
|
||||
child: Button({
|
||||
className: 'txt-norm icon-material sidebar-todo-add',
|
||||
halign: 'end',
|
||||
vpack: 'center',
|
||||
label: 'close',
|
||||
setup: setupCursorHover,
|
||||
onClicked: (self) => {
|
||||
newTaskEntryRevealer.revealChild = false;
|
||||
confirmAddTask.revealChild = false;
|
||||
cancelAddTask.revealChild = false;
|
||||
newTaskButton.revealChild = true;
|
||||
newTaskEntry.text = '';
|
||||
}
|
||||
})
|
||||
});
|
||||
const newTaskEntry = Widget.Entry({
|
||||
// hexpand: true,
|
||||
vpack: 'center',
|
||||
className: 'txt-small sidebar-todo-entry',
|
||||
placeholderText: getString('Add a task...'),
|
||||
onAccept: ({ text }) => {
|
||||
if (text == '') return;
|
||||
Todo.add(text)
|
||||
newTaskEntry.text = '';
|
||||
},
|
||||
onChange: ({ text }) => confirmAddTask.child.toggleClassName('sidebar-todo-add-available', text != ''),
|
||||
});
|
||||
const newTaskEntryRevealer = Revealer({
|
||||
transition: 'slide_right',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
revealChild: false,
|
||||
child: newTaskEntry,
|
||||
});
|
||||
const confirmAddTask = Revealer({
|
||||
transition: 'slide_right',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
revealChild: false,
|
||||
child: Button({
|
||||
className: 'txt-norm icon-material sidebar-todo-add',
|
||||
halign: 'end',
|
||||
vpack: 'center',
|
||||
label: 'arrow_upward',
|
||||
setup: setupCursorHover,
|
||||
onClicked: (self) => {
|
||||
if (newTaskEntry.text == '') return;
|
||||
Todo.add(newTaskEntry.text);
|
||||
newTaskEntry.text = '';
|
||||
}
|
||||
})
|
||||
});
|
||||
return Box({ // The list, with a New button
|
||||
vertical: true,
|
||||
className: 'spacing-v-5',
|
||||
setup: (box) => {
|
||||
box.pack_start(todoItems(false), true, true, 0);
|
||||
box.pack_start(Box({
|
||||
setup: (self) => {
|
||||
self.pack_start(cancelAddTask, false, false, 0);
|
||||
self.pack_start(newTaskEntryRevealer, true, true, 0);
|
||||
self.pack_start(confirmAddTask, false, false, 0);
|
||||
self.pack_start(newTaskButton, false, false, 0);
|
||||
}
|
||||
}), false, false, 0);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export const TodoWidget = () => TabContainer({
|
||||
icons: ['format_list_bulleted', 'task_alt'],
|
||||
names: [getString('Unfinished'), getString('Done')],
|
||||
children: [
|
||||
UndoneTodoList(),
|
||||
todoItems(true),
|
||||
]
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue