Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add dark mode using flatlaf #4145

Closed

Conversation

Happyholic1203
Copy link

What this commit is about

I integrated a dark mode into Ghidra using flatlaf.

The idea is to introduce a new Look and Feel option for dark mode (flatlaf), and a different set of default color values for dark mode, so:

  • user changes/preferences (color settings in EditTool Options) still take effect in both modes
  • the restore buttons in Tool Options work in both modes (when in dark mode → restore to default restores the colors to dark-mode defaults, not light-mode defaults, and vice versa)
  • all existing Look and Feel themes continue to work as usual

This commit is coded with these principles in mind:

  • default color values are computed only once (at startup), so it has minimum performance impact
  • whenever possible, let flatlaf pick the color values for the dark mode
    • e.g., JPanel knows which color to use for its background when in dark mode, so I'll try to let JPanel pick its colors in dark mode
    • in light mode, everything works as usual
  • if we're unable to let flatlaf pick the color values, a ColorContext interface is introduced to manage all the default color values by computing them once when Ghidra starts up

Most of the colors are borrowed from the ghidra-dark repo (many thanks, Zack!), which I've been using for quite some time.

Why?

There are several great repos that bring dark mode into Ghidra, and I find myself using ghidra-dark the most.
The way it works: it uses a python script to

  • install the flatlaf jar into Ghidra
  • modify the Ghidra launch scripts so flatlaf theme is activated when Ghidra launches
  • configure the color settings in Ghidra

However, some color values in Ghidra aren't configurable, such as the background of graphs and GTable.

This commit aims to fix all that by integrating the spirit of ghidra-dark into Ghidra, and introduce a different set of default color values for those that aren't configurable.

How it looks like

Some of the most commonly used windows look like this:

貼上的影像_2022_4_9_下午5_50

The file chooser:

貼上的影像_2022_4_9_下午5_51

The banner:

貼上的影像_2022_4_9_下午5_51

How to use it

Step 1:
image

Step 2:
貼上的影像_2022_4_9_下午7_44

Step 3: restart Ghidra

That's it!

As you might have noticed, there are 2 new Swing Look And Feel themes:

  • Flat Light: if your macOS uses dark mode, but you want to use Ghidra in light mode, you might want to choose this one (in the Mac OS X theme, the colors in your menu bar and context menu might not look right, if you're using dark mode system-wise)
  • Flat Dark (the dark mode) → choose this one to activate dark mode

Known issues

  • Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerConcepts.html has a hardcoded bgcolor="white" → I guess the html files might be auto-generated (so this might not be the right place to patch), but I didn't look into it
  • Alternating background colors in GTables are gone (in dark mode)
  • Python interpreter auto-completion: too many colors to customize (auto-complete)
  • Hovering over listing display

I have little sense in colors, so I tend not to solve the above problems because they involve a little more sense in colors.

Feedback is welcome

I might not be very responsive, but I'll try my best to answer questions.
There might be better ways to do this, and I'd appreciate any suggestions/insights.
I hope you find this useful. =)

@Happyholic1203
Copy link
Author

#13

@dragonmacher
Copy link
Collaborator

I hope you find this useful. =)

Useful indeed. There is some stirring taking place...

@SiriusED
Copy link

SiriusED commented May 23, 2022

Finally found fully dark theme, just Built the Ghidra assembly with this theme, seems everything is fine and working well. If no bugs found while using I will keep this theme for sure. Thanks for your job.

UPD:
Fount some issue with color, not sure it's Ghidra's setting or not but here is the issue:

When you mouse hover on a Code editor or in Symbol tree you can see a tooltips
image

This black color needs to be changed to some #ddd or #bbb

@ryanmkurtz ryanmkurtz added the Status: Internal This is being tracked internally by the Ghidra team label May 23, 2022
@AttackMac
Copy link

Small thing, noticed that Tip of the day window still had a white box in it. Hard coded to white, link below to line.
https://github.com/NationalSecurityAgency/ghidra/blob/master/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/totd/TipOfTheDayDialog.java#L99

@AttackMac
Copy link

This is a really nitpick thing I noticed while using darkmode and running into UndefinedFunctions.

UndefinedFunction is greyed out in base Ghidra.
image
In dark mode it also greyed out, but,is less readable due to the changes in the coloring fonts. It is especially glaring since everything else in dark mode looks so good.
image

@jjc224
Copy link

jjc224 commented Sep 4, 2022

How do you install this? It's not clear to me. Are we just supposed to use ghidra-dark and rely on the commit to fix any issues, or does Ghidra need to be recompiled with Flatlaf?

ghidra-dark installs, but it breaks things for me on the latest 10.1.5 (Void Linux). I had to remove Ghidra, purge ~/.ghidra, and reinstall. I don't see the two new 'Flat' theme options either, before or after installing Flatlaf/ghidra-dark. I must be doing something wrong.

@dreyes15
Copy link

dreyes15 commented Sep 6, 2022

@jjc224 What I did was clone @Happyholic1203's repo and re-build Ghidra from source. The zip file created after building has the dark mode.

@illusion0001
Copy link

Had this error when trying to do a patch instruction Ctrl+Shift+G, reverting to default theme does not have this issue.

Log (Click to View)
Cannot invoke "java.awt.geom.AffineTransform.getScaleY()" because "t" is null
java.lang.NullPointerException: Cannot invoke "java.awt.geom.AffineTransform.getScaleY()" because "t" is null
	at com.formdev.flatlaf.util.HiDPIUtils.computeTextYCorrection(HiDPIUtils.java:127)
	at com.formdev.flatlaf.util.HiDPIUtils.createGraphicsTextYCorrection(HiDPIUtils.java:191)
	at com.formdev.flatlaf.ui.FlatLabelUI.createGraphicsHTMLTextYCorrection(FlatLabelUI.java:242)
	at com.formdev.flatlaf.ui.FlatLabelUI.paint(FlatLabelUI.java:248)
	at java.desktop/javax.swing.plaf.ComponentUI.update(ComponentUI.java:161)
	at java.desktop/javax.swing.JComponent.paintComponent(JComponent.java:842)
	at ghidra.util.HTMLUtilities$1.paint(HTMLUtilities.java:768)
	at ghidra.util.HTMLUtilities.fromHTML(HTMLUtilities.java:788)
	at docking.DockingErrorDisplay.doDisplayMessage(DockingErrorDisplay.java:123)
	at docking.DockingErrorDisplay.lambda$displayMessage$0(DockingErrorDisplay.java:102)
	at ghidra.util.Swing.runIfSwingOrRunLater(Swing.java:124)
	at docking.DockingErrorDisplay.displayMessage(DockingErrorDisplay.java:102)
	at docking.DockingErrorDisplay.displayWarningMessage(DockingErrorDisplay.java:62)
	at ghidra.util.Msg.showWarn(Msg.java:223)
	at ghidra.app.plugin.core.assembler.PatchInstructionAction.warnLanguage(PatchInstructionAction.java:201)
	at ghidra.app.plugin.core.assembler.PatchInstructionAction.prepare(PatchInstructionAction.java:220)
	at ghidra.app.plugin.core.assembler.AbstractPatchAction.actionPerformed(AbstractPatchAction.java:379)
	at docking.ExecutableAction.execute(ExecutableAction.java:40)
	at docking.action.MultipleKeyAction.actionPerformed(MultipleKeyAction.java:142)
	at java.desktop/javax.swing.SwingUtilities.notifyAction(SwingUtilities.java:1810)
	at docking.KeyBindingOverrideKeyEventDispatcher.actionInProgress(KeyBindingOverrideKeyEventDispatcher.java:219)
	at docking.KeyBindingOverrideKeyEventDispatcher.dispatchKeyEvent(KeyBindingOverrideKeyEventDispatcher.java:117)
	at java.desktop/java.awt.DefaultKeyboardFocusManager.preDispatchKeyEvent(DefaultKeyboardFocusManager.java:1144)
	at java.desktop/java.awt.DefaultKeyboardFocusManager.typeAheadAssertions(DefaultKeyboardFocusManager.java:1020)
	at java.desktop/java.awt.DefaultKeyboardFocusManager.dispatchEvent(DefaultKeyboardFocusManager.java:848)
	at java.desktop/java.awt.Component.dispatchEventImpl(Component.java:4882)
	at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2324)
	at java.desktop/java.awt.Window.dispatchEventImpl(Window.java:2780)
	at java.desktop/java.awt.Component.dispatchEvent(Component.java:4833)
	at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:773)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:722)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:716)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:97)
	at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:746)
	at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:744)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86)
	at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:743)
	at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
	at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)

---------------------------------------------------
Build Date: 2022-Sep-24 1910 UTC
Ghidra Version: 10.1.5
Java Home: D:\jdk-17.0.4.1
JVM Version: Eclipse Adoptium 17.0.4.1
OS: Windows 10 10.0 amd64

@XVilka
Copy link
Contributor

XVilka commented Nov 4, 2022

This needs to be rebased to resolve the conflicts wtih the current code.

@ryanmkurtz ryanmkurtz added this to the 10.3 milestone Nov 16, 2022
@ryanmkurtz ryanmkurtz added the Reason: Internal effort This will be solved internally label Nov 17, 2022
@dragonmacher
Copy link
Collaborator

Thank you for your effort in this PR. We have taken the initial work and used that as inspiration for our new Theming feature.

Commit: e657a70

@ubitux
Copy link

ubitux commented Nov 18, 2022

The tooltip is still off though:

2022-11-18-121113-Ed4iedoo

Also, EditTheme and pressing Esc raises:

Cannot invoke "docking.ActionContext.getComponentProvider()" because "localContext" is null
java.lang.NullPointerException: Cannot invoke "docking.ActionContext.getComponentProvider()" because "localContext" is null
	at docking.action.MultipleKeyAction$ActionData.isMyProvider(MultipleKeyAction.java:355)
	at docking.action.MultipleKeyAction.getValidContextActions(MultipleKeyAction.java:168)
	at docking.action.MultipleKeyAction.getDialogActions(MultipleKeyAction.java:297)
	at docking.action.MultipleKeyAction.getActionsForCurrentOrDefaultContext(MultipleKeyAction.java:278)
	at docking.action.MultipleKeyAction.geValidKeyBindingPrecedence(MultipleKeyAction.java:259)
	at docking.KeyBindingOverrideKeyEventDispatcher.getValidKeyBindingPrecedence(KeyBindingOverrideKeyEventDispatcher.java:171)
	at docking.KeyBindingOverrideKeyEventDispatcher.dispatchKeyEvent(KeyBindingOverrideKeyEventDispatcher.java:145)
	at java.desktop/java.awt.DefaultKeyboardFocusManager.preDispatchKeyEvent(DefaultKeyboardFocusManager.java:1141)
	at java.desktop/java.awt.DefaultKeyboardFocusManager.typeAheadAssertions(DefaultKeyboardFocusManager.java:1020)
	at java.desktop/java.awt.DefaultKeyboardFocusManager.dispatchEvent(DefaultKeyboardFocusManager.java:848)
	at java.desktop/java.awt.Component.dispatchEventImpl(Component.java:4876)
	at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2324)
	at java.desktop/java.awt.Window.dispatchEventImpl(Window.java:2780)
	at java.desktop/java.awt.Component.dispatchEvent(Component.java:4827)
	at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:775)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:720)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:714)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:97)
	at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:747)
	at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:745)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86)
	at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:744)
	at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
	at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)

---------------------------------------------------
Build Date: 2022-Nov-18 1200 CET
Ghidra Version: 10.3
Java Home: /usr/lib/jvm/java-19-openjdk
JVM Version: N/A 19.0.1
OS: Linux 6.0.8-arch1-1 amd64

@dragonmacher
Copy link
Collaborator

I believe the exception had been fixed. I will add the tooltip to the list of items that still need updating.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature: GUI Reason: Internal effort This will be solved internally Status: Internal This is being tracked internally by the Ghidra team
Projects
None yet
Development

Successfully merging this pull request may close these issues.