1- import { Component , Show , createMemo , createResource , onMount , type JSX } from "solid-js"
1+ import { Component , Show , createMemo , onMount , type JSX } from "solid-js"
22import { createStore } from "solid-js/store"
33import { Button } from "@opencode-ai/ui/button"
44import { Icon } from "@opencode-ai/ui/icon"
@@ -11,7 +11,9 @@ import { showToast } from "@opencode-ai/ui/toast"
1111import { useParams } from "@solidjs/router"
1212import { useLanguage } from "@/context/language"
1313import { usePermission } from "@/context/permission"
14- import { usePlatform } from "@/context/platform"
14+ import { usePlatform , type DisplayBackend } from "@/context/platform"
15+ import { useGlobalSync } from "@/context/global-sync"
16+ import { useGlobalSDK } from "@/context/global-sdk"
1517import {
1618 monoDefault ,
1719 monoFontFamily ,
@@ -40,6 +42,20 @@ type ThemeOption = {
4042 name : string
4143}
4244
45+ type ShellOption = {
46+ path : string
47+ name : string
48+ acceptable : boolean
49+ }
50+
51+ type ShellSelectOption = {
52+ id : string
53+ value : string
54+ label : string
55+ }
56+
57+
58+
4359// To prevent audio from overlapping/playing very quickly when navigating the settings menus,
4460// delay the playback by 100ms during quick selection changes and pause existing sounds.
4561const stopDemoSound = ( ) => {
@@ -75,12 +91,10 @@ export const SettingsGeneral: Component = () => {
7591 const params = useParams ( )
7692 const settings = useSettings ( )
7793
78- onMount ( ( ) => {
79- void theme . loadThemes ( )
80- } )
81-
8294 const [ store , setStore ] = createStore ( {
8395 checking : false ,
96+ shells : [ ] as ShellOption [ ] ,
97+ displayBackend : null as DisplayBackend | null ,
8498 } )
8599
86100 const linux = createMemo ( ( ) => platform . platform === "desktop" && platform . os === "linux" )
@@ -165,6 +179,61 @@ export const SettingsGeneral: Component = () => {
165179
166180 const themeOptions = createMemo < ThemeOption [ ] > ( ( ) => theme . ids ( ) . map ( ( id ) => ( { id, name : theme . name ( id ) } ) ) )
167181
182+ const globalSync = useGlobalSync ( )
183+ const globalSdk = useGlobalSDK ( )
184+
185+ const syncDisplayBackend = ( ) => {
186+ if ( ! linux ( ) || ! platform . getDisplayBackend ) return
187+ return Promise . resolve ( platform . getDisplayBackend ( ) ) . then ( ( value ) => setStore ( "displayBackend" , value ) ) . catch ( ( ) => undefined )
188+ }
189+
190+ onMount ( ( ) => {
191+ void theme . loadThemes ( )
192+ void globalSdk . client . pty . shells ( ) . then ( ( res ) => setStore ( "shells" , res . data || [ ] ) ) . catch ( ( ) => undefined )
193+ void syncDisplayBackend ( )
194+ } )
195+
196+ const autoOption = { id : "auto" , value : "" , label : language . t ( "settings.general.row.shell.autoDefault" ) }
197+ const currentShell = createMemo ( ( ) => globalSync . data . config . shell ?? "" )
198+
199+ const shellOptions = createMemo < ShellSelectOption [ ] > ( ( ) => {
200+ const list = store . shells
201+ const current = globalSync . data . config . shell
202+
203+ const nameCounts = new Map < string , number > ( )
204+ for ( const s of list ) {
205+ nameCounts . set ( s . name , ( nameCounts . get ( s . name ) || 0 ) + 1 )
206+ }
207+
208+ const options = [
209+ autoOption ,
210+ ...list . map ( ( s ) => {
211+ const dup = ( nameCounts . get ( s . name ) || 0 ) > 1
212+ const text = dup ? s . path : s . name
213+ const label = s . acceptable ? text : `${ text } (${ language . t ( "settings.general.row.shell.terminalOnly" ) } )`
214+ return {
215+ id : s . path ,
216+ value : dup ? s . path : s . name ,
217+ label,
218+ }
219+ } ) ,
220+ ]
221+
222+ if ( current && ! options . some ( ( o ) => o . value === current ) ) {
223+ options . push ( { id : current , value : current , label : current } )
224+ }
225+
226+ return options
227+ } )
228+
229+ const onDisplayBackendChange = ( checked : boolean ) => {
230+ const update = platform . setDisplayBackend ?.( checked ? "wayland" : "auto" )
231+ if ( ! update ) return
232+ void update . finally ( ( ) => {
233+ void syncDisplayBackend ( )
234+ } )
235+ }
236+
168237 const colorSchemeOptions = createMemo ( ( ) : { value : ColorScheme ; label : string } [ ] => [
169238 { value : "system" , label : language . t ( "theme.scheme.system" ) } ,
170239 { value : "light" , label : language . t ( "theme.scheme.light" ) } ,
@@ -243,6 +312,27 @@ export const SettingsGeneral: Component = () => {
243312 </ div >
244313 </ SettingsRow >
245314
315+ < SettingsRow
316+ title = { language . t ( "settings.general.row.shell.title" ) }
317+ description = { language . t ( "settings.general.row.shell.description" ) }
318+ >
319+ < Select
320+ data-action = "settings-shell"
321+ options = { shellOptions ( ) }
322+ current = { shellOptions ( ) . find ( ( o ) => o . value === currentShell ( ) ) ?? autoOption }
323+ value = { ( o ) => o . id }
324+ label = { ( o ) => o . label }
325+ onSelect = { ( option ) => {
326+ if ( ! option ) return
327+ globalSync . updateConfig ( { shell : option . value } )
328+ } }
329+ variant = "secondary"
330+ size = "small"
331+ triggerVariant = "settings"
332+ triggerStyle = { { "min-width" : "180px" } }
333+ />
334+ </ SettingsRow >
335+
246336 < SettingsRow
247337 title = { language . t ( "settings.general.row.reasoningSummaries.title" ) }
248338 description = { language . t ( "settings.general.row.reasoningSummaries.description" ) }
@@ -651,70 +741,32 @@ export const SettingsGeneral: Component = () => {
651741
652742 < SoundsSection />
653743
654- { /*<Show when={platform.platform === "desktop" && platform.os === "windows" && platform.getWslEnabled}>
655- {(_) => {
656- const [enabledResource, actions] = createResource(() => platform.getWslEnabled?.())
657- const enabled = () => (enabledResource.state === "pending" ? undefined : enabledResource.latest)
658-
659- return (
660- <div class="flex flex-col gap-1">
661- <h3 class="text-14-medium text-text-strong pb-2">{language.t("settings.desktop.section.wsl")}</h3>
662-
663- <SettingsList>
664- <SettingsRow
665- title={language.t("settings.desktop.wsl.title")}
666- description={language.t("settings.desktop.wsl.description")}
667- >
668- <div data-action="settings-wsl">
669- <Switch
670- checked={enabled() ?? false}
671- disabled={enabledResource.state === "pending"}
672- onChange={(checked) => platform.setWslEnabled?.(checked)?.finally(() => actions.refetch())}
673- />
674- </div>
675- </SettingsRow>
676- </SettingsList>
677- </div>
678- )
679- }}
680- </Show>*/ }
681-
682744 < UpdatesSection />
683745
684746 < Show when = { linux ( ) } >
685- { ( _ ) => {
686- const [ valueResource , actions ] = createResource ( ( ) => platform . getDisplayBackend ?.( ) )
687- const value = ( ) => ( valueResource . state === "pending" ? undefined : valueResource . latest )
688-
689- const onChange = ( checked : boolean ) =>
690- platform . setDisplayBackend ?.( checked ? "wayland" : "auto" ) . finally ( ( ) => actions . refetch ( ) )
691-
692- return (
693- < div class = "flex flex-col gap-1" >
694- < h3 class = "text-14-medium text-text-strong pb-2" > { language . t ( "settings.general.section.display" ) } </ h3 >
695-
696- < SettingsList >
697- < SettingsRow
698- title = {
699- < div class = "flex items-center gap-2" >
700- < span > { language . t ( "settings.general.row.wayland.title" ) } </ span >
701- < Tooltip value = { language . t ( "settings.general.row.wayland.tooltip" ) } placement = "top" >
702- < span class = "text-text-weak" >
703- < Icon name = "help" size = "small" />
704- </ span >
705- </ Tooltip >
706- </ div >
707- }
708- description = { language . t ( "settings.general.row.wayland.description" ) }
709- >
710- < div data-action = "settings-wayland" >
711- < Switch checked = { value ( ) === "wayland" } onChange = { onChange } />
712- </ div >
713- </ SettingsRow >
714- </ SettingsList >
715- </ div >
716- )
717- } }
747+ < div class = "flex flex-col gap-1" >
748+ < h3 class = "text-14-medium text-text-strong pb-2" > { language . t ( "settings.general.section.display" ) } </ h3 >
749+
750+ < SettingsList >
751+ < SettingsRow
752+ title = {
753+ < div class = "flex items-center gap-2" >
754+ < span > { language . t ( "settings.general.row.wayland.title" ) } </ span >
755+ < Tooltip value = { language . t ( "settings.general.row.wayland.tooltip" ) } placement = "top" >
756+ < span class = "text-text-weak" >
757+ < Icon name = "help" size = "small" />
758+ </ span >
759+ </ Tooltip >
760+ </ div >
761+ }
762+ description = { language . t ( "settings.general.row.wayland.description" ) }
763+ >
764+ < div data-action = "settings-wayland" >
765+ < Switch checked = { store . displayBackend === "wayland" } onChange = { onDisplayBackendChange } />
766+ </ div >
767+ </ SettingsRow >
768+ </ SettingsList >
769+ </ div >
718770 </ Show >
719771
720772 < Show when = { desktop ( ) && import . meta. env . VITE_OPENCODE_CHANNEL === "beta" } >
0 commit comments