Skip to content

Commit

Permalink
Add an option to use the Windows title bar (#912)
Browse files Browse the repository at this point in the history
Co-authored-by: Brendan Forster <github@brendanforster.com>
  • Loading branch information
mon-jai and shiftkey committed May 28, 2024
1 parent a0b1174 commit 5775c01
Show file tree
Hide file tree
Showing 17 changed files with 291 additions and 29 deletions.
4 changes: 4 additions & 0 deletions app/src/lib/app-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { WindowState } from './window-state'
import { Shell } from './shells'

import { ApplicableTheme, ApplicationTheme } from '../ui/lib/application-theme'
import { TitleBarStyle } from '../ui/lib/title-bar-style'
import { IAccountRepositories } from './stores/api-repositories-store'
import { ManualConflictResolution } from '../models/manual-conflict-resolution'
import { Banner } from '../models/banner'
Expand Down Expand Up @@ -278,6 +279,9 @@ export interface IAppState {
/** The currently applied appearance (aka theme) */
readonly currentTheme: ApplicableTheme

/** The selected title bar style for the application */
readonly titleBarStyle: TitleBarStyle

/**
* A map keyed on a user account (GitHub.com or GitHub Enterprise)
* containing an object with repositories that the authenticated
Expand Down
48 changes: 48 additions & 0 deletions app/src/lib/get-title-bar-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { writeFile } from 'fs/promises'
import { existsSync, readFileSync } from 'fs'
import { join } from 'path'
import { app } from 'electron'
import { TitleBarStyle } from '../ui/lib/title-bar-style'

export type TitleBarConfig = {
titleBarStyle: TitleBarStyle
}

let cachedTitleBarConfig: TitleBarConfig | null = null

// The function has to be synchronous,
// since we need its return value to create electron BrowserWindow
export function readTitleBarConfigFileSync(): TitleBarConfig {
if (cachedTitleBarConfig) {
return cachedTitleBarConfig
}

const titleBarConfigPath = getTitleBarConfigPath()

if (existsSync(titleBarConfigPath)) {
const storedTitleBarConfig = JSON.parse(
readFileSync(titleBarConfigPath, 'utf8')
)

if (
storedTitleBarConfig.titleBarStyle === 'native' ||
storedTitleBarConfig.titleBarStyle === 'custom'
) {
cachedTitleBarConfig = storedTitleBarConfig
}
}

// Cache the default value if the config file is not found, or if it contains an invalid value.
if (cachedTitleBarConfig == null) {
cachedTitleBarConfig = { titleBarStyle: 'native' }
}

return cachedTitleBarConfig
}

export function saveTitleBarConfigFile(config: TitleBarConfig) {
return writeFile(getTitleBarConfigPath(), JSON.stringify(config), 'utf8')
}

const getTitleBarConfigPath = () =>
join(app.getPath('userData'), '.title-bar-config')
4 changes: 4 additions & 0 deletions app/src/lib/ipc-shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Architecture } from './get-architecture'
import { EndpointToken } from './endpoint-token'
import { PathType } from '../ui/lib/app-proxy'
import { ThemeSource } from '../ui/lib/theme-source'
import { TitleBarStyle } from '../ui/lib/title-bar-style'
import { DesktopNotificationPermission } from 'desktop-notifications/dist/notification-permission'
import { NotificationCallback } from 'desktop-notifications/dist/notification-callback'
import { DesktopAliveEvent } from './stores/alive-store'
Expand Down Expand Up @@ -65,6 +66,7 @@ export type RequestChannels = {
blur: () => void
'update-accounts': (accounts: ReadonlyArray<EndpointToken>) => void
'quit-and-install-updates': () => void
'restart-app': () => void
'quit-app': () => void
'minimize-window': () => void
'maximize-window': () => void
Expand Down Expand Up @@ -123,6 +125,8 @@ export type RequestResponseChannels = {
'should-use-dark-colors': () => Promise<boolean>
'save-guid': (guid: string) => Promise<void>
'get-guid': () => Promise<string>
'save-title-bar-style': (titleBarStyle: TitleBarStyle) => Promise<void>
'get-title-bar-style': () => Promise<TitleBarStyle>
'show-notification': (
title: string,
body: string,
Expand Down
15 changes: 15 additions & 0 deletions app/src/lib/stores/app-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ import {
getPersistedThemeName,
setPersistedTheme,
} from '../../ui/lib/application-theme'
import { TitleBarStyle } from '../../ui/lib/title-bar-style'
import {
getAppMenu,
getCurrentWindowState,
Expand All @@ -91,6 +92,8 @@ import {
sendWillQuitEvenIfUpdatingSync,
quitApp,
sendCancelQuittingSync,
saveTitleBarStyle,
getTitleBarStyle,
} from '../../ui/main-process-proxy'
import {
API,
Expand Down Expand Up @@ -528,6 +531,7 @@ export class AppStore extends TypedBaseStore<IAppState> {
private selectedBranchesTab = BranchesTab.Branches
private selectedTheme = ApplicationTheme.System
private currentTheme: ApplicableTheme = ApplicationTheme.Light
private titleBarStyle: TitleBarStyle = 'native'

private useWindowsOpenSSH: boolean = false

Expand Down Expand Up @@ -1027,6 +1031,7 @@ export class AppStore extends TypedBaseStore<IAppState> {
selectedBranchesTab: this.selectedBranchesTab,
selectedTheme: this.selectedTheme,
currentTheme: this.currentTheme,
titleBarStyle: this.titleBarStyle,
apiRepositories: this.apiRepositoriesStore.getState(),
useWindowsOpenSSH: this.useWindowsOpenSSH,
showCommitLengthWarning: this.showCommitLengthWarning,
Expand Down Expand Up @@ -2218,6 +2223,8 @@ export class AppStore extends TypedBaseStore<IAppState> {
this.emitUpdate()
})

this.titleBarStyle = await getTitleBarStyle()

this.lastThankYou = getObject<ILastThankYou>(lastThankYouKey)

this.pullRequestSuggestedNextAction =
Expand Down Expand Up @@ -6564,6 +6571,14 @@ export class AppStore extends TypedBaseStore<IAppState> {
return Promise.resolve()
}

/**
* Set the title bar style for the application
*/
public _setTitleBarStyle(titleBarStyle: TitleBarStyle) {
this.titleBarStyle = titleBarStyle
return saveTitleBarStyle(titleBarStyle)
}

public async _resolveCurrentEditor() {
const match = await findEditorOrDefault(this.selectedExternalEditor)
const resolvedExternalEditor = match != null ? match.editor : null
Expand Down
4 changes: 4 additions & 0 deletions app/src/main-process/app-window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
getWindowState,
registerWindowStateChangedEvents,
} from '../lib/window-state'
import { readTitleBarConfigFileSync } from '../lib/get-title-bar-config'
import { MenuEvent } from './menu'
import { URLActionType } from '../lib/parse-app-url'
import { ILaunchStats } from '../lib/stats'
Expand Down Expand Up @@ -75,6 +76,9 @@ export class AppWindow {
} else if (__WIN32__) {
windowOptions.frame = false
} else if (__LINUX__) {
if (readTitleBarConfigFileSync().titleBarStyle === 'custom') {
windowOptions.frame = false
}
windowOptions.icon = join(__dirname, 'static', 'logos', '512x512.png')

// relax restriction here for users trying to run app at a small
Expand Down
19 changes: 19 additions & 0 deletions app/src/main-process/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ import {
} from '../lib/get-architecture'
import { buildSpellCheckMenu } from './menu/build-spell-check-menu'
import { getMainGUID, saveGUIDFile } from '../lib/get-main-guid'
import {
readTitleBarConfigFileSync,
saveTitleBarConfigFile,
} from '../lib/get-title-bar-config'
import {
getNotificationsPermission,
requestNotificationsPermission,
Expand Down Expand Up @@ -509,6 +513,11 @@ app.on('ready', () => {
mainWindow?.quitAndInstallUpdate()
)

ipcMain.on('restart-app', () => {
app.relaunch()
app.exit()
})

ipcMain.on('quit-app', () => app.quit())

ipcMain.on('minimize-window', () => mainWindow?.minimizeWindow())
Expand Down Expand Up @@ -694,6 +703,16 @@ app.on('ready', () => {

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

ipcMain.handle(
'get-title-bar-style',
async () => readTitleBarConfigFileSync().titleBarStyle
)

ipcMain.handle(
'save-title-bar-style',
async (_, titleBarStyle) => await saveTitleBarConfigFile({ titleBarStyle })
)

ipcMain.handle('show-notification', async (_, title, body, userInfo) =>
showNotification(title, body, userInfo)
)
Expand Down
2 changes: 2 additions & 0 deletions app/src/models/popup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export enum PopupType {
UnknownAuthors = 'UnknownAuthors',
ConfirmRepoRulesBypass = 'ConfirmRepoRulesBypass',
TestIcons = 'TestIcons',
ConfirmRestart = 'ConfirmRestart',
}

interface IBasePopup {
Expand Down Expand Up @@ -425,5 +426,6 @@ export type PopupDetail =
| {
type: PopupType.TestIcons
}
| { type: PopupType.ConfirmRestart }

export type Popup = IBasePopup & PopupDetail
19 changes: 12 additions & 7 deletions app/src/ui/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ import { Welcome } from './welcome'
import { AppMenuBar } from './app-menu'
import { UpdateAvailable, renderBanner } from './banners'
import { Preferences } from './preferences'
import { ConfirmRestart } from './preferences/confirm-restart'
import { RepositorySettings } from './repository-settings'
import { AppError } from './app-error'
import { MissingRepository } from './missing-repository'
Expand Down Expand Up @@ -1463,8 +1464,8 @@ export class App extends React.Component<IAppProps, IAppState> {
* on Windows.
*/
private renderAppMenuBar() {
// We only render the app menu bar on Windows
if (!__WIN32__) {
// We do not render the app menu bar on macOS
if (__DARWIN__) {
return null
}

Expand Down Expand Up @@ -1515,22 +1516,22 @@ export class App extends React.Component<IAppProps, IAppState> {
this.state.currentFoldout &&
this.state.currentFoldout.type === FoldoutType.AppMenu

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

// When we're in full-screen mode on Windows we only need to render
// the title bar when the menu bar is active. On other platforms we
// never render the title bar while in full-screen mode.
if (inFullScreen) {
if (!__WIN32__ || !menuBarActive) {
if (__DARWIN__ || !menuBarActive) {
return null
}
}

const showAppIcon = __WIN32__ && !this.state.showWelcomeFlow
const showAppIcon = !__DARWIN__ && !this.state.showWelcomeFlow
const inWelcomeFlow = this.state.showWelcomeFlow
const inNoRepositoriesView = this.inNoRepositoriesViewState()

Expand Down Expand Up @@ -1723,6 +1724,7 @@ export class App extends React.Component<IAppProps, IAppState> {
onDismissed={onPopupDismissedFn}
selectedShell={this.state.selectedShell}
selectedTheme={this.state.selectedTheme}
titleBarStyle={this.state.titleBarStyle}
repositoryIndicatorsEnabled={this.state.repositoryIndicatorsEnabled}
onOpenFileInExternalEditor={this.openFileInExternalEditor}
underlineLinks={this.state.underlineLinks}
Expand Down Expand Up @@ -2640,6 +2642,9 @@ export class App extends React.Component<IAppProps, IAppState> {
/>
)
}
case PopupType.ConfirmRestart: {
return <ConfirmRestart onDismissed={onPopupDismissedFn} />
}
default:
return assertNever(popup, `Unknown popup type: ${popup}`)
}
Expand Down
15 changes: 15 additions & 0 deletions app/src/ui/dispatcher/dispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ import { TipState, IValidBranch } from '../../models/tip'
import { Banner, BannerType } from '../../models/banner'

import { ApplicationTheme } from '../lib/application-theme'
import { TitleBarStyle } from '../lib/title-bar-style'
import { installCLI } from '../lib/install-cli'
import {
executeMenuItem,
Expand Down Expand Up @@ -2441,6 +2442,20 @@ export class Dispatcher {
return this.appStore._setSelectedTheme(theme)
}

/**
* Set the title bar style for the application
*/
public async setTitleBarStyle(titleBarStyle: TitleBarStyle) {
const existingState = this.appStore.getState()
const { titleBarStyle: existingTitleBarStyle } = existingState

await this.appStore._setTitleBarStyle(titleBarStyle)

if (titleBarStyle !== existingTitleBarStyle) {
this.showPopup({ type: PopupType.ConfirmRestart })
}
}

/**
* Increments either the `repoWithIndicatorClicked` or
* the `repoWithoutIndicatorClicked` metric
Expand Down
17 changes: 17 additions & 0 deletions app/src/ui/lib/title-bar-style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* This string enum represents the supported modes for rendering the title bar
* in the app.
*
* - 'native' - Use the default window style and chrome supported by the window
* manager
*
* - 'custom' - Hide the default window style and chrome and display the menu
* provided by GitHub Desktop
*
* This is only available on the Linux build. For other operating systems this
* is not configurable:
*
* - macOS uses the native title bar
* - Windows uses the custom title bar
*/
export type TitleBarStyle = 'native' | 'custom'
7 changes: 7 additions & 0 deletions app/src/ui/main-process-proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,9 @@ export const checkForUpdates = invokeProxy('check-for-updates', 1)
/** Tell the main process to quit the app and install updates */
export const quitAndInstallUpdate = sendProxy('quit-and-install-updates', 0)

/** Tell the main process to restart the app */
export const restartApp = sendProxy('restart-app', 0)

/** Tell the main process to quit the app */
export const quitApp = sendProxy('quit-app', 0)

Expand Down Expand Up @@ -379,6 +382,10 @@ export const showOpenDialog = invokeProxy('show-open-dialog', 1)
export const saveGUID = invokeProxy('save-guid', 1)
export const getGUID = invokeProxy('get-guid', 0)

/** Tell the main process read/save the the title bar style */
export const saveTitleBarStyle = invokeProxy('save-title-bar-style', 1)
export const getTitleBarStyle = invokeProxy('get-title-bar-style', 0)

/** Tell the main process to show a notification */
export const showNotification = invokeProxy('show-notification', 3)

Expand Down
Loading

0 comments on commit 5775c01

Please sign in to comment.