safe
This commit is contained in:
@@ -7,6 +7,9 @@
|
||||
padding: 28px;
|
||||
background: colors.$bg-translucent;
|
||||
border-radius: $radius $radius 0 0;
|
||||
max-width: 1600px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
|
||||
& entry {
|
||||
background: transparent;
|
||||
@@ -22,29 +25,46 @@
|
||||
}
|
||||
|
||||
& flowbox {
|
||||
padding: 16px 24px;
|
||||
padding: 16px 36px;
|
||||
|
||||
& > flowboxchild {
|
||||
& > button {
|
||||
padding: 10px;
|
||||
border-radius: 24px;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
color.change($color: colors.$bg-primary, $alpha: 0.9),
|
||||
color.change($color: colors.$bg-secondary, $alpha: 0.7)
|
||||
);
|
||||
border: 1px solid transparent;
|
||||
|
||||
& image {
|
||||
-gtk-icon-size: 64px;
|
||||
}
|
||||
|
||||
& label {
|
||||
& label.app-name {
|
||||
margin-top: 24px;
|
||||
font-size: 16px;
|
||||
text-shadow: 1px 1px 1px rgba(colors.$bg-primary, .2);
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
&:focus > button,
|
||||
&:selected > button,
|
||||
& > button:hover {
|
||||
background-color: rgba($color: colors.$bg-secondary, $alpha: .5);
|
||||
&:selected > button {
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
color.change($color: colors.$bg-secondary, $alpha: 0.95),
|
||||
color.change($color: colors.$bg-tertiary, $alpha: 0.8)
|
||||
);
|
||||
border-color: colors.$bg-tertiary;
|
||||
box-shadow: 0 0 0 1px colors.$bg-tertiary;
|
||||
|
||||
& label.app-name {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
@use "sass:color";
|
||||
@use "./colors";
|
||||
|
||||
.runner .popup-window-container {
|
||||
@@ -29,16 +30,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
& scrolledwindow {
|
||||
margin: 6px;
|
||||
}
|
||||
|
||||
& list {
|
||||
padding: 0 12px;
|
||||
& .result {
|
||||
padding: 10px;
|
||||
background: colors.$bg-primary;
|
||||
margin: 2px 0;
|
||||
border-radius: 14px;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
color.change($color: colors.$bg-primary, $alpha: 0.9),
|
||||
color.change($color: colors.$bg-secondary, $alpha: 0.7)
|
||||
);
|
||||
margin: 6px 0;
|
||||
border-radius: 18px;
|
||||
border: 1px solid transparent;
|
||||
|
||||
& image {
|
||||
-gtk-icon-size: 28px;
|
||||
@@ -47,7 +50,7 @@
|
||||
|
||||
& .title {
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
& .description {
|
||||
@@ -57,17 +60,19 @@
|
||||
}
|
||||
|
||||
& > *:selected .result,
|
||||
& > *:active .result,
|
||||
& > *:hover .result {
|
||||
background: colors.$bg-secondary;
|
||||
}
|
||||
& > *:active .result {
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
color.change($color: colors.$bg-secondary, $alpha: 0.95),
|
||||
color.change($color: colors.$bg-tertiary, $alpha: 0.8)
|
||||
);
|
||||
|
||||
& > *:first-child {
|
||||
margin-top: 12px;
|
||||
}
|
||||
border-color: colors.$bg-tertiary;
|
||||
box-shadow: 0 0 0 1px colors.$bg-tertiary;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
& .title {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -67,7 +67,7 @@ export class Shell extends Adw.Application {
|
||||
super({
|
||||
applicationId: "io.github.retrozinndev.colorshell",
|
||||
flags: Gio.ApplicationFlags.HANDLES_COMMAND_LINE,
|
||||
version: COLORSHELL_VERSION ?? "0.0.0-unknown",
|
||||
version: (typeof COLORSHELL_VERSION !== "undefined" ? COLORSHELL_VERSION : "0.0.0-unknown"),
|
||||
});
|
||||
|
||||
setConsoleLogDomain("Colorshell");
|
||||
|
||||
+3
-3
@@ -44,8 +44,8 @@ export namespace Cli {
|
||||
name: "version",
|
||||
alias: "v",
|
||||
help: "print the current colorshell version",
|
||||
onCalled: () => `colorshell by retrozinndev, version ${COLORSHELL_VERSION
|
||||
}${DEVEL ? "(devel)" : ""}`
|
||||
onCalled: () => `colorshell by retrozinndev, version ${(typeof COLORSHELL_VERSION !== "undefined" ? COLORSHELL_VERSION : "unknown")
|
||||
}${(typeof DEVEL !== "undefined" && DEVEL) ? "(devel)" : ""}`
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -110,7 +110,7 @@ export namespace Cli {
|
||||
|
||||
initialized = true;
|
||||
rootScope = scope;
|
||||
DEVEL && modules.push(devel);
|
||||
(typeof DEVEL !== "undefined" && DEVEL) && modules.push(devel);
|
||||
|
||||
scope.run(() => {
|
||||
if(communicationMethod instanceof Gio.SocketService) {
|
||||
|
||||
Vendored
+3
-3
@@ -1,7 +1,7 @@
|
||||
declare const SRC: string
|
||||
declare const DEVEL: boolean;
|
||||
declare const GRESOURCES_FILE: string;
|
||||
declare const COLORSHELL_VERSION: string;
|
||||
declare const DEVEL: boolean | undefined;
|
||||
declare const GRESOURCES_FILE: string | undefined;
|
||||
declare const COLORSHELL_VERSION: string | undefined;
|
||||
|
||||
declare module "inline:*" {
|
||||
const content: string
|
||||
|
||||
@@ -37,7 +37,7 @@ Audio Controls:
|
||||
|
||||
Media Controls:
|
||||
media: manage colorshell's active player, see "media help".
|
||||
${DEVEL ? `
|
||||
${(typeof DEVEL !== "undefined" && DEVEL) ? `
|
||||
Development Tools:
|
||||
dev: tools to help debugging colorshell
|
||||
` : ""}
|
||||
@@ -60,8 +60,8 @@ export function handleArguments(cmd: RemoteCaller, args: Array<string>): number
|
||||
|
||||
case "version":
|
||||
case "v":
|
||||
cmd.print_literal(`colorshell by retrozinndev, version ${COLORSHELL_VERSION
|
||||
}${DEVEL ? " (devel)" : ""}\nhttps://github.com/retrozinndev/colorshell`);
|
||||
cmd.print_literal(`colorshell by retrozinndev, version ${(typeof COLORSHELL_VERSION !== "undefined" ? COLORSHELL_VERSION : "unknown")
|
||||
}${(typeof DEVEL !== "undefined" && DEVEL) ? " (devel)" : ""}\nhttps://github.com/retrozinndev/colorshell`);
|
||||
return 0;
|
||||
|
||||
case "dev":
|
||||
|
||||
@@ -120,7 +120,13 @@ export class NightLight extends GObject.Object {
|
||||
}
|
||||
|
||||
public applyIdentity(): void {
|
||||
try {
|
||||
this.dispatch("identity");
|
||||
} catch (e) {
|
||||
// hyprsunset not available, skip
|
||||
console.warn("Night Light: hyprsunset not available, cannot apply identity");
|
||||
return;
|
||||
}
|
||||
|
||||
if(!this.#identity) {
|
||||
this.#identity = true;
|
||||
@@ -133,7 +139,13 @@ export class NightLight extends GObject.Object {
|
||||
private dispatch(call: "identity"): string;
|
||||
|
||||
private dispatch(call: "temperature"|"gamma"|"identity", val?: number): string {
|
||||
try {
|
||||
return exec(`hyprctl hyprsunset ${call}${val != null ? ` ${val}` : ""}`);
|
||||
} catch (e) {
|
||||
// hyprsunset not available, return empty string
|
||||
console.warn(`Night Light: hyprsunset not available, skipping ${call} command`);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private async dispatchAsync(call: "temperature", val: number): Promise<string>;
|
||||
|
||||
+106
-32
@@ -4,7 +4,7 @@ import GObject, { register, getter, gtype, property, setter } from "ags/gobject"
|
||||
|
||||
import Gio from "gi://Gio?version=2.0";
|
||||
import GLib from "gi://GLib?version=2.0";
|
||||
import { createSubscription, encoder } from "./utils";
|
||||
import { createSubscription } from "./utils";
|
||||
import { Notifications } from "./notifications";
|
||||
import { generalConfig } from "../config";
|
||||
import { createRoot, getScope, Scope } from "ags";
|
||||
@@ -161,37 +161,45 @@ class Wallpaper extends GObject.Object {
|
||||
return this.instance;
|
||||
}
|
||||
|
||||
private writeChanges(): void {
|
||||
this.#hyprpaperFile.replace_async(null, false,
|
||||
Gio.FileCreateFlags.REPLACE_DESTINATION,
|
||||
GLib.PRIORITY_DEFAULT, null, (_, result) => {
|
||||
const res = this.#hyprpaperFile.replace_finish(result);
|
||||
if(!res) {
|
||||
console.error(`Wallpaper: an error occurred when trying to replace the hyprpaper file`);
|
||||
private writeChanges(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const content = `# This file was automatically generated by colorshell
|
||||
|
||||
preload = ${this.#wallpaper}
|
||||
splash = ${this.#splash}
|
||||
wallpaper = , ${this.positioning === "cover" ? "" : `${this.positioning}:`}${this.#wallpaper}
|
||||
`;
|
||||
|
||||
// Use synchronous file writing for reliability
|
||||
const filePath = this.#hyprpaperFile.get_path();
|
||||
if(!filePath) {
|
||||
reject(new Error("Could not get hyprpaper file path"));
|
||||
return;
|
||||
}
|
||||
|
||||
// success
|
||||
res.write_bytes_async(encoder.encode(`\
|
||||
# This file was automatically generated by colorshell
|
||||
|
||||
preload = ${this.#wallpaper}
|
||||
splash = ${this.#splash}
|
||||
wallpaper = , ${this.positioning === "cover" ? "" : `${this.positioning}:`}${
|
||||
this.#wallpaper}`.split('\n').map(str => str.trimStart()).join('\n')),
|
||||
GLib.PRIORITY_DEFAULT, null, (_, asyncRes) => {
|
||||
if(_!.write_finish(asyncRes)) res.flush(null);
|
||||
res.close(null);
|
||||
// Ensure directory exists
|
||||
const parentDir = this.#hyprpaperFile.get_parent();
|
||||
if(parentDir && !parentDir.query_exists(null)) {
|
||||
parentDir.make_directory_with_parents(null);
|
||||
}
|
||||
);
|
||||
|
||||
return;
|
||||
// Write file synchronously using GLib
|
||||
const success = GLib.file_set_contents(filePath, content);
|
||||
if(success) {
|
||||
resolve();
|
||||
} else {
|
||||
reject(new Error("Failed to write hyprpaper config file"));
|
||||
}
|
||||
);
|
||||
} catch (e: any) {
|
||||
reject(new Error(`Failed to write config file: ${e.message}`));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public getData(): WalData {
|
||||
const content = readFile(`${GLib.getenv("XDG_CACHE_HOME")}/wal/colors.json`);
|
||||
const cacheHome = GLib.getenv("XDG_CACHE_HOME") || `${GLib.get_home_dir()}/.cache`;
|
||||
const content = readFile(`${cacheHome}/wal/colors.json`);
|
||||
return JSON.parse(content) as WalData;
|
||||
}
|
||||
|
||||
@@ -210,10 +218,41 @@ class Wallpaper extends GObject.Object {
|
||||
}
|
||||
|
||||
public reloadColors(): void {
|
||||
execAsync(`wal -t --cols16 ${this.colorMode} -i "${this.#wallpaper}"`).then(() => {
|
||||
const cacheHome = GLib.getenv("XDG_CACHE_HOME") || `${GLib.get_home_dir()}/.cache`;
|
||||
const colorsKittyPath = `${cacheHome}/wal/colors-kitty.conf`;
|
||||
const kittyConfigPath = `${GLib.get_user_config_dir()}/kitty/kitty.conf`;
|
||||
|
||||
const runWal = (extraArgs: string = "") =>
|
||||
execAsync(`wal -t --cols16 ${this.colorMode} ${extraArgs} -i "${this.#wallpaper}"`);
|
||||
|
||||
// First try default backend; if it fails (e.g. some images on aarch64),
|
||||
// fall back to a more forgiving backend like "colorz".
|
||||
runWal().catch((e: Error) => {
|
||||
console.error(`Wallpaper: Couldn't update shell colors with default backend. Stderr: ${e.message}`);
|
||||
console.log("Wallpaper: Falling back to pywal backend 'colorz'");
|
||||
return runWal("--backend colorz");
|
||||
}).then(() => {
|
||||
console.log("Wallpaper: reloaded shell colors");
|
||||
|
||||
// First, try to set colors on all existing kitty instances
|
||||
execAsync(`kitty @ set-colors --all ${colorsKittyPath}`).then(() => {
|
||||
console.log("Wallpaper: reloaded colors in existing kitty instances");
|
||||
}).catch((e: Error) => {
|
||||
console.error(`Wallpaper: Couldn't update shell colors. Stderr: ${e.message}`);
|
||||
// It's okay if this fails (e.g., no kitty instances running)
|
||||
console.log(`Wallpaper: Couldn't reload kitty colors in existing instances: ${e.message}`);
|
||||
});
|
||||
|
||||
// Then, update the configured colors for future kitty instances
|
||||
// This is critical - it tells kitty to use these colors for new windows
|
||||
execAsync(`kitty @ set-colors --configured ${colorsKittyPath}`).then(() => {
|
||||
console.log("Wallpaper: configured colors for future kitty instances");
|
||||
}).catch((e: Error) => {
|
||||
// If no kitty instances are running, we can't set configured colors
|
||||
// In this case, new instances should still pick up colors from the include directive
|
||||
console.log(`Wallpaper: Couldn't set configured colors (new instances will use include directive): ${e.message}`);
|
||||
});
|
||||
}).catch((e: Error) => {
|
||||
console.error(`Wallpaper: Couldn't update shell colors even with fallback backend. Stderr: ${e.message}`);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -221,10 +260,39 @@ class Wallpaper extends GObject.Object {
|
||||
if(this.wallpaper.trim() === "")
|
||||
return;
|
||||
|
||||
await execAsync(`hyprctl hyprpaper reload \", ${
|
||||
this.positioning === "cover" ? "" : `${this.positioning}:`
|
||||
}${this.wallpaper}\"`);
|
||||
write && this.writeChanges();
|
||||
const wallpaperPath = this.#wallpaper.trim();
|
||||
|
||||
try {
|
||||
// Write config file first if needed
|
||||
if(write) {
|
||||
await this.writeChanges();
|
||||
}
|
||||
|
||||
// Unload all current wallpapers
|
||||
await execAsync(`hyprctl hyprpaper unload all`).catch(() => {
|
||||
// Ignore errors - this is usually fine
|
||||
});
|
||||
|
||||
// Preload the new wallpaper
|
||||
await execAsync(`hyprctl hyprpaper preload "${wallpaperPath}"`);
|
||||
|
||||
// Set wallpaper on all monitors
|
||||
await execAsync(`hyprctl hyprpaper wallpaper ", ${wallpaperPath}"`);
|
||||
|
||||
// Note: We don't need to reload or restart hyprpaper here
|
||||
// The preload and wallpaper commands should apply the change immediately
|
||||
// The config file is written for persistence across hyprpaper restarts
|
||||
|
||||
} catch (e: any) {
|
||||
console.error(`Wallpaper: Error reloading wallpaper: ${e.message}`);
|
||||
console.error(`Wallpaper: Stack trace: ${e.stack}`);
|
||||
Notifications.getDefault().sendNotification({
|
||||
appName: "colorshell",
|
||||
summary: "Failed to set wallpaper",
|
||||
body: `Error: ${e.message}`
|
||||
});
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public setWallpaper(path: string|Gio.File, write: boolean = true): void {
|
||||
@@ -244,10 +312,16 @@ class Wallpaper extends GObject.Object {
|
||||
|
||||
public async pickWallpaper(): Promise<string|undefined> {
|
||||
return (await execAsync(`zenity --file-selection`).then(wall => {
|
||||
if(!wall.trim()) return undefined;
|
||||
const trimmedWall = wall.trim();
|
||||
if(!trimmedWall) return undefined;
|
||||
|
||||
this.setWallpaper(wall);
|
||||
return wall;
|
||||
// Ensure path is absolute
|
||||
const absolutePath = GLib.path_is_absolute(trimmedWall)
|
||||
? trimmedWall
|
||||
: GLib.build_filenamev([GLib.get_current_dir(), trimmedWall]);
|
||||
|
||||
this.setWallpaper(absolutePath);
|
||||
return absolutePath;
|
||||
}).catch((e: Error) => {
|
||||
console.error(`Wallpaper: Couldn't pick wallpaper, is \`zenity\` installed? Stderr: ${e.message}`);
|
||||
return undefined;
|
||||
|
||||
+36
-1
@@ -276,11 +276,14 @@ export function openRunner(props: RunnerProps, placeholders?: Array<Result>): As
|
||||
props.height ??= 420;
|
||||
|
||||
let clickTimeout: GLib.Source|undefined;
|
||||
let lastMouseX: number|null = null;
|
||||
let lastMouseY: number|null = null;
|
||||
let lastKeyboardNavTime: number = 0;
|
||||
|
||||
if(!instance)
|
||||
instance = Windows.getDefault().createWindowForFocusedMonitor((mon, root) =>
|
||||
<PopupWindow namespace={"runner"} monitor={mon} widthRequest={props.width}
|
||||
heightRequest={props.height} exclusivity={Astal.Exclusivity.IGNORE} halign={Gtk.Align.CENTER}
|
||||
exclusivity={Astal.Exclusivity.IGNORE} halign={Gtk.Align.CENTER}
|
||||
marginTop={(AstalHyprland.get_default().get_monitor(mon)?.height / 2) - (props.height! / 2)}
|
||||
valign={Gtk.Align.START} hexpand orientation={Gtk.Orientation.VERTICAL}
|
||||
$={() => {
|
||||
@@ -302,12 +305,14 @@ export function openRunner(props: RunnerProps, placeholders?: Array<Result>): As
|
||||
case Gdk.KEY_Up:
|
||||
selectPreviousItem(listbox);
|
||||
gtkEntry?.grab_focus();
|
||||
lastKeyboardNavTime = Date.now();
|
||||
return;
|
||||
|
||||
case Gdk.KEY_Right:
|
||||
case Gdk.KEY_Down:
|
||||
selectNextItem(listbox);
|
||||
gtkEntry?.grab_focus();
|
||||
lastKeyboardNavTime = Date.now();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -374,6 +379,36 @@ export function openRunner(props: RunnerProps, placeholders?: Array<Result>): As
|
||||
child.closeOnClick &&
|
||||
Runner.close();
|
||||
}
|
||||
}} $={(self) => {
|
||||
// Hover-based selection: only triggers when the mouse actually moves
|
||||
const motion = Gtk.EventControllerMotion.new();
|
||||
self.add_controller(motion);
|
||||
|
||||
motion.connect("motion", (_controller, x, y) => {
|
||||
const now = Date.now();
|
||||
|
||||
// While user is actively navigating with keyboard,
|
||||
// don't let hover steal selection
|
||||
if(lastKeyboardNavTime && now - lastKeyboardNavTime < 200)
|
||||
return;
|
||||
|
||||
// First motion: just record pointer position, don't change selection
|
||||
if(lastMouseX === null && lastMouseY === null) {
|
||||
lastMouseX = x;
|
||||
lastMouseY = y;
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore synthetic events that don't actually move the pointer
|
||||
if(x === lastMouseX && y === lastMouseY)
|
||||
return;
|
||||
|
||||
lastMouseX = x;
|
||||
lastMouseY = y;
|
||||
|
||||
const row = self.get_row_at_y(y);
|
||||
row && self.select_row(row as Gtk.ListBoxRow);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Gtk.ScrolledWindow>
|
||||
|
||||
@@ -13,9 +13,15 @@ import { generalConfig } from "../config";
|
||||
|
||||
|
||||
function getNotificationImage(notif: AstalNotifd.Notification|HistoryNotification): (string|undefined) {
|
||||
const img = notif.image || notif.appIcon;
|
||||
// AstalNotifd.Notification uses snake_case properties (app_icon, image),
|
||||
// while our HistoryNotification uses camelCase (appIcon, image).
|
||||
const anyNotif = notif as any;
|
||||
const img: string | undefined =
|
||||
anyNotif.image ||
|
||||
anyNotif.app_icon ||
|
||||
anyNotif.appIcon;
|
||||
|
||||
if(!img || !img.includes('/'))
|
||||
if (typeof img !== "string" || !img.includes("/"))
|
||||
return undefined;
|
||||
|
||||
return pathToURI(img);
|
||||
|
||||
@@ -26,7 +26,8 @@ export const AppsWindow = (mon: number) => {
|
||||
return <PopupWindow namespace="apps-window" layer={Astal.Layer.OVERLAY}
|
||||
exclusivity={Astal.Exclusivity.IGNORE} monitor={mon} marginTop={64}
|
||||
class={"apps-window"} orientation={Gtk.Orientation.VERTICAL}
|
||||
cssBackgroundWindow="background: rgba(0, 0, 0, .2);"
|
||||
cssBackgroundWindow="background: rgba(0, 0, 0, .2);" halign={Gtk.Align.FILL}
|
||||
valign={Gtk.Align.FILL} hexpand vexpand
|
||||
actionKeyPressed={(self, key) => {
|
||||
const entry = getPopupWindowContainer(self).get_first_child()!
|
||||
.get_first_child()!.get_first_child()! as Gtk.SearchEntry;
|
||||
@@ -36,8 +37,8 @@ export const AppsWindow = (mon: number) => {
|
||||
|
||||
entry.grab_focus();
|
||||
}}>
|
||||
<Gtk.Box hexpand={false} halign={Gtk.Align.CENTER}>
|
||||
<Gtk.SearchEntry hexpand={false} onSearchChanged={(self) => {
|
||||
<Gtk.Box hexpand halign={Gtk.Align.CENTER}>
|
||||
<Gtk.SearchEntry hexpand onSearchChanged={(self) => {
|
||||
setResults(getAstalApps().fuzzy_query(self.text.trim()));
|
||||
}} onStopSearch={(self) => (self.get_root() as Astal.Window)?.close()} />
|
||||
</Gtk.Box>
|
||||
@@ -46,9 +47,11 @@ export const AppsWindow = (mon: number) => {
|
||||
hscrollbarPolicy={Gtk.PolicyType.NEVER} overlayScrolling
|
||||
propagateNaturalHeight={false} hexpand vexpand>
|
||||
|
||||
<Gtk.Box hexpand={false} vexpand={false}>
|
||||
<Gtk.FlowBox rowSpacing={60} columnSpacing={60} activateOnSingleClick
|
||||
minChildrenPerLine={1} homogeneous onChildActivated={(_, child) =>
|
||||
<Gtk.Box hexpand vexpand>
|
||||
<Gtk.FlowBox orientation={Gtk.Orientation.HORIZONTAL}
|
||||
rowSpacing={32} columnSpacing={32} activateOnSingleClick
|
||||
minChildrenPerLine={4} maxChildrenPerLine={10} hexpand homogeneous
|
||||
onChildActivated={(_, child) =>
|
||||
child.get_child()!.activate() // pass activation to button
|
||||
}>
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ export const Bar = (mon: number) => {
|
||||
halign={Gtk.Align.START} spacing={widgetSpacing}
|
||||
$type="start">
|
||||
|
||||
<Apps />
|
||||
<Apps monitor={mon} />
|
||||
<Workspaces />
|
||||
<FocusedClient />
|
||||
</Gtk.Box>
|
||||
@@ -29,14 +29,14 @@ export const Bar = (mon: number) => {
|
||||
spacing={widgetSpacing} halign={Gtk.Align.CENTER}
|
||||
$type="center">
|
||||
|
||||
<Clock />
|
||||
<Media />
|
||||
<Clock monitor={mon} />
|
||||
<Media monitor={mon} />
|
||||
</Gtk.Box>
|
||||
<Gtk.Box class={"widgets-right"} homogeneous={false}
|
||||
spacing={widgetSpacing} halign={Gtk.Align.END}
|
||||
$type="end">
|
||||
<Tray />
|
||||
<Status />
|
||||
<Status monitor={mon} />
|
||||
</Gtk.Box>
|
||||
</Gtk.CenterBox>
|
||||
</Gtk.Box>
|
||||
|
||||
@@ -4,10 +4,10 @@ import { createBinding } from "ags";
|
||||
import { tr } from "../../../i18n/intl";
|
||||
|
||||
|
||||
export const Apps = () =>
|
||||
export const Apps = ({ monitor }: { monitor: number }) =>
|
||||
<Gtk.Button class={createBinding(Windows.getDefault(), "openWindows").as((openWindows) =>
|
||||
`apps ${Object.hasOwn(openWindows, "apps-window") ? "open" : ""}`
|
||||
)} iconName={"applications-other-symbolic"} halign={Gtk.Align.CENTER}
|
||||
hexpand tooltipText={tr("apps")} onClicked={() =>
|
||||
Windows.getDefault().open("apps-window")}
|
||||
Windows.getDefault().open("apps-window", false, monitor)}
|
||||
/>;
|
||||
|
||||
@@ -5,10 +5,10 @@ import { time } from "../../../modules/utils";
|
||||
import { generalConfig } from "../../../config";
|
||||
|
||||
|
||||
export const Clock = () =>
|
||||
export const Clock = ({ monitor }: { monitor: number }) =>
|
||||
<Gtk.Button class={createBinding(Windows.getDefault(), "openWindows").as((wins) =>
|
||||
`clock ${wins.includes("center-window") ? "open" : ""}`)}
|
||||
onClicked={() => Windows.getDefault().toggle("center-window")}
|
||||
onClicked={() => Windows.getDefault().toggle("center-window", monitor)}
|
||||
label={time((dt) => dt.format(
|
||||
generalConfig.getProperty("clock.date_format", "string"))
|
||||
?? "An error occurred"
|
||||
|
||||
@@ -11,7 +11,7 @@ import AstalMpris from "gi://AstalMpris";
|
||||
import Pango from "gi://Pango?version=1.0";
|
||||
|
||||
|
||||
export const Media = () =>
|
||||
export const Media = ({ monitor }: { monitor: number }) =>
|
||||
<Gtk.Box class={"media"} visible={createBinding(Player.getDefault(), "player").as(p => p.available)}>
|
||||
<Gtk.EventControllerScroll $={(self) => {
|
||||
self.set_flags(Gtk.EventControllerScrollFlags.VERTICAL)
|
||||
@@ -42,7 +42,7 @@ export const Media = () =>
|
||||
return true;
|
||||
}}
|
||||
/>
|
||||
<Gtk.GestureClick onReleased={() => Windows.getDefault().toggle("center-window")} />
|
||||
<Gtk.GestureClick onReleased={() => Windows.getDefault().toggle("center-window", monitor)} />
|
||||
<Gtk.EventControllerMotion onEnter={(self) => {
|
||||
const revealer = self.get_widget()!.get_last_child() as Gtk.Revealer;
|
||||
revealer.set_reveal_child(true);
|
||||
|
||||
@@ -14,12 +14,12 @@ import AstalNetwork from "gi://AstalNetwork";
|
||||
import AstalWp from "gi://AstalWp";
|
||||
|
||||
|
||||
export const Status = () =>
|
||||
export const Status = ({ monitor }: { monitor: number }) =>
|
||||
<Gtk.Button class={createBinding(Windows.getDefault(), "openWindows").as((openWins) =>
|
||||
openWins.includes("control-center") ?
|
||||
"open status"
|
||||
: "status"
|
||||
)} onClicked={() => Windows.getDefault().toggle("control-center")}>
|
||||
)} onClicked={() => Windows.getDefault().toggle("control-center", monitor)}>
|
||||
|
||||
<Gtk.Box>
|
||||
<Gtk.Box class={"volume-indicators"} spacing={5}>
|
||||
|
||||
+53
-10
@@ -17,8 +17,10 @@ import AstalHyprland from "gi://AstalHyprland";
|
||||
export type WindowInstance = { instance?: Astal.Window, connections: Array<number> };
|
||||
export type WindowData = {
|
||||
create: () => (Astal.Window | Array<Astal.Window>);
|
||||
createForMonitor?: (mon: number) => Astal.Window;
|
||||
instance?: WindowInstance | Array<WindowInstance>;
|
||||
status?: "open" | "closed";
|
||||
preferredMonitor?: number | null;
|
||||
};
|
||||
|
||||
|
||||
@@ -41,12 +43,12 @@ export class Windows extends GObject.Object {
|
||||
#scope!: ReturnType<typeof getScope>;
|
||||
#windows: Record<string, WindowData> = {
|
||||
"bar": { create: this.createWindowForMonitors(Bar) },
|
||||
"osd": { create: this.createWindowForFocusedMonitor(OSD), },
|
||||
"control-center": { create: this.createWindowForFocusedMonitor(ControlCenter), },
|
||||
"center-window": { create: this.createWindowForFocusedMonitor(CenterWindow), },
|
||||
"logout-menu": { create: this.createWindowForFocusedMonitor(LogoutMenu), },
|
||||
"floating-notifications": { create: this.createWindowForFocusedMonitor(FloatingNotifications), },
|
||||
"apps-window": { create: this.createWindowForFocusedMonitor(AppsWindow) }
|
||||
"osd": { create: this.createWindowForFocusedMonitor(OSD), createForMonitor: this.createWindowForMonitor(OSD) },
|
||||
"control-center": { create: this.createWindowForFocusedMonitor(ControlCenter), createForMonitor: this.createWindowForMonitor(ControlCenter) },
|
||||
"center-window": { create: this.createWindowForFocusedMonitor(CenterWindow), createForMonitor: this.createWindowForMonitor(CenterWindow) },
|
||||
"logout-menu": { create: this.createWindowForFocusedMonitor(LogoutMenu), createForMonitor: this.createWindowForMonitor(LogoutMenu) },
|
||||
"floating-notifications": { create: this.createWindowForFocusedMonitor(FloatingNotifications), createForMonitor: this.createWindowForMonitor(FloatingNotifications) },
|
||||
"apps-window": { create: this.createWindowForFocusedMonitor(AppsWindow), createForMonitor: this.createWindowForMonitor(AppsWindow) }
|
||||
};
|
||||
|
||||
@signal(String) windowOpen(_name: string) {}
|
||||
@@ -244,6 +246,29 @@ export class Windows extends GObject.Object {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a window instance for a specific monitor
|
||||
* @param create generates the window. use provided monitor number in the returned window
|
||||
* @returns a function that when called with a monitor ID, returns a Astal.Window instance
|
||||
*/
|
||||
public createWindowForMonitor(create: (mon: number, scope: ReturnType<typeof getScope>) => GObject.Object|Astal.Window): ((mon: number) => Astal.Window) {
|
||||
return (mon: number) => {
|
||||
return createRoot((dispose) => {
|
||||
const scope = getScope();
|
||||
const instance = create(mon, scope) as Astal.Window;
|
||||
const connection = instance.connect("close-request", () => dispose());
|
||||
|
||||
this.#scope.onMount(dispose)
|
||||
scope.onCleanup(() =>
|
||||
GObject.signal_handler_is_connected(instance, connection) &&
|
||||
instance.disconnect(connection)
|
||||
);
|
||||
|
||||
return instance;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public addWindow(name: string, create: () => Astal.Window|Array<Astal.Window>): void {
|
||||
this.#windows[name] = { create };
|
||||
}
|
||||
@@ -264,7 +289,7 @@ export class Windows extends GObject.Object {
|
||||
return this.openWindows.includes(name);
|
||||
}
|
||||
|
||||
public open(name: string, ignoreOpenStatus: boolean = false): void {
|
||||
public open(name: string, ignoreOpenStatus: boolean = false, monitor?: number | null): void {
|
||||
if(this.isOpen(name) && !ignoreOpenStatus) return;
|
||||
|
||||
const window = this.#windows[name];
|
||||
@@ -273,8 +298,22 @@ export class Windows extends GObject.Object {
|
||||
return;
|
||||
}
|
||||
|
||||
// Store preferred monitor if provided
|
||||
if(monitor !== undefined) {
|
||||
window.preferredMonitor = monitor;
|
||||
}
|
||||
|
||||
this.#windows[name].status = "open";
|
||||
const windowInstance = window.create();
|
||||
|
||||
// Use createForMonitor if monitor is specified and available, otherwise use default create
|
||||
let windowInstance: Astal.Window | Array<Astal.Window>;
|
||||
if(monitor !== null && monitor !== undefined && window.createForMonitor) {
|
||||
windowInstance = window.createForMonitor(monitor);
|
||||
} else if(window.preferredMonitor !== null && window.preferredMonitor !== undefined && window.createForMonitor) {
|
||||
windowInstance = window.createForMonitor(window.preferredMonitor);
|
||||
} else {
|
||||
windowInstance = window.create();
|
||||
}
|
||||
|
||||
if(Array.isArray(windowInstance)) {
|
||||
window.instance = windowInstance.map(wi => {
|
||||
@@ -309,8 +348,12 @@ export class Windows extends GObject.Object {
|
||||
this.notify("open-windows");
|
||||
}
|
||||
|
||||
public toggle(name: string): void {
|
||||
this.isOpen(name) ? this.close(name) : this.open(name);
|
||||
public toggle(name: string, monitor?: number | null): void {
|
||||
if(this.isOpen(name)) {
|
||||
this.close(name);
|
||||
} else {
|
||||
this.open(name, false, monitor);
|
||||
}
|
||||
}
|
||||
|
||||
public closeAll(): void {
|
||||
|
||||
Reference in New Issue
Block a user