@@ -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,18 @@ 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+
4357// To prevent audio from overlapping/playing very quickly when navigating the settings menus,
4458// delay the playback by 100ms during quick selection changes and pause existing sounds.
4559const stopDemoSound = ( ) => {
@@ -75,10 +89,6 @@ export const SettingsGeneral: Component = () => {
7589 const params = useParams ( )
7690 const settings = useSettings ( )
7791
78- onMount ( ( ) => {
79- void theme . loadThemes ( )
80- } )
81-
8292 const [ store , setStore ] = createStore ( {
8393 checking : false ,
8494 } )
@@ -165,6 +175,70 @@ export const SettingsGeneral: Component = () => {
165175
166176 const themeOptions = createMemo < ThemeOption [ ] > ( ( ) => theme . ids ( ) . map ( ( id ) => ( { id, name : theme . name ( id ) } ) ) )
167177
178+ const globalSync = useGlobalSync ( )
179+ const globalSdk = useGlobalSDK ( )
180+
181+ const [ shells ] = createResource (
182+ ( ) =>
183+ globalSdk . client . pty
184+ . shells ( )
185+ . then ( ( res ) => res . data ?? [ ] )
186+ . catch ( ( ) => [ ] as ShellOption [ ] ) ,
187+ { initialValue : [ ] as ShellOption [ ] } ,
188+ )
189+
190+ const [ displayBackend , { refetch : refetchDisplayBackend } ] = createResource (
191+ ( ) => ( linux ( ) && platform . getDisplayBackend ? true : false ) ,
192+ ( ) => Promise . resolve ( platform . getDisplayBackend ?.( ) ?? null ) . catch ( ( ) => null as DisplayBackend | null ) ,
193+ { initialValue : null as DisplayBackend | null } ,
194+ )
195+
196+ onMount ( ( ) => {
197+ void theme . loadThemes ( )
198+ } )
199+
200+ const autoOption = { id : "auto" , value : "" , label : language . t ( "settings.general.row.shell.autoDefault" ) }
201+ const currentShell = createMemo ( ( ) => globalSync . data . config . shell ?? "" )
202+
203+ const shellOptions = createMemo < ShellSelectOption [ ] > ( ( ) => {
204+ const list = shells . latest
205+ const current = globalSync . data . config . shell
206+
207+ const nameCounts = new Map < string , number > ( )
208+ for ( const s of list ) {
209+ nameCounts . set ( s . name , ( nameCounts . get ( s . name ) || 0 ) + 1 )
210+ }
211+
212+ const options = [
213+ autoOption ,
214+ ...list . map ( ( s ) => {
215+ const ambiguousName = ( nameCounts . get ( s . name ) || 0 ) > 1
216+ const text = ambiguousName ? s . path : s . name
217+ const label = s . acceptable ? text : `${ text } (${ language . t ( "settings.general.row.shell.terminalOnly" ) } )`
218+ return {
219+ id : s . path ,
220+ // Prefer name over path - "bash" is much cleaner than the explicit full route even when it may change due to PATH.
221+ value : ambiguousName ? s . path : s . name ,
222+ label,
223+ }
224+ } ) ,
225+ ]
226+
227+ if ( current && ! options . some ( ( o ) => o . value === current ) ) {
228+ options . push ( { id : current , value : current , label : current } )
229+ }
230+
231+ return options
232+ } )
233+
234+ const onDisplayBackendChange = ( checked : boolean ) => {
235+ const update = platform . setDisplayBackend ?.( checked ? "wayland" : "auto" )
236+ if ( ! update ) return
237+ void update . finally ( ( ) => {
238+ void refetchDisplayBackend ( )
239+ } )
240+ }
241+
168242 const colorSchemeOptions = createMemo ( ( ) : { value : ColorScheme ; label : string } [ ] => [
169243 { value : "system" , label : language . t ( "theme.scheme.system" ) } ,
170244 { value : "light" , label : language . t ( "theme.scheme.light" ) } ,
@@ -243,6 +317,27 @@ export const SettingsGeneral: Component = () => {
243317 </ div >
244318 </ SettingsRow >
245319
320+ < SettingsRow
321+ title = { language . t ( "settings.general.row.shell.title" ) }
322+ description = { language . t ( "settings.general.row.shell.description" ) }
323+ >
324+ < Select
325+ data-action = "settings-shell"
326+ options = { shellOptions ( ) }
327+ current = { shellOptions ( ) . find ( ( o ) => o . value === currentShell ( ) ) ?? autoOption }
328+ value = { ( o ) => o . id }
329+ label = { ( o ) => o . label }
330+ onSelect = { ( option ) => {
331+ if ( ! option ) return
332+ globalSync . updateConfig ( { shell : option . value } )
333+ } }
334+ variant = "secondary"
335+ size = "small"
336+ triggerVariant = "settings"
337+ triggerStyle = { { "min-width" : "180px" } }
338+ />
339+ </ SettingsRow >
340+
246341 < SettingsRow
247342 title = { language . t ( "settings.general.row.reasoningSummaries.title" ) }
248343 description = { language . t ( "settings.general.row.reasoningSummaries.description" ) }
@@ -651,70 +746,32 @@ export const SettingsGeneral: Component = () => {
651746
652747 < SoundsSection />
653748
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-
682749 < UpdatesSection />
683750
684751 < 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- } }
752+ < div class = "flex flex-col gap-1" >
753+ < h3 class = "text-14-medium text-text-strong pb-2" > { language . t ( "settings.general.section.display" ) } </ h3 >
754+
755+ < SettingsList >
756+ < SettingsRow
757+ title = {
758+ < div class = "flex items-center gap-2" >
759+ < span > { language . t ( "settings.general.row.wayland.title" ) } </ span >
760+ < Tooltip value = { language . t ( "settings.general.row.wayland.tooltip" ) } placement = "top" >
761+ < span class = "text-text-weak" >
762+ < Icon name = "help" size = "small" />
763+ </ span >
764+ </ Tooltip >
765+ </ div >
766+ }
767+ description = { language . t ( "settings.general.row.wayland.description" ) }
768+ >
769+ < div data-action = "settings-wayland" >
770+ < Switch checked = { displayBackend . latest === "wayland" } onChange = { onDisplayBackendChange } />
771+ </ div >
772+ </ SettingsRow >
773+ </ SettingsList >
774+ </ div >
718775 </ Show >
719776
720777 < Show when = { desktop ( ) && import . meta. env . VITE_OPENCODE_CHANNEL === "beta" } >
0 commit comments