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 { NotifPopups, NotifCenter } from './widgets/notifs/main';
import PowerMenu from './widgets/powermenu/main';
import Screenshot from './widgets/screenshot/main';
import MonitorClicks from './services/monitor-clicks';
@ -48,6 +49,7 @@ switch (CONF) {
NotifPopups();
NotifCenter();
PowerMenu();
Screenshot();
new MonitorClicks();
},

View file

@ -51,7 +51,7 @@ self: {
})
// {
"${configDir}/node_modules".source =
buildNodeModules ./. "sha256-f0hbPvHTqeFM7mfmV+sN4EEuE0F91f5kjJ/EHy0oU+Y=";
buildNodeModules ./. "sha256-pK9S6qUjTIL0JDegYJlHSY5XEpLFKfA98MfZ59Q3IL4=";
"${configDir}/tsconfig.json".source = pkgs.writers.writeJSON "tsconfig.json" {
"$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';
const Hyprland = AstalHyprland.get_default();
@ -109,3 +111,16 @@ export const centerCursor = async(): Promise<void> => {
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",
"@stylistic/eslint-plugin": "2.9.0",
"@types/eslint__js": "8.42.3",
"@types/node": "22.7.7",
"@types/node": "22.8.1",
"eslint": "9.13.0",
"eslint-plugin-jsdoc": "50.4.3",
"fzf": "0.5.2",
"jiti": "2.3.3",
"typescript": "5.6.3",
"typescript-eslint": "8.10.0"
"typescript-eslint": "8.11.0"
}
}

View file

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

View file

@ -19,3 +19,4 @@ window, viewport {
@import 'widgets/lockscreen/style.scss';
@import 'widgets/notifs/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 { get_app_icon } from '../../lib';
/* Types */
import AstalApps from 'gi://AstalApps';
type AppItemProps = Widget.BoxProps & {
@ -26,18 +28,11 @@ export class AppItem extends Widget.Box {
this.app = app;
const icon = (
<icon css="font-size: 42px; margin-right: 25px;" />
) as Widget.Icon;
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;
}
<icon
icon={get_app_icon(this.app)}
css="font-size: 42px; margin-right: 25px;"
/>
);
const textBox = (
<box

View file

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

View file

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

View file

@ -24,11 +24,7 @@
}
.cal-box {
border-radius: 30px;
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;
calendar {
@ -36,7 +32,6 @@
background-color: inherit;
padding: .5rem .10rem 0;
margin-left: 10px;
border-radius: 30px;
&>* {
border: solid 0 transparent;

View file

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

View file

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