import { bind, execAsync, Variable } from 'astal'; import { App, Gtk, Widget } from 'astal/gtk3'; import AstalApps from 'gi://AstalApps'; import AstalHyprland from 'gi://AstalHyprland'; import PopupWindow from '../misc/popup-window'; import Separator from '../misc/separator'; import { hyprMessage } from '../../lib'; const ICON_SEP = 6; const takeScreenshot = (selector: string[], delay = 1000): void => { App.get_window('win-screenshot')?.set_visible(false); setTimeout(() => { execAsync([ `${SRC}/widgets/screenshot/capture.sh`, ].concat(selector)).catch(console.error); }, delay); }; export default () => { const hyprland = AstalHyprland.get_default(); const windowList = <box vertical /> as Widget.Box; const updateWindows = async() => { if (!App.get_window('win-screenshot')?.get_visible()) { return; } const applications = AstalApps.Apps.new(); 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={applications.fuzzy_query(client.class)[0].iconName} /> <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.get_name()]); }} > <label label={`${monitor.get_name()}: ${monitor.get_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; let frozen = false; const freezeIcon = <icon icon="checkbox-symbolic" /> as Widget.Icon; const freezeButton = ( <button cursor="pointer" className="header-btn" onButtonReleaseEvent={() => { frozen = !frozen; freezeIcon.set_icon(frozen ? 'checkbox-checked-symbolic' : 'checkbox-symbolic'); }} > <box halign={Gtk.Align.CENTER}> {freezeIcon} <Separator size={ICON_SEP} /> freeze </box> </button> ) as Widget.Button; const regionButton = ( <button cursor="pointer" className="header-btn" onButtonReleaseEvent={() => { takeScreenshot(['region', frozen ? 'true' : 'false'], 0); }} > <box halign={Gtk.Align.CENTER}> <icon icon="tool-crop-symbolic" /> </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" /> <box> {regionButton} {freezeButton} </box> </box> {stack} </box> </PopupWindow> ); };