Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update to GNOME Shell 46.0.2 types #93

Merged
merged 4 commits into from
Sep 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
},
"devDependencies": {
"@eslint/js": "^9.9.1",
"@girs/gjs": "^3.3.0",
"@girs/gnome-shell": "^46.0.0-beta6",
"@girs/soup-3.0": "^3.4.4-3.3.0",
"@girs/gjs": "4.0.0-beta.14",
"@girs/gnome-shell": "46.0.2",
"@girs/soup-3.0": "^3.4.4-4.0.0-beta.14",
"@tsconfig/strictest": "^2.0.5",
"eslint": "^9.9.1",
"eslint-config-prettier": "^9.1.0",
Expand Down
1,176 changes: 598 additions & 578 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

3 changes: 0 additions & 3 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,9 +178,6 @@ const initializeExtension = (

// Wire up the current source
const currentSource = settings.get_string("selected-source");
if (currentSource === null) {
throw new Error("Current source 'null'?");
}
const sourceSelector = destroyer.add(SourceSelector.forKey(currentSource));
indicator.updateSelectedSource(sourceSelector.selectedSource.metadata);
const sourceSettings = SourceSettings.fromBaseSettings(extension, settings);
Expand Down
98 changes: 98 additions & 0 deletions src/lib/fixes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Copyright Sebastian Wiesner <[email protected]>
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0.If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
// Alternatively, the contents of this file may be used under the terms
// of the GNU General Public License Version 2 or later, as described below:
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// Only use type imports here, as this module is used in prefs as well as extension code!
import type GLib from "gi://GLib";
import type Gio from "gi://Gio";
import type Soup from "gi://Soup";
import type Gtk from "gi://Gtk";

/**
* This module provides workarounds and fixes for various incomplete type
* declarations in Gnome shell types and its dependencies.
*/

/**
* @see https://github.com/gjsify/ts-for-gir/issues/196
*/
export interface GLibErrorWithStack extends GLib.Error {
readonly stack: string;
}

/**
* @see https://github.com/gjsify/ts-for-gir/issues/171#issuecomment-2117301067
*/
export interface PromisifiedFileOutputStream extends Gio.FileOutputStream {
// eslint-disable-next-line @typescript-eslint/no-misused-promises
splice_async(
source: Gio.InputStream,
flags: Gio.OutputStreamSpliceFlags,
io_priority: number,
cancellable?: Gio.Cancellable | null,
): Promise<ReturnType<Gio.OutputStream["splice_finish"]>>;
}

/**
* @see https://github.com/gjsify/ts-for-gir/issues/171#issuecomment-2117301067
*/
export interface PromisifiedGioFile extends Gio.File {
// eslint-disable-next-line @typescript-eslint/no-misused-promises
create_async(
flags: Gio.FileCreateFlags,
io_priority: number,
cancellable?: Gio.Cancellable | null,
): Promise<ReturnType<Gio.File["create_finish"]>>;

// eslint-disable-next-line @typescript-eslint/no-misused-promises
delete_async(
io_priority: number,
cancellable?: Gio.Cancellable | null,
): Promise<ReturnType<Gio.File["delete_finish"]>>;
}

/**
* @see https://github.com/gjsify/ts-for-gir/issues/171#issuecomment-2117301067
*/
export interface PromisifiedSoupSession extends Soup.Session {
// eslint-disable-next-line @typescript-eslint/no-misused-promises
send_async(
msg: Soup.Message,
io_priority: number,
cancellable?: Gio.Cancellable | null,
): Promise<ReturnType<Soup.Session["send_finish"]>>;

// eslint-disable-next-line @typescript-eslint/no-misused-promises
send_and_read_async(
msg: Soup.Message,
io_priority: number,
cancellable?: Gio.Cancellable | null,
): Promise<ReturnType<Soup.Session["send_and_read_finish"]>>;
}

/**
* @see https://github.com/gjsify/ts-for-gir/issues/200
* @see https://github.com/gjsify/ts-for-gir/issues/140
*/
export interface PromisifiedGtkFileDialog extends Gtk.FileDialog {
// eslint-disable-next-line @typescript-eslint/no-misused-promises
select_folder(
parent?: Gtk.Window | null,
cancellable?: Gio.Cancellable | null,
): Promise<ReturnType<Gtk.FileDialog["select_folder_finish"]>>;
}
15 changes: 10 additions & 5 deletions src/lib/network/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ import Soup from "gi://Soup";
import { IOError } from "../common/gio.js";

import type { ExtensionMetadata } from "resource:///org/gnome/shell/extensions/extension.js";
import {
PromisifiedFileOutputStream,
PromisifiedGioFile,
PromisifiedSoupSession,
} from "../fixes.js";

export const createSession = (
extensionMetadata: ExtensionMetadata,
Expand Down Expand Up @@ -83,7 +88,7 @@ export const getString = async (
cancellable: Gio.Cancellable,
): Promise<string> => {
const message = Soup.Message.new("GET", url);
const response = await session
const response = await (session as PromisifiedSoupSession)
.send_and_read_async(message, 0, cancellable)
.catch((cause: unknown) => {
throw new HttpRequestError(url, `Failed to get data from ${url}`, {
Expand Down Expand Up @@ -146,7 +151,7 @@ const deletePartialDownloadIgnoreError = async (
cancellable: Gio.Cancellable | null,
): Promise<void> => {
try {
await file.delete_async(0, cancellable);
await (file as PromisifiedGioFile).delete_async(0, cancellable);
} catch (error) {
console.warn(
`Failed to delete result of partial download at ${file.get_path() ?? ""}`,
Expand All @@ -173,7 +178,7 @@ export const downloadToFile = async (
return;
}
const message = Soup.Message.new("GET", url);
const source = await session
const source = await (session as PromisifiedSoupSession)
.send_async(message, 0, cancellable)
.catch((cause: unknown) => {
throw new HttpRequestError(url, `Failed to make GET request to ${url}`, {
Expand Down Expand Up @@ -215,15 +220,15 @@ export const downloadToFile = async (
}
// Now open the target file for reading, and safely delete it in case of error.
try {
const sink = await target
const sink = await (target as PromisifiedGioFile)
.create_async(Gio.FileCreateFlags.NONE, 0, null)
.catch((cause: unknown) => {
throw new IOError(
`Failed to open target file at ${target.get_path() ?? ""} to download from ${url}`,
{ cause },
);
});
await sink
await (sink as PromisifiedFileOutputStream)
.splice_async(
source,
Gio.OutputStreamSpliceFlags.CLOSE_SOURCE |
Expand Down
6 changes: 1 addition & 5 deletions src/lib/services/desktop-background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,6 @@ export class DesktopBackgroundService {
* @param image The image file to use as new background
*/
setBackgroundImageFile(image: Gio.File): void {
const uri = image.get_uri();
if (uri === null) {
throw new Error("Failed obtain URI from file");
}
this.backgroundImage = uri;
this.backgroundImage = image.get_uri();
}
}
14 changes: 4 additions & 10 deletions src/lib/services/image-metadata-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,9 @@ export class ImageMetadataStore {
* Store metadata for the given image.
*/
storedMetadataForImage(image: ImageFile) {
const uri = image.file.get_uri();
if (uri === null) {
throw new Error("Failed to get URI for current image, not storing!");
}
const stored: StoredMetadata = {
metadata: image.metadata,
uri: uri,
uri: image.file.get_uri(),
};
this.settings.set_string("current-metadata", JSON.stringify(stored));
}
Expand All @@ -87,11 +83,9 @@ export class ImageMetadataStore {
* @returns The stored image or null if no image was stored or the store was invalid
*/
loadFromMetadata(): ImageFile | null {
const storedRaw = this.settings.get_string("current-metadata");
if (storedRaw === null) {
return null;
}
const stored = parseStoredMetadata(storedRaw);
const stored = parseStoredMetadata(
this.settings.get_string("current-metadata"),
);
if (stored !== null) {
return {
metadata: stored.metadata,
Expand Down
4 changes: 1 addition & 3 deletions src/lib/services/refresh-error-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,9 +169,7 @@ export class RefreshErrorHandler
// If the inner error is a GLib error use its message instead of the wrapper message for better accuracy
// and locatization.
const errorMessage =
(error.cause instanceof GLib.Error
? error.cause.message
: error.message) ?? "";
error.cause instanceof GLib.Error ? error.cause.message : error.message;
notification.body = i18n.format(description, errorMessage);
}
}
Expand Down
6 changes: 1 addition & 5 deletions src/lib/source/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,7 @@ export class SourceSettings {
extension: Extension,
settings: Gio.Settings,
): SourceSettings {
const schemaId = settings.schemaId;
if (schemaId === null) {
throw new Error("Base settings have no schema ID!");
}
return new SourceSettings(extension, schemaId);
return new SourceSettings(extension, settings.schemaId);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/lib/sources/apod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ const getImage = async (
cancellable: Gio.Cancellable,
): Promise<DownloadableImage> => {
const apiKey = settings.get_string("api-key");
if (apiKey === null || apiKey.length === 0) {
if (apiKey.length === 0) {
throw new InvalidAPIKeyError(metadata);
}

Expand Down
8 changes: 1 addition & 7 deletions src/lib/sources/bing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import Soup from "gi://Soup";

import metadata from "./metadata/bing.js";
import { Source } from "../source/source.js";
import { HttpRequestError, getJSON } from "../network/http.js";
import { getJSON } from "../network/http.js";
import { DownloadableImage } from "../download.js";
import { decodeQuery, encodeQuery } from "../network/uri.js";

Expand Down Expand Up @@ -76,12 +76,6 @@ export const getTodaysImages = async (
urlbaseUHD,
GLib.UriFlags.NONE,
);
if (imageUrl === null) {
throw new HttpRequestError(
url,
`Failed to join ${urlbaseUHD} to https://www.bing.com`,
);
}
const startdate = image.startdate;
const suggestedFilename = decodeQuery(imageUrl)["id"];
return {
Expand Down
15 changes: 4 additions & 11 deletions src/lib/ui/error-detail-dialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,12 @@ import { pgettext } from "resource:///org/gnome/shell/extensions/extension.js";

import { ModalDialog } from "resource:///org/gnome/shell/ui/modalDialog.js";
import { unfoldCauses } from "../common/error.js";
import { GLibErrorWithStack } from "../fixes.js";

/**
* Shortcut for `GLib.markup_escape_text`.
*/
function e(s: string): string {
const escaped = GLib.markup_escape_text(s, -1);
if (escaped === null) {
// This can't happen I believe, because markup_escape_text would always return a string when given a string, but
// let's guard against it nonetheless.
throw new Error(`Failed to escape markup in ${s}`);
}
return escaped;
}
const e = (s: string): string => GLib.markup_escape_text(s, -1);

const formatStacktrace = (stack: string | undefined): string => {
return (
Expand All @@ -63,7 +56,7 @@ const formatOneError = (error: unknown): string => {
const stack = formatStacktrace(error.stack);
return `<b>${e(error.name)}: ${e(error.message)}</b>\n${stack}`;
} else if (error instanceof GLib.Error) {
const stack = formatStacktrace(error.stack);
const stack = formatStacktrace((error as GLibErrorWithStack).stack);
return `<b>${error.toString()}</b>\n${stack}`;
} else if (typeof error === "string") {
return e(`<b>${error}</b>`);
Expand All @@ -90,7 +83,7 @@ export const ErrorDetailDialog = GObject.registerClass(
class ErrorDetailDialog extends ModalDialog {
private readonly messageLabel: St.Label;

constructor(params?: ModalDialog.ConstructorProperties) {
constructor(params?: Partial<ModalDialog.ConstructorProps>) {
super(params);

const contentBox = new St.BoxLayout({
Expand Down
33 changes: 11 additions & 22 deletions src/prefs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import SOURCES from "./lib/sources/metadata/all.js";
import { SourceMetadata } from "./lib/source/source.js";

import type { ExtensionMetadata } from "@girs/gnome-shell/extensions/extension";
import { PromisifiedGtkFileDialog } from "./lib/fixes.js";

Gio._promisify(Gtk.FileDialog.prototype, "select_folder");

Expand All @@ -55,17 +56,12 @@ but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.`;

const getTemplate = (name: string): string => {
const uri = GLib.uri_resolve_relative(
const getTemplate = (name: string): string =>
GLib.uri_resolve_relative(
import.meta.url,
`ui/${name}.ui`,
GLib.UriFlags.NONE,
);
if (uri === null) {
throw new Error(`Failed to resolve URI for template ${name}!`);
}
return uri;
};

interface AllSettings {
readonly extension: Gio.Settings;
Expand Down Expand Up @@ -137,13 +133,15 @@ const SourcesPage = GObject.registerClass(
if (picturesDirectory) {
dialog.initialFolder = Gio.file_new_for_path(picturesDirectory);
}
// eslint-disable-next-line @typescript-eslint/no-confusing-void-expression
const file = await (dialog.select_folder(
const file = await (dialog as PromisifiedGtkFileDialog).select_folder(
this.root as Gtk.Window,
null,
) as unknown as Promise<Gio.File>);
const value = new GLib.Variant("ms", file.get_uri());
this.settings.extension.set_value("image-download-folder", value);
);
if (file) {
const value = new GLib.Variant("ms", file.get_uri());
this.settings.extension.set_value("image-download-folder", value);
} else {
console.warn("No folder selected; dialog cancelled?");
}
}

private initialize(
Expand Down Expand Up @@ -176,9 +174,6 @@ const SourcesPage = GObject.registerClass(
});

const selectedKey = this.settings.extension.get_string("selected-source");
if (selectedKey === null) {
throw new Error("'selected-source' is null?");
}
const selectedSource = buttons.get(selectedKey)?.source;
if (typeof selectedSource === "undefined") {
throw new Error(`${selectedKey} does not denote a known source!`);
Expand All @@ -187,9 +182,6 @@ const SourcesPage = GObject.registerClass(
buttons.get(selectedKey)?.button.set_active(true);
this.settings.extension.connect("changed::selected-source", () => {
const newKey = this.settings.extension.get_string("selected-source");
if (newKey === null) {
throw new Error("'selected-source' is null?");
}
const item = buttons.get(newKey);
if (typeof item === "undefined") {
throw new Error(`Source ${newKey} not known?`);
Expand Down Expand Up @@ -303,9 +295,6 @@ export default class PictureOfTheDayPreferences extends ExtensionPreferences {
// Load relevant settings
const extensionSettings = this.getSettings();
const schema_id = extensionSettings.schemaId;
if (schema_id === null) {
throw new Error("Schema ID of settings schema unexpectedly null?");
}
const allSettings: AllSettings = {
extension: extensionSettings,
sourceAPOD: this.getSettings(`${schema_id}.source.${apod.key}`),
Expand Down