feat(agsV2): migrate screenshot window
All checks were successful
Discord / discord commits (push) Has been skipped

This commit is contained in:
matt1432 2024-10-28 14:45:10 -04:00
parent c17a45e5d0
commit 653961ada1
15 changed files with 259 additions and 54 deletions

View file

@ -12,6 +12,7 @@ import Corners from './widgets/corners/main';
import IconBrowser from './widgets/icon-browser/main'; import IconBrowser from './widgets/icon-browser/main';
import { NotifPopups, NotifCenter } from './widgets/notifs/main'; import { NotifPopups, NotifCenter } from './widgets/notifs/main';
import PowerMenu from './widgets/powermenu/main'; import PowerMenu from './widgets/powermenu/main';
import Screenshot from './widgets/screenshot/main';
import MonitorClicks from './services/monitor-clicks'; import MonitorClicks from './services/monitor-clicks';
@ -48,6 +49,7 @@ switch (CONF) {
NotifPopups(); NotifPopups();
NotifCenter(); NotifCenter();
PowerMenu(); PowerMenu();
Screenshot();
new MonitorClicks(); new MonitorClicks();
}, },

View file

@ -51,7 +51,7 @@ self: {
}) })
// { // {
"${configDir}/node_modules".source = "${configDir}/node_modules".source =
buildNodeModules ./. "sha256-f0hbPvHTqeFM7mfmV+sN4EEuE0F91f5kjJ/EHy0oU+Y="; buildNodeModules ./. "sha256-pK9S6qUjTIL0JDegYJlHSY5XEpLFKfA98MfZ59Q3IL4=";
"${configDir}/tsconfig.json".source = pkgs.writers.writeJSON "tsconfig.json" { "${configDir}/tsconfig.json".source = pkgs.writers.writeJSON "tsconfig.json" {
"$schema" = "https://json.schemastore.org/tsconfig"; "$schema" = "https://json.schemastore.org/tsconfig";

View file

@ -1,4 +1,6 @@
import { Gdk } from 'astal/gtk3'; import { App, Gdk, Widget } from 'astal/gtk3';
import AstalApps from 'gi://AstalApps';
import AstalHyprland from 'gi://AstalHyprland'; import AstalHyprland from 'gi://AstalHyprland';
const Hyprland = AstalHyprland.get_default(); const Hyprland = AstalHyprland.get_default();
@ -109,3 +111,16 @@ export const centerCursor = async(): Promise<void> => {
await hyprMessage(`dispatch movecursor ${x} ${y}`); await hyprMessage(`dispatch movecursor ${x} ${y}`);
}; };
export const get_app_icon = (app: AstalApps.Application): string => {
if (app.get_icon_name() && Widget.Icon.lookup_icon(app.get_icon_name())) {
return app.get_icon_name();
}
else {
const iconString = app.get_key('Icon');
App.add_icons(iconString);
return iconString;
}
};

Binary file not shown.

View file

@ -6,12 +6,12 @@
"@eslint/js": "9.13.0", "@eslint/js": "9.13.0",
"@stylistic/eslint-plugin": "2.9.0", "@stylistic/eslint-plugin": "2.9.0",
"@types/eslint__js": "8.42.3", "@types/eslint__js": "8.42.3",
"@types/node": "22.7.7", "@types/node": "22.8.1",
"eslint": "9.13.0", "eslint": "9.13.0",
"eslint-plugin-jsdoc": "50.4.3", "eslint-plugin-jsdoc": "50.4.3",
"fzf": "0.5.2", "fzf": "0.5.2",
"jiti": "2.3.3", "jiti": "2.3.3",
"typescript": "5.6.3", "typescript": "5.6.3",
"typescript-eslint": "8.10.0" "typescript-eslint": "8.11.0"
} }
} }

View file

