feat(agsV2): migrate screenshot window
All checks were successful
Discord / discord commits (push) Has been skipped
All checks were successful
Discord / discord commits (push) Has been skipped
This commit is contained in:
parent
c17a45e5d0
commit
653961ada1
15 changed files with 259 additions and 54 deletions
|
@ -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();
|
||||
},
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
|
BIN
nixosModules/ags/v2/package-lock.json
generated
BIN
nixosModules/ags/v2/package-lock.json
generated
Binary file not shown.
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
.bar {
|
||||
margin-left: 5px;
|
||||
margin-right: 15px;
|
||||
margin-bottom: 13px;
|
||||
|
||||
.bar-item {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -67,8 +67,6 @@
|
|||
|
||||
.notification-list-box {
|
||||
padding: 0 12px;
|
||||
border-radius: 30px;
|
||||
border-top: 2px solid $accent-color;
|
||||
|
||||
.notification {
|
||||
box-shadow: none;
|
||||
|
|
|
@ -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" />
|
||||
|
|
163
nixosModules/ags/v2/widgets/screenshot/main.tsx
Normal file
163
nixosModules/ags/v2/widgets/screenshot/main.tsx
Normal 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>
|
||||
);
|
||||
};
|
25
nixosModules/ags/v2/widgets/screenshot/style.scss
Normal file
25
nixosModules/ags/v2/widgets/screenshot/style.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue