diff options
author | Hossein <hossein@libreoffice.org> | 2021-10-20 10:55:25 +0200 |
---|---|---|
committer | Ilmari Lauhakangas <ilmari.lauhakangas@libreoffice.org> | 2021-10-22 08:09:26 +0200 |
commit | dc984e7fed26ed26a2cafecb0c5b27feca56bfe2 (patch) | |
tree | e06fb34bb7b6e9e5ba4c022c2474a0f6333be11a /vcl | |
parent | 58a3e2a6fad68d8d5e6091e063c0b26e3608f998 (diff) |
Converted VCL documentation to Markdown
Converted VCL documentation to Markdown format:
* README.md Main README file (Added link to other md files)
* README.vars Environment Variables in VCL
* README.GDIMetaFile GDIMetaFile class
* README.lifecycle Understanding Transitional VCL Lifecycle
* README.scheduler VCL Scheduler
Change-Id: I15d973b14005a4944f7b5104c5c80184e1083203
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/123867
Tested-by: Jenkins
Reviewed-by: Ilmari Lauhakangas <ilmari.lauhakangas@libreoffice.org>
Diffstat (limited to 'vcl')
-rw-r--r-- | vcl/README.GDIMetaFile | 242 | ||||
-rw-r--r-- | vcl/README.lifecycle | 440 | ||||
-rw-r--r-- | vcl/README.md | 8 | ||||
-rw-r--r-- | vcl/README.scheduler | 154 | ||||
-rw-r--r-- | vcl/README.vars | 130 |
5 files changed, 510 insertions, 464 deletions
diff --git a/vcl/README.GDIMetaFile b/vcl/README.GDIMetaFile index 98be38d086d8..386a91e2adff 100644 --- a/vcl/README.GDIMetaFile +++ b/vcl/README.GDIMetaFile @@ -1,180 +1,171 @@ -GDIMetaFile class -================= +# GDIMetaFile class -The GDIMetaFile class reads, writes, manipulates and replays metafiles via the VCL module. +The `GDIMetaFile` class reads, writes, manipulates and replays metafiles via the +`VCL` module. -A typical use case is to initialize a new GDIMetaFile, open the actual stored metafile and -read it in via GDIMetaFile::Read( aIStream ). This reads in the metafile into the GDIMetafile -object - it can read in an old-style VCLMTF metafile (back in the days that Microsoft didn't -document the metafile format this was used), as well as EMF+ files - and adds them to a list -(vector) of MetaActions. You can also populate your own GDIMetaFile via AddAction(), -RemoveAction(), ReplaceAction(), etc. +A typical use case is to initialize a new `GDIMetaFile`, open the actual stored +metafile and read it in via `GDIMetaFile::Read( aIStream )`. This reads in the +metafile into the `GDIMetafile` object - it can read in an old-style `VCLMTF` +metafile (back in the days that Microsoft didn't document the metafile format +this was used), as well as EMF+ files - and adds them to a list (vector) of +`MetaActions`. You can also populate your own `GDIMetaFile` via `AddAction()`, +`RemoveAction()`, `ReplaceAction()`, etc. -Once the GDIMetafile object is read to be used, you can "play" the metafile, pause it, wind -forward or rewind the metafile. The metafile can be moved, scaled, rotated and clipped, as -well have the colours adjusted or replaced, or even made monochrome. +Once the `GDIMetafile` object is read to be used, you can "play" the metafile, +"pause" it, "wind forward" or "rewind" the metafile. The metafile can be moved, +scaled, rotated and clipped, as well have the colours adjusted or replaced, or +even made monochrome. -The GDIMetafile can be used to get an OutputDevice's metafile via the Linker() and Record() -functions. +The GDIMetafile can be used to get an `OutputDevice`'s metafile via the +`Linker()` and `Record()` functions. +## Using GDIMetafile -Using GDIMetafile ------------------ +First, create a new `GDIMetafile`, this can be done via the default constructor. +It can then be constructed manually, or you can use `Record()` on an +`OutputDevice` to populate the `GDIMetaFile`, or of course you can read it from +file with `Read()`. From here you can then elect to manipulate the metafile, or +play it back to another `GDIMetafile` or to an `OutputDevice` via `Play()`. To +store the file, use `Write()`. -First, create a new GDIMetafile, this can be done via the default constructor. It can then -be constructed manually, or you can use Record() on an OutputDevice to populate the -GDIMetaFile, or of course you can read it from file with Read(). From here you can then -elect to manipulate the metafile, or play it back to another GDIMetafile or to an -OutputDevice via Play(). To store the file, use Write(). +### CONSTRUCTORS AND DESTRUCTORS -CONSTRUCTORS AND DESTRUCTORS +- `GDIMetaFile` +- `GDIMetaFile( cosnt GDIMetaFile& rMtf )` - copy constructor +- `~GDIMetaFile` -- GDIMetaFile -- GDIMetaFile( cosnt GDIMetaFile& rMtf ) - copy constructor -- ~GDIMetaFile +### OPERATORS +- `operator =` +- `operator ==` +- `operator !=` -OPERATORS +### RECORDING AND PLAYBACK FUNCTIONS -- operator = -- operator == -- operator != - - -RECORDING AND PLAYBACK FUNCTIONS - -- Play(GDIMetaFile&, size_t) - play back metafile into another metafile up - to position -- Play(OutputDevice*, size_t) - play back metafile into OutputDevice up to - position -- Play(OutputDevice*, Point, Size, size_t) - play back metafile into OutputDevice at a - particular location on the OutputDevice, up +- `Play(GDIMetaFile&, size_t)` - play back metafile into another + metafile up to position +- `Play(OutputDevice*, size_t)` - play back metafile into + `OutputDevice` up to position +- `Play(OutputDevice*, Point, Size, size_t)` - play back metafile into + `OutputDevice` at a particular + location on the `OutputDevice`, up to the position in the metafile -- Pause - pauses or continues the playback -- IsPause -- Stop - stop playback fully -- WindStart - windback to start of the metafile -- windPrev - windback one record -- GetActionSize - get the number of records in the metafile - +- `Pause` - pauses or continues the playback +- `IsPause` +- `Stop` - stop playback fully +- `WindStart` - windback to start of the metafile +- `windPrev` - windback one record +- `GetActionSize` - get the number of records in the + metafile -METAFILE RECORD FUNCTIONS -- FirstAction - get the first metafile record -- NextAction - get the next metafile record from the - current position -- GetAction(size_t) - get the metafile record at location in file -- GetCurAction - get the current metafile record -- AddAction(MetaAction*) - appends a metafile record -- AddAction(MetaAction*, size_t) - adds a metafile record to a particular - location in the file -- RemoveAction - removes record at file location -- Clear - first stops if recording, then removes all - metafile records -- push_back - pushes back, basically a thin wrapper to the - metafile record list +### METAFILE RECORD FUNCTIONS +- `FirstAction` - get the first metafile record +- `NextAction` - get the next metafile record from + the current position +- `GetAction(size_t)` - get the metafile record at + location in file +- `GetCurAction` - get the current metafile record +- `AddAction(MetaAction*)` - appends a metafile record +- `AddAction(MetaAction*, size_t)` - adds a metafile record to a + particular location in the file +- `RemoveAction` - removes record at file location +- `Clear` - first stops if recording, then + removes all metafile records +- `push_back` - pushes back, basically a thin + wrapper to the metafile record list -READ AND WRITING -- Read -- Write -- GetChecksum -- GetSizeBytes +### READ AND WRITING +- `Read` +- `Write` +- `GetChecksum` +- `GetSizeBytes` -DISPLACEMENT FUNCTIONS -- Move( long nX, long nX) -- Move( long nX, long nX, long nDPIX, long nDPIY ) - Move method getting specifics how to - handle MapMode( MapUnit::MapPixel ) +### DISPLACEMENT FUNCTIONS +- `Move( long nX, long nX)` +- `Move( long nX, long nX, long nDPIX, long nDPIY )` - Move method getting + specifics how to handle + MapMode( MapUnit::MapPixel ) -TRANSFORMATION FUNCTIONS -- Scale( double fScaleX, double fScaleY ) -- Scale( const Fraction& rScaleX, const Fraction& rScaleY ) -- Mirror -- Rotate( long nAngle10 ) -- Clip( const Rectangle& ) +### TRANSFORMATION FUNCTIONS +- `Scale( double fScaleX, double fScaleY )` +- `Scale( const Fraction& rScaleX, const Fraction& rScaleY )` +- `Mirror` +- `Rotate( long nAngle10 )` +- `Clip( const Rectangle& )` -COLOR ADJUSTMENT FUNCTIONS -- Adjust - change luminance, contrast, gamma and RGB via a - percentage -- Convert - colour conversion -- ReplaceColors -- GetMonochromeMtf +### COLOR ADJUSTMENT FUNCTIONS +- `Adjust` - change luminance, contrast, + gamma and RGB via a percentage +- `Convert` - colour conversion +- `ReplaceColors` +- `GetMonochromeMtf` -Related classes ---------------- +## Related classes -MetaAction: a base class used by all records. It implements a command-like pattern, and also -acts as a prototype for other actions. +`MetaAction`: a base class used by all records. It implements a command-like +pattern, and also acts as a prototype for other actions. +### CONSTRUCTORS AND DESTRUCTORS -CONSTRUCTORS AND DESTRUCTORS - -- MetaAction() - default constructor, sets mnRefCount to 1 and +- `MetaAction()` - default constructor, sets mnRefCount to 1 and mnType, in this case MetaActionType::NONE -- MetaAction(sal_uInt16 nType) - virtual constructor, sets mnType to nType, and +- `MetaAction(sal_uInt16 nType)` - virtual constructor, sets mnType to nType, and mnRefCount to 1 -- ~MetaAction - +- `~MetaAction` -COMMAND FUNCTION +### COMMAND FUNCTION -- Execute(OutputDevice*) - execute the functionality of the record to the +- `Execute(OutputDevice*)` - execute the functionality of the record to the OutputDevice. Part of command pattern. +### FACTORY FUNCTION +- `Clone()` - prototype clone function -FACTORY FUNCTION - -- Clone() - prototype clone function - +### MANIPULATION FUNCTIONS -MANIPULATION FUNCTIONS +- `Move(long nHorzMove, long nVerMove)` +- `Scale(double fScaleX, double fScaleY)` -- Move(long nHorzMove, long nVerMove) -- Scale(double fScaleX, double fScaleY) +### READ AND WRITE FUNCTIONS -READ AND WRITE FUNCTIONS - -- Read -- Write -- ReadMetaAction - a static function, only used to determine which +- `Read` +- `Write` +- `ReadMetaAction` - a static function, only used to determine which MetaAction to call on to read the record, which means that this is the function that must be used. +### INTROSPECTIVE FUNCTIONS -INTROSPECTIVE FUNCTIONS - -- GetType - - - -A note about MetaCommentAction: -------------------------------- - -So this class is the most interesting - a comment record is what is used to extended metafiles, to -make what we call an "Enhanced Metafile". This basically gets the OutputDevice's connect metafile -and adds the record via this when it runs Execute(). It doesn't actually do anything else, unlike -other MetaActions which invoke functions from OutputDevice. And if there is no connect metafile in -OutputDevice, then it just does nothing at all in Execute. Everything else works as normal (Read, -Write, etc). - +- `GetType` +### A note about MetaCommentAction -Basic pseudocode ----------------- +So this class is the most interesting - a comment record is what is used to +extended metafiles, to make what we call an "Enhanced Metafile". This basically +gets the `OutputDevice`'s connect metafile and adds the record via this when it +runs `Execute()`. It doesn't actually do anything else, unlike other +`MetaAction`s which invoke functions from `OutputDevice`. And if there is no +connect metafile in `OutputDevice`, then it just does nothing at all in Execute. +Everything else works as normal (Read, Write, etc). -The following illustrates an exceptionally basic and incomplete implementation of how to use -GDIMetafile. An example can be found at vcl/workben/mtfdemo.cxx +## Basic pseudocode +The following illustrates an exceptionally basic and incomplete implementation +of how to use `GDIMetafile`. An example can be found at +`vcl/workben/mtfdemo.cxx` +``` DemoWin::Paint() { // assume that VCL has been initialized and a new application created @@ -188,3 +179,4 @@ DemoWin::Paint() pMtf->Play(pWin); } +``` diff --git a/vcl/README.lifecycle b/vcl/README.lifecycle index 9933e40f0955..8b0e5de2d0fd 100644 --- a/vcl/README.lifecycle +++ b/vcl/README.lifecycle @@ -1,22 +1,32 @@ -** Understanding transitional VCL lifecycle ** +# Understanding Transitional VCL Lifecycle ----------- How it used to look ---------- +## How it used to look + +All VCL classes were explicitly lifecycle managed; so you would do: + +``` +Dialog aDialog(...); // old - on stack allocation +aDialog.Execute(...); +``` - All VCL classes were explicitly lifecycle managed; so you would -do: - Dialog aDialog(...); // old - on stack allocation - aDialog.Execute(...); or: - Dialog *pDialog = new Dialog(...); // old - manual heap allocation - pDialog->Execute(...); - delete pDialog; + +``` +Dialog *pDialog = new Dialog(...); // old - manual heap allocation +pDialog->Execute(...); +delete pDialog; +``` + or: - std::shared_ptr<Dialog> xDialog(new pDialog()); // old - xDialog->Execute(...); - // depending who shared the ptr this would be freed sometime - In several cases this lead to rather unpleasant code, when -various shared_ptr wrappers were used, the lifecycle was far less than +``` +std::shared_ptr<Dialog> xDialog(new pDialog()); // old +xDialog->Execute(...); +// depending who shared the ptr this would be freed sometime +``` + +In several cases this lead to rather unpleasant code, when +various `shared_ptr` wrappers were used, the lifecycle was far less than obvious. Where controls were wrapped by other ref-counted classes - such as UNO interfaces, which were also used by native Window pointers, the lifecycle became extremely opaque. In addition VCL had @@ -24,294 +34,324 @@ significant issues with re-enterancy and event emission - adding various means such as DogTags to try to detect destruction of a window between calls: - ImplDelData aDogTag( this ); // 'orrible old code - Show( true, ShowFlags::NoActivate ); - if( !aDogTag.IsDead() ) // did 'this' go invalid yet ? - Update(); +``` +ImplDelData aDogTag( this ); // 'orrible old code +Show( true, ShowFlags::NoActivate ); +if( !aDogTag.IsDead() ) // did 'this' go invalid yet ? + Update(); +``` - Unfortunately use of such protection is/was ad-hoc, and far +Unfortunately use of such protection is/was ad-hoc, and far from uniform, despite the prevalence of such potential problems. - When a lifecycle problem was hit, typically it would take the +When a lifecycle problem was hit, typically it would take the form of accessing memory that had been freed, and contained garbage due to lingering pointers to freed objects. ----------- Where we are now: ---------- +## Where we are now - To fix this situation we now have a VclPtr - which is a smart - reference-counting pointer (include/vcl/vclptr.hxx) which is - designed to look and behave -very- much like a normal pointer - to reduce code-thrash. VclPtr is used to wrap all OutputDevice - derived classes thus: +To fix this situation we now have a `VclPtr` - which is a smart +reference-counting pointer (`include/vcl/vclptr.hxx`) which is +designed to look and behave -very- much like a normal pointer +to reduce code-thrash. `VclPtr` is used to wrap all `OutputDevice` +derived classes thus: - VclPtr<Dialog> pDialog( new Dialog( ... ), SAL_NO_ACQUIRE ); - ... - pDialog.disposeAndClear(); +``` +VclPtr<Dialog> pDialog( new Dialog( ... ), SAL_NO_ACQUIRE ); +... +pDialog.disposeAndClear(); +``` - However - while the VclPtr reference count controls the - lifecycle of the Dialog object, it is necessary to be able to - break reference count cycles. These are extremely common in - widget hierarchies as each widget holds (smart) pointers to - its parents and also its children. +However - while the `VclPtr` reference count controls the +lifecycle of the Dialog object, it is necessary to be able to +break reference count cycles. These are extremely common in +widget hierarchies as each widget holds (smart) pointers to +its parents and also its children. - Thus - all previous 'delete' calls are replaced with 'dispose' - method calls: +Thus - all previous `delete` calls are replaced with `dispose` +method calls: -** What is dispose ? +## What is dispose ? - Dispose is defined to be a method that releases all references - that an object holds - thus allowing their underlying - resources to be released. However - in this specific case it - also releases all backing graphical resources. In practical - terms, all destructor functionality has been moved into - 'dispose' methods, in order to provide a minimal initial - behavioral change. +Dispose is defined to be a method that releases all references +that an object holds - thus allowing their underlying +resources to be released. However - in this specific case it +also releases all backing graphical resources. In practical +terms, all destructor functionality has been moved into +`dispose` methods, in order to provide a minimal initial +behavioral change. - As such a VclPtr can have three states: +As such a `VclPtr` can have three states: - VclPtr<PushButton> pButton; - ... - assert (pButton == nullptr || !pButton); // null - assert (pButton && !pButton->isDisposed()); // alive - assert (pButton && pButton->isDisposed()); // disposed +``` +VclPtr<PushButton> pButton; +... +assert (pButton == nullptr || !pButton); // null +assert (pButton && !pButton->isDisposed()); // alive +assert (pButton && pButton->isDisposed()); // disposed +``` -** ScopedVclPtr - making disposes easier +## `ScopedVclPtr` - making disposes easier - While replacing existing code with new, it can be a bit - tiresome to have to manually add 'disposeAndClear()' - calls to VclPtr<> instances. +While replacing existing code with new, it can be a bit +tiresome to have to manually add `disposeAndClear()` +calls to `VclPtr<>` instances. - Luckily it is easy to avoid that with a ScopedVclPtr which - does this for you when it goes out of scope. +Luckily it is easy to avoid that with a `ScopedVclPtr` which +does this for you when it goes out of scope. -** One extra gotcha - an initial reference-count of 1 +## One extra gotcha - an initial reference-count of 1 - In the normal world of love and sanity, eg. creating UNO - objects, the objects start with a ref-count of zero. Thus - the first reference is always taken after construction by - the surrounding smart pointer. +In the normal world of love and sanity, eg. creating UNO +objects, the objects start with a ref-count of zero. Thus +the first reference is always taken after construction by +the surrounding smart pointer. - Unfortunately, the existing VCL code is somewhat tortured, - and does a lot of reference and de-reference action on the - class -during- construction. This forces us to construct with - a reference of 1 - and to hand that into the initial smart - pointer with a SAL_NO_ACQUIRE. +Unfortunately, the existing VCL code is somewhat tortured, +and does a lot of reference and de-reference action on the +class -during- construction. This forces us to construct with +a reference of 1 - and to hand that into the initial smart +pointer with a `SAL_NO_ACQUIRE`. - To make this easier, we have 'Instance' template wrappers - that make this apparently easier, by constructing the - pointer for you. +To make this easier, we have `Instance` template wrappers +that make this apparently easier, by constructing the +pointer for you. -** How does my familiar code change ? +### How does my familiar code change ? - Lets tweak the exemplary code above to fit the new model: +Lets tweak the exemplary code above to fit the new model: +``` - Dialog aDialog(... dialog params ... ); - aDialog.Execute(...); + ScopedVclPtrInstance<Dialog> pDialog(... dialog params ... ); + pDialog->Execute(...); // VclPtr behaves much like a pointer +``` or: + +``` - Dialog *pDialog = new Dialog(... dialog params ...); + VclPtrInstance<Dialog> pDialog(... dialog params ...); pDialog->Execute(...); - delete pDialog; + pDialog.disposeAndClear(); // done manually - replaces a delete +``` + or: + +``` - std::shared_ptr<Dialog> xDialog(new Dialog(...)); + ScopedVclPtrInstance<Dialog> xDialog(...); xDialog->Execute(...); + // depending how shared_ptr was shared perhaps + // someone else gets a VclPtr to xDialog +``` + or: + +``` - VirtualDevice aDev; + ScopedVclPtrInstance<VirtualDevice> pDev; +``` - Other things that are changed are these: +Other things that are changed are these: +``` - pButton = new PushButton(NULL); + pButton = VclPtr<PushButton>::Create(nullptr); ... - vcl::Window *pWindow = new PushButton(NULL); + VclPtr<vcl::Window> pWindow; + pWindow.reset(VclPtr<PushButton>::Create(nullptr)); +``` -** Why are these 'disposeOnce' calls in destructors ? +### Why are these `disposeOnce` calls in destructors? - This is an interim measure while we are migrating, such that - it is possible to delete an object conventionally and ensure - that its dispose method gets called. In the 'end' we would - instead assert that a Window has been disposed in its - destructor, and elide these calls. +This is an interim measure while we are migrating, such that +it is possible to delete an object conventionally and ensure +that its dispose method gets called. In the 'end' we would +instead assert that a Window has been disposed in its +destructor, and elide these calls. - As the object's vtable is altered as we go down the - destruction process, and we want to call the correct dispose - methods we need this disposeOnce(); call for the interim in - every destructor. This is enforced by a clang plugin. +As the object's vtable is altered as we go down the +destruction process, and we want to call the correct dispose +methods we need this `disposeOnce();` call for the interim in +every destructor. This is enforced by a clang plugin. - The plus side of disposeOnce is that the mechanics behind it - ensure that a dispose() method is only called a single time, - simplifying their implementation. +The plus side of disposeOnce is that the mechanics behind it +ensure that a `dispose()` method is only called a single time, +simplifying their implementation. ----------- Who owns & disposes what ? ---------- +### Who owns & disposes what? - Window sub-classes tend to create their widgets in one of two +Window sub-classes tend to create their widgets in one of two ways and often both. - 1. Derive from VclBuilderContainer. The VclBuilder then owns - many of the sub-windows, which are fetched by a 'get' - method into local variables often in constructors eg. - - VclPtr<PushButton> mpButton; // in the class - , get(mpButton, "buttonName") // in the constructor - mpButton.clear(); // in dispose. +1. Derive from `VclBuilderContainer`. The `VclBuilder` then owns + many of the sub-windows, which are fetched by a `get` + method into local variables often in constructors eg. - We only clear, not disposeAndClear() in our dispose method - for this case, since the VclBuilder / Container truly owns - this Window, and needs to dispose its hierarchy in the - right order - first children then parents. +``` +VclPtr<PushButton> mpButton; // in the class +, get(mpButton, "buttonName") // in the constructor +mpButton.clear(); // in dispose. +``` - 2. Explicitly allocated Windows. These are often created and - managed by custom widgets: +We only clear, not `disposeAndClear()` in our dispose method +for this case, since the `VclBuilder` / Container truly owns +this Window, and needs to dispose its hierarchy in the +right order - first children then parents. - VclPtr<ComplexWidget> mpComplex; // in the class - , mpComplex( VclPtr<ComplexWidget>::Create( this ) ) // constructor - mpComplex.disposeAndClear(); // in dispose +2. Explicitly allocated Windows. These are often created and + managed by custom widgets: - ie. an owner has to dispose things they explicitly allocate. +``` +VclPtr<ComplexWidget> mpComplex; // in the class +, mpComplex( VclPtr<ComplexWidget>::Create( this ) ) // constructor +mpComplex.disposeAndClear(); // in dispose +``` - In order to ensure that the VclBuilderConstructor - sub-classes have their Windows disposed at the correct time - there is a disposeBuilder(); method - that should be added - -only- to the class immediately deriving from - VclBuilderContainer's dispose. +ie. an owner has to dispose things they explicitly allocate. ----------- What remains to be done ? ---------- +In order to ensure that the VclBuilderConstructor +sub-classes have their Windows disposed at the correct time +there is a `disposeBuilder();` method - that should be added +-only- to the class immediately deriving from +`VclBuilderContainer`'s dispose. - * Expand the VclPtr pattern to many other less - than safe VCL types. +### What remains to be done? - * create factory functions for VclPtr<> types and privatize - their constructors. +* Expand the `VclPtr` pattern to many other less + than safe VCL types. - * Pass 'const VclPtr<> &' instead of pointers everywhere - + add 'explicit' keywords to VclPtr constructors to - accelerate compilation etc. +* create factory functions for `VclPtr<>` types and privatize + their constructors. - * Cleanup common existing methods such that they continue to - work post-dispose. +* Pass `const VclPtr<> &` instead of pointers everywhere + + add `explicit` keywords to VclPtr constructors to + accelerate compilation etc. - * Dispose functions should be audited to: - + not leave dangling pointsr - + shrink them - some work should incrementally - migrate back to destructors. +* Cleanup common existing methods such that they continue to + work post-dispose. - * VclBuilder - + ideally should keep a reference to pointers assigned - in 'get()' calls - to avoid needing explicit 'clear' - code in destructors. +* Dispose functions should be audited to: + + not leave dangling pointsr + + shrink them - some work should incrementally + migrate back to destructors. ----------- FAQ / debugging hints ---------- +* `VclBuilder` + + ideally should keep a reference to pointers assigned + in `get()` calls - to avoid needing explicit `clear` + code in destructors. -** Compile with dbgutil +## FAQ / debugging hints - This is by far the best way to turn on debugging and - assertions that help you find problems. In particular - there are a few that are really helpful: +### Compile with dbgutil - vcl/source/window/window.cxx (Window::dispose) - "Window ( N4sfx27sidebar20SidebarDockingWindowE (Properties)) - ^^^ class name window title ^^^ - with live children destroyed: N4sfx27sidebar6TabBarE () - N4sfx27sidebar4DeckE () 10FixedImage ()" +This is by far the best way to turn on debugging and +assertions that help you find problems. In particular +there are a few that are really helpful: - You can de-mangle these names if you can't read them thus: +``` +vcl/source/window/window.cxx (Window::dispose) +"Window ( N4sfx27sidebar20SidebarDockingWindowE (Properties)) + ^^^ class name window title ^^^ +with live children destroyed: N4sfx27sidebar6TabBarE () +N4sfx27sidebar4DeckE () 10FixedImage ()" +``` - $ c++filt -t N4sfx27sidebar20SidebarDockingWindowE - sfx2::sidebar::SidebarDockingWindow +You can de-mangle these names if you can't read them thus: - In the above case - it is clear that the children have not been - disposed before their parents. As an aside, having a dispose chain - separate from destructors allows us to emit real type names for - parents here. +``` +$ c++filt -t N4sfx27sidebar20SidebarDockingWindowE +sfx2::sidebar::SidebarDockingWindow +``` - To fix this, we will need to get the dispose ordering right, - occasionally in the conversion we re-ordered destruction, or - omitted a disposeAndClear() in a ::dispose() method. +In the above case - it is clear that the children have not been +disposed before their parents. As an aside, having a dispose chain +separate from destructors allows us to emit real type names for +parents here. - => If you see this, check the order of disposeAndClear() in - the sfx2::Sidebar::SidebarDockingWindow::dispose() method +To fix this, we will need to get the dispose ordering right, +occasionally in the conversion we re-ordered destruction, or +omitted a `disposeAndClear()` in a `::dispose()` method. - => also worth git grepping for 'new sfx::sidebar::TabBar' to - see where those children were added. +- If you see this, check the order of `disposeAndClear()` in + the `sfx2::Sidebar::SidebarDockingWindow::dispose()` method -** Check what it used to do +- also worth `git grep`ing for `new sfx::sidebar::TabBar` to + see where those children were added. - While a ton of effort has been put into ensuring that the new - lifecycle code is the functional equivalent of the old code, - the code was created by humans. If you identify an area where - something asserts or crashes here are a few helpful heuristics: +### Check what it used to do - * Read the git log -u -- path/to/file.cxx +While a ton of effort has been put into ensuring that the new +lifecycle code is the functional equivalent of the old code, +the code was created by humans. If you identify an area where +something asserts or crashes here are a few helpful heuristics: - => Is the order of destruction different ? +* Read the `git log -u -- path/to/file.cxx` - in the past many things were destructed (in reverse order of - declaration in the class) without explicit code. Some of these - may be important to do explicitly at the end of the destructor. +### Is the order of destruction different? - eg. having a 'Idle' or 'Timer' as a member, may now need an - explicit .Stop() and/or protection from running on a - disposed Window in its callback. +In the past many things were destructed (in reverse order of +declaration in the class) without explicit code. Some of these +may be important to do explicitly at the end of the destructor. - => Is it 'clear' not 'disposeAndClear' ? +eg. having a `Idle` or `Timer` as a member, may now need an + explicit `.Stop()` and/or protection from running on a + disposed Window in its callback. - sometimes we get this wrong. If the code previously used to - use 'delete pFoo;' it should now read pFoo->disposeAndClear(); - Conversely if it didn't delete it, it should be 'clear()' it - is by far the best to leave disposing to the VclBuilder where - possible. +### Is it `clear` not `disposeAndClear`? - In simple cases, if we allocate the widget with VclPtrInstance - or VclPtr<Foo>::Create - then we need to disposeAndClear it too. +sometimes we get this wrong. If the code previously used to +use `delete pFoo;` it should now read `pFoo->disposeAndClear();`. +Conversely if it didn't delete it, it should be `clear()` it +is by far the best to leave disposing to the `VclBuilder` where +possible. -** Event / focus / notification ordering +In simple cases, if we allocate the widget with `VclPtrInstance` +or `VclPtr<Foo>::Create` - then we need to `disposeAndClear` it too. - In the old world, a large amount of work was done in the - ~Window destructor that is now done in Window::dispose. +### Event / focus / notification ordering - Since those Windows were in the process of being destroyed - themselves, their vtables were adjusted to only invoke Window - methods. In the new world, sub-classed methods such as - PreNotify, GetFocus, LoseFocus and others are invoked all down - the inheritance chain from children to parent, during dispose. +In the old world, a large amount of work was done in the +`~Window` destructor that is now done in `Window::dispose`. - The easiest way to fix these is to just ensure that these - cleanup methods, especially LoseFocus, continue to work even - on disposed Window sub-class instances. +Since those Windows were in the process of being destroyed +themselves, their vtables were adjusted to only invoke Window +methods. In the new world, sub-classed methods such as +`PreNotify`, `GetFocus`, `LoseFocus` and others are invoked all down +the inheritance chain from children to parent, during dispose. -** It crashes with some invalid memory... +The easiest way to fix these is to just ensure that these +cleanup methods, especially LoseFocus, continue to work even +on disposed Window sub-class instances. - Assuming that the invalid memory is a Window sub-class itself, - then almost certainly there is some cockup in the - reference-counting; eg. if you hit an OutputDevice::release - assert on mnRefCount - then almost certainly you have a - Window that has already been destroyed. This can easily - happen via this sort of pattern: +### It crashes with some invalid memory... - Dialog *pDlg = VclPtr<Dialog>(nullptr /* parent */); - // by here the pDlg quite probably points to free'd memory... +Assuming that the invalid memory is a Window sub-class itself, +then almost certainly there is some cockup in the +reference-counting; eg. if you hit an `OutputDevice::release` +assert on `mnRefCount` - then almost certainly you have a +Window that has already been destroyed. This can easily +happen via this sort of pattern: - It is necessary in these cases to ensure that the *pDlg is - a VclPtr<Dialog> instead. +``` +Dialog *pDlg = VclPtr<Dialog>(nullptr /* parent */); +// by here the pDlg quite probably points to free'd memory... +``` -** It crashes with some invalid memory #2... +It is necessary in these cases to ensure that the `*pDlg` is +a `VclPtr<Dialog>` instead. - Often a ::dispose method will free some pImpl member, but - not NULL it; and (cf. above) we can now get various virtual - methods called post-dispose; so: +### It crashes with some invalid memory #2... - a) delete pImpl; pImpl = NULL; // in the destructor - b) if (pImpl && ...) // in the subsequently called method +Often a `::dispose` method will free some `pImpl` member, but +not `NULL` it; and (cf. above) we can now get various `virtual` +methods called post-dispose; so: +a) `delete pImpl; pImpl = NULL; // in the destructor` +b) `if (pImpl && ...) // in the subsequently called method` diff --git a/vcl/README.md b/vcl/README.md index 25498991e4fb..63eb77d67405 100644 --- a/vcl/README.md +++ b/vcl/README.md @@ -82,7 +82,7 @@ The way COM is used in LO generally: - oslWorkerWrapperFunction() puts every thread spawned via `oslCreateThread()` into MTA (free-threaded) -# GDIMetafile +## GDIMetafile GDIMetafile is a vector drawing representation that corresponds directly to the SVM (StarView Metafile) format; it is extremely important as @@ -243,3 +243,9 @@ a mutex (`SolarMutex` or other) locked" fundamental rule: In short, never hold any mutex unless necessary, especially not when calling out. + +## Read More +* [Environment variables in VCL](README.vars) +* [GDIMetaFile class](README.GDIMetaFile) +* [Understanding transitional VCL lifecycle](README.lifecycle) +* [VCL scheduler](README.scheduler) diff --git a/vcl/README.scheduler b/vcl/README.scheduler index 38e17d8cedef..ae62f10d04a3 100644 --- a/vcl/README.scheduler +++ b/vcl/README.scheduler @@ -1,4 +1,6 @@ -= Introduction = +# VCL Scheduler + +## Introduction The VCL scheduler handles LOs primary event queue. It is simple by design, currently just a single-linked list, processed in list-order by priority @@ -21,7 +23,7 @@ C.1. Higher priority tasks starve lower priority tasks C.2. Tasks should be split into sensible blocks If this can't really be done, process pending tasks by calling - Application::Reschedule(). Or use a thread. + `Application::Reschedule()`. Or use a thread. C.3. This is not an OS scheduler There is no real way to "fix" B.2. and B.3. @@ -29,7 +31,7 @@ C.3. This is not an OS scheduler Otherwise make your task suspendable. -= Driving the scheduler AKA the system timer = +## Driving the scheduler AKA the system timer 1. There is just one system timer, which drives LO event loop 2. The timer has to run in the main window thread @@ -50,31 +52,31 @@ task. After invoking the task and if the task is still active, it is pushed to the end of the queue and the timeout is eventually adjusted. -= Locking = +## Locking The locking is quite primitive: all interaction with internal Scheduler -structures are locked. This includes the ImplSchedulerContext and the -Task::mpSchedulerData, which is actually a part of the scheduler. +structures are locked. This includes the `ImplSchedulerContext` and the +`Task::mpSchedulerData`, which is actually a part of the scheduler. Before invoking the task, we have to release the lock, so others can Start new Tasks. The Scheduler just processes its own Tasks in the main thread and needs -the SolarMutex for it and for DeInit (tested by DBG_TESTSOLARMUTEX). All +the `SolarMutex` for it and for `DeInit` (tested by `DBG_TESTSOLARMUTEX`). All the other interaction just take the scheduler mutex or don't need locking at all. There is a "workaround" for static Task objects, which would crash LO on -destruction, because Task::~Task would try to de-register itself in the -Scheduler, while the SchedulerLock would be long gone. OTOH this makes +destruction, because `Task::~Task` would try to de-register itself in the +Scheduler, while the `SchedulerLock` would be long gone. OTOH this makes Task handling less error-prone, than doing "special" manual cleanup. There is also a "= TODOs and ideas =" to get rid if static Tasks. Actually the scheduler mutex should never be locked when calling into non-scheduler code, so it was converted to a non-recursive -std::mutex. +`std::mutex`. -= Idle processing = +## Idle processing Confusingly, there are 2 concepts that are called 'idle': @@ -84,20 +86,20 @@ This is not necessarily when LO is idle, in fact such tasks may be invoked while there is input in the OS event queue pending. (TODO: This case should be fixed by renaming.) -* Low priority tasks, represented by priorities TaskPriority::HIGH_IDLE and lower. -In addition to being invoked only when there is no task with a higher priority, -pending input in the OS event queue also takes precedence. +* Low priority tasks, represented by priorities `TaskPriority::HIGH_IDLE` and +lower. In addition to being invoked only when there is no task with a higher +priority, pending input in the OS event queue also takes precedence. -= Lifecycle / thread-safety of Scheduler-based objects = +## Lifecycle / thread-safety of Scheduler-based objects A scheduler object it thread-safe in the way, that it can be associated to any thread and any thread is free to call any functions on it. The owner must -guarantee that the Invoke() function can be called, while the Scheduler object +guarantee that the `Invoke()` function can be called, while the Scheduler object exists / is not disposed. -= Anti-pattern: Dependencies via (fine grained) priorities = +## Anti-pattern: Dependencies via (fine grained) priorities "Idle 1" should run before "Idle 2", therefore give "Idle 1" a higher priority then "Idle 2". This just works correct for low frequency idles, but otherwise @@ -115,9 +117,9 @@ has to start "Task 2" and if "Task 1" is started again, it has to stop can be added to the scheduler reasonably. -= Implementation details = +## Implementation details -== General: event priority for DoYield == +### General: event priority for `DoYield` There are three types of events, with different priority: @@ -127,6 +129,7 @@ There are three types of events, with different priority: They should be processed according to the following code: +``` bool DoYield( bool bWait, bool bAllCurrent ) { bool bWasEvent = ProcessUserEvents( bAllCurrent ); @@ -145,41 +148,42 @@ bool DoYield( bool bWait, bool bAllCurrent ) } return bWasEvent; } +``` -== General: main thread deferral == +### General: main thread deferral In almost all VCL backends, we run main thread deferrals by disabling the -SolarMutex using a boolean. In the case of the redirect, this makes -tryToAcquire and doAcquire return true or 1, while a release is ignored. -Also the IsCurrentThread() mutex check function will act accordingly, so all -the DBG_TESTSOLARMUTEX won't fail. +`SolarMutex` using a boolean. In the case of the redirect, this makes +`tryToAcquire` and `doAcquire` return `true` or `1`, while a release is ignored. +Also the `IsCurrentThread()` mutex check function will act accordingly, so all +the `DBG_TESTSOLARMUTEX` won't fail. Since we just disable the locks when we start running the deferred code in the main thread, we won't let the main thread run into stuff, where it would normally wait for the SolarMutex. -Eventually this will move into the SolarMutex. KDE / Qt also does main -thread redirects using Qt::BlockingQueuedConnection. +Eventually this will move into the `SolarMutex`. KDE / Qt also does main +thread redirects using `Qt::BlockingQueuedConnection`. -== General: processing all current events for DoYield == +### General: processing all current events for `DoYield` This is easily implemented for all non-priority queue based implementations. Windows and macOS both have a timestamp attached to their events / messages, so simply get the current time and just process anything < timestamp. -For the KDE backend this is already the default behaviour - single event +For the **KDE** backend this is already the default behaviour - single event processing isn't even supported. The headless backend accomplishes this by just processing a copy of the list of current events. -Problematic in this regard is the Gtk+ backend. g_main_context_iteration +Problematic in this regard is the **Gtk+** backend. `g_main_context_iteration` dispatches "only those highest priority event sources". There is no real way to tell, when these became ready. I've added a workaround idea to the TODO -list. FWIW: Qt runs just a single timer source in the glib main context, +list. FWIW: **Qt** runs just a single timer source in the glib main context, basically the same we're doing with the LO scheduler as a system event. -The gen X11 backend has some levels of redirection, but needs quite some work +The gen **X11** backend has some levels of redirection, but needs quite some work to get this fixed. -== General: non-main thread yield == +### General: non-main thread yield Yielding from a non-main thread must not wait in the main thread, as this may block the main thread until some events happen. @@ -187,7 +191,7 @@ may block the main thread until some events happen. Currently we wait on an extra conditional, which is cleared by the main event loop. -== General: invalidation of elapsed timer event messages == +### General: invalidation of elapsed timer event messages Since the system timer to run the scheduler is single-shot, there should never be more than one elapsed timer event in system event queue. When stopping or @@ -204,9 +208,9 @@ To prevent these problems, we don't even try to remove these events, but invalidate them by versioning the timer events. Timer events with invalid versions are processed but simply don't run the scheduler. -== General: track time of long running tasks == +### General: track time of long running tasks -There is TaskStopwatch class. It'll track the time and report a timeout either +There is `TaskStopwatch` class. It'll track the time and report a timeout either when the tasks time slice is finished or some system event did occur. Eventually it will be merged into the main scheduler, so each invoked task can @@ -215,8 +219,8 @@ other long running tasks, so interactivity can be improved. There were some questions coming up when implementing it: -=== Why does the scheduler not detect that we only have idle tasks pending, -and skip the instant timeout? === +#### Why does the scheduler not detect that we only have idle tasks pending, +and skip the instant timeout? You never know how long a task will run. Currently the scheduler simply asks each task when it'll be ready to run, until two runnable tasks are found. @@ -226,7 +230,7 @@ and just a very few long term pending Timers. Especially UNO calls add a lot of Idles to the task list, which just need to be processed in order. -=== Why not use things like Linux timer wheels? === +#### Why not use things like Linux timer wheels? LO has relatively few timers and a lot one-shot Idles. 99% of time the search for the next task is quick, because there are just ~5 long term timers per @@ -239,7 +243,7 @@ But the Linux timer wheel mainly relies on the facts that the OS timers are expected to not expire, as they are use to catch "error" timeouts, which rarely happen, so this definitely not matches LO's usage. -=== Not really usable to find misbehaving tasks === +#### Not really usable to find misbehaving tasks The TaskStopwatch class is just a little time keeper + detecting of input events. This is not about misbehaving Tasks, but long running tasks, which @@ -250,20 +254,20 @@ There is the TODO to merge the functionality into the Scheduler itself, at which point we can think about profiling individual Tasks to improve interactivity. -== macOS implementation details == +### macOS implementation details Generally the Scheduler is handled as expected, except on resize, which is handled with different runloop-modes in macOS. In case of a resize, the normal -runloop is suspended in sendEvent, so we can't call the scheduler via posted +`runloop` is suspended in `sendEvent`, so we can't call the scheduler via posted main loop-events. Instead the scheduler uses the timer again. Like the Windows backend, all Cocoa / GUI handling also has to be run in the main thread. We're emulating Windows out-of-order PeekMessage processing, -via a YieldWakeupEvent and two conditionals. When in a RUNINMAIN call, all -the DBG_TESTSOLARMUTEX calls are disabled, as we can't release the SolarMutex, -but we can prevent running any other SolarMutex based code. Those wakeup -events must be ignored to prevent busy-locks. For more info read the "General: -main thread deferral" section. +via a `YieldWakeupEvent` and two conditionals. When in a `RUNINMAIN` call, all +the `DBG_TESTSOLARMUTEX` calls are disabled, as we can't release the +`SolarMutex`, but we can prevent running any other `SolarMutex` based code. +Those wakeup events must be ignored to prevent busy-locks. For more info read +the "General: main thread deferral" section. We can neither rely on macOS dispatch_sync code block execution nor the message handling, as both can't be prioritized or filtered and the first @@ -277,51 +281,51 @@ An additional problem is the filtering of events on Window close. This drops posted timer events, when a Window is closed resulting in a busy DoYield loop, so we have to re-post the event, after closing a window. -== Windows implementation details == +### Windows implementation details Posted or sent event messages often trigger processing of WndProc in -PeekMessage, GetMessage or DispatchMessage, independently from the message to -fetch, remove or dispatch ("During this call, the system delivers pending, +`PeekMessage`, `GetMessage` or `DispatchMessage`, independently from the message +to fetch, remove or dispatch ("During this call, the system delivers pending, nonqueued messages..."). Additionally messages have an inherited priority -based on the function used to generate them. Even if WM_TIMER messages should -have the lowest priority, a manually posted WM_TIMER is processed with the +based on the function used to generate them. Even if `WM_TIMER` messages should +have the lowest priority, a manually posted `WM_TIMER` is processed with the priority of a PostMessage message. So we're giving up on processing all our Scheduler events as a message in the system message loop. Instead we just indicate a 0ms timer message by setting -the m_bDirectTimeout in the timer object. This timer is always processed, if +the `m_bDirectTimeout` in the timer object. This timer is always processed, if the system message wasn't already our timer. As a result we can also skip the polling. All this is one more reason to drop the single message processing in favour of always processing all pending (system) events. There is another special case, we have to handle: window updates during move and resize of windows. These system actions run in their own nested message -loop. So we have to completely switch to timers, even for 0ms. But these +loop. So we have to completely switch to timers, even for 0 ms. But these posted events prevent any event processing, while we're busy. The only viable -solution seems to be to switch to WM_TIMER based timers, as these generate -messages with the lowest system priority (but they don't allow 0ms timeouts). +solution seems to be to switch to `WM_TIMER` based timers, as these generate +messages with the lowest system priority (but they don't allow 0 ms timeouts). So processing slows down during resize and move, but we gain working painting, even when busy. An additional workaround is implemented for the delayed queuing of posted -messages, where PeekMessage in WinSalTimer::Stop() won't be able remove the +messages, where `PeekMessage` in `WinSalTimer::Stop()` won't be able remove the just posted timer callback message. See "General: invalidation of elapsed timer event messages" for the details. To run the required GUI code in the main thread without unlocking the -SolarMutex, we "disable" it. For more infos read the "General: main thread +`SolarMutex`, we "disable" it. For more infos read the "General: main thread deferral" section. -== KDE implementation details == +### KDE implementation details This implementation also works as intended. But there is a different Yield -handling, because Qts QAbstractEventDispatcher::processEvents will always +handling, because Qts `QAbstractEventDispatcher::processEvents` will always process all pending events. -= TODOs and ideas = +## TODOs and ideas -== Task dependencies AKA children == +### Task dependencies AKA children Every task can have a list of children / a child. @@ -330,7 +334,7 @@ Every task can have a list of children / a child. This should be easy to implement. -== Per priority time-sorted queues == +### Per priority time-sorted queues This would result in O(1) scheduler. It was used in the Linux kernel for some time (search Ingo Molnar's O(1) scheduler). This can be a scheduling @@ -343,11 +347,11 @@ queues and schedules the first ready event of any queue. The downside of this approach: Insert / Start / Reschedule(for "auto" tasks) now need O(log(n)) to find the position in the queue of the priority. -== Always process all (higher priority) pending events == +### Always process all (higher priority) pending events -Currently Application::Reschedule() processes a single event or "all" events, +Currently `Application::Reschedule()` processes a single event or "all" events, with "all" defined as "100 events" in most backends. This already is ignored -by the KDE backend, as Qt defines its QAbstractEventDispatcher::processEvents +by the KDE backend, as Qt defines its `QAbstractEventDispatcher::processEvents` processing all pending events (there are ways to skip event classes, but no easy way to process just a single event). @@ -355,14 +359,14 @@ Since the Scheduler is always handled by the system message queue, there is really no more reasoning to stop after 100 events to prevent LO Scheduler starvation. -== Drop static inherited or composed Task objects == +### Drop static inherited or composed Task objects The sequence of destruction of static objects is not defined. So a static Task can not be guaranteed to happen before the Scheduler. When dynamic unloading is involved, this becomes an even worse problem. This way we could drop the mbStatic workaround from the Task class. -== Run the LO application in its own thread == +### Run the LO application in its own thread This would probably get rid of most of the macOS and Windows implementation details / workarounds, but is quite probably a large amount of work. @@ -374,7 +378,7 @@ and can process system events. That's just a theory - it definitely needs more analysis before even attending an implementation. -== Re-evaluate the macOS ImplNSAppPostEvent == +### Re-evaluate the macOS `ImplNSAppPostEvent` Probably a solution comparable to the Windows backends delayed PostMessage workaround using a validation timestamp is better then the current peek, @@ -382,8 +386,9 @@ remove, re-postEvent, which has to run in the main thread. Originally I didn't evaluate, if the event is actually lost or just delayed. -== Drop nMaxEvents from Gtk+ based backends == +### Drop `nMaxEvents` from Gtk+ based backends +``` gint last_priority = G_MAXINT; bool bWasEvent = false; do { @@ -403,22 +408,23 @@ do { } } while ( bHasPending ) +``` -The idea is to use g_main_context_prepare and keep the max_priority as an +The idea is to use g_main_context_prepare and keep the `max_priority` as an indicator. We cannot prevent running newer lower events, but we can prevent running new higher events, which should be sufficient for most stuff. This also touches user event processing, which currently runs as a high priority idle in the event loop. -== Drop nMaxEvents from gen (X11) backend == +### Drop nMaxEvents from gen (X11) backend -A few layers of indirection make this code hard to follow. The SalXLib::Yield -and SalX11Display::Yield architecture makes it impossible to process just the +A few layers of indirection make this code hard to follow. The `SalXLib::Yield` +and `SalX11Display::Yield` architecture makes it impossible to process just the current events. This really needs a refactoring and rearchitecture step, which will also affect the Gtk+ and KDE backend for the user event handling. -== Merge TaskStopwatch functionality into the Scheduler == +### Merge TaskStopwatch functionality into the Scheduler This way it can be easier used to profile Tasks, eventually to improve LO's interactivity. diff --git a/vcl/README.vars b/vcl/README.vars index f9e0c600d2c7..cdf356f6a2e0 100644 --- a/vcl/README.vars +++ b/vcl/README.vars @@ -1,64 +1,66 @@ -Environment variables in VCL: - -General -------- -SAL_USE_VCLPLUGIN - use a VCL plugin -SAL_RTL_ENABLED - Enable RTL UI -SAL_NO_NWF - disable native widgets -SAL_FORCEDPI - force a specific DPI (gtk3 & qt5/kf5 plugins only) -SAL_FORCE_HC - force high-contrast mode - -SAL_NO_FONT_LOOKUP - disable font search and fallback and always use a hard-coded font name (for some unit tests) - -LO_COLLECT_UIINFO - enable the uitesting logging, value is expected to be a relative file name that -will be used to write the log under instdir/uitest/. - -VCL_DOUBLEBUFFERING_AVOID_PAINT - don't paint the buffer, useful to see where we do direct painting -VCL_DOUBLEBUFFERING_FORCE_ENABLE - enable double buffered painting -VCL_DOUBLEBUFFERING_ENABLE - enable a safe subset of double buffered painting (currently in Writer, not in any other applications) - -VCL_DEBUG_DISABLE_PDFCOMPRESSION - disable compression in the PDF writer - -SAL_DISABLE_WATCHDOG - don't start the thread that watches for broken GL/Vulkan/OpenCL drivers - -Gtk+ ----- -VCL_GTK3_PAINTDEBUG - in debug builds, if set to 1 then holding down shift+0 forces a redraw event, shift+1 repaints everything, and -shift+2 dumps cairo frames to pngs as /tmp/frame<n>.png -GDK_SCALE=2 - for HiDPI scaling (just supports integers) - -Bitmap ------- -VCL_NO_THREAD_SCALE - disable threaded bitmap scale -VCL_NO_THREAD_IMPORT - disable threaded bitmap import -EMF_PLUS_DISABLE - use EMF rendering and ignore EMF+ specifics - -OpenGL ------- -SAL_DISABLEGL - disable OpenGL use -SAL_GL_NO_SWAP - disable buffer swapping if set (should show nothing) -SAL_GL_SLEEP_ON_SWAP - sleep for half a second on each swap-buffers. - -Skia ----- -SAL_DISABLESKIA=1 - force disabled Skia -SAL_ENABLESKIA=1 - enable Skia, unless denylisted (and if the VCL backend supports Skia) -SAL_FORCESKIA=1 - force using Skia, even if denylisted -SAL_SKIA=raster|vulkan|metal - select Skia's drawing method, by default Vulkan or Metal are used if available -SAL_DISABLE_SKIA_CACHE=1 - disable caching of complex images -SAL_SKIA_KEEP_BITMAP_BUFFER=1 - SkiaSalBitmap will keep its bitmap buffer even after storing in SkImage - -OpenGL,Skia ------------ -SAL_WITHOUT_WIDGET_CACHE - disable LRU caching of native widget textures - -Qt ------ -QT_SCALE_FACTOR=2 - for HiDPI testing (also supports float) -SAL_VCL_QT5_NO_FONTCONFIG - ignore fontconfig provided font substitutions -SAL_VCL_QT5_NO_NATIVE - disable QStyle'd controls -SAL_VCL_QT5_USE_CAIRO - use cairo for text rendering and fonts (default for kf5, but not qt5) - -Kf5 ------ -SAL_VCL_KF5_USE_QFONT - use QFont for text rendering (default for qt5, but not kf5) +# Environment variables in VCL + +## General + +These are the general environment variables used in the VCL: + +* `SAL_USE_VCLPLUGIN` - use a VCL plugin +* `SAL_RTL_ENABLED` - Enable RTL UI +* `SAL_NO_NWF` - disable native widgets +* `SAL_FORCEDPI` - force a specific DPI (gtk3 & qt5/kf5 plugins only) +* `SAL_FORCE_HC` - force high-contrast mode + +* `SAL_NO_FONT_LOOKUP` - disable font search and fallback and always use a hard-coded font name (for some unit tests) + +* `LO_COLLECT_UIINFO` - enable the uitesting logging, value is expected to be a relative file name that +will be used to write the log under `instdir/uitest/`. + +* `VCL_DOUBLEBUFFERING_AVOID_PAINT` - don't paint the buffer, useful to see where we do direct painting +* `VCL_DOUBLEBUFFERING_FORCE_ENABLE` - enable double buffered painting +* `VCL_DOUBLEBUFFERING_ENABLE` - enable a safe subset of double buffered painting (currently in Writer, not in any other applications) + +* `VCL_DEBUG_DISABLE_PDFCOMPRESSION` - disable compression in the PDF writer + +* `SAL_DISABLE_WATCHDOG` - don't start the thread that watches for broken GL/Vulkan/OpenCL drivers + +## Gtk+ + +* `VCL_GTK3_PAINTDEBUG` - in debug builds, if set to `1` then holding down `shift+0` forces a redraw event, `shift+1` repaints everything, and +`shift+2` dumps cairo frames to pngs as `/tmp/frame<n>.png` +* `GDK_SCALE=2` - for HiDPI scaling (just supports integers) + +## Bitmap + +* `VCL_NO_THREAD_SCALE` - disable threaded bitmap scale +* `VCL_NO_THREAD_IMPORT` - disable threaded bitmap import +* `EMF_PLUS_DISABLE` - use EMF rendering and ignore EMF+ specifics + +## OpenGL + +* `SAL_DISABLEGL` - disable OpenGL use +* `SAL_GL_NO_SWAP` - disable buffer swapping if set (should show nothing) +* `SAL_GL_SLEEP_ON_SWAP` - sleep for half a second on each swap-buffers. + +## Skia + +* `SAL_DISABLESKIA=1` - force disabled Skia +* `SAL_ENABLESKIA=1` - enable Skia, unless denylisted (and if the VCL backend supports Skia) +* `SAL_FORCESKIA=1` - force using Skia, even if denylisted +* `SAL_SKIA=raster|vulkan|metal` - select Skia's drawing method, by default Vulkan or Metal are used if available +* `SAL_DISABLE_SKIA_CACHE=1` - disable caching of complex images +* `SAL_SKIA_KEEP_BITMAP_BUFFER=1` - `SkiaSalBitmap` will keep its bitmap buffer even after storing in `SkImage` + +## OpenGL,Skia + +* `SAL_WITHOUT_WIDGET_CACHE` - disable LRU caching of native widget textures + +## Qt + +* `QT_SCALE_FACTOR=2` - for HiDPI testing (also supports float) +* `SAL_VCL_QT5_NO_FONTCONFIG` - ignore fontconfig provided font substitutions +* `SAL_VCL_QT5_NO_NATIVE` - disable `QStyle`'d controls +* `SAL_VCL_QT5_USE_CAIRO` - use cairo for text rendering and fonts (default for kf5, but not qt5) + +## Kf5 + +* `SAL_VCL_KF5_USE_QFONT` - use `QFont` for text rendering (default for qt5, but not kf5) |