Jan 292018
 

I have made a few changes to the code in Fake TSpeedButton based on a TBitBtn:

  • I replaced all that line drawing with a call to the WinAPI function DrawEdge.
  • I set both bitmaps to Transparent.
  • I moved the bitmap generation to a sub procedure.

Thus I got rid of about 20 LOC. That’s negative productivity for you. 😉
(Which yet again shows that Lines Of Code is an idiotic metric for software developer productivity.)

The code has been tested with several Delphi versions (6, 7, 2007, XE2 and 10.2) in Windows 8.1 and is now used in the GExperts Rename Component Expert. There is no release yet, but you can always compile your own.

Jan 282018
 

When you google for TSpeedButton and Focus a lot of hits are where people ask how to set the focus to a TSpeedButton and the answer of course is, that it isn’t possible because TSpeedButton descends from TGraphicControl which does not have a window handle and therefore cannot receive the input focus. And that is usually the end to it. I have found 0 (zero) answers on how to get around this shortcoming.

So I posed the same question myself in the Google+ Delphi Developers Community, hoping against hope that somebody in the 30 years that Delphi has existed has come up with a solution (e.g. a component that allows both: A Button that can stay down and still receive focus.

Apparently there are a few, most of them part of a larger component collection, mostly commercial ones, none that is simple and stand alone and free.

While waiting for answers I played around with various solutions and came up with a solution that you can see in the animated GIF above:

Take a TBitBtn, set its Caption xor assign a Glyph to it (no, it won’t work with both), set its Tag to 1, if you want it to start in the Down state, and call TdzSpeedBitBtn.Create(BitBtn).

If you don’t want to change the Down “property” yourself later on, you don’t need to keep the TdzSpeedBitBtn reference around. It will attach itself to the BitBtn and automatically be destroyed when the BitBtn is destroyed.

If you want to execute an event when the button is clicked, feel free to assign an OnClick event prior to passing it to TdzSpeedBitBtn.

If you want to be able to change the Down state, keep the reference. It does have a Down property which will let you change the Down state of the button. If you only want to read it, simply check if the Tag property is <> 0.

EDIT1: Replaced all that line drawing with a call to the WinAPI function DrawEdge.
EDIT2: Set both bitmaps to Transparent.
EDIT3: Moved bitmap generation to a sub procedure.

  TdzSpeedBitBtn.Create(b_Test1);
  TdzSpeedBitBtn.Create(b_Test2);
  TdzSpeedBitBtn.Create(b_Test3);
  TdzSpeedBitBtn.Create(b_Test4);

The helper class TdzSpeedButton isn’t even very complicated. Here is the source code:

unit GX_dzSpeedBitBtn;

interface

uses
  Windows,
  Classes,
  Buttons,
  Graphics;

type
  TdzSpeedBitBtn = class(TComponent)
  private
    FCaption: string;
    FBtn: TBitBtn;
    FOrigBmp: TBitmap;
    FOrigOnClick: TNotifyEvent;
    FUpBmp: TBitmap;
    FDownBmp: TBitmap;
    procedure doOnClick(_Sender: TObject);
    procedure HandleOnClick(_Sender: TObject);
    function GetDown: Boolean;
    procedure SetDown(const Value: Boolean);
    procedure UpdateGlyph;
  public
    constructor Create(_btn: TComponent); override;
    destructor Destroy; override;
    property Down: Boolean read GetDown write SetDown;
  end;

implementation

{ TdzSpeedBitBtn }

constructor TdzSpeedBitBtn.Create(_btn: TComponent);

  procedure PrepareBmp(w, h: Integer; _Color: TColor; _Edge: UINT; out _bmp: TBitmap);
  var
    cnv: TCanvas;
    qrc: TRect;
    TextSize: TSize;
  begin
    _bmp := TBitmap.Create;
    _bmp.Width := w;
    _bmp.Height := h;
    _bmp.TransparentColor := clFuchsia;

    cnv := _bmp.Canvas;

    cnv.Brush.Color := _Color;
    cnv.Brush.Style := bsSolid;
    cnv.FillRect(Rect(0, 0, w, h));

    qrc := Rect(0, 0, w - 1, h - 2);
    DrawEdge(cnv.Handle, qrc, _Edge, BF_RECT);

    if FCaption <> '' then begin
      TextSize := cnv.TextExtent(FCaption);
      cnv.TextOut((w - TextSize.cx) div 2, (h - TextSize.cy) div 2, FCaption);
    end else begin
      cnv.Draw((w - FOrigBmp.Width) div 2, (h - FOrigBmp.Height) div 2, FOrigBmp);
    end;

  end;

var
  w: Integer;
  h: Integer;
  ColBack1: TColor;
  ColBack2: TColor;
begin
  inherited Create(_btn);
  FBtn := _btn as TBitBtn;
  FOrigOnClick := FBtn.OnClick;
  FCaption := FBtn.Caption;

  FOrigBmp := TBitmap.Create;
  FOrigBmp.Assign(FBtn.Glyph);
  FOrigBmp.Transparent := True;

  FBtn.Caption := '';

  w := FBtn.Width - 1;
  h := FBtn.Height - 1;

  ColBack1 := rgb(240, 240, 240); // clBtnFace;
  ColBack2 := rgb(245, 245, 245); // a bit lighter than clBtnFace;

  PrepareBmp(w, h, ColBack1, EDGE_RAISED, FUpBmp);
  PrepareBmp(w, h, ColBack2, EDGE_SUNKEN, FDownBmp);

  FBtn.OnClick := HandleOnClick;

  UpdateGlyph;
end;

destructor TdzSpeedBitBtn.Destroy;
begin
  // If we get here, either the constructor failed (which automatically calls the destructor)
  // or FBtn was already destroyed, so we must not access it at all.
  FUpBmp.Free;
  FDownBmp.Free;
  FOrigBmp.Free;
  inherited;
end;

procedure TdzSpeedBitBtn.doOnClick(_Sender: TObject);
begin
  if Assigned(FOrigOnClick) then
    FOrigOnClick(_Sender);
end;

procedure TdzSpeedBitBtn.HandleOnClick(_Sender: TObject);
begin
  Down := not Down;
  doOnClick(_Sender);
end;

function TdzSpeedBitBtn.GetDown: Boolean;
begin
  Result := (FBtn.Tag <> 0);
end;

procedure TdzSpeedBitBtn.SetDown(const Value: Boolean);
begin
  if Value then
    FBtn.Tag := 1
  else
    FBtn.Tag := 0;
  UpdateGlyph;
end;

procedure TdzSpeedBitBtn.UpdateGlyph;
begin
  if FBtn.Tag <> 0 then
    FBtn.Glyph := FDownBmp
  else
    FBtn.Glyph := FUpBmp;
end;

end.

There is no support for grouping these buttons (yet). I’ll probably not bother but simply use the OnClick event for that.

I have so far tested this only with Delphi 2007 and on Windows 8.1. So it is still possible that it doesn’t work with other Delphi versions (I am going to use it in GExperts, so I will find out) or on other Windows versions (I have only Windows 8.1 to test, so that’s up to people who use GExperts on these versions.).

I will also add this unit to my dzlib library once I am

Jan 272018
 

The ToolsAPI interface IOTAComponent declares two methods for getting property values:

  IOTAComponent = interface(IUnknown)
  // [...]
    { Given the index or name, returns the property value. The untyped var
      must be large enough to hold the returned value.  If the property is
      a descendant of TPersistent, the return value is a IOTAComponent. For
      properties of any other object type, the return value is nil. }
    function GetPropValue(Index: Integer; var Value): Boolean;
    function GetPropValueByName(const Name: string; var Value): Boolean;

The comment says “The untyped var must be large enough to hold the returned value.” which pretty much lets us guess, how large it must be for an enum (or even a Boolean). Let’s say, we want to read the Enabled property of a component, do we simply pass a Boolean? Or for the Anchors property, do we pass a TAnchors variable (which is a set of TAnchorKind)?

David Hoyle’s The Delphi Open Tools API Book only mentions what to pass for strings. I had to find out the hard way: For Enums, Booleans and Sets, the methods always expect 4 bytes. So for enums and sets, better pass an integer, for Booleans, pass a LongBool.

var
  BoolValue: LongBool;
  IntValue: integer;
  AlignValue: TAlign;
begin
  if _Component.GetPropValueByName('Align', IntValue) then begin
    AlignValue := TAlign(IntValue);
    _Component.GetPropValueByName('AlignWithMargins', BoolValue);
  end;
end;
Jan 252018
 

If I want to clone a Linux system (or any the boot partition/drive of any operating system), I usually use Clonezilla and make a image of the boot disk or boot partition. Unfortunately those image files can become quite large and it is a pain in the lower back to restore them on a smaller hard disk than the original one.

This becomes an issue if you want to move such a system to a virtual machine where you want to keep the size of the boot vdisk to a minimum. So, what other options are there?

In Linux almost everything is a file and there isn’t much “magic” involved in the boot process, so why not take an existing Linux VM and simply synchronize the files only? This won’t be perfect, of course since the disk device names will change so you will most likely end up with an unbootable target system. But we all know how to fix that, don’t we? Simply fix the grub configuration and edit fstab and we are done.

The command to synchronize the files over the network is rsync:

rsync -av --one-file-system --numeric-ids -X -H --acls --delete --sparse --exclude=proc --exclude=dev --exclude=var/log / IpOfTheTargetSystem:/

Where IpOfTheTargetSystem is the IP address of the target system. (And don’t forge the trailing colon and forward slash).

This must be executed as root on the source system. Make sure that ssh login as root works on the target system first, otherwise you will wonder what the problem is later.

Note that this will overwrite the /etc and /boot directories, which has the advantage of cloning your configuration but the disadvantage mentioned above: You will most likely end up with an unbootable system. Also, notice the –delete switch? It will, without asking you, delete all files on the target system that do not exist on the source system.

Also, don’t just execute any command you find on the Internet! Who knows what nefarious purpose I have by posting it here?

(Actually this is mostly for me so I can look up all the switches I had to find out for it to do what I want.)

Jan 242018
 

The instructions how to install Webmin on Debian (and thereby also Ubuntu) seem a bit outdated because edits to the file /etc/apt/sources.list should be replaced by adding a file to the directory /etc/apt/sources.list.d/.

So, instead of adding

deb https://download.webmin.com/download/repository sarge contrib

to the file

/etc/apt/sources.list

create a new file

/etc/apt/sources.list.d/webmin.list

with that content and possibly a comment why you added it.

The rest seems to be up to date:

“You should also fetch and install my GPG key with which the repository is signed, with the commands:”

cd /root
wget http://www.webmin.com/jcameron-key.asc
apt-key add jcameron-key.asc

“You will now be able to install with the commands:”

apt-get update
apt-get install apt-transport-https
apt-get install webmin

“All dependencies should be resolved automatically.”

The reason why I installed Webmin was that updating a server from Ubuntu 14.04 to 16.04 broke the Webmin installation. I kept getting the error “module proc does not exist”. Google did not turn up anything useful for this so I decided to simply uninstall Webmin:

apt remove webmin
apt autoremove

And then I reinstalled it with the procedure described above. The error went away. I also got a new UI which will take a while to get used to.

Jan 232018
 

(Disclaimer: I am by no means an expert with XenServer. So please don’t take anything you read here for granted. It’s my own experience and what I found in documentation and online.)

If switching a XenServer Linux VM to paravirtualization fails, you usually end up with a non booting VM which is quite annoying. Switching it back to hardware assisted virtualization isn’t difficult, if you know what to do:

  1. Open a console on the XenServer host (local or via ssh)
  2. Get the UUID of the VM you want to change:
    xe vm-list name-label="NameOfTheVM"
  3. change two parameters of the VM
    1. Set HVM-boot-policy to “BIOS order”
      xe vm-param-set uuid=UuidOfTheVM HVM-boot-policy="BIOS order"
    2. Set PV-bootloader to “”
      xe vm-param-set uuid=UuidOfTheVM PV-bootloader=""

If everything works, the virtualization mode of the VM in XenCenter should be switched back to “Hardware-assisted Virtualization (HVM)” and the VM will boot again.

Source: This Citrix Forum post

Another article about this

Jan 082018
 

Apparently shrinking a Windows VM (actually any kind of VM) cannot be done with XenServer and XenCenter. You need to create an image of the original volume to a smaller virtual disk in order to do that.

On top of that, the tool that used to work fine for this, Citrix XenConvert, has been deprecated by Citrix and is no longer available for download.

And just to make matters worse, this answer on ServerFault links to an article that is no longer available (or did, until I edited it). Fortunately it’s still in the WaybackMachine

Now, where do we get XenConvert? It’s available for download here in various versions. I took version 2.5. and followed the instructions in the above mentioned article. You might want to virus check the installer before running it.

  1. Create a new virtual disk with the desired size.
  2. Attach it to your VM.
  3. Start the VM.
  4. Optionally, shrink the source drive (partition) from within the virtual machine (Windows 7+ comes with the tools for that).
  5. Format the newly created virtual disk.
  6. Install XenConvert
  7. Start XenConvert, select From: Volume and To: Volume (That’s not the default.)
  8. Select source and destination volumes.
  9. Run the conversion (which takes forever).
  10. Activate the new partition in the Disk Manager (if you forget this, you won’t be able to boot from it).
  11. Shutdown the VM.
  12. Detach the original virtual disk.
  13. Boot the VM (from the new virtual disk).

If everything works, you can now delete the original virtual disk.

Jan 072018
 

When Mozilla released their last big update, they deprecated quite a few APIs for plugins. One of these plugins was FireGestures which I used to get some of the mouse gestures of Opera in Firefox. There are a few new plugins that try to replace FireGestures but none of them really seemed to work quite the way I wanted them, so I thought “Hey, you are a programmer, just write it yourself. It can’t be that hard.”.

It turned out it isn’t that hard, really. So, here comes dzMouseGestures, a tool, that supports 3 different mouse gestures and converts them to keyboard input for Mozilla Firefox:

  • Down -> Right: Ctrl+w
  • Left -> Right: Alt+Left
  • Right -> Left: Alt+Right

It requires the user to hold down the right mouse button while making these gestures.

It took me a few hours to figure out how to install a system wide mouse hook and use that to interpret the mouse movements, convert them to gestures and send key sequences to Firefox. It works for me.

The source code and version 0.0.2 of the binaries (a dll with the hook and an executable that loads this dll and displays some debug information) is available on SourceForge.

Jan 042018
 

(Disclaimer: I am by no means an expert with XenServer. So please don’t take anything you read here for granted. It’s my own experience and what I found in documentation and online.)

In my previous post, I described how to add a Storage Repository to a XenServer using the xe command line tool.

Now, since I have installed the device driver for the RAID controller, created a RAID 5 and added it as Storage Repository (in the same way as described in the linked article), I want to get rid of the SATA drive which I had added as a temporary measure. Guess what, there seem to be no way of doing that using the XenCenter tool (even though the second link below mentions a “Detach” option I could not find it, see the disclaimer above). So, again, it’s the xe command line to the rescue.

xe help

writes a list of supported commands to the console which only contains one command with an sr- prefix: sr-list. Only if you tell it to give you a complete list, you will see what we need here, the sr-destroy command.

xe help --all
[... very long list of commands ...]
xe help sr-destroy
command name            : sr-destroy
        reqd params     : uuid
        optional params :
        description     : Destroy the SR.

But where do we get that uuid? Simple, by asking for it:

xe sr-list
[...]
uuid ( RO)                : 3f1d80c6-929a-f547-9ab1-63a2ca638cbf
          name-label ( RW): XenServer Tools
    name-description ( RW): XenServer Tools ISOs
                host ( RO): xenserver1
                type ( RO): iso
        content-type ( RO): iso
[...]
uuid ( RO)                : <UUID of SR>
          name-label ( RW): temporary-sata-disk
    name-description ( RW):
                host ( RO): xenserver1
                type ( RO): lvm
        content-type ( RO): user

As you can see, there is one Storage Repository with the label temporary-sata-disk. That’s the one I want to remove (the actual list is longer). So

xe sr-destroy uuid=<UUID of SR>

should delete that Storage Repository, right?

No, of course not, you get the error message

The SR is still connected to a host via a PBD. It cannot be destroyed or forgotten.
sr: <UUID of SR> (temporary-sata-disk)

There is also an sr-forget command but it displays the same error message.

So simple guessing doesn’t get us any further. Maybe we should read the docs? Nah, we don’t do that yet, we google and find this and also this.

One of the answers in the second link gives an extended version of the sr-list command:

xe sr-list uuid=<UUID of SR> params all
uuid ( RO)                    : <UUID of SR>
              name-label ( RW): temporary-sata-disk
[...]
      allowed-operations (SRO): unplug; plug; PBD.create; update; PBD.destroy; VDI.resize; VDI.clone; scan; VDI.snapshot; VDI.mirror; VDI.create; VDI.destroy
[...]
                    VDIs (SRO):
                    PBDs (SRO): <UUID of PBD>
[...]

This shows a lot of stuff, in particular it shows a list of allowed operations and a list of VDIs and PDBs currently connected to the Storage Repository. In the case above, we can see, that there are no VDIs connected to it and one PDB. So we follow the instructions in the first link:

xe sr-list name-label=temporary-sata-disk
uuid ( RO)                : <UUID of SR>
          name-label ( RW): temporary-sata-disk
    name-description ( RW): 
                host ( RO): xenserver1
                type ( RO): lvm
        content-type ( RO): user

which gets us the UUID of the SR (OK, we already had that one)

xe pbd-list sr-uuid=<UUID of SR>
uuid ( RO)                  : <UUID of PBD>
             host-uuid ( RO): 78059c4a-73f2-4975-936a-537529772d67
               sr-uuid ( RO): <UUID of SR>
         device-config (MRO): device: /dev/sdb
    currently-attached ( RO): true

which gets us the UUID of the PDB (we already had that one too)

xe pbd-unplug uuid=<UUID of PBD>

followed by

xe sr-forget uuid=<UUID of SR>

After that last command, the Storage Repository vanishes from XenCenter. Also

xe sr-list

no longer lists it. So I guess, it’s safe now to

  • Shut down the computer
  • Remove the hard disk that was used for that Storage Repository
  • Turn on the computer again

One last question: What is the difference between the xe commands sr-forget and sr-destroy? My google fu nearly left me here, until I found a link to XenServer Administrator’s Guide – Layer 8 Consulting (PDF!). There, on page 45 it says
Destroying or forgetting a SR
You can destroy an SR, which actually deletes the contents of the SR from the physical media. Alternatively you can forget an SR, which allows you to re-attach the SR, for example, to another XenServer host, without removing any of the SR contents.”
So, there you go.

Jan 042018
 

(Disclaimer: I am by no means an expert with XenServer. So please don’t take anything you read here for granted. It’s my own experience and what I found in documentation and online.)

There is at least one reason why installing updates for XenServer via XenCenter may fail (with unhelpful error messages of course): You haven’t got created any Storage Repository yet.

One might think that this is pretty unusual but your’s truly has actually managed to run into this problem. Why? Because I didn’t install the drivers necessary for accessing the hardware RAID controller (in my case the LSI megaraid package). Usually you do that during the XenServer setup but I was at that time not aware that I would actually need a driver. And thus apart from the boot disk there was nowhere to put a Storage Repository. And I didn’t want to put that on the boot ssd. So I ended up with a XenServer installation that didn’t have any Storage Repositories.

So, what can you do?

You can attach a single SATA hard disk to the system and configure it as a storage.

One might think that is easy to do using XenCenter since there is a menu entry Storage -> New SR, but no, there you can only add NVS, iSCSI, Hardware HBA or Software FCoE. Local drives aren’t even mentioned.

So you have to resort to the xe command as described here:

  1. Shut down and turn off the computer
  2. Attach the hard disk and boot
  3. On the console use fdisk to get information about the new hard disk
    fdisk -l
    

    Search for the new hard drive in the list. It will be the last on the list and it is indicated as /dev/sdc. c is the position it is in. Generally, it starts at a, and the list continues. You can verify the device path using the SCSI ID in /dev/disk/by-id directory by listing out the contents.
    Note: I wouldn’t rely on the drive being the last one in the list. Definitely verify the drive parameters that fdisk outputs.

  4. Run the following command from the command line interface (in the console or on the a computer with XenCenter installed):
    xe sr-create name-label=<Name of Storage> shared=false device-config:device=<Path of the Storage device> type=lvm content-type=user
    
    • Name of Storage is the name of the Storage Repository you require
    • Path of the Storage device is the path as noted in the preceding tasks, /dev/sdc)
      Now, the installed hard drive is visible in the XenServer Console.

    Note: This works fine if you run it on the server console. If you run it from a remote computer with XenCenter, you need additional parameters to actually connect to the server. In that case insert the following between xe and sr-create:

    -s <server> -u <username> -pw <password>
    
    • server is the server’s name or IP address
    • username is the user to logon (e.g. root)
    • password is the password of the given user

And yes, it worked. After adding this Storage Repository I could install the updates.

%d bloggers like this: