Skip to content

Commit

Permalink
feat: implement function row and backports, and CI
Browse files Browse the repository at this point in the history
  • Loading branch information
Alex4386 committed Mar 16, 2024
1 parent 6567c52 commit 4ff4cc0
Show file tree
Hide file tree
Showing 54 changed files with 156 additions and 70 deletions.
37 changes: 37 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs

name: Deploy

on:
push:
branches: ['main']

jobs:
build:
runs-on: ubuntu-latest
environment: DEPLOYMENT

steps:
- uses: actions/checkout@v3
- name: Setup environment
run: |
./.vscode/setup.sh
# Set up BuildKit Docker container builder to be able to build
# multi-platform images and export cache
# https://github.com/docker/setup-buildx-action
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0

- name: Build with Decky CLI
run: |
./.vscode/build.sh
- name: Upload a Build Artifact
uses: actions/upload-artifact@v2
with:
# Artifact name
name: DeckyTerminal Build
# A file, directory or wildcard pattern that describes what to upload
path: out/*.zip
13 changes: 0 additions & 13 deletions defaults/defaults.txt

This file was deleted.

File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
18 changes: 18 additions & 0 deletions src/common/components.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { DialogButton } from "decky-frontend-lib";

export function IconDialogButton(props: Parameters<typeof DialogButton>[0]): ReturnType<typeof DialogButton> {
return <DialogButton
{...{
...props,
children: undefined,
style: {
minWidth: '0',
width: '50px',
padding: '10px',
...(props.style),
}
}}
>
{props.children}
</DialogButton>
}
4 changes: 1 addition & 3 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,17 @@ import {
staticClasses,
} from "decky-frontend-lib";
import { FaTerminal } from "react-icons/fa";

import { registerRoutes, unregisterRoutes } from "./routes";
import SidePanel from "./panel";
import TerminalGlobal from "./common/global";


export default definePlugin((serverApi: ServerAPI) => {
TerminalGlobal.setServer(serverApi);
registerRoutes(serverApi.routerHook);

return {
title: <div className={staticClasses.Title}>Decky Terminal</div>,
content: <SidePanel serverAPI={serverApi} />,
content: <SidePanel />,
icon: <FaTerminal />,
onDismount() {
unregisterRoutes(serverApi.routerHook)
Expand Down
97 changes: 63 additions & 34 deletions src/pages/Terminal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import { AttachAddon } from 'xterm-addon-attach';
import { FitAddon } from 'xterm-addon-fit';
import TerminalGlobal from "../common/global";
import XTermCSS from "../common/xterm_css";
import { FaExpand, FaKeyboard, FaTerminal } from "react-icons/fa";
import { FaArrowDown, FaArrowLeft, FaArrowRight, FaArrowUp, FaChevronCircleLeft, FaExpand, FaKeyboard, FaTerminal } from "react-icons/fa";
import { IconDialogButton } from "../common/components";

const Terminal: VFC = () => {

Expand All @@ -25,6 +26,7 @@ const Terminal: VFC = () => {
const [fullScreen, setFullScreen] = useState(false);
const [title, setTitle] = useState<string | null>(null);
const [config, setConfig] = useState<Record<string, any> | null>(null);
const [openFunctionRow, setOpenFunctionRow] = useState<boolean>(false);
let prevId: string|undefined = undefined;

// Create a ref to hold the xterm instance
Expand All @@ -45,7 +47,7 @@ const Terminal: VFC = () => {
const getConfig = async (): Promise<Record<string, any>|undefined> => {
const serverAPI = TerminalGlobal.getServer()
const config = await serverAPI.callPluginMethod<{}, string[]>("get_config", {});
console.log('getConfig', config);

if (config.success) {
setConfig(config.result)

Expand All @@ -57,21 +59,20 @@ const Terminal: VFC = () => {

const updateTitle = async (title: string): Promise<Record<string, any>|undefined> => {
const serverAPI = TerminalGlobal.getServer()
await serverAPI.callPluginMethod<{ id: string, title: string }, string[]>("set_terminal_title", { id, title });
await serverAPI.callPluginMethod<{ terminal_id: string, title: string }, string[]>("set_terminal_title", { terminal_id: id, title });

return;
}

const connectIO = async () => {
console.log('ConnectIO Triggered!');
prevId = id;
setTitle(id);

const xterm = xtermRef.current

const serverAPI = TerminalGlobal.getServer()
const localConfig = await getConfig()
console.log('config', config, 'localConfig', localConfig);

if (localConfig && xterm) {
if (localConfig.__version__ === 1) {
if (localConfig.font_family?.trim()) {
Expand All @@ -84,14 +85,12 @@ const Terminal: VFC = () => {
xterm.options.fontSize = fs;
}
}

console.log('xterm.options', xterm.options)
}
}

const terminalResult = await serverAPI.callPluginMethod<{
id: string
}, number>("get_terminal", { id });
terminal_id: string
}, number>("get_terminal", { terminal_id: id });
if (terminalResult.success) {
if (terminalResult.result === null) {
xterm?.write("--- Terminal Not Found ---");
Expand All @@ -105,8 +104,6 @@ const Terminal: VFC = () => {

const result = await serverAPI.callPluginMethod<{}, number>("get_server_port", {});
if (result.success) {
console.log('connectIO', result.result)

const url = new URL('ws://127.0.0.1:'+result.result+'/v1/terminals/'+id);
const ws = new WebSocket(url);

Expand Down Expand Up @@ -141,7 +138,6 @@ const Terminal: VFC = () => {

if (xterm) {
xterm.onResize((e) => {
console.log('Resize triggered to ', xterm)
setWindowSize(e.rows, e.cols);
});

Expand All @@ -161,19 +157,16 @@ const Terminal: VFC = () => {
};

const setWindowSize = async (rows: number, cols: number) => {
console.log('Setting WindowSize to', rows, cols);
const serverAPI = TerminalGlobal.getServer()
const result = await serverAPI.callPluginMethod<{
id: string,
terminal_id: string,
rows: number,
cols: number,
}, number>("change_terminal_window_size", {
id,
terminal_id: id,
rows,
cols,
});

console.log('setWindowSize', result);
}

const openKeyboard = () => {
Expand All @@ -183,7 +176,6 @@ const Terminal: VFC = () => {
}

const fakeInput = fakeInputRef.current as any
console.log('fakeInput', fakeInput)
if (fakeInput?.m_elInput) {
fakeInput.m_elInput.click()
} else {
Expand All @@ -203,8 +195,6 @@ const Terminal: VFC = () => {
//scrollback: 0,
});
xtermRef.current = xterm;

console.log('xterm configured')
wrappedConnectIO()

// Clean up function
Expand Down Expand Up @@ -239,8 +229,6 @@ const Terminal: VFC = () => {
if (isFullScreen) xterm.resize(res.cols - colOffset, res.rows - 1)
else xterm.resize(res.cols + colOffset, res.rows)
}

console.log('triggered fit!', xtermRef.current?.cols, xtermRef.current?.rows)
}
}

Expand All @@ -258,7 +246,6 @@ const Terminal: VFC = () => {

const gamepadHandler = (evt: GamepadEvent) => {
if (config?.use_dpad) {
console.log('gamepadEvent', evt);
evt.preventDefault();

let command: string | undefined = undefined;
Expand Down Expand Up @@ -311,6 +298,12 @@ const Terminal: VFC = () => {
return 'calc('+amount+'em + '+final+'px)';
}

if (!fullScreen) {
if (config?.extra_keys) {
amount += 3;
}
}

return amount+'em';
};

Expand Down Expand Up @@ -345,17 +338,53 @@ const Terminal: VFC = () => {
</Focusable>

{
true &&
<div style={{ display: 'flex', justifyContent: 'center', gap: '1rem', padding: '1rem 0' }}>
<DialogButton onClick={() => wsRef.current?.send('\x1b[A')}></DialogButton>
<DialogButton onClick={() => wsRef.current?.send('\x1b[B')}></DialogButton>
<DialogButton onClick={() => wsRef.current?.send('\x1b[D')}></DialogButton>
<DialogButton onClick={() => wsRef.current?.send('\x1b[C')}></DialogButton>

<DialogButton onClick={() => wsRef.current?.send('\x03')}>^C</DialogButton>
<DialogButton onClick={() => wsRef.current?.send('\x04')}>^C</DialogButton>
<DialogButton onClick={() => wsRef.current?.send('\x1a')}>^Z</DialogButton>
</div>
(config?.extra_keys && (!fullScreen || config?.handheld_mode)) &&
<Focusable style={{ overflowX: 'scroll', display: 'flex', gap: '1rem', padding: '.5rem', width: 'fit-content', maxWidth: 'calc(100% - 2rem)', margin: '0 auto' }}>
<div style={{ display: 'flex', justifyContent: 'center', gap: '.5rem' }}>
<IconDialogButton onClick={() => wsRef.current?.send('\x1b')}>Esc</IconDialogButton>
</div>

<div style={{ display: 'flex', justifyContent: 'center', gap: '.5rem'}}>
{
openFunctionRow &&
<div style={{ display: 'flex', justifyContent: 'center', gap: '.25rem'}}>
<IconDialogButton onClick={() => wsRef.current?.send('\x1b[1P')}>F1</IconDialogButton>
<IconDialogButton onClick={() => wsRef.current?.send('\x1b[1Q')}>F2</IconDialogButton>
<IconDialogButton onClick={() => wsRef.current?.send('\x1b[1R')}>F3</IconDialogButton>
<IconDialogButton onClick={() => wsRef.current?.send('\x1b[1S')}>F4</IconDialogButton>
<IconDialogButton onClick={() => wsRef.current?.send('\x1b[15~')}>F5</IconDialogButton>
<IconDialogButton onClick={() => wsRef.current?.send('\x1b[17~')}>F6</IconDialogButton>
<IconDialogButton onClick={() => wsRef.current?.send('\x1b[18~')}>F7</IconDialogButton>
<IconDialogButton onClick={() => wsRef.current?.send('\x1b[19~')}>F8</IconDialogButton>
<IconDialogButton onClick={() => wsRef.current?.send('\x1b[20~')}>F9</IconDialogButton>
<IconDialogButton onClick={() => wsRef.current?.send('\x1b[21~')}>F10</IconDialogButton>
<IconDialogButton onClick={() => wsRef.current?.send('\x1b[23~')}>F11</IconDialogButton>
<IconDialogButton onClick={() => wsRef.current?.send('\x1b[24~')}>F12</IconDialogButton>
</div>
}

{
openFunctionRow ?
<IconDialogButton onClick={() => setOpenFunctionRow(false)}><FaChevronCircleLeft /></IconDialogButton> :
<IconDialogButton onClick={() => setOpenFunctionRow(true)}>Fn</IconDialogButton>

}
</div>

<div style={{ display: 'flex', justifyContent: 'center', gap: '.5rem'}}>
<IconDialogButton onClick={() => wsRef.current?.send('\x1b[D')}><FaArrowLeft /></IconDialogButton>
<IconDialogButton onClick={() => wsRef.current?.send('\x1b[A')}><FaArrowUp /></IconDialogButton>
<IconDialogButton onClick={() => wsRef.current?.send('\x1b[B')}><FaArrowDown /></IconDialogButton>
<IconDialogButton onClick={() => wsRef.current?.send('\x1b[C')}><FaArrowRight /></IconDialogButton>
</div>

<div style={{ display: 'flex', justifyContent: 'center', gap: '.5rem'}}>
<IconDialogButton onClick={() => wsRef.current?.send('\x03')}>^C</IconDialogButton>
<IconDialogButton onClick={() => wsRef.current?.send('\x04')}>^D</IconDialogButton>
<IconDialogButton onClick={() => wsRef.current?.send('\x12')}>^R</IconDialogButton>
<IconDialogButton onClick={() => wsRef.current?.send('\x1a')}>^Z</IconDialogButton>
</div>
</Focusable>
}

{
Expand Down
37 changes: 28 additions & 9 deletions src/pages/settings/SettingsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@ const SettingsPage: VFC = () => {

const getShells = async () => {
const serverAPI = TerminalGlobal.getServer()
console.log('getShells triggered')
const shells = await serverAPI.callPluginMethod<{}, string[]>("get_shells", {});
console.log('getShells', shells);
if (shells.success) {
setShells(shells.result)
} else {
Expand All @@ -25,7 +23,6 @@ const SettingsPage: VFC = () => {
const getConfig = async () => {
const serverAPI = TerminalGlobal.getServer()
const config = await serverAPI.callPluginMethod<{}, string[]>("get_config", {});
console.log('getConfig', config);
if (config.success) {
setConfig(config.result)
}
Expand Down Expand Up @@ -92,15 +89,19 @@ const SettingsPage: VFC = () => {
})
}

const setExtraKeys = async (enabled: boolean) => {
await appendConfig({
extra_keys: enabled,
})
}

const setUseDisplay = async (enabled: boolean) => {
await appendConfig({
use_display: enabled,
})
}

useEffect(() => {
console.log('Fetching Settings')

if (!shells || shells.length === 0) getShells();
if (!config) getConfig();

Expand Down Expand Up @@ -197,17 +198,35 @@ const SettingsPage: VFC = () => {
<Focusable
style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '1rem' }}>
<div>
<div className={staticClasses.Text}>Use Display</div>
<div className={staticClasses.Label}>Set Display environment variable to allow GUI Applications to run</div>
<div className={staticClasses.Text}>Enable Extra keys</div>
<div className={staticClasses.Label}>Add a row for arrow keys and Ctrl+C,D,Z</div>
</div>
<div style={{ minWidth: '200px' }}>
<ToggleField
disabled={false}
checked={config?.use_display ?? false}
onChange={(e) => {setUseDisplay(e)}}
checked={config?.extra_keys ?? false}
onChange={(e) => {setExtraKeys(e)}}
bottomSeparator={"none"} />
</div>
</Focusable>
{
/*
<Focusable
style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '1rem' }}>
<div>
<div className={staticClasses.Text}>Use Display</div>
<div className={staticClasses.Label}>Set Display environment variable to allow GUI Applications to run</div>
</div>
<div style={{ minWidth: '200px' }}>
<ToggleField
disabled={false}
checked={config?.use_display ?? false}
onChange={(e) => {setUseDisplay(e)}}
bottomSeparator={"none"} />
</div>
</Focusable>
*/
}
</Focusable>
);
};
Expand Down
Loading

0 comments on commit 4ff4cc0

Please sign in to comment.