@ -134,23 +134,25 @@ export default class MonitorClicks extends GObject.Object {
overlayLayer.find( overlayLayer.find(
(n) => n.namespace.startsWith(name), (n) => n.namespace.startsWith(name),
) || ) ||
// Return an empty Layer if widget doesn't exist // Return an empty Layer if widget doesn't exist
{ {
address: '', address: '',
x: 0, x: 0,
y: 0, y: 0,
w: 0, w: 0,
h: 0, h: 0,
namespace: '', namespace: '',
}, },
); );
}); });
return arr; return arr;
}; };
const clickIsOnWidget = (w: Layer) => { const clickIsOnWidget = (w: Layer) => {
return pos.x > w.x && pos.x < w.x + w.w && return (
pos.y > w.y && pos.y < w.y + w.h; pos.x > w.x && pos.x < w.x + w.w &&
pos.y > w.y && pos.y < w.y + w.h
);
}; };
const noCloseWidgets = getNoCloseWidgets(noCloseWidgetsNames); const noCloseWidgets = getNoCloseWidgets(noCloseWidgetsNames);
@ -158,24 +160,25 @@ export default class MonitorClicks extends GObject.Object {
const widgets = overlayLayer.filter((n) => { const widgets = overlayLayer.filter((n) => {
let window = null as null | PopupWindow; let window = null as null | PopupWindow;
if (App.get_windows().some((win) => if (App.get_windows().some((win) => win.name === n.namespace)) {
win.name === n.namespace)) {
window = (App.get_window(n.namespace) as PopupWindow); window = (App.get_window(n.namespace) as PopupWindow);
} }
return window && return window &&
window.close_on_unfocus && window.close_on_unfocus &&
window.close_on_unfocus === window.close_on_unfocus ===
clickStage; clickStage;
}); });
if (noCloseWidgets.some(clickIsOnWidget)) { if (noCloseWidgets.some(clickIsOnWidget)) {
// Don't handle clicks when on certain widgets // Don't handle clicks when on certain widgets
} }
else { else {
widgets.forEach((w) => { widgets.forEach((w) => {
if (!(pos.x > w.x && pos.x < w.x + w.w && if (!(
pos.y > w.y && pos.y < w.y + w.h)) { pos.x > w.x && pos.x < w.x + w.w &&
pos.y > w.y && pos.y < w.y + w.h
)) {
App.get_window(w.namespace)?.set_visible(false); App.get_window(w.namespace)?.set_visible(false);
} }
}); });

View file

@ -19,3 +19,4 @@ window, viewport {
@import 'widgets/lockscreen/style.scss'; @import 'widgets/lockscreen/style.scss';
@import 'widgets/notifs/style.scss'; @import 'widgets/notifs/style.scss';
@import 'widgets/powermenu/style.scss'; @import 'widgets/powermenu/style.scss';
@import 'widgets/screenshot/style.scss';

View file

@ -1,6 +1,8 @@
import { App, Gtk, Widget } from 'astal/gtk3'; import { Gtk, Widget } from 'astal/gtk3';
import { register } from 'astal/gobject'; import { register } from 'astal/gobject';
import { get_app_icon } from '../../lib';
/* Types */ /* Types */
import AstalApps from 'gi://AstalApps'; import AstalApps from 'gi://AstalApps';
type AppItemProps = Widget.BoxProps & { type AppItemProps = Widget.BoxProps & {
@ -26,18 +28,11 @@ export class AppItem extends Widget.Box {
this.app = app; this.app = app;
const icon = ( const icon = (
<icon css="font-size: 42px; margin-right: 25px;" /> <icon
) as Widget.Icon; icon={get_app_icon(this.app)}
css="font-size: 42px; margin-right: 25px;"
if (app.get_icon_name() && Widget.Icon.lookup_icon(app.get_icon_name())) { />
icon.icon = this.app.get_icon_name(); );
}
else {
const iconString = this.app.get_key('Icon');
App.add_icons(iconString);
icon.icon = iconString;
}
const textBox = ( const textBox = (
<box <box

View file

@ -122,6 +122,7 @@ export default () => {
<button <button
css="margin-left: 5px;" css="margin-left: 5px;"
cursor="pointer"
onButtonReleaseEvent={refreshApplications} onButtonReleaseEvent={refreshApplications}
> >
<icon icon="view-refresh-symbolic" css="font-size: 26px;" /> <icon icon="view-refresh-symbolic" css="font-size: 26px;" />
@ -129,18 +130,20 @@ export default () => {
</box> </box>
<scrollable <eventbox cursor="pointer">
className="widget app-list" <scrollable
className="widget app-list"
css="min-height: 600px; min-width: 600px;" css="min-height: 600px; min-width: 600px;"
hscroll={Gtk.PolicyType.NEVER} hscroll={Gtk.PolicyType.NEVER}
vscroll={Gtk.PolicyType.AUTOMATIC} vscroll={Gtk.PolicyType.AUTOMATIC}
> >
<box vertical> <box vertical>
{list} {list}
{placeholder} {placeholder}
</box> </box>
</scrollable> </scrollable>
</eventbox>
</box> </box>
</PopupWindow> </PopupWindow>
); );

View file

@ -1,4 +1,6 @@
.bar { .bar {
margin-left: 5px;
margin-right: 15px;
margin-bottom: 13px; margin-bottom: 13px;
.bar-item { .bar-item {

View file

@ -24,11 +24,7 @@
} }
.cal-box { .cal-box {
border-radius: 30px;
padding: 0 1rem .2rem; padding: 0 1rem .2rem;
background-color: $window_bg_color;
border-bottom: 2px solid $accent_color;
border-top: 2px solid $accent_color;
margin: 0 12px 18px; margin: 0 12px 18px;
calendar { calendar {
@ -36,7 +32,6 @@
background-color: inherit; background-color: inherit;
padding: .5rem .10rem 0; padding: .5rem .10rem 0;
margin-left: 10px; margin-left: 10px;
border-radius: 30px;
&>* { &>* {
border: solid 0 transparent; border: solid 0 transparent;

View file

@ -67,8 +67,6 @@
.notification-list-box { .notification-list-box {
padding: 0 12px; padding: 0 12px;
border-radius: 30px;
border-top: 2px solid $accent-color;
.notification { .notification {
box-shadow: none; box-shadow: none;

View file

@ -10,6 +10,7 @@ const PowermenuWidget = () => (
<centerbox className="powermenu widget"> <centerbox className="powermenu widget">
<button <button
className="shutdown button" className="shutdown button"
cursor="pointer"
onButtonReleaseEvent={() => execAsync(['systemctl', 'poweroff']).catch(print)} onButtonReleaseEvent={() => execAsync(['systemctl', 'poweroff']).catch(print)}
> >
<icon icon="system-shutdown-symbolic" /> <icon icon="system-shutdown-symbolic" />
@ -17,6 +18,7 @@ const PowermenuWidget = () => (
<button <button
className="reboot button" className="reboot button"
cursor="pointer"
onButtonReleaseEvent={() => execAsync(['systemctl', 'reboot']).catch(print)} onButtonReleaseEvent={() => execAsync(['systemctl', 'reboot']).catch(print)}
> >
<icon icon="system-restart-symbolic" /> <icon icon="system-restart-symbolic" />
@ -24,6 +26,7 @@ const PowermenuWidget = () => (
<button <button
className="logout button" className="logout button"
cursor="pointer"
onButtonReleaseEvent={() => hyprMessage('dispatch exit').catch(print)} onButtonReleaseEvent={() => hyprMessage('dispatch exit').catch(print)}
> >
<icon icon="system-log-out-symbolic" /> <icon icon="system-log-out-symbolic" />

View file

@ -0,0 +1,163 @@
import { bind, execAsync, Variable } from 'astal';
import { App, Gtk, Widget } from 'astal/gtk3';
import AstalApps from 'gi://AstalApps';
const Applications = AstalApps.Apps.new();
import AstalHyprland from 'gi://AstalHyprland';
const Hyprland = AstalHyprland.get_default();
import PopupWindow from '../misc/popup-window';
import Separator from '../misc/separator';
import { get_app_icon, hyprMessage } from '../../lib';
const ICON_SEP = 6;
const takeScreenshot = (selector: string, delay = 1000): void => {
App.get_window('win-screenshot')?.set_visible(false);
setTimeout(() => {
execAsync([
'bash',
'-c',
`grim ${selector} - | satty -f - || true`,
]).catch(console.error);
}, delay);
};
export default () => {
const windowList = <box vertical /> as Widget.Box;
const updateWindows = async() => {
if (!App.get_window('win-screenshot')?.visible) {
return;
}
windowList.children = (JSON.parse(await hyprMessage('j/clients')) as AstalHyprland.Client[])
.filter((client) => client.workspace.id === Hyprland.get_focused_workspace().get_id())
.map((client) => (
<button
className="item-btn"
cursor="pointer"
onButtonReleaseEvent={() => {
takeScreenshot(`-w ${client.address}`);
}}
>
<box halign={Gtk.Align.CENTER}>
<icon icon={get_app_icon(Applications.fuzzy_query(client.class)[0])} />
<Separator size={ICON_SEP} />
<label
label={client.title}
truncate
max_width_chars={50}
/>
</box>
</button>
));
};
Hyprland.connect('notify::clients', updateWindows);
Hyprland.connect('notify::focused-workspace', updateWindows);
const Shown = Variable<string>('monitors');
const stack = (
<stack
shown={bind(Shown)}
transitionType={Gtk.StackTransitionType.SLIDE_LEFT_RIGHT}
>
<scrollable name="monitors">
<box vertical>
{bind(Hyprland, 'monitors').as((monitors) => monitors.map((monitor) => (
<button
className="item-btn"
cursor="pointer"
onButtonReleaseEvent={() => {
takeScreenshot(`-o ${monitor.name}`);
}}
>
<label
label={`${monitor.name}: ${monitor.description}`}
truncate
maxWidthChars={50}
/>
</button>
)))}
</box>
</scrollable>
<scrollable name="windows">
{windowList}
</scrollable>
</stack>
) as Widget.Stack;
const StackButton = ({ label = '', iconName = '' }) => (
<button
cursor="pointer"
className={bind(Shown).as((shown) =>
`header-btn${shown === label ? ' active' : ''}`)}
onButtonReleaseEvent={() => {
Shown.set(label);
}}
>
<box halign={Gtk.Align.CENTER}>
<icon icon={iconName} />
<Separator size={ICON_SEP} />
{label}
</box>
</button>
) as Widget.Button;
const regionButton = (
<button
cursor="pointer"
className="header-btn"
onButtonReleaseEvent={() => {
takeScreenshot('-g "$(slurp)"', 0);
}}
>
<box halign={Gtk.Align.CENTER}>
<icon icon="tool-pencil-symbolic" />
<Separator size={ICON_SEP} />
region
</box>
</button>
) as Widget.Button;
return (
<PopupWindow
name="screenshot"
on_open={() => {
updateWindows();
}}
>
<box
className="screenshot widget"
vertical
>
<box
className="header"
homogeneous
>
<StackButton label="monitors" iconName="display-symbolic" />
<StackButton label="windows" iconName="window-symbolic" />
{regionButton}
</box>
{stack}
</box>
</PopupWindow>
);
};

View file

@ -0,0 +1,25 @@
.screenshot {
font-size: 30px;
.header {
.header-btn {
margin: 5px;
transition: background 400ms;
&.active {
background: $window_bg_color;
}
}
}
scrollable {
margin: 5px;
min-height: 400px;
box {
.item-btn {
margin: 3px;
}
}
}
}