Skip to content
33 changes: 13 additions & 20 deletions web/packages/teleterm/src/services/config/configService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { createConfigStore } from './configStore';

const createAppConfigSchema = (platform: Platform) => {
const defaultKeymap = getDefaultKeymap(platform);
const defaultFonts = getDefaultFonts(platform);
const defaultTerminalFont = getDefaultTerminalFont(platform);

// Important: all keys except 'usageReporting.enabled' are currently not
// configurable by the user. Before we let the user configure them,
Expand Down Expand Up @@ -85,12 +85,14 @@ const createAppConfigSchema = (platform: Platform) => {
'keymap.openQuickInput': omitStoredConfigValue(
z.string().default(defaultKeymap['open-quick-input'])
),
'fonts.sansSerifFamily': omitStoredConfigValue(
z.string().default(defaultFonts['sansSerif'])
),
'fonts.monoFamily': omitStoredConfigValue(
z.string().default(defaultFonts['mono'])
),
/**
* This value can be provided by the user and is unsanitized. This means that it cannot be directly interpolated
* in a styled component or used in CSS, as it may inject malicious CSS code.
* Before using it, sanitize it with `CSS.escape` or pass it as a `style` prop.
* Read more https://frontarm.com/james-k-nelson/how-can-i-use-css-in-js-securely/.
*/
'terminal.fontFamily': z.string().default(defaultTerminalFont),
'terminal.fontSize': z.number().int().min(1).max(256).default(15),
});
};

Expand Down Expand Up @@ -189,23 +191,14 @@ const getDefaultKeymap = (platform: Platform) => {
}
};

