Skip to content

Commit

Permalink
feat(plugins): Support built-in plugins, scripts, & convert session p…
Browse files Browse the repository at this point in the history
…lugin to scripts
  • Loading branch information
mmstick committed Dec 22, 2020
1 parent 19ebae0 commit b825c0a
Show file tree
Hide file tree
Showing 14 changed files with 476 additions and 359 deletions.
7 changes: 5 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ endif
ifeq ($(strip $(DESTDIR)),)
INSTALLBASE = $(XDG_DATA_HOME)/gnome-shell/extensions
PLUGIN_BASE = $(XDG_DATA_HOME)/pop-shell/launcher
SCRIPTS_BASE = $(XDG_DATA_HOME)/pop-shell/scripts
else
INSTALLBASE = $(DESTDIR)/usr/share/gnome-shell/extensions
PLUGIN_BASE = $(DESTDIR)/usr/lib/pop-shell/launcher
SCRIPTS_BASE = $(DESTDIR)/usr/lib/pop-shell/scripts
endif
INSTALLNAME = $(UUID)

Expand Down Expand Up @@ -77,10 +79,11 @@ local-install: depcheck compile install configure enable restart-shell

install:
rm -rf $(INSTALLBASE)/$(INSTALLNAME)
mkdir -p $(INSTALLBASE)/$(INSTALLNAME) $(PLUGIN_BASE)
mkdir -p $(INSTALLBASE)/$(INSTALLNAME) $(PLUGIN_BASE) $(SCRIPTS_BASE)
cp -r _build/* $(INSTALLBASE)/$(INSTALLNAME)/
cp -r src/plugins/* $(PLUGIN_BASE)
chmod +x $(PLUGIN_BASE)/**/*.js
cp -r src/scripts/* $(SCRIPTS_BASE)
chmod +x $(PLUGIN_BASE)/**/*.js $(SCRIPTS_BASE)/*

uninstall:
rm -rf $(INSTALLBASE)/$(INSTALLNAME)
Expand Down
3 changes: 2 additions & 1 deletion debian/rules
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ override_dh_install:

override_dh_fixperms:
dh_fixperms
chmod +x debian/pop-shell/usr/lib/pop-shell/launcher/**/*.js
chmod +x debian/pop-shell/usr/lib/pop-shell/launcher/**/*.js
chmod +x debian/pop-shell/usr/lib/pop-shell/scripts/*
1 change: 1 addition & 0 deletions src/arena.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/** Hop slot arena allocator */
export class Arena<T> {
private slots: Array<null | T> = new Array();

private unused: Array<number> = new Array()

truncate(n: number) {
Expand Down
13 changes: 7 additions & 6 deletions src/dialog_launcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import * as lib from 'lib';
import * as log from 'log';
import * as result from 'result';
import * as search from 'dialog_search';
import * as launch from 'launcher';
import * as launch from 'launcher_service';
import * as plugins from 'launcher_plugins';

import type { ShellWindow } from 'window';
import type { Ext } from 'extension';
Expand Down Expand Up @@ -39,7 +40,7 @@ export class Launcher extends search.Search {
options: Array<launch.SearchOption>
desktop_apps: Array<[string, AppInfo]>
service: launch.LauncherService
last_plugin: null | launch.Plugin.Source
last_plugin: null | plugins.Plugin.Source
mode: number;

constructor(ext: Ext) {
Expand Down Expand Up @@ -195,9 +196,9 @@ export class Launcher extends search.Search {
}
} else if ("plugin" in option) {
const { plugin, id } = option
launch.Plugin.submit(plugin, id)
plugins.Plugin.submit(plugin, id)

const response = launch.Plugin.listen(plugin)
const response = plugins.Plugin.listen(plugin)
if (response) {
if (response.event === "fill") {
this.set_text(response.text)
Expand All @@ -212,8 +213,8 @@ export class Launcher extends search.Search {

let complete = () => {
if (this.last_plugin) {
launch.Plugin.complete(this.last_plugin)
const res = launch.Plugin.listen(this.last_plugin)
plugins.Plugin.complete(this.last_plugin)
const res = plugins.Plugin.listen(this.last_plugin)
if (res && res.event === "fill") {
this.set_text(res.text)
}
Expand Down
2 changes: 1 addition & 1 deletion src/dialog_search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
const Me = imports.misc.extensionUtils.getCurrentExtension();

import * as Lib from 'lib';
import { SearchOption } from './launcher';
import { SearchOption } from 'launcher_service';

const { Clutter, St } = imports.gi;
const { ModalDialog } = imports.ui.modalDialog;
Expand Down
241 changes: 241 additions & 0 deletions src/launcher_plugins.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
// @ts-ignore
const Me = imports.misc.extensionUtils.getCurrentExtension()

const { Gio, GLib } = imports.gi

import * as utils from 'utils'

/** The trait which all builtin plugins implement */
export abstract class Builtin {
/** Stores the last search result */
last_response: null | Response.Response = null

/** Results of the last query */
selections: Array<Response.Selection> = new Array()

/** Initializes default values and resets state */
abstract init(): void

/** Uses the search input to query for search results */
abstract query(query: string): Response.Response

/** Applies an option by its ID */
abstract submit(id: number): Response.Response

/** Dispatches a launcher request, and stores the response */
handle(event: Request.Request) {
switch (event.event) {
case "complete":
this.last_response = { event: "noop" }
break
case "query":
this.last_response = this.query(event.value)
break
case "submit":
this.last_response = this.submit(event.id)
break
default:
this.last_response = { event: "noop" }

}
}
}

export namespace Request {
export type Request = Complete | Submit | Query | Quit

export interface Complete {
event: 'complete',
}

export interface Submit {
event: 'submit',
id: number
}

export interface Quit {
event: 'quit'
}

export interface Query {
event: 'query',
value: string
}
}

export namespace Response {
export interface Selection {
id: number
name: string
description: null | string
icon?: string
content_type?: string
}

export interface Query {
event: "queried",
selections: Array<Selection>
}

export interface Fill {
event: "fill",
text: string
}

export interface Close {
event: "close"
}

export interface NoOp {
event: 'noop'
}

export type Response = Query | Fill | Close | NoOp

export function parse(input: string): null | Response {
try {
let object = JSON.parse(input) as Response
switch (object.event) {
case "close":
case "fill":
case "queried":
return object
}
} catch (e) {

}

return null
}
}

export namespace Plugin {
export interface Config {
name: string
description: string
pattern: string
exec: string
icon: string
}

export function read(file: string): Config | null {
global.log(`found plugin at ${file}`)
try {
let [ok, contents] = Gio.file_new_for_path(file)
.load_contents(null)

if (ok) return parse(imports.byteArray.toString(contents))
} catch (e) {

}

return null
}

export function parse(input: string): Config | null {
try {
return JSON.parse(input)
} catch (e) {
return null
}
}

export interface External {
cmd: string
proc: null | utils.AsyncIPC
}

export interface BuiltinVariant {
builtin: Builtin
}

export interface Source {
config: Config
backend: External | BuiltinVariant
pattern: null | RegExp
}

export function listen(plugin: Plugin.Source): null | Response.Response {
if ('builtin' in plugin.backend) {
return plugin.backend.builtin.last_response
} else {
const backend = plugin.backend
if (!backend.proc) {
const proc = Plugin.start(backend)
if (proc) {
backend.proc = proc
} else {
return null
}
}

try {
let [bytes,] = backend.proc.stdout.read_line(null)
return Response.parse(imports.byteArray.toString(bytes))
} catch (e) {
return null
}
}
}

export function complete(plugin: Plugin.Source): boolean {
return send(plugin, { event: "complete" })
}

export function query(plugin: Plugin.Source, value: string): boolean {
return send(plugin, { event: "query", value })
}

export function quit(plugin: Plugin.Source) {
if ('proc' in plugin.backend) {
if (plugin.backend.proc) {
send(plugin, { event: "quit" })
plugin.backend.proc = null
}
} else {
send(plugin, { event: "quit" })
}
}

export function submit(plugin: Plugin.Source, id: number): boolean {
return send(plugin, { event: "submit", id })
}

export function send(plugin: Plugin.Source, event: Request.Request): boolean {
const backend = plugin.backend

if ('builtin' in backend) {
backend.builtin.handle(event)
return true
} else {
let string = JSON.stringify(event)

if (!backend.proc) {
backend.proc = start(backend)
}

function attempt(name: string, plugin: Plugin.External, string: string) {
if (!plugin.proc) return false

try {
plugin.proc.stdin.write_bytes(new GLib.Bytes(string + "\n"), null)
return true
} catch (e) {
global.log(`failed to send message to ${name}: ${e}`)
return false
}
}

if (!attempt(plugin.config.name, backend, string)) {
backend.proc = start(backend)
if (!attempt(plugin.config.name, backend, string)) return false
}
}

return true
}

export function start(plugin: Plugin.External): null | utils.AsyncIPC {
return utils.async_process_ipc([plugin.cmd])
}
}

0 comments on commit b825c0a

Please sign in to comment.