forked from Shinonome/dots-hyprland
ags: sync
This commit is contained in:
@@ -154,7 +154,7 @@ export default () => {
|
||||
className: 'spacing-h-10 margin-left-10',
|
||||
children: [
|
||||
BarResource('Swap Usage', 'swap_horiz', `free | awk '/^Swap/ {if ($2 > 0) printf("%.2f\\n", ($3/$2) * 100); else print "0";}'`),
|
||||
BarResource('CPU Usage', 'settings_motion_mode', `top -bn1 | grep Cpu | awk '{print $2}'`),
|
||||
BarResource('CPU Usage', 'settings_motion_mode', `top -bn1 | grep Cpu | sed 's/\\,/\\./g' | awk '{print $2}'`),
|
||||
]
|
||||
}),
|
||||
setup: (self) => self.hook(Mpris, label => {
|
||||
|
||||
@@ -13,6 +13,13 @@ const BATTERY_LOW = 20;
|
||||
const WEATHER_CACHE_FOLDER = `${GLib.get_user_cache_dir()}/ags/weather`;
|
||||
Utils.exec(`mkdir -p ${WEATHER_CACHE_FOLDER}`);
|
||||
|
||||
let WEATHER_CITY = '';
|
||||
try {
|
||||
WEATHER_CITY = GLib.getenv('AGS_WEATHER_CITY');
|
||||
} catch (e) {
|
||||
print(e);
|
||||
}
|
||||
|
||||
const BatBatteryProgress = () => {
|
||||
const _updateProgress = (circprog) => { // Set circular progress value
|
||||
circprog.css = `font-size: ${Math.abs(Battery.percent)}px;`
|
||||
@@ -155,15 +162,24 @@ const BatteryModule = () => Stack({
|
||||
],
|
||||
setup: (self) => self.poll(900000, async (self) => {
|
||||
const WEATHER_CACHE_PATH = WEATHER_CACHE_FOLDER + '/wttr.in.txt';
|
||||
Utils.execAsync('curl ipinfo.io')
|
||||
const updateWeatherForCity = (city) => execAsync(`curl https://wttr.in/${city}?format=j1`)
|
||||
.then(output => {
|
||||
return JSON.parse(output)['city'].toLowerCase();
|
||||
})
|
||||
.then((city) => execAsync(`curl https://wttr.in/${city}?format=j1`)
|
||||
.then(output => {
|
||||
const weather = JSON.parse(output);
|
||||
Utils.writeFile(JSON.stringify(weather), WEATHER_CACHE_PATH)
|
||||
.catch(print);
|
||||
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_C;
|
||||
const feelsLike = weather.current_condition[0].FeelsLikeC;
|
||||
const weatherSymbol = WEATHER_SYMBOL[WWO_CODE[weatherCode]];
|
||||
self.children[0].label = weatherSymbol;
|
||||
self.children[1].label = `${temperature}℃ • Feels like ${feelsLike}℃`;
|
||||
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_C;
|
||||
@@ -172,23 +188,20 @@ const BatteryModule = () => Stack({
|
||||
self.children[0].label = weatherSymbol;
|
||||
self.children[1].label = `${temperature}℃ • Feels like ${feelsLike}℃`;
|
||||
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_C;
|
||||
const feelsLike = weather.current_condition[0].FeelsLikeC;
|
||||
const weatherSymbol = WEATHER_SYMBOL[WWO_CODE[weatherCode]];
|
||||
self.children[0].label = weatherSymbol;
|
||||
self.children[1].label = `${temperature}℃ • Feels like ${feelsLike}℃`;
|
||||
self.tooltipText = weatherDesc;
|
||||
} catch (err) {
|
||||
print(err);
|
||||
}
|
||||
}));
|
||||
} catch (err) {
|
||||
print(err);
|
||||
}
|
||||
});
|
||||
if (WEATHER_CITY != '' && WEATHER_CITY != null) {
|
||||
updateWeatherForCity(WEATHER_CITY);
|
||||
}
|
||||
else {
|
||||
Utils.execAsync('curl ipinfo.io')
|
||||
.then(output => {
|
||||
return JSON.parse(output)['city'].toLowerCase();
|
||||
})
|
||||
.then(updateWeatherForCity);
|
||||
}
|
||||
}),
|
||||
})
|
||||
}),
|
||||
|
||||
@@ -10,15 +10,8 @@ const SysTrayItem = (item) => Button({
|
||||
className: 'bar-systray-item',
|
||||
child: Icon({
|
||||
hpack: 'center',
|
||||
setup: (self) => {
|
||||
self.hook(item, (self) => self.icon = item.icon);
|
||||
Utils.timeout(1, () => {
|
||||
const styleContext = self.get_parent().get_style_context();
|
||||
const width = styleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
|
||||
const height = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
|
||||
self.size = Math.max(width, height, 1); // im too lazy to add another box lol
|
||||
})
|
||||
},
|
||||
icon: item.icon,
|
||||
setup: (self) => self.hook(item, (self) => self.icon = item.icon),
|
||||
}),
|
||||
setup: (self) => self
|
||||
.hook(item, (self) => self.tooltipMarkup = item['tooltip-markup'])
|
||||
|
||||
@@ -21,6 +21,7 @@ const WorkspaceContents = (count = 10) => {
|
||||
attribute: {
|
||||
initialized: false,
|
||||
workspaceMask: 0,
|
||||
workspaceGroup: 0,
|
||||
updateMask: (self) => {
|
||||
const offset = Math.floor((Hyprland.active.workspace.id - 1) / count) * NUM_OF_WORKSPACES_SHOWN;
|
||||
// if (self.attribute.initialized) return; // We only need this to run once
|
||||
@@ -46,6 +47,12 @@ const WorkspaceContents = (count = 10) => {
|
||||
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) => {
|
||||
|
||||
@@ -59,12 +59,6 @@ const AppButton = ({ icon, ...rest }) => Widget.Revealer({
|
||||
className: 'dock-app-icon',
|
||||
child: Widget.Icon({
|
||||
icon: icon,
|
||||
setup: (self) => Utils.timeout(1, () => {
|
||||
const styleContext = self.get_parent().get_style_context();
|
||||
const width = styleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
|
||||
const height = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
|
||||
self.size = Math.max(width, height, 1);
|
||||
})
|
||||
}),
|
||||
}),
|
||||
overlays: [Widget.Box({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
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 MusicControls from './musiccontrols.js';
|
||||
import ColorScheme from './colorscheme.js';
|
||||
import NotificationPopups from './notificationpopups.js';
|
||||
|
||||
@@ -23,7 +23,7 @@ export default (monitor = 0) => Widget.Window({
|
||||
css: 'min-height: 2px;',
|
||||
children: [
|
||||
IndicatorValues(),
|
||||
// MusicControls(),
|
||||
MusicControls(),
|
||||
NotificationPopups(),
|
||||
ColorScheme(),
|
||||
]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { Gio, GLib } = imports.gi;
|
||||
const { Gdk, GdkPixbuf, Gio, GLib, 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';
|
||||
@@ -27,8 +27,9 @@ var lastCoverPath = '';
|
||||
|
||||
function isRealPlayer(player) {
|
||||
return (
|
||||
!player.busName.startsWith('org.mpris.MediaPlayer2.firefox') &&
|
||||
!player.busName.startsWith('org.mpris.MediaPlayer2.playerctld')
|
||||
!player.busName.startsWith('org.mpris.MediaPlayer2.firefox') && // Firefox mpris dbus is useless
|
||||
!player.busName.startsWith('org.mpris.MediaPlayer2.playerctld') && // Doesn't have cover art
|
||||
!player.busName.endsWith('.mpd') // Non-instance mpd bus
|
||||
);
|
||||
}
|
||||
|
||||
@@ -69,7 +70,7 @@ function getTrackfont(player) {
|
||||
return DEFAULT_MUSIC_FONT;
|
||||
}
|
||||
function trimTrackTitle(title) {
|
||||
if(!title) return '';
|
||||
if (!title) return '';
|
||||
const cleanRegexes = [
|
||||
/【[^】]*】/, // Touhou n weeb stuff
|
||||
/\[FREE DOWNLOAD\]/, // F-777
|
||||
@@ -80,7 +81,7 @@ function trimTrackTitle(title) {
|
||||
|
||||
const TrackProgress = ({ player, ...rest }) => {
|
||||
const _updateProgress = (circprog) => {
|
||||
const player = Mpris.getPlayer();
|
||||
// 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;`
|
||||
@@ -122,69 +123,123 @@ const TrackArtists = ({ player, ...rest }) => Label({
|
||||
}, 'notify::track-artists'),
|
||||
})
|
||||
|
||||
const CoverArt = ({ player, ...rest }) => Box({
|
||||
...rest,
|
||||
className: 'osd-music-cover',
|
||||
children: [
|
||||
Widget.Overlay({
|
||||
child: Box({ // Fallback
|
||||
className: 'osd-music-cover-fallback',
|
||||
homogeneous: true,
|
||||
children: [Label({
|
||||
className: 'icon-material txt-hugeass',
|
||||
label: 'music_note',
|
||||
})]
|
||||
}),
|
||||
overlays: [ // Real
|
||||
Box({
|
||||
attribute: {
|
||||
'updateCover': (self) => {
|
||||
const player = Mpris.getPlayer();
|
||||
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);
|
||||
|
||||
// Player closed
|
||||
// Note that cover path still remains, so we're checking title
|
||||
if (!player || player.trackTitle == "") {
|
||||
self.css = `background-image: none;`;
|
||||
App.applyCss(`${App.configDir}/style.css`);
|
||||
return;
|
||||
}
|
||||
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 == "") {
|
||||
self.css = `background-image: none;`;
|
||||
App.applyCss(`${App.configDir}/style.css`);
|
||||
return;
|
||||
}
|
||||
|
||||
const coverPath = player.coverPath;
|
||||
const stylePath = `${player.coverPath}${lightDark}${COVER_COLORSCHEME_SUFFIX}`;
|
||||
if (player.coverPath == lastCoverPath) { // Since 'notify::cover-path' emits on cover download complete
|
||||
self.css = `background-image: url('${coverPath}');`;
|
||||
}
|
||||
lastCoverPath = player.coverPath;
|
||||
const coverPath = player.coverPath;
|
||||
const stylePath = `${player.coverPath}${lightDark}${COVER_COLORSCHEME_SUFFIX}`;
|
||||
if (player.coverPath == lastCoverPath) { // Since 'notify::cover-path' emits on cover download complete
|
||||
// Utils.timeout(200, () => { self.css = `background-image: url('${coverPath}');`; });
|
||||
Utils.timeout(200, () => self.attribute.showImage(self, coverPath));
|
||||
}
|
||||
lastCoverPath = player.coverPath;
|
||||
|
||||
// If a colorscheme has already been generated, skip generation
|
||||
if (fileExists(stylePath)) {
|
||||
self.css = `background-image: url('${coverPath}');`;
|
||||
App.applyCss(stylePath);
|
||||
return;
|
||||
}
|
||||
// If a colorscheme has already been generated, skip generation
|
||||
if (fileExists(stylePath)) {
|
||||
// Utils.timeout(200, () => { self.css = `background-image: url('${coverPath}');`; });
|
||||
self.attribute.showImage(self, coverPath)
|
||||
App.applyCss(stylePath);
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate colors
|
||||
execAsync(['bash', '-c',
|
||||
`${App.configDir}/scripts/color_generation/generate_colors_material.py --path '${coverPath}' > ${App.configDir}/scss/_musicmaterial.scss ${lightDark}`])
|
||||
.then(() => {
|
||||
exec(`wal -i "${player.coverPath}" -n -t -s -e -q ${lightDark}`)
|
||||
exec(`cp ${GLib.get_user_cache_dir()}/wal/colors.scss ${App.configDir}/scss/_musicwal.scss`);
|
||||
exec(`sassc ${App.configDir}/scss/_music.scss ${stylePath}`);
|
||||
self.css = `background-image: url('${coverPath}');`;
|
||||
App.applyCss(`${stylePath}`);
|
||||
})
|
||||
.catch(print);
|
||||
},
|
||||
},
|
||||
className: 'osd-music-cover-art',
|
||||
$: [
|
||||
[player, (self) => self.attribute.updateCover(self), 'notify::cover-path']
|
||||
],
|
||||
})
|
||||
]
|
||||
})
|
||||
],
|
||||
})
|
||||
// Generate colors
|
||||
execAsync(['bash', '-c',
|
||||
`${App.configDir}/scripts/color_generation/generate_colors_material.py --path '${coverPath}' > ${App.configDir}/scss/_musicmaterial.scss ${lightDark}`])
|
||||
.then(() => {
|
||||
exec(`wal -i "${player.coverPath}" -n -t -s -e -q ${lightDark}`)
|
||||
exec(`cp ${GLib.get_user_cache_dir()}/wal/colors.scss ${App.configDir}/scss/_musicwal.scss`);
|
||||
exec(`sassc ${App.configDir}/scss/_music.scss ${stylePath}`);
|
||||
// self.css = `background-image: url('${coverPath}');`;
|
||||
Utils.timeout(200, () => self.attribute.showImage(self, coverPath));
|
||||
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,
|
||||
@@ -197,7 +252,7 @@ const TrackControls = ({ player, ...rest }) => Widget.Revealer({
|
||||
children: [
|
||||
Button({
|
||||
className: 'osd-music-controlbtn',
|
||||
onClicked: () => execAsync('playerctl previous').catch(print),
|
||||
onClicked: () => player.previous(),
|
||||
child: Label({
|
||||
className: 'icon-material osd-music-controlbtn-txt',
|
||||
label: 'skip_previous',
|
||||
@@ -205,9 +260,7 @@ const TrackControls = ({ player, ...rest }) => Widget.Revealer({
|
||||
}),
|
||||
Button({
|
||||
className: 'osd-music-controlbtn',
|
||||
onClicked: () => execAsync(['bash', '-c', 'playerctl next || playerctl position `bc <<< "100 * $(playerctl metadata mpris:length) / 1000000 / 100"`'])
|
||||
.catch(print)
|
||||
,
|
||||
onClicked: () => player.next(),
|
||||
child: Label({
|
||||
className: 'icon-material osd-music-controlbtn-txt',
|
||||
label: 'skip_next',
|
||||
@@ -215,8 +268,8 @@ const TrackControls = ({ player, ...rest }) => Widget.Revealer({
|
||||
}),
|
||||
],
|
||||
}),
|
||||
setup: (self) => szelf.hook(Mpris, (self) => {
|
||||
const player = Mpris.getPlayer();
|
||||
setup: (self) => self.hook(Mpris, (self) => {
|
||||
// const player = Mpris.getPlayer();
|
||||
if (!player)
|
||||
self.revealChild = false;
|
||||
else
|
||||
@@ -264,7 +317,7 @@ const TrackTime = ({ player, ...rest }) => {
|
||||
children: [
|
||||
Label({
|
||||
setup: (self) => self.poll(1000, (self) => {
|
||||
const player = Mpris.getPlayer();
|
||||
// const player = Mpris.getPlayer();
|
||||
if (!player) return;
|
||||
self.label = lengthStr(player.position);
|
||||
}),
|
||||
@@ -272,7 +325,7 @@ const TrackTime = ({ player, ...rest }) => {
|
||||
Label({ label: '/' }),
|
||||
Label({
|
||||
setup: (self) => self.hook(Mpris, (self) => {
|
||||
const player = Mpris.getPlayer();
|
||||
// const player = Mpris.getPlayer();
|
||||
if (!player) return;
|
||||
self.label = lengthStr(player.length);
|
||||
}),
|
||||
@@ -296,7 +349,7 @@ const PlayState = ({ player }) => {
|
||||
overlays: [
|
||||
Widget.Button({
|
||||
className: 'osd-music-playstate-btn',
|
||||
onClicked: () => execAsync('playerctl play-pause').catch(print),
|
||||
onClicked: () => player.playPause(),
|
||||
child: Widget.Label({
|
||||
justification: 'center',
|
||||
hpack: 'fill',
|
||||
@@ -313,7 +366,7 @@ const PlayState = ({ player }) => {
|
||||
}
|
||||
|
||||
const MusicControlsWidget = (player) => Box({
|
||||
className: 'osd-music spacing-h-20',
|
||||
className: 'osd-music spacing-h-20 test',
|
||||
children: [
|
||||
CoverArt({ player: player, vpack: 'center' }),
|
||||
Box({
|
||||
@@ -344,35 +397,62 @@ const MusicControlsWidget = (player) => Box({
|
||||
]
|
||||
})
|
||||
|
||||
export default () => MarginRevealer({
|
||||
export default () => Revealer({
|
||||
transition: 'slide_down',
|
||||
transitionDuration: 150,
|
||||
revealChild: false,
|
||||
showClass: 'osd-show',
|
||||
hideClass: 'osd-hide',
|
||||
child: Box({
|
||||
setup: (self) => self.hook(Mpris, box => {
|
||||
let foundPlayer = false;
|
||||
|
||||
box.children.forEach(child => {
|
||||
child.destroy();
|
||||
child = null;
|
||||
});
|
||||
Mpris.players.forEach((player, i) => {
|
||||
if (isRealPlayer(player)) {
|
||||
foundPlayer = true;
|
||||
box.children = [MusicControlsWidget(player)];
|
||||
const newInstance = MusicControlsWidget(player);
|
||||
box.add(newInstance);
|
||||
}
|
||||
});
|
||||
|
||||
if (!foundPlayer) {
|
||||
const children = box.get_children();
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i];
|
||||
child.destroy();
|
||||
child = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}, 'notify::players'),
|
||||
}),
|
||||
setup: (self) => self.hook(showMusicControls, (revealer) => {
|
||||
if (showMusicControls.value) revealer.attribute.show();
|
||||
else revealer.attribute.hide();
|
||||
revealer.revealChild = showMusicControls.value;
|
||||
}),
|
||||
})
|
||||
|
||||
// export default () => MarginRevealer({
|
||||
// transition: 'slide_down',
|
||||
// revealChild: false,
|
||||
// showClass: 'osd-show',
|
||||
// hideClass: 'osd-hide',
|
||||
// child: Box({
|
||||
// setup: (self) => self.hook(Mpris, box => {
|
||||
// let foundPlayer = false;
|
||||
// Mpris.players.forEach((player, i) => {
|
||||
// if (isRealPlayer(player)) {
|
||||
// foundPlayer = true;
|
||||
// box.children.forEach(child => {
|
||||
// child.destroy();
|
||||
// child = null;
|
||||
// });
|
||||
// const newInstance = MusicControlsWidget(player);
|
||||
// box.children = [newInstance];
|
||||
// }
|
||||
// });
|
||||
|
||||
// if (!foundPlayer) {
|
||||
// const children = box.get_children();
|
||||
// for (let i = 0; i < children.length; i++) {
|
||||
// const child = children[i];
|
||||
// child.destroy();
|
||||
// child = null;
|
||||
// }
|
||||
// return;
|
||||
// }
|
||||
// }, 'notify::players'),
|
||||
// }),
|
||||
// setup: (self) => self.hook(showMusicControls, (revealer) => {
|
||||
// if (showMusicControls.value) revealer.attribute.show();
|
||||
// else revealer.attribute.hide();
|
||||
// }),
|
||||
// })
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
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 Service from 'resource:///com/github/Aylur/ags/service.js';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
|
||||
const { Box, EventBox, Button, Revealer } = Widget;
|
||||
const { execAsync, exec } = Utils;
|
||||
const { execAsync } = Utils;
|
||||
import { MaterialIcon } from '../../lib/materialicon.js';
|
||||
import { separatorLine } from '../../lib/separator.js';
|
||||
import { defaultOskLayout, oskLayouts } from '../../data/keyboardlayouts.js';
|
||||
@@ -21,6 +20,18 @@ function releaseAllKeys() {
|
||||
.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({
|
||||
@@ -76,6 +87,10 @@ const keyboardControls = Box({
|
||||
]
|
||||
})
|
||||
|
||||
var shiftMode = ShiftMode.Off;
|
||||
var shiftButton;
|
||||
var rightShiftButton;
|
||||
var allButtons = [];
|
||||
const keyboardItself = (kbJson) => {
|
||||
return Box({
|
||||
vertical: true,
|
||||
@@ -88,14 +103,32 @@ const keyboardItself = (kbJson) => {
|
||||
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`);
|
||||
});
|
||||
button.connect('clicked', () => { // release
|
||||
execAsync(`ydotool key ${key.keycode}:0`);
|
||||
|
||||
if (shiftMode == ShiftMode.Normal) {
|
||||
shiftMode = ShiftMode.Off;
|
||||
if (typeof shiftButton !== 'undefined') {
|
||||
execAsync(`ydotool key 42:0`);
|
||||
shiftButton.toggleClassName('osk-key-active', false);
|
||||
}
|
||||
if (typeof rightShiftButton !== 'undefined') {
|
||||
execAsync(`ydotool key 54:0`);
|
||||
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") {
|
||||
@@ -104,14 +137,53 @@ const keyboardItself = (kbJson) => {
|
||||
execAsync(`ydotool key ${key.keycode}:0`);
|
||||
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`);
|
||||
button.toggleClassName('osk-key-active', true);
|
||||
pressed = 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`);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// - Active ws hook optimization: only update when moving to next group
|
||||
//
|
||||
const { Gdk, Gtk } = imports.gi;
|
||||
const { Gravity } = imports.gi.Gdk;
|
||||
import { SCREEN_HEIGHT, SCREEN_WIDTH } from '../../imports.js';
|
||||
import App from 'resource:///com/github/Aylur/ags/app.js';
|
||||
import Variable from 'resource:///com/github/Aylur/ags/variable.js';
|
||||
@@ -48,260 +49,398 @@ function substitute(str) {
|
||||
if (!iconExists(str)) str = str.toLowerCase().replace(/\s+/g, '-'); // Turn into kebab-case
|
||||
return str;
|
||||
}
|
||||
export default () => {
|
||||
const clientMap = new Map();
|
||||
let workspaceGroup = 0;
|
||||
const ContextMenuWorkspaceArray = ({ label, actionFunc, thisWorkspace }) => Widget.MenuItem({
|
||||
label: `${label}`,
|
||||
setup: (menuItem) => {
|
||||
let submenu = new Gtk.Menu();
|
||||
submenu.className = 'menu';
|
||||
|
||||
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);
|
||||
});
|
||||
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, title, xwayland }) => {
|
||||
const revealInfoCondition = (Math.min(w, h) * OVERVIEW_SCALE > 70);
|
||||
if (w <= 0 || h <= 0 || (c === '' && title === '')) return null;
|
||||
if (x + w <= 0) x += (Math.floor(x / SCREEN_WIDTH) * SCREEN_WIDTH);
|
||||
else if (x < 0) { x = 0; w = x + w; }
|
||||
if (y + h <= 0) x += (Math.floor(y / SCREEN_HEIGHT) * SCREEN_HEIGHT);
|
||||
else if (y < 0) { y = 0; h = y + h; }
|
||||
|
||||
if (x >= SCREEN_WIDTH) x %= SCREEN_WIDTH;
|
||||
else if (x + w > SCREEN_WIDTH) w = SCREEN_WIDTH - x;
|
||||
if (y >= SCREEN_HEIGHT) y %= SCREEN_HEIGHT;
|
||||
else if (y + h > SCREEN_HEIGHT) h = SCREEN_HEIGHT - y;
|
||||
|
||||
// title = truncateTitle(title);
|
||||
return Widget.Button({
|
||||
attribute: { x, y },
|
||||
className: 'overview-tasks-window',
|
||||
hpack: 'center',
|
||||
vpack: 'center',
|
||||
onClicked: () => {
|
||||
Hyprland.sendMessage(`dispatch focuswindow address:${address}`);
|
||||
App.closeWindow('overview');
|
||||
},
|
||||
onMiddleClickRelease: () => Hyprland.sendMessage(`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.sendMessage(`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_pointer(null); // Show the menu at the pointer's position
|
||||
},
|
||||
child: Widget.Box({
|
||||
css: `
|
||||
min-width: ${Math.max(w * OVERVIEW_SCALE - 4, 1)}px;
|
||||
min-height: ${Math.max(h * OVERVIEW_SCALE - 4, 1)}px;
|
||||
`,
|
||||
homogeneous: true,
|
||||
child: Widget.Box({
|
||||
vertical: true,
|
||||
vpack: 'center',
|
||||
className: 'spacing-v-5',
|
||||
children: [
|
||||
Widget.Icon({
|
||||
icon: substitute(c),
|
||||
size: Math.min(w, h) * OVERVIEW_SCALE / 2.5,
|
||||
}),
|
||||
// TODO: Add xwayland tag instead of just having italics
|
||||
Widget.Revealer({
|
||||
transition: 'slide_down',
|
||||
revealChild: revealInfoCondition,
|
||||
child: Widget.Label({
|
||||
truncate: 'end',
|
||||
className: `${xwayland ? 'txt txt-italic' : 'txt'}`,
|
||||
css: `
|
||||
font-size: ${Math.min(SCREEN_WIDTH, SCREEN_HEIGHT) * OVERVIEW_SCALE / 14.6}px;
|
||||
margin: 0px ${Math.min(SCREEN_WIDTH, SCREEN_HEIGHT) * 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.drag_source_set_icon_gicon(icon);
|
||||
|
||||
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 = Gtk.Fixed.new();
|
||||
// const clientMap = new Map();
|
||||
const WorkspaceNumber = (index) => Widget.Label({
|
||||
className: 'overview-tasks-workspace-number',
|
||||
label: `${index}`,
|
||||
css: `
|
||||
margin: ${Math.min(SCREEN_WIDTH, SCREEN_HEIGHT) * OVERVIEW_SCALE * OVERVIEW_WS_NUM_MARGIN_SCALE}px;
|
||||
font-size: ${SCREEN_HEIGHT * OVERVIEW_SCALE * OVERVIEW_WS_NUM_SCALE}px;
|
||||
`,
|
||||
})
|
||||
const widget = Widget.Box({
|
||||
className: 'overview-tasks-workspace',
|
||||
vpack: 'center',
|
||||
css: `
|
||||
min-width: ${SCREEN_WIDTH * OVERVIEW_SCALE}px;
|
||||
min-height: ${SCREEN_HEIGHT * OVERVIEW_SCALE}px;
|
||||
`,
|
||||
children: [Widget.EventBox({
|
||||
hexpand: true,
|
||||
vexpand: true,
|
||||
onPrimaryClick: () => {
|
||||
Hyprland.sendMessage(`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) => {
|
||||
Hyprland.sendMessage(`dispatch movetoworkspacesilent ${index},address:${data.get_text()}`)
|
||||
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);
|
||||
});
|
||||
},
|
||||
child: fixed,
|
||||
})],
|
||||
});
|
||||
widget.clear = () => {
|
||||
fixed.get_children().forEach(ch => ch.destroy());
|
||||
const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN;
|
||||
fixed.put(WorkspaceNumber(offset + index), 0, 0);
|
||||
}
|
||||
widget.set = (clientJson) => {
|
||||
// if(clientMap.get(clientJson.address)) clientMap.get(clientJson.address).destroy();
|
||||
const newWindow = Window(clientJson);
|
||||
if (newWindow === null) return;
|
||||
// clientMap.set(clientJson.address, newWindow);
|
||||
fixed.put(newWindow,
|
||||
Math.max(0, newWindow.attribute.x * OVERVIEW_SCALE),
|
||||
Math.max(0, newWindow.attribute.y * OVERVIEW_SCALE)
|
||||
);
|
||||
};
|
||||
// widget.unset = (clientAddress) => {
|
||||
// if(clientMap.get(clientAddress)) {
|
||||
// clientMap.get(clientAddress).destroy();
|
||||
// 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: {
|
||||
update: (box) => {
|
||||
const offset = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN) * NUM_OF_WORKSPACES_SHOWN;
|
||||
if (!App.getWindow(windowName).visible) return;
|
||||
execAsync('hyprctl -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];
|
||||
if (offset + startWorkspace <= client.workspace.id &&
|
||||
client.workspace.id <= offset + startWorkspace + workspaces) {
|
||||
kids[client.workspace.id - (offset + startWorkspace)]
|
||||
?.set(client);
|
||||
}
|
||||
}
|
||||
kids.forEach(kid => kid.show());
|
||||
|
||||
}).catch(print);
|
||||
submenu.append(button);
|
||||
}
|
||||
menuItem.set_reserve_indicator(true);
|
||||
menuItem.set_submenu(submenu);
|
||||
}
|
||||
},
|
||||
setup: (box) => box
|
||||
.hook(overviewTick, (box) => box.attribute.update(box))
|
||||
.hook(Hyprland, (box, clientAddress) => {
|
||||
box.attribute.update(box)
|
||||
}, 'client-removed')
|
||||
.hook(Hyprland, (box, clientAddress) => {
|
||||
box.attribute.update(box);
|
||||
}, 'client-added')
|
||||
.hook(Hyprland.active.workspace, (box) => box.attribute.update(box))
|
||||
.hook(App, (box, name, visible) => { // Update on open
|
||||
if (name == 'overview' && visible) box.attribute.update(box);
|
||||
})
|
||||
|
||||
const Window = ({ address, at: [x, y], size: [w, h], workspace: { id, name }, class: c, title, xwayland }, screenCoords) => {
|
||||
const revealInfoCondition = (Math.min(w, h) * 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 / SCREEN_WIDTH) * SCREEN_WIDTH);
|
||||
else if (x < 0) { w = x + w; x = 0; }
|
||||
if (y + h <= 0) x += (Math.floor(y / SCREEN_HEIGHT) * SCREEN_HEIGHT);
|
||||
else if (y < 0) { h = y + h; y = 0; }
|
||||
// Truncate if offscreen
|
||||
if (x + w > SCREEN_WIDTH) w = SCREEN_WIDTH - x;
|
||||
if (y + h > SCREEN_HEIGHT) h = SCREEN_HEIGHT - y;
|
||||
|
||||
const appIcon = Widget.Icon({
|
||||
icon: substitute(c),
|
||||
size: Math.min(w, h) * OVERVIEW_SCALE / 2.5,
|
||||
});
|
||||
return Widget.Button({
|
||||
attribute: {
|
||||
address, x, y, w, h, ws: id,
|
||||
updateIconSize: (self) => {
|
||||
appIcon.size = Math.min(self.attribute.w, self.attribute.h) * OVERVIEW_SCALE / 2.5;
|
||||
},
|
||||
},
|
||||
className: 'overview-tasks-window',
|
||||
hpack: 'start',
|
||||
vpack: 'start',
|
||||
css: `
|
||||
margin-left: ${Math.round(x * OVERVIEW_SCALE)}px;
|
||||
margin-top: ${Math.round(y * OVERVIEW_SCALE)}px;
|
||||
margin-right: -${Math.round((x + w) * OVERVIEW_SCALE)}px;
|
||||
margin-bottom: -${Math.round((y + h) * OVERVIEW_SCALE)}px;
|
||||
`,
|
||||
onClicked: (self) => {
|
||||
Hyprland.sendMessage(`dispatch focuswindow address:${address}`);
|
||||
App.closeWindow('overview');
|
||||
},
|
||||
onMiddleClickRelease: () => Hyprland.sendMessage(`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.sendMessage(`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',
|
||||
className: 'spacing-v-5',
|
||||
children: [
|
||||
appIcon,
|
||||
// TODO: Add xwayland tag instead of just having italics
|
||||
// Widget.Revealer({
|
||||
// transition: 'slide_down',
|
||||
// revealChild: revealInfoCondition,
|
||||
// child: Widget.Label({
|
||||
// truncate: 'end',
|
||||
// className: `${xwayland ? 'txt txt-italic' : 'txt'}`,
|
||||
// css: `
|
||||
// font-size: ${Math.min(SCREEN_WIDTH, SCREEN_HEIGHT) * OVERVIEW_SCALE / 14.6}px;
|
||||
// margin: 0px ${Math.min(SCREEN_WIDTH, SCREEN_HEIGHT) * 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.drag_source_set_icon_gicon(icon);
|
||||
|
||||
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 OVERVIEW_SCALE
|
||||
const newCss = `
|
||||
margin-left: ${Math.round(x)}px;
|
||||
margin-top: ${Math.round(y)}px;
|
||||
margin-right: -${Math.round(x + (widget.attribute.w * OVERVIEW_SCALE))}px;
|
||||
margin-bottom: -${Math.round(y + (widget.attribute.h * 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 OVERVIEW_SCALE
|
||||
const newCss = `
|
||||
margin-left: ${Math.round(x)}px;
|
||||
margin-top: ${Math.round(y)}px;
|
||||
margin-right: -${Math.round(x + (widget.attribute.w * OVERVIEW_SCALE))}px;
|
||||
margin-bottom: -${Math.round(y + (widget.attribute.h * OVERVIEW_SCALE))}px;
|
||||
`;
|
||||
widget.css = newCss;
|
||||
},
|
||||
}
|
||||
})
|
||||
,
|
||||
});
|
||||
const WorkspaceNumber = (index) => Widget.Label({
|
||||
className: 'overview-tasks-workspace-number',
|
||||
label: `${index}`,
|
||||
css: `
|
||||
margin: ${Math.min(SCREEN_WIDTH, SCREEN_HEIGHT) * OVERVIEW_SCALE * OVERVIEW_WS_NUM_MARGIN_SCALE}px;
|
||||
font-size: ${SCREEN_HEIGHT * OVERVIEW_SCALE * OVERVIEW_WS_NUM_SCALE}px;
|
||||
`,
|
||||
})
|
||||
const widget = Widget.Box({
|
||||
className: 'overview-tasks-workspace',
|
||||
vpack: 'center',
|
||||
css: `
|
||||
min-width: ${SCREEN_WIDTH * OVERVIEW_SCALE}px;
|
||||
min-height: ${SCREEN_HEIGHT * OVERVIEW_SCALE}px;
|
||||
`,
|
||||
children: [Widget.EventBox({
|
||||
hexpand: true,
|
||||
vexpand: true,
|
||||
onPrimaryClick: () => {
|
||||
Hyprland.sendMessage(`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) => {
|
||||
Hyprland.sendMessage(`dispatch movetoworkspacesilent ${index},address:${data.get_text()}`)
|
||||
overviewTick.setValue(!overviewTick.value);
|
||||
});
|
||||
},
|
||||
child: 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 || client.ws !== offset + index) return;
|
||||
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] * OVERVIEW_SCALE),
|
||||
Math.max(0, clientJson.at[1] * 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 * OVERVIEW_SCALE),
|
||||
Math.max(0, newWindow.attribute.y * 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);
|
||||
|
||||
export default () => Widget.Revealer({
|
||||
revealChild: true,
|
||||
transition: 'slide_down',
|
||||
transitionDuration: 200,
|
||||
child: Widget.Box({
|
||||
vertical: true,
|
||||
className: 'overview-tasks',
|
||||
children: Array.from({ length: NUM_OF_WORKSPACE_ROWS }, (_, index) =>
|
||||
OverviewRow({
|
||||
startWorkspace: 1 + index * NUM_OF_WORKSPACE_COLS,
|
||||
workspaces: NUM_OF_WORKSPACE_COLS,
|
||||
})
|
||||
)
|
||||
}),
|
||||
});
|
||||
return array;
|
||||
};
|
||||
|
||||
const OverviewRow = ({ startWorkspace, workspaces, windowName = 'overview' }) => Widget.Box({
|
||||
children: arr(startWorkspace, workspaces).map(Workspace),
|
||||
attribute: {
|
||||
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;
|
||||
if (!App.getWindow(windowName).visible) return;
|
||||
Hyprland.sendMessage('j/clients').then(clients => {
|
||||
const allClients = JSON.parse(clients);
|
||||
const kids = box.get_children();
|
||||
kids.forEach(kid => kid.clear());
|
||||
// console.log('----------------------------');
|
||||
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;
|
||||
}
|
||||
// const modID = client.workspace.id % NUM_OF_WORKSPACES_SHOWN;
|
||||
// console.log(`[${startWorkspace} -> ${startWorkspace + workspaces - 1}] ? (${client.workspace.id} == ${modID})`);
|
||||
// // console.log(`[${startWorkspace} -> ${startWorkspace + workspaces}] ? (${modID})`);
|
||||
// if (startWorkspace <= modID && modID < startWorkspace + workspaces) {
|
||||
// console.log('i care');
|
||||
// const clientWidget = clientMap.get(client.address);
|
||||
// console.log(childID, kids[childID], clientWidget);
|
||||
// if (kids[childID] && clientWidget) {
|
||||
// console.log('hmm remove', clientWidget.attribute)
|
||||
// kids[childID].remove(clientWidget);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
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.sendMessage('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) => {
|
||||
const previousGroup = box.attribute.workspaceGroup;
|
||||
const currentGroup = Math.floor((Hyprland.active.workspace.id - 1) / NUM_OF_WORKSPACES_SHOWN);
|
||||
if (currentGroup !== previousGroup) {
|
||||
box.attribute.update(box);
|
||||
workspaceGroup = currentGroup;
|
||||
}
|
||||
// box.attribute.update(box);
|
||||
})
|
||||
.hook(App, (box, name, visible) => { // Update on open
|
||||
if (name == 'overview' && visible) box.attribute.update(box);
|
||||
})
|
||||
},
|
||||
});
|
||||
|
||||
return Widget.Revealer({
|
||||
revealChild: true,
|
||||
transition: 'slide_down',
|
||||
transitionDuration: 200,
|
||||
child: Widget.Box({
|
||||
vertical: true,
|
||||
className: 'overview-tasks',
|
||||
children: Array.from({ length: NUM_OF_WORKSPACE_ROWS }, (_, index) =>
|
||||
OverviewRow({
|
||||
startWorkspace: 1 + index * NUM_OF_WORKSPACE_COLS,
|
||||
workspaces: NUM_OF_WORKSPACE_COLS,
|
||||
})
|
||||
)
|
||||
}),
|
||||
});
|
||||
}
|
||||
@@ -38,12 +38,6 @@ export const DirectoryButton = ({ parentPath, name, type, icon }) => {
|
||||
homogeneous: true,
|
||||
child: Widget.Icon({
|
||||
icon: icon,
|
||||
setup: (self) => Utils.timeout(1, () => {
|
||||
const styleContext = self.get_parent().get_style_context();
|
||||
const width = styleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
|
||||
const height = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
|
||||
self.size = Math.max(width, height, 1);
|
||||
})
|
||||
}),
|
||||
}),
|
||||
Widget.Label({
|
||||
@@ -112,12 +106,6 @@ export const DesktopEntryButton = (app) => {
|
||||
homogeneous: true,
|
||||
child: Widget.Icon({
|
||||
icon: app.iconName,
|
||||
setup: (self) => Utils.timeout(1, () => {
|
||||
const styleContext = self.get_parent().get_style_context();
|
||||
const width = styleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
|
||||
const height = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
|
||||
self.size = Math.max(width, height, 1);
|
||||
})
|
||||
}),
|
||||
}),
|
||||
Widget.Label({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { Gtk } = imports.gi;
|
||||
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';
|
||||
@@ -76,7 +76,7 @@ export const SearchAndWindows = () => {
|
||||
child: Widget.Label({
|
||||
className: 'overview-search-prompt txt-small txt',
|
||||
label: 'Type to search'
|
||||
})
|
||||
}),
|
||||
});
|
||||
|
||||
const entryIconRevealer = Widget.Revealer({
|
||||
@@ -217,7 +217,10 @@ export const SearchAndWindows = () => {
|
||||
entry,
|
||||
Widget.Box({
|
||||
className: 'overview-search-icon-box',
|
||||
setup: box => box.pack_start(entryPromptRevealer, true, true, 0),
|
||||
setup: (box) => {
|
||||
box.pack_start(entryPromptRevealer, true, true, 0)
|
||||
// enableClickthrough(box);
|
||||
},
|
||||
}),
|
||||
entryIcon,
|
||||
]
|
||||
@@ -233,10 +236,31 @@ export const SearchAndWindows = () => {
|
||||
}
|
||||
})
|
||||
.on('key-press-event', (widget, event) => { // Typing
|
||||
if (event.get_keyval()[1] >= 32 && event.get_keyval()[1] <= 126 && widget != entry) {
|
||||
Utils.timeout(1, () => entry.grab_focus());
|
||||
entry.set_text(entry.text + String.fromCharCode(event.get_keyval()[1]));
|
||||
entry.set_position(-1);
|
||||
const keyval = event.get_keyval()[1];
|
||||
const modstate = event.get_state()[1];
|
||||
if (modstate & Gdk.ModifierType.CONTROL_MASK) { // Ctrl held
|
||||
if (keyval == Gdk.KEY_b)
|
||||
entry.set_position(Math.max(entry.get_position() - 1, 0));
|
||||
else if (keyval == Gdk.KEY_f)
|
||||
entry.set_position(Math.min(entry.get_position() + 1, entry.get_text().length));
|
||||
else if (keyval == Gdk.KEY_n) { // simulate Down arrow
|
||||
entry.get_root_window().simulate_key_press(Gdk.KEY_Down, Gdk.ModifierType.NONE);
|
||||
// entry.get_root_window().simulate_key_release(Gdk.KEY_Down, Gdk.ModifierType.NONE);
|
||||
}
|
||||
else if (keyval == Gdk.KEY_k) { // Delete to end
|
||||
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 { // 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);
|
||||
}
|
||||
}
|
||||
})
|
||||
,
|
||||
|
||||
@@ -44,6 +44,7 @@ function copyToClipboard(text) {
|
||||
function substituteLang(str) {
|
||||
const subs = [
|
||||
{ from: 'javascript', to: 'js' },
|
||||
{ from: 'bash', to: 'sh' },
|
||||
];
|
||||
|
||||
for (const { from, to } of subs) {
|
||||
@@ -58,7 +59,7 @@ const HighlightedCode = (content, lang) => {
|
||||
const buffer = new GtkSource.Buffer();
|
||||
const sourceView = new GtkSource.View({
|
||||
buffer: buffer,
|
||||
wrap_mode: Gtk.WrapMode.WORD
|
||||
wrap_mode: Gtk.WrapMode.NONE
|
||||
});
|
||||
const langManager = GtkSource.LanguageManager.get_default();
|
||||
let displayLang = langManager.get_language(substituteLang(lang)); // Set your preferred language
|
||||
|
||||
@@ -19,14 +19,6 @@ export const chatGPTTabIcon = Icon({
|
||||
hpack: 'center',
|
||||
className: 'sidebar-chat-apiswitcher-icon',
|
||||
icon: `openai-symbolic`,
|
||||
setup: (self) => Utils.timeout(513, () => { // stupid condition race
|
||||
const styleContext = self.get_style_context();
|
||||
const width = styleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
|
||||
const height = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
|
||||
// console.log(Math.round(Math.max(width, height, 1)));
|
||||
self.size = Math.max(width, height, 1) * 116 / 180;
|
||||
// ↑ Why such a specific proportion? See https://openai.com/brand#logos
|
||||
})
|
||||
});
|
||||
|
||||
const ChatGPTInfo = () => {
|
||||
@@ -34,14 +26,6 @@ const ChatGPTInfo = () => {
|
||||
hpack: 'center',
|
||||
className: 'sidebar-chat-welcome-logo',
|
||||
icon: `openai-symbolic`,
|
||||
setup: (self) => Utils.timeout(513, () => { // stupid condition race
|
||||
const styleContext = self.get_style_context();
|
||||
const width = styleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
|
||||
const height = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
|
||||
// console.log(Math.round(Math.max(width, height, 1)));
|
||||
self.size = Math.max(width, height, 1) * 116 / 180;
|
||||
// ↑ Why such a specific proportion? See https://openai.com/brand#logos
|
||||
})
|
||||
});
|
||||
return Box({
|
||||
vertical: true,
|
||||
|
||||
@@ -20,12 +20,6 @@ export const geminiTabIcon = Icon({
|
||||
hpack: 'center',
|
||||
className: 'sidebar-chat-apiswitcher-icon',
|
||||
icon: `google-gemini-symbolic`,
|
||||
setup: (self) => Utils.timeout(513, () => { // stupid condition race
|
||||
const styleContext = self.get_style_context();
|
||||
const width = styleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
|
||||
const height = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
|
||||
self.size = Math.max(width, height, 1) * 116 / 180;
|
||||
})
|
||||
})
|
||||
|
||||
const GeminiInfo = () => {
|
||||
@@ -33,12 +27,6 @@ const GeminiInfo = () => {
|
||||
hpack: 'center',
|
||||
className: 'sidebar-chat-welcome-logo',
|
||||
icon: `google-gemini-symbolic`,
|
||||
setup: (self) => Utils.timeout(513, () => { // stupid condition race
|
||||
const styleContext = self.get_style_context();
|
||||
const width = styleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
|
||||
const height = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
|
||||
self.size = Math.max(width, height, 1) * 116 / 180;
|
||||
})
|
||||
});
|
||||
return Box({
|
||||
vertical: true,
|
||||
|
||||
@@ -417,6 +417,14 @@ export const sendMessage = (text) => {
|
||||
true
|
||||
));
|
||||
}
|
||||
else if (text.startsWith('/place')) {
|
||||
const newImage = WaifuImage(['/place']);
|
||||
waifuContent.add(newImage);
|
||||
Utils.timeout(IMAGE_REVEAL_DELAY, () => newImage.attribute.update(
|
||||
DummyTag(400, 600, 'https://placewaifu.com/image/400/600', '#F0A235'),
|
||||
true
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
else WaifuService.fetch(text);
|
||||
|
||||
@@ -13,19 +13,12 @@ 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';
|
||||
const TextView = Widget.subclass(Gtk.TextView, "AgsTextView");
|
||||
import { enableClickthrough } from '../../lib/roundedcorner.js';
|
||||
const TextView = Widget.subclass(Gtk.TextView, "AgsTextView");
|
||||
|
||||
|
||||
const EXPAND_INPUT_THRESHOLD = 30;
|
||||
const APIS = [
|
||||
{
|
||||
name: 'Assistant (ChatGPT 3.5)',
|
||||
sendCommand: chatGPTSendMessage,
|
||||
contentWidget: chatGPTView,
|
||||
commandBar: chatGPTCommands,
|
||||
tabIcon: chatGPTTabIcon,
|
||||
placeholderText: 'Message ChatGPT...',
|
||||
},
|
||||
{
|
||||
name: 'Assistant (Gemini Pro)',
|
||||
sendCommand: geminiSendMessage,
|
||||
@@ -34,6 +27,14 @@ const APIS = [
|
||||
tabIcon: geminiTabIcon,
|
||||
placeholderText: 'Message Gemini...',
|
||||
},
|
||||
{
|
||||
name: 'Assistant (ChatGPT 3.5)',
|
||||
sendCommand: chatGPTSendMessage,
|
||||
contentWidget: chatGPTView,
|
||||
commandBar: chatGPTCommands,
|
||||
tabIcon: chatGPTTabIcon,
|
||||
placeholderText: 'Message ChatGPT...',
|
||||
},
|
||||
{
|
||||
name: 'Waifus',
|
||||
sendCommand: waifuSendMessage,
|
||||
@@ -141,6 +142,7 @@ const chatPlaceholderRevealer = Revealer({
|
||||
transition: 'crossfade',
|
||||
transitionDuration: 200,
|
||||
child: chatPlaceholder,
|
||||
setup: enableClickthrough,
|
||||
});
|
||||
|
||||
const textboxArea = Box({ // Entry area
|
||||
@@ -159,12 +161,20 @@ const textboxArea = Box({ // Entry area
|
||||
const apiContentStack = Stack({
|
||||
vexpand: true,
|
||||
transition: 'slide_left_right',
|
||||
items: APIS.map(api => [api.name, api.contentWidget]),
|
||||
transitionDuration: 160,
|
||||
children: APIS.reduce((acc, api) => {
|
||||
acc[api.name] = api.contentWidget;
|
||||
return acc;
|
||||
}, {}),
|
||||
})
|
||||
|
||||
const apiCommandStack = Stack({
|
||||
transition: 'slide_up_down',
|
||||
items: APIS.map(api => [api.name, api.commandBar]),
|
||||
transitionDuration: 160,
|
||||
children: APIS.reduce((acc, api) => {
|
||||
acc[api.name] = api.commandBar;
|
||||
return acc;
|
||||
}, {}),
|
||||
})
|
||||
|
||||
function switchToTab(id) {
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
const { Box, Button, Label } = Widget;
|
||||
|
||||
export const SidebarModule = ({
|
||||
name,
|
||||
child
|
||||
}) => {
|
||||
return Box({
|
||||
className: 'sidebar-module',
|
||||
vertical: true,
|
||||
children: [
|
||||
Button({
|
||||
child: Box({
|
||||
children: [
|
||||
Label({
|
||||
className: 'txt-small txt',
|
||||
label: `${name}`,
|
||||
}),
|
||||
Box({
|
||||
hexpand: true,
|
||||
}),
|
||||
Label({
|
||||
className: 'sidebar-module-btn-arrow',
|
||||
})
|
||||
]
|
||||
})
|
||||
})
|
||||
]
|
||||
});
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
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, Label, Scrollable } = Widget;
|
||||
import { SidebarModule } from './module.js';
|
||||
|
||||
export const QuickScripts = () => SidebarModule({
|
||||
name: 'Quick scripts',
|
||||
child: Box({
|
||||
})
|
||||
})
|
||||
@@ -30,7 +30,11 @@ let currentTabId = 0;
|
||||
export const contentStack = Stack({
|
||||
vexpand: true,
|
||||
transition: 'slide_left_right',
|
||||
items: contents.map(item => [item.name, item.content]),
|
||||
transitionDuration: 160,
|
||||
children: contents.reduce((acc, item) => {
|
||||
acc[item.name] = item.content;
|
||||
return acc;
|
||||
}, {}),
|
||||
})
|
||||
|
||||
function switchToTab(id) {
|
||||
|
||||
@@ -2,15 +2,18 @@ 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 { QuickScripts } from './quickscripts.js';
|
||||
import QuickScripts from './tools/quickscripts.js';
|
||||
import ColorPicker from './tools/colorpicker.js';
|
||||
|
||||
export default Scrollable({
|
||||
hscroll: "never",
|
||||
vscroll: "automatic",
|
||||
child: Box({
|
||||
vertical: true,
|
||||
className: 'spacing-v-10',
|
||||
children: [
|
||||
// QuickScripts(),
|
||||
QuickScripts(),
|
||||
ColorPicker(),
|
||||
]
|
||||
})
|
||||
});
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
// 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;
|
||||
|
||||
const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
|
||||
|
||||
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
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
// 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 '../../../lib/materialicon.js';
|
||||
import { setupCursorHover } from '../../../lib/cursorhover.js';
|
||||
|
||||
import { ColorPickerSelection, hslToHex, hslToRgbValues, hexToHSL } from './color.js';
|
||||
|
||||
const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
|
||||
|
||||
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: '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: 'Color picker',
|
||||
revealChild: false,
|
||||
child: Box({
|
||||
className: 'spacing-h-5',
|
||||
children: [
|
||||
hueSelector,
|
||||
saturationAndLightnessSelector,
|
||||
result,
|
||||
]
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import { setupCursorHover } from '../../../lib/cursorhover.js';
|
||||
import { MaterialIcon } from '../../../lib/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}`,
|
||||
}),
|
||||
Box({
|
||||
hexpand: true,
|
||||
}),
|
||||
Box({
|
||||
className: 'sidebar-module-btn-arrow',
|
||||
homogeneous: true,
|
||||
children: [headerButtonIcon],
|
||||
})
|
||||
]
|
||||
})
|
||||
});
|
||||
const content = Revealer({
|
||||
revealChild: revealChild,
|
||||
transition: 'slide_down',
|
||||
transitionDuration: 200,
|
||||
child: Box({
|
||||
className: 'margin-top-5',
|
||||
homogeneous: true,
|
||||
children: [child],
|
||||
}),
|
||||
});
|
||||
return Box({
|
||||
className: 'sidebar-module',
|
||||
vertical: true,
|
||||
children: [
|
||||
header,
|
||||
content,
|
||||
]
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
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 '../../../lib/materialicon.js';
|
||||
import { setupCursorHover } from '../../../lib/cursorhover.js';
|
||||
|
||||
Gtk.IconTheme.get_default().append_search_path(`${App.configDir}/assets`);
|
||||
const distroID = exec(`bash -c 'cat /etc/os-release | grep "^ID=" | cut -d "=" -f 2'`).trim();
|
||||
const isDebianDistro = (distroID == 'linuxmint' || distroID == 'ubuntu' || distroID == 'debian' || distroID == 'zorin' || distroID == 'pop' || distroID == 'raspbian' || distroID == 'kali' || distroID == 'elementary');
|
||||
const isArchDistro = (distroID == 'arch' || distroID == 'endeavouros');
|
||||
const hasFlatpak = !!exec(`bash -c 'command -v flatpak'`);
|
||||
|
||||
const scripts = [
|
||||
{
|
||||
icon: 'nixos-symbolic',
|
||||
name: '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: '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: 'Update packages',
|
||||
command: `sudo apt update && sudo apt upgrade -y`,
|
||||
enabled: isDebianDistro,
|
||||
},
|
||||
{
|
||||
icon: 'fedora-symbolic',
|
||||
name: 'Update packages',
|
||||
command: `sudo dnf upgrade -y`,
|
||||
enabled: distroID == 'fedora',
|
||||
},
|
||||
{
|
||||
icon: 'arch-symbolic',
|
||||
name: 'Update packages',
|
||||
command: `sudo pacman -Syyu`,
|
||||
enabled: isArchDistro,
|
||||
},
|
||||
{
|
||||
icon: 'flatpak-symbolic',
|
||||
name: 'Uninstall unused flatpak packages',
|
||||
command: `flatpak uninstall --unused`,
|
||||
enabled: hasFlatpak,
|
||||
},
|
||||
];
|
||||
|
||||
export default () => SidebarModule({
|
||||
icon: MaterialIcon('code', 'norm'),
|
||||
name: '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: () => {
|
||||
App.closeWindow('sideleft');
|
||||
execAsync([`bash`, `-c`, `foot fish -C "${script.command}"`]).catch(print)
|
||||
.then(() => {
|
||||
scriptStateIcon.label = 'done';
|
||||
})
|
||||
},
|
||||
setup: setupCursorHover,
|
||||
}),
|
||||
],
|
||||
})
|
||||
}),
|
||||
})
|
||||
});
|
||||
@@ -20,7 +20,7 @@ export default (props) => {
|
||||
vertical: true,
|
||||
className: 'spacing-v-5',
|
||||
children: [
|
||||
MaterialIcon('notifications_active', 'badonkers'),
|
||||
MaterialIcon('notifications_active', 'gigantic'),
|
||||
Label({ label: 'No notifications', className: 'txt-small' }),
|
||||
]
|
||||
}),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
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';
|
||||
|
||||
@@ -151,7 +152,7 @@ export const ModuleIdleInhibitor = (props = {}) => Widget.Button({ // TODO: Make
|
||||
self.toggleClassName('sidebar-button-active', self.attribute.enabled);
|
||||
if (self.attribute.enabled) {
|
||||
self.attribute.inhibitor = Utils.subprocess(
|
||||
['wayland-idle-inhibitor.py'],
|
||||
[`${App.configDir}/scripts/wayland-idle-inhibitor.py`],
|
||||
(output) => print(output),
|
||||
(err) => logError(err),
|
||||
self,
|
||||
|
||||
@@ -92,7 +92,7 @@ const todoItems = (isDone) => Widget.Scrollable({
|
||||
vpack: 'center',
|
||||
className: 'txt',
|
||||
children: [
|
||||
MaterialIcon(`${isDone ? 'checklist' : 'check_circle'}`, 'badonkers'),
|
||||
MaterialIcon(`${isDone ? 'checklist' : 'check_circle'}`, 'gigantic'),
|
||||
Label({ label: `${isDone ? 'Finished tasks will go here' : 'Nothing here!'}` })
|
||||
]
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user