function getDefaultFonts(platform: Platform) {
function getDefaultTerminalFont(platform: Platform) {
switch (platform) {
case 'win32':
return {
sansSerif: "system-ui, 'Segoe WPC', 'Segoe UI', sans-serif",
mono: "'Consolas', 'Courier New', monospace",
};
return "'Consolas', 'Courier New', monospace";
case 'linux':
return {
sansSerif: "system-ui, 'Ubuntu', 'Droid Sans', sans-serif",
mono: "'Droid Sans Mono', 'Courier New', monospace, 'Droid Sans Fallback'",
};
return "'Droid Sans Mono', 'Courier New', monospace, 'Droid Sans Fallback'";
case 'darwin':
return {
sansSerif: '-apple-system, BlinkMacSystemFont, sans-serif',
mono: "Menlo, Monaco, 'Courier New', monospace",
};
return "Menlo, Monaco, 'Courier New', monospace";
}
}

Expand Down
13 changes: 2 additions & 11 deletions web/packages/teleterm/src/ui/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,24 +26,15 @@ import { AppInitializer } from 'teleterm/ui/AppInitializer';
import CatchError from './components/CatchError';
import AppContextProvider from './appContextProvider';
import AppContext from './appContext';
import ThemeProvider from './ThemeProvider';
import { ThemeProvider } from './ThemeProvider';

export const App: React.FC<{ ctx: AppContext }> = ({ ctx }) => {
return (
<StyledApp>
<CatchError>
<DndProvider backend={HTML5Backend}>
<AppContextProvider value={ctx}>
<ThemeProvider
fonts={{
mono: ctx.mainProcessClient.configService.get(
'fonts.monoFamily'
).value,
sansSerif: ctx.mainProcessClient.configService.get(
'fonts.sansSerifFamily'
).value,
}}
>
<ThemeProvider>
<AppInitializer />
</ThemeProvider>
</AppContextProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
import Document from 'teleterm/ui/Document';
import { useAppContext } from 'teleterm/ui/appContextProvider';

import Terminal from './Terminal';
import { Terminal } from './Terminal';
import DocumentReconnect from './DocumentReconnect';
import useDocTerminal, { Props } from './useDocumentTerminal';
import { useTshFileTransferHandlers } from './useTshFileTransferHandlers';
Expand All @@ -40,10 +40,15 @@ export default function DocumentTerminalContainer({ doc, visible }: Props) {

export function DocumentTerminal(props: Props & { visible: boolean }) {
const ctx = useAppContext();
const { configService } = ctx.mainProcessClient;
const { visible, doc } = props;
const state = useDocTerminal(doc);
const ptyProcess = state.data?.ptyProcess;
const { upload, download } = useTshFileTransferHandlers();
const unsanitizedTerminalFontFamily = configService.get(
'terminal.fontFamily'
).value;
const terminalFontSize = configService.get('terminal.fontSize').value;

return (
<Document
Expand Down Expand Up @@ -101,6 +106,8 @@ export function DocumentTerminal(props: Props & { visible: boolean }) {
<Terminal
ptyProcess={ptyProcess}
visible={props.visible}
unsanitizedFontFamily={unsanitizedTerminalFontFamily}
fontSize={terminalFontSize}
onEnterKey={state.data.refreshTitle}
/>
</>
Expand Down
32 changes: 21 additions & 11 deletions web/packages/teleterm/src/ui/DocumentTerminal/Terminal/Terminal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,36 @@ limitations under the License.
*/

import React, { useEffect, useRef } from 'react';
import styled, { useTheme } from 'styled-components';
import styled from 'styled-components';
import { Box, Flex } from 'design';
import { debounce } from 'shared/utils/highbar';

import { IPtyProcess } from 'teleterm/sharedProcess/ptyHost';

import XTermCtrl from './ctrl';

export default function Terminal(props: Props) {
type TerminalProps = {
ptyProcess: IPtyProcess;
visible: boolean;
/**
* This value can be provided by the user and is unsanitized. This means that it cannot be directly interpolated
* in a styled component or used in CSS, as it may inject malicious CSS code.
* Before using it, sanitize it with `CSS.escape` or pass it as a `style` prop.
* Read more https://frontarm.com/james-k-nelson/how-can-i-use-css-in-js-securely/.
*/
unsanitizedFontFamily: string;
fontSize: number;
onEnterKey?(): void;
};

export function Terminal(props: TerminalProps) {
const refElement = useRef<HTMLElement>();
const refCtrl = useRef<XTermCtrl>();
const fontFamily = useTheme().fonts.mono;

useEffect(() => {
const ctrl = new XTermCtrl(props.ptyProcess, {
el: refElement.current,
fontFamily,
fontSize: props.fontSize,
});

ctrl.open();
Expand Down Expand Up @@ -70,17 +83,14 @@ export default function Terminal(props: Props) {
width="100%"
style={{ overflow: 'hidden' }}
>
<StyledXterm ref={refElement} />
<StyledXterm
ref={refElement}
style={{ fontFamily: props.unsanitizedFontFamily }}
/>
</Flex>
);
}

type Props = {
ptyProcess: IPtyProcess;
visible: boolean;
onEnterKey?(): void;
};

const StyledXterm = styled(Box)`
height: 100%;
width: 100%;
Expand Down
11 changes: 9 additions & 2 deletions web/packages/teleterm/src/ui/DocumentTerminal/Terminal/ctrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const WINDOW_RESIZE_DEBOUNCE_DELAY = 200;

type Options = {
el: HTMLElement;
fontFamily?: string;
fontSize: number;
};

export default class TtyTerminal {
Expand All @@ -51,7 +51,14 @@ export default class TtyTerminal {
open(): void {
this.term = new Terminal({
cursorBlink: false,
fontFamily: this.options.fontFamily,
/**
* `fontFamily` can be provided by the user and is unsanitized. This means that it cannot be directly used in CSS,
* as it may inject malicious CSS code.
* To sanitize the value, we set it as a style on the HTML element and then read it from it.
* Read more https://frontarm.com/james-k-nelson/how-can-i-use-css-in-js-securely/.
*/
fontFamily: this.el.style.fontFamily,
fontSize: this.options.fontSize,
scrollback: 5000,
theme: {
background: theme.colors.primary.darker,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,4 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import Terminal from './Terminal';

export default Terminal;
export { Terminal } from './Terminal';
41 changes: 14 additions & 27 deletions web/packages/teleterm/src/ui/ThemeProvider/ThemeProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,34 +15,21 @@ limitations under the License.
*/

import React from 'react';
import { ThemeProvider, StyleSheetManager } from 'styled-components';
import {
ThemeProvider as StyledThemeProvider,
StyleSheetManager,
} from 'styled-components';

import { GlobalStyle } from './globals';
import theme from './theme';

type TeletermThemeProvider = {
fonts: {
mono: string;
sansSerif: string;
};
};

const TeletermThemeProvider: React.FC<TeletermThemeProvider> = props => {
if (props?.fonts) {
theme.font = props.fonts.sansSerif;
theme.fonts = props.fonts;
}

return (
<ThemeProvider theme={theme}>
<StyleSheetManager disableVendorPrefixes>
<React.Fragment>
<GlobalStyle />
{props.children}
</React.Fragment>
</StyleSheetManager>
</ThemeProvider>
);
};

export default TeletermThemeProvider;
export const ThemeProvider: React.FC = props => (
<StyledThemeProvider theme={theme}>
<StyleSheetManager disableVendorPrefixes>
<React.Fragment>
<GlobalStyle />
{props.children}
</React.Fragment>
</StyleSheetManager>
</StyledThemeProvider>
);
3 changes: 1 addition & 2 deletions web/packages/teleterm/src/ui/ThemeProvider/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,4 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import ThemeProvider from './ThemeProvider';
export default ThemeProvider;
export { ThemeProvider } from './ThemeProvider';
9 changes: 7 additions & 2 deletions web/packages/teleterm/src/ui/ThemeProvider/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,16 @@ const borders = [
'32px solid',
];

const sansSerif = 'system-ui';

const theme = {
colors,
typography,
font: fonts.sansSerif,
fonts: fonts,
font: sansSerif,
fonts: {
sansSerif,
mono: fonts.mono,
},
fontWeights,
fontSizes,
space,
Expand Down