Skip to content

Commit cb52cd4

Browse files
mon-jaishiftkey
andcommitted
fix: add an option to use the Windows title bar (#912)
Co-authored-by: Brendan Forster <github@brendanforster.com>
1 parent eae9ab1 commit cb52cd4

File tree

17 files changed

+287
-29
lines changed

17 files changed

+287
-29
lines changed

app/src/lib/app-state.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import { WindowState } from './window-state'
3232
import { Shell } from './shells'
3333

3434
import { ApplicableTheme, ApplicationTheme } from '../ui/lib/application-theme'
35+
import { TitleBarStyle } from '../ui/lib/title-bar-style'
3536
import { IAccountRepositories } from './stores/api-repositories-store'
3637
import { ManualConflictResolution } from '../models/manual-conflict-resolution'
3738
import { Banner } from '../models/banner'
@@ -286,6 +287,9 @@ export interface IAppState {
286287
/** The selected tab size preference */
287288
readonly selectedTabSize: number
288289

290+
/** The selected title bar style for the application */
291+
readonly titleBarStyle: TitleBarStyle
292+
289293
/**
290294
* A map keyed on a user account (GitHub.com or GitHub Enterprise)
291295
* containing an object with repositories that the authenticated
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { writeFile } from 'fs/promises'
2+
import { existsSync, readFileSync } from 'fs'
3+
import { join } from 'path'
4+
import { app } from 'electron'
5+
import { TitleBarStyle } from '../ui/lib/title-bar-style'
6+
7+
export type TitleBarConfig = {
8+
titleBarStyle: TitleBarStyle
9+
}
10+
11+
let cachedTitleBarConfig: TitleBarConfig | null = null
12+
13+
// The function has to be synchronous,
14+
// since we need its return value to create electron BrowserWindow
15+
export function readTitleBarConfigFileSync(): TitleBarConfig {
16+
if (cachedTitleBarConfig) {
17+
return cachedTitleBarConfig
18+
}
19+
20+
const titleBarConfigPath = getTitleBarConfigPath()
21+
22+
if (existsSync(titleBarConfigPath)) {
23+
const storedTitleBarConfig = JSON.parse(
24+
readFileSync(titleBarConfigPath, 'utf8')
25+
)
26+
27+
if (
28+
storedTitleBarConfig.titleBarStyle === 'native' ||
29+
storedTitleBarConfig.titleBarStyle === 'custom'
30+
) {
31+
cachedTitleBarConfig = storedTitleBarConfig
32+
}
33+
}
34+
35+
// Cache the default value if the config file is not found, or if it contains an invalid value.
36+
if (cachedTitleBarConfig == null) {
37+
cachedTitleBarConfig = { titleBarStyle: 'native' }
38+
}
39+
40+
return cachedTitleBarConfig
41+
}
42+
43+
export function saveTitleBarConfigFile(config: TitleBarConfig) {
44+
return writeFile(getTitleBarConfigPath(), JSON.stringify(config), 'utf8')
45+
}
46+
47+
const getTitleBarConfigPath = () =>
48+
join(app.getPath('userData'), '.title-bar-config')

app/src/lib/ipc-shared.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { Architecture } from './get-architecture'
1313
import { EndpointToken } from './endpoint-token'
1414
import { PathType } from '../ui/lib/app-proxy'
1515
import { ThemeSource } from '../ui/lib/theme-source'
16+
import { TitleBarStyle } from '../ui/lib/title-bar-style'
1617
import { DesktopNotificationPermission } from 'desktop-notifications/dist/notification-permission'
1718
import { NotificationCallback } from 'desktop-notifications/dist/notification-callback'
1819
import { DesktopAliveEvent } from './stores/alive-store'
@@ -66,6 +67,7 @@ export type RequestChannels = {
6667
blur: () => void
6768
'update-accounts': (accounts: ReadonlyArray<EndpointToken>) => void
6869
'quit-and-install-updates': () => void
70+
'restart-app': () => void
6971
'quit-app': () => void
7072
'minimize-window': () => void
7173
'maximize-window': () => void
@@ -124,6 +126,8 @@ export type RequestResponseChannels = {
124126
'should-use-dark-colors': () => Promise<boolean>
125127
'save-guid': (guid: string) => Promise<void>
126128
'get-guid': () => Promise<string>
129+
'save-title-bar-style': (titleBarStyle: TitleBarStyle) => Promise<void>
130+
'get-title-bar-style': () => Promise<TitleBarStyle>
127131
'show-notification': (
128132
title: string,
129133
body: string,

app/src/lib/stores/app-store.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ import {
8080
getPersistedThemeName,
8181
setPersistedTheme,
8282
} from '../../ui/lib/application-theme'
83+
import { TitleBarStyle } from '../../ui/lib/title-bar-style'
8384
import {
8485
getAppMenu,
8586
getCurrentWindowState,
@@ -91,6 +92,8 @@ import {
9192
sendWillQuitEvenIfUpdatingSync,
9293
quitApp,
9394
sendCancelQuittingSync,
95+
saveTitleBarStyle,
96+
getTitleBarStyle,
9497
} from '../../ui/main-process-proxy'
9598
import {
9699
API,
@@ -547,6 +550,7 @@ export class AppStore extends TypedBaseStore<IAppState> {
547550
private selectedTheme = ApplicationTheme.System
548551
private currentTheme: ApplicableTheme = ApplicationTheme.Light
549552
private selectedTabSize = tabSizeDefault
553+
private titleBarStyle: TitleBarStyle = 'native'
550554

551555
private useWindowsOpenSSH: boolean = false
552556

@@ -1053,6 +1057,7 @@ export class AppStore extends TypedBaseStore<IAppState> {
10531057
selectedTheme: this.selectedTheme,
10541058
currentTheme: this.currentTheme,
10551059
selectedTabSize: this.selectedTabSize,
1060+
titleBarStyle: this.titleBarStyle,
10561061
apiRepositories: this.apiRepositoriesStore.getState(),
10571062
useWindowsOpenSSH: this.useWindowsOpenSSH,
10581063
showCommitLengthWarning: this.showCommitLengthWarning,
@@ -2252,6 +2257,8 @@ export class AppStore extends TypedBaseStore<IAppState> {
22522257
this.emitUpdate()
22532258
})
22542259

2260+
this.titleBarStyle = await getTitleBarStyle()
2261+
22552262
this.lastThankYou = getObject<ILastThankYou>(lastThankYouKey)
22562263

22572264
this.useCustomEditor =
@@ -6600,6 +6607,14 @@ export class AppStore extends TypedBaseStore<IAppState> {
66006607
return Promise.resolve()
66016608
}
66026609

6610+
/*
6611+
* Set the title bar style for the application
6612+
*/
6613+
public _setTitleBarStyle(titleBarStyle: TitleBarStyle) {
6614+
this.titleBarStyle = titleBarStyle
6615+
return saveTitleBarStyle(titleBarStyle)
6616+
}
6617+
66036618
public async _resolveCurrentEditor() {
66046619
const match = await findEditorOrDefault(this.selectedExternalEditor)
66056620
const resolvedExternalEditor = match != null ? match.editor : null

app/src/main-process/app-window.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
getWindowState,
1515
registerWindowStateChangedEvents,
1616
} from '../lib/window-state'
17+
import { readTitleBarConfigFileSync } from '../lib/get-title-bar-config'
1718
import { MenuEvent } from './menu'
1819
import { URLActionType } from '../lib/parse-app-url'
1920
import { ILaunchStats } from '../lib/stats'
@@ -77,6 +78,9 @@ export class AppWindow {
7778
} else if (__WIN32__) {
7879
windowOptions.frame = false
7980
} else if (__LINUX__) {
81+
if (readTitleBarConfigFileSync().titleBarStyle === 'custom') {
82+
windowOptions.frame = false
83+
}
8084
windowOptions.icon = join(__dirname, 'static', 'logos', '512x512.png')
8185

8286
// relax restriction here for users trying to run app at a small

app/src/main-process/main.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ import {
4444
} from '../lib/get-architecture'
4545
import { buildSpellCheckMenu } from './menu/build-spell-check-menu'
4646
import { getMainGUID, saveGUIDFile } from '../lib/get-main-guid'
47+
import {
48+
readTitleBarConfigFileSync,
49+
saveTitleBarConfigFile,
50+
} from '../lib/get-title-bar-config'
4751
import {
4852
getNotificationsPermission,
4953
requestNotificationsPermission,
@@ -509,6 +513,11 @@ app.on('ready', () => {
509513
mainWindow?.quitAndInstallUpdate()
510514
)
511515

516+
ipcMain.on('restart-app', () => {
517+
app.relaunch()
518+
app.exit()
519+
})
520+
512521
ipcMain.on('quit-app', () => app.quit())
513522

514523
ipcMain.on('minimize-window', () => mainWindow?.minimizeWindow())
@@ -697,6 +706,16 @@ app.on('ready', () => {
697706

698707
ipcMain.handle('save-guid', (_, guid) => saveGUIDFile(guid))
699708

709+
ipcMain.handle(
710+
'get-title-bar-style',
711+
async () => readTitleBarConfigFileSync().titleBarStyle
712+
)
713+
714+
ipcMain.handle(
715+
'save-title-bar-style',
716+
async (_, titleBarStyle) => await saveTitleBarConfigFile({ titleBarStyle })
717+
)
718+
700719
ipcMain.handle('show-notification', async (_, title, body, userInfo) =>
701720
showNotification(title, body, userInfo)
702721
)

app/src/models/popup.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ export enum PopupType {
9696
UnknownAuthors = 'UnknownAuthors',
9797
ConfirmRepoRulesBypass = 'ConfirmRepoRulesBypass',
9898
TestIcons = 'TestIcons',
99+
ConfirmRestart = 'ConfirmRestart',
99100
}
100101

101102
interface IBasePopup {
@@ -430,5 +431,6 @@ export type PopupDetail =
430431
| {
431432
type: PopupType.TestIcons
432433
}
434+
| { type: PopupType.ConfirmRestart }
433435

434436
export type Popup = IBasePopup & PopupDetail

app/src/ui/app.tsx

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ import { Welcome } from './welcome'
7171
import { AppMenuBar } from './app-menu'
7272
import { UpdateAvailable, renderBanner } from './banners'
7373
import { Preferences } from './preferences'
74+
import { ConfirmRestart } from './preferences/confirm-restart'
7475
import { RepositorySettings } from './repository-settings'
7576
import { AppError } from './app-error'
7677
import { MissingRepository } from './missing-repository'
@@ -1496,8 +1497,8 @@ export class App extends React.Component<IAppProps, IAppState> {
14961497
* on Windows.
14971498
*/
14981499
private renderAppMenuBar() {
1499-
// We only render the app menu bar on Windows
1500-
if (!__WIN32__) {
1500+
// We do not render the app menu bar on macOS
1501+
if (__DARWIN__) {
15011502
return null
15021503
}
15031504

@@ -1548,22 +1549,22 @@ export class App extends React.Component<IAppProps, IAppState> {
15481549
this.state.currentFoldout &&
15491550
this.state.currentFoldout.type === FoldoutType.AppMenu
15501551

1551-
// As Linux still uses the classic Electron menu, we are opting out of the
1552-
// custom menu that is shown as part of the title bar below
1553-
if (__LINUX__) {
1552+
// We do not render the app menu bar on Linux when the user has selected
1553+
// the "native" menu option
1554+
if (__LINUX__ && this.state.titleBarStyle === 'native') {
15541555
return null
15551556
}
15561557

15571558
// When we're in full-screen mode on Windows we only need to render
15581559
// the title bar when the menu bar is active. On other platforms we
15591560
// never render the title bar while in full-screen mode.
15601561
if (inFullScreen) {
1561-
if (!__WIN32__ || !menuBarActive) {
1562+
if (__DARWIN__ || !menuBarActive) {
15621563
return null
15631564
}
15641565
}
15651566

1566-
const showAppIcon = __WIN32__ && !this.state.showWelcomeFlow
1567+
const showAppIcon = !__DARWIN__ && !this.state.showWelcomeFlow
15671568
const inWelcomeFlow = this.state.showWelcomeFlow
15681569
const inNoRepositoriesView = this.inNoRepositoriesViewState()
15691570

@@ -1762,6 +1763,7 @@ export class App extends React.Component<IAppProps, IAppState> {
17621763
customEditor={this.state.customEditor}
17631764
useCustomShell={this.state.useCustomShell}
17641765
customShell={this.state.customShell}
1766+
titleBarStyle={this.state.titleBarStyle}
17651767
repositoryIndicatorsEnabled={this.state.repositoryIndicatorsEnabled}
17661768
onOpenFileInExternalEditor={this.openFileInExternalEditor}
17671769
underlineLinks={this.state.underlineLinks}
@@ -2682,6 +2684,9 @@ export class App extends React.Component<IAppProps, IAppState> {
26822684
/>
26832685
)
26842686
}
2687+
case PopupType.ConfirmRestart: {
2688+
return <ConfirmRestart onDismissed={onPopupDismissedFn} />
2689+
}
26852690
default:
26862691
return assertNever(popup, `Unknown popup type: ${popup}`)
26872692
}

app/src/ui/dispatcher/dispatcher.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ import { TipState, IValidBranch } from '../../models/tip'
8787
import { Banner, BannerType } from '../../models/banner'
8888

8989
import { ApplicationTheme } from '../lib/application-theme'
90+
import { TitleBarStyle } from '../lib/title-bar-style'
9091
import { installCLI } from '../lib/install-cli'
9192
import {
9293
executeMenuItem,
@@ -2465,6 +2466,19 @@ export class Dispatcher {
24652466
public setSelectedTabSize(tabSize: number) {
24662467
return this.appStore._setSelectedTabSize(tabSize)
24672468
}
2469+
/*
2470+
* Set the title bar style for the application
2471+
*/
2472+
public async setTitleBarStyle(titleBarStyle: TitleBarStyle) {
2473+
const existingState = this.appStore.getState()
2474+
const { titleBarStyle: existingTitleBarStyle } = existingState
2475+
2476+
await this.appStore._setTitleBarStyle(titleBarStyle)
2477+
2478+
if (titleBarStyle !== existingTitleBarStyle) {
2479+
this.showPopup({ type: PopupType.ConfirmRestart })
2480+
}
2481+
}
24682482

24692483
/**
24702484
* Increments either the `repoWithIndicatorClicked` or

app/src/ui/lib/title-bar-style.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/**
2+
* This string enum represents the supported modes for rendering the title bar
3+
* in the app.
4+
*
5+
* - 'native' - Use the default window style and chrome supported by the window
6+
* manager
7+
*
8+
* - 'custom' - Hide the default window style and chrome and display the menu
9+
* provided by GitHub Desktop
10+
*
11+
* This is only available on the Linux build. For other operating systems this
12+
* is not configurable:
13+
*
14+
* - macOS uses the native title bar
15+
* - Windows uses the custom title bar
16+
*/
17+
export type TitleBarStyle = 'native' | 'custom'

0 commit comments

Comments
 (